From 2c9f68fcb7da56d0dfddb112cd23ecf8829f6ba3 Mon Sep 17 00:00:00 2001 From: Giao Ho Date: Fri, 31 Mar 2023 19:09:31 +0700 Subject: [PATCH] Validate and replace recursion with iteration --- build.gradle | 2 +- .../java/com/joutvhu/xirr/NewtonsXirr.java | 70 ++++++++++++------- src/main/java/com/joutvhu/xirr/Xirr.java | 10 +-- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/build.gradle b/build.gradle index d8f8441..ee78bb4 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ plugins { } group = 'com.github.joutvhu' -version = '1.0.0' +version = '1.0.1' def snapshotVersion = version.endsWith('-SNAPSHOT') || version.endsWith('.SNAPSHOT') diff --git a/src/main/java/com/joutvhu/xirr/NewtonsXirr.java b/src/main/java/com/joutvhu/xirr/NewtonsXirr.java index 04a30d9..98c4c11 100644 --- a/src/main/java/com/joutvhu/xirr/NewtonsXirr.java +++ b/src/main/java/com/joutvhu/xirr/NewtonsXirr.java @@ -7,50 +7,72 @@ class NewtonsXirr { public NewtonsXirr(Transaction[] transactions) { double[] payments = new double[transactions.length]; double[] days = new double[transactions.length]; + boolean positive = false; + boolean negative = false; for (int i = 0; i < payments.length; i++) { Transaction transaction = transactions[i]; payments[i] = transaction.getAmount(); days[i] = transaction.getWhen(); + if (payments[i] > 0) + positive = true; + if (payments[i] < 0) + negative = true; } + validate(positive, negative); this.f = fTotalXirr(payments, days); this.df = dfTotalXirr(payments, days); } public NewtonsXirr(double[] payments, double[] days) { + if (payments.length != days.length) { + throw new XirrException("Payments and Days must have the same number of elements."); + } + boolean positive = false; + boolean negative = false; + for (double payment : payments) { + if (payment > 0) + positive = true; + if (payment < 0) + negative = true; + if (positive && negative) + break; + } + validate(positive, negative); this.f = fTotalXirr(payments, days); this.df = dfTotalXirr(payments, days); } - private static XirrFx composeFunctions(XirrFx f1, XirrFx f2) { - return x -> f1.calculate(x) + f2.calculate(x); - } - - private static XirrFx fXirr(double p, double dt, double dt0) { - return x -> p * Math.pow((1.0 + x), ((dt0 - dt) / 365.0)); - } - - private static XirrFx dfXirr(double p, double dt, double dt0) { - return x -> (1.0 / 365.0) * (dt0 - dt) * p * Math.pow((x + 1.0), (((dt0 - dt) / 365.0) - 1.0)); + private static void validate(boolean positive, boolean negative) { + if (!positive && !negative) { + throw new XirrException("Expects at least one positive cash flow and one negative cash flow."); + } + if (!positive) { + throw new XirrException("Expects at least one positive cash flow."); + } + if (!negative) { + throw new XirrException("Expects at least one negative cash flow."); + } } public static XirrFx fTotalXirr(double[] payments, double[] days) { - XirrFx resf = x -> 0.0; - - for (int i = 0; i < payments.length; i++) { - resf = composeFunctions(resf, fXirr(payments[i], days[i], days[0])); - } - - return resf; + return x -> { + double rate = 0.0; + for (int i = 0; i < payments.length; i++) { + rate += payments[i] * Math.pow((1.0 + x), ((days[0] - days[i]) / 365.0)); + } + return rate; + }; } public static XirrFx dfTotalXirr(double[] payments, double[] days) { - XirrFx resf = x -> 0.0; - - for (int i = 0; i < payments.length; i++) { - resf = composeFunctions(resf, dfXirr(payments[i], days[i], days[0])); - } - - return resf; + return x -> { + double rate = 0.0; + for (int i = 0; i < payments.length; i++) { + rate += (1.0 / 365.0) * (days[0] - days[i]) * + payments[i] * Math.pow((x + 1.0), (((days[0] - days[i]) / 365.0) - 1.0)); + } + return rate; + }; } public double next(double x) { diff --git a/src/main/java/com/joutvhu/xirr/Xirr.java b/src/main/java/com/joutvhu/xirr/Xirr.java index 479d3b6..b8fcf7f 100644 --- a/src/main/java/com/joutvhu/xirr/Xirr.java +++ b/src/main/java/com/joutvhu/xirr/Xirr.java @@ -6,7 +6,7 @@ public class Xirr { private final double accurate; private final double tries; - public Xirr() { + private Xirr() { accurate = 0.000001; tries = 100; } @@ -37,14 +37,16 @@ public double xirr(Transaction... transactions) { return xirr(newtonsXirr, 0.1); } + public double xirr(Transaction[] transactions, double guess) { + NewtonsXirr newtonsXirr = new NewtonsXirr(transactions); + return xirr(newtonsXirr, guess); + } + public double xirr(double[] payments, double[] days) { return xirr(payments, days, 0.1); } public double xirr(double[] payments, double[] days, double guess) { - if (payments.length != days.length) { - throw new XirrException("Payments and Days must have the same number of elements."); - } NewtonsXirr newtonsXirr = new NewtonsXirr(payments, days); return xirr(newtonsXirr, guess); }