mirror of
https://github.com/unidoc/unioffice.git
synced 2025-04-25 13:48:53 +08:00
Financial functions - part 1 (#359)
* ACCRINTM, COUPNUM, COUPPCD, AMORDEGRC, AMORLINC * COUPDAYBS * COUPNCD, COUPDAYS, COUPDAYSNC * CUMIPMT, CUMPRINC
This commit is contained in:
parent
e17f3d3164
commit
64edd6c139
@ -416,10 +416,14 @@ func validateDate(year, month, day int) bool {
|
|||||||
if day < 1 {
|
if day < 1 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
return day <= getDaysInMonth(year, month)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDaysInMonth(year, month int) int {
|
||||||
if month == 2 && isLeapYear(year) {
|
if month == 2 && isLeapYear(year) {
|
||||||
return day <= 29
|
return 29
|
||||||
} else {
|
} else {
|
||||||
return day <= daysInMonth[month-1]
|
return daysInMonth[month-1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -779,10 +783,14 @@ func YearFrac(ctx Context, ev Evaluator, args []Result) Result {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return MakeErrorResult("incorrect end date")
|
return MakeErrorResult("incorrect end date")
|
||||||
}
|
}
|
||||||
return yearFrac(startDate, endDate, basis)
|
return yearFracFromTime(startDate, endDate, basis)
|
||||||
}
|
}
|
||||||
|
|
||||||
func yearFrac(startDate, endDate time.Time, basis int) Result {
|
func yearFrac(startDate, endDate float64, basis int) Result {
|
||||||
|
return yearFracFromTime(dateFromDays(startDate), dateFromDays(endDate), basis)
|
||||||
|
}
|
||||||
|
|
||||||
|
func yearFracFromTime(startDate, endDate time.Time, basis int) Result {
|
||||||
startDateS := startDate.Unix()
|
startDateS := startDate.Unix()
|
||||||
endDateS := endDate.Unix()
|
endDateS := endDate.Unix()
|
||||||
sy, sm, sd := startDate.Date()
|
sy, sm, sd := startDate.Date()
|
||||||
@ -821,6 +829,21 @@ func yearFrac(startDate, endDate time.Time, basis int) Result {
|
|||||||
return MakeErrorResultType(ErrorTypeValue, "")
|
return MakeErrorResultType(ErrorTypeValue, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDaysInYear(year, basis int) int {
|
||||||
|
switch basis {
|
||||||
|
case 1:
|
||||||
|
if isLeapYear(year) {
|
||||||
|
return 366
|
||||||
|
} else {
|
||||||
|
return 365
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
return 365
|
||||||
|
default:
|
||||||
|
return 360
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func makeDateS(y int, m time.Month, d int) int64 {
|
func makeDateS(y int, m time.Month, d int) int64 {
|
||||||
if y == 1900 && int(m) <= 2 {
|
if y == 1900 && int(m) <= 2 {
|
||||||
d--
|
d--
|
||||||
@ -855,3 +878,104 @@ func feb29Between(date1, date2 time.Time) bool {
|
|||||||
var mar1year2 = makeDateS(year2, time.March, 1)
|
var mar1year2 = makeDateS(year2, time.March, 1)
|
||||||
return (isLeapYear(year2) && date2S >= mar1year2 && date1S < mar1year2)
|
return (isLeapYear(year2) && date2S >= mar1year2 && date1S < mar1year2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getDiff(from, to time.Time, basis int) float64 {
|
||||||
|
if from.After(to) {
|
||||||
|
from, to = to, from
|
||||||
|
}
|
||||||
|
diff := 0
|
||||||
|
|
||||||
|
yFrom, mFromM, dFromOrig := from.Date()
|
||||||
|
yTo, mToM, dToOrig := to.Date()
|
||||||
|
|
||||||
|
mFrom, mTo := int(mFromM), int(mToM)
|
||||||
|
dFrom, dTo := getDayOnBasis(yFrom, mFrom, dFromOrig, basis), getDayOnBasis(yTo, mTo, dToOrig, basis)
|
||||||
|
|
||||||
|
if !basis30(basis) {
|
||||||
|
return daysFromDate(yTo, mTo, dTo) - daysFromDate(yFrom, mFrom, dFrom)
|
||||||
|
}
|
||||||
|
|
||||||
|
if basis == 0 {
|
||||||
|
if (mFrom == 2 || dFrom < 30 ) && dToOrig == 31 {
|
||||||
|
dTo = 31
|
||||||
|
} else if mTo == 2 && dTo == getDaysInMonth(yTo, mTo) {
|
||||||
|
dTo = getDaysInMonth(yTo, 2)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if mFrom == 2 && dFrom == 30 {
|
||||||
|
dFrom = getDaysInMonth(yFrom, 2)
|
||||||
|
}
|
||||||
|
if mTo == 2 && dTo == 30 {
|
||||||
|
dTo = getDaysInMonth(yTo, 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if yFrom < yTo || (yFrom == yTo && mFrom < mTo) {
|
||||||
|
diff = 30 - dFrom + 1
|
||||||
|
dFromOrig = 1
|
||||||
|
dFrom = 1
|
||||||
|
fromNew := time.Date(yFrom, time.Month(mFrom), dFromOrig, 0, 0, 0, 0, time.UTC).AddDate(0,1,0)
|
||||||
|
if fromNew.Year() < yTo {
|
||||||
|
diff += getDaysInMonthRange(fromNew.Year(), int(fromNew.Month()), 12, basis)
|
||||||
|
fromNew = fromNew.AddDate(0, 13 - int(fromNew.Month()), 0)
|
||||||
|
diff += getDaysInYearRange(fromNew.Year(), yTo - 1, basis)
|
||||||
|
}
|
||||||
|
diff += getDaysInMonthRange(yTo, int(fromNew.Month()), mTo - 1, basis)
|
||||||
|
fromNew = fromNew.AddDate(0, mTo - int(fromNew.Month()), 0)
|
||||||
|
mFrom = fromNew.Day()
|
||||||
|
}
|
||||||
|
diff += dTo - dFrom
|
||||||
|
if diff > 0 {
|
||||||
|
return float64(diff)
|
||||||
|
} else {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDayOnBasis(year, month, dayOrig, basis int) int {
|
||||||
|
if !basis30(basis) {
|
||||||
|
return dayOrig
|
||||||
|
}
|
||||||
|
day := dayOrig
|
||||||
|
dim := getDaysInMonth(year, month)
|
||||||
|
if day > 30 || dayOrig >= dim || day >= dim {
|
||||||
|
day = 30
|
||||||
|
}
|
||||||
|
return day
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDaysInMonthRange(y, from, to, basis int) int {
|
||||||
|
if from > to {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if basis30(basis) {
|
||||||
|
return (to - from + 1) * 30
|
||||||
|
}
|
||||||
|
days := 0
|
||||||
|
for m := from; m <= to; m++ {
|
||||||
|
days += getDaysInMonth(y, m)
|
||||||
|
}
|
||||||
|
return days
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDaysInYearRange(from, to, basis int) int {
|
||||||
|
if from > to {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if basis30(basis) {
|
||||||
|
return (to - from + 1) * 360
|
||||||
|
}
|
||||||
|
days := 0
|
||||||
|
for y := from; y <= to; y++ {
|
||||||
|
dy := 365
|
||||||
|
if isLeapYear(y) {
|
||||||
|
dy = 366
|
||||||
|
}
|
||||||
|
days += dy
|
||||||
|
}
|
||||||
|
return days
|
||||||
|
}
|
||||||
|
|
||||||
|
func basis30(basis int) bool {
|
||||||
|
return basis == 0 || basis == 4
|
||||||
|
}
|
||||||
|
@ -17,24 +17,49 @@ func init() {
|
|||||||
RegisterFunction("MDURATION", Mduration)
|
RegisterFunction("MDURATION", Mduration)
|
||||||
RegisterFunction("PDURATION", Pduration)
|
RegisterFunction("PDURATION", Pduration)
|
||||||
RegisterFunction("_xlfn.PDURATION", Pduration)
|
RegisterFunction("_xlfn.PDURATION", Pduration)
|
||||||
|
RegisterFunction("ACCRINTM", Accrintm)
|
||||||
|
RegisterFunction("AMORDEGRC", Amordegrc)
|
||||||
|
RegisterFunction("AMORLINC", Amorlinc)
|
||||||
|
RegisterFunction("COUPDAYBS", Coupdaybs)
|
||||||
|
RegisterFunction("COUPDAYS", Coupdays)
|
||||||
|
RegisterFunction("COUPDAYSNC", Coupdaysnc)
|
||||||
|
RegisterFunction("COUPNUM", Coupnum)
|
||||||
|
RegisterFunction("COUPNCD", Coupncd)
|
||||||
|
RegisterFunction("COUPPCD", Couppcd)
|
||||||
|
RegisterFunction("CUMIPMT", Cumipmt)
|
||||||
|
RegisterFunction("CUMPRINC", Cumprinc)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Duration implements the Excel DURATION function.
|
// Duration implements the Excel DURATION function.
|
||||||
func Duration(args []Result) Result {
|
func Duration(args []Result) Result {
|
||||||
settlementDate, maturityDate, coupon, yield, freq, basis, err := parseDurationData(args, "DURATION")
|
parsedArgs, err := parseDurationData(args, "DURATION")
|
||||||
if err.Type == ResultTypeError {
|
if err.Type == ResultTypeError {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return getDuration(dateFromDays(settlementDate), dateFromDays(maturityDate), coupon, yield, freq, basis)
|
settlementDate := parsedArgs.settlementDate
|
||||||
|
maturityDate := parsedArgs.maturityDate
|
||||||
|
coupon := parsedArgs.coupon
|
||||||
|
yield := parsedArgs.yield
|
||||||
|
freq := parsedArgs.freq
|
||||||
|
basis := parsedArgs.basis
|
||||||
|
|
||||||
|
return getDuration(settlementDate, maturityDate, coupon, yield, freq, basis)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mduration implements the Excel MDURATION function.
|
// Mduration implements the Excel MDURATION function.
|
||||||
func Mduration(args []Result) Result {
|
func Mduration(args []Result) Result {
|
||||||
settlementDate, maturityDate, coupon, yield, freq, basis, err := parseDurationData(args, "MDURATION")
|
parsedArgs, err := parseDurationData(args, "MDURATION")
|
||||||
if err.Type == ResultTypeError {
|
if err.Type == ResultTypeError {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
duration := getDuration(dateFromDays(settlementDate), dateFromDays(maturityDate), coupon, yield, freq, basis)
|
settlementDate := parsedArgs.settlementDate
|
||||||
|
maturityDate := parsedArgs.maturityDate
|
||||||
|
coupon := parsedArgs.coupon
|
||||||
|
yield := parsedArgs.yield
|
||||||
|
freq := parsedArgs.freq
|
||||||
|
basis := parsedArgs.basis
|
||||||
|
|
||||||
|
duration := getDuration(settlementDate, maturityDate, coupon, yield, freq, basis)
|
||||||
if duration.Type == ResultTypeError {
|
if duration.Type == ResultTypeError {
|
||||||
return duration
|
return duration
|
||||||
}
|
}
|
||||||
@ -71,8 +96,162 @@ func Pduration(args []Result) Result {
|
|||||||
return MakeNumberResult((math.Log10(specifiedValue) - math.Log10(currentValue)) / math.Log10(1 + rate))
|
return MakeNumberResult((math.Log10(specifiedValue) - math.Log10(currentValue)) / math.Log10(1 + rate))
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCouppcd finds last coupon date before settlement (can be equal to settlement).
|
type couponArgs struct {
|
||||||
func getCouppcd(settlementDate, maturityDate time.Time, freq int) time.Time {
|
settlementDate float64
|
||||||
|
maturityDate float64
|
||||||
|
freq int
|
||||||
|
basis int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coupdaybs implements the Excel COUPDAYBS function.
|
||||||
|
func Coupdaybs(args []Result) Result {
|
||||||
|
parsedArgs, err := parseCouponArgs(args, "COUPDAYBS")
|
||||||
|
if err.Type == ResultTypeError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
settlementDate := dateFromDays(parsedArgs.settlementDate)
|
||||||
|
maturityDate := dateFromDays(parsedArgs.maturityDate)
|
||||||
|
freq := parsedArgs.freq
|
||||||
|
basis := parsedArgs.basis
|
||||||
|
pcd := couppcd(settlementDate, maturityDate, freq, basis)
|
||||||
|
return MakeNumberResult(getDiff(pcd, settlementDate, basis))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coupdays implements the Excel COUPDAYS function.
|
||||||
|
func Coupdays(args []Result) Result {
|
||||||
|
parsedArgs, err := parseCouponArgs(args, "COUPDAYS")
|
||||||
|
if err.Type == ResultTypeError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
settlementDate := dateFromDays(parsedArgs.settlementDate)
|
||||||
|
maturityDate := dateFromDays(parsedArgs.maturityDate)
|
||||||
|
freq := parsedArgs.freq
|
||||||
|
basis := parsedArgs.basis
|
||||||
|
if basis == 1 {
|
||||||
|
pcd := couppcd(settlementDate, maturityDate, freq, 1)
|
||||||
|
next := pcd.AddDate(0, 12 / freq, 0)
|
||||||
|
return MakeNumberResult(getDiff(pcd, next, basis))
|
||||||
|
}
|
||||||
|
return MakeNumberResult(float64(getDaysInYear(0, basis)) / float64(freq))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coupdaysnc implements the Excel COUPDAYSNC function.
|
||||||
|
func Coupdaysnc(args []Result) Result {
|
||||||
|
parsedArgs, err := parseCouponArgs(args, "COUPDAYSNC")
|
||||||
|
if err.Type == ResultTypeError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
settlementDate := dateFromDays(parsedArgs.settlementDate)
|
||||||
|
maturityDate := dateFromDays(parsedArgs.maturityDate)
|
||||||
|
freq := parsedArgs.freq
|
||||||
|
basis := parsedArgs.basis
|
||||||
|
ncd := coupncd(settlementDate, maturityDate, freq)
|
||||||
|
return MakeNumberResult(getDiff(settlementDate, ncd, basis))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couppcd implements the Excel COUPPCD function.
|
||||||
|
func Couppcd(args []Result) Result {
|
||||||
|
parsedArgs, err := parseCouponArgs(args, "COUPPCD")
|
||||||
|
if err.Type == ResultTypeError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
settlementDate := dateFromDays(parsedArgs.settlementDate)
|
||||||
|
maturityDate := dateFromDays(parsedArgs.maturityDate)
|
||||||
|
freq := parsedArgs.freq
|
||||||
|
basis := parsedArgs.basis
|
||||||
|
pcd := couppcd(settlementDate, maturityDate, freq, basis)
|
||||||
|
y, m, d := pcd.Date()
|
||||||
|
return MakeNumberResult(daysFromDate(y, int(m), d))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coupnum implements the Excel COUPNUM function.
|
||||||
|
func Coupnum(args []Result) Result {
|
||||||
|
parsedArgs, err := parseCouponArgs(args, "COUPNUM")
|
||||||
|
if err.Type == ResultTypeError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
settlementDate := dateFromDays(parsedArgs.settlementDate)
|
||||||
|
maturityDate := dateFromDays(parsedArgs.maturityDate)
|
||||||
|
freq := parsedArgs.freq
|
||||||
|
basis := parsedArgs.basis
|
||||||
|
cn, err := coupnum(settlementDate, maturityDate, freq, basis)
|
||||||
|
if err.Type == ResultTypeError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return MakeNumberResult(cn)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Coupncd implements the Excel COUPNCD function.
|
||||||
|
func Coupncd(args []Result) Result {
|
||||||
|
parsedArgs, err := parseCouponArgs(args, "COUPNCD")
|
||||||
|
if err.Type == ResultTypeError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
settlementDate := dateFromDays(parsedArgs.settlementDate)
|
||||||
|
maturityDate := dateFromDays(parsedArgs.maturityDate)
|
||||||
|
freq := parsedArgs.freq
|
||||||
|
ncd := coupncd(settlementDate, maturityDate, freq)
|
||||||
|
y, m, d := ncd.Date()
|
||||||
|
return MakeNumberResult(daysFromDate(y, int(m), d))
|
||||||
|
}
|
||||||
|
|
||||||
|
func coupncd(settlementDate, maturityDate time.Time, freq int) time.Time {
|
||||||
|
ncd := time.Date(settlementDate.Year(), maturityDate.Month(), maturityDate.Day(), 0, 0, 0, 0, time.UTC)
|
||||||
|
if ncd.After(settlementDate) {
|
||||||
|
ncd = ncd.AddDate(-1, 0, 0)
|
||||||
|
}
|
||||||
|
for !ncd.After(settlementDate) {
|
||||||
|
ncd = ncd.AddDate(0, 12 / freq, 0)
|
||||||
|
}
|
||||||
|
return ncd
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCouponArgs(args []Result, funcName string) (*couponArgs, Result) {
|
||||||
|
argsNum := len(args)
|
||||||
|
if argsNum != 3 && argsNum != 4 {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires four arguments")
|
||||||
|
}
|
||||||
|
if args[0].Type != ResultTypeNumber {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires settlement date to be number argument")
|
||||||
|
}
|
||||||
|
settlementDate := args[0].ValueNumber
|
||||||
|
if settlementDate < 0 {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires settlement date to be non negative")
|
||||||
|
}
|
||||||
|
if args[1].Type != ResultTypeNumber {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires maturity date to be number argument")
|
||||||
|
}
|
||||||
|
maturityDate := args[1].ValueNumber
|
||||||
|
if maturityDate <= settlementDate {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires maturity date to be later than settlement date")
|
||||||
|
}
|
||||||
|
if args[2].Type != ResultTypeNumber {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires frequency to be number argument")
|
||||||
|
}
|
||||||
|
freq := args[2].ValueNumber
|
||||||
|
if !checkFreq(freq) {
|
||||||
|
return nil, MakeErrorResult("Incorrect frequency for " + funcName)
|
||||||
|
}
|
||||||
|
basis := 0
|
||||||
|
if argsNum == 4 {
|
||||||
|
if args[3].Type != ResultTypeNumber {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires basis to be number argument")
|
||||||
|
}
|
||||||
|
basis = int(args[3].ValueNumber)
|
||||||
|
if !checkBasis(basis) {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for " + funcName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &couponArgs{
|
||||||
|
settlementDate,
|
||||||
|
maturityDate,
|
||||||
|
int(freq),
|
||||||
|
basis,
|
||||||
|
}, MakeEmptyResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
// couppcd finds last coupon date before settlement (can be equal to settlement).
|
||||||
|
func couppcd(settlementDate, maturityDate time.Time, freq, basis int) time.Time {
|
||||||
rDate := maturityDate
|
rDate := maturityDate
|
||||||
diffYears := settlementDate.Year() - maturityDate.Year()
|
diffYears := settlementDate.Year() - maturityDate.Year()
|
||||||
rDate = rDate.AddDate(diffYears, 0, 0)
|
rDate = rDate.AddDate(diffYears, 0, 0)
|
||||||
@ -86,41 +265,44 @@ func getCouppcd(settlementDate, maturityDate time.Time, freq int) time.Time {
|
|||||||
return rDate
|
return rDate
|
||||||
}
|
}
|
||||||
|
|
||||||
// getCoupnum gets count of coupon dates.
|
// coupnum gets count of coupon dates.
|
||||||
func getCoupnum(settlementDate, maturityDate time.Time, freq, basis int) float64 {
|
func coupnum(settlementDate, maturityDate time.Time, freq, basis int) (float64, Result) {
|
||||||
if maturityDate.After(settlementDate) {
|
if maturityDate.After(settlementDate) {
|
||||||
aDate := getCouppcd(settlementDate, maturityDate, freq)
|
aDate := couppcd(settlementDate, maturityDate, freq, basis)
|
||||||
months := (maturityDate.Year() - aDate.Year()) * 12 + int(maturityDate.Month()) - int(aDate.Month())
|
months := (maturityDate.Year() - aDate.Year()) * 12 + int(maturityDate.Month()) - int(aDate.Month())
|
||||||
return float64(months * freq) / 12.0
|
return float64(months * freq) / 12.0, MakeEmptyResult()
|
||||||
}
|
}
|
||||||
return 0.0 // replace for error
|
return 0, MakeErrorResultType(ErrorTypeNum, "Settlement date should be before maturity date")
|
||||||
}
|
}
|
||||||
|
|
||||||
// getDuration returns the Macauley duration for an assumed par value of $100. It is defined as the weighted average of the present value of cash flows, and is used as a measure of a bond price's response to changes in yield.
|
// getDuration returns the Macauley duration for an assumed par value of $100. It is defined as the weighted average of the present value of cash flows, and is used as a measure of a bond price's response to changes in yield.
|
||||||
func getDuration(settlementDate, maturityDate time.Time, coup, yield, freq float64, basis int) Result {
|
func getDuration(settlementDate, maturityDate, coup, yield, freq float64, basis int) Result {
|
||||||
fracResult := yearFrac(settlementDate, maturityDate, basis)
|
fracResult := yearFrac(settlementDate, maturityDate, basis)
|
||||||
if fracResult.Type == ResultTypeError {
|
if fracResult.Type == ResultTypeError {
|
||||||
return fracResult
|
return fracResult
|
||||||
}
|
}
|
||||||
frac := fracResult.ValueNumber
|
frac := fracResult.ValueNumber
|
||||||
nCoups := getCoupnum(settlementDate, maturityDate, int(freq), basis)
|
coups, err := coupnum(dateFromDays(settlementDate), dateFromDays(maturityDate), int(freq), basis)
|
||||||
|
if err.Type == ResultTypeError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
duration := 0.0
|
duration := 0.0
|
||||||
p := 0.0
|
p := 0.0
|
||||||
coup *= 100 / freq
|
coup *= 100 / freq
|
||||||
yield /= freq
|
yield /= freq
|
||||||
yield++
|
yield++
|
||||||
diff := frac * freq - nCoups
|
diff := frac * freq - coups
|
||||||
for t := 1.0; t < nCoups; t++ {
|
for t := 1.0; t < coups; t++ {
|
||||||
tDiff := t + diff
|
tDiff := t + diff
|
||||||
add := coup / math.Pow(yield, tDiff)
|
add := coup / math.Pow(yield, tDiff)
|
||||||
p += add
|
p += add
|
||||||
duration += tDiff * add
|
duration += tDiff * add
|
||||||
}
|
}
|
||||||
|
|
||||||
add := (coup + 100) / math.Pow(yield, nCoups + diff)
|
add := (coup + 100) / math.Pow(yield, coups + diff)
|
||||||
|
|
||||||
p += add
|
p += add
|
||||||
duration += (nCoups + diff) * add
|
duration += (coups + diff) * add
|
||||||
|
|
||||||
duration /= p
|
duration /= p
|
||||||
duration /= freq
|
duration /= freq
|
||||||
@ -128,10 +310,19 @@ func getDuration(settlementDate, maturityDate time.Time, coup, yield, freq float
|
|||||||
return MakeNumberResult(duration)
|
return MakeNumberResult(duration)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type durationArgs struct {
|
||||||
|
settlementDate float64
|
||||||
|
maturityDate float64
|
||||||
|
coupon float64
|
||||||
|
yield float64
|
||||||
|
freq float64
|
||||||
|
basis int
|
||||||
|
}
|
||||||
|
|
||||||
// validateDurationData returns settlement date, maturity date, coupon rate, yield rate, frequency of payments, day count basis and error result by parsing incoming arguments
|
// validateDurationData returns settlement date, maturity date, coupon rate, yield rate, frequency of payments, day count basis and error result by parsing incoming arguments
|
||||||
func parseDurationData(args []Result, funcName string) (float64, float64, float64, float64, float64, int, Result) {
|
func parseDurationData(args []Result, funcName string) (*durationArgs, Result) {
|
||||||
if len(args) != 5 && len(args) != 6 {
|
if len(args) != 5 && len(args) != 6 {
|
||||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult(funcName + " requires five or six arguments")
|
return nil, MakeErrorResult(funcName + " requires five or six arguments")
|
||||||
}
|
}
|
||||||
var settlementDate, maturityDate float64
|
var settlementDate, maturityDate float64
|
||||||
settlementResult := args[0]
|
settlementResult := args[0]
|
||||||
@ -141,11 +332,11 @@ func parseDurationData(args []Result, funcName string) (float64, float64, float6
|
|||||||
case ResultTypeString:
|
case ResultTypeString:
|
||||||
settlementFromString := DateValue([]Result{settlementResult})
|
settlementFromString := DateValue([]Result{settlementResult})
|
||||||
if settlementFromString.Type == ResultTypeError {
|
if settlementFromString.Type == ResultTypeError {
|
||||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult("Incorrect settltment date for " + funcName)
|
return nil, MakeErrorResult("Incorrect settltment date for " + funcName)
|
||||||
}
|
}
|
||||||
settlementDate = settlementFromString.ValueNumber
|
settlementDate = settlementFromString.ValueNumber
|
||||||
default:
|
default:
|
||||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult("Incorrect argument for " + funcName)
|
return nil, MakeErrorResult("Incorrect argument for " + funcName)
|
||||||
}
|
}
|
||||||
maturityResult := args[1]
|
maturityResult := args[1]
|
||||||
switch maturityResult.Type {
|
switch maturityResult.Type {
|
||||||
@ -154,49 +345,447 @@ func parseDurationData(args []Result, funcName string) (float64, float64, float6
|
|||||||
case ResultTypeString:
|
case ResultTypeString:
|
||||||
maturityFromString := DateValue([]Result{maturityResult})
|
maturityFromString := DateValue([]Result{maturityResult})
|
||||||
if maturityFromString.Type == ResultTypeError {
|
if maturityFromString.Type == ResultTypeError {
|
||||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult("Incorrect settltment date for " + funcName)
|
return nil, MakeErrorResult("Incorrect settltment date for " + funcName)
|
||||||
}
|
}
|
||||||
maturityDate = maturityFromString.ValueNumber
|
maturityDate = maturityFromString.ValueNumber
|
||||||
default:
|
default:
|
||||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult("Incorrect argument for " + funcName)
|
return nil, MakeErrorResult("Incorrect argument for " + funcName)
|
||||||
}
|
}
|
||||||
if settlementDate >= maturityDate {
|
if settlementDate >= maturityDate {
|
||||||
return 0, 0, 0, 0, 0, 0, MakeErrorResultType(ErrorTypeNum, "Settlement date should be before maturity date")
|
return nil, MakeErrorResultType(ErrorTypeNum, "Settlement date should be before maturity date")
|
||||||
}
|
}
|
||||||
couponResult := args[2]
|
couponResult := args[2]
|
||||||
if couponResult.Type != ResultTypeNumber {
|
if couponResult.Type != ResultTypeNumber {
|
||||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult(funcName + " requires third argument of type number")
|
return nil, MakeErrorResult(funcName + " requires third argument of type number")
|
||||||
}
|
}
|
||||||
coupon := couponResult.ValueNumber
|
coupon := couponResult.ValueNumber
|
||||||
if coupon < 0 {
|
if coupon < 0 {
|
||||||
return 0, 0, 0, 0, 0, 0, MakeErrorResultType(ErrorTypeNum, "Coupon rate should not be negative")
|
return nil, MakeErrorResultType(ErrorTypeNum, "Coupon rate should not be negative")
|
||||||
}
|
}
|
||||||
yieldResult := args[3]
|
yieldResult := args[3]
|
||||||
if yieldResult.Type != ResultTypeNumber {
|
if yieldResult.Type != ResultTypeNumber {
|
||||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult(funcName + " requires fourth argument of type number")
|
return nil, MakeErrorResult(funcName + " requires fourth argument of type number")
|
||||||
}
|
}
|
||||||
yield := yieldResult.ValueNumber
|
yield := yieldResult.ValueNumber
|
||||||
if yield < 0 {
|
if yield < 0 {
|
||||||
return 0, 0, 0, 0, 0, 0, MakeErrorResultType(ErrorTypeNum, "Yield rate should not be negative")
|
return nil, MakeErrorResultType(ErrorTypeNum, "Yield rate should not be negative")
|
||||||
}
|
}
|
||||||
freqResult := args[4]
|
freqResult := args[4]
|
||||||
if freqResult.Type != ResultTypeNumber {
|
if freqResult.Type != ResultTypeNumber {
|
||||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult(funcName + " requires fifth argument of type number")
|
return nil, MakeErrorResult(funcName + " requires fifth argument of type number")
|
||||||
}
|
}
|
||||||
freq := float64(int(freqResult.ValueNumber))
|
freq := float64(int(freqResult.ValueNumber))
|
||||||
if freq != 1 && freq != 2 && freq != 4 {
|
if !checkFreq(freq) {
|
||||||
return 0, 0, 0, 0, 0, 0, MakeErrorResultType(ErrorTypeNum, "Incorrect frequence value")
|
return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect frequence value")
|
||||||
}
|
}
|
||||||
basis := 0
|
basis := 0
|
||||||
if len(args) == 6 {
|
if len(args) == 6 {
|
||||||
basisResult := args[5]
|
basisResult := args[5]
|
||||||
if basisResult.Type != ResultTypeNumber {
|
if basisResult.Type != ResultTypeNumber {
|
||||||
return 0, 0, 0, 0, 0, 0, MakeErrorResult(funcName + " requires sixth argument of type number")
|
return nil, MakeErrorResult(funcName + " requires sixth argument of type number")
|
||||||
}
|
}
|
||||||
basis = int(basisResult.ValueNumber)
|
basis = int(basisResult.ValueNumber)
|
||||||
if basis < 0 || basis > 4 {
|
if !checkBasis(basis) {
|
||||||
return 0, 0, 0, 0, 0, 0, MakeErrorResultType(ErrorTypeNum, "Incorrect basis value")
|
return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect basis value")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return settlementDate, maturityDate, coupon, yield, freq, basis, MakeEmptyResult()
|
return &durationArgs{
|
||||||
|
settlementDate,
|
||||||
|
maturityDate,
|
||||||
|
coupon,
|
||||||
|
yield,
|
||||||
|
freq,
|
||||||
|
basis,
|
||||||
|
}, MakeEmptyResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkFreq(freq float64) bool {
|
||||||
|
return freq == 1 || freq == 2 || freq == 4
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkBasis(basis int) bool {
|
||||||
|
return basis >= 0 && basis <= 4
|
||||||
|
}
|
||||||
|
|
||||||
|
// Accrintm implements the Excel ACCRINTM function.
|
||||||
|
func Accrintm(args []Result) Result {
|
||||||
|
argsNum := len(args)
|
||||||
|
if argsNum != 4 && argsNum != 5 {
|
||||||
|
return MakeErrorResult("ACCRINTM requires four or five arguments")
|
||||||
|
}
|
||||||
|
if args[0].Type != ResultTypeNumber {
|
||||||
|
return MakeErrorResult("ACCRINTM requires issue date to be number argument")
|
||||||
|
}
|
||||||
|
issue := args[0].ValueNumber
|
||||||
|
if args[1].Type != ResultTypeNumber {
|
||||||
|
return MakeErrorResult("ACCRINTM requires settlement date to be number argument")
|
||||||
|
}
|
||||||
|
settlement := args[1].ValueNumber
|
||||||
|
if issue >= settlement {
|
||||||
|
return MakeErrorResultType(ErrorTypeNum, "ACCRINTM requires settlement date to be later than issue date")
|
||||||
|
}
|
||||||
|
if args[2].Type != ResultTypeNumber {
|
||||||
|
return MakeErrorResult("ACCRINTM requires rate to be number argument")
|
||||||
|
}
|
||||||
|
rate := args[2].ValueNumber
|
||||||
|
if rate <= 0 {
|
||||||
|
return MakeErrorResultType(ErrorTypeNum, "ACCRINTM requires rate to be positive number argument")
|
||||||
|
}
|
||||||
|
if args[3].Type != ResultTypeNumber {
|
||||||
|
return MakeErrorResult("ACCRINTM requires par to be number argument")
|
||||||
|
}
|
||||||
|
par := args[3].ValueNumber
|
||||||
|
if par <= 0 {
|
||||||
|
return MakeErrorResultType(ErrorTypeNum, "ACCRINTM requires par to be positive number argument")
|
||||||
|
}
|
||||||
|
basis := 0
|
||||||
|
if argsNum == 5 {
|
||||||
|
if args[4].Type != ResultTypeNumber {
|
||||||
|
return MakeErrorResult("ACCRINTM requires basis to be number argument")
|
||||||
|
}
|
||||||
|
basis = int(args[4].ValueNumber)
|
||||||
|
if !checkBasis(basis) {
|
||||||
|
return MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for ACCRINTM")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fracResult := yearFrac(issue, settlement, basis)
|
||||||
|
if fracResult.Type == ResultTypeError {
|
||||||
|
return fracResult
|
||||||
|
}
|
||||||
|
return MakeNumberResult(par * rate * fracResult.ValueNumber)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Amordegrc implements the Excel AMORDEGRC function.
|
||||||
|
func Amordegrc(args []Result) Result {
|
||||||
|
parsedArgs, err := parseAmorArgs(args, "AMORDEGRC")
|
||||||
|
if err.Type == ResultTypeError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cost := parsedArgs.cost
|
||||||
|
datePurchased := parsedArgs.datePurchased
|
||||||
|
firstPeriod := parsedArgs.firstPeriod
|
||||||
|
salvage := parsedArgs.salvage
|
||||||
|
period := parsedArgs.period
|
||||||
|
rate := parsedArgs.rate
|
||||||
|
if rate >= 0.5 {
|
||||||
|
return MakeErrorResultType(ErrorTypeNum, "AMORDEGRC requires rate to be less than 0.5")
|
||||||
|
}
|
||||||
|
basis := parsedArgs.basis
|
||||||
|
|
||||||
|
lifeOfAssets := 1.0 / rate
|
||||||
|
amorCoeff := 2.5
|
||||||
|
if lifeOfAssets < 3 {
|
||||||
|
amorCoeff = 1
|
||||||
|
} else if lifeOfAssets < 5 {
|
||||||
|
amorCoeff = 1.5
|
||||||
|
} else if lifeOfAssets <= 6 {
|
||||||
|
amorCoeff = 2
|
||||||
|
}
|
||||||
|
|
||||||
|
rate *= amorCoeff
|
||||||
|
yfResult := yearFrac(datePurchased, firstPeriod, basis)
|
||||||
|
if yfResult.Type == ResultTypeError {
|
||||||
|
return MakeErrorResult("incorrect dates for AMORDEGRC")
|
||||||
|
}
|
||||||
|
nRate := mathRound(yfResult.ValueNumber * rate * cost)
|
||||||
|
cost -= nRate
|
||||||
|
rest := cost - salvage
|
||||||
|
|
||||||
|
for n := 0; n < period; n++ {
|
||||||
|
nRate = mathRound(rate * cost)
|
||||||
|
rest -= nRate
|
||||||
|
if rest < 0 {
|
||||||
|
switch period - n {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
return MakeNumberResult(mathRound(cost * 0.5))
|
||||||
|
default:
|
||||||
|
return MakeNumberResult(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cost -= nRate
|
||||||
|
}
|
||||||
|
|
||||||
|
return MakeNumberResult(nRate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Amorlinc implements the Excel AMORLINC function.
|
||||||
|
func Amorlinc(args []Result) Result {
|
||||||
|
parsedArgs, err := parseAmorArgs(args, "AMORLINC")
|
||||||
|
if err.Type == ResultTypeError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cost := parsedArgs.cost
|
||||||
|
datePurchased := parsedArgs.datePurchased
|
||||||
|
firstPeriod := parsedArgs.firstPeriod
|
||||||
|
salvage := parsedArgs.salvage
|
||||||
|
period := parsedArgs.period
|
||||||
|
rate := parsedArgs.rate
|
||||||
|
basis := parsedArgs.basis
|
||||||
|
|
||||||
|
yfResult := yearFrac(datePurchased, firstPeriod, basis)
|
||||||
|
if yfResult.Type == ResultTypeError {
|
||||||
|
return MakeErrorResult("incorrect dates for AMORLINC")
|
||||||
|
}
|
||||||
|
r0 := yfResult.ValueNumber * rate * cost
|
||||||
|
if period == 0 {
|
||||||
|
return MakeNumberResult(r0)
|
||||||
|
}
|
||||||
|
|
||||||
|
oneRate := cost * rate
|
||||||
|
costDelta := cost - salvage
|
||||||
|
numOfFullPeriods := int((costDelta - r0) / oneRate)
|
||||||
|
|
||||||
|
if period <= numOfFullPeriods {
|
||||||
|
return MakeNumberResult(oneRate)
|
||||||
|
} else if period == numOfFullPeriods + 1 {
|
||||||
|
return MakeNumberResult(costDelta - oneRate * float64(numOfFullPeriods) - r0)
|
||||||
|
} else {
|
||||||
|
return MakeNumberResult(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type amorArgs struct {
|
||||||
|
cost float64
|
||||||
|
datePurchased float64
|
||||||
|
firstPeriod float64
|
||||||
|
salvage float64
|
||||||
|
period int
|
||||||
|
rate float64
|
||||||
|
basis int
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseAmorArgs(args []Result, funcName string) (*amorArgs, Result) {
|
||||||
|
argsNum := len(args)
|
||||||
|
if argsNum != 6 && argsNum != 7 {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires six or seven arguments")
|
||||||
|
}
|
||||||
|
if args[0].Type != ResultTypeNumber {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires cost to be number argument")
|
||||||
|
}
|
||||||
|
cost := args[0].ValueNumber
|
||||||
|
if cost < 0 {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires cost to be positive")
|
||||||
|
}
|
||||||
|
if args[1].Type != ResultTypeNumber {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires date purchased to be number argument")
|
||||||
|
}
|
||||||
|
datePurchased := args[1].ValueNumber
|
||||||
|
if datePurchased < 0 {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires date purchased to be positive")
|
||||||
|
}
|
||||||
|
if args[2].Type != ResultTypeNumber {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires first period to be number argument")
|
||||||
|
}
|
||||||
|
firstPeriod := args[2].ValueNumber
|
||||||
|
if firstPeriod < datePurchased {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires first period to be later than date purchased")
|
||||||
|
}
|
||||||
|
if args[3].Type != ResultTypeNumber {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires salvage to be number argument")
|
||||||
|
}
|
||||||
|
salvage := args[3].ValueNumber
|
||||||
|
if salvage < 0 || salvage > cost {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires salvage to be between 0 and the initial cost")
|
||||||
|
}
|
||||||
|
if args[4].Type != ResultTypeNumber {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires period to be number argument")
|
||||||
|
}
|
||||||
|
period := int(args[4].ValueNumber)
|
||||||
|
if period < 0 {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires period to be non-negative")
|
||||||
|
}
|
||||||
|
if args[5].Type != ResultTypeNumber {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires depreciation rate to be number argument")
|
||||||
|
}
|
||||||
|
rate := args[5].ValueNumber
|
||||||
|
if rate < 0 {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires rate to be more than 0 and less than 0.5")
|
||||||
|
}
|
||||||
|
basis := 0
|
||||||
|
if argsNum == 7 {
|
||||||
|
if args[6].Type != ResultTypeNumber {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires basis to be number argument")
|
||||||
|
}
|
||||||
|
basis = int(args[6].ValueNumber)
|
||||||
|
if !checkBasis(basis) || basis == 2 {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for " + funcName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &amorArgs{
|
||||||
|
cost,
|
||||||
|
datePurchased,
|
||||||
|
firstPeriod,
|
||||||
|
salvage,
|
||||||
|
period,
|
||||||
|
rate,
|
||||||
|
basis,
|
||||||
|
}, MakeEmptyResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func mathRound(x float64) float64 {
|
||||||
|
return float64(int(x + 0.5))
|
||||||
|
}
|
||||||
|
|
||||||
|
type cumulArgs struct {
|
||||||
|
rate float64
|
||||||
|
nPer float64
|
||||||
|
pv float64
|
||||||
|
startPeriod float64
|
||||||
|
endPeriod float64
|
||||||
|
t int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cumipmt implements the Excel CUMIPMT function.
|
||||||
|
func Cumipmt(args []Result) Result {
|
||||||
|
parsedArgs, err := parseCumulArgs(args, "CUMIPMT")
|
||||||
|
if err.Type == ResultTypeError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rate := parsedArgs.rate
|
||||||
|
nPer := parsedArgs.nPer
|
||||||
|
pv := parsedArgs.pv
|
||||||
|
startPeriod := parsedArgs.startPeriod
|
||||||
|
endPeriod := parsedArgs.endPeriod
|
||||||
|
t := parsedArgs.t
|
||||||
|
|
||||||
|
payment := pmt(rate, nPer, pv, 0, t)
|
||||||
|
interest := 0.0
|
||||||
|
if startPeriod == 1 {
|
||||||
|
if t == 0 {
|
||||||
|
interest = -pv
|
||||||
|
startPeriod++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := startPeriod; i <= endPeriod; i++ {
|
||||||
|
if t == 1 {
|
||||||
|
interest += fv(rate, i - 2, payment, pv, 1) - payment
|
||||||
|
} else {
|
||||||
|
interest += fv(rate, i - 1, payment, pv, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
interest *= rate
|
||||||
|
return MakeNumberResult(interest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cumprinc implements the Excel CUMPRINC function.
|
||||||
|
func Cumprinc(args []Result) Result {
|
||||||
|
parsedArgs, err := parseCumulArgs(args, "CUMPRINC")
|
||||||
|
if err.Type == ResultTypeError {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rate := parsedArgs.rate
|
||||||
|
nPer := parsedArgs.nPer
|
||||||
|
pv := parsedArgs.pv
|
||||||
|
startPeriod := parsedArgs.startPeriod
|
||||||
|
endPeriod := parsedArgs.endPeriod
|
||||||
|
t := parsedArgs.t
|
||||||
|
|
||||||
|
payment := pmt(rate, nPer, pv, 0, t)
|
||||||
|
principal := 0.0
|
||||||
|
if startPeriod == 1 {
|
||||||
|
if t == 0 {
|
||||||
|
principal = payment + pv * rate
|
||||||
|
} else {
|
||||||
|
principal = payment
|
||||||
|
}
|
||||||
|
startPeriod++
|
||||||
|
}
|
||||||
|
for i := startPeriod; i <= endPeriod; i++ {
|
||||||
|
if t == 1 {
|
||||||
|
principal += payment - (fv(rate, i - 2, payment, pv, 1) - payment) * rate
|
||||||
|
} else {
|
||||||
|
principal += payment - fv(rate, i - 1, payment, pv, 0) * rate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MakeNumberResult(principal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCumulArgs(args []Result, funcName string) (*cumulArgs, Result) {
|
||||||
|
if len(args) != 6 {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires six arguments")
|
||||||
|
}
|
||||||
|
if args[0].Type != ResultTypeNumber {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires rate to be number argument")
|
||||||
|
}
|
||||||
|
rate := args[0].ValueNumber
|
||||||
|
if rate <= 0 {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires rate to be positive number argument")
|
||||||
|
}
|
||||||
|
if args[1].Type != ResultTypeNumber {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires number of periods to be number argument")
|
||||||
|
}
|
||||||
|
nPer := args[1].ValueNumber
|
||||||
|
if nPer <= 0 {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires number of periods to be positive number argument")
|
||||||
|
}
|
||||||
|
if args[2].Type != ResultTypeNumber {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires present value to be number argument")
|
||||||
|
}
|
||||||
|
pv := args[2].ValueNumber
|
||||||
|
if pv <= 0 {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires present value to be positive number argument")
|
||||||
|
}
|
||||||
|
if args[3].Type != ResultTypeNumber {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires start period to be number argument")
|
||||||
|
}
|
||||||
|
startPeriod := args[3].ValueNumber
|
||||||
|
if startPeriod <= 0 {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires start period to be positive number argument")
|
||||||
|
}
|
||||||
|
if args[4].Type != ResultTypeNumber {
|
||||||
|
return nil, MakeErrorResult(funcName + " requires end period to be number argument")
|
||||||
|
}
|
||||||
|
endPeriod := args[4].ValueNumber
|
||||||
|
if endPeriod <= 0 {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires end period to be positive number argument")
|
||||||
|
}
|
||||||
|
if endPeriod < startPeriod {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires end period to be later or equal to start period")
|
||||||
|
}
|
||||||
|
if endPeriod > nPer {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires periods to be in number of periods range")
|
||||||
|
}
|
||||||
|
t := int(args[5].ValueNumber)
|
||||||
|
if t != 0 && t != 1 {
|
||||||
|
return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires type to be 0 or 1")
|
||||||
|
}
|
||||||
|
return &cumulArgs{
|
||||||
|
rate,
|
||||||
|
nPer,
|
||||||
|
pv,
|
||||||
|
startPeriod,
|
||||||
|
endPeriod,
|
||||||
|
t,
|
||||||
|
}, MakeEmptyResult()
|
||||||
|
}
|
||||||
|
|
||||||
|
func pmt(rate, periods, present, future float64, t int ) float64 {
|
||||||
|
var result float64
|
||||||
|
if rate == 0 {
|
||||||
|
result = (present + future) / periods
|
||||||
|
} else {
|
||||||
|
term := math.Pow(1 + rate, periods)
|
||||||
|
if t == 1 {
|
||||||
|
result = (future * rate / (term - 1) + present * rate / (1 - 1 / term)) / (1 + rate)
|
||||||
|
} else {
|
||||||
|
result = future * rate / (term - 1) + present * rate / (1 - 1 / term)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -result
|
||||||
|
}
|
||||||
|
|
||||||
|
func fv(rate, periods, payment, value float64, t int) float64 {
|
||||||
|
var result float64
|
||||||
|
if rate == 0 {
|
||||||
|
result = value + payment * periods
|
||||||
|
} else {
|
||||||
|
term := math.Pow(1 + rate, periods)
|
||||||
|
if t == 1 {
|
||||||
|
result = value * term + payment * (1 + rate) * (term - 1) / rate
|
||||||
|
} else {
|
||||||
|
result = value * term + payment * (term - 1) / rate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -result
|
||||||
}
|
}
|
||||||
|
@ -1659,3 +1659,212 @@ func TestIf(t *testing.T) {
|
|||||||
|
|
||||||
runTests(t, ctx, td)
|
runTests(t, ctx, td)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAccrintm(t *testing.T) {
|
||||||
|
ss := spreadsheet.New()
|
||||||
|
sheet := ss.AddSheet()
|
||||||
|
|
||||||
|
ctx := sheet.FormulaContext()
|
||||||
|
|
||||||
|
td := []testStruct{
|
||||||
|
{`=ACCRINTM(39539,39614,0.1,1000,0))`, `20.5555555555 ResultTypeNumber`},
|
||||||
|
{`=ACCRINTM(39539,39614,0.1,1000,1))`, `20.4918032786 ResultTypeNumber`},
|
||||||
|
{`=ACCRINTM(39539,39614,0.1,1000,2))`, `20.8333333333 ResultTypeNumber`},
|
||||||
|
{`=ACCRINTM(39539,39614,0.1,1000,3))`, `20.5479452054 ResultTypeNumber`},
|
||||||
|
{`=ACCRINTM(39539,39614,0.1,1000,4))`, `20.5555555555 ResultTypeNumber`},
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests(t, ctx, td)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAmordegrc(t *testing.T) {
|
||||||
|
ss := spreadsheet.New()
|
||||||
|
sheet := ss.AddSheet()
|
||||||
|
|
||||||
|
ctx := sheet.FormulaContext()
|
||||||
|
|
||||||
|
td := []testStruct{
|
||||||
|
{`=AMORDEGRC(2400,39679,39813,300,1,0.15,1)`, `776 ResultTypeNumber`},
|
||||||
|
{`=AMORDEGRC(2400,39679,39813,300,1,0.15,2)`, `#NUM! ResultTypeError`},
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests(t, ctx, td)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAmorlinc(t *testing.T) {
|
||||||
|
ss := spreadsheet.New()
|
||||||
|
sheet := ss.AddSheet()
|
||||||
|
|
||||||
|
ctx := sheet.FormulaContext()
|
||||||
|
|
||||||
|
td := []testStruct{
|
||||||
|
{`=AMORLINC(2400,39679,39813,300,1,0.15,1)`, `360 ResultTypeNumber`},
|
||||||
|
{`=AMORLINC(2400,39679,39813,300,1,0.15,2)`, `#NUM! ResultTypeError`},
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests(t, ctx, td)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCoupdaybs(t *testing.T) {
|
||||||
|
ss := spreadsheet.New()
|
||||||
|
sheet := ss.AddSheet()
|
||||||
|
|
||||||
|
ctx := sheet.FormulaContext()
|
||||||
|
|
||||||
|
td := []testStruct{
|
||||||
|
{`=COUPDAYBS(40568,40862,2,0)`, `70 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYBS(40568,40862,2,1)`, `71 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYBS(40568,40862,2,2)`, `71 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYBS(40568,40862,2,3)`, `71 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYBS(40568,40862,2,4)`, `70 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYBS(40599,40862,2,0)`, `100 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYBS(40599,40862,2,1)`, `102 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYBS(40599,40862,2,2)`, `102 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYBS(40599,40862,2,3)`, `102 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYBS(40599,40862,2,4)`, `100 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYBS(40811,40862,2,0)`, `130 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYBS(40811,40862,2,1)`, `133 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYBS(40811,40862,2,2)`, `133 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYBS(40811,40862,2,3)`, `133 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYBS(40811,40862,2,4)`, `130 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYBS(40872,40568,2,1)`, `#NUM! ResultTypeError`},
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests(t, ctx, td)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCoupdays(t *testing.T) {
|
||||||
|
ss := spreadsheet.New()
|
||||||
|
sheet := ss.AddSheet()
|
||||||
|
|
||||||
|
ctx := sheet.FormulaContext()
|
||||||
|
|
||||||
|
td := []testStruct{
|
||||||
|
{`=COUPDAYS(40964,41228,1,0)`, `360 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYS(40964,41228,1,1)`, `366 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYS(40964,41228,1,2)`, `360 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYS(40964,41228,1,3)`, `365 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYS(40964,41228,1,4)`, `360 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYS(40964,41228,2,0)`, `180 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYS(40964,41228,2,1)`, `182 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYS(40964,41228,2,2)`, `180 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYS(40964,41228,2,3)`, `182.5 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYS(40964,41228,2,4)`, `180 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYS(40964,41228,4,0)`, `90 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYS(40964,41228,4,1)`, `90 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYS(40964,41228,4,2)`, `90 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYS(40964,41228,4,3)`, `91.25 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYS(40964,41228,4,4)`, `90 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYS(41228,40964,2,1)`, `#NUM! ResultTypeError`},
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests(t, ctx, td)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCoupdaysnc(t *testing.T) {
|
||||||
|
ss := spreadsheet.New()
|
||||||
|
sheet := ss.AddSheet()
|
||||||
|
|
||||||
|
ctx := sheet.FormulaContext()
|
||||||
|
|
||||||
|
td := []testStruct{
|
||||||
|
{`=COUPDAYSNC(40933,41228,1,0)`, `290 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYSNC(40933,41228,1,1)`, `295 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYSNC(40933,41228,1,2)`, `295 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYSNC(40933,41228,1,3)`, `295 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYSNC(40933,41228,1,4)`, `290 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYSNC(40933,41228,2,0)`, `110 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYSNC(40933,41228,2,1)`, `111 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYSNC(40933,41228,2,2)`, `111 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYSNC(40933,41228,2,3)`, `111 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYSNC(40933,41228,2,4)`, `110 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYSNC(40933,41228,4,0)`, `20 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYSNC(40933,41228,4,1)`, `21 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYSNC(40933,41228,4,2)`, `21 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYSNC(40933,41228,4,3)`, `21 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYSNC(40933,41228,4,4)`, `20 ResultTypeNumber`},
|
||||||
|
{`=COUPDAYSNC(41228,40933,2,1)`, `#NUM! ResultTypeError`},
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests(t, ctx, td)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCoupncd(t *testing.T) {
|
||||||
|
ss := spreadsheet.New()
|
||||||
|
sheet := ss.AddSheet()
|
||||||
|
|
||||||
|
ctx := sheet.FormulaContext()
|
||||||
|
|
||||||
|
td := []testStruct{
|
||||||
|
{`=COUPNCD(40568,40862,1,1)`, `40862 ResultTypeNumber`},
|
||||||
|
{`=COUPNCD(40568,40862,2,1)`, `40678 ResultTypeNumber`},
|
||||||
|
{`=COUPNCD(40568,40862,4,1)`, `40589 ResultTypeNumber`},
|
||||||
|
{`=COUPNCD(40872,40568,2,1)`, `#NUM! ResultTypeError`},
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests(t, ctx, td)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCouppcd(t *testing.T) {
|
||||||
|
ss := spreadsheet.New()
|
||||||
|
sheet := ss.AddSheet()
|
||||||
|
|
||||||
|
ctx := sheet.FormulaContext()
|
||||||
|
|
||||||
|
td := []testStruct{
|
||||||
|
{`=COUPPCD(40568,40862,2,1)`, `40497 ResultTypeNumber`},
|
||||||
|
{`=COUPPCD(40872,40568,2,1)`, `#NUM! ResultTypeError`},
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests(t, ctx, td)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCoupnum(t *testing.T) {
|
||||||
|
ss := spreadsheet.New()
|
||||||
|
sheet := ss.AddSheet()
|
||||||
|
|
||||||
|
ctx := sheet.FormulaContext()
|
||||||
|
|
||||||
|
td := []testStruct{
|
||||||
|
{`=COUPNUM(39107,39767,2,1)`, `4 ResultTypeNumber`},
|
||||||
|
{`=COUPNUM(39767,39107,2,1)`, `#NUM! ResultTypeError`},
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests(t, ctx, td)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCumipmt(t *testing.T) {
|
||||||
|
ss := spreadsheet.New()
|
||||||
|
sheet := ss.AddSheet()
|
||||||
|
|
||||||
|
sheet.Cell("A1").SetNumber(0.09)
|
||||||
|
sheet.Cell("A2").SetNumber(30)
|
||||||
|
sheet.Cell("A3").SetNumber(125000)
|
||||||
|
|
||||||
|
ctx := sheet.FormulaContext()
|
||||||
|
|
||||||
|
td := []testStruct{
|
||||||
|
{`=CUMIPMT(A1/12,A2*12,A3,13,24,0)`, `-11135.23213 ResultTypeNumber`},
|
||||||
|
{`=CUMIPMT(A1/12,A2*12,A3,1,1,0)`, `-937.5 ResultTypeNumber`},
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests(t, ctx, td)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCumprinc(t *testing.T) {
|
||||||
|
ss := spreadsheet.New()
|
||||||
|
sheet := ss.AddSheet()
|
||||||
|
|
||||||
|
sheet.Cell("A1").SetNumber(0.09)
|
||||||
|
sheet.Cell("A2").SetNumber(30)
|
||||||
|
sheet.Cell("A3").SetNumber(125000)
|
||||||
|
|
||||||
|
ctx := sheet.FormulaContext()
|
||||||
|
|
||||||
|
td := []testStruct{
|
||||||
|
{`=CUMPRINC(A1/12,A2*12,A3,13,24,0)`, `-934.10712342 ResultTypeNumber`},
|
||||||
|
{`=CUMPRINC(A1/12,A2*12,A3,1,1,0)`, `-68.27827118 ResultTypeNumber`},
|
||||||
|
}
|
||||||
|
|
||||||
|
runTests(t, ctx, td)
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user