From f67323c5f4ed6edba5d61fee6f6f58b812e480e6 Mon Sep 17 00:00:00 2001 From: Sam Fredrickson Date: Fri, 29 Mar 2024 19:19:38 -0700 Subject: [PATCH] Support hardcoded starting prices. The Coindesk API doesn't have data going all the way back. But since history isn't changing, we can simply put in known prices. Also, extend the CDPR cells to have four digits instead of just two. --- config/default.yaml | 11 ++++++ moon/moon.go | 94 ++++++++++++++++++++++++++------------------- moon/moon_test.go | 10 ++++- tui/asset/asset.go | 2 +- 4 files changed, 75 insertions(+), 42 deletions(-) diff --git a/config/default.yaml b/config/default.yaml index dc4417e..fd83019 100644 --- a/config/default.yaml +++ b/config/default.yaml @@ -28,6 +28,12 @@ defaults: time: 2019-12-31T16:00:00-08:00 - name: 2017- time: 2016-12-31T16:00:00-08:00 + - name: 2013- + time: 2012-12-31T16:00:00-08:00 + startingPrice: 13.30 + - name: 2011- + time: 2010-12-31T16:00:00-08:00 + startingPrice: 0.30 assets: ETH: @@ -44,6 +50,11 @@ assets: value: 20000 - name: $25k value: 25000 + constantBases: + - name: 2020- + time: 2019-12-31T16:00:00-08:00 + - name: 2017- + time: 2016-12-31T16:00:00-08:00 LTC: goals: - name: $100 diff --git a/moon/moon.go b/moon/moon.go index ed6ae5e..68484fb 100644 --- a/moon/moon.go +++ b/moon/moon.go @@ -2,6 +2,8 @@ package moon import ( "context" + "errors" + "fmt" "math" "time" @@ -74,30 +76,10 @@ func (m *Math) Refresh(ctx context.Context) (err error) { tasks.WithMaxGoroutines(len(m.Columns)) //tasks.WithMaxGoroutines(1) now := time.Now() - for i := range m.Columns { c := &m.Columns[i] tasks.Go(func() error { - c.StartingDate = c.Base.From(now) - nextDay := c.StartingDate.Add(time.Hour * 24) - resp, err := coindesk.GetPriceValues(ctx, - m.Asset, c.StartingDate, nextDay) - if err != nil { - return err - } - if len(resp.Data.Entries) == 0 { - c.Projections.Dates = nil - return nil - } - c.StartingPrice = resp.Data.Entries[0].Price - c.Gain = float64(m.CurrentPrice) / float64(c.StartingPrice) - days := now.Sub(c.StartingDate).Hours() / 24 - c.CDPR = CDPR(days, c.Gain) - c.Projections = ProjectDates( - now, float64(m.CurrentPrice), - c.CDPR, m.Goals, - ) - return nil + return c.project(ctx, m, now) }) } err = tasks.Wait() @@ -114,27 +96,49 @@ type Column struct { Projections Projection } -var DefaultGoals = []Goal{ - {"$100k", 100000}, - {"$150k", 150000}, - {"$200k", 200000}, - {"$250k", 250000}, - {"$300k", 300000}, - {"$500k", 500000}, - {"$1m", 1000000}, +func (c *Column) project(ctx context.Context, m *Math, now time.Time) (err error) { + err = c.fillStartingPrice(ctx, m.Asset, now) + if err != nil { + return + } + + c.Gain = float64(m.CurrentPrice) / float64(c.StartingPrice) + days := now.Sub(c.StartingDate).Hours() / 24 + c.CDPR = CDPR(days, c.Gain) + c.Projections = ProjectDates( + now, float64(m.CurrentPrice), + c.CDPR, m.Goals, + ) + return } -var DefaultConstantBases = []ConstantBase{ - {"2020-", time.Unix(1577836800, 0)}, - {"2017-", time.Unix(1483228800, 0)}, +func (c *Column) fillStartingPrice( + ctx context.Context, asset coindesk.Asset, now time.Time, +) error { + // if base provides a hardcoded starting price, use it + c.StartingDate = c.Base.From(now) + c.StartingPrice = coindesk.Price(c.Base.GetStartingPrice()) + if c.StartingPrice != 0 { + return nil + } + + // otherwise, look up the starting price via Coindesk + nextDay := c.StartingDate.Add(time.Hour * 24) + resp, err := coindesk.GetPriceValues(ctx, asset, c.StartingDate, nextDay) + if err != nil { + err = fmt.Errorf("getting price for %s on %v: %w", + asset, c.StartingDate, err) + return err + } + if len(resp.Data.Entries) == 0 { + c.Projections.Dates = nil + return errEmptyPriceEntries + } + c.StartingPrice = resp.Data.Entries[0].Price + return nil } -var DefaultRelativeBases = []RelativeBase{ - {"Month", time.Duration(-30) * time.Hour * 24}, - {"Quarter", time.Duration(-90) * time.Hour * 24}, - {"Half-Year", time.Duration(-182) * time.Hour * 24}, - {"Year", time.Duration(-365) * time.Hour * 24}, -} +var errEmptyPriceEntries = errors.New("price values response has no entries") type Goal struct { Name string `koanf:"name"` @@ -145,12 +149,14 @@ type Goal struct { type Base interface { From(now time.Time) time.Time Label() string + GetStartingPrice() float64 } // ConstantBase is a base that is a constant time, e.g. 2020-01-01. type ConstantBase struct { - Name string `koanf:"name"` - Time time.Time `koanf:"time"` + Name string `koanf:"name"` + Time time.Time `koanf:"time"` + StartingPrice float64 `koanf:"startingPrice"` } func (cb ConstantBase) From(_ time.Time) time.Time { @@ -161,6 +167,10 @@ func (cb ConstantBase) Label() string { return cb.Name } +func (cb ConstantBase) GetStartingPrice() float64 { + return cb.StartingPrice +} + // RelativeBase is a base that is relative, e.g. "90 days ago." type RelativeBase struct { Name string `koanf:"name"` @@ -175,3 +185,7 @@ func (rb RelativeBase) From(now time.Time) time.Time { func (rb RelativeBase) Label() string { return rb.Name } + +func (rb RelativeBase) GetStartingPrice() float64 { + return 0 +} diff --git a/moon/moon_test.go b/moon/moon_test.go index 4ea4e6b..746c084 100644 --- a/moon/moon_test.go +++ b/moon/moon_test.go @@ -11,6 +11,14 @@ func TestCDPR(t *testing.T) { } func TestProjection(t *testing.T) { - p := moon.ProjectDates(time.Now(), 68900, 1.0055, moon.DefaultGoals) + p := moon.ProjectDates(time.Now(), 68900, 1.0055, []moon.Goal{ + {"$100k", 100000}, + {"$150k", 150000}, + {"$200k", 200000}, + {"$250k", 250000}, + {"$300k", 300000}, + {"$500k", 500000}, + {"$1m", 1000000}, + }) _ = p } diff --git a/tui/asset/asset.go b/tui/asset/asset.go index 0243312..c9e815c 100644 --- a/tui/asset/asset.go +++ b/tui/asset/asset.go @@ -206,7 +206,7 @@ func refillProjections(m *Model) { func renderEntries(c moon.Column) (entries []string) { entries = append(entries, fmt.Sprintf("$%.2f", c.StartingPrice)) - entries = append(entries, fmt.Sprintf("%.2f%%", (c.CDPR-1)*100)) + entries = append(entries, fmt.Sprintf("%.4f%%", (c.CDPR-1)*100)) never := c.CDPR <= 1 for i := range c.Projections.Dates { var cell string