Skip to content

Commit

Permalink
feat: added summary of all positions i.e. totals
Browse files Browse the repository at this point in the history
  • Loading branch information
achannarasappa committed Feb 4, 2021
1 parent f406226 commit fc8ba9f
Show file tree
Hide file tree
Showing 9 changed files with 223 additions and 29 deletions.
9 changes: 6 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ var (
separate bool
extraInfoExchange bool
extraInfoFundamentals bool
proxy string
proxy string
showSummary bool
err error
rootCmd = &cobra.Command{
Use: "ticker",
Expand All @@ -33,7 +34,8 @@ var (
Separate: &separate,
ExtraInfoExchange: &extraInfoExchange,
ExtraInfoFundamentals: &extraInfoFundamentals,
Proxy: &proxy,
ShowSummary: &showSummary,
Proxy: &proxy,
},
err,
),
Expand All @@ -56,7 +58,8 @@ func init() {
rootCmd.Flags().BoolVar(&separate, "show-separator", false, "layout with separators between each quote")
rootCmd.Flags().BoolVar(&extraInfoExchange, "show-tags", false, "display currency, exchange name, and quote delay for each quote")
rootCmd.Flags().BoolVar(&extraInfoFundamentals, "show-fundamentals", false, "display open price, high, low, and volume for each quote")
rootCmd.Flags().StringVar(&proxy, "proxy", "", "proxy URL for requests (default is none)")
rootCmd.Flags().BoolVar(&showSummary, "show-summary", false, "display summary of total gain and loss for positions")
rootCmd.Flags().StringVar(&proxy, "proxy", "", "proxy URL for requests (default is none)")
}

func initConfig() {
Expand Down
3 changes: 3 additions & 0 deletions internal/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type Config struct {
Separate bool `yaml:"show-separator"`
ExtraInfoExchange bool `yaml:"show-tags"`
ExtraInfoFundamentals bool `yaml:"show-fundamentals"`
ShowSummary bool `yaml:"show-summary"`
Proxy string `yaml:"proxy"`
}

Expand All @@ -30,6 +31,7 @@ type Options struct {
Separate *bool
ExtraInfoExchange *bool
ExtraInfoFundamentals *bool
ShowSummary *bool
Proxy *string
}

Expand Down Expand Up @@ -92,6 +94,7 @@ func mergeConfig(config Config, options Options) Config {
config.Separate = getBoolOption(*options.Separate, config.Separate)
config.ExtraInfoExchange = getBoolOption(*options.ExtraInfoExchange, config.ExtraInfoExchange)
config.ExtraInfoFundamentals = getBoolOption(*options.ExtraInfoFundamentals, config.ExtraInfoFundamentals)
config.ShowSummary = getBoolOption(*options.ShowSummary, config.ShowSummary)
config.Proxy = getProxy(*options.Proxy, config.Proxy)

return config
Expand Down
47 changes: 47 additions & 0 deletions internal/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ var _ = Describe("Cli", func() {
separate bool
extraInfoExchange bool
extraInfoFundamentals bool
showSummary bool
proxy string
)

Expand All @@ -79,6 +80,7 @@ var _ = Describe("Cli", func() {
Separate: &separate,
ExtraInfoExchange: &extraInfoExchange,
ExtraInfoFundamentals: &extraInfoFundamentals,
ShowSummary: &showSummary,
Proxy: &proxy,
}
watchlist = "GME,BB"
Expand All @@ -87,6 +89,7 @@ var _ = Describe("Cli", func() {
separate = false
extraInfoExchange = false
extraInfoFundamentals = false
showSummary = false
fs = afero.NewMemMapFs()
//nolint:errcheck
fs.MkdirAll("./", 0755)
Expand Down Expand Up @@ -389,6 +392,50 @@ var _ = Describe("Cli", func() {
})
})
})

Describe("show-summary option", func() {
When("show-summary flag is set as a cli argument", func() {
It("should set the config to the cli argument value", func() {
showSummary = true
inputConfig := cli.Config{}
outputErr := Validate(&inputConfig, fs, options, nil)(&cobra.Command{}, []string{})
Expect(outputErr).To(BeNil())
Expect(inputConfig.ShowSummary).To(Equal(true))
})

When("the config file also has a show-summary flag defined", func() {
It("should set the show-summary flag from the cli argument", func() {
showSummary = true
inputConfig := cli.Config{
ShowSummary: false,
}
outputErr := Validate(&inputConfig, fs, options, nil)(&cobra.Command{}, []string{})
Expect(outputErr).To(BeNil())
Expect(inputConfig.ShowSummary).To(Equal(true))
})
})
})

When("show-summary flag is set in the config file", func() {
It("should set the config to the cli argument value", func() {
inputConfig := cli.Config{
ShowSummary: true,
}
outputErr := Validate(&inputConfig, fs, options, nil)(&cobra.Command{}, []string{})
Expect(outputErr).To(BeNil())
Expect(inputConfig.ShowSummary).To(Equal(true))
})
})

When("show-summary flag is not set", func() {
It("should disable the option", func() {
inputConfig := cli.Config{}
outputErr := Validate(&inputConfig, fs, options, nil)(&cobra.Command{}, []string{})
Expect(outputErr).To(BeNil())
Expect(inputConfig.ShowSummary).To(Equal(false))
})
})
})
})

//nolint:errcheck
Expand Down
50 changes: 50 additions & 0 deletions internal/ui/component/summary/summary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package summary

import (
"strings"
"ticker/internal/position"
. "ticker/internal/ui/util"
)

type Model struct {
Width int
Summary position.PositionSummary
}

// NewModel returns a model with default values.
func NewModel() Model {
return Model{
Width: 80,
}
}

func (m Model) View() string {

if m.Width < 80 {
return ""
}

return strings.Join([]string{
StyleNeutralFaded("Day:"),
quoteChangeText(m.Summary.DayChange, m.Summary.DayChangePercent),
StyleNeutralFaded("•"),
StyleNeutralFaded("Change:"),
quoteChangeText(m.Summary.Change, m.Summary.ChangePercent),
StyleNeutralFaded("•"),
StyleNeutralFaded("Value:"),
ValueText(m.Summary.Value),
}, " ") + "\n" + StyleLine(strings.Repeat("━", m.Width))

}

func quoteChangeText(change float64, changePercent float64) string {
if change == 0.0 {
return StyleNeutralFaded(ConvertFloatToString(change) + " (" + ConvertFloatToString(changePercent) + "%)")
}

if change > 0.0 {
return StylePricePositive(changePercent)("↑ " + ConvertFloatToString(change) + " (" + ConvertFloatToString(changePercent) + "%)")
}

return StylePriceNegative(changePercent)("↓ " + ConvertFloatToString(change) + " (" + ConvertFloatToString(changePercent) + "%)")
}
15 changes: 15 additions & 0 deletions internal/ui/component/summary/summary_suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package summary_test

import (
"testing"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/format"
)

func TestSummary(t *testing.T) {
format.TruncatedDiff = false
RegisterFailHandler(Fail)
RunSpecs(t, "Summary Suite")
}
52 changes: 52 additions & 0 deletions internal/ui/component/summary/summary_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package summary_test

import (
"strings"
"ticker/internal/position"
. "ticker/internal/ui/component/summary"

"github.com/acarl005/stripansi"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

func removeFormatting(text string) string {
return stripansi.Strip(text)
}

var _ = Describe("Summary", func() {

It("should render a summary", func() {
m := NewModel()
m.Summary = position.PositionSummary{
Value: 10000,
Cost: 1000,
Change: 9000,
DayChange: 100.0,
ChangePercent: 1000.0,
DayChangePercent: 10.0,
}
Expect(removeFormatting(m.View())).To(Equal(strings.Join([]string{
"Day: ↑ 100.00 (10.00%) • Change: ↑ 9000.00 (1000.00%) • Value: 10000.00",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
}, "\n")))
})

When("no quotes are set", func() {
It("should render an empty summary", func() {
m := NewModel()
Expect(removeFormatting(m.View())).To(Equal(strings.Join([]string{
"Day: 0.00 (0.00%) • Change: 0.00 (0.00%) • Value: ",
"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
}, "\n")))
})
})

When("the window width is less than the minimum", func() {
It("should render an empty summary", func() {
m := NewModel()
m.Width = 10
Expect(m.View()).To(Equal(""))
})
})
})
24 changes: 22 additions & 2 deletions internal/ui/component/watchlist/watchlist.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,12 @@ func item(q quote.Quote, p position.Position, width int) string {
},
Cell{
Width: 25,
Text: ValueChangeText(p.DayChange, p.DayChangePercent),
Text: valueChangeText(p.DayChange, p.DayChangePercent),
Align: RightAlign,
},
Cell{
Width: 25,
Text: QuoteChangeText(q.Change, q.ChangePercent),
Text: quoteChangeText(q.Change, q.ChangePercent),
Align: RightAlign,
},
),
Expand Down Expand Up @@ -177,6 +177,26 @@ func marketStateText(q quote.Quote) string {
return ""
}

func valueChangeText(change float64, changePercent float64) string {
if change == 0.0 {
return ""
}

return quoteChangeText(change, changePercent)
}

func quoteChangeText(change float64, changePercent float64) string {
if change == 0.0 {
return StyleNeutralFaded(" " + ConvertFloatToString(change) + " (" + ConvertFloatToString(changePercent) + "%)")
}

if change > 0.0 {
return StylePricePositive(changePercent)("↑ " + ConvertFloatToString(change) + " (" + ConvertFloatToString(changePercent) + "%)")
}

return StylePriceNegative(changePercent)("↓ " + ConvertFloatToString(change) + " (" + ConvertFloatToString(changePercent) + "%)")
}

// Sort by change percent and keep all inactive quotes at the end
func sortQuotes(q []quote.Quote) []quote.Quote {
if len(q) <= 0 {
Expand Down
32 changes: 28 additions & 4 deletions internal/ui/ui.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package ui

import (
"fmt"
"strings"
"ticker/internal/cli"
"ticker/internal/position"
"ticker/internal/quote"
"ticker/internal/ui/component/summary"
"ticker/internal/ui/component/watchlist"
"time"

Expand All @@ -22,16 +24,18 @@ var (
)

const (
verticalMargins = 1
footerHeight = 1
)

type Model struct {
ready bool
headerHeight int
getQuotes func() []quote.Quote
getPositions func([]quote.Quote) map[string]position.Position
requestInterval int
viewport viewport.Model
watchlist watchlist.Model
summary summary.Model
lastUpdateTime string
}

Expand All @@ -55,11 +59,13 @@ func NewModel(config cli.Config, client *resty.Client) Model {
symbols := position.GetSymbols(config.Watchlist, aggregatedLots)

return Model{
headerHeight: getVerticalMargin(config),
ready: false,
requestInterval: 3,
getQuotes: quote.GetQuotes(*client, symbols),
getPositions: position.GetPositions(aggregatedLots),
watchlist: watchlist.NewModel(config.Separate, config.ExtraInfoExchange, config.ExtraInfoFundamentals),
summary: summary.NewModel(),
}
}

Expand Down Expand Up @@ -93,7 +99,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {

case tea.WindowSizeMsg:
m.watchlist.Width = msg.Width
viewportHeight := msg.Height - verticalMargins
m.summary.Width = msg.Width
viewportHeight := msg.Height - m.headerHeight - footerHeight

if !m.ready {
m.viewport = viewport.Model{Width: msg.Width, Height: viewportHeight}
Expand All @@ -106,8 +113,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.viewport.SetContent(m.watchlist.View())

case QuoteMsg:
positions := m.getPositions(msg.quotes)
m.watchlist.Quotes = msg.quotes
m.watchlist.Positions = m.getPositions(msg.quotes)
m.watchlist.Positions = positions
m.summary.Summary = position.GetPositionSummary(positions)
m.lastUpdateTime = msg.time
if m.ready {
m.viewport.SetContent(m.watchlist.View())
Expand All @@ -126,7 +135,14 @@ func (m Model) View() string {
return "\n Initalizing..."
}

return fmt.Sprintf("%s\n%s", m.viewport.View(), footer(m.viewport.Width, m.lastUpdateTime))
return strings.Join(
[]string{
m.summary.View(),
m.viewport.View(),
footer(m.viewport.Width, m.lastUpdateTime),
},
"\n",
)
}

func footer(width int, time string) string {
Expand All @@ -152,3 +168,11 @@ func footer(width int, time string) string {
)

}

func getVerticalMargin(config cli.Config) int {
if config.ShowSummary {
return 2
}

return 0
}
Loading

0 comments on commit fc8ba9f

Please sign in to comment.