Skip to content

Instantly share code, notes, and snippets.

@teepark
Created January 26, 2021 03:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save teepark/35e19b359ae670ebcf715d16c8f40282 to your computer and use it in GitHub Desktop.
Save teepark/35e19b359ae670ebcf715d16c8f40282 to your computer and use it in GitHub Desktop.
package finmath
import (
"github.com/ericlagergren/decimal"
"github.com/ericlagergren/decimal/math"
)
func CalculateFV(pv, pmt, i, n *decimal.Big) *decimal.Big {
if i.Cmp(zero) == 0 {
d := new(decimal.Big).Mul(pmt, n)
d.Add(d, pv)
return d.Neg(d)
}
tmp := new(decimal.Big).Add(i, one)
math.Pow(tmp, tmp, n)
d := new(decimal.Big).Sub(one, tmp)
d.Mul(d, pmt)
d.Quo(d, i)
tmp.Mul(tmp, pv)
return d.Sub(d, tmp)
}
func CalculatePV(pmt, fv, i, n *decimal.Big) *decimal.Big {
if i.Cmp(zero) == 0 {
d := new(decimal.Big).Mul(pmt, n)
d.Add(d, fv)
return d.Neg(d)
}
tmp := new(decimal.Big).Add(i, one)
math.Pow(tmp, tmp, n)
d := new(decimal.Big).Quo(pmt, i)
d.Quo(d, tmp)
tmp.Quo(fv, tmp)
d.Sub(d, tmp)
tmp.Quo(pmt, i)
return d.Sub(d, tmp)
}
func CalculatePMT(pv, fv, i, n *decimal.Big) *decimal.Big {
if i.Cmp(zero) == 0 {
d := new(decimal.Big).Add(pv, fv)
d.Quo(d, n)
return d.Neg(d)
}
tmp := new(decimal.Big).Add(i, one)
math.Pow(tmp, tmp, n)
d := new(decimal.Big).Mul(pv, tmp)
d.Add(d, fv)
d.Mul(d, i)
tmp.Neg(tmp)
tmp.Add(tmp, one)
return d.Quo(d, tmp)
}
func CalculateN(pv, pmt, fv, i *decimal.Big) *decimal.Big {
if i.Cmp(zero) == 0 {
d := new(decimal.Big).Add(pv, fv)
d.Quo(d, pmt)
return d.Neg(d)
}
tmp := new(decimal.Big).Quo(pmt, i)
d := new(decimal.Big).Sub(tmp, fv)
tmp.Add(tmp, pv)
d.Quo(d, tmp)
math.Log(d, d)
tmp.Add(i, one)
math.Log(tmp, tmp)
return d.Quo(d, tmp)
}
func CalculateI(pv, pmt, fv, n *decimal.Big) *decimal.Big {
if justCash(pv, pmt, fv, n).Cmp(zero) == 0 {
return zero
}
f := func(i *decimal.Big) *decimal.Big {
if i.Cmp(zero) == 0 {
d := new(decimal.Big).Mul(pmt, n)
d.Add(d, pv)
return d.Add(d, fv)
}
tmp := new(decimal.Big).Add(one, i)
math.Pow(tmp, tmp, n)
d := new(decimal.Big).Mul(pv, tmp)
d.Add(d, fv)
if pmt.Cmp(zero) == 0 {
return d
}
tmp.Sub(tmp, one)
tmp.Mul(tmp, pmt)
tmp.Quo(tmp, i)
return d.Add(d, tmp)
}
// first derivative of f
fPrime := func(i *decimal.Big) *decimal.Big {
root := new(decimal.Big).Add(i, one)
tmp := new(decimal.Big).Sub(n, one)
pow := new(decimal.Big)
math.Pow(pow, root, tmp)
d := new(decimal.Big).Mul(pow, pv)
d.Mul(d, n)
if pmt.Cmp(zero) == 0 {
return d
}
term := new(decimal.Big).Mul(pow, n)
math.Pow(tmp, root, n)
tmp.Sub(tmp, one)
tmp.Quo(tmp, i)
term.Sub(term, tmp)
term.Mul(term, pmt)
term.Quo(term, i)
d.Add(d, term)
return d
}
return findRootNR(f, fPrime, defaultFindRootConfig)
}
func CalculateNPV(cfs []*decimal.Big, i *decimal.Big) *decimal.Big {
if len(cfs) == 0 {
return zero
}
sum := new(decimal.Big).Copy(cfs[0])
term := new(decimal.Big)
for j := 1; j < len(cfs); j++ {
if cfs[j].Cmp(zero) == 0 {
continue
}
term.Add(i, one)
math.Pow(term, term, decimal.New(int64(j), 0))
term.Quo(cfs[j], term)
sum.Add(sum, term)
}
return sum
}
func CalculateIRR(cfs []*decimal.Big) *decimal.Big {
f := func(i *decimal.Big) *decimal.Big {
return CalculateNPV(cfs, i)
}
fPrime := func(i *decimal.Big) *decimal.Big {
if len(cfs) == 0 {
return zero
}
sum := new(decimal.Big).Copy(cfs[0])
jd := new(decimal.Big)
tmp := new(decimal.Big)
for j, cf := range cfs {
if j == 0 {
continue
}
jd.SetMantScale(int64(j+1), 0)
tmp.Add(i, one)
math.Pow(tmp, tmp, jd)
tmp.Quo(cf, tmp)
jd.SetMantScale(int64(-1*j), 0)
tmp.Mul(tmp, jd)
sum.Add(sum, tmp)
}
return sum
}
return findRootNR(f, fPrime, defaultFindRootConfig)
}
func CalculateMIRR(cfs []*decimal.Big, rr *decimal.Big) *decimal.Big {
cf0 := cfs[0]
cfs[0] = zero
nfv := calcNetFutureValue(cfs, rr)
nInv := decimal.New(int64(len(cfs) - 1), 0)
nInv.Quo(one, nInv)
d := new(decimal.Big).Quo(nfv, cf0)
d.Neg(d)
math.Pow(d, d, nInv)
d.Sub(d, one)
return d
}
func calcNetFutureValue(cfs []*decimal.Big, i *decimal.Big) *decimal.Big {
if len(cfs) == 0 {
return zero
}
sum := new(decimal.Big).Copy(cfs[len(cfs)-1])
tmp := new(decimal.Big)
pow := new(decimal.Big)
for j, cf := range cfs[:len(cfs)-1] {
tmp.Add(i, one)
pow.SetMantScale(int64(len(cfs) - j - 1), 0)
math.Pow(tmp, tmp, pow)
tmp.Mul(tmp, cf)
sum.Add(sum, tmp)
}
return sum
}
// add up cash flows ignoring any potential interest
func justCash(pv, pmt, fv, n *decimal.Big) *decimal.Big {
d := new(decimal.Big).Mul(pmt, n)
d.Add(d, pv)
return d.Add(d, fv)
}
type findRootConfig struct {
InitialGuess *decimal.Big
Tolerance *decimal.Big
}
// Newton-Raphson method for finding a function root
func findRootNR(f func(*decimal.Big) *decimal.Big, fPrime func(*decimal.Big) *decimal.Big, conf findRootConfig) *decimal.Big {
guess := new(decimal.Big).Copy(conf.InitialGuess)
result := f(guess)
tmp := new(decimal.Big)
for tmp.Abs(result).Cmp(conf.Tolerance) == 1 {
tmp.Copy(fPrime(guess))
tmp.Quo(result, tmp)
guess.Sub(guess, tmp)
result = f(guess)
}
return guess
}
var (
zero = decimal.New(0, 0)
one = decimal.New(1, 0)
two = decimal.New(2, 0)
defaultFindRootConfig = findRootConfig{
InitialGuess: decimal.New(1, 1),
Tolerance: decimal.New(1, 10),
}
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment