Skip to content

Commit

Permalink
feat: Added option to view exchange information
Browse files Browse the repository at this point in the history
  • Loading branch information
achannarasappa committed Jan 31, 2021
1 parent 919d536 commit 8bf1836
Show file tree
Hide file tree
Showing 7 changed files with 162 additions and 32 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ ticker -w NET,AAPL,TSLA
| |--config|`~/.ticker.yaml`|config with watchlist and positions|
|-i|--interval|`5`|Refresh interval in seconds|
|-w|--watchlist||comma separated list of symbols to watch|
|-e|--extra-info-exchange||display currency, exchange name, and quote delay for each quote |
| |--compact||compact layout without separators between each quote|

## Configuration
Expand Down
26 changes: 16 additions & 10 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,26 @@ import (
)

var (
configPath string
config cli.Config
watchlist string
refreshInterval int
compact bool
rootCmd = &cobra.Command{
configPath string
config cli.Config
watchlist string
refreshInterval int
compact bool
extraInfoExchange bool
extraInfoQuote bool
rootCmd = &cobra.Command{
Use: "ticker",
Short: "Terminal stock ticker and stock gain/loss tracker",
Args: cli.Validate(
&config,
afero.NewOsFs(),
cli.Options{
ConfigPath: &configPath,
RefreshInterval: &refreshInterval,
Watchlist: &watchlist,
Compact: &compact,
ConfigPath: &configPath,
RefreshInterval: &refreshInterval,
Watchlist: &watchlist,
Compact: &compact,
ExtraInfoExchange: &extraInfoExchange,
ExtraInfoQuote: &extraInfoQuote,
},
),
Run: cli.Run(ui.Start(&config)),
Expand All @@ -50,6 +54,8 @@ func init() {
rootCmd.Flags().StringVarP(&watchlist, "watchlist", "w", "", "comma separated list of symbols to watch")
rootCmd.Flags().IntVarP(&refreshInterval, "interval", "i", 0, "refresh interval in seconds")
rootCmd.Flags().BoolVar(&compact, "compact", false, "compact layout without separators between each quote")
rootCmd.Flags().BoolVar(&extraInfoExchange, "extra-info-exchange", false, "display currency, exchange name, and quote delay for each quote")
rootCmd.Flags().BoolVar(&extraInfoQuote, "extra-info-quote", false, "display open price, high, low, and volume for each quote")
}

func initConfig() {
Expand Down
22 changes: 14 additions & 8 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,21 @@ import (
)

type Config struct {
RefreshInterval int `yaml:"interval"`
Watchlist []string `yaml:"watchlist"`
Lots []position.Lot `yaml:"lots"`
Compact bool `yaml:"compact"`
RefreshInterval int `yaml:"interval"`
Watchlist []string `yaml:"watchlist"`
Lots []position.Lot `yaml:"lots"`
Compact bool `yaml:"compact"`
ExtraInfoExchange bool `yaml:"extra-info-exchange"`
ExtraInfoQuote bool `yaml:"extra-info-quote"`
}

type Options struct {
ConfigPath *string
RefreshInterval *int
Watchlist *string
Compact *bool
ConfigPath *string
RefreshInterval *int
Watchlist *string
Compact *bool
ExtraInfoExchange *bool
ExtraInfoQuote *bool
}

func Run(uiStartFn func() error) func(*cobra.Command, []string) {
Expand Down Expand Up @@ -78,6 +82,8 @@ func read(fs afero.Fs, options Options, configFile *Config) (Config, error) {

config.RefreshInterval = getRefreshInterval(*options.RefreshInterval, configFile.RefreshInterval)
config.Compact = getBoolOption(*options.Compact, configFile.Compact)
config.ExtraInfoExchange = getBoolOption(*options.ExtraInfoExchange, configFile.ExtraInfoExchange)
config.ExtraInfoQuote = getBoolOption(*options.ExtraInfoQuote, configFile.ExtraInfoQuote)

return config, err

Expand Down
3 changes: 3 additions & 0 deletions internal/quote/quote.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ type ResponseQuote struct {
ShortName string `json:"shortName"`
Symbol string `json:"symbol"`
MarketState string `json:"marketState"`
Currency string `json:"currency"`
ExchangeName string `json:"fullExchangeName"`
ExchangeDelay float64 `json:"exchangeDataDelayedBy"`
RegularMarketChange float64 `json:"regularMarketChange"`
RegularMarketChangePercent float64 `json:"regularMarketChangePercent"`
RegularMarketPrice float64 `json:"regularMarketPrice"`
Expand Down
74 changes: 66 additions & 8 deletions internal/ui/component/watchlist/watchlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package watchlist
import (
"fmt"
"math"
"strconv"
"strings"
"ticker/internal/position"
"ticker/internal/quote"
Expand All @@ -19,6 +20,8 @@ var (
styleNeutralBold = NewStyle("#d4d4d4", "", true)
styleNeutralFaded = NewStyle("#616161", "", false)
styleLine = NewStyle("#3a3a3a", "", false)
styleTag = NewStyle("#d4d4d4", "#3a3a3a", false)
styleTagEnd = NewStyle("#3a3a3a", "#3a3a3a", false)
stylePricePositive = newStyleFromGradient("#C6FF40", "#779929")
stylePriceNegative = newStyleFromGradient("#FF7940", "#994926")
)
Expand All @@ -28,17 +31,21 @@ const (
)

type Model struct {
Width int
Quotes []quote.Quote
Positions map[string]position.Position
Compact bool
Width int
Quotes []quote.Quote
Positions map[string]position.Position
Compact bool
ExtraInfoExchange bool
ExtraInfoQuote bool
}

// NewModel returns a model with default values.
func NewModel(compact bool) Model {
func NewModel(compact bool, extraInfoExchange bool, extraInfoQuote bool) Model {
return Model{
Width: 80,
Compact: compact,
Width: 80,
Compact: compact,
ExtraInfoExchange: extraInfoExchange,
ExtraInfoQuote: extraInfoQuote,
}
}

Expand All @@ -51,7 +58,17 @@ func (m Model) View() string {
quotes := sortQuotes(m.Quotes)
items := make([]string, 0)
for _, quote := range quotes {
items = append(items, item(quote, m.Positions[quote.Symbol], m.Width))
items = append(
items,
strings.Join(
[]string{
item(quote, m.Positions[quote.Symbol], m.Width),
// extraInfoQuote(m.ExtraInfoQuote, quote, m.Width),
extraInfoExchange(m.ExtraInfoExchange, quote, m.Width),
},
"",
),
)
}

return strings.Join(items, separator(m.Compact, m.Width))
Expand Down Expand Up @@ -113,6 +130,47 @@ func item(q quote.Quote, p position.Position, width int) string {
)
}

func extraInfoExchange(show bool, q quote.Quote, width int) string {
if !show {
return ""
}
return "\n" + Line(
width,
Cell{
Text: "",
Align: RightAlign,
},
Cell{
Text: tagText(q.ExchangeName) + " " + tagText(exchangeDelayText(q.ExchangeDelay)) + " " + tagText(q.Currency),
Align: RightAlign,
},
Cell{
Width: 1,
Text: " ",
Align: RightAlign,
},
)
}

// func extraInfoQuote(show bool, q quote.Quote, width int) string {
// if !show {
// return ""
// }
// return ""
// }

func exchangeDelayText(delay float64) string {
if delay <= 0 {
return "Real-Time"
}

return "Delayed " + strconv.FormatFloat(delay, 'f', 0, 64) + "min"
}

func tagText(text string) string {
return styleTagEnd(" ") + styleTag(text) + styleTagEnd(" ")
}

func marketStateText(q quote.Quote) string {
if q.IsRegularTradingSession {
return styleNeutralFaded(" ⦿ ")
Expand Down
66 changes: 61 additions & 5 deletions internal/ui/component/watchlist/watchlist_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ var _ = Describe("Watchlist", func() {
}
}

m := NewModel(false)
m := NewModel(false, false, false)
m.Width = 80
m.Positions = positionMap
m.Quotes = []Quote{
Expand Down Expand Up @@ -153,7 +153,7 @@ var _ = Describe("Watchlist", func() {
When("there are more than one symbols on the watchlist", func() {
It("should render a watchlist with each symbol", func() {

m := NewModel(false)
m := NewModel(false, false, false)
m.Width = 80
m.Quotes = []Quote{
{
Expand Down Expand Up @@ -220,7 +220,7 @@ var _ = Describe("Watchlist", func() {
When("the compact layout flag is set", func() {
It("should render a watchlist without separators", func() {

m := NewModel(true)
m := NewModel(true, false, false)
m.Quotes = []Quote{
{
ResponseQuote: ResponseQuote{
Expand Down Expand Up @@ -269,16 +269,72 @@ var _ = Describe("Watchlist", func() {
})
})

When("the option for extra exchange information is set", func() {
It("should render extra exchange information", func() {
m := NewModel(true, true, false)
m.Quotes = []Quote{
{
ResponseQuote: ResponseQuote{
Symbol: "BTC-USD",
ShortName: "Bitcoin",
Currency: "USD",
ExchangeName: "Cryptocurrency",
ExchangeDelay: 0,
},
Price: 50000.0,
Change: 10000.0,
ChangePercent: 20.0,
IsActive: true,
IsRegularTradingSession: true,
},
}
expected := strings.Join([]string{
"BTC-USD ⦿ 50000.00",
"Bitcoin ↑ 10000.00 (20.00%)",
" Cryptocurrency Real-Time USD ",
}, "\n")
Expect(removeFormatting(m.View())).To(Equal(expected))
})

When("the exchange has a delay", func() {
It("should render extra exchange information with the delay amount", func() {
m := NewModel(true, true, false)
m.Quotes = []Quote{
{
ResponseQuote: ResponseQuote{
Symbol: "BTC-USD",
ShortName: "Bitcoin",
Currency: "USD",
ExchangeName: "Cryptocurrency",
ExchangeDelay: 15,
},
Price: 50000.0,
Change: 10000.0,
ChangePercent: 20.0,
IsActive: true,
IsRegularTradingSession: true,
},
}
expected := strings.Join([]string{
"BTC-USD ⦿ 50000.00",
"Bitcoin ↑ 10000.00 (20.00%)",
" Cryptocurrency Delayed 15min USD ",
}, "\n")
Expect(removeFormatting(m.View())).To(Equal(expected))
})
})
})

When("no quotes are set", func() {
It("should render an empty watchlist", func() {
m := NewModel(false)
m := NewModel(false, false, false)
Expect(m.View()).To(Equal(""))
})
})

When("the window width is less than the minimum", func() {
It("should render an empty watchlist", func() {
m := NewModel(false)
m := NewModel(false, false, false)
m.Width = 70
Expect(m.View()).To(Equal("Terminal window too narrow to render content\nResize to fix (70/80)"))
})
Expand Down
2 changes: 1 addition & 1 deletion internal/ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ func NewModel(config cli.Config, client *resty.Client) Model {
requestInterval: 3,
getQuotes: quote.GetQuotes(*client, symbols),
getPositions: position.GetPositions(aggregatedLots),
watchlist: watchlist.NewModel(config.Compact),
watchlist: watchlist.NewModel(config.Compact, config.ExtraInfoExchange, config.ExtraInfoQuote),
}
}

Expand Down

0 comments on commit 8bf1836

Please sign in to comment.