From 49e80b9e47a76252d27b1e2863541fc3b49ed488 Mon Sep 17 00:00:00 2001 From: xuri Date: Thu, 21 Oct 2021 00:33:25 +0800 Subject: [PATCH] ref #65: new formula functions DISC and INTRATE --- calc.go | 104 +++++++++++++++++++++++++++++++++++++++++++++++++++ calc_test.go | 26 +++++++++++++ 2 files changed, 130 insertions(+) diff --git a/calc.go b/calc.go index 82817f3..8b53352 100644 --- a/calc.go +++ b/calc.go @@ -327,6 +327,7 @@ type formulaFuncs struct { // DEC2OCT // DECIMAL // DEGREES +// DISC // DOLLARDE // DOLLARFR // EFFECT @@ -385,6 +386,7 @@ type formulaFuncs struct { // IMSUM // IMTAN // INT +// INTRATE // IPMT // IRR // ISBLANK @@ -8773,6 +8775,57 @@ func (fn *formulaFuncs) DDB(argsList *list.List) formulaArg { return newNumberFormulaArg(depreciation) } +// DISC function calculates the Discount Rate for a security. The syntax of +// the function is: +// +// DISC(settlement,maturity,pr,redemption,[basis]) +// +func (fn *formulaFuncs) DISC(argsList *list.List) formulaArg { + if argsList.Len() != 4 && argsList.Len() != 5 { + return newErrorFormulaArg(formulaErrorVALUE, "DISC 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) + } + if maturity.Number <= settlement.Number { + return newErrorFormulaArg(formulaErrorNUM, "DISC requires maturity > settlement") + } + pr := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if pr.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if pr.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "DISC 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, "DISC 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) / redemption.Number / frac.Number) +} + // DOLLARDE function converts a dollar value in fractional notation, into a // dollar value expressed as a decimal. The syntax of the function is: // @@ -8918,6 +8971,57 @@ func (fn *formulaFuncs) FVSCHEDULE(argsList *list.List) formulaArg { return newNumberFormulaArg(principal) } +// INTRATE function calculates the interest rate for a fully invested +// security. The syntax of the function is: +// +// INTRATE(settlement,maturity,investment,redemption,[basis]) +// +func (fn *formulaFuncs) INTRATE(argsList *list.List) formulaArg { + if argsList.Len() != 4 && argsList.Len() != 5 { + return newErrorFormulaArg(formulaErrorVALUE, "INTRATE 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) + } + if maturity.Number <= settlement.Number { + return newErrorFormulaArg(formulaErrorNUM, "INTRATE requires maturity > settlement") + } + investment := argsList.Front().Next().Next().Value.(formulaArg).ToNumber() + if investment.Type != ArgNumber { + return newErrorFormulaArg(formulaErrorVALUE, formulaErrorVALUE) + } + if investment.Number <= 0 { + return newErrorFormulaArg(formulaErrorNUM, "INTRATE requires investment > 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, "INTRATE 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 - investment.Number) / investment.Number / frac.Number) +} + // IPMT function calculates the interest payment, during a specific period of a // loan or investment that is paid in constant periodic payments, with a // constant interest rate. The syntax of the function is: diff --git a/calc_test.go b/calc_test.go index fb5876c..7262fa9 100644 --- a/calc_test.go +++ b/calc_test.go @@ -1286,6 +1286,8 @@ func TestCalcCellValue(t *testing.T) { "=DDB(10000,1000,5,3)": "1440", "=DDB(10000,1000,5,4)": "864", "=DDB(10000,1000,5,5)": "296", + // DISC + "=DISC(\"04/01/2016\",\"03/31/2021\",95,100)": "0.01", // DOLLARDE "=DOLLARDE(1.01,16)": "1.0625", // DOLLARFR @@ -1300,6 +1302,8 @@ func TestCalcCellValue(t *testing.T) { // FVSCHEDULE "=FVSCHEDULE(10000,A1:A5)": "240000", "=FVSCHEDULE(10000,0.5)": "15000", + // INTRATE + "=INTRATE(\"04/01/2005\",\"03/31/2010\",1000,2125)": "0.225", // IPMT "=IPMT(0.05/12,2,60,50000)": "-205.26988187971995", "=IPMT(0.035/4,2,8,0,5000,1)": "5.257455237829077", @@ -2428,6 +2432,17 @@ func TestCalcCellValue(t *testing.T) { "=DDB(0,0,\"\",0,0)": "strconv.ParseFloat: parsing \"\": invalid syntax", "=DDB(0,0,0,\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax", "=DDB(0,0,0,0,\"\")": "strconv.ParseFloat: parsing \"\": invalid syntax", + // DISC + "=DISC()": "DISC requires 4 or 5 arguments", + "=DISC(\"\",\"03/31/2021\",95,100)": "#VALUE!", + "=DISC(\"04/01/2016\",\"\",95,100)": "#VALUE!", + "=DISC(\"04/01/2016\",\"03/31/2021\",\"\",100)": "#VALUE!", + "=DISC(\"04/01/2016\",\"03/31/2021\",95,\"\")": "#VALUE!", + "=DISC(\"04/01/2016\",\"03/31/2021\",95,100,\"\")": "#NUM!", + "=DISC(\"03/31/2021\",\"04/01/2016\",95,100)": "DISC requires maturity > settlement", + "=DISC(\"04/01/2016\",\"03/31/2021\",0,100)": "DISC requires pr > 0", + "=DISC(\"04/01/2016\",\"03/31/2021\",95,0)": "DISC requires redemption > 0", + "=DISC(\"04/01/2016\",\"03/31/2021\",95,100,5)": "invalid basis", // DOLLARDE "=DOLLARDE()": "DOLLARDE requires 2 arguments", "=DOLLARDE(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax", @@ -2459,6 +2474,17 @@ func TestCalcCellValue(t *testing.T) { "=FVSCHEDULE()": "FVSCHEDULE requires 2 arguments", "=FVSCHEDULE(\"\",0)": "strconv.ParseFloat: parsing \"\": invalid syntax", "=FVSCHEDULE(0,\"x\")": "strconv.ParseFloat: parsing \"x\": invalid syntax", + // INTRATE + "=INTRATE()": "INTRATE requires 4 or 5 arguments", + "=INTRATE(\"\",\"03/31/2021\",95,100)": "#VALUE!", + "=INTRATE(\"04/01/2016\",\"\",95,100)": "#VALUE!", + "=INTRATE(\"04/01/2016\",\"03/31/2021\",\"\",100)": "#VALUE!", + "=INTRATE(\"04/01/2016\",\"03/31/2021\",95,\"\")": "#VALUE!", + "=INTRATE(\"04/01/2016\",\"03/31/2021\",95,100,\"\")": "#NUM!", + "=INTRATE(\"03/31/2021\",\"04/01/2016\",95,100)": "INTRATE requires maturity > settlement", + "=INTRATE(\"04/01/2016\",\"03/31/2021\",0,100)": "INTRATE requires investment > 0", + "=INTRATE(\"04/01/2016\",\"03/31/2021\",95,0)": "INTRATE requires redemption > 0", + "=INTRATE(\"04/01/2016\",\"03/31/2021\",95,100,5)": "invalid basis", // IPMT "=IPMT()": "IPMT requires at least 4 arguments", "=IPMT(0,0,0,0,0,0,0)": "IPMT allows at most 6 arguments",