From bea5c67759fab4c2a0d8ae24f22397eb771f9ae9 Mon Sep 17 00:00:00 2001 From: Simon Roberts Date: Wed, 18 Aug 2021 11:46:56 +1000 Subject: [PATCH 01/13] When rendering portfolio chart, only append data when resizing array --- cointop/chart.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cointop/chart.go b/cointop/chart.go index a566b75..81f7d09 100644 --- a/cointop/chart.go +++ b/cointop/chart.go @@ -256,10 +256,11 @@ func (ct *Cointop) PortfolioChart() error { for i := range graphData { price := graphData[i] sum := p.Holdings * price - if len(data)-1 >= i { + if i < len(data) { data[i] += sum + } else { + data = append(data, sum) } - data = append(data, sum) } } From 9553ec8a0223c3d6c9c4ea0741ffe68bada1f395 Mon Sep 17 00:00:00 2001 From: Simon Roberts Date: Wed, 18 Aug 2021 11:51:35 +1000 Subject: [PATCH 02/13] When building portfolio slice, include first coin only --- cointop/portfolio.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cointop/portfolio.go b/cointop/portfolio.go index 58ab85c..d46b980 100644 --- a/cointop/portfolio.go +++ b/cointop/portfolio.go @@ -529,12 +529,20 @@ func (ct *Cointop) GetPortfolioSlice() []*Coin { return sliced } +OUTER: for i := range ct.State.allCoins { coin := ct.State.allCoins[i] p, isNew := ct.PortfolioEntry(coin) if isNew { continue } + // check not already found + for j := range sliced { + if coin.Symbol == sliced[j].Symbol { + continue OUTER + } + } + coin.Holdings = p.Holdings balance := coin.Price * p.Holdings balancestr := fmt.Sprintf("%.2f", balance) From 2a9f9952864554ca68af9668b54ab46221dbf1e6 Mon Sep 17 00:00:00 2001 From: Simon Roberts Date: Thu, 19 Aug 2021 09:05:39 +1000 Subject: [PATCH 03/13] Store default chart range in configuration file --- cointop/cointop.go | 1 + cointop/config.go | 69 ++++++++++++++++++++++++++++++---------------- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/cointop/cointop.go b/cointop/cointop.go index 40dccf8..1f16367 100644 --- a/cointop/cointop.go +++ b/cointop/cointop.go @@ -44,6 +44,7 @@ type State struct { coinsTableColumns []string convertMenuVisible bool defaultView string + defaultChartRange string // DEPRECATED: favorites by 'symbol' is deprecated because of collisions. favoritesBySymbol map[string]bool diff --git a/cointop/config.go b/cointop/config.go index 9464e4d..e158768 100644 --- a/cointop/config.go +++ b/cointop/config.go @@ -31,18 +31,19 @@ var possibleConfigPaths = []string{ } type config struct { - Shortcuts map[string]interface{} `toml:"shortcuts"` - Favorites map[string]interface{} `toml:"favorites"` - Portfolio map[string]interface{} `toml:"portfolio"` - PriceAlerts map[string]interface{} `toml:"price_alerts"` - Currency interface{} `toml:"currency"` - DefaultView interface{} `toml:"default_view"` - CoinMarketCap map[string]interface{} `toml:"coinmarketcap"` - API interface{} `toml:"api"` - Colorscheme interface{} `toml:"colorscheme"` - RefreshRate interface{} `toml:"refresh_rate"` - CacheDir interface{} `toml:"cache_dir"` - Table map[string]interface{} `toml:"table"` + Shortcuts map[string]interface{} `toml:"shortcuts"` + Favorites map[string]interface{} `toml:"favorites"` + Portfolio map[string]interface{} `toml:"portfolio"` + PriceAlerts map[string]interface{} `toml:"price_alerts"` + Currency interface{} `toml:"currency"` + DefaultView interface{} `toml:"default_view"` + DefaultChartRange interface{} `toml:"default_chart_range"` + CoinMarketCap map[string]interface{} `toml:"coinmarketcap"` + API interface{} `toml:"api"` + Colorscheme interface{} `toml:"colorscheme"` + RefreshRate interface{} `toml:"refresh_rate"` + CacheDir interface{} `toml:"cache_dir"` + Table map[string]interface{} `toml:"table"` } // SetupConfig loads config file @@ -69,6 +70,9 @@ func (ct *Cointop) SetupConfig() error { if err := ct.loadDefaultViewFromConfig(); err != nil { return err } + if err := ct.loadDefaultChartRangeFromConfig(); err != nil { + return err + } if err := ct.loadAPIKeysFromConfig(); err != nil { return err } @@ -255,6 +259,7 @@ func (ct *Cointop) configToToml() ([]byte, error) { var currencyIfc interface{} = ct.State.currencyConversion var defaultViewIfc interface{} = ct.State.defaultView + var defaultChartRangeIfc interface{} = ct.State.defaultChartRange var colorschemeIfc interface{} = ct.colorschemeName var refreshRateIfc interface{} = uint(ct.State.refreshRate.Seconds()) var cacheDirIfc interface{} = ct.State.cacheDir @@ -289,18 +294,19 @@ func (ct *Cointop) configToToml() ([]byte, error) { tableMapIfc["keep_row_focus_on_sort"] = keepRowFocusOnSortIfc var inputs = &config{ - API: apiChoiceIfc, - Colorscheme: colorschemeIfc, - CoinMarketCap: cmcIfc, - Currency: currencyIfc, - DefaultView: defaultViewIfc, - Favorites: favoritesMapIfc, - RefreshRate: refreshRateIfc, - Shortcuts: shortcutsIfcs, - Portfolio: portfolioIfc, - PriceAlerts: priceAlertsMapIfc, - CacheDir: cacheDirIfc, - Table: tableMapIfc, + API: apiChoiceIfc, + Colorscheme: colorschemeIfc, + CoinMarketCap: cmcIfc, + Currency: currencyIfc, + DefaultView: defaultViewIfc, + DefaultChartRange: defaultChartRangeIfc, + Favorites: favoritesMapIfc, + RefreshRate: refreshRateIfc, + Shortcuts: shortcutsIfcs, + Portfolio: portfolioIfc, + PriceAlerts: priceAlertsMapIfc, + CacheDir: cacheDirIfc, + Table: tableMapIfc, } var b bytes.Buffer @@ -399,6 +405,21 @@ func (ct *Cointop) loadDefaultViewFromConfig() error { return nil } +// LoadDefaultChartRangeFromConfig loads default chart range from config file to struct +func (ct *Cointop) loadDefaultChartRangeFromConfig() error { + ct.debuglog("loadDefaultChartRangeFromConfig()") + if defaultChartRange, ok := ct.config.DefaultChartRange.(string); ok { + // validate configured value + _, present := ct.chartRangesMap[defaultChartRange] + if !present { + defaultChartRange = DefaultChartRange + } + ct.State.defaultChartRange = defaultChartRange + ct.State.selectedChartRange = defaultChartRange + } + return nil +} + // LoadAPIKeysFromConfig loads API keys from config file to struct func (ct *Cointop) loadAPIKeysFromConfig() error { ct.debuglog("loadAPIKeysFromConfig()") From 719f0cc3cb39311f1e6c2a18ff60f64259e828bc Mon Sep 17 00:00:00 2001 From: Simon Roberts Date: Thu, 19 Aug 2021 09:06:16 +1000 Subject: [PATCH 04/13] Simple documentation for default_chart_range --- docs/content/config.md | 1 + docs/content/faq.md | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/docs/content/config.md b/docs/content/config.md index 275aea0..6c6dac2 100644 --- a/docs/content/config.md +++ b/docs/content/config.md @@ -39,6 +39,7 @@ You can configure the actions you want for each key in `config.toml`: ```toml currency = "USD" default_view = "" +default_chart_range = "1Y" api = "coingecko" colorscheme = "cointop" refresh_rate = 60 diff --git a/docs/content/faq.md b/docs/content/faq.md index 65c42bc..605e2f3 100644 --- a/docs/content/faq.md +++ b/docs/content/faq.md @@ -328,6 +328,12 @@ draft: false In the config file, set `default_view = "default"` +## How do I set the default chart range? + + In the config file, set `default_chart_range = "3M"` + + Supported date ranges are `All Time`, `YTD`, `1Y`, `6M`, `3M`, `1M`, `7D`, `3D`, `24H`. + ## How can use a different config file other than the default? Run cointop with the `--config` flag, eg `cointop --config="/path/to/config.toml"`, to use the specified file as the config. From 4828c3e014a64bf71e2c794164c1d948835f7db1 Mon Sep 17 00:00:00 2001 From: Simon Roberts Date: Thu, 19 Aug 2021 11:54:02 +1000 Subject: [PATCH 05/13] Use the highest-rank coin to calculate PortfolioSlice --- cointop/portfolio.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cointop/portfolio.go b/cointop/portfolio.go index d46b980..93a89dc 100644 --- a/cointop/portfolio.go +++ b/cointop/portfolio.go @@ -537,9 +537,14 @@ OUTER: continue } // check not already found + updateSlice := -1 for j := range sliced { if coin.Symbol == sliced[j].Symbol { - continue OUTER + if coin.Rank >= sliced[j].Rank { + continue OUTER // skip updates from lower-ranked coins + } + updateSlice = j // update this later + break } } @@ -551,7 +556,12 @@ OUTER: } balance, _ = strconv.ParseFloat(balancestr, 64) coin.Balance = balance - sliced = append(sliced, coin) + if updateSlice == -1 { + sliced = append(sliced, coin) + } else { + sliced[updateSlice] = coin + } + } sort.Slice(sliced, func(i, j int) bool { From 6ec915abe92e2cd8a4457cb0097ae421f44e4986 Mon Sep 17 00:00:00 2001 From: Simon Roberts Date: Thu, 19 Aug 2021 16:40:54 +1000 Subject: [PATCH 06/13] Include DefaultChartRange in default config --- cointop/cointop.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cointop/cointop.go b/cointop/cointop.go index 1f16367..17a9843 100644 --- a/cointop/cointop.go +++ b/cointop/cointop.go @@ -244,6 +244,7 @@ func NewCointop(config *Config) (*Cointop, error) { cacheDir: DefaultCacheDir, coinsTableColumns: DefaultCoinTableHeaders, currencyConversion: DefaultCurrency, + defaultChartRange: DefaultChartRange, // DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility. favoritesBySymbol: make(map[string]bool), favorites: make(map[string]bool), From ac93b8fbe08bd13a27dbf5f331589f5607022133 Mon Sep 17 00:00:00 2001 From: Miguel Mota Date: Sun, 22 Aug 2021 04:27:41 -0700 Subject: [PATCH 07/13] Return error if default chart range is invalid --- cointop/config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cointop/config.go b/cointop/config.go index e158768..0f379c2 100644 --- a/cointop/config.go +++ b/cointop/config.go @@ -410,9 +410,9 @@ func (ct *Cointop) loadDefaultChartRangeFromConfig() error { ct.debuglog("loadDefaultChartRangeFromConfig()") if defaultChartRange, ok := ct.config.DefaultChartRange.(string); ok { // validate configured value - _, present := ct.chartRangesMap[defaultChartRange] - if !present { - defaultChartRange = DefaultChartRange + _, ok := ct.chartRangesMap[defaultChartRange] + if !ok { + return fmt.Errorf("invalid default chart range %q. Valid ranges are: %s", defaultChartRange, strings.Join(ChartRanges(), ",")) } ct.State.defaultChartRange = defaultChartRange ct.State.selectedChartRange = defaultChartRange From a34417ad61518d570afcd6a12f12ded2d0556fd8 Mon Sep 17 00:00:00 2001 From: Simon Roberts Date: Wed, 18 Aug 2021 11:51:35 +1000 Subject: [PATCH 08/13] When building portfolio slice, include first coin only --- cointop/portfolio.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/cointop/portfolio.go b/cointop/portfolio.go index 58ab85c..d46b980 100644 --- a/cointop/portfolio.go +++ b/cointop/portfolio.go @@ -529,12 +529,20 @@ func (ct *Cointop) GetPortfolioSlice() []*Coin { return sliced } +OUTER: for i := range ct.State.allCoins { coin := ct.State.allCoins[i] p, isNew := ct.PortfolioEntry(coin) if isNew { continue } + // check not already found + for j := range sliced { + if coin.Symbol == sliced[j].Symbol { + continue OUTER + } + } + coin.Holdings = p.Holdings balance := coin.Price * p.Holdings balancestr := fmt.Sprintf("%.2f", balance) From b078dbd2f67cd1483f0207e0147f537710fdf679 Mon Sep 17 00:00:00 2001 From: Simon Roberts Date: Thu, 19 Aug 2021 11:54:02 +1000 Subject: [PATCH 09/13] Use the highest-rank coin to calculate PortfolioSlice --- cointop/portfolio.go | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/cointop/portfolio.go b/cointop/portfolio.go index d46b980..93a89dc 100644 --- a/cointop/portfolio.go +++ b/cointop/portfolio.go @@ -537,9 +537,14 @@ OUTER: continue } // check not already found + updateSlice := -1 for j := range sliced { if coin.Symbol == sliced[j].Symbol { - continue OUTER + if coin.Rank >= sliced[j].Rank { + continue OUTER // skip updates from lower-ranked coins + } + updateSlice = j // update this later + break } } @@ -551,7 +556,12 @@ OUTER: } balance, _ = strconv.ParseFloat(balancestr, 64) coin.Balance = balance - sliced = append(sliced, coin) + if updateSlice == -1 { + sliced = append(sliced, coin) + } else { + sliced[updateSlice] = coin + } + } sort.Slice(sliced, func(i, j int) bool { From b83d15cdc106290d7d7d7b6c07c8f5d25224cd5c Mon Sep 17 00:00:00 2001 From: Simon Roberts Date: Thu, 19 Aug 2021 09:05:39 +1000 Subject: [PATCH 10/13] Store default chart range in configuration file --- cointop/cointop.go | 1 + cointop/config.go | 69 ++++++++++++++++++++++++++++++---------------- 2 files changed, 46 insertions(+), 24 deletions(-) diff --git a/cointop/cointop.go b/cointop/cointop.go index 40dccf8..1f16367 100644 --- a/cointop/cointop.go +++ b/cointop/cointop.go @@ -44,6 +44,7 @@ type State struct { coinsTableColumns []string convertMenuVisible bool defaultView string + defaultChartRange string // DEPRECATED: favorites by 'symbol' is deprecated because of collisions. favoritesBySymbol map[string]bool diff --git a/cointop/config.go b/cointop/config.go index 9464e4d..e158768 100644 --- a/cointop/config.go +++ b/cointop/config.go @@ -31,18 +31,19 @@ var possibleConfigPaths = []string{ } type config struct { - Shortcuts map[string]interface{} `toml:"shortcuts"` - Favorites map[string]interface{} `toml:"favorites"` - Portfolio map[string]interface{} `toml:"portfolio"` - PriceAlerts map[string]interface{} `toml:"price_alerts"` - Currency interface{} `toml:"currency"` - DefaultView interface{} `toml:"default_view"` - CoinMarketCap map[string]interface{} `toml:"coinmarketcap"` - API interface{} `toml:"api"` - Colorscheme interface{} `toml:"colorscheme"` - RefreshRate interface{} `toml:"refresh_rate"` - CacheDir interface{} `toml:"cache_dir"` - Table map[string]interface{} `toml:"table"` + Shortcuts map[string]interface{} `toml:"shortcuts"` + Favorites map[string]interface{} `toml:"favorites"` + Portfolio map[string]interface{} `toml:"portfolio"` + PriceAlerts map[string]interface{} `toml:"price_alerts"` + Currency interface{} `toml:"currency"` + DefaultView interface{} `toml:"default_view"` + DefaultChartRange interface{} `toml:"default_chart_range"` + CoinMarketCap map[string]interface{} `toml:"coinmarketcap"` + API interface{} `toml:"api"` + Colorscheme interface{} `toml:"colorscheme"` + RefreshRate interface{} `toml:"refresh_rate"` + CacheDir interface{} `toml:"cache_dir"` + Table map[string]interface{} `toml:"table"` } // SetupConfig loads config file @@ -69,6 +70,9 @@ func (ct *Cointop) SetupConfig() error { if err := ct.loadDefaultViewFromConfig(); err != nil { return err } + if err := ct.loadDefaultChartRangeFromConfig(); err != nil { + return err + } if err := ct.loadAPIKeysFromConfig(); err != nil { return err } @@ -255,6 +259,7 @@ func (ct *Cointop) configToToml() ([]byte, error) { var currencyIfc interface{} = ct.State.currencyConversion var defaultViewIfc interface{} = ct.State.defaultView + var defaultChartRangeIfc interface{} = ct.State.defaultChartRange var colorschemeIfc interface{} = ct.colorschemeName var refreshRateIfc interface{} = uint(ct.State.refreshRate.Seconds()) var cacheDirIfc interface{} = ct.State.cacheDir @@ -289,18 +294,19 @@ func (ct *Cointop) configToToml() ([]byte, error) { tableMapIfc["keep_row_focus_on_sort"] = keepRowFocusOnSortIfc var inputs = &config{ - API: apiChoiceIfc, - Colorscheme: colorschemeIfc, - CoinMarketCap: cmcIfc, - Currency: currencyIfc, - DefaultView: defaultViewIfc, - Favorites: favoritesMapIfc, - RefreshRate: refreshRateIfc, - Shortcuts: shortcutsIfcs, - Portfolio: portfolioIfc, - PriceAlerts: priceAlertsMapIfc, - CacheDir: cacheDirIfc, - Table: tableMapIfc, + API: apiChoiceIfc, + Colorscheme: colorschemeIfc, + CoinMarketCap: cmcIfc, + Currency: currencyIfc, + DefaultView: defaultViewIfc, + DefaultChartRange: defaultChartRangeIfc, + Favorites: favoritesMapIfc, + RefreshRate: refreshRateIfc, + Shortcuts: shortcutsIfcs, + Portfolio: portfolioIfc, + PriceAlerts: priceAlertsMapIfc, + CacheDir: cacheDirIfc, + Table: tableMapIfc, } var b bytes.Buffer @@ -399,6 +405,21 @@ func (ct *Cointop) loadDefaultViewFromConfig() error { return nil } +// LoadDefaultChartRangeFromConfig loads default chart range from config file to struct +func (ct *Cointop) loadDefaultChartRangeFromConfig() error { + ct.debuglog("loadDefaultChartRangeFromConfig()") + if defaultChartRange, ok := ct.config.DefaultChartRange.(string); ok { + // validate configured value + _, present := ct.chartRangesMap[defaultChartRange] + if !present { + defaultChartRange = DefaultChartRange + } + ct.State.defaultChartRange = defaultChartRange + ct.State.selectedChartRange = defaultChartRange + } + return nil +} + // LoadAPIKeysFromConfig loads API keys from config file to struct func (ct *Cointop) loadAPIKeysFromConfig() error { ct.debuglog("loadAPIKeysFromConfig()") From 142777d965b3ecd12b96b31dede13323195efcc8 Mon Sep 17 00:00:00 2001 From: Simon Roberts Date: Thu, 19 Aug 2021 09:06:16 +1000 Subject: [PATCH 11/13] Simple documentation for default_chart_range --- docs/content/config.md | 1 + docs/content/faq.md | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/docs/content/config.md b/docs/content/config.md index 275aea0..6c6dac2 100644 --- a/docs/content/config.md +++ b/docs/content/config.md @@ -39,6 +39,7 @@ You can configure the actions you want for each key in `config.toml`: ```toml currency = "USD" default_view = "" +default_chart_range = "1Y" api = "coingecko" colorscheme = "cointop" refresh_rate = 60 diff --git a/docs/content/faq.md b/docs/content/faq.md index 65c42bc..605e2f3 100644 --- a/docs/content/faq.md +++ b/docs/content/faq.md @@ -328,6 +328,12 @@ draft: false In the config file, set `default_view = "default"` +## How do I set the default chart range? + + In the config file, set `default_chart_range = "3M"` + + Supported date ranges are `All Time`, `YTD`, `1Y`, `6M`, `3M`, `1M`, `7D`, `3D`, `24H`. + ## How can use a different config file other than the default? Run cointop with the `--config` flag, eg `cointop --config="/path/to/config.toml"`, to use the specified file as the config. From e60bc6dcd6ea615f866c2989d76b1f766a92984f Mon Sep 17 00:00:00 2001 From: Simon Roberts Date: Thu, 19 Aug 2021 16:40:54 +1000 Subject: [PATCH 12/13] Include DefaultChartRange in default config --- cointop/cointop.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cointop/cointop.go b/cointop/cointop.go index 1f16367..17a9843 100644 --- a/cointop/cointop.go +++ b/cointop/cointop.go @@ -244,6 +244,7 @@ func NewCointop(config *Config) (*Cointop, error) { cacheDir: DefaultCacheDir, coinsTableColumns: DefaultCoinTableHeaders, currencyConversion: DefaultCurrency, + defaultChartRange: DefaultChartRange, // DEPRECATED: favorites by 'symbol' is deprecated because of collisions. Kept for backward compatibility. favoritesBySymbol: make(map[string]bool), favorites: make(map[string]bool), From 28a7bfbbd9ccb658f92999a003a2e77a7faff4d3 Mon Sep 17 00:00:00 2001 From: Miguel Mota Date: Sun, 22 Aug 2021 04:27:41 -0700 Subject: [PATCH 13/13] Return error if default chart range is invalid --- cointop/config.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cointop/config.go b/cointop/config.go index e158768..0f379c2 100644 --- a/cointop/config.go +++ b/cointop/config.go @@ -410,9 +410,9 @@ func (ct *Cointop) loadDefaultChartRangeFromConfig() error { ct.debuglog("loadDefaultChartRangeFromConfig()") if defaultChartRange, ok := ct.config.DefaultChartRange.(string); ok { // validate configured value - _, present := ct.chartRangesMap[defaultChartRange] - if !present { - defaultChartRange = DefaultChartRange + _, ok := ct.chartRangesMap[defaultChartRange] + if !ok { + return fmt.Errorf("invalid default chart range %q. Valid ranges are: %s", defaultChartRange, strings.Join(ChartRanges(), ",")) } ct.State.defaultChartRange = defaultChartRange ct.State.selectedChartRange = defaultChartRange