From 748bb33977210ec56803c02492b8f5d82b4a601e Mon Sep 17 00:00:00 2001 From: Vyacheslav Zgordan Date: Mon, 30 Dec 2019 06:17:18 +0300 Subject: [PATCH 1/6] YIELDDISC --- spreadsheet/formula/fnfinance.go | 43 +++++++++++++++++++++++++++ spreadsheet/formula/functions_test.go | 22 ++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/spreadsheet/formula/fnfinance.go b/spreadsheet/formula/fnfinance.go index 7b5d73174f..6d3f9a627b 100644 --- a/spreadsheet/formula/fnfinance.go +++ b/spreadsheet/formula/fnfinance.go @@ -64,6 +64,7 @@ func init() { RegisterFunction("TBILLPRICE", Tbillprice) RegisterFunction("TBILLYIELD", Tbillyield) RegisterFunction("VDB", Vdb) + RegisterFunction("YIELDDISC", Yielddisc) } func getSettlementMaturity(settlementResult, maturityResult Result, funcName string) (float64, float64, Result) { @@ -2454,6 +2455,48 @@ func getDDB(cost, salvage, life, period, factor float64) float64 { return ddb } +// Yielddisc implements the Excel YIELDDISC function. +func Yielddisc(args []Result) Result { + argsNum := len(args) + if argsNum != 4 && argsNum != 5 { + return MakeErrorResult("YIELDDISC requires four or five arguments") + } + settlementDate, maturityDate, errResult := getSettlementMaturity(args[0], args[1], "YIELDDISC") + if errResult.Type == ResultTypeError { + return errResult + } + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("YIELDDISC requires pr to be number argument") + } + pr := args[2].ValueNumber + if pr <= 0 { + return MakeErrorResultType(ErrorTypeNum, "YIELDDISC requires pr to be positive number argument") + } + if args[3].Type != ResultTypeNumber { + return MakeErrorResult("YIELDDISC requires redemption to be number argument") + } + redemption := args[3].ValueNumber + if redemption <= 0 { + return MakeErrorResultType(ErrorTypeNum, "YIELDDISC requires redemption to be positive number argument") + } + basis := 0 + if argsNum == 5 && args[4].Type != ResultTypeEmpty { + if args[4].Type != ResultTypeNumber { + return MakeErrorResult("YIELDDISC requires basis to be number argument") + } + basis = int(args[4].ValueNumber) + if !checkBasis(basis) { + return MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for YIELDDISC") + } + } + frac, errResult := yearFrac(settlementDate, maturityDate, basis) + if errResult.Type == ResultTypeError { + return errResult + } + + return MakeNumberResult((redemption / pr - 1) / frac) +} + func approxEqual(a, b float64) bool { return math.Abs(a - b) < 1.0e-6 } diff --git a/spreadsheet/formula/functions_test.go b/spreadsheet/formula/functions_test.go index 3ee3efc232..2b430c3096 100644 --- a/spreadsheet/formula/functions_test.go +++ b/spreadsheet/formula/functions_test.go @@ -2609,3 +2609,25 @@ func TestVdb(t *testing.T) { runTests(t, ctx, td) } + +func TestYielddisc(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetDate(time.Date(2008, 2, 16, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A2").SetDate(time.Date(2008, 3, 1, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A3").SetNumber(99.795) + sheet.Cell("A4").SetNumber(100) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=YIELDDISC(A1,A2,A3,A4,0)`, `0.04930106718 ResultTypeNumber`}, + {`=YIELDDISC(A1,A2,A3,A4,1)`, `0.05370294818 ResultTypeNumber`}, + {`=YIELDDISC(A1,A2,A3,A4,2)`, `0.05282257198 ResultTypeNumber`}, + {`=YIELDDISC(A1,A2,A3,A4,3)`, `0.05355621882 ResultTypeNumber`}, + {`=YIELDDISC(A1,A2,A3,A4,4)`, `0.04930106718 ResultTypeNumber`}, + } + + runTests(t, ctx, td) +} From 917f92381c997220831fe9411fa6952deaf1f005 Mon Sep 17 00:00:00 2001 From: Vyacheslav Zgordan Date: Mon, 30 Dec 2019 09:00:02 +0300 Subject: [PATCH 2/6] XIRR --- spreadsheet/formula/fnfinance.go | 74 +++++++++++++++++++++++++-- spreadsheet/formula/functions_test.go | 26 ++++++++++ 2 files changed, 97 insertions(+), 3 deletions(-) diff --git a/spreadsheet/formula/fnfinance.go b/spreadsheet/formula/fnfinance.go index 6d3f9a627b..82bb89a2e0 100644 --- a/spreadsheet/formula/fnfinance.go +++ b/spreadsheet/formula/fnfinance.go @@ -64,6 +64,7 @@ func init() { RegisterFunction("TBILLPRICE", Tbillprice) RegisterFunction("TBILLYIELD", Tbillyield) RegisterFunction("VDB", Vdb) + RegisterFunction("XIRR", Xirr) RegisterFunction("YIELDDISC", Yielddisc) } @@ -1240,8 +1241,6 @@ func Irr(args []Result) Result { } dates := []float64{} - positive := false - negative := false for i := 0; i < vlen; i++ { if i == 0 { @@ -1249,6 +1248,17 @@ func Irr(args []Result) Result { } else { dates = append(dates, dates[i - 1] + 365) } + } + return irr(values, dates, guess) +} + +//irr is used to calculate results for Irr and Xirr as method is the same +func irr(values, dates []float64, guess float64) Result { + + positive := false + negative := false + + for i := 0; i < len(values); i++ { if values[i] > 0 { positive = true } @@ -1273,7 +1283,7 @@ func Irr(args []Result) Result { epsRate := math.Abs(newRate - resultRate) resultRate = newRate iter++ - if iter > maxIter || epsRate <= epsMax || math.Abs(resultValue) <= epsMax { + if epsRate <= epsMax || math.Abs(resultValue) <= epsMax { break } if iter > maxIter { @@ -2500,3 +2510,61 @@ func Yielddisc(args []Result) Result { func approxEqual(a, b float64) bool { return math.Abs(a - b) < 1.0e-6 } + +// Xirr implements the Excel XIRR function. +func Xirr(args []Result) Result { + argsNum := len(args) + if argsNum != 2 && argsNum != 3 { + return MakeErrorResult("XIRR requires two or three arguments") + } + if args[0].Type != ResultTypeList && args[0].Type != ResultTypeArray { + return MakeErrorResult("XIRR requires values to be of array type") + } + valuesR := arrayFromRange(args[0]) + values := []float64{} + for _, row := range valuesR { + for _, vR := range row { + if vR.Type == ResultTypeNumber && !vR.IsBoolean { + values = append(values, vR.ValueNumber) + } + } + } + vlen := len(values) + if len(values) < 2 { + return MakeErrorResultType(ErrorTypeNum, "") + } + if args[1].Type != ResultTypeList && args[1].Type != ResultTypeArray { + return MakeErrorResult("XIRR requires dates to be of array type") + } + datesR := arrayFromRange(args[1]) + dates := []float64{} + lastDate := 0.0 + for _, row := range datesR { + for _, vR := range row { + if vR.Type == ResultTypeNumber && !vR.IsBoolean { + newDate := vR.ValueNumber + if newDate < lastDate { + return MakeErrorResultType(ErrorTypeNum, "XIRR requires dates to be in ascending order") + } + dates = append(dates, newDate) + lastDate = newDate + } else { + return MakeErrorResult("Incorrect date format") + } + } + } + if len(dates) != vlen { + return MakeErrorResultType(ErrorTypeNum, "") + } + guess := 0.1 + if argsNum == 3 && args[2].Type != ResultTypeEmpty { + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("XIRR requires guess to be number argument") + } + guess = args[2].ValueNumber + if guess <= -1 { + return MakeErrorResult("XIRR requires guess to be more than -1") + } + } + return irr(values, dates, guess) +} diff --git a/spreadsheet/formula/functions_test.go b/spreadsheet/formula/functions_test.go index 2b430c3096..e54d155cb9 100644 --- a/spreadsheet/formula/functions_test.go +++ b/spreadsheet/formula/functions_test.go @@ -2631,3 +2631,29 @@ func TestYielddisc(t *testing.T) { runTests(t, ctx, td) } + +func TestXirr(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetNumber(-10000) + sheet.Cell("A2").SetNumber(2750) + sheet.Cell("A3").SetNumber(4250) + sheet.Cell("A4").SetNumber(3250) + sheet.Cell("A5").SetNumber(2750) + sheet.Cell("B1").SetDate(time.Date(2008, 1, 1, 0, 0, 0, 0, time.UTC)) + sheet.Cell("B2").SetDate(time.Date(2008, 3, 1, 0, 0, 0, 0, time.UTC)) + sheet.Cell("B3").SetDate(time.Date(2008, 10, 30, 0, 0, 0, 0, time.UTC)) + sheet.Cell("B4").SetDate(time.Date(2009, 2, 15, 0, 0, 0, 0, time.UTC)) + sheet.Cell("B5").SetDate(time.Date(2009, 4, 1, 0, 0, 0, 0, time.UTC)) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=XIRR(A1:A5,B1:B5,0.1)`, `0.37336253351 ResultTypeNumber`}, + {`=XIRR(A1:A5,B1:B5,"hello world")`, `#VALUE! ResultTypeError`}, + {`=XIRR(A2:A5,B2:B5,0.1)`, `#NUM! ResultTypeError`}, + } + + runTests(t, ctx, td) +} From f63595044b40e267c0e28cfef490808f1c6e90dd Mon Sep 17 00:00:00 2001 From: Vyacheslav Zgordan Date: Mon, 30 Dec 2019 14:03:17 +0300 Subject: [PATCH 3/6] XNPV --- spreadsheet/formula/fnfinance.go | 90 ++++++++++++++++++++------- spreadsheet/formula/functions_test.go | 26 ++++++++ 2 files changed, 92 insertions(+), 24 deletions(-) diff --git a/spreadsheet/formula/fnfinance.go b/spreadsheet/formula/fnfinance.go index 82bb89a2e0..957606ba84 100644 --- a/spreadsheet/formula/fnfinance.go +++ b/spreadsheet/formula/fnfinance.go @@ -65,6 +65,7 @@ func init() { RegisterFunction("TBILLYIELD", Tbillyield) RegisterFunction("VDB", Vdb) RegisterFunction("XIRR", Xirr) + RegisterFunction("XNPV", Xnpv) RegisterFunction("YIELDDISC", Yielddisc) } @@ -2517,12 +2518,63 @@ func Xirr(args []Result) Result { if argsNum != 2 && argsNum != 3 { return MakeErrorResult("XIRR requires two or three arguments") } - if args[0].Type != ResultTypeList && args[0].Type != ResultTypeArray { - return MakeErrorResult("XIRR requires values to be of array type") + xStruct, errResult := getXargs(args[0], args[1], "XIRR") + if errResult.Type == ResultTypeError { + return errResult } - valuesR := arrayFromRange(args[0]) + values := xStruct.values + dates := xStruct.dates + guess := 0.1 + if argsNum == 3 && args[2].Type != ResultTypeEmpty { + if args[2].Type != ResultTypeNumber { + return MakeErrorResult("XIRR requires guess to be number argument") + } + guess = args[2].ValueNumber + if guess <= -1 { + return MakeErrorResult("XIRR requires guess to be more than -1") + } + } + return irr(values, dates, guess) +} + +// Xnpv implements the Excel XNPV function. +func Xnpv(args []Result) Result { + if len(args) != 3 { + return MakeErrorResult("XNPV requires three arguments") + } + if args[0].Type != ResultTypeNumber { + return MakeErrorResult("XNPV requires rate to be number argument") + } + rate := args[0].ValueNumber + if rate <= 0 { + return MakeErrorResultType(ErrorTypeNum, "XNPV requires rate to be positive") + } + xStruct, errResult := getXargs(args[1], args[2], "XIRR") + if errResult.Type == ResultTypeError { + return errResult + } + values := xStruct.values + dates := xStruct.dates + xnpv := 0.0 + firstDate := dates[0] + for i, value := range values { + xnpv += value / math.Pow(1 + rate, (dates[i] - firstDate) / 365) + } + return MakeNumberResult(xnpv) +} + +type xargs struct { + values []float64 + dates []float64 +} + +func getXargs(valuesR, datesR Result, funcName string) (*xargs, Result) { + if valuesR.Type != ResultTypeList && valuesR.Type != ResultTypeArray { + return nil, MakeErrorResult(funcName + " requires values to be of array type") + } + valuesArr := arrayFromRange(valuesR) values := []float64{} - for _, row := range valuesR { + for _, row := range valuesArr { for _, vR := range row { if vR.Type == ResultTypeNumber && !vR.IsBoolean { values = append(values, vR.ValueNumber) @@ -2531,40 +2583,30 @@ func Xirr(args []Result) Result { } vlen := len(values) if len(values) < 2 { - return MakeErrorResultType(ErrorTypeNum, "") + return nil, MakeErrorResultType(ErrorTypeNum, "") } - if args[1].Type != ResultTypeList && args[1].Type != ResultTypeArray { - return MakeErrorResult("XIRR requires dates to be of array type") + if datesR.Type != ResultTypeList && datesR.Type != ResultTypeArray { + return nil, MakeErrorResult(funcName + " requires dates to be of array type") } - datesR := arrayFromRange(args[1]) + datesArr := arrayFromRange(datesR) dates := []float64{} lastDate := 0.0 - for _, row := range datesR { + for _, row := range datesArr { for _, vR := range row { if vR.Type == ResultTypeNumber && !vR.IsBoolean { - newDate := vR.ValueNumber + newDate := float64(int(vR.ValueNumber)) if newDate < lastDate { - return MakeErrorResultType(ErrorTypeNum, "XIRR requires dates to be in ascending order") + return nil, MakeErrorResultType(ErrorTypeNum, funcName + " requires dates to be in ascending order") } dates = append(dates, newDate) lastDate = newDate } else { - return MakeErrorResult("Incorrect date format") + return nil, MakeErrorResult("Incorrect date format") } } } if len(dates) != vlen { - return MakeErrorResultType(ErrorTypeNum, "") - } - guess := 0.1 - if argsNum == 3 && args[2].Type != ResultTypeEmpty { - if args[2].Type != ResultTypeNumber { - return MakeErrorResult("XIRR requires guess to be number argument") - } - guess = args[2].ValueNumber - if guess <= -1 { - return MakeErrorResult("XIRR requires guess to be more than -1") - } + return nil, MakeErrorResultType(ErrorTypeNum, "") } - return irr(values, dates, guess) + return &xargs{values, dates}, MakeEmptyResult() } diff --git a/spreadsheet/formula/functions_test.go b/spreadsheet/formula/functions_test.go index e54d155cb9..42371e4cbd 100644 --- a/spreadsheet/formula/functions_test.go +++ b/spreadsheet/formula/functions_test.go @@ -2657,3 +2657,29 @@ func TestXirr(t *testing.T) { runTests(t, ctx, td) } + +func TestXnpv(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetNumber(-10000) + sheet.Cell("A2").SetNumber(2750) + sheet.Cell("A3").SetNumber(4250) + sheet.Cell("A4").SetNumber(3250) + sheet.Cell("A5").SetNumber(2750) + sheet.Cell("B1").SetDate(time.Date(2008, 1, 1, 0, 0, 0, 0, time.UTC)) + sheet.Cell("B2").SetDate(time.Date(2008, 3, 1, 0, 0, 0, 0, time.UTC)) + sheet.Cell("B3").SetDate(time.Date(2008, 10, 30, 0, 0, 0, 0, time.UTC)) + sheet.Cell("B4").SetDate(time.Date(2009, 2, 15, 0, 0, 0, 0, time.UTC)) + sheet.Cell("B5").SetDate(time.Date(2009, 4, 1, 0, 0, 0, 0, time.UTC)) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=XNPV(0.09,A1:A5,B1:B5)`, `2086.64760203 ResultTypeNumber`}, + {`=XNPV(-0.01,A1:A5,B1:B5)`, `#NUM! ResultTypeError`}, + {`=XNPV("hello world",A1:A5,B1:B5)`, `#VALUE! ResultTypeError`}, + } + + runTests(t, ctx, td) +} From 4996b4dca3c8c0abc149f07efb5c246270c95940 Mon Sep 17 00:00:00 2001 From: Vyacheslav Zgordan Date: Mon, 30 Dec 2019 17:17:29 +0300 Subject: [PATCH 4/6] YIELD --- spreadsheet/formula/fnfinance.go | 130 ++++++++++++++++++++++++-- spreadsheet/formula/functions_test.go | 29 ++++++ 2 files changed, 149 insertions(+), 10 deletions(-) diff --git a/spreadsheet/formula/fnfinance.go b/spreadsheet/formula/fnfinance.go index 957606ba84..ea6305bada 100644 --- a/spreadsheet/formula/fnfinance.go +++ b/spreadsheet/formula/fnfinance.go @@ -66,6 +66,7 @@ func init() { RegisterFunction("VDB", Vdb) RegisterFunction("XIRR", Xirr) RegisterFunction("XNPV", Xnpv) + RegisterFunction("YIELD", Yield) RegisterFunction("YIELDDISC", Yielddisc) } @@ -1573,7 +1574,7 @@ func Oddlprice(args []Result) Result { return MakeErrorResultType(ErrorTypeNum, "Incorrect frequence value") } basis := 0 - if len(args) == 8 { + if len(args) == 8 && args[7].Type != ResultTypeEmpty { basisResult := args[7] if basisResult.Type != ResultTypeNumber { return MakeErrorResult("ODDLPRICE requires basis of type number") @@ -1652,7 +1653,7 @@ func Oddlyield(args []Result) Result { return MakeErrorResultType(ErrorTypeNum, "Incorrect frequence value") } basis := 0 - if len(args) == 8 { + if len(args) == 8 && args[7].Type != ResultTypeEmpty { if args[7].Type != ResultTypeNumber { return MakeErrorResult("ODDLYIELD requires basis of type number") } @@ -1850,13 +1851,12 @@ func Price(args []Result) Result { if freqResult.Type != ResultTypeNumber { return MakeErrorResult("PRICE requires frequency of type number") } - freqF := freqResult.ValueNumber - if !checkFreq(freqF) { + freq := freqResult.ValueNumber + if !checkFreq(freq) { return MakeErrorResultType(ErrorTypeNum, "Incorrect frequence value") } - freq := int(freqF) basis := 0 - if argsNum == 7 { + if argsNum == 7 && args[6].Type != ResultTypeEmpty { if args[6].Type != ResultTypeNumber { return MakeErrorResult("PRICE requires basis to be number argument") } @@ -1865,11 +1865,20 @@ func Price(args []Result) Result { return MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for PRICE") } } + price, errResult := getPrice(settlementDate, maturityDate, rate, yield, redemption, freq, basis) + if errResult.Type == ResultTypeError { + return errResult + } + return MakeNumberResult(price) +} + +func getPrice(settlementDate, maturityDate, rate, yield, redemption, freqF float64, basis int) (float64, Result) { + freq := int(freqF) e := coupdays(settlementDate, maturityDate, freq, basis) dsc := coupdaysnc(settlementDate, maturityDate, freq, basis) / e n, errResult := coupnum(settlementDate, maturityDate, freq, basis) if errResult.Type == ResultTypeError { - return errResult + return 0, errResult } a := coupdaybs(settlementDate, maturityDate, freq, basis) ret := redemption / math.Pow(1 + yield / freqF, n - 1 + dsc) @@ -1879,7 +1888,7 @@ func Price(args []Result) Result { for k := 0.0; k < n; k++ { ret += t1 / math.Pow(t2, k + dsc) } - return MakeNumberResult(ret) + return ret, MakeEmptyResult() } // Pricedisc implements the Excel PRICEDISC function. @@ -1955,7 +1964,7 @@ func Pricemat(args []Result) Result { return MakeErrorResultType(ErrorTypeNum, "PRICEMAT requires yield to be non negative") } basis := 0 - if argsNum == 6 { + if argsNum == 6 && args[5].Type != ResultTypeEmpty { if args[5].Type != ResultTypeNumber { return MakeErrorResult("PRICEMAT requires basis to be number argument") } @@ -2198,7 +2207,7 @@ func Sln(args []Result) Result { // Syd implements the Excel SYD function. func Syd(args []Result) Result { if len(args) != 4 { - return MakeErrorResult("SYD requires three arguments") + return MakeErrorResult("SYD requires four arguments") } if args[0].Type != ResultTypeNumber { return MakeErrorResult("SYD requires cost to be number argument") @@ -2610,3 +2619,104 @@ func getXargs(valuesR, datesR Result, funcName string) (*xargs, Result) { } return &xargs{values, dates}, MakeEmptyResult() } + +// Yield implements the Excel YIELD function. +func Yield(args []Result) Result { + argsNum := len(args) + if argsNum != 6 && argsNum != 7 { + return MakeErrorResult("YIELD requires six or seven arguments") + } + settlementDate, maturityDate, errResult := getSettlementMaturity(args[0], args[1], "YIELD") + if errResult.Type == ResultTypeError { + return errResult + } + rateResult := args[2] + if rateResult.Type != ResultTypeNumber { + return MakeErrorResult("YIELD requires rate of type number") + } + rate := rateResult.ValueNumber + if rate < 0 { + return MakeErrorResultType(ErrorTypeNum, "Rate should be non negative") + } + prResult := args[3] + if prResult.Type != ResultTypeNumber { + return MakeErrorResult("YIELD requires pr of type number") + } + pr := prResult.ValueNumber + if pr <= 0 { + return MakeErrorResultType(ErrorTypeNum, "pr should be positive") + } + redemptionResult := args[4] + if redemptionResult.Type != ResultTypeNumber { + return MakeErrorResult("YIELD requires redemption of type number") + } + redemption := redemptionResult.ValueNumber + if redemption < 0 { + return MakeErrorResultType(ErrorTypeNum, "Yield should be non negative") + } + freqResult := args[5] + if freqResult.Type != ResultTypeNumber { + return MakeErrorResult("YIELD requires frequency of type number") + } + freq := float64(int(freqResult.ValueNumber)) + if !checkFreq(freq) { + return MakeErrorResultType(ErrorTypeNum, "Incorrect frequence value") + } + basis := 0 + if argsNum == 7 && args[6].Type != ResultTypeEmpty { + basisResult := args[6] + if basisResult.Type != ResultTypeNumber { + return MakeErrorResult("YIELD requires basis of type number") + } + basis = int(basisResult.ValueNumber) + if !checkBasis(basis) { + return MakeErrorResultType(ErrorTypeNum, "Incorrect basis value for YIELD") + } + } + + priceN := 0.0 + yield1 := 0.0 + yield2 := 1.0 + price1, errResult := getPrice(settlementDate, maturityDate, rate, yield1, redemption, freq, basis) + if errResult.Type == ResultTypeError { + return errResult + } + price2, errResult := getPrice(settlementDate, maturityDate, rate, yield2, redemption, freq, basis) + if errResult.Type == ResultTypeError { + return errResult + } + + yieldN := (yield2 - yield1) * 0.5 + + for iter := 0; iter < 100 && priceN != pr; iter++ { + priceN, errResult = getPrice(settlementDate, maturityDate, rate, yieldN, redemption, freq, basis) + if errResult.Type == ResultTypeError { + return errResult + } + if pr == price1 { + return MakeNumberResult(yield1) + } else if pr == price2 { + return MakeNumberResult(yield2) + } else if pr == priceN { + return MakeNumberResult(yieldN) + } else if pr < price2 { + yield2 *= 2.0 + price2, errResult = getPrice(settlementDate, maturityDate, rate, yield2, redemption, freq, basis) + if errResult.Type == ResultTypeError { + return errResult + } + yieldN = (yield2 - yield1) * 0.5 + } else { + if pr < priceN { + yield1 = yieldN + price1 = priceN + } else { + yield2 = yieldN + price2 = priceN + } + yieldN = yield2 - (yield2 - yield1) * ((pr - price2) / (price1 - price2)) + } + } + + return MakeNumberResult(yieldN) +} diff --git a/spreadsheet/formula/functions_test.go b/spreadsheet/formula/functions_test.go index 42371e4cbd..842a57822b 100644 --- a/spreadsheet/formula/functions_test.go +++ b/spreadsheet/formula/functions_test.go @@ -2683,3 +2683,32 @@ func TestXnpv(t *testing.T) { runTests(t, ctx, td) } + +func TestYield(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetDate(time.Date(2008, 2, 15, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A2").SetDate(time.Date(2016, 11, 15, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A3").SetNumber(0.0575) + sheet.Cell("A4").SetNumber(95.04287) + sheet.Cell("A5").SetNumber(100) + sheet.Cell("A6").SetNumber(2) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=YIELD(A1,A2,A3,A4,A5,A6)`, `0.06500000688 ResultTypeNumber`}, + {`=YIELD(A1,A2,A3,A4,A5,A6,)`, `0.06500000688 ResultTypeNumber`}, + {`=YIELD(A1,A2,A3,A4,A5,A6,0)`, `0.06500000688 ResultTypeNumber`}, + {`=YIELD(A1,A2,A3,A4,A5,A6,1)`, `0.0650018206 ResultTypeNumber`}, + {`=YIELD(A1,A2,A3,A4,A5,A6,2)`, `0.06495005528 ResultTypeNumber`}, + {`=YIELD(A1,A2,A3,A4,A5,A6,3)`, `0.06501459236 ResultTypeNumber`}, + {`=YIELD(A1,A2,A3,A4,A5,A6,4)`, `0.06500000688 ResultTypeNumber`}, + {`=YIELD(A2,A1,A3,A4,A5,A6,4)`, `#NUM! ResultTypeError`}, + {`=YIELD(A1,A2,A3,A4,A5,A6,5)`, `#NUM! ResultTypeError`}, + {`=YIELD("hello world",A2,A3,A4,A5,A6,4)`, `#VALUE! ResultTypeError`}, + } + + runTests(t, ctx, td) +} From 4e6a13c6efe934a148f8afdade083b6abc6b803f Mon Sep 17 00:00:00 2001 From: Vyacheslav Zgordan Date: Mon, 30 Dec 2019 17:58:50 +0300 Subject: [PATCH 5/6] YIELDMAT --- spreadsheet/formula/fnfinance.go | 63 +++++++++++++++++++++++++++ spreadsheet/formula/functions_test.go | 28 ++++++++++++ 2 files changed, 91 insertions(+) diff --git a/spreadsheet/formula/fnfinance.go b/spreadsheet/formula/fnfinance.go index ea6305bada..e1e2cc6dfe 100644 --- a/spreadsheet/formula/fnfinance.go +++ b/spreadsheet/formula/fnfinance.go @@ -68,6 +68,7 @@ func init() { RegisterFunction("XNPV", Xnpv) RegisterFunction("YIELD", Yield) RegisterFunction("YIELDDISC", Yielddisc) + RegisterFunction("YIELDMAT", Yieldmat) } func getSettlementMaturity(settlementResult, maturityResult Result, funcName string) (float64, float64, Result) { @@ -2720,3 +2721,65 @@ func Yield(args []Result) Result { return MakeNumberResult(yieldN) } + +// Yieldmat implements the Excel YIELDMAT function. +func Yieldmat(args []Result) Result { + argsNum := len(args) + if argsNum != 5 && argsNum != 6 { + return MakeErrorResult("YIELDMAT requires five or six arguments") + } + settlementDate, maturityDate, errResult := getSettlementMaturity(args[0], args[1], "YIELDMAT") + if errResult.Type == ResultTypeError { + return errResult + } + issueDate, errResult := parseDate(args[2], "issue date", "YIELDMAT") + if errResult.Type == ResultTypeError { + return errResult + } + if issueDate >= settlementDate { + return MakeErrorResult("YIELDMAT requires issue date to be before settlement date") + } + if args[3].Type != ResultTypeNumber { + return MakeErrorResult("YIELDMAT requires rate of type number") + } + rate := args[3].ValueNumber + if rate < 0 { + return MakeErrorResultType(ErrorTypeNum, "YIELDMAT requires rate to be non negative") + } + if args[4].Type != ResultTypeNumber { + return MakeErrorResult("YIELDMAT requires yield of type number") + } + pr := args[4].ValueNumber + if pr <= 0 { + return MakeErrorResultType(ErrorTypeNum, "YIELDMAT requires pr to be positive") + } + basis := 0 + if argsNum == 6 && args[5].Type != ResultTypeEmpty { + if args[5].Type != ResultTypeNumber { + return MakeErrorResult("YIELDMAT requires basis to be number argument") + } + basis = int(args[5].ValueNumber) + if !checkBasis(basis) { + return MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for YIELDMAT") + } + } + dim, errResult := yearFrac(issueDate, maturityDate, basis) + if errResult.Type == ResultTypeError { + return errResult + } + dis, errResult := yearFrac(issueDate, settlementDate, basis) + if errResult.Type == ResultTypeError { + return errResult + } + dsm, errResult := yearFrac(settlementDate, maturityDate, basis) + if errResult.Type == ResultTypeError { + return errResult + } + + y := 1 + dim * rate + y *= math.Pow(pr / 100 + dis * rate, -1) + y-- + y *= math.Pow(dsm, -1) + + return MakeNumberResult(y) +} diff --git a/spreadsheet/formula/functions_test.go b/spreadsheet/formula/functions_test.go index 842a57822b..41a295eae9 100644 --- a/spreadsheet/formula/functions_test.go +++ b/spreadsheet/formula/functions_test.go @@ -2712,3 +2712,31 @@ func TestYield(t *testing.T) { runTests(t, ctx, td) } + +func TestYieldmat(t *testing.T) { + ss := spreadsheet.New() + sheet := ss.AddSheet() + + sheet.Cell("A1").SetDate(time.Date(2008, 3, 15, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A2").SetDate(time.Date(2008, 11, 3, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A3").SetDate(time.Date(2007, 11, 8, 0, 0, 0, 0, time.UTC)) + sheet.Cell("A4").SetNumber(0.0625) + sheet.Cell("A5").SetNumber(100.0123) + + ctx := sheet.FormulaContext() + + td := []testStruct{ + {`=YIELDMAT(A1,A2,A3,A4,A5)`, `0.06095433369 ResultTypeNumber`}, + {`=YIELDMAT(A1,A2,A3,A4,A5,)`, `0.06095433369 ResultTypeNumber`}, + {`=YIELDMAT(A1,A2,A3,A4,A5,0)`, `0.06095433369 ResultTypeNumber`}, + {`=YIELDMAT(A1,A2,A3,A4,A5,1)`, `0.06096668564 ResultTypeNumber`}, + {`=YIELDMAT(A1,A2,A3,A4,A5,2)`, `0.06094805915 ResultTypeNumber`}, + {`=YIELDMAT(A1,A2,A3,A4,A5,3)`, `0.06096362992 ResultTypeNumber`}, + {`=YIELDMAT(A1,A2,A3,A4,A5,4)`, `0.06095433369 ResultTypeNumber`}, + {`=YIELDMAT(A2,A1,A3,A4,A5,4)`, `#NUM! ResultTypeError`}, + {`=YIELDMAT(A1,A2,A3,A4,A5,5)`, `#NUM! ResultTypeError`}, + {`=YIELDMAT("hello world",A2,A3,A4,A5,4)`, `#VALUE! ResultTypeError`}, + } + + runTests(t, ctx, td) +} From 2e4149bd2e3a311aa57da0e6a379c510f74d7b35 Mon Sep 17 00:00:00 2001 From: Vyacheslav Zgordan Date: Mon, 30 Dec 2019 21:47:33 +0300 Subject: [PATCH 6/6] some tests added --- spreadsheet/formula/fnfinance.go | 28 ++++++++++++++++----------- spreadsheet/formula/functions_test.go | 2 ++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/spreadsheet/formula/fnfinance.go b/spreadsheet/formula/fnfinance.go index e1e2cc6dfe..b9c4173520 100644 --- a/spreadsheet/formula/fnfinance.go +++ b/spreadsheet/formula/fnfinance.go @@ -102,6 +102,7 @@ func Coupdaybs(args []Result) Result { return MakeNumberResult(coupdaybs(parsedArgs.settlementDate, parsedArgs.maturityDate, parsedArgs.freq, parsedArgs.basis)) } +// coupdaybs returns the number of days from the beginning of the coupon period to the settlement date. func coupdaybs(settlementDateF, maturityDateF float64, freq, basis int) float64 { settlementDate := dateFromDays(settlementDateF) maturityDate := dateFromDays(maturityDateF) @@ -118,6 +119,7 @@ func Coupdays(args []Result) Result { return MakeNumberResult(coupdays(parsedArgs.settlementDate, parsedArgs.maturityDate, parsedArgs.freq, parsedArgs.basis)) } +// coupdays returns the number of days in the coupon period that contains the settlement date. func coupdays(settlementDateF, maturityDateF float64, freq, basis int) float64 { settlementDate := dateFromDays(settlementDateF) maturityDate := dateFromDays(maturityDateF) @@ -138,6 +140,7 @@ func Coupdaysnc(args []Result) Result { return MakeNumberResult(coupdaysnc(parsedArgs.settlementDate, parsedArgs.maturityDate, parsedArgs.freq, parsedArgs.basis)) } +// coupdaysnc returns the number of days from the settlement date to the next coupon date. func coupdaysnc(settlementDateF, maturityDateF float64, freq, basis int) float64 { settlementDate := dateFromDays(settlementDateF) maturityDate := dateFromDays(maturityDateF) @@ -189,6 +192,7 @@ func Coupncd(args []Result) Result { return MakeNumberResult(daysFromDate(y, int(m), d)) } +// coupncd finds next coupon date after settlement. 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) { @@ -1255,7 +1259,7 @@ func Irr(args []Result) Result { return irr(values, dates, guess) } -//irr is used to calculate results for Irr and Xirr as method is the same +// irr is used to calculate results for Irr and Xirr as method is the same func irr(values, dates []float64, guess float64) Result { positive := false @@ -1974,23 +1978,23 @@ func Pricemat(args []Result) Result { return MakeErrorResultType(ErrorTypeNum, "Incorrect basis argument for PRICEMAT") } } - dsmyf, errResult := yearFrac(settlementDate, maturityDate, basis) + dsm, errResult := yearFrac(settlementDate, maturityDate, basis) if errResult.Type == ResultTypeError { return errResult } - dimyf, errResult := yearFrac(issueDate, maturityDate, basis) + dim, errResult := yearFrac(issueDate, maturityDate, basis) if errResult.Type == ResultTypeError { return errResult } - ayf, errResult := yearFrac(issueDate, settlementDate, basis) + dis, errResult := yearFrac(issueDate, settlementDate, basis) if errResult.Type == ResultTypeError { return errResult } - num := 1 + dimyf * rate - den := 1 + dsmyf * yield + num := 1 + dim * rate + den := 1 + dsm * yield - return MakeNumberResult((num / den - ayf * rate) * 100) + return MakeNumberResult((num / den - dis * rate) * 100) } // Pv implements the Excel PV function. @@ -2559,7 +2563,7 @@ func Xnpv(args []Result) Result { if rate <= 0 { return MakeErrorResultType(ErrorTypeNum, "XNPV requires rate to be positive") } - xStruct, errResult := getXargs(args[1], args[2], "XIRR") + xStruct, errResult := getXargs(args[1], args[2], "XNPV") if errResult.Type == ResultTypeError { return errResult } @@ -2588,6 +2592,8 @@ func getXargs(valuesR, datesR Result, funcName string) (*xargs, Result) { for _, vR := range row { if vR.Type == ResultTypeNumber && !vR.IsBoolean { values = append(values, vR.ValueNumber) + } else { + return nil, MakeErrorResult(funcName + "requires values to be numbers") } } } @@ -2611,7 +2617,7 @@ func getXargs(valuesR, datesR Result, funcName string) (*xargs, Result) { dates = append(dates, newDate) lastDate = newDate } else { - return nil, MakeErrorResult("Incorrect date format") + return nil, MakeErrorResult(funcName + "requires dates to be numbers") } } } @@ -2777,9 +2783,9 @@ func Yieldmat(args []Result) Result { } y := 1 + dim * rate - y *= math.Pow(pr / 100 + dis * rate, -1) + y /= pr / 100 + dis * rate y-- - y *= math.Pow(dsm, -1) + y /= dsm return MakeNumberResult(y) } diff --git a/spreadsheet/formula/functions_test.go b/spreadsheet/formula/functions_test.go index 41a295eae9..3429a581ab 100644 --- a/spreadsheet/formula/functions_test.go +++ b/spreadsheet/formula/functions_test.go @@ -2622,6 +2622,8 @@ func TestYielddisc(t *testing.T) { ctx := sheet.FormulaContext() td := []testStruct{ + {`=YIELDDISC(A1,A2,A3,A4)`, `0.04930106718 ResultTypeNumber`}, + {`=YIELDDISC(A1,A2,A3,A4,)`, `0.04930106718 ResultTypeNumber`}, {`=YIELDDISC(A1,A2,A3,A4,0)`, `0.04930106718 ResultTypeNumber`}, {`=YIELDDISC(A1,A2,A3,A4,1)`, `0.05370294818 ResultTypeNumber`}, {`=YIELDDISC(A1,A2,A3,A4,2)`, `0.05282257198 ResultTypeNumber`},