From 31de7837e1a880f355da85409f6b2b3023d10e48 Mon Sep 17 00:00:00 2001 From: Maksim Syomochkin Date: Fri, 17 Jan 2020 13:14:42 +0300 Subject: [PATCH] Add test && docs --- xirr.go | 27 +++++++++++++++----- xirr_test.go | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 7 deletions(-) create mode 100644 xirr_test.go diff --git a/xirr.go b/xirr.go index f9424ea..c5645b1 100644 --- a/xirr.go +++ b/xirr.go @@ -1,3 +1,7 @@ +/* +Goxirr is a simple implementation of a function for calculating +the Internal Rate of Return for irregular cash flow (XIRR). +*/ package goxirr import ( @@ -5,16 +9,22 @@ import ( "time" ) +//A Transaction represents a single transaction from a series of irregular payments. type Transaction struct { Date time.Time Cash float64 } -func Xirr(transactions []Transaction) float64 { +//Transactions represents a cash flow consisting of individual transactions +type Transactions []Transaction + +//Xirr return the Internal Rate of Return (IRR) for an irregular series of cash flows (XIRR) +func Xirr(transactions Transactions) float64 { var years []float64 - for _, ta := range transactions { - years = append(years, (ta.Date.Sub(transactions[0].Date).Hours()/24)/365) + for _, t := range transactions { + years = append(years, (t.Date.Sub(transactions[0].Date).Hours()/24)/365) } + residual := 1.0 step := 0.05 guess := 0.05 @@ -22,11 +32,13 @@ func Xirr(transactions []Transaction) float64 { limit := 10000 for math.Abs(residual) > epsilon && limit > 0 { - limit -= 1 + limit-- residual = 0.0 - for i, trans := range transactions { - residual += trans.Cash / math.Pow(guess, years[i]) + + for i, t := range transactions { + residual += t.Cash / math.Pow(guess, years[i]) } + if math.Abs(residual) > epsilon { if residual > 0 { guess += step @@ -36,5 +48,6 @@ func Xirr(transactions []Transaction) float64 { } } } - return (guess - 1) * 100 + + return math.Round(((guess-1)*100)*100) / 100 } diff --git a/xirr_test.go b/xirr_test.go new file mode 100644 index 0000000..bbe884b --- /dev/null +++ b/xirr_test.go @@ -0,0 +1,72 @@ +package goxirr + +import ( + "fmt" + "testing" + "time" +) + +func ExampleXirr() { + firstDate := time.Date(2019, time.January, 1, 0, 0, 0, 0, time.UTC) + t1 := Transaction{ + Date: firstDate, + Cash: -100, + } + t2 := Transaction{ + Date: firstDate.Add(time.Hour * 24 * 365), + Cash: 112, + } + + tas := Transactions{t1, t2} + fmt.Println(Xirr(tas)) + // Output: 12 +} + +func TestXirr(t *testing.T) { + type args struct { + transactions []Transaction + } + + var case1, case2, case3 args + case1.transactions = append(case1.transactions, Transaction{ + Date: time.Date(2019, time.January, 1, 0, 0, 0, 0, time.UTC), + Cash: -100, + }, Transaction{ + Date: time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC), + Cash: 200, + }) + + case2.transactions = append(case2.transactions, Transaction{ + Date: time.Date(2019, time.January, 1, 0, 0, 0, 0, time.UTC), + Cash: -100, + }, Transaction{ + Date: time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC), + Cash: 100, + }) + + case3.transactions = append(case3.transactions, Transaction{ + Date: time.Date(2019, time.January, 1, 0, 0, 0, 0, time.UTC), + Cash: -100, + }, Transaction{ + Date: time.Date(2020, time.January, 1, 0, 0, 0, 0, time.UTC), + Cash: 112, + }) + + tests := []struct { + name string + args args + want float64 + }{ + {name: "100%", args: case1, want: 100}, + {name: "0%", args: case2, want: 0.0}, + {name: "12%", args: case3, want: 12}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Xirr(tt.args.transactions); got != tt.want { + t.Errorf("Xirr() = %v, want %v", got, tt.want) + } + }) + } +}