From c4aa47c51c1e90002431577cb822477702afbc30 Mon Sep 17 00:00:00 2001 From: Kukulkan Date: Thu, 28 Jan 2021 22:56:45 +0530 Subject: [PATCH 01/27] Added PV an NPV computation --- reducing_utils.go | 57 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/reducing_utils.go b/reducing_utils.go index 7efcddd..f543670 100644 --- a/reducing_utils.go +++ b/reducing_utils.go @@ -168,3 +168,60 @@ func Fv(rate float64, nper int64, pmt float64, pv float64, when paymentperiod.Ty secondFactor := (1 + rate*when.Value()) * (factor - 1) / rate return -pv*factor - pmt*secondFactor } + +/* +Pv computes present value by solving the following equation: + + fv + + pv*(1+rate)**nper + + pmt*(1 + rate*when)/rate*((1 + rate)**nper - 1) == 0 + + +Params: + + fv : a future value + rate : an interest rate compounded once per period + nper : total number of periods + pmt : a (fixed) payment, paid either + at the beginning (when = 1) or the end (when = 0) of each period + when : specification of whether payment is made + at the beginning (when = 1) or the end + (when = 0) of each period + +References: + [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May). + Open Document Format for Office Applications (OpenDocument)v1.2, + Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version, + Pre-Draft 12. Organization for the Advancement of Structured Information + Standards (OASIS). Billerica, MA, USA. [ODT Document]. + Available: + http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula + OpenDocument-formula-20090508.odt +*/ +func Pv(rate float64, nper int64, pmt float64, fv float64, when paymentperiod.Type) float64 { + factor := math.Pow(1.0+float64(rate), float64(nper)) + secondFactor := (1 + rate*when.Value()) * (factor - 1) / rate + return (-fv + pmt*secondFactor) / factor +} + +/* +Npv computes the Net Present Value of a cash flow series + +Params: + + rate : a discount rate compounded once per period + values : the value of the cash flow for that time period. Values provided here must be an array of float64 + +References: + L. J. Gitman, “Principles of Managerial Finance, Brief,” 3rd ed., Addison-Wesley, 2003, pg. 346. + +*/ +func Npv(rate float64, values []float64) float64 { + internal_npv := float64(0.0) + current_rate_t := float64(1.0) + for _, current_val := range values { + internal_npv += (current_val / current_rate_t) + current_rate_t *= (1 + rate) + } + return internal_npv +} From c2a226ea5db014a0b35a320a5d48a3aae616febe Mon Sep 17 00:00:00 2001 From: Kukulkan Date: Fri, 29 Jan 2021 23:17:21 +0530 Subject: [PATCH 02/27] testing for pv and npv functions --- README.md | 40 ++++++++++++++++++++++-- reducing_utils.go | 2 +- reducing_utils_test.go | 69 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 59adfda..29b7897 100644 --- a/README.md +++ b/README.md @@ -20,10 +20,10 @@ which are as follows: | pmt | ✅ | | ppmt | ✅ | | nper | | -| pv | | +| pv | ✅ | | rate | | | irr | | -| npv | | +| npv | ✅ | | mirr | | # Index @@ -33,6 +33,8 @@ While the numpy-financial package contains a set of elementary financial functio + [Generated plot](#generated-plot) * [Fv(Future value)](#fv) + [Example(Fv)](#examplefv) + * [Pv(Present value)](#pv) + * [Npv(Net present value)](#npv) * [Pmt(Payment)](#pmt) + [Example(Pmt-Loan)](#examplepmt-loan) + [Example(Pmt-Investment)](#examplepmt-investment) @@ -150,6 +152,40 @@ func main() { [Run on go-playground](https://play.golang.org/p/l2-5aCHTBmH) +## Pv + +```go +func Pv(rate float64, nper int64, pmt float64, fv float64, when paymentperiod.Type) float64 +``` +Params: +```text +fv : a future value + rate : an interest rate compounded once per period + nper : total number of periods + pmt : a (fixed) payment, paid either + at the beginning (when = 1) or the end (when = 0) of each period + when : specification of whether payment is made + at the beginning (when = 1) or the end + (when = 0) of each period +``` + +Pv computes present value some periods(nper) before the future value. + + +## Npv + +```go +func Npv(rate float64, values []float64) float64 +``` +Params: +```text + rate : a discount rate compounded once per period + values : the value of the cash flow for that time period. Values provided here must be an array of float64 +``` + +Npv computes net present value based on the discount rate and the values of cash flow over the course of the cash flow period + + ## Pmt ```go diff --git a/reducing_utils.go b/reducing_utils.go index f543670..dc887a4 100644 --- a/reducing_utils.go +++ b/reducing_utils.go @@ -201,7 +201,7 @@ References: func Pv(rate float64, nper int64, pmt float64, fv float64, when paymentperiod.Type) float64 { factor := math.Pow(1.0+float64(rate), float64(nper)) secondFactor := (1 + rate*when.Value()) * (factor - 1) / rate - return (-fv + pmt*secondFactor) / factor + return (-fv - pmt*secondFactor) / factor } /* diff --git a/reducing_utils_test.go b/reducing_utils_test.go index 0cdc107..72f3360 100644 --- a/reducing_utils_test.go +++ b/reducing_utils_test.go @@ -232,3 +232,72 @@ func Test_PPmt(t *testing.T) { }) } } + +func Test_Pv(t *testing.T) { + type args struct { + rate float64 + nper int64 + pmt float64 + fv float64 + when paymentperiod.Type + } + tests := []struct { + name string + args args + want float64 + }{ + { + name: "success", args: args{ + rate: 0.24 / 12, + nper: 1 * 12, + pmt: -300, + fv: 1000, + when: paymentperiod.BEGINNING, + }, + want: 2447.561238019001, + }, { + name: "success", args: args{ + rate: 0.24 / 12, + nper: 1 * 12, + pmt: -300, + fv: 1000, + when: paymentperiod.ENDING, + }, + want: 2384.1091906934976, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Pv(tt.args.rate, tt.args.nper, tt.args.pmt, tt.args.fv, tt.args.when); got != tt.want { + t.Errorf("pv() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_Npv(t *testing.T) { + type args struct { + rate float64 + values []float64 + } + tests := []struct { + name string + args args + want float64 + }{ + { + name: "success", args: args{ + rate: 0.2, + values: []float64{-1000.0, 100.0, 100.0, 100.0}, + }, + want: -789.3518518518518, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := Npv(tt.args.rate, tt.args.values); got != tt.want { + t.Errorf("npv() = %v, want %v", got, tt.want) + } + }) + } +} From 5c4df0b245cd83221838d4face7dd41272db9fbe Mon Sep 17 00:00:00 2001 From: Kukulkan Date: Sat, 30 Jan 2021 22:15:12 +0530 Subject: [PATCH 03/27] missing examples added --- README.md | 57 +++++++++++++++++++++++++++++++++- example_reducing_utils_test.go | 25 +++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 29b7897..a79a306 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,9 @@ While the numpy-financial package contains a set of elementary financial functio * [Fv(Future value)](#fv) + [Example(Fv)](#examplefv) * [Pv(Present value)](#pv) - * [Npv(Net present value)](#npv) + + [Example(Pv)](#examplepv) + * [Npv(Net present value)](#npv) + + [Example(Npv)](#examplenpv) * [Pmt(Payment)](#pmt) + [Example(Pmt-Loan)](#examplepmt-loan) + [Example(Pmt-Investment)](#examplepmt-investment) @@ -171,6 +173,35 @@ fv : a future value Pv computes present value some periods(nper) before the future value. +### Example(Pv) + +If an investment has a 6% p.a. rate of return, compounded annually, and you wish to possess ₹ 1,49,716 at the end of 10 peroids while providing ₹ 10,000 per period, how much should you put as your initial deposit ? + +```go +package main + +import ( + "fmt" + gofinancial "github.com/razorpay/go-financial" + "github.com/razorpay/go-financial/enums/paymentperiod" + "math" +) + +func main() { + rate := 0.06 + nper := int64(10) + payment := float64(-10000) + fv := float64(149716) + when := paymentperiod.ENDING + + pv := gofinancial.Pv(rate, nper, payment, fv, when) + fmt.Printf("pv:%v", math.Round(pv)) + // Output: + // pv:-10000 +} +``` +[Run on go-playground](https://play.golang.org/p/hZhOLjkHdUF) + ## Npv @@ -185,6 +216,30 @@ Params: Npv computes net present value based on the discount rate and the values of cash flow over the course of the cash flow period +### Example(Npv) + +Given a rate of 0.281 per period and initial deposit of 100 followed by withdrawls of 39, 59, 55, 20. What is the net present value of the cash flow ? + +```go +package main + +import ( + "fmt" + gofinancial "github.com/razorpay/go-financial" + "math" +) + +func main() { + rate := 0.281 + values := []float64{-100, 39, 59, 55, 20} + npv := gofinancial.Npv(rate, values) + fmt.Printf("npv:%v", math.Round(npv)) + // Output: + // npv: -0.008478591638455768 +} +``` +[Run on go-playground](https://play.golang.org/p/cRORiLA1AQN) + ## Pmt diff --git a/example_reducing_utils_test.go b/example_reducing_utils_test.go index 08db2ef..e7d03e6 100644 --- a/example_reducing_utils_test.go +++ b/example_reducing_utils_test.go @@ -153,3 +153,28 @@ func ExamplePPmt_loan() { // period:23 principal:-4846 // period:24 principal:-4918 } + +// If an investment has a 6% p.a. rate of return, compounded annually, and you wish to possess ₹ 1,49,716 at the end of 10 peroids while providing ₹ 10,000 per period, +// how much should you put as your initial deposit ? +func ExamplePv() { + rate := 0.06 + nper := int64(10) + payment := float64(-10000) + fv := float64(149716) + when := paymentperiod.ENDING + + pv := gofinancial.Pv(rate, nper, payment, fv, when) + fmt.Printf("pv:%v", math.Round(pv)) + // Output: + // pv:-10000 +} + +//Given a rate of 0.281 per period and initial deposit of 100 followed by withdrawls of 39, 59, 55, 20. What is the net present value of the cash flow ? +func ExampleNpv() { + rate := 0.281 + values := []float64{-100, 39, 59, 55, 20} + npv := gofinancial.Npv(rate, values) + fmt.Printf("npv:%v", math.Round(npv)) + // Output: + // npv: -0.008478591638455768 +} From ae9c788b70fd3f51fa039e0ad6babdb4c5533730 Mon Sep 17 00:00:00 2001 From: Kukulkan Date: Sat, 30 Jan 2021 22:21:20 +0530 Subject: [PATCH 04/27] comment line violation --- example_reducing_utils_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/example_reducing_utils_test.go b/example_reducing_utils_test.go index e7d03e6..651ff44 100644 --- a/example_reducing_utils_test.go +++ b/example_reducing_utils_test.go @@ -169,7 +169,8 @@ func ExamplePv() { // pv:-10000 } -//Given a rate of 0.281 per period and initial deposit of 100 followed by withdrawls of 39, 59, 55, 20. What is the net present value of the cash flow ? +//Given a rate of 0.281 per period and initial deposit of 100 followed by withdrawls of 39, 59, 55, 20. +// What is the net present value of the cash flow ? func ExampleNpv() { rate := 0.281 values := []float64{-100, 39, 59, 55, 20} From ad35b8a36ed1e1839e037e7afb8983a68d77e128 Mon Sep 17 00:00:00 2001 From: Kukulkan Date: Sat, 30 Jan 2021 22:29:52 +0530 Subject: [PATCH 05/27] gofumpt linting --- example_reducing_utils_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example_reducing_utils_test.go b/example_reducing_utils_test.go index 651ff44..2607c38 100644 --- a/example_reducing_utils_test.go +++ b/example_reducing_utils_test.go @@ -169,7 +169,7 @@ func ExamplePv() { // pv:-10000 } -//Given a rate of 0.281 per period and initial deposit of 100 followed by withdrawls of 39, 59, 55, 20. +// Given a rate of 0.281 per period and initial deposit of 100 followed by withdrawls of 39, 59, 55, 20. // What is the net present value of the cash flow ? func ExampleNpv() { rate := 0.281 From b850524a321816b873441e1a5deb8fe14cf5ad2c Mon Sep 17 00:00:00 2001 From: Kukulkan Date: Mon, 1 Feb 2021 09:24:36 +0530 Subject: [PATCH 06/27] indentation oopsie --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a79a306..5b43042 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ func Pv(rate float64, nper int64, pmt float64, fv float64, when paymentperiod.Ty ``` Params: ```text -fv : a future value + fv : a future value rate : an interest rate compounded once per period nper : total number of periods pmt : a (fixed) payment, paid either @@ -196,8 +196,8 @@ func main() { pv := gofinancial.Pv(rate, nper, payment, fv, when) fmt.Printf("pv:%v", math.Round(pv)) - // Output: - // pv:-10000 + // Output: + // pv:-10000 } ``` [Run on go-playground](https://play.golang.org/p/hZhOLjkHdUF) @@ -234,8 +234,8 @@ func main() { values := []float64{-100, 39, 59, 55, 20} npv := gofinancial.Npv(rate, values) fmt.Printf("npv:%v", math.Round(npv)) - // Output: - // npv: -0.008478591638455768 + // Output: + // npv: -0.008478591638455768 } ``` [Run on go-playground](https://play.golang.org/p/cRORiLA1AQN) From 90ef39ce6411f76ff13421017c3832bb72588c35 Mon Sep 17 00:00:00 2001 From: Kukulkan Date: Mon, 1 Feb 2021 11:53:32 +0530 Subject: [PATCH 07/27] new play.golang links --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5b43042..9528d38 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ func main() { // pv:-10000 } ``` -[Run on go-playground](https://play.golang.org/p/hZhOLjkHdUF) +[Run on go-playground](https://play.golang.org/p/xe1dXKxDEcY) ## Npv @@ -238,7 +238,7 @@ func main() { // npv: -0.008478591638455768 } ``` -[Run on go-playground](https://play.golang.org/p/cRORiLA1AQN) +[Run on go-playground](https://play.golang.org/p/ma1it8-rFzn) ## Pmt From 089af1ebcdd75f7f3ef45072439f1c6242d27656 Mon Sep 17 00:00:00 2001 From: Kukulkan Date: Tue, 2 Feb 2021 09:10:17 +0530 Subject: [PATCH 08/27] variable naming --- reducing_utils.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/reducing_utils.go b/reducing_utils.go index dc887a4..0defce7 100644 --- a/reducing_utils.go +++ b/reducing_utils.go @@ -209,7 +209,7 @@ Npv computes the Net Present Value of a cash flow series Params: - rate : a discount rate compounded once per period + rate : a discount rate applied once per period values : the value of the cash flow for that time period. Values provided here must be an array of float64 References: @@ -217,11 +217,11 @@ References: */ func Npv(rate float64, values []float64) float64 { - internal_npv := float64(0.0) - current_rate_t := float64(1.0) + internalNpv := float64(0.0) + currentRateT := float64(1.0) for _, current_val := range values { - internal_npv += (current_val / current_rate_t) - current_rate_t *= (1 + rate) + internalNpv += (current_val / currentRateT) + currentRateT *= (1 + rate) } - return internal_npv + return internalNpv } From ac619a2fe0dbe9e85961a44a72453395c6096149 Mon Sep 17 00:00:00 2001 From: Kukulkan Date: Wed, 3 Feb 2021 11:22:52 +0530 Subject: [PATCH 09/27] better example --- example_reducing_utils_test.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/example_reducing_utils_test.go b/example_reducing_utils_test.go index 2607c38..ae69983 100644 --- a/example_reducing_utils_test.go +++ b/example_reducing_utils_test.go @@ -169,13 +169,13 @@ func ExamplePv() { // pv:-10000 } -// Given a rate of 0.281 per period and initial deposit of 100 followed by withdrawls of 39, 59, 55, 20. +// Given a discount rate of 8% per period and initial deposit of 40000 followed by withdrawls of 5000, 8000, 12000 and 30000. // What is the net present value of the cash flow ? func ExampleNpv() { - rate := 0.281 - values := []float64{-100, 39, 59, 55, 20} + rate := 0.08 + values := []float64{-40000, 5000, 8000, 12000, 30000} npv := gofinancial.Npv(rate, values) fmt.Printf("npv:%v", math.Round(npv)) // Output: - // npv: -0.008478591638455768 + // npv:3065 } From 04c9309961b5d2d416c8905764755071686f411b Mon Sep 17 00:00:00 2001 From: Kaustubh Joshi Date: Sat, 20 Mar 2021 13:51:04 +0530 Subject: [PATCH 10/27] Rate calculation --- README.md | 72 +++++++++++++++++++++++++++++++++++-- reducing_utils.go | 80 ++++++++++++++++++++++++++++++++++++++++++ reducing_utils_test.go | 47 +++++++++++++++++++++++++ 3 files changed, 196 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 82b260d..863b95d 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ which are as follows: | ppmt | ✅ | Computes principal payment for a loan| | nper | ✅ | Computes the number of periodic payments| | pv | ✅ | Computes the present value of a payment| -| rate | | Computes the rate of interest per period| +| rate | ✅ | Computes the rate of interest per period| | irr | | Computes the internal rate of return| | npv | ✅ | Computes the net present value of a series of cash flow| | mirr | | Computes the modified internal rate of return| @@ -45,9 +45,11 @@ While the numpy-financial package contains a set of elementary financial functio + [Example(IPmt-Investment)](#exampleipmt-investment) * [PPmt(Principal Payment)](#ppmt) + [Example(PPmt-Loan)](#exampleppmt-loan) - * [Nper(Number of payments)](#nper) + * [Nper(Number of payments)](#nper) + [Example(Nper-Loan)](#examplenper-loan) - + * [Rate(Interest Rate)](#rate) + + [Example(Rate-Investment)](#examplerate-investment) + Detailed documentation is available at [godoc](https://godoc.org/github.com/razorpay/go-financial). ## Amortisation(Generate Table) @@ -551,3 +553,67 @@ func main() { } ``` [Run on go-playground](https://play.golang.org/p/5LIuZiRcbMy) + +## Rate + +```go +func Rate(pv, fv, pmt float64, nper int64, when paymentperiod.Type, params ...float64) (float64, bool) +``` +Params: +```text +pv : a present value +fv : a future value +pmt : a (fixed) payment, paid either at the beginning (when = 1) + or the end (when = 0) of each period +pv : a present value +fv : a future value +nper : total number of periods to be compounded for +when : specification of whether payment is made at the beginning (when = 1) + or the end (when = 0) of each period +params : varadic variable with following specifications: + 0th index -> total number of iterations for which function should run (default is 100) + 1st index -> an initial guess amount to start from (default is 0.1) + 2nd index -> tolerance threshold (default is 1e-6) +``` + +Returns: +```text +rate : a float64 value for the corresponding rate +valid : returns true if rate difference is less than the threshold (returns false conversely) +``` + +Rate computes the interest rate to ensure a balanced cashflow equation + +### Example(Rate-Investment) + +If an investment of $2000 is done and an amount of $100 is added at the start of each period, for what periodic interest rate would the invester be able to withdraw $3000 after the end of 4 periods ? + +```go +package main + +import ( + "fmt" + "math" + + gofinancial "github.com/razorpay/go-financial" + "github.com/razorpay/go-financial/enums/paymentperiod" +) + +func main() { + fv := float64(-3000) + pmt := float64(100) + pv := float64(2000) + when := paymentperiod.BEGINNING + nper := int64(4) + + rate, ok := gofinancial.Rate(pv, fv, pmt, nper, when) + if ok { + fmt.Printf("rate:%v ", rate) + } else { + fmt.Printf("NaN") + } + // Output: + // rate: 0.06106257989825202 +} +``` +[Run on go-playground](https://play.golang.org/p/aExBDDafHH4) \ No newline at end of file diff --git a/reducing_utils.go b/reducing_utils.go index bf0ac91..d376087 100644 --- a/reducing_utils.go +++ b/reducing_utils.go @@ -258,3 +258,83 @@ func Npv(rate float64, values []float64) float64 { } return internalNpv } + +/* +This function computs the ratio that is used to find a single value that sets the non-liner equation to zero + +Params: + nper : number of compounding periods + pmt : a (fixed) payment, paid either + at the beginning (when = 1) or the end (when = 0) of each period + pv : a present value + fv : a future value + when : specification of whether payment is made + at the beginning (when = 1) or the end (when = 0) of each period + curRate: the rate compounded once per period rate +*/ +func getRateRatio(pv, fv, pmt, curRate float64, nper int64, when paymentperiod.Type) float64 { + f0 := math.Pow((1 + curRate), float64(nper)) + f1 := f0 / (1 + curRate) + y := fv + pv*f0 + pmt*(1.0+curRate*when.Value())*(f0-1)/curRate + derivative := (float64(nper) * f1 * pv) + (pmt * ((when.Value() * (f0 - 1) / curRate) + ((1.0 + curRate*when.Value()) * ((curRate*float64(nper)*f1 - f0 + 1) / (curRate * curRate))))) + + return y / derivative +} + +/* +Rate computes the Interest rate per period by running Newton Rapson to find an approximate value for: + +y = fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate*((1+rate)**nper-1) +(0 - y_previous) /(rate - rate_previous) = dy/drate {derivative of y w.r.t. rate} + + +Params: + nper : number of compounding periods + pmt : a (fixed) payment, paid either + at the beginning (when = 1) or the end (when = 0) of each period + pv : a present value + fv : a future value + when : specification of whether payment is made + at the beginning (when = 1) or the end (when = 0) of each period + params : optional parameters for maxIter, tolerance, and initialGuess +References: + Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May). Open Document + Format for Office Applications (OpenDocument)v1.2, Part 2: Recalculated + Formula (OpenFormula) Format - Annotated Version, Pre-Draft 12. + Organization for the Advancement of Structured Information Standards + (OASIS). Billerica, MA, USA. [ODT Document]. Available: + http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula + OpenDocument-formula-20090508.odt +*/ + +func Rate(pv, fv, pmt float64, nper int64, when paymentperiod.Type, params ...float64) (float64, bool) { + initialGuess := 0.1 + tolerance := 1e-6 + maxIter := 100 + + for index, value := range params { + switch index { + case 0: + maxIter = int(value) + case 1: + initialGuess = value + case 2: + tolerance = value + default: + //no more values to be read + } + } + + var nextIterRate, currentIterRate float64 = initialGuess, initialGuess + + for iter := 0; iter < maxIter; iter++ { + currentIterRate = nextIterRate + nextIterRate = currentIterRate - getRateRatio(pv, fv, pmt, currentIterRate, nper, when) + } + + if math.Abs(nextIterRate-currentIterRate) > tolerance { + return nextIterRate, false + } + + return nextIterRate, true +} diff --git a/reducing_utils_test.go b/reducing_utils_test.go index fa69c91..3cffac3 100644 --- a/reducing_utils_test.go +++ b/reducing_utils_test.go @@ -337,3 +337,50 @@ func Test_Nper(t *testing.T) { }) } } + +func Test_rate(t *testing.T) { + //res, err := Rate(2000.0, -3000.0, 100.0, 4, 1.0) + //0.06106257989825202 + //res, err := Rate(-3000, 1000, 500, 2, 1.0) + //-0.25968757625671507 + + type args struct { + pv float64 + fv float64 + pmt float64 + nper int64 + when paymentperiod.Type + } + tests := []struct { + name string + args args + want float64 + }{ + { + name: "success", args: args{ + pv: 2000, + fv: -3000, + pmt: 100, + nper: 4, + when: paymentperiod.BEGINNING, + }, + want: 0.06106257989825202, + }, { + name: "success", args: args{ + pv: -3000, + fv: 1000, + pmt: 500, + nper: 2, + when: paymentperiod.BEGINNING, + }, + want: -0.25968757625671507, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got, _ := Rate(tt.args.pv, tt.args.fv, tt.args.pmt, tt.args.nper, tt.args.when); assertions.ShouldAlmostEqual(got, tt.want) != "" { + t.Errorf("fv() = %v, want %v", got, tt.want) + } + }) + } +} From bb23dc899945691e817c06e5126641e490edba31 Mon Sep 17 00:00:00 2001 From: Kaustubh Joshi Date: Mon, 22 Mar 2021 15:32:43 +0530 Subject: [PATCH 11/27] modified as per decimal values --- README.md | 15 +++++++------- reducing_utils.go | 44 ++++++++++++++++++++++++++++-------------- reducing_utils_test.go | 33 ++++++++++++++----------------- 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/README.md b/README.md index 8a3603c..d60d2a3 100644 --- a/README.md +++ b/README.md @@ -600,7 +600,7 @@ params : varadic variable with following specifications: Returns: ```text -rate : a float64 value for the corresponding rate +rate : a value for the corresponding rate valid : returns true if rate difference is less than the threshold (returns false conversely) ``` @@ -615,18 +615,17 @@ package main import ( "fmt" - "math" - gofinancial "github.com/razorpay/go-financial" "github.com/razorpay/go-financial/enums/paymentperiod" + "github.com/shopspring/decimal" ) func main() { - fv := float64(-3000) - pmt := float64(100) - pv := float64(2000) + fv := decimal.NewFromFloat(-3000) + pmt := decimal.NewFromFloat(100) + pv := decimal.NewFromFloat(2000) when := paymentperiod.BEGINNING - nper := int64(4) + nper := decimal.NewFromInt(4) rate, ok := gofinancial.Rate(pv, fv, pmt, nper, when) if ok { @@ -638,4 +637,4 @@ func main() { // rate: 0.06106257989825202 } ``` -[Run on go-playground](https://play.golang.org/p/aExBDDafHH4) +[Run on go-playground](https://play.golang.org/p/I655r9QWu0H) diff --git a/reducing_utils.go b/reducing_utils.go index ba33373..d99ad56 100644 --- a/reducing_utils.go +++ b/reducing_utils.go @@ -304,13 +304,27 @@ Params: at the beginning (when = 1) or the end (when = 0) of each period curRate: the rate compounded once per period rate */ -func getRateRatio(pv, fv, pmt, curRate float64, nper int64, when paymentperiod.Type) float64 { - f0 := math.Pow((1 + curRate), float64(nper)) - f1 := f0 / (1 + curRate) - y := fv + pv*f0 + pmt*(1.0+curRate*when.Value())*(f0-1)/curRate - derivative := (float64(nper) * f1 * pv) + (pmt * ((when.Value() * (f0 - 1) / curRate) + ((1.0 + curRate*when.Value()) * ((curRate*float64(nper)*f1 - f0 + 1) / (curRate * curRate))))) - - return y / derivative +func getRateRatio(pv, fv, pmt, curRate decimal.Decimal, nper int64, when paymentperiod.Type) decimal.Decimal { + oneInDecimal := decimal.NewFromInt(1) + whenInDecimal := decimal.NewFromInt(when.Value()) + nperInDecimal := decimal.NewFromInt(nper) + + f0 := curRate.Add(oneInDecimal).Pow(decimal.NewFromInt(nper)) // f0 := math.Pow((1 + curRate), float64(nper)) + f1 := f0.Div(curRate.Add(oneInDecimal)) // f1 := f0 / (1 + curRate) + + yP0 := pv.Mul(f0) + yP1 := pmt.Mul(oneInDecimal.Add(curRate.Mul(whenInDecimal))).Mul(f0.Sub(oneInDecimal)).Div(curRate) + y := fv.Add(yP0).Add(yP1) // y := fv + pv*f0 + pmt*(1.0+curRate*when.Value())*(f0-1)/curRate + + derivativeP0 := nperInDecimal.Mul(f1).Mul(pv) + derivativeP1 := pmt.Mul(whenInDecimal).Mul(f0.Sub(oneInDecimal)).Div(curRate) + derivativeP2s0 := oneInDecimal.Add(curRate.Mul(whenInDecimal)) + derivativeP2s1 := ((curRate.Mul((nperInDecimal)).Mul(f1)).Sub(f0).Add(oneInDecimal)).Div(curRate.Mul(curRate)) + derivativeP2 := derivativeP2s0.Mul(derivativeP2s1) + derivative := derivativeP0.Add(derivativeP1).Add(derivativeP2) + // derivative := (float64(nper) * f1 * pv) + (pmt * ((when.Value() * (f0 - 1) / curRate) + ((1.0 + curRate*when.Value()) * ((curRate*float64(nper)*f1 - f0 + 1) / (curRate * curRate))))) + + return y.Div(derivative) } /* @@ -339,32 +353,32 @@ References: OpenDocument-formula-20090508.odt */ -func Rate(pv, fv, pmt float64, nper int64, when paymentperiod.Type, params ...float64) (float64, bool) { - initialGuess := 0.1 - tolerance := 1e-6 +func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, params ...decimal.Decimal) (decimal.Decimal, bool) { + initialGuess := decimal.NewFromFloat(0.1) + tolerance := decimal.NewFromFloat(1e-6) maxIter := 100 for index, value := range params { switch index { case 0: - maxIter = int(value) + maxIter = int(value.IntPart()) case 1: initialGuess = value case 2: tolerance = value default: - //no more values to be read + // no more values to be read } } - var nextIterRate, currentIterRate float64 = initialGuess, initialGuess + var nextIterRate, currentIterRate decimal.Decimal = initialGuess, initialGuess for iter := 0; iter < maxIter; iter++ { currentIterRate = nextIterRate - nextIterRate = currentIterRate - getRateRatio(pv, fv, pmt, currentIterRate, nper, when) + nextIterRate = currentIterRate.Sub(getRateRatio(pv, fv, pmt, currentIterRate, nper, when)) } - if math.Abs(nextIterRate-currentIterRate) > tolerance { + if nextIterRate.Sub(currentIterRate).Abs().GreaterThan(tolerance) { return nextIterRate, false } diff --git a/reducing_utils_test.go b/reducing_utils_test.go index 0ae0c0b..afd350a 100644 --- a/reducing_utils_test.go +++ b/reducing_utils_test.go @@ -357,47 +357,42 @@ func Test_Nper(t *testing.T) { } func Test_rate(t *testing.T) { - //res, err := Rate(2000.0, -3000.0, 100.0, 4, 1.0) - //0.06106257989825202 - //res, err := Rate(-3000, 1000, 500, 2, 1.0) - //-0.25968757625671507 - type args struct { - pv float64 - fv float64 - pmt float64 + pv decimal.Decimal + fv decimal.Decimal + pmt decimal.Decimal nper int64 when paymentperiod.Type } tests := []struct { name string args args - want float64 + want decimal.Decimal }{ { name: "success", args: args{ - pv: 2000, - fv: -3000, - pmt: 100, + pv: decimal.NewFromInt(2000), + fv: decimal.NewFromInt(-3000), + pmt: decimal.NewFromInt(100), nper: 4, when: paymentperiod.BEGINNING, }, - want: 0.06106257989825202, + want: decimal.NewFromFloat(0.06106257989825202), }, { name: "success", args: args{ - pv: -3000, - fv: 1000, - pmt: 500, + pv: decimal.NewFromInt(-3000), + fv: decimal.NewFromInt(1000), + pmt: decimal.NewFromInt(500), nper: 2, when: paymentperiod.BEGINNING, }, - want: -0.25968757625671507, + want: decimal.NewFromFloat(-0.25968757625671507), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got, _ := Rate(tt.args.pv, tt.args.fv, tt.args.pmt, tt.args.nper, tt.args.when); assertions.ShouldAlmostEqual(got, tt.want) != "" { - t.Errorf("fv() = %v, want %v", got, tt.want) + if got, isValid := Rate(tt.args.pv, tt.args.fv, tt.args.pmt, tt.args.nper, tt.args.when); !isValid || isAlmostEqual(got, tt.want, decimal.NewFromFloat(precision)) != nil { + t.Errorf("rate() = (%v,%v), want (%v,%v)", got, isValid, tt.want, true) } }) } From 34480761bdaa4d1e89d1da1cb3b4c8b7c09687cf Mon Sep 17 00:00:00 2001 From: Kaustubh Joshi Date: Sun, 28 Mar 2021 22:06:35 +0530 Subject: [PATCH 12/27] minor modifications --- README.md | 20 ++++++++++---------- reducing_utils.go | 25 +++++-------------------- reducing_utils_test.go | 41 +++++++++++++++++++++++++---------------- 3 files changed, 40 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index d60d2a3..cf6dba4 100644 --- a/README.md +++ b/README.md @@ -579,7 +579,7 @@ func main() { ## Rate ```go -func Rate(pv, fv, pmt float64, nper int64, when paymentperiod.Type, params ...float64) (float64, bool) +func Rate(pv, fv, pmt decimal, nper int64, when paymentperiod.Type, params ...float64) (decimal, bool) ``` Params: ```text @@ -587,15 +587,12 @@ pv : a present value fv : a future value pmt : a (fixed) payment, paid either at the beginning (when = 1) or the end (when = 0) of each period -pv : a present value -fv : a future value nper : total number of periods to be compounded for when : specification of whether payment is made at the beginning (when = 1) or the end (when = 0) of each period -params : varadic variable with following specifications: - 0th index -> total number of iterations for which function should run (default is 100) - 1st index -> an initial guess amount to start from (default is 0.1) - 2nd index -> tolerance threshold (default is 1e-6) +maxIter : total number of iterations for which function should run +tolerance : tolerance threshold for acceptable result +initialGuess : an initial guess amount to start from ``` Returns: @@ -608,7 +605,7 @@ Rate computes the interest rate to ensure a balanced cashflow equation ### Example(Rate-Investment) -If an investment of $2000 is done and an amount of $100 is added at the start of each period, for what periodic interest rate would the invester be able to withdraw $3000 after the end of 4 periods ? +If an investment of $2000 is done and an amount of $100 is added at the start of each period, for what periodic interest rate would the invester be able to withdraw $3000 after the end of 4 periods ? (assuming 100 iterations, 1e-6 threshold and 0.1 as initial guessing point) ```go package main @@ -626,8 +623,11 @@ func main() { pv := decimal.NewFromFloat(2000) when := paymentperiod.BEGINNING nper := decimal.NewFromInt(4) + maxIter := 100 + tolerance := decimal.NewFromFloat(1e-6) + initialGuess := decimal.NewFromFloat(0.1), - rate, ok := gofinancial.Rate(pv, fv, pmt, nper, when) + rate, ok := gofinancial.Rate(pv, fv, pmt, nper, when, maxIter, tolerance, initialGuess) if ok { fmt.Printf("rate:%v ", rate) } else { @@ -637,4 +637,4 @@ func main() { // rate: 0.06106257989825202 } ``` -[Run on go-playground](https://play.golang.org/p/I655r9QWu0H) +[Run on go-playground](https://play.golang.org/p/9khVcHwjkh5) diff --git a/reducing_utils.go b/reducing_utils.go index d99ad56..b869aef 100644 --- a/reducing_utils.go +++ b/reducing_utils.go @@ -342,7 +342,9 @@ Params: fv : a future value when : specification of whether payment is made at the beginning (when = 1) or the end (when = 0) of each period - params : optional parameters for maxIter, tolerance, and initialGuess + maxIter : total number of iterations to perform calculation + tolerance : accept result only if the difference in iteration values is less than the tolerance provided + initialGuess : an initial point to start approximating from References: Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May). Open Document Format for Office Applications (OpenDocument)v1.2, Part 2: Recalculated @@ -353,27 +355,10 @@ References: OpenDocument-formula-20090508.odt */ -func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, params ...decimal.Decimal) (decimal.Decimal, bool) { - initialGuess := decimal.NewFromFloat(0.1) - tolerance := decimal.NewFromFloat(1e-6) - maxIter := 100 - - for index, value := range params { - switch index { - case 0: - maxIter = int(value.IntPart()) - case 1: - initialGuess = value - case 2: - tolerance = value - default: - // no more values to be read - } - } - +func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, maxIter int64, tolerance, initialGuess decimal.Decimal) (decimal.Decimal, bool) { var nextIterRate, currentIterRate decimal.Decimal = initialGuess, initialGuess - for iter := 0; iter < maxIter; iter++ { + for iter := int64(0); iter < maxIter; iter++ { currentIterRate = nextIterRate nextIterRate = currentIterRate.Sub(getRateRatio(pv, fv, pmt, currentIterRate, nper, when)) } diff --git a/reducing_utils_test.go b/reducing_utils_test.go index afd350a..46f0f18 100644 --- a/reducing_utils_test.go +++ b/reducing_utils_test.go @@ -358,11 +358,14 @@ func Test_Nper(t *testing.T) { func Test_rate(t *testing.T) { type args struct { - pv decimal.Decimal - fv decimal.Decimal - pmt decimal.Decimal - nper int64 - when paymentperiod.Type + pv decimal.Decimal + fv decimal.Decimal + pmt decimal.Decimal + nper int64 + when paymentperiod.Type + maxIter int64 + tolerance decimal.Decimal + initialGuess decimal.Decimal } tests := []struct { name string @@ -371,27 +374,33 @@ func Test_rate(t *testing.T) { }{ { name: "success", args: args{ - pv: decimal.NewFromInt(2000), - fv: decimal.NewFromInt(-3000), - pmt: decimal.NewFromInt(100), - nper: 4, - when: paymentperiod.BEGINNING, + pv: decimal.NewFromInt(2000), + fv: decimal.NewFromInt(-3000), + pmt: decimal.NewFromInt(100), + nper: 4, + when: paymentperiod.BEGINNING, + maxIter: 100, + tolerance: decimal.NewFromFloat(1e-7), + initialGuess: decimal.NewFromFloat(0.1), }, want: decimal.NewFromFloat(0.06106257989825202), }, { name: "success", args: args{ - pv: decimal.NewFromInt(-3000), - fv: decimal.NewFromInt(1000), - pmt: decimal.NewFromInt(500), - nper: 2, - when: paymentperiod.BEGINNING, + pv: decimal.NewFromInt(-3000), + fv: decimal.NewFromInt(1000), + pmt: decimal.NewFromInt(500), + nper: 2, + when: paymentperiod.BEGINNING, + maxIter: 100, + tolerance: decimal.NewFromFloat(1e-7), + initialGuess: decimal.NewFromFloat(0.1), }, want: decimal.NewFromFloat(-0.25968757625671507), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got, isValid := Rate(tt.args.pv, tt.args.fv, tt.args.pmt, tt.args.nper, tt.args.when); !isValid || isAlmostEqual(got, tt.want, decimal.NewFromFloat(precision)) != nil { + if got, isValid := Rate(tt.args.pv, tt.args.fv, tt.args.pmt, tt.args.nper, tt.args.when, tt.args.maxIter, tt.args.tolerance, tt.args.initialGuess); !isValid || isAlmostEqual(got, tt.want, decimal.NewFromFloat(precision)) != nil { t.Errorf("rate() = (%v,%v), want (%v,%v)", got, isValid, tt.want, true) } }) From 0e7e689c0b04899276c68925954f5e2d7416cb71 Mon Sep 17 00:00:00 2001 From: Kaustubh Joshi Date: Sun, 11 Apr 2021 19:53:33 +0530 Subject: [PATCH 13/27] required modifications --- README.md | 2 +- reducing_utils_test.go | 32 ++++++++++++++++++++++++-------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index cf6dba4..1aa2fbd 100644 --- a/README.md +++ b/README.md @@ -579,7 +579,7 @@ func main() { ## Rate ```go -func Rate(pv, fv, pmt decimal, nper int64, when paymentperiod.Type, params ...float64) (decimal, bool) +func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, maxIter int64, tolerance, initialGuess decimal.Decimal) (decimal.Decimal, bool) ``` Params: ```text diff --git a/reducing_utils_test.go b/reducing_utils_test.go index 46f0f18..8acb90b 100644 --- a/reducing_utils_test.go +++ b/reducing_utils_test.go @@ -356,7 +356,7 @@ func Test_Nper(t *testing.T) { } } -func Test_rate(t *testing.T) { +func Test_Rate(t *testing.T) { type args struct { pv decimal.Decimal fv decimal.Decimal @@ -368,9 +368,10 @@ func Test_rate(t *testing.T) { initialGuess decimal.Decimal } tests := []struct { - name string - args args - want decimal.Decimal + name string + args args + wantValue decimal.Decimal + validity bool }{ { name: "success", args: args{ @@ -383,7 +384,8 @@ func Test_rate(t *testing.T) { tolerance: decimal.NewFromFloat(1e-7), initialGuess: decimal.NewFromFloat(0.1), }, - want: decimal.NewFromFloat(0.06106257989825202), + wantValue: decimal.NewFromFloat(0.06106257989825202), + validity: true, }, { name: "success", args: args{ pv: decimal.NewFromInt(-3000), @@ -395,13 +397,27 @@ func Test_rate(t *testing.T) { tolerance: decimal.NewFromFloat(1e-7), initialGuess: decimal.NewFromFloat(0.1), }, - want: decimal.NewFromFloat(-0.25968757625671507), + wantValue: decimal.NewFromFloat(-0.25968757625671507), + validity: true, + }, { + name: "failure", args: args{ + pv: decimal.NewFromInt(3000), + fv: decimal.NewFromInt(1000), + pmt: decimal.NewFromInt(100), + nper: 2, + when: paymentperiod.BEGINNING, + maxIter: 100, + tolerance: decimal.NewFromFloat(1e-7), + initialGuess: decimal.NewFromFloat(0.1), + }, + wantValue: decimal.NewFromFloat(0.4907342754506849), + validity: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got, isValid := Rate(tt.args.pv, tt.args.fv, tt.args.pmt, tt.args.nper, tt.args.when, tt.args.maxIter, tt.args.tolerance, tt.args.initialGuess); !isValid || isAlmostEqual(got, tt.want, decimal.NewFromFloat(precision)) != nil { - t.Errorf("rate() = (%v,%v), want (%v,%v)", got, isValid, tt.want, true) + if got, isValid := Rate(tt.args.pv, tt.args.fv, tt.args.pmt, tt.args.nper, tt.args.when, tt.args.maxIter, tt.args.tolerance, tt.args.initialGuess); isValid != tt.validity || isAlmostEqual(got, tt.wantValue, decimal.NewFromFloat(precision)) != nil { + t.Errorf("Rate returned (%v,%v), wanted (%v,%v)", got, isValid, tt.wantValue, tt.validity) } }) } From ddc5f1a6e0b46371b392ea452d33d10819de6e8d Mon Sep 17 00:00:00 2001 From: Kaustubh Joshi Date: Sun, 11 Apr 2021 19:55:00 +0530 Subject: [PATCH 14/27] required modifications 2 --- reducing_utils_test.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/reducing_utils_test.go b/reducing_utils_test.go index 8acb90b..41cbd40 100644 --- a/reducing_utils_test.go +++ b/reducing_utils_test.go @@ -368,10 +368,10 @@ func Test_Rate(t *testing.T) { initialGuess decimal.Decimal } tests := []struct { - name string - args args - wantValue decimal.Decimal - validity bool + name string + args args + want decimal.Decimal + validity bool }{ { name: "success", args: args{ @@ -384,8 +384,8 @@ func Test_Rate(t *testing.T) { tolerance: decimal.NewFromFloat(1e-7), initialGuess: decimal.NewFromFloat(0.1), }, - wantValue: decimal.NewFromFloat(0.06106257989825202), - validity: true, + want: decimal.NewFromFloat(0.06106257989825202), + validity: true, }, { name: "success", args: args{ pv: decimal.NewFromInt(-3000), @@ -397,8 +397,8 @@ func Test_Rate(t *testing.T) { tolerance: decimal.NewFromFloat(1e-7), initialGuess: decimal.NewFromFloat(0.1), }, - wantValue: decimal.NewFromFloat(-0.25968757625671507), - validity: true, + want: decimal.NewFromFloat(-0.25968757625671507), + validity: true, }, { name: "failure", args: args{ pv: decimal.NewFromInt(3000), @@ -410,14 +410,14 @@ func Test_Rate(t *testing.T) { tolerance: decimal.NewFromFloat(1e-7), initialGuess: decimal.NewFromFloat(0.1), }, - wantValue: decimal.NewFromFloat(0.4907342754506849), - validity: false, + want: decimal.NewFromFloat(0.4907342754506849), + validity: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got, isValid := Rate(tt.args.pv, tt.args.fv, tt.args.pmt, tt.args.nper, tt.args.when, tt.args.maxIter, tt.args.tolerance, tt.args.initialGuess); isValid != tt.validity || isAlmostEqual(got, tt.wantValue, decimal.NewFromFloat(precision)) != nil { - t.Errorf("Rate returned (%v,%v), wanted (%v,%v)", got, isValid, tt.wantValue, tt.validity) + if got, isValid := Rate(tt.args.pv, tt.args.fv, tt.args.pmt, tt.args.nper, tt.args.when, tt.args.maxIter, tt.args.tolerance, tt.args.initialGuess); isValid != tt.validity || isAlmostEqual(got, tt.want, decimal.NewFromFloat(precision)) != nil { + t.Errorf("Rate returned (%v,%v), wanted (%v,%v)", got, isValid, tt.want, tt.validity) } }) } From 487899761649c558f6ad74c0cd07365b81b35bff Mon Sep 17 00:00:00 2001 From: Kaustubh Joshi Date: Mon, 19 Apr 2021 10:54:12 +0530 Subject: [PATCH 15/27] required fixes and comment modifications --- error_codes.go | 1 + reducing_utils.go | 54 ++++++++++++++++++++++-------------------- reducing_utils_test.go | 24 +++++++++---------- 3 files changed, 41 insertions(+), 38 deletions(-) diff --git a/error_codes.go b/error_codes.go index c588037..0840413 100644 --- a/error_codes.go +++ b/error_codes.go @@ -8,4 +8,5 @@ var ( ErrInvalidFrequency = errors.New("invalid frequency") ErrNotEqual = errors.New("input values are not equal") ErrOutOfBounds = errors.New("error in representing data as it is out of bounds") + ErrTolerence = errors.New("nan error as tolerence level exceeded") ) diff --git a/reducing_utils.go b/reducing_utils.go index b869aef..8ce8814 100644 --- a/reducing_utils.go +++ b/reducing_utils.go @@ -292,9 +292,10 @@ func Npv(rate decimal.Decimal, values []decimal.Decimal) decimal.Decimal { } /* -This function computs the ratio that is used to find a single value that sets the non-liner equation to zero +This function computes the ratio that is used to find a single value that sets the non-liner equation to zero Params: + nper : number of compounding periods pmt : a (fixed) payment, paid either at the beginning (when = 1) or the end (when = 0) of each period @@ -329,43 +330,44 @@ func getRateRatio(pv, fv, pmt, curRate decimal.Decimal, nper int64, when payment /* Rate computes the Interest rate per period by running Newton Rapson to find an approximate value for: - -y = fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate*((1+rate)**nper-1) -(0 - y_previous) /(rate - rate_previous) = dy/drate {derivative of y w.r.t. rate} - + y = fv + pv*(1+rate)**nper + pmt*(1+rate*when)/rate*((1+rate)**nper-1)*(0 - y_previous) /(rate - rate_previous) = dy/drate {derivative of y w.r.t. rate} Params: - nper : number of compounding periods + nper : number of compounding periods pmt : a (fixed) payment, paid either - at the beginning (when = 1) or the end (when = 0) of each period - pv : a present value - fv : a future value + at the beginning (when = 1) or the end (when = 0) of each period + pv : a present value + fv : a future value when : specification of whether payment is made - at the beginning (when = 1) or the end (when = 0) of each period - maxIter : total number of iterations to perform calculation - tolerance : accept result only if the difference in iteration values is less than the tolerance provided - initialGuess : an initial point to start approximating from + at the beginning (when = 1) or the end (when = 0) of each period + maxIter : total number of iterations to perform calculation + tolerance : accept result only if the difference in iteration values is less than the tolerance provided + initialGuess : an initial point to start approximating from + References: - Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May). Open Document - Format for Office Applications (OpenDocument)v1.2, Part 2: Recalculated - Formula (OpenFormula) Format - Annotated Version, Pre-Draft 12. - Organization for the Advancement of Structured Information Standards - (OASIS). Billerica, MA, USA. [ODT Document]. Available: - http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula - OpenDocument-formula-20090508.odt + [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May). + Open Document Format for Office Applications (OpenDocument)v1.2, + Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version, + Pre-Draft 12. Organization for the Advancement of Structured Information + Standards (OASIS). Billerica, MA, USA. [ODT Document]. + Available: + http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formula + OpenDocument-formula-20090508.odt */ - -func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, maxIter int64, tolerance, initialGuess decimal.Decimal) (decimal.Decimal, bool) { +func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, maxIter int64, tolerance, initialGuess decimal.Decimal) (decimal.Decimal, error) { var nextIterRate, currentIterRate decimal.Decimal = initialGuess, initialGuess for iter := int64(0); iter < maxIter; iter++ { currentIterRate = nextIterRate nextIterRate = currentIterRate.Sub(getRateRatio(pv, fv, pmt, currentIterRate, nper, when)) + //skip further loops if |nextIterRate-currentIterRate| < tolerance + if nextIterRate.Sub(currentIterRate).Abs().LessThan(tolerance) { + break + } } - if nextIterRate.Sub(currentIterRate).Abs().GreaterThan(tolerance) { - return nextIterRate, false + if nextIterRate.Sub(currentIterRate).Abs().GreaterThanOrEqual(tolerance) { + return decimal.Zero, ErrTolerence } - - return nextIterRate, true + return nextIterRate, nil } diff --git a/reducing_utils_test.go b/reducing_utils_test.go index 41cbd40..36764c5 100644 --- a/reducing_utils_test.go +++ b/reducing_utils_test.go @@ -368,10 +368,10 @@ func Test_Rate(t *testing.T) { initialGuess decimal.Decimal } tests := []struct { - name string - args args - want decimal.Decimal - validity bool + name string + args args + want decimal.Decimal + anyErr error }{ { name: "success", args: args{ @@ -384,8 +384,8 @@ func Test_Rate(t *testing.T) { tolerance: decimal.NewFromFloat(1e-7), initialGuess: decimal.NewFromFloat(0.1), }, - want: decimal.NewFromFloat(0.06106257989825202), - validity: true, + want: decimal.NewFromFloat(0.06106257989825202), + anyErr: nil, }, { name: "success", args: args{ pv: decimal.NewFromInt(-3000), @@ -397,8 +397,8 @@ func Test_Rate(t *testing.T) { tolerance: decimal.NewFromFloat(1e-7), initialGuess: decimal.NewFromFloat(0.1), }, - want: decimal.NewFromFloat(-0.25968757625671507), - validity: true, + want: decimal.NewFromFloat(-0.25968757625671507), + anyErr: nil, }, { name: "failure", args: args{ pv: decimal.NewFromInt(3000), @@ -410,14 +410,14 @@ func Test_Rate(t *testing.T) { tolerance: decimal.NewFromFloat(1e-7), initialGuess: decimal.NewFromFloat(0.1), }, - want: decimal.NewFromFloat(0.4907342754506849), - validity: false, + want: decimal.Zero, + anyErr: ErrTolerence, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got, isValid := Rate(tt.args.pv, tt.args.fv, tt.args.pmt, tt.args.nper, tt.args.when, tt.args.maxIter, tt.args.tolerance, tt.args.initialGuess); isValid != tt.validity || isAlmostEqual(got, tt.want, decimal.NewFromFloat(precision)) != nil { - t.Errorf("Rate returned (%v,%v), wanted (%v,%v)", got, isValid, tt.want, tt.validity) + if got, err := Rate(tt.args.pv, tt.args.fv, tt.args.pmt, tt.args.nper, tt.args.when, tt.args.maxIter, tt.args.tolerance, tt.args.initialGuess); err != tt.anyErr || isAlmostEqual(got, tt.want, decimal.NewFromFloat(precision)) != nil { + t.Errorf("Rate returned (%v,%v), wanted (%v,%v)", got, err, tt.want, tt.anyErr) } }) } From 4134c548cffa1bcc52746fea3175384177734834 Mon Sep 17 00:00:00 2001 From: Kaustubh Joshi Date: Mon, 19 Apr 2021 10:56:46 +0530 Subject: [PATCH 16/27] gofumpt --- reducing_utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reducing_utils.go b/reducing_utils.go index 8ce8814..1220ecc 100644 --- a/reducing_utils.go +++ b/reducing_utils.go @@ -360,7 +360,7 @@ func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, maxI for iter := int64(0); iter < maxIter; iter++ { currentIterRate = nextIterRate nextIterRate = currentIterRate.Sub(getRateRatio(pv, fv, pmt, currentIterRate, nper, when)) - //skip further loops if |nextIterRate-currentIterRate| < tolerance + // skip further loops if |nextIterRate-currentIterRate| < tolerance if nextIterRate.Sub(currentIterRate).Abs().LessThan(tolerance) { break } From 97b52a1ba597939e6af4be1f66ede5c1bca86765 Mon Sep 17 00:00:00 2001 From: Kaustubh Joshi Date: Mon, 19 Apr 2021 11:05:15 +0530 Subject: [PATCH 17/27] README changes --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1aa2fbd..5422b9e 100644 --- a/README.md +++ b/README.md @@ -579,7 +579,7 @@ func main() { ## Rate ```go -func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, maxIter int64, tolerance, initialGuess decimal.Decimal) (decimal.Decimal, bool) +func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, maxIter int64, tolerance, initialGuess decimal.Decimal) (decimal.Decimal, error) ``` Params: ```text @@ -598,7 +598,7 @@ initialGuess : an initial guess amount to start from Returns: ```text rate : a value for the corresponding rate -valid : returns true if rate difference is less than the threshold (returns false conversely) +error : returns nil if rate difference is less than the threshold (returns an error conversely) ``` Rate computes the interest rate to ensure a balanced cashflow equation @@ -627,14 +627,14 @@ func main() { tolerance := decimal.NewFromFloat(1e-6) initialGuess := decimal.NewFromFloat(0.1), - rate, ok := gofinancial.Rate(pv, fv, pmt, nper, when, maxIter, tolerance, initialGuess) - if ok { - fmt.Printf("rate:%v ", rate) - } else { + rate, err := gofinancial.Rate(pv, fv, pmt, nper, when, maxIter, tolerance, initialGuess) + if err != nil { fmt.Printf("NaN") + } else { + fmt.Printf("rate:%v ", rate) } // Output: // rate: 0.06106257989825202 } ``` -[Run on go-playground](https://play.golang.org/p/9khVcHwjkh5) +[Run on go-playground](https://play.golang.org/p/H2uybe1dbRj) From 60d3d66f5029e13462733b7e46473c5d98420c47 Mon Sep 17 00:00:00 2001 From: Kaustubh Joshi Date: Sun, 25 Apr 2021 09:53:30 +0530 Subject: [PATCH 18/27] error printing --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5422b9e..28ca3ee 100644 --- a/README.md +++ b/README.md @@ -629,9 +629,9 @@ func main() { rate, err := gofinancial.Rate(pv, fv, pmt, nper, when, maxIter, tolerance, initialGuess) if err != nil { - fmt.Printf("NaN") + fmt.Printf(err) } else { - fmt.Printf("rate:%v ", rate) + fmt.Printf("rate: %v ", rate) } // Output: // rate: 0.06106257989825202 From 4edb2b70b317da810b0421082d101523753e8107 Mon Sep 17 00:00:00 2001 From: kaustubh Date: Mon, 26 Jul 2021 11:28:04 +0530 Subject: [PATCH 19/27] IRR function added --- reducing_utils.go | 21 +++++++++++++++++++++ reducing_utils_test.go | 8 ++++++++ 2 files changed, 29 insertions(+) diff --git a/reducing_utils.go b/reducing_utils.go index 1220ecc..bb0bba6 100644 --- a/reducing_utils.go +++ b/reducing_utils.go @@ -371,3 +371,24 @@ func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, maxI } return nextIterRate, nil } + +/*TODO -> Comments */ +func Irr(cashflow []decimal.Decimal, tolerance, prev_point, next_point decimal.Decimal) (decimal.Decimal, error) { + x_p := prev_point + x_n := next_point + + for i := 0; i < 100; i++ { + y_p := Npv(x_p, cashflow) + y_n := Npv(x_n, cashflow) + _v1 := x_n.Sub(x_p).Div(y_n.Sub(y_p).Add(decimal.NewFromFloat(0.0000001))) + _v2 := y_n.Neg().Mul(_v1) + x_n, x_p = x_n.Add(_v2), x_n + + } + + if Npv(x_n, cashflow).LessThan(tolerance) { + return x_n, nil + } + + return decimal.Zero, ErrOutOfBounds +} diff --git a/reducing_utils_test.go b/reducing_utils_test.go index 36764c5..7f75d99 100644 --- a/reducing_utils_test.go +++ b/reducing_utils_test.go @@ -422,3 +422,11 @@ func Test_Rate(t *testing.T) { }) } } + +// func Test_Irr(t *testing.T) { +// t.Run("IRR", func(t *testing.T) { +// cashFlow := []decimal.Decimal{decimal.NewFromFloat(1000), decimal.NewFromFloat(-900), decimal.NewFromFloat(-200)} +// v1, v2 := Irr(cashFlow) +// t.Logf("%v,%v", v1, v2) +// }) +// } From d86b2f499dd11d35d89dabae792bd9ac769911a7 Mon Sep 17 00:00:00 2001 From: kaustubh Date: Mon, 26 Jul 2021 22:28:01 +0530 Subject: [PATCH 20/27] test for irr --- README.md | 60 ++++++++++++++++++++++++++++++++++++- reducing_utils.go | 25 ++++++++++++---- reducing_utils_test.go | 67 +++++++++++++++++++++++++++++++++++++----- 3 files changed, 138 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 28ca3ee..9fc469f 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ which are as follows: | nper | ✅ | Computes the number of periodic payments| | pv | ✅ | Computes the present value of a payment| | rate | ✅ | Computes the rate of interest per period| -| irr | | Computes the internal rate of return| +| irr | ✅ | Computes the internal rate of return| | npv | ✅ | Computes the net present value of a series of cash flow| | mirr | | Computes the modified internal rate of return| @@ -49,6 +49,8 @@ While the numpy-financial package contains a set of elementary financial functio + [Example(Nper-Loan)](#examplenper-loan) * [Rate(Interest Rate)](#rate) + [Example(Rate-Investment)](#examplerate-investment) + * [Irr(Internal Rate of Return)](#irr) + + [Example(Irr)](#exampleirr) Detailed documentation is available at [godoc](https://godoc.org/github.com/razorpay/go-financial). ## Amortisation(Generate Table) @@ -638,3 +640,59 @@ func main() { } ``` [Run on go-playground](https://play.golang.org/p/H2uybe1dbRj) + +## Irr + +```go +func Irr(values []decimal.Decimal, maxIter int64, tolerance, prev_point, next_point decimal.Decimal) (decimal.Decimal, error) +``` + +Params: +```text +values : the value of the cash flow for that time period. Values provided here must be an array of float64 +maxIter : total number of iterations for which the function should run +tolerance : accept result only if the difference in iteration values is less than the tolerance provided +prev_point : an initial point to start approximating from +next_point : next point to use for secant +``` + +Returns: +```text +rate : a rate for the corresponding values +error : returns nil if NPV is close to zero (returns an error conversely) +``` + +Irr computes the rate to ensure a net zero cashflow + +### Example(Irr) + +If an initial inflow of $123400 is done followed by successive outflows of $36200, $54800 and $48100; for what value of rate does the npv evalute to zero ? (assuming 100 iterations, 1e-6 threshold and 0.1 and 0.2 as initial guessing points) + +```go +package main + +import ( + "fmt" + gofinancial "github.com/razorpay/go-financial" + "github.com/razorpay/go-financial/enums/paymentperiod" + "github.com/shopspring/decimal" +) + +func main() { + cashflow := []decimal.Decimal{decimal.NewFromFloat(-123400), decimal.NewFromFloat(36200), decimal.NewFromFloat(54800), decimal.NewFromFloat(48100)} + maxIter := 100 + tolerance := decimal.NewFromFloat(1e-6) + prev_point := decimal.NewFromFloat(0.1) + next_point := decimal.NewFromFloat(0.2) + + irr, err := gofinancial.Irr(cashflow, maxIter, tolerance, prev_point, next_point) + if err != nil { + fmt.Printf(err) + } else { + fmt.Printf("irr: %v ", irr) + } + // Output: + // irr: 0.05961637856732953787613704103503 +} +``` +[Run on go-playground](https://play.golang.org/p/H2uybe1dbRj) \ No newline at end of file diff --git a/reducing_utils.go b/reducing_utils.go index bb0bba6..4b5ae3b 100644 --- a/reducing_utils.go +++ b/reducing_utils.go @@ -372,21 +372,34 @@ func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, maxI return nextIterRate, nil } -/*TODO -> Comments */ -func Irr(cashflow []decimal.Decimal, tolerance, prev_point, next_point decimal.Decimal) (decimal.Decimal, error) { +/* +IRR computes the rate of return for a balanced cashflow (if feasible!) using the secant method of approximation. + +Params: + values : the value of the cash flow for that time period. Values provided here must be an array of float64 + maxIter : total number of iterations for which the function should run + tolerance : accept result only if the difference in iteration values is less than the tolerance provided + prev_point : an initial point to start approximating from + next_point : next point to use for secant + +References: + [G] L. J. Gitman, "Principles of Managerial Finance, Brief," 3rd ed., + Addison-Wesley, 2003, pg. 348. +*/ +func Irr(values []decimal.Decimal, maxIter int64, tolerance, prev_point, next_point decimal.Decimal) (decimal.Decimal, error) { x_p := prev_point x_n := next_point - for i := 0; i < 100; i++ { - y_p := Npv(x_p, cashflow) - y_n := Npv(x_n, cashflow) + for i := int64(0); i < maxIter; i++ { + y_p := Npv(x_p, values) + y_n := Npv(x_n, values) _v1 := x_n.Sub(x_p).Div(y_n.Sub(y_p).Add(decimal.NewFromFloat(0.0000001))) _v2 := y_n.Neg().Mul(_v1) x_n, x_p = x_n.Add(_v2), x_n } - if Npv(x_n, cashflow).LessThan(tolerance) { + if Npv(x_n, values).LessThan(tolerance) { return x_n, nil } diff --git a/reducing_utils_test.go b/reducing_utils_test.go index 7f75d99..7b42947 100644 --- a/reducing_utils_test.go +++ b/reducing_utils_test.go @@ -423,10 +423,63 @@ func Test_Rate(t *testing.T) { } } -// func Test_Irr(t *testing.T) { -// t.Run("IRR", func(t *testing.T) { -// cashFlow := []decimal.Decimal{decimal.NewFromFloat(1000), decimal.NewFromFloat(-900), decimal.NewFromFloat(-200)} -// v1, v2 := Irr(cashFlow) -// t.Logf("%v,%v", v1, v2) -// }) -// } +func Test_Irr(t *testing.T) { + type args struct { + cashFlow []decimal.Decimal + maxIter int64 + tolerance decimal.Decimal + prev_point decimal.Decimal + next_point decimal.Decimal + } + tests := []struct { + name string + args args + want decimal.Decimal + anyErr error + }{ + { + name: "success", + args: args{ + cashFlow: []decimal.Decimal{decimal.NewFromFloat(1000), decimal.NewFromFloat(-900), decimal.NewFromFloat(200), decimal.NewFromFloat(-400)}, + maxIter: 100, + tolerance: decimal.NewFromFloat(1e-6), + prev_point: decimal.NewFromFloat(0.1), + next_point: decimal.NewFromFloat(0.2), + }, + want: decimal.NewFromFloat(0.06491271005017), + anyErr: nil, + }, + { + name: "success", + args: args{ + cashFlow: []decimal.Decimal{decimal.NewFromFloat(-123400), decimal.NewFromFloat(36200), decimal.NewFromFloat(54800), decimal.NewFromFloat(48100)}, + maxIter: 100, + tolerance: decimal.NewFromFloat(1e-6), + prev_point: decimal.NewFromFloat(0.1), + next_point: decimal.NewFromFloat(0.2), + }, + want: decimal.NewFromFloat(0.0596163785673), + anyErr: nil, + }, + { + name: "failure", + args: args{ + cashFlow: []decimal.Decimal{decimal.NewFromFloat(1000), decimal.NewFromFloat(-900), decimal.NewFromFloat(200)}, + maxIter: 100, + tolerance: decimal.NewFromFloat(1e-6), + prev_point: decimal.NewFromFloat(0.1), + next_point: decimal.NewFromFloat(0.2), + }, + want: decimal.Zero, + anyErr: ErrOutOfBounds, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got, err := Irr(tt.args.cashFlow, tt.args.maxIter, tt.args.tolerance, tt.args.prev_point, tt.args.next_point); err != tt.anyErr || isAlmostEqual(got, tt.want, decimal.NewFromFloat(precision)) != nil { + t.Errorf("IRR returned (%v,%v), wanted (%v,%v)", got, err, tt.want, tt.anyErr) + } + }) + } +} From 3f66ca5a8ccacece1a8db54f63793cba4c3e2577 Mon Sep 17 00:00:00 2001 From: kaustubh Date: Mon, 26 Jul 2021 22:30:07 +0530 Subject: [PATCH 21/27] readme.md updated --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9fc469f..fe505b8 100644 --- a/README.md +++ b/README.md @@ -673,8 +673,7 @@ package main import ( "fmt" - gofinancial "github.com/razorpay/go-financial" - "github.com/razorpay/go-financial/enums/paymentperiod" + gofinancial "github.com/razorpay/go-financial" "github.com/shopspring/decimal" ) @@ -695,4 +694,4 @@ func main() { // irr: 0.05961637856732953787613704103503 } ``` -[Run on go-playground](https://play.golang.org/p/H2uybe1dbRj) \ No newline at end of file +[Run on go-playground](https://play.golang.org/p/izqvtBWEOqJ) \ No newline at end of file From ef1410419aa574562e3bb9282f07ab704f036b80 Mon Sep 17 00:00:00 2001 From: kaustubh Date: Tue, 27 Jul 2021 11:23:11 +0530 Subject: [PATCH 22/27] mirr added --- README.md | 8 +++++++- reducing_utils.go | 38 ++++++++++++++++++++++++++++++++++++++ reducing_utils_test.go | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 28ca3ee..1920be9 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ which are as follows: | rate | ✅ | Computes the rate of interest per period| | irr | | Computes the internal rate of return| | npv | ✅ | Computes the net present value of a series of cash flow| -| mirr | | Computes the modified internal rate of return| +| mirr | ✅ | Computes the modified internal rate of return| # Index While the numpy-financial package contains a set of elementary financial functions, this pkg also contains some helper functions on top of it. Their usage and description can be found below: @@ -49,6 +49,8 @@ While the numpy-financial package contains a set of elementary financial functio + [Example(Nper-Loan)](#examplenper-loan) * [Rate(Interest Rate)](#rate) + [Example(Rate-Investment)](#examplerate-investment) + * [Mirr(Modified irr)](#mirr) + + [Example(Mirr)](#examplemirr) Detailed documentation is available at [godoc](https://godoc.org/github.com/razorpay/go-financial). ## Amortisation(Generate Table) @@ -638,3 +640,7 @@ func main() { } ``` [Run on go-playground](https://play.golang.org/p/H2uybe1dbRj) + +## Mirr + +### Example(Mirr) \ No newline at end of file diff --git a/reducing_utils.go b/reducing_utils.go index 1220ecc..db5f4a3 100644 --- a/reducing_utils.go +++ b/reducing_utils.go @@ -371,3 +371,41 @@ func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, maxI } return nextIterRate, nil } + +/* +Mirr calculates the Modified internal rate of return for a given cashflow and finance and reinvestment rates + +Params: + cashflows: periodic chasflow statement + financeRate: interest rate for inflows + reinvestRate: interst received for reinvesting outflows +*/ +func Mirr(cashflows []decimal.Decimal, financeRate, reinvestRate decimal.Decimal) decimal.Decimal { + var inflow, outflow []decimal.Decimal + + for _, v := range cashflows { + if v.IsNegative() { + inflow = append(inflow, v) + outflow = append(outflow, decimal.Zero) + } + if v.IsPositive() { + outflow = append(outflow, v) + inflow = append(inflow, decimal.Zero) + } + } + + one := decimal.NewFromFloat(1) + n_1 := decimal.NewFromInt(int64(len(cashflows) - 1)) + _pv := Npv(financeRate, inflow) + _fv := Npv(reinvestRate, outflow).Mul(one.Add(reinvestRate).Pow(n_1)) + + // ! shopspring decimal struct based underroot is imprecise (hence math library) + _x1 := _fv.Div(_pv.Neg()) + _go_x1, _ := _x1.Float64() + _go_x2, _ := one.Div(n_1).Float64() + _x3 := math.Pow(_go_x1, _go_x2) + + _mirr := decimal.NewFromFloat(_x3).Sub(one) + return _mirr + +} diff --git a/reducing_utils_test.go b/reducing_utils_test.go index 36764c5..3819544 100644 --- a/reducing_utils_test.go +++ b/reducing_utils_test.go @@ -422,3 +422,41 @@ func Test_Rate(t *testing.T) { }) } } + +func Test_Mirr(t *testing.T) { + type args struct { + cashflow []decimal.Decimal + financeRate decimal.Decimal + reinvestRate decimal.Decimal + } + tests := []struct { + name string + args args + want decimal.Decimal + }{ + { + name: "success", args: args{ + cashflow: []decimal.Decimal{decimal.NewFromInt(-1000), decimal.NewFromInt(-4000), decimal.NewFromInt(5000), decimal.NewFromInt(2000)}, + financeRate: decimal.NewFromFloat(0.1), + reinvestRate: decimal.NewFromFloat(0.12), + }, + want: decimal.NewFromFloat(0.1790856860), + }, + { + name: "success", args: args{ + cashflow: []decimal.Decimal{decimal.NewFromInt(-2000), decimal.NewFromInt(2000), decimal.NewFromInt(2000)}, + financeRate: decimal.NewFromFloat(0.3), + reinvestRate: decimal.NewFromFloat(0.1), + }, + want: decimal.NewFromFloat(0.4491376746), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if res := Mirr(tt.args.cashflow, tt.args.financeRate, tt.args.reinvestRate); isAlmostEqual(res, tt.want, decimal.NewFromFloat(precision)) != nil { + t.Errorf("Mirr returned (%v), wanted (%v)", res, tt.want) + } + }) + } +} From 49062ef7c7c68b726bb8110fe9e0fe5f66069115 Mon Sep 17 00:00:00 2001 From: kaustubh Date: Tue, 27 Jul 2021 11:41:52 +0530 Subject: [PATCH 23/27] Readme.md updated --- README.md | 43 ++++++++++++++++++++++++++++++++++++++++++- reducing_utils.go | 6 +++--- 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 1920be9..162a9e0 100644 --- a/README.md +++ b/README.md @@ -643,4 +643,45 @@ func main() { ## Mirr -### Example(Mirr) \ No newline at end of file +```go +func Mirr(cashflows []decimal.Decimal, financeRate, reinvestRate decimal.Decimal) decimal.Decimal +``` + +Params: +```text + cashflows: periodic chasflow statement + financeRate: interest rate for inflows + reinvestRate: interst received for reinvesting outflows +``` + +Returns: +```text +mirr : a value for the corresponding mirr +``` + +Mirr calculates the Modified internal rate of return for a given cashflow and finance and reinvestment rates + +### Example(Mirr) + +```go +package main + +import ( + "fmt" + gofinancial "github.com/razorpay/go-financial" + "github.com/shopspring/decimal" +) + +func main() { + cashflow := []decimal.Decimal{decimal.NewFromInt(-1000), decimal.NewFromInt(-4000), decimal.NewFromInt(5000), decimal.NewFromInt(2000)} + financeRate := decimal.NewFromFloat(0.1) + reinvestRate := decimal.NewFromFloat(0.12) + + mirr := gofinancial.Mirr(cashflow,financeRate,reinvestRate) + + fmt.Printf("mirr: %v",mirr) + // Output: + // mirr: 0.1790856860348926 +} +``` +[Run on go-playground](https://play.golang.org/p/ci6pBUkX4jy) \ No newline at end of file diff --git a/reducing_utils.go b/reducing_utils.go index db5f4a3..1510b2c 100644 --- a/reducing_utils.go +++ b/reducing_utils.go @@ -376,9 +376,9 @@ func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, maxI Mirr calculates the Modified internal rate of return for a given cashflow and finance and reinvestment rates Params: - cashflows: periodic chasflow statement - financeRate: interest rate for inflows - reinvestRate: interst received for reinvesting outflows + cashflows : periodic chasflow statement + financeRate : interest rate for inflows + reinvestRate : interst received for reinvesting outflows */ func Mirr(cashflows []decimal.Decimal, financeRate, reinvestRate decimal.Decimal) decimal.Decimal { var inflow, outflow []decimal.Decimal From f1363f911c14adfac3f144f06241382894d1ae5b Mon Sep 17 00:00:00 2001 From: kaustubh Date: Tue, 27 Jul 2021 11:43:20 +0530 Subject: [PATCH 24/27] lint check --- reducing_utils.go | 1 - 1 file changed, 1 deletion(-) diff --git a/reducing_utils.go b/reducing_utils.go index 1510b2c..0d46f91 100644 --- a/reducing_utils.go +++ b/reducing_utils.go @@ -407,5 +407,4 @@ func Mirr(cashflows []decimal.Decimal, financeRate, reinvestRate decimal.Decimal _mirr := decimal.NewFromFloat(_x3).Sub(one) return _mirr - } From 305673f7ddeeea4e37587d4da3cc72e118cb8662 Mon Sep 17 00:00:00 2001 From: kaustubh Date: Fri, 27 Aug 2021 17:13:43 +0530 Subject: [PATCH 25/27] naming convention --- reducing_utils.go | 8 ++++---- reducing_utils_test.go | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/reducing_utils.go b/reducing_utils.go index 0d46f91..97f7890 100644 --- a/reducing_utils.go +++ b/reducing_utils.go @@ -376,14 +376,14 @@ func Rate(pv, fv, pmt decimal.Decimal, nper int64, when paymentperiod.Type, maxI Mirr calculates the Modified internal rate of return for a given cashflow and finance and reinvestment rates Params: - cashflows : periodic chasflow statement + cashFlows : periodic chasflow statement financeRate : interest rate for inflows reinvestRate : interst received for reinvesting outflows */ -func Mirr(cashflows []decimal.Decimal, financeRate, reinvestRate decimal.Decimal) decimal.Decimal { +func Mirr(cashFlows []decimal.Decimal, financeRate, reinvestRate decimal.Decimal) decimal.Decimal { var inflow, outflow []decimal.Decimal - for _, v := range cashflows { + for _, v := range cashFlows { if v.IsNegative() { inflow = append(inflow, v) outflow = append(outflow, decimal.Zero) @@ -395,7 +395,7 @@ func Mirr(cashflows []decimal.Decimal, financeRate, reinvestRate decimal.Decimal } one := decimal.NewFromFloat(1) - n_1 := decimal.NewFromInt(int64(len(cashflows) - 1)) + n_1 := decimal.NewFromInt(int64(len(cashFlows) - 1)) _pv := Npv(financeRate, inflow) _fv := Npv(reinvestRate, outflow).Mul(one.Add(reinvestRate).Pow(n_1)) diff --git a/reducing_utils_test.go b/reducing_utils_test.go index 3819544..d725043 100644 --- a/reducing_utils_test.go +++ b/reducing_utils_test.go @@ -425,7 +425,7 @@ func Test_Rate(t *testing.T) { func Test_Mirr(t *testing.T) { type args struct { - cashflow []decimal.Decimal + cashFlows []decimal.Decimal financeRate decimal.Decimal reinvestRate decimal.Decimal } @@ -436,7 +436,7 @@ func Test_Mirr(t *testing.T) { }{ { name: "success", args: args{ - cashflow: []decimal.Decimal{decimal.NewFromInt(-1000), decimal.NewFromInt(-4000), decimal.NewFromInt(5000), decimal.NewFromInt(2000)}, + cashFlows: []decimal.Decimal{decimal.NewFromInt(-1000), decimal.NewFromInt(-4000), decimal.NewFromInt(5000), decimal.NewFromInt(2000)}, financeRate: decimal.NewFromFloat(0.1), reinvestRate: decimal.NewFromFloat(0.12), }, @@ -444,7 +444,7 @@ func Test_Mirr(t *testing.T) { }, { name: "success", args: args{ - cashflow: []decimal.Decimal{decimal.NewFromInt(-2000), decimal.NewFromInt(2000), decimal.NewFromInt(2000)}, + cashFlows: []decimal.Decimal{decimal.NewFromInt(-2000), decimal.NewFromInt(2000), decimal.NewFromInt(2000)}, financeRate: decimal.NewFromFloat(0.3), reinvestRate: decimal.NewFromFloat(0.1), }, @@ -454,7 +454,7 @@ func Test_Mirr(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if res := Mirr(tt.args.cashflow, tt.args.financeRate, tt.args.reinvestRate); isAlmostEqual(res, tt.want, decimal.NewFromFloat(precision)) != nil { + if res := Mirr(tt.args.cashFlows, tt.args.financeRate, tt.args.reinvestRate); isAlmostEqual(res, tt.want, decimal.NewFromFloat(precision)) != nil { t.Errorf("Mirr returned (%v), wanted (%v)", res, tt.want) } }) From 8ddf09f361e7b5b7efad66f0885c7623572db584 Mon Sep 17 00:00:00 2001 From: kaustubh Date: Fri, 27 Aug 2021 17:19:15 +0530 Subject: [PATCH 26/27] naming convention --- reducing_utils.go | 11 ++++++----- reducing_utils_test.go | 42 +++++++++++++++++++++--------------------- 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/reducing_utils.go b/reducing_utils.go index 4b5ae3b..e518d2c 100644 --- a/reducing_utils.go +++ b/reducing_utils.go @@ -379,17 +379,18 @@ Params: values : the value of the cash flow for that time period. Values provided here must be an array of float64 maxIter : total number of iterations for which the function should run tolerance : accept result only if the difference in iteration values is less than the tolerance provided - prev_point : an initial point to start approximating from - next_point : next point to use for secant + prevPoint : an initial point to start approximating from + nextPoint : next point to use for secant References: [G] L. J. Gitman, "Principles of Managerial Finance, Brief," 3rd ed., Addison-Wesley, 2003, pg. 348. */ -func Irr(values []decimal.Decimal, maxIter int64, tolerance, prev_point, next_point decimal.Decimal) (decimal.Decimal, error) { - x_p := prev_point - x_n := next_point +func Irr(values []decimal.Decimal, maxIter int64, tolerance, prevPoint, nextPoint decimal.Decimal) (decimal.Decimal, error) { + x_p := prevPoint + x_n := nextPoint + // https://en.wikipedia.org/wiki/Secant_method for i := int64(0); i < maxIter; i++ { y_p := Npv(x_p, values) y_n := Npv(x_n, values) diff --git a/reducing_utils_test.go b/reducing_utils_test.go index 7b42947..f611254 100644 --- a/reducing_utils_test.go +++ b/reducing_utils_test.go @@ -425,11 +425,11 @@ func Test_Rate(t *testing.T) { func Test_Irr(t *testing.T) { type args struct { - cashFlow []decimal.Decimal - maxIter int64 - tolerance decimal.Decimal - prev_point decimal.Decimal - next_point decimal.Decimal + cashFlow []decimal.Decimal + maxIter int64 + tolerance decimal.Decimal + prevPoint decimal.Decimal + nextPoint decimal.Decimal } tests := []struct { name string @@ -440,11 +440,11 @@ func Test_Irr(t *testing.T) { { name: "success", args: args{ - cashFlow: []decimal.Decimal{decimal.NewFromFloat(1000), decimal.NewFromFloat(-900), decimal.NewFromFloat(200), decimal.NewFromFloat(-400)}, - maxIter: 100, - tolerance: decimal.NewFromFloat(1e-6), - prev_point: decimal.NewFromFloat(0.1), - next_point: decimal.NewFromFloat(0.2), + cashFlow: []decimal.Decimal{decimal.NewFromFloat(1000), decimal.NewFromFloat(-900), decimal.NewFromFloat(200), decimal.NewFromFloat(-400)}, + maxIter: 100, + tolerance: decimal.NewFromFloat(1e-6), + prevPoint: decimal.NewFromFloat(0.1), + nextPoint: decimal.NewFromFloat(0.2), }, want: decimal.NewFromFloat(0.06491271005017), anyErr: nil, @@ -452,11 +452,11 @@ func Test_Irr(t *testing.T) { { name: "success", args: args{ - cashFlow: []decimal.Decimal{decimal.NewFromFloat(-123400), decimal.NewFromFloat(36200), decimal.NewFromFloat(54800), decimal.NewFromFloat(48100)}, - maxIter: 100, - tolerance: decimal.NewFromFloat(1e-6), - prev_point: decimal.NewFromFloat(0.1), - next_point: decimal.NewFromFloat(0.2), + cashFlow: []decimal.Decimal{decimal.NewFromFloat(-123400), decimal.NewFromFloat(36200), decimal.NewFromFloat(54800), decimal.NewFromFloat(48100)}, + maxIter: 100, + tolerance: decimal.NewFromFloat(1e-6), + prevPoint: decimal.NewFromFloat(0.1), + nextPoint: decimal.NewFromFloat(0.2), }, want: decimal.NewFromFloat(0.0596163785673), anyErr: nil, @@ -464,11 +464,11 @@ func Test_Irr(t *testing.T) { { name: "failure", args: args{ - cashFlow: []decimal.Decimal{decimal.NewFromFloat(1000), decimal.NewFromFloat(-900), decimal.NewFromFloat(200)}, - maxIter: 100, - tolerance: decimal.NewFromFloat(1e-6), - prev_point: decimal.NewFromFloat(0.1), - next_point: decimal.NewFromFloat(0.2), + cashFlow: []decimal.Decimal{decimal.NewFromFloat(1000), decimal.NewFromFloat(-900), decimal.NewFromFloat(200)}, + maxIter: 100, + tolerance: decimal.NewFromFloat(1e-6), + prevPoint: decimal.NewFromFloat(0.1), + nextPoint: decimal.NewFromFloat(0.2), }, want: decimal.Zero, anyErr: ErrOutOfBounds, @@ -477,7 +477,7 @@ func Test_Irr(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got, err := Irr(tt.args.cashFlow, tt.args.maxIter, tt.args.tolerance, tt.args.prev_point, tt.args.next_point); err != tt.anyErr || isAlmostEqual(got, tt.want, decimal.NewFromFloat(precision)) != nil { + if got, err := Irr(tt.args.cashFlow, tt.args.maxIter, tt.args.tolerance, tt.args.prevPoint, tt.args.nextPoint); err != tt.anyErr || isAlmostEqual(got, tt.want, decimal.NewFromFloat(precision)) != nil { t.Errorf("IRR returned (%v,%v), wanted (%v,%v)", got, err, tt.want, tt.anyErr) } }) From 9606bf78d51ef73b3dc925f0cc50e591da17aa20 Mon Sep 17 00:00:00 2001 From: kaustubh Date: Wed, 1 Sep 2021 23:02:29 +0530 Subject: [PATCH 27/27] math var renaming --- reducing_utils.go | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/reducing_utils.go b/reducing_utils.go index e518d2c..cab8ff7 100644 --- a/reducing_utils.go +++ b/reducing_utils.go @@ -387,21 +387,21 @@ References: Addison-Wesley, 2003, pg. 348. */ func Irr(values []decimal.Decimal, maxIter int64, tolerance, prevPoint, nextPoint decimal.Decimal) (decimal.Decimal, error) { - x_p := prevPoint - x_n := nextPoint + xP := prevPoint + xN := nextPoint // https://en.wikipedia.org/wiki/Secant_method for i := int64(0); i < maxIter; i++ { - y_p := Npv(x_p, values) - y_n := Npv(x_n, values) - _v1 := x_n.Sub(x_p).Div(y_n.Sub(y_p).Add(decimal.NewFromFloat(0.0000001))) - _v2 := y_n.Neg().Mul(_v1) - x_n, x_p = x_n.Add(_v2), x_n + yP := Npv(xP, values) + yN := Npv(xN, values) + _v1 := xN.Sub(xP).Div(yN.Sub(yP).Add(decimal.NewFromFloat(0.0000001))) + _v2 := yN.Neg().Mul(_v1) + xN, xP = xN.Add(_v2), xN } - if Npv(x_n, values).LessThan(tolerance) { - return x_n, nil + if Npv(xN, values).LessThan(tolerance) { + return xN, nil } return decimal.Zero, ErrOutOfBounds