diff --git a/README.md b/README.md
index fed1b2a..debfd89 100644
--- a/README.md
+++ b/README.md
@@ -1008,6 +1008,12 @@ Frequently asked questions:
- A: Run the command `cointop reset` to delete the config files and cache. Cointop will generate a new config when starting up. You can run `cointop --reset` to reset before running cointop.
+- Q: Why aren't Home or End keys working for me?
+
+ - A: Make sure to not manually set `TERM` in your `~/.bashrc`, `~/.zshrc`, or any where else. The `TERM` environment variable should be automatically set by your terminal, otherwise this may cause the terminal emulator to send escape codes when these keys are pressed. See [this Arch wiki](https://wiki.archlinux.org/index.php/Home_and_End_keys_not_working) for more info.
+
+ - A: Use the `cointop price` command. Here are some examples:
+
- Q: What is the size of the binary?
- A: The Go build size is ~8MB but packed with UPX it's only a ~3MB executable binary.
@@ -1040,8 +1046,7 @@ Frequently asked questions:
- Q: Does cointop do mining?
- - A: Cointop does not do any kind of mining.
-
+ - A: Cointop does not do any kind of cryptocurrency mining.
- Q: How can I run the cointop SSH server on port 22?
diff --git a/cointop/chart.go b/cointop/chart.go
index 7716322..1326b10 100644
--- a/cointop/chart.go
+++ b/cointop/chart.go
@@ -2,6 +2,7 @@ package cointop
import (
"fmt"
+ "sort"
"strings"
"sync"
"time"
@@ -142,7 +143,7 @@ func (ct *Cointop) ChartPoints(symbol string, name string) error {
}
for i := range graphData.MarketCapByAvailableSupply {
price := graphData.MarketCapByAvailableSupply[i][1]
- data = append(data, price/1e9)
+ data = append(data, price)
}
} else {
convert := ct.State.currencyConversion
@@ -150,9 +151,12 @@ func (ct *Cointop) ChartPoints(symbol string, name string) error {
if err != nil {
return nil
}
-
- for i := range graphData.Price {
- price := graphData.Price[i][1]
+ sorted := graphData.Price
+ sort.Slice(sorted[:], func(i, j int) bool {
+ return sorted[i][0] < sorted[j][0]
+ })
+ for i := range sorted {
+ price := sorted[i][1]
data = append(data, price)
}
}
@@ -230,8 +234,12 @@ func (ct *Cointop) PortfolioChart() error {
if err != nil {
return err
}
- for i := range apiGraphData.Price {
- price := apiGraphData.Price[i][1]
+ sorted := apiGraphData.Price
+ sort.Slice(sorted[:], func(i, j int) bool {
+ return sorted[i][0] < sorted[j][0]
+ })
+ for i := range sorted {
+ price := sorted[i][1]
graphData = append(graphData, price)
}
}
@@ -358,6 +366,7 @@ func (ct *Cointop) ToggleCoinChart() error {
ct.UpdateChart()
}()
+ // TODO: not do this (SoC)
go ct.UpdateMarketbar()
return nil
diff --git a/cointop/cointop.go b/cointop/cointop.go
index 27d2b60..0e54335 100644
--- a/cointop/cointop.go
+++ b/cointop/cointop.go
@@ -225,7 +225,7 @@ func NewCointop(config *Config) (*Cointop, error) {
marketBarHeight: 1,
onlyTable: config.OnlyTable,
refreshRate: 60 * time.Second,
- selectedChartRange: "7D",
+ selectedChartRange: "1Y",
shortcutKeys: DefaultShortcuts(),
sortBy: "rank",
page: 0,
@@ -340,10 +340,6 @@ func NewCointop(config *Config) (*Cointop, error) {
}
}
- if ct.apiChoice == CoinGecko {
- ct.State.selectedChartRange = "1Y"
- }
-
if ct.apiChoice == CoinMarketCap {
ct.api = api.NewCMC(ct.apiKeys.cmc)
} else if ct.apiChoice == CoinGecko {
diff --git a/go.mod b/go.mod
index 10e616c..379455a 100644
--- a/go.mod
+++ b/go.mod
@@ -11,7 +11,7 @@ require (
github.com/maruel/panicparse v1.5.0
github.com/mattn/go-colorable v0.1.7 // indirect
github.com/mattn/go-runewidth v0.0.9
- github.com/miguelmota/go-coinmarketcap v0.1.6
+ github.com/miguelmota/go-coinmarketcap v0.1.7
github.com/miguelmota/gocui v0.4.2
github.com/miguelmota/termbox-go v0.0.0-20191229070316-58d4fcbce2a7
github.com/mitchellh/go-wordwrap v1.0.0
diff --git a/go.sum b/go.sum
index f55aa25..41dcdab 100644
--- a/go.sum
+++ b/go.sum
@@ -93,8 +93,8 @@ github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/Qd
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE=
-github.com/miguelmota/go-coinmarketcap v0.1.6 h1:YIe+VdFhEgyGESfmkL7BHRDIdf6CUOAjJisml01AFqs=
-github.com/miguelmota/go-coinmarketcap v0.1.6/go.mod h1:Jdv/kqtKclIElmoNAZMMJn0DSQv+j7p/H1te/GGnxhA=
+github.com/miguelmota/go-coinmarketcap v0.1.7 h1:9kTFWMom73IuGXqacD/LYPiUeX1qLpuLH8BhceHXYt0=
+github.com/miguelmota/go-coinmarketcap v0.1.7/go.mod h1:hBjej1IiB5+pfj+0cZhnxRkAc2bgky8qWLhCJTQ3zjw=
github.com/miguelmota/gocui v0.4.2 h1:nMYnYn3RjV7FlWFcidQa9eAkX3kT7XMI6yJMxEkAz6s=
github.com/miguelmota/gocui v0.4.2/go.mod h1:wVtmhuLR+VAS9VRBIJZBNJS9IgH+9QOZ/m/MvRarOZ4=
github.com/miguelmota/termbox-go v0.0.0-20191229070316-58d4fcbce2a7 h1:sZmjSV25xMXIGAaATVuOtC9VtGHMydXpd9OejNaTxQE=
diff --git a/pkg/api/impl/coinmarketcap/coinmarketcap.go b/pkg/api/impl/coinmarketcap/coinmarketcap.go
index d6b7ed9..9d03230 100644
--- a/pkg/api/impl/coinmarketcap/coinmarketcap.go
+++ b/pkg/api/impl/coinmarketcap/coinmarketcap.go
@@ -1,9 +1,13 @@
package coinmarketcap
import (
+ "encoding/json"
"errors"
"fmt"
+ "io/ioutil"
+ "net/http"
"os"
+ "sort"
"strings"
"time"
@@ -19,6 +23,9 @@ var ErrQuoteNotFound = errors.New("quote not found")
// ErrPingFailed is the error for when pinging the API fails
var ErrPingFailed = errors.New("failed to ping")
+// ErrFetchGraphData is the error for when fetching graph data fails
+var ErrFetchGraphData = errors.New("graph data fetch error")
+
// Service service
type Service struct {
client *cmc.Client
@@ -149,35 +156,138 @@ func (s *Service) GetCoinDataBatch(names []string, convert string) ([]apitypes.C
// GetCoinGraphData gets coin graph data
func (s *Service) GetCoinGraphData(convert, symbol string, name string, start int64, end int64) (apitypes.CoinGraph, error) {
ret := apitypes.CoinGraph{}
- graphData, err := cmcv2.TickerGraph(&cmcv2.TickerGraphOptions{
- Symbol: symbol,
- Start: start,
- End: end,
+ symbol = strings.ToUpper(symbol)
+ info, err := s.client.Cryptocurrency.Info(&cmc.InfoOptions{
+ Slug: name,
})
if err != nil {
return ret, err
}
-
- ret.MarketCapByAvailableSupply = graphData.MarketCapByAvailableSupply
- ret.PriceBTC = graphData.PriceBTC
- ret.Price = graphData.PriceUSD
- ret.Volume = graphData.VolumeUSD
+ var coinID string
+ if len(info) == 0 {
+ return ret, ErrFetchGraphData
+ }
+ for k := range info {
+ coinID = fmt.Sprintf("%v", info[k].ID)
+ }
+ if convert == "" {
+ convert = "usd"
+ }
+ convert = strings.ToUpper(convert)
+ interval := getChartInterval(start, end)
+ params := []string{
+ fmt.Sprintf("convert=%s,%s", convert, symbol),
+ "format=chart_crypto_details",
+ fmt.Sprintf("id=%s", coinID),
+ fmt.Sprintf("interval=%s", interval),
+ fmt.Sprintf("time_start=%v", start),
+ fmt.Sprintf("time_end=%v", end),
+ }
+ baseURL := "https://web-api.coinmarketcap.com/v1.1"
+ url := fmt.Sprintf("%s/cryptocurrency/quotes/historical?%s", baseURL, strings.Join(params, "&"))
+ resp, err := makeReq(url)
+ if err != nil {
+ return ret, err
+ }
+ var result map[string]interface{}
+ err = json.Unmarshal(resp, &result)
+ if err != nil {
+ return ret, err
+ }
+ data, ok := result["data"]
+ if !ok {
+ return ret, ErrFetchGraphData
+ }
+ ifcs, ok := data.(map[string]interface{})
+ if !ok {
+ return ret, ErrFetchGraphData
+ }
+ var prices [][]float64
+ for datetime, item := range ifcs {
+ ifc, ok := item.(map[string]interface{})
+ if !ok {
+ return ret, ErrFetchGraphData
+ }
+ for key, obj := range ifc {
+ if key != convert {
+ continue
+ }
+ arrIfc, ok := obj.([]interface{})
+ if !ok {
+ return ret, ErrFetchGraphData
+ }
+ if len(arrIfc) == 0 {
+ return ret, ErrFetchGraphData
+ }
+ val := arrIfc[0].(float64)
+ t, err := time.Parse(time.RFC3339, datetime)
+ if err != nil {
+ return ret, err
+ }
+ prices = append(prices, []float64{float64(t.Unix()), val})
+ }
+ }
+ sort.Slice(prices[:], func(i, j int) bool {
+ return prices[i][0] < prices[j][0]
+ })
+ ret.Price = prices
return ret, nil
}
// GetGlobalMarketGraphData gets global market graph data
func (s *Service) GetGlobalMarketGraphData(convert string, start int64, end int64) (apitypes.MarketGraph, error) {
ret := apitypes.MarketGraph{}
- graphData, err := cmcv2.GlobalMarketGraph(&cmcv2.GlobalMarketGraphOptions{
- Start: start,
- End: end,
- })
+ if convert == "" {
+ convert = "usd"
+ }
+ convert = strings.ToUpper(convert)
+ interval := getChartInterval(start, end)
+ params := []string{
+ fmt.Sprintf("convert=%s", convert),
+ "format=chart",
+ fmt.Sprintf("interval=%s", interval),
+ fmt.Sprintf("time_start=%v", start),
+ fmt.Sprintf("time_end=%v", end),
+ }
+ baseURL := "https://web-api.coinmarketcap.com/v1.1"
+ url := fmt.Sprintf("%s/global-metrics/quotes/historical?%s", baseURL, strings.Join(params, "&"))
+ resp, err := makeReq(url)
if err != nil {
return ret, err
}
-
- ret.MarketCapByAvailableSupply = graphData.MarketCapByAvailableSupply
- ret.VolumeUSD = graphData.VolumeUSD
+ var result map[string]interface{}
+ err = json.Unmarshal(resp, &result)
+ if err != nil {
+ return ret, err
+ }
+ data, ok := result["data"]
+ if !ok {
+ return ret, ErrFetchGraphData
+ }
+ mapIfc, ok := data.(map[string]interface{})
+ if !ok {
+ return ret, ErrFetchGraphData
+ }
+ var marketCap [][]float64
+ for datetime, item := range mapIfc {
+ arrIfc, ok := item.([]interface{})
+ if !ok {
+ return ret, ErrFetchGraphData
+ }
+ if len(arrIfc) == 0 {
+ return ret, ErrFetchGraphData
+ }
+ val := arrIfc[0].(float64)
+ t, err := time.Parse(time.RFC3339, datetime)
+ if err != nil {
+ return ret, err
+ }
+ marketCap = append(marketCap, []float64{float64(t.Unix()), val})
+ }
+ sort.Slice(marketCap[:], func(i, j int) bool {
+ return marketCap[i][0] < marketCap[j][0]
+ })
+ ret.MarketCapByAvailableSupply = marketCap
return ret, nil
}
@@ -229,7 +339,6 @@ func (s *Service) CoinLink(name string) string {
// SupportedCurrencies returns a list of supported currencies
func (s *Service) SupportedCurrencies() []string {
-
// keep these in alphabetical order
return []string{
"AUD",
@@ -269,3 +378,55 @@ func (s *Service) SupportedCurrencies() []string {
"ZAR",
}
}
+
+// doReq does HTTP request with client
+func doReq(req *http.Request) ([]byte, error) {
+ client := &http.Client{}
+ resp, err := client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+ body, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, err
+ }
+ if resp.StatusCode != 200 {
+ return nil, fmt.Errorf("%s", body)
+ }
+
+ return body, nil
+}
+
+// makeReq is an HTTP GET request helper
+func makeReq(url string) ([]byte, error) {
+ req, err := http.NewRequest("GET", url, nil)
+ if err != nil {
+ return nil, err
+ }
+ resp, err := doReq(req)
+ if err != nil {
+ return nil, err
+ }
+
+ return resp, err
+}
+
+// getChartInterval returns the interval to use for given time range
+func getChartInterval(start, end int64) string {
+ interval := "15m"
+ delta := end - start
+ if delta >= 604800 {
+ interval = "1h"
+ }
+ if delta >= 2629746 {
+ interval = "1d"
+ }
+ if delta >= 604800 {
+ interval = "1h"
+ }
+ if delta >= 2592000 {
+ interval = "1d"
+ }
+ return interval
+}
diff --git a/pkg/termui/linechart.go b/pkg/termui/linechart.go
index 0e76f5e..ab26670 100644
--- a/pkg/termui/linechart.go
+++ b/pkg/termui/linechart.go
@@ -198,7 +198,16 @@ func (lc *LineChart) calcLabelX() {
}
func shortenFloatVal(x float64) string {
- s := fmt.Sprintf("%.2f", x)
+ if x > 1e12 {
+ return fmt.Sprintf("%.2fT", x/1e12)
+ }
+ if x > 1e9 {
+ return fmt.Sprintf("%.2fB", x/1e9)
+ }
+ if x > 1e6 {
+ return fmt.Sprintf("%.2fB", x/1e6)
+ }
+
//if len(s)-3 > 3 {
//s = fmt.Sprintf("%.2e", x)
//}
@@ -206,7 +215,7 @@ func shortenFloatVal(x float64) string {
//if x < 0 {
//s = fmt.Sprintf("%.2f", x)
//}
- return s
+ return fmt.Sprintf("%.2f", x)
}
func (lc *LineChart) calcLabelY() {
@@ -217,7 +226,8 @@ func (lc *LineChart) calcLabelY() {
lc.labelY = make([][]rune, n)
maxLen := 0
for i := 0; i < n; i++ {
- s := str2runes(shortenFloatVal(lc.bottomValue + float64(i)*span/float64(n)))
+ val := lc.bottomValue + float64(i)*span/float64(n)
+ s := str2runes(shortenFloatVal(val))
if len(s) > maxLen {
maxLen = len(s)
}