-
Notifications
You must be signed in to change notification settings - Fork 22
/
Copy pathreducing_utils.go
373 lines (314 loc) · 13.6 KB
/
reducing_utils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
/*
This package is a go native port of the numpy-financial package with some additional helper
functions.
The functions in this package are a scalar version of their vectorised counterparts in
the numpy-financial(https://github.com/numpy/numpy-financial) library.
Currently, only some functions are ported, the remaining will be ported soon.
*/
package gofinancial
import (
"fmt"
"math"
"github.com/razorpay/go-financial/enums/paymentperiod"
"github.com/shopspring/decimal"
)
/*
Pmt compute the fixed payment(principal + interest) against a loan amount ( fv = 0).
It can also be used to calculate the recurring payments needed to achieve a certain future value
given an initial deposit, a fixed periodically compounded interest rate, and the total number of periods.
It is obtained by solving the following equation:
fv + pv*(1 + rate)**nper + pmt*(1 + rate*when)/rate*((1 + rate)**nper - 1) == 0
Params:
rate : rate of interest compounded once per period
nper : total number of periods to be compounded for
pv : present value (e.g., an amount borrowed)
fv : future value (e.g., 0)
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 Pmt(rate decimal.Decimal, nper int64, pv decimal.Decimal, fv decimal.Decimal, when paymentperiod.Type) decimal.Decimal {
one := decimal.NewFromInt(1)
minusOne := decimal.NewFromInt(-1)
dWhen := decimal.NewFromInt(when.Value())
dNper := decimal.NewFromInt(nper)
dRateWithWhen := rate.Mul(dWhen)
factor := one.Add(rate).Pow(dNper)
var secondFactor decimal.Decimal
if rate.Equal(decimal.Zero) {
secondFactor = dNper
} else {
secondFactor = factor.Sub(one).Mul(one.Add(dRateWithWhen)).Div(rate)
}
return pv.Mul(factor).Add(fv).Div(secondFactor).Mul(minusOne)
}
/*
IPmt computes interest payment for a loan under a given period.
Params:
rate : rate of interest compounded once per period
per : period under consideration
nper : total number of periods to be compounded for
pv : present value (e.g., an amount borrowed)
fv : future value (e.g., 0)
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 IPmt(rate decimal.Decimal, per int64, nper int64, pv decimal.Decimal, fv decimal.Decimal, when paymentperiod.Type) decimal.Decimal {
totalPmt := Pmt(rate, nper, pv, fv, when)
one := decimal.NewFromInt(1)
ipmt := rbl(rate, per, totalPmt, pv, when).Mul(rate)
if when == paymentperiod.BEGINNING {
if per == 1 {
return decimal.Zero
} else {
// paying at the beginning, so discount it.
return ipmt.Div(one.Add(rate))
}
} else {
return ipmt
}
}
/*
PPmt computes principal payment for a loan under a given period.
Params:
rate : rate of interest compounded once per period
per : period under consideration
nper : total number of periods to be compounded for
pv : present value (e.g., an amount borrowed)
fv : future value (e.g., 0)
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 PPmt(rate decimal.Decimal, per int64, nper int64, pv decimal.Decimal, fv decimal.Decimal, when paymentperiod.Type) decimal.Decimal {
total := Pmt(rate, nper, pv, fv, when)
ipmt := IPmt(rate, per, nper, pv, fv, when)
return total.Sub(ipmt)
}
// Rbl computes remaining balance
func rbl(rate decimal.Decimal, per int64, pmt decimal.Decimal, pv decimal.Decimal, when paymentperiod.Type) decimal.Decimal {
return Fv(rate, per-1, pmt, pv, when)
}
/*
Nper computes the number of periodic payments by solving the equation:
fv +
pv*(1 + rate)**nper +
pmt*(1 + rate*when)/rate*((1 + rate)**nper - 1) = 0
Params:
rate : an interest rate compounded once per period
pmt : a (fixed) payment, paid either
at the beginning (when = 1) or the end (when = 0) of each period
pv : a present value
when : specification of whether payment is made
at the beginning (when = 1) or the end
(when = 0) of each period
fv: a future value
when : specification of whether payment is made
at the beginning (when = 1) or the end
(when = 0) of each period
*/
func Nper(rate decimal.Decimal, pmt decimal.Decimal, pv decimal.Decimal, fv decimal.Decimal, when paymentperiod.Type) (result decimal.Decimal, err error) {
defer func() {
if r := recover(); r != nil {
result = decimal.Zero
err = fmt.Errorf("%w: %v", ErrOutOfBounds, r)
}
}()
one := decimal.NewFromInt(1)
minusOne := decimal.NewFromInt(-1)
dWhen := decimal.NewFromInt(when.Value())
dRateWithWhen := rate.Mul(dWhen)
z := pmt.Mul(one.Add(dRateWithWhen)).Div(rate)
numerator := minusOne.Mul(fv).Add(z).Div(pv.Add(z))
denominator := one.Add(rate)
floatNumerator, _ := numerator.BigFloat().Float64()
floatDenominator, _ := denominator.BigFloat().Float64()
logNumerator := math.Log(floatNumerator)
logDenominator := math.Log(floatDenominator)
dlogDenominator := decimal.NewFromFloat(logDenominator)
result = decimal.NewFromFloat(logNumerator).Div(dlogDenominator)
return result, nil
}
/*
Fv computes future value at the end of some periods(nper) by solving the following equation:
fv +
pv*(1+rate)**nper +
pmt*(1 + rate*when)/rate*((1 + rate)**nper - 1) == 0
Params:
pv : a present 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 Fv(rate decimal.Decimal, nper int64, pmt decimal.Decimal, pv decimal.Decimal, when paymentperiod.Type) decimal.Decimal {
one := decimal.NewFromInt(1)
minusOne := decimal.NewFromInt(-1)
dWhen := decimal.NewFromInt(when.Value())
dRateWithWhen := rate.Mul(dWhen)
dNper := decimal.NewFromInt(nper)
factor := one.Add(rate).Pow(dNper)
secondFactor := factor.Sub(one).Mul(one.Add(dRateWithWhen)).Div(rate)
return pv.Mul(factor).Add(pmt.Mul(secondFactor)).Mul(minusOne)
}
/*
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 decimal.Decimal, nper int64, pmt decimal.Decimal, fv decimal.Decimal, when paymentperiod.Type) decimal.Decimal {
one := decimal.NewFromInt(1)
minusOne := decimal.NewFromInt(-1)
dWhen := decimal.NewFromInt(when.Value())
dNper := decimal.NewFromInt(nper)
dRateWithWhen := rate.Mul(dWhen)
factor := one.Add(rate).Pow(dNper)
secondFactor := factor.Sub(one).Mul(one.Add(dRateWithWhen)).Div(rate)
return fv.Add(pmt.Mul(secondFactor)).Div(factor).Mul(minusOne)
}
/*
Npv computes the Net Present Value of a cash flow series
Params:
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:
L. J. Gitman, “Principles of Managerial Finance, Brief,” 3rd ed., Addison-Wesley, 2003, pg. 346.
*/
func Npv(rate decimal.Decimal, values []decimal.Decimal) decimal.Decimal {
internalNpv := decimal.NewFromFloat(0.0)
currentRateT := decimal.NewFromFloat(1.0)
one := decimal.NewFromInt(1)
for _, currentVal := range values {
internalNpv = internalNpv.Add(currentVal.Div(currentRateT))
currentRateT = currentRateT.Mul(one.Add(rate))
}
return internalNpv
}
/*
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
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 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)
}
/*
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
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:
[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, 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().GreaterThanOrEqual(tolerance) {
return decimal.Zero, ErrTolerence
}
return nextIterRate, nil
}