ana-ledger/ledger/predict.go

82 lines
1.9 KiB
Go

package ledger
import (
"fmt"
"maps"
"math"
"regexp"
"time"
)
func RegisterWithCompoundingInterestPrediction(reg Register, window time.Duration, pattern string, apy float64) (Register, error) {
lastBalances := make(Balances)
p := regexp.MustCompile(pattern)
for _, d := range reg.Dates() {
if t, _ := dateToTime(d); time.Now().Before(t) {
continue
}
for name := range reg[d] {
if p.MatchString(name) {
lastBalances[name] = reg[d][name]
}
}
}
predictedTimes := predictionTimes(window)
result := maps.Clone(reg)
for _, predictionTime := range predictedTimes {
k := predictionTime.Format("2006-01")
if _, ok := result[k]; !ok {
result[k] = make(Balances)
}
for k2, v2 := range lastBalances {
if _, ok := result[k][k2]; !ok {
result[k][k2] = maps.Clone(v2)
}
}
}
addedSoFar := make(Balances)
for _, predictionTime := range predictedTimes {
k := predictionTime.Format("2006-01")
for name := range lastBalances {
if _, ok := addedSoFar[name]; !ok {
addedSoFar[name] = make(Balance)
}
for currency := range result[k][name] {
// A = P(1 + r/n)**nt
p := result[k][name][currency] + addedSoFar[name][currency]
r := apy
n := 12.0
t := 1.0 / 12.0
result[k][name][currency] = p * math.Pow(1.0+(r/n), n*t)
addedSoFar[name][currency] += (result[k][name][currency] - p)
}
}
}
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) {
for _, layout := range []string{
"2006-01-02",
"2006-01",
} {
if t, err := time.ParseInLocation(layout, s, time.Local); err == nil {
return t, err
}
}
return time.Time{}, fmt.Errorf("no layout matching %q", s)
}