diff --git a/cointop/config.go b/cointop/config.go index 066d326..40b7af3 100644 --- a/cointop/config.go +++ b/cointop/config.go @@ -647,10 +647,8 @@ func (ct *Cointop) loadPortfolioHoldingsFromConfig(holdingsIfc []interface{}) er buyPrice := 0.0 if len(tupleIfc) >= 3 { - if parsePrice, err := ct.InterfaceToFloat64(tupleIfc[2]); err != nil { + if buyPrice, err = ct.InterfaceToFloat64(tupleIfc[2]); err != nil { return err - } else { - buyPrice = parsePrice } } diff --git a/cointop/conversion.go b/cointop/conversion.go index 4bcc18a..51756c7 100644 --- a/cointop/conversion.go +++ b/cointop/conversion.go @@ -302,19 +302,19 @@ func CurrencySymbol(currency string) string { return "?" } -func (ct *Cointop) Convert(convertFrom string, convertTo string, amount float64) (float64, error) { +// Convert converts an amount to another currency type +func (ct *Cointop) Convert(convertFrom, convertTo string, amount float64) (float64, error) { convertFrom = strings.ToLower(convertFrom) convertTo = strings.ToLower(convertTo) - var rate float64 if convertFrom == convertTo { - rate = 1.0 - } else { - crate, err := ct.api.GetExchangeRate(convertFrom, convertTo, true) - if err != nil { - return 0, err - } - rate = crate + return amount, nil + } + + rate, err := ct.api.GetExchangeRate(convertFrom, convertTo, true) + if err != nil { + return 0, err } + return rate * amount, nil } diff --git a/cointop/keybindings.go b/cointop/keybindings.go index 04af301..b220901 100644 --- a/cointop/keybindings.go +++ b/cointop/keybindings.go @@ -328,9 +328,9 @@ func (ct *Cointop) SetKeybindingAction(shortcutKey string, action string) error case "sort_column_cost": fn = ct.Sortfn("cost", true) case "sort_column_pnl": - fn = ct.Sortfn("profit", true) + fn = ct.Sortfn("pnl", true) case "sort_column_pnl_percent": - fn = ct.Sortfn("profit_percent", true) + fn = ct.Sortfn("pnl_percent", true) default: fn = ct.Keyfn(ct.Noop) } diff --git a/cointop/portfolio.go b/cointop/portfolio.go index 3b3be51..61641fd 100644 --- a/cointop/portfolio.go +++ b/cointop/portfolio.go @@ -37,8 +37,8 @@ var SupportedPortfolioTableHeaders = []string{ "last_updated", "cost_price", "cost", - "profit", - "profit_percent", + "pnl", + "pnl_percent", } // DefaultPortfolioTableHeaders are the default portfolio table header columns @@ -53,12 +53,23 @@ var DefaultPortfolioTableHeaders = []string{ "24h_change", "7d_change", "percent_holdings", + "cost_price", + "cost", + "pnl", + "pnl_percent", "last_updated", } // HiddenBalanceChars are the characters to show when hidding balances var HiddenBalanceChars = "********" +var costColumns = map[string]bool{ + "cost_price": true, + "cost": true, + "pnl": true, + "pnl_percent": true, +} + // ValidPortfolioTableHeader returns the portfolio table headers func (ct *Cointop) ValidPortfolioTableHeader(name string) bool { for _, v := range SupportedPortfolioTableHeaders { @@ -84,6 +95,25 @@ func (ct *Cointop) GetPortfolioTable() *table.Table { headers := ct.GetPortfolioTableHeaders() ct.ClearSyncMap(&ct.State.tableColumnWidths) ct.ClearSyncMap(&ct.State.tableColumnAlignLeft) + + displayCostColumns := false + for _, coin := range ct.State.coins { + if coin.BuyPrice > 0 && coin.BuyCurrency != "" { + displayCostColumns = true + break + } + } + + if !displayCostColumns { + filtered := make([]string, 0) + for _, header := range headers { + if _, ok := costColumns[header]; !ok { + filtered = append(filtered, header) + } + } + headers = filtered + } + for _, coin := range ct.State.coins { leftMargin := 1 rightMargin := 1 @@ -332,7 +362,6 @@ func (ct *Cointop) GetPortfolioTable() *table.Table { cost = costPrice * coin.Holdings } } - // text := ct.FormatPrice(cost) text := humanize.FixedMonetaryf(cost, 2) if ct.State.hidePortfolioBalances { text = HiddenBalanceChars @@ -352,7 +381,7 @@ func (ct *Cointop) GetPortfolioTable() *table.Table { Color: ct.colorscheme.TableColumnPrice, Text: text, }) - case "profit": + case "pnl": text := "" colorProfit := ct.colorscheme.TableColumnChange if coin.BuyPrice > 0 && coin.BuyCurrency != "" { @@ -385,7 +414,7 @@ func (ct *Cointop) GetPortfolioTable() *table.Table { Color: colorProfit, Text: text, }) - case "profit_percent": + case "pnl_percent": profitPercent := 0.0 if coin.BuyPrice > 0 && coin.BuyCurrency != "" { costPrice, err := ct.Convert(coin.BuyCurrency, ct.State.currencyConversion, coin.BuyPrice) @@ -812,8 +841,7 @@ func (ct *Cointop) PrintHoldingsTable(options *TablePrintOptions) error { records := make([][]string, len(holdings)) symbol := ct.CurrencySymbol() - // TODO: buy_price, buy_currency, profit, profit_percent, etc - headers := []string{"name", "symbol", "price", "holdings", "balance", "24h%", "%holdings"} + headers := []string{"name", "symbol", "price", "holdings", "balance", "24h%", "%holdings", "buy_price", "buy_currency", "pnl", "pnl_percent"} if len(filterCols) > 0 { for _, col := range filterCols { valid := false diff --git a/cointop/sort.go b/cointop/sort.go index fd596be..f45b2f7 100644 --- a/cointop/sort.go +++ b/cointop/sort.go @@ -72,9 +72,9 @@ func (ct *Cointop) Sort(sortBy string, desc bool, list []*Coin, renderHeaders bo return a.BuyPrice < b.BuyPrice case "cost": return (a.BuyPrice * a.Holdings) < (b.BuyPrice * b.Holdings) // TODO: convert? - case "profit": + case "pnl": return (a.Price - a.BuyPrice) < (b.Price - b.BuyPrice) - case "profit_percent": + case "pnl_percent": return (a.Price - a.BuyPrice) < (b.Price - b.BuyPrice) default: return a.Rank < b.Rank diff --git a/cointop/table_header.go b/cointop/table_header.go index c526c47..cc71b24 100644 --- a/cointop/table_header.go +++ b/cointop/table_header.go @@ -133,17 +133,17 @@ var HeaderColumns = map[string]*HeaderColumn{ }, "cost": { Slug: "cost", - Label: "cost[!]", + Label: "[!]cost", PlainLabel: "cost", }, - "profit": { - Slug: "profit", - Label: "PNL[@]", + "pnl": { + Slug: "pnl", + Label: "[@]PNL", PlainLabel: "PNL", }, - "profit_percent": { - Slug: "profit_percent", - Label: "PNL%[#]", + "pnl_percent": { + Slug: "pnl_percent", + Label: "[#]PNL%", PlainLabel: "PNL%", }, } @@ -231,7 +231,7 @@ func (ct *Cointop) UpdateTableHeader() error { } leftAlign := ct.GetTableColumnAlignLeft(col) switch col { - case "price", "balance", "profit", "cost": + case "price", "balance", "pnl", "cost": label = fmt.Sprintf("%s%s", ct.CurrencySymbol(), label) } if leftAlign { diff --git a/docs/content/faq.md b/docs/content/faq.md index 9da8352..cbe8547 100644 --- a/docs/content/faq.md +++ b/docs/content/faq.md @@ -186,11 +186,11 @@ draft: false ## How do I include buy/cost price in my portfolio? - Currently there is no UI for this. If you want to include the cost of your coins in the Portfolio screen, you will need to edit your config.toml + Currently there is no UI for this. If you want to include the cost of your coins in the Portfolio screen, you will need to edit your config.toml - Each coin consists of four values: coin name, coin amount, cost-price, cost-currency. + Each coin consists of four values: coin name, coin amount, cost-price, cost-currency. - For example, the following configuration includes 100 ALGO at USD1.95 each; and 0.1 BTC at AUD50100.83 each. + For example, the following configuration includes 100 ALGO at USD1.95 each; and 0.1 BTC at AUD50100.83 each. ```toml holdings = [["Algorand", "100", "1.95", "USD"], ["Bitcoin", "0.1", "50100.83", "AUD"]] @@ -200,12 +200,12 @@ draft: false - `cost_price` the price and currency that the coins were purchased at - `cost` the cost (in the current currency) of the coins - - `profit` the PNL of the coins (current value vs original cost) - - `profit_percent` the PNL of the coins as a fraction of the original cost + - `pnl` the PNL of the coins (current value vs original cost) + - `pnl_percent` the PNL of the coins as a fraction of the original cost With the holdings above, and the currency set to GBP (British Pounds) cointop will look something like this: - - ![Screen Shot 2021-10-22 at 8 41 21 am](https://user-images.githubusercontent.com/122371/138361142-8e1f32b5-ca24-471d-a628-06968f07c65f.png) + + ![portfolio profit and loss](https://user-images.githubusercontent.com/122371/138361142-8e1f32b5-ca24-471d-a628-06968f07c65f.png) ## How do I hide my portfolio balances (private mode)? @@ -520,7 +520,7 @@ draft: false ## How can I get more information when something is going wrong? Cointop creates a logfile at `/tmp/cointop.log`. Normally nothing is written to this, but if you set the environment variable - `DEBUG=1` cointop will write a lot of output describing its operation. Furthermore, if you also set `DEBUG_HTTP=1` it will + `DEBUG=1` cointop will write a lot of output describing its operation. Furthermore, if you also set `DEBUG_HTTP=1` it will emit lots about every HTTP request that cointop makes to coingecko (backend). Developers may ask for this information to help diagnose any problems you may experience. diff --git a/pkg/api/impl/coingecko/coingecko.go b/pkg/api/impl/coingecko/coingecko.go index 2b9553a..5046ecf 100644 --- a/pkg/api/impl/coingecko/coingecko.go +++ b/pkg/api/impl/coingecko/coingecko.go @@ -161,7 +161,7 @@ func (s *Service) GetExchangeRates(cached bool) (*types.ExchangeRatesItem, error } // GetExchangeRate gets the current excange rate between two currencies -func (s *Service) GetExchangeRate(convertFrom string, convertTo string, cached bool) (float64, error) { +func (s *Service) GetExchangeRate(convertFrom, convertTo string, cached bool) (float64, error) { convertFrom = strings.ToLower(convertFrom) convertTo = strings.ToLower(convertTo) if convertFrom == convertTo { diff --git a/pkg/api/impl/coinmarketcap/coinmarketcap.go b/pkg/api/impl/coinmarketcap/coinmarketcap.go index e3a15fe..71a7b73 100644 --- a/pkg/api/impl/coinmarketcap/coinmarketcap.go +++ b/pkg/api/impl/coinmarketcap/coinmarketcap.go @@ -432,7 +432,7 @@ func getChartInterval(start, end int64) string { } // GetExchangeRate gets the current excange rate between two currencies -func (s *Service) GetExchangeRate(convertFrom string, convertTo string, cached bool) (float64, error) { +func (s *Service) GetExchangeRate(convertFrom, convertTo string, cached bool) (float64, error) { if convertFrom == convertTo { return 1.0, nil } diff --git a/pkg/api/interface.go b/pkg/api/interface.go index 294c16c..55ffcf2 100644 --- a/pkg/api/interface.go +++ b/pkg/api/interface.go @@ -16,5 +16,5 @@ type Interface interface { CoinLink(name string) string SupportedCurrencies() []string Price(name string, convert string) (float64, error) - GetExchangeRate(convertFrom string, convertTo string, cached bool) (float64, error) // I don't love this caching + GetExchangeRate(convertFrom, convertTo string, cached bool) (float64, error) // I don't love this caching }