bpi growth is also just compounding interest but ignore for now

main
bel 2023-10-25 21:23:06 -06:00
parent 76aa4ff34a
commit 5a532bcf1b
4 changed files with 142 additions and 42 deletions

View File

@ -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)

View File

@ -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) {

View File

@ -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])
}
})
}

43
ledger/register.go Normal file
View File

@ -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
}