package financial import ( "errors" "fmt" "math" "time" log "github.com/rs/zerolog/log" ) var errInvalidPayments = errors.New("negative and positive payments are required") var errXirr = errors.New("xirr cannot be calculated") type Payment struct { Date time.Time Amount float64 } type cashflow struct { Years float64 Amount float64 } func XIRR(payments []Payment) (float64, error) { var yearCashflows = convertToCashflows(payments) log.Print(yearCashflows) const decimals = 3 var precision = math.Pow(10, -decimals) var minRate = precision const maxRate = 1000000 return calculateXirr(yearCashflows, minRate, maxRate, precision, decimals) } func findMinDate(payments []Payment) time.Time { var result = payments[0].Date for i := 1; i < len(payments); i++ { var d = payments[i].Date if d.Before(result) { result = d } } return result } func convertToCashflows(payments []Payment) []cashflow { var minDate = findMinDate(payments) var result = make([]cashflow, len(payments)) for i, x := range payments { result[i] = cashflow{ Years: yearsBetween(minDate, x.Date), Amount: x.Amount, } } return result } func yearsBetween(start, finish time.Time) float64 { return float64(finish.Sub(start)/(24*time.Hour)) / 365.25 } func calcEquation(cashflows []cashflow, interestRate float64) float64 { var sum float64 for _, x := range cashflows { sum += x.Amount * math.Pow(interestRate, -x.Years) } return sum } func calculateXirr(cashFlows []cashflow, lowRate, highRate, precision float64, decimals int) (float64, error) { var lowResult = calcEquation(cashFlows, lowRate) var highResult = calcEquation(cashFlows, highRate) for { if math.Signbit(lowResult) == math.Signbit(highResult) { return 0, errXirr } var middleRate = 0.5 * (lowRate + highRate) var middleResult = calcEquation(cashFlows, middleRate) if math.Signbit(middleResult) == math.Signbit(lowResult) { lowRate = middleRate lowResult = middleResult } else { highRate = middleRate highResult = middleResult } if math.Abs(middleResult) <= precision { return 0.5 * (highRate + lowRate), nil } } } func CalculateXIRR() { var tests = []struct { Payments []Payment Xirr float64 }{ { Payments: []Payment{ Payment{mustParseDate("2022-07-01"), -100000.00}, Payment{mustParseDate("2023-01-01"), 4921.97}, Payment{mustParseDate("2023-01-01"), 100000.00}, }, Xirr: 10.00, }, } log.Print(tests) for i, test := range tests { var xirr, err = XIRR(test.Payments) if err != nil { log.Print(err.Error()) continue } log.Print(xirr) if math.Abs(xirr-test.Xirr) > 0.005 { fmt.Sprintf("%v %v %v", i, test, xirr) continue } } } func mustParseDate(s string) time.Time { var t, err = time.Parse("2006-01-02", s) if err != nil { panic(err) } return t }