@@ -2,16 +2,24 @@ package cointop
22
33import (
44 "fmt"
5+ "math"
56 "sort"
67 "sync"
78 "time"
89
910 "github.com/miguelmota/cointop/pkg/chartplot"
11+ "github.com/miguelmota/cointop/pkg/timedata"
1012 "github.com/miguelmota/cointop/pkg/timeutil"
1113 "github.com/miguelmota/cointop/pkg/ui"
1214 log "github.com/sirupsen/logrus"
1315)
1416
17+ // PriceData is the time-series data for a Coin used when building a Portfolio view for chart
18+ type PriceData struct {
19+ coin * Coin
20+ data [][]float64
21+ }
22+
1523// ChartView is structure for chart view
1624type ChartView = ui.View
1725
@@ -120,7 +128,7 @@ func (ct *Cointop) ChartPoints(symbol string, name string) error {
120128 start := nowseconds - int64 (rangeseconds .Seconds ())
121129 end := nowseconds
122130
123- var data []float64
131+ var cacheData [] []float64
124132
125133 keyname := symbol
126134 if keyname == "" {
@@ -131,21 +139,18 @@ func (ct *Cointop) ChartPoints(symbol string, name string) error {
131139 cached , found := ct .cache .Get (cachekey )
132140 if found {
133141 // cache hit
134- data , _ = cached .([]float64 )
142+ cacheData , _ = cached .([] []float64 )
135143 log .Debug ("ChartPoints() soft cache hit" )
136144 }
137145
138- if len (data ) == 0 {
146+ if len (cacheData ) == 0 {
139147 if symbol == "" {
140148 convert := ct .State .currencyConversion
141149 graphData , err := ct .api .GetGlobalMarketGraphData (convert , start , end )
142150 if err != nil {
143151 return nil
144152 }
145- for i := range graphData .MarketCapByAvailableSupply {
146- price := graphData .MarketCapByAvailableSupply [i ][1 ]
147- data = append (data , price )
148- }
153+ cacheData = graphData .MarketCapByAvailableSupply
149154 } else {
150155 convert := ct .State .currencyConversion
151156 graphData , err := ct .api .GetCoinGraphData (convert , symbol , name , start , end )
@@ -156,20 +161,33 @@ func (ct *Cointop) ChartPoints(symbol string, name string) error {
156161 sort .Slice (sorted [:], func (i , j int ) bool {
157162 return sorted [i ][0 ] < sorted [j ][0 ]
158163 })
159- for i := range sorted {
160- price := sorted [i ][1 ]
161- data = append (data , price )
162- }
164+ cacheData = sorted
163165 }
164166
165- ct .cache .Set (cachekey , data , 10 * time .Second )
167+ ct .cache .Set (cachekey , cacheData , 10 * time .Second )
166168 if ct .filecache != nil {
167169 go func () {
168- ct .filecache .Set (cachekey , data , 24 * time .Hour )
170+ ct .filecache .Set (cachekey , cacheData , 24 * time .Hour )
169171 }()
170172 }
171173 }
172174
175+ // Resample cachedata
176+ timeQuantum := timedata .CalculateTimeQuantum (cacheData )
177+ newStart := time .Unix (start , 0 ).Add (timeQuantum )
178+ newEnd := time .Unix (end , 0 ).Add (- timeQuantum )
179+ timeData := timedata .ResampleTimeSeriesData (cacheData , float64 (newStart .UnixMilli ()), float64 (newEnd .UnixMilli ()), chart .GetChartDataSize (maxX ))
180+
181+ // Extract just the values from the data
182+ var data []float64
183+ for i := range timeData {
184+ value := timeData [i ][1 ]
185+ if math .IsNaN (value ) {
186+ value = 0.0
187+ }
188+ data = append (data , value )
189+ }
190+
173191 chart .SetData (data )
174192 ct .State .chartPoints = chart .GetChartPoints (maxX )
175193
@@ -202,7 +220,7 @@ func (ct *Cointop) PortfolioChart() error {
202220 start := nowseconds - int64 (rangeseconds .Seconds ())
203221 end := nowseconds
204222
205- var data []float64
223+ var allCacheData []PriceData
206224 portfolio := ct .GetPortfolioSlice ()
207225 chartname := ct .SelectedCoinName ()
208226 for _ , p := range portfolio {
@@ -217,46 +235,65 @@ func (ct *Cointop) PortfolioChart() error {
217235 continue
218236 }
219237
220- var graphData []float64
238+ var cacheData [][] float64 // [][time,value]
221239 cachekey := ct .CompositeCacheKey (p .Symbol , p .Name , convert , selectedChartRange )
222240 cached , found := ct .cache .Get (cachekey )
223241 if found {
224242 // cache hit
225- graphData , _ = cached .([]float64 )
243+ cacheData , _ = cached .([] []float64 )
226244 log .Debug ("PortfolioChart() soft cache hit" )
227245 } else {
228246 if ct .filecache != nil {
229- ct .filecache .Get (cachekey , & graphData )
247+ ct .filecache .Get (cachekey , & cacheData )
230248 }
231249
232- if len (graphData ) == 0 {
250+ if len (cacheData ) == 0 {
233251 time .Sleep (2 * time .Second )
234252
235253 apiGraphData , err := ct .api .GetCoinGraphData (convert , p .Symbol , p .Name , start , end )
236254 if err != nil {
237255 return err
238256 }
239- sorted := apiGraphData .Price
240- sort .Slice (sorted [:], func (i , j int ) bool {
241- return sorted [i ][0 ] < sorted [j ][0 ]
257+
258+ cacheData = apiGraphData .Price
259+ sort .Slice (cacheData [:], func (i , j int ) bool {
260+ return cacheData [i ][0 ] < cacheData [j ][0 ]
242261 })
243- for i := range sorted {
244- price := sorted [i ][1 ]
245- graphData = append (graphData , price )
246- }
247262 }
248263
249- ct .cache .Set (cachekey , graphData , 10 * time .Second )
264+ ct .cache .Set (cachekey , cacheData , 10 * time .Second )
250265 if ct .filecache != nil {
251266 go func () {
252- ct .filecache .Set (cachekey , graphData , 24 * time .Hour )
267+ ct .filecache .Set (cachekey , cacheData , 24 * time .Hour )
253268 }()
254269 }
255270 }
256271
257- for i := range graphData {
258- price := graphData [i ]
259- sum := p .Holdings * price
272+ allCacheData = append (allCacheData , PriceData {p , cacheData })
273+ }
274+
275+ // Use the gap between price samples to adjust start/end in by one interval
276+ var timeQuantum time.Duration
277+ for _ , cacheData := range allCacheData {
278+ timeQuantum = timedata .CalculateTimeQuantum (cacheData .data )
279+ if timeQuantum != 0 {
280+ break // use the first one
281+ }
282+ }
283+ newStart := time .Unix (start , 0 ).Add (timeQuantum )
284+ newEnd := time .Unix (end , 0 ).Add (- timeQuantum )
285+
286+ // Resample and sum data
287+ var data []float64
288+ for _ , cacheData := range allCacheData {
289+ coinData := timedata .ResampleTimeSeriesData (cacheData .data , float64 (newStart .UnixMilli ()), float64 (newEnd .UnixMilli ()), chart .GetChartDataSize (maxX ))
290+ // sum (excluding NaN)
291+ for i := range coinData {
292+ price := coinData [i ][1 ]
293+ if math .IsNaN (price ) {
294+ price = 0.0
295+ }
296+ sum := cacheData .coin .Holdings * price
260297 if i < len (data ) {
261298 data [i ] += sum
262299 } else {
0 commit comments