From 154effdf82df2f662b91361006c46a69c3cd3635 Mon Sep 17 00:00:00 2001 From: xuri Date: Sun, 24 Oct 2021 12:20:24 +0800 Subject: [PATCH] ref #65: new formula functions YIELDDISC and YIELDMAT --- calc.go | 113 +++++++++++++++++++++++++++++++++++++++++++++++++++ calc_test.go | 28 +++++++++++++ 2 files changed, 141 insertions(+) diff --git a/calc.go b/calc.go index 385503f..51a830e 100644 --- a/calc.go +++ b/calc.go @@ -554,6 +554,8 @@ type formulaFuncs struct { // XOR // YEAR // YEARFRAC +// YIELDDISC +// YIELDMAT // Z.TEST // ZTEST // @@ -9705,3 +9707,114 @@ func (fn *formulaFuncs) SYD(argsList *list.List) formulaArg { } return newNumberFormulaArg(((cost.Number - salvage.Number) * (life.Number - per.Number + 1) * 2) / (life.Number * (life.Number + 1))) } + +// YIELDDISC function calculates the annual yield of a discounted security. +// The syntax of the function is: +// +// YIELDDISC(settlement,maturity,pr,redemption,[basis]) +// +func (fn *formulaFuncs) YIELDDISC(argsList *list.List) formulaArg { + if argsList.Len() != 4 && argsList.Len() != 5 { + return newErrorFormulaArg(formulaErrorVALUE, "YIELDDISC requires 4 or 5 arguments") + } + args := list.New().Init() + args.PushBack(argsList.Front().Value.(formulaArg)) + settlement := fn.DATEVALUE(args) + if settlement.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + args.Init() + args.PushBack(argsList.Front().Next().Value.(formulaArg)) + maturity := fn.DATEVALUE(args) + if maturity.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + pr := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if pr.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if pr.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "YIELDDISC requires pr > 0") + } + redemption := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if redemption.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if redemption.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "YIELDDISC requires redemption > 0") + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 5 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + frac := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) + if frac.Type != ArgNumber { + return frac + } + return newNumberFormulaArg((redemption.Number/pr.Number - 1) / frac.Number) +} + +// YIELDMAT function calculates the annual yield of a security that pays +// interest at maturity. The syntax of the function is: +// +// YIELDMAT(settlement,maturity,issue,rate,pr,[basis]) +// +func (fn *formulaFuncs) YIELDMAT(argsList *list.List) formulaArg { + if argsList.Len() != 5 && argsList.Len() != 6 { + return newErrorFormulaArg(formulaErrorVALUE, "YIELDMAT requires 5 or 6 arguments") + } + args := list.New().Init() + args.PushBack(argsList.Front().Value.(formulaArg)) + settlement := fn.DATEVALUE(args) + if settlement.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + args.Init() + args.PushBack(argsList.Front().Next().Value.(formulaArg)) + maturity := fn.DATEVALUE(args) + if maturity.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + args.Init() + args.PushBack(argsList.Front().Next().Next().Value.(formulaArg)) + issue := fn.DATEVALUE(args) + if issue.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if issue.Number >= settlement.Number { + return newErrorFormulaArg(formulaErrorNUM, "YIELDMAT requires settlement > issue") + } + rate := argsList.Front().Next().Next().Next().Value.(formulaArg).ToNumber() + if rate.Type != ArgNumber { + return rate + } + if rate.Number < 0 { + return newErrorFormulaArg(formulaErrorNUM, "YIELDMAT requires rate >= 0") + } + pr := argsList.Front().Next().Next().Next().Next().Value.(formulaArg).ToNumber() + if pr.Type != ArgNumber { + return pr + } + if pr.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "YIELDMAT requires pr > 0") + } + basis := newNumberFormulaArg(0) + if argsList.Len() == 6 { + if basis = argsList.Back().Value.(formulaArg).ToNumber(); basis.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorNUM, formulaErrorNUM) + } + } + dim := yearFrac(issue.Number, maturity.Number, int(basis.Number)) + if dim.Type != ArgNumber { + return dim + } + dis := yearFrac(issue.Number, settlement.Number, int(basis.Number)) + dsm := yearFrac(settlement.Number, maturity.Number, int(basis.Number)) + result := 1 + dim.Number*rate.Number + result /= pr.Number/100 + dis.Number*rate.Number + result-- + result /= dsm.Number + return newNumberFormulaArg(result) +} diff --git a/calc_test.go b/calc_test.go index 2412615..1df622e 100644 --- a/calc_test.go +++ b/calc_test.go @@ -1354,6 +1354,12 @@ func TestCalcCellValue(t *testing.T) { // SYD "=SYD(10000,1000,5,1)": "3000", "=SYD(10000,1000,5,2)": "2400", + // YIELDDISC + "=YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,100)": "0.0622012325059031", + "=YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,100,0)": "0.0622012325059031", + // YIELDMAT + "=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5.5%,101)": "0.0419422478838651", + "=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5.5%,101,0)": "0.0419422478838651", } for formula, expected := range mathCalc { f := prepareCalcData(cellData) @@ -2615,6 +2621,28 @@ func TestCalcCellValue(t *testing.T) { "=SYD(10000,1000,0,1)": "SYD requires life argument to be > 0", "=SYD(10000,1000,5,0)": "SYD requires per argument to be > 0", "=SYD(10000,1000,1,5)": "#NUM!", + // YIELDDISC + "=YIELDDISC()": "YIELDDISC requires 4 or 5 arguments", + "=YIELDDISC(\"\",\"06/30/2017\",97,100,0)": "#VALUE!", + "=YIELDDISC(\"01/01/2017\",\"\",97,100,0)": "#VALUE!", + "=YIELDDISC(\"01/01/2017\",\"06/30/2017\",\"\",100,0)": "#VALUE!", + "=YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,\"\",0)": "#VALUE!", + "=YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,100,\"\")": "#NUM!", + "=YIELDDISC(\"01/01/2017\",\"06/30/2017\",0,100)": "YIELDDISC requires pr > 0", + "=YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,0)": "YIELDDISC requires redemption > 0", + "=YIELDDISC(\"01/01/2017\",\"06/30/2017\",97,100,5)": "invalid basis", + // YIELDMAT + "=YIELDMAT()": "YIELDMAT requires 5 or 6 arguments", + "=YIELDMAT(\"\",\"06/30/2018\",\"06/01/2014\",5.5%,101,0)": "#VALUE!", + "=YIELDMAT(\"01/01/2017\",\"\",\"06/01/2014\",5.5%,101,0)": "#VALUE!", + "=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"\",5.5%,101,0)": "#VALUE!", + "=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",\"\",101,0)": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax", + "=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5,5.5%,\"\")": "#NUM!", + "=YIELDMAT(\"06/01/2014\",\"06/30/2018\",\"01/01/2017\",5.5%,101,0)": "YIELDMAT requires settlement > issue", + "=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",-1,101,0)": "YIELDMAT requires rate >= 0", + "=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",1,0,0)": "YIELDMAT requires pr > 0", + "=YIELDMAT(\"01/01/2017\",\"06/30/2018\",\"06/01/2014\",5.5%,101,5)": "invalid basis", } for formula, expected := range mathCalcError { f := prepareCalcData(cellData)