moonmath/moon/moon.go

197 lines
4.2 KiB
Go
Raw Normal View History

2024-03-17 09:10:05 +00:00
package moon
import (
"context"
"fmt"
2024-03-17 09:10:05 +00:00
"math"
"time"
2024-03-19 21:29:37 +00:00
"code.humancabbage.net/sam/moonmath/coindesk"
2024-03-17 09:10:05 +00:00
"github.com/sourcegraph/conc/pool"
)
type Math struct {
2024-03-20 04:44:11 +00:00
Asset coindesk.Asset
2024-03-17 09:10:05 +00:00
CurrentPrice coindesk.Price
Columns []Column
2024-03-20 04:44:11 +00:00
Goals []Goal
Labels []string
2024-03-17 09:10:05 +00:00
}
2024-03-20 04:44:11 +00:00
func NewMath(asset coindesk.Asset, goals []Goal, bases []Base) (m Math) {
if goals == nil || bases == nil {
panic("goals and bases must be set")
2024-03-17 09:10:05 +00:00
}
2024-03-20 04:44:11 +00:00
m.Asset = asset
2024-03-17 09:10:05 +00:00
m.Goals = goals
m.Labels = []string{"Starting", "CDPR"}
2024-03-17 09:10:05 +00:00
m.Columns = make([]Column, len(bases))
for i := range bases {
m.Columns[i].Base = bases[i]
}
for i := range goals {
m.Labels = append(m.Labels, goals[i].Name)
}
2024-03-17 09:10:05 +00:00
return
}
func CDPR(days, gain float64) float64 {
if gain <= 0 {
return 0
}
cdpr := math.Pow(gain, 1/days)
return cdpr
}
type Projection struct {
Dates []time.Time
}
func ProjectDates(
2024-03-20 04:44:11 +00:00
from time.Time, currentPrice float64, cdpr float64, goals []Goal,
2024-03-17 09:10:05 +00:00
) (p Projection) {
if cdpr <= 0 {
return
}
logP := math.Log(currentPrice)
logR := math.Log(cdpr)
for _, goal := range goals {
2024-03-20 04:44:11 +00:00
daysToGo := (math.Log(goal.Value) - logP) / logR
2024-03-17 09:10:05 +00:00
date := from.Add(time.Hour * 24 * time.Duration(daysToGo))
p.Dates = append(p.Dates, date)
}
return
}
func (m *Math) Refresh(ctx context.Context) (err error) {
2024-03-20 04:44:11 +00:00
resp, err := coindesk.GetAssetTickers(ctx, m.Asset)
2024-03-17 09:10:05 +00:00
if err != nil {
return
}
2024-03-20 04:44:11 +00:00
m.CurrentPrice = resp.Data[m.Asset].OHLC.Closing
2024-03-17 09:10:05 +00:00
tasks := pool.New().WithErrors()
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,
2024-03-20 04:44:11 +00:00
m.Asset, c.StartingDate, nextDay)
2024-03-17 09:10:05 +00:00
if err != nil {
return err
}
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)
if c.CDPR > 1 {
c.Projections = ProjectDates(now, float64(m.CurrentPrice), c.CDPR, m.Goals)
} else {
c.Projections.Dates = nil
}
return nil
})
}
err = tasks.Wait()
return
}
type Column struct {
Base Base
StartingDate time.Time
StartingPrice coindesk.Price
Gain float64
CDPR float64
Projections Projection
}
func (c *Column) Column() (entries []string) {
entries = append(entries, fmt.Sprintf("$%.2f", c.StartingPrice))
entries = append(entries, fmt.Sprintf("%.2f%%", (c.CDPR-1)*100))
never := c.CDPR <= 1
for i := range c.Projections.Dates {
var cell string
if never {
cell = "NEVER!!!!!"
} else {
cell = c.
Projections.
Dates[i].
Format("2006-01-02")
}
entries = append(entries, cell)
}
return
}
2024-03-20 04:44:11 +00:00
var DefaultGoals = []Goal{
{"$100k", 100000},
{"$150k", 150000},
{"$200k", 200000},
{"$250k", 250000},
{"$300k", 300000},
{"$500k", 500000},
{"$1m", 1000000},
}
var DefaultConstantBases = []ConstantBase{
{"2020-", time.Unix(1577836800, 0)},
{"2019-", time.Unix(1546300800, 0)},
{"2018-", time.Unix(1514764800, 0)},
{"2017-", time.Unix(1483228800, 0)},
}
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},
2024-03-17 09:10:05 +00:00
}
2024-03-20 04:44:11 +00:00
type Goal struct {
Name string `koanf:"name"`
Value float64 `koanf:"value"`
2024-03-17 09:10:05 +00:00
}
// Base is a temporal point of comparison used for price projection.
type Base interface {
From(now time.Time) time.Time
2024-03-20 04:44:11 +00:00
Label() string
2024-03-17 09:10:05 +00:00
}
// ConstantBase is a base that is a constant time, e.g. 2020-01-01.
type ConstantBase struct {
2024-03-20 04:44:11 +00:00
Name string `koanf:"name"`
Time time.Time `koanf:"time"`
2024-03-17 09:10:05 +00:00
}
func (cb ConstantBase) From(_ time.Time) time.Time {
2024-03-20 04:44:11 +00:00
return cb.Time
2024-03-17 09:10:05 +00:00
}
2024-03-20 04:44:11 +00:00
func (cb ConstantBase) Label() string {
return cb.Name
2024-03-17 09:10:05 +00:00
}
// RelativeBase is a base that is relative, e.g. "90 days ago."
type RelativeBase struct {
2024-03-20 04:44:11 +00:00
Name string `koanf:"name"`
Offset time.Duration `koanf:"offset"`
2024-03-17 09:10:05 +00:00
}
func (rb RelativeBase) From(now time.Time) time.Time {
2024-03-20 04:44:11 +00:00
then := now.Add(time.Duration(rb.Offset))
2024-03-17 09:10:05 +00:00
return then
}
2024-03-20 04:44:11 +00:00
func (rb RelativeBase) Label() string {
return rb.Name
2024-03-17 09:10:05 +00:00
}