You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
cointop/cointop/chart.go

337 lines
6.8 KiB
Go

package cointop
import (
"fmt"
"strings"
6 years ago
"sync"
"time"
"github.com/miguelmota/cointop/pkg/color"
"github.com/miguelmota/cointop/pkg/fcache"
"github.com/miguelmota/cointop/pkg/now"
"github.com/miguelmota/cointop/pkg/termui"
)
6 years ago
var chartlock sync.Mutex
var chartpointslock sync.Mutex
func (ct *Cointop) updateChart() error {
6 years ago
chartlock.Lock()
defer chartlock.Unlock()
5 years ago
if ct.portfoliovisible {
if err := ct.portfolioChart(); err != nil {
return err
}
} else {
coin := ct.selectedCoinSymbol()
ct.chartPoints(coin)
}
if len(ct.chartpoints) != 0 {
ct.chartview.Clear()
}
6 years ago
var body string
5 years ago
if len(ct.chartpoints) == 0 {
body = "\n\n\n\n\nnot enough data for chart"
} else {
for i := range ct.chartpoints {
var s string
for j := range ct.chartpoints[i] {
p := ct.chartpoints[i][j]
s = fmt.Sprintf("%s%c", s, p.Ch)
}
body = fmt.Sprintf("%s%s\n", body, s)
6 years ago
5 years ago
}
}
6 years ago
ct.update(func() {
fmt.Fprint(ct.chartview, color.White(body))
})
return nil
}
5 years ago
func (ct *Cointop) chartPoints(coin string) error {
maxX := ct.maxtablewidth - 3
6 years ago
chartpointslock.Lock()
defer chartpointslock.Unlock()
5 years ago
// TODO: not do this (SoC)
go ct.updateMarketbar()
chart := termui.NewLineChart()
chart.Height = 10
chart.AxesColor = termui.ColorWhite
chart.LineColor = termui.ColorCyan
chart.Border = false
rangeseconds := ct.chartrangesmap[ct.selectedchartrange]
if ct.selectedchartrange == "YTD" {
6 years ago
ytd := time.Now().Unix() - int64(now.BeginningOfYear().Unix())
rangeseconds = time.Duration(ytd) * time.Second
}
now := time.Now()
nowseconds := now.Unix()
start := nowseconds - int64(rangeseconds.Seconds())
end := nowseconds
var data []float64
keyname := coin
if keyname == "" {
keyname = "globaldata"
}
cachekey := strings.ToLower(fmt.Sprintf("%s_%s", keyname, strings.Replace(ct.selectedchartrange, " ", "", -1)))
cached, found := ct.cache.Get(cachekey)
if found {
// cache hit
data, _ = cached.([]float64)
ct.debuglog("soft cache hit")
}
if len(data) == 0 {
if coin == "" {
graphData, err := ct.api.GetGlobalMarketGraphData(start, end)
if err != nil {
return nil
}
for i := range graphData.MarketCapByAvailableSupply {
price := graphData.MarketCapByAvailableSupply[i][1]
data = append(data, price/1E9)
}
} else {
graphData, err := ct.api.GetCoinGraphData(coin, start, end)
if err != nil {
return nil
}
for i := range graphData.PriceUSD {
price := graphData.PriceUSD[i][1]
data = append(data, price)
}
}
ct.cache.Set(cachekey, data, 10*time.Second)
go func() {
_ = fcache.Set(cachekey, data, 24*time.Hour)
}()
}
chart.Data = data
termui.Body = termui.NewGrid()
termui.Body.Width = maxX
termui.Body.AddRows(
termui.NewRow(
termui.NewCol(12, 0, chart),
),
)
var points [][]termui.Cell
// calculate layout
termui.Body.Align()
w := termui.Body.Width
h := 10
row := termui.Body.Rows[0]
b := row.Buffer()
for i := 0; i < h; i = i + 1 {
var rowpoints []termui.Cell
for j := 0; j < w; j = j + 1 {
p := b.At(j, i)
rowpoints = append(rowpoints, p)
}
points = append(points, rowpoints)
}
ct.chartpoints = points
return nil
}
5 years ago
func (ct *Cointop) portfolioChart() error {
maxX := ct.maxtablewidth - 3
chartpointslock.Lock()
defer chartpointslock.Unlock()
// TODO: not do this (SoC)
go ct.updateMarketbar()
chart := termui.NewLineChart()
chart.Height = 10
chart.AxesColor = termui.ColorWhite
chart.LineColor = termui.ColorCyan
chart.Border = false
rangeseconds := ct.chartrangesmap[ct.selectedchartrange]
if ct.selectedchartrange == "YTD" {
ytd := time.Now().Unix() - int64(now.BeginningOfYear().Unix())
rangeseconds = time.Duration(ytd) * time.Second
}
now := time.Now()
nowseconds := now.Unix()
start := nowseconds - int64(rangeseconds.Seconds())
end := nowseconds
var data []float64
portfolio := ct.getPortfolioSlice()
chartname := ct.selectedCoinName()
for _, p := range portfolio {
// filter by selected chart if selected
if chartname != "" {
if chartname != p.Name {
continue
}
}
if p.Holdings <= 0 {
continue
}
var graphData []float64
coin := p.Symbol
cachekey := strings.ToLower(fmt.Sprintf("%s_%s", coin, strings.Replace(ct.selectedchartrange, " ", "", -1)))
cached, found := ct.cache.Get(cachekey)
if found {
// cache hit
graphData, _ = cached.([]float64)
ct.debuglog("soft cache hit")
} else {
_ = fcache.Get(cachekey, &graphData)
if len(graphData) == 0 {
time.Sleep(2 * time.Second)
apiGraphData, err := ct.api.GetCoinGraphData(coin, start, end)
if err != nil {
return err
}
for i := range apiGraphData.PriceUSD {
price := apiGraphData.PriceUSD[i][1]
graphData = append(graphData, price)
}
}
ct.cache.Set(cachekey, graphData, 10*time.Second)
go func() {
_ = fcache.Set(cachekey, graphData, 24*time.Hour)
}()
}
for i := range graphData {
price := graphData[i]
sum := p.Holdings * price
if len(data)-1 >= i {
data[i] += sum
}
data = append(data, sum)
}
}
chart.Data = data
termui.Body = termui.NewGrid()
termui.Body.Width = maxX
termui.Body.AddRows(
termui.NewRow(
termui.NewCol(12, 0, chart),
),
)
var points [][]termui.Cell
// calculate layout
termui.Body.Align()
w := termui.Body.Width
h := 10
row := termui.Body.Rows[0]
b := row.Buffer()
for i := 0; i < h; i = i + 1 {
var rowpoints []termui.Cell
for j := 0; j < w; j = j + 1 {
p := b.At(j, i)
rowpoints = append(rowpoints, p)
}
points = append(points, rowpoints)
}
ct.chartpoints = points
return nil
}
func (ct *Cointop) nextChartRange() error {
sel := 0
max := len(ct.chartranges)
for i, k := range ct.chartranges {
if k == ct.selectedchartrange {
sel = i + 1
break
}
}
if sel > max-1 {
sel = 0
}
ct.selectedchartrange = ct.chartranges[sel]
go ct.updateChart()
return nil
}
func (ct *Cointop) prevChartRange() error {
sel := 0
for i, k := range ct.chartranges {
if k == ct.selectedchartrange {
sel = i - 1
break
}
}
if sel < 0 {
sel = len(ct.chartranges) - 1
}
ct.selectedchartrange = ct.chartranges[sel]
go ct.updateChart()
return nil
}
func (ct *Cointop) firstChartRange() error {
ct.selectedchartrange = ct.chartranges[0]
go ct.updateChart()
return nil
}
func (ct *Cointop) lastChartRange() error {
ct.selectedchartrange = ct.chartranges[len(ct.chartranges)-1]
go ct.updateChart()
return nil
}
func (ct *Cointop) selectedCoinName() string {
coin := ct.selectedcoin
if coin != nil {
return coin.Name
}
return ""
}
func (ct *Cointop) selectedCoinSymbol() string {
coin := ct.selectedcoin
if coin != nil {
return coin.Symbol
}
return ""
}
func (ct *Cointop) toggleCoinChart() error {
highlightedcoin := ct.highlightedRowCoin()
if ct.selectedcoin == highlightedcoin {
ct.selectedcoin = nil
} else {
ct.selectedcoin = highlightedcoin
}
5 years ago
go ct.updateChart()
go ct.updateMarketbar()
return nil
}