2020-11-30 18:22:33 +03:00

181 lines
5.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using EvoCalculator.Core.Models.Calculation.Models;
namespace EvoCalculator.Core.FinanceFormulas
{
public class XIRR
{
private readonly Flow[] _flows;
private readonly double _guess = 0.1;
private const int MaxIterations = 100;
private const double DefaultTolerance = 1E-6;
private const double DefaultGuess = 0.1;
public XIRR(Flow[] flows)
{
_flows = flows;
}
public XIRR(Flow[] flows, double guess)
{
_flows = flows;
_guess = guess;
}
private static double NewtonsMethodImplementation(IEnumerable<Flow> 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;
}
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<Flow> 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);
}
}
private static double BisectionMethodImplementation(IEnumerable<Flow> 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 Func<IEnumerable<Flow>, double> NewthonsMethod =
cf => NewtonsMethodImplementation(cf);
private Func<IEnumerable<Flow>, double> BisectionMethod =
cf => BisectionMethodImplementation(cf);
private double CalcXirr(IEnumerable<Flow> flows, Func<IEnumerable<Flow>, 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;
}
}
}