From 5a532bcf1b8c8c8025802c38fb4f7dc510c19575 Mon Sep 17 00:00:00 2001 From: bel Date: Wed, 25 Oct 2023 21:23:06 -0600 Subject: [PATCH] bpi growth is also just compounding interest but ignore for now --- ledger/deltas.go | 4 +- ledger/predict.go | 102 ++++++++++++++++++++++++++--------------- ledger/predict_test.go | 35 ++++++++++++-- ledger/register.go | 43 +++++++++++++++++ 4 files changed, 142 insertions(+), 42 deletions(-) create mode 100644 ledger/register.go diff --git a/ledger/deltas.go b/ledger/deltas.go index 0f1b3a4..6a326e3 100644 --- a/ledger/deltas.go +++ b/ledger/deltas.go @@ -24,8 +24,8 @@ func (deltas Deltas) Like(like ...Like) Deltas { return result } -func (deltas Deltas) Register() map[string]Balances { - dateToBalances := map[string]Balances{} +func (deltas Deltas) Register() Register { + dateToBalances := make(Register) for _, delta := range deltas { if _, ok := dateToBalances[delta.Date]; !ok { dateToBalances[delta.Date] = make(Balances) diff --git a/ledger/predict.go b/ledger/predict.go index 32e693d..24de9cf 100644 --- a/ledger/predict.go +++ b/ledger/predict.go @@ -3,56 +3,86 @@ package ledger import ( "fmt" "io" - "log" "maps" + "regexp" "time" ) -func RegisterWithContributionPrediction(reg map[string]Balances, windowAsPercentOfTotalDuration float64) (map[string]Balances, error) { +func RegisterWithContributionPrediction(reg Register, window time.Duration) (Register, error) { return reg, io.EOF } -func BPIWithFixedGrowthPrediction(reg map[string]Balances, windowAsPercentOfTotalDuration float64, pattern string, rate float64) (map[string]Balances, error) { - result := make(map[string]Balances) - return result, io.EOF +func BPIsWithFixedGrowthPrediction(bpis BPIs, window time.Duration, pattern string, apy float64) (BPIs, error) { + last := map[Currency]struct { + t string + v float64 + }{} + for currency, bpi := range bpis { + for date, value := range bpi { + if date > last[currency].t { + was := last[currency] + was.t = date + was.v = value + last[currency] = was + } + } + } + + result := make(BPIs) + p := regexp.MustCompile(pattern) + for currency, v := range bpis { + result[currency] = maps.Clone(v) + if p.MatchString(string(currency)) { + for _, predictionTime := range predictionTimes(window) { + k2 := predictionTime.Format("2006-01") + was := last[currency] + was.v *= 1.0 + apy + result[currency][k2] = was.v + last[currency] = was + } + } + } + return result, nil } -func RegisterWithCompoundingInterestPrediction(reg map[string]Balances, windowAsPercentOfTotalDuration float64, pattern string, rate float64) (map[string]Balances, error) { - nameToLastDate := func() map[string]string { - result := map[string]string{} - for date, balances := range reg { - for name := range balances { - if result[name] < date { - result[name] = date - } +func RegisterWithCompoundingInterestPrediction(reg Register, window time.Duration, pattern string, apy float64) (Register, error) { + lastBalances := make(Balances) + p := regexp.MustCompile(pattern) + for _, date := range reg.Dates() { + for name := range reg[date] { + if p.MatchString(name) { + lastBalances[name] = reg[date][name] } } - return result - }() - firstTime, lastTime := func() (time.Time, time.Time) { - var latest time.Time - first := time.Now() - for _, v := range nameToLastDate { - v2, _ := dateToTime(v) - if latest.Before(v2) { - latest = v2 - } - if first.Before(v2) { - first = v2 - } - } - return first, latest - }() - log.Println("first", firstTime, "last time", lastTime, "nameToDate", nameToLastDate) - - lastPrediction := lastTime - for lastPrediction.Before(lastTime.Add(lastTime.Sub(firstTime) * time.Duration(windowAsPercentOfTotalDuration))) { - lastPrediction = lastPrediction.Add(time.Hour * 24 * (45 - lastPrediction.Day())) - TODO ^ } result := maps.Clone(reg) - return result, io.EOF + for _, predictionTime := range predictionTimes(window) { + k := predictionTime.Format("2006-01") + if _, ok := result[k]; !ok { + result[k] = make(Balances) + } + for name, balance := range lastBalances { + balance2 := maps.Clone(balance) + for k := range balance2 { + balance2[k] *= 1.0 + (apy / 12) + } + result[k][name] = balance2 + lastBalances[name] = balance2 + } + } + + return result, nil +} + +func predictionTimes(window time.Duration) []time.Time { + result := []time.Time{} + last := time.Now() + for last.Before(time.Now().Add(window)) { + last = last.Add(-1 * time.Hour * 24 * time.Duration(last.Day())).Add(time.Hour * 24 * 33) + result = append(result, last) + } + return result } func dateToTime(s string) (time.Time, error) { diff --git a/ledger/predict_test.go b/ledger/predict_test.go index 158d996..1964393 100644 --- a/ledger/predict_test.go +++ b/ledger/predict_test.go @@ -1,14 +1,16 @@ package ledger import ( + "slices" "testing" + "time" ) func TestRegisterPrediction(t *testing.T) { t.Run("contribution", func(t *testing.T) { input := newTestRegister() - got, err := RegisterWithContributionPrediction(input, .12) + got, err := RegisterWithContributionPrediction(input, time.Hour*24*365) if err != nil { t.Fatal(err) } @@ -26,7 +28,7 @@ func TestRegisterPrediction(t *testing.T) { t.Run("compounding interest", func(t *testing.T) { input := newTestRegister() - got, err := RegisterWithCompoundingInterestPrediction(input, 1, "X", .04) + got, err := RegisterWithCompoundingInterestPrediction(input, time.Hour*24*365, "X", .04) if err != nil { t.Fatal(err) } @@ -35,13 +37,38 @@ func TestRegisterPrediction(t *testing.T) { if len(got) <= len(input) { t.Error(len(got)) } - t.Error("not impl") + + for _, date := range got.Dates() { + for name, balance := range got[date] { + t.Logf("%s | %s %s", date, name, balance.Debug()) + } + } }) } func TestBPIPrediction(t *testing.T) { t.Run("fixed growth", func(t *testing.T) { - t.Error("not impl") + bpis := BPIs{ + USD: BPI{ + "2001-01": -1000, + "2001-02": 100, + }, + } + + got, err := BPIsWithFixedGrowthPrediction(bpis, time.Hour*24*365, string(USD), 0.06) + if err != nil { + t.Fatal(err) + } + + dates := []string{} + for d := range got[USD] { + dates = append(dates, d) + } + slices.Sort(dates) + + for _, d := range dates { + t.Logf("%s | %s %.2f", USD, d, got[USD][d]) + } }) } diff --git a/ledger/register.go b/ledger/register.go new file mode 100644 index 0000000..2056fbc --- /dev/null +++ b/ledger/register.go @@ -0,0 +1,43 @@ +package ledger + +import ( + "slices" + "time" +) + +type Register map[string]Balances + +func (register Register) Names() []string { + names := map[string]int{} + for _, v := range register { + for name := range v { + names[name] = 1 + } + } + + result := make([]string, 0, len(register)) + for k := range names { + result = append(result, k) + } + slices.Sort(result) + return result +} + +func (register Register) Dates() []string { + result := make([]string, 0, len(register)) + for k := range register { + result = append(result, k) + } + slices.Sort(result) + return result +} + +func (register Register) Times() []time.Time { + dates := register.Dates() + result := make([]time.Time, len(dates)) + for i := range dates { + v, _ := dateToTime(dates[i]) + result[i] = v + } + return result +}