using System; using System.Collections.Generic; using System.Linq; using EvoCalculator.Core.Models.Common; namespace EvoCalculator.Core.FinanceFormulas; public class XIRR { private const int MaxIterations = 100; private const double DefaultTolerance = 1E-6; private const double DefaultGuess = 0.1; private readonly Flow[] _flows; private readonly double _guess = 0.1; private readonly Func, double> BisectionMethod = cf => BisectionMethodImplementation(cf); private readonly Func, double> NewthonsMethod = cf => NewtonsMethodImplementation(cf); public XIRR(Flow[] flows) { _flows = flows; } public XIRR(Flow[] flows, double guess) { _flows = flows; _guess = guess; } private static double NewtonsMethodImplementation(IEnumerable flows, double guess = DefaultGuess, double tolerance = DefaultTolerance, int maxIterations = MaxIterations) { var x0 = guess; var i = 0; double error; do { var dfx0 = new XNPV(flows, x0).GetResultPrime(); if (Math.Abs(dfx0 - 0) < double.Epsilon) throw new InvalidOperationException("Could not calculate: No solution found. df(x) = 0"); var fx0 = new XNPV(flows, x0).GetResultPrime(); var x1 = x0 - fx0 / dfx0; error = Math.Abs(x1 - x0); x0 = x1; } while (error > tolerance && ++i < maxIterations); if (i == maxIterations) throw new InvalidOperationException("Could not calculate: No solution found. Max iterations reached."); return x0; } private static double BisectionMethodImplementation(IEnumerable flows, double tolerance = DefaultTolerance, int maxIterations = MaxIterations) { // From "Applied Numerical Analysis" by Gerald var brackets = Brackets.Find(flows); if (Math.Abs(brackets.First - brackets.Second) < double.Epsilon) throw new ArgumentException("Could not calculate: bracket failed"); double f3; double result; var x1 = brackets.First; var x2 = brackets.Second; var i = 0; do { var f1 = new XNPV(flows, x1).GetResult(); var f2 = new XNPV(flows, x2).GetResult(); if (Math.Abs(f1) < double.Epsilon && Math.Abs(f2) < double.Epsilon) throw new InvalidOperationException("Could not calculate: No solution found"); if (f1 * f2 > 0) throw new ArgumentException("Could not calculate: bracket failed for x1, x2"); result = (x1 + x2) / 2; f3 = new XNPV(flows, result).GetResult(); if (f3 * f1 < 0) x2 = result; else x1 = result; } while (Math.Abs(x1 - x2) / 2 > tolerance && Math.Abs(f3) > double.Epsilon && ++i < maxIterations); if (i == maxIterations) throw new InvalidOperationException("Could not calculate: No solution found"); return result; } private double CalcXirr(IEnumerable flows, Func, double> method) { if (flows.Count(cf => cf.Value > 0) == 0) throw new ArgumentException("Add at least one positive item"); if (flows.Count(c => c.Value < 0) == 0) throw new ArgumentException("Add at least one negative item"); var result = method(flows); if (double.IsInfinity(result)) throw new InvalidOperationException("Could not calculate: Infinity"); if (double.IsNaN(result)) throw new InvalidOperationException("Could not calculate: Not a number"); return result; } public double GetResult() { try { try { return CalcXirr(_flows, NewthonsMethod); } catch (InvalidOperationException) { // Failed: try another algorithm return CalcXirr(_flows, BisectionMethod); } } catch (ArgumentException e) { Console.WriteLine(e.Message); } catch (InvalidOperationException exception) { Console.WriteLine(exception.Message); } return 0; } public struct Brackets { public readonly double First; public readonly double Second; private Brackets(double first, double second) { First = first; Second = second; } internal static Brackets Find(IEnumerable flows, double guess = DefaultGuess, int maxIterations = MaxIterations) { const double bracketStep = 0.5; var leftBracket = guess - bracketStep; var rightBracket = guess + bracketStep; var i = 0; while (new XNPV(flows, leftBracket).GetResult() * new XNPV(flows, rightBracket).GetResult() > 0 && i++ < maxIterations) { leftBracket -= bracketStep; rightBracket += bracketStep; } return i >= maxIterations ? new Brackets(0, 0) : new Brackets(leftBracket, rightBracket); } } }