172 lines
3.6 KiB
Go
172 lines
3.6 KiB
Go
package moon
|
|
|
|
import (
|
|
"context"
|
|
"math"
|
|
"time"
|
|
|
|
"code.humancabbage.net/sam/moonmath/coindesk"
|
|
"github.com/sourcegraph/conc/pool"
|
|
)
|
|
|
|
type Math struct {
|
|
Asset coindesk.Asset
|
|
CurrentPrice coindesk.Price
|
|
Columns []Column
|
|
Goals []Goal
|
|
}
|
|
|
|
func NewMath(asset coindesk.Asset, goals []Goal, bases []Base) (m Math) {
|
|
if goals == nil || bases == nil {
|
|
panic("goals and bases must be set")
|
|
}
|
|
m.Asset = asset
|
|
m.Goals = goals
|
|
m.Columns = make([]Column, len(bases))
|
|
for i := range bases {
|
|
m.Columns[i].Base = bases[i]
|
|
}
|
|
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(
|
|
from time.Time, currentPrice float64, cdpr float64, goals []Goal,
|
|
) (p Projection) {
|
|
if cdpr <= 0 {
|
|
return
|
|
}
|
|
logP := math.Log(currentPrice)
|
|
logR := math.Log(cdpr)
|
|
for _, goal := range goals {
|
|
daysToGo := (math.Log(goal.Value) - logP) / logR
|
|
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) {
|
|
resp, err := coindesk.GetAssetTickers(ctx, m.Asset)
|
|
if err != nil {
|
|
return
|
|
}
|
|
m.CurrentPrice = resp.Data[m.Asset].OHLC.Closing
|
|
|
|
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,
|
|
m.Asset, c.StartingDate, nextDay)
|
|
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
|
|
}
|
|
|
|
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},
|
|
}
|
|
|
|
type Goal struct {
|
|
Name string `koanf:"name"`
|
|
Value float64 `koanf:"value"`
|
|
}
|
|
|
|
// Base is a temporal point of comparison used for price projection.
|
|
type Base interface {
|
|
From(now time.Time) time.Time
|
|
Label() string
|
|
}
|
|
|
|
// 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"`
|
|
}
|
|
|
|
func (cb ConstantBase) From(_ time.Time) time.Time {
|
|
return cb.Time
|
|
}
|
|
|
|
func (cb ConstantBase) Label() string {
|
|
return cb.Name
|
|
}
|
|
|
|
// RelativeBase is a base that is relative, e.g. "90 days ago."
|
|
type RelativeBase struct {
|
|
Name string `koanf:"name"`
|
|
Offset time.Duration `koanf:"offset"`
|
|
}
|
|
|
|
func (rb RelativeBase) From(now time.Time) time.Time {
|
|
then := now.Add(time.Duration(rb.Offset))
|
|
return then
|
|
}
|
|
|
|
func (rb RelativeBase) Label() string {
|
|
return rb.Name
|
|
}
|