// using System; // using System.Linq; // using Money = System.Decimal; // using Rate = System.Double; // using System.Collections.Generic; // // public // struct Pair // { // public Pair(T first, Z second) // { // First = first; // Second = second; // } // // public readonly T First; // // public readonly Z Second; // } // // // public class CashFlow // { // public CashFlow(Money amount, DateTime date) // { // Amount = amount; // Date = date; // } // // public readonly Money Amount; // public readonly DateTime Date; // } // // public struct AlgorithmResult // { // public AlgorithmResult(TKindOfResult kind, TValue value) // { // Kind = kind; // Value = value; // } // // public readonly TKindOfResult Kind; // public readonly TValue Value; // } // // public enum ApproximateResultKind // { // ApproximateSolution, // ExactSolution, // NoSolutionWithinTolerance // } // // public static class Algorithms // { // internal static Money CalculateXNPV(IEnumerable cfs, Rate r) // { // if (r <= -1) // r = -0.99999999; // Very funky ... Better check what an IRR <= -100% means // // return (from cf in cfs // let startDate = cfs.OrderBy(cf1 => cf1.Date).First().Date // select cf.Amount / (decimal) Math.Pow(1 + r, (cf.Date - startDate).Days / 365.0)).Sum(); // } // // internal static Pair FindBrackets(Func, Rate, Money> func, // IEnumerable cfs) // { // // Abracadabra magic numbers ... // const int maxIter = 100; // const Rate bracketStep = 0.5; // const Rate guess = 0.1; // // Rate leftBracket = guess - bracketStep; // Rate rightBracket = guess + bracketStep; // var iter = 0; // // while (func(cfs, leftBracket) * func(cfs, rightBracket) > 0 && iter++ < maxIter) // { // leftBracket -= bracketStep; // rightBracket += bracketStep; // } // // if (iter >= maxIter) // return new Pair(0, 0); // // return new Pair(leftBracket, rightBracket); // } // // // From "Applied Numerical Analyis" by Gerald // internal static AlgorithmResult Bisection(Func func, // Pair brackets, Rate tol, int maxIters) // { // int iter = 1; // // Money f3 = 0; // Rate x3 = 0; // Rate x1 = brackets.First; // Rate x2 = brackets.Second; // // do // { // var f1 = func(x1); // var f2 = func(x2); // // if (f1 == 0 && f2 == 0) // return new AlgorithmResult(ApproximateResultKind.NoSolutionWithinTolerance, // x1); // // if (f1 * f2 > 0) // throw new ArgumentException("x1 x2 values don't bracket a root"); // // x3 = (x1 + x2) / 2; // f3 = func(x3); // // if (f3 * f1 < 0) // x2 = x3; // else // x1 = x3; // // iter++; // } while (Math.Abs(x1 - x2) / 2 > tol && f3 != 0 && iter < maxIters); // // if (f3 == 0) // return new AlgorithmResult(ApproximateResultKind.ExactSolution, x3); // // if (Math.Abs(x1 - x2) / 2 < tol) // return new AlgorithmResult(ApproximateResultKind.ApproximateSolution, x3); // // if (iter > maxIters) // return new AlgorithmResult(ApproximateResultKind.NoSolutionWithinTolerance, // x3); // // throw new Exception("It should never get here"); // } // // public static AlgorithmResult CalculateXIRR(IEnumerable cfs, Rate tolerance, // int maxIters) // { // var brackets = FindBrackets(CalculateXNPV, cfs); // // if (brackets.First == brackets.Second) // return new AlgorithmResult(ApproximateResultKind.NoSolutionWithinTolerance, // brackets.First); // // return Bisection(r => CalculateXNPV(cfs, r), brackets, tolerance, maxIters); // } // } // // // TESTS // using Microsoft.VisualStudio.TestTools.UnitTesting; // using System.Collections.Generic; // using System; // using Rate = System.Double; // // namespace TimeLineTest // { // [TestClass()] // public class AlgorithmsTest // { // IEnumerable cfs = new CashFlow[] // { // new CashFlow(-10000, new DateTime(2008, 1, 1)), // new CashFlow(2750, new DateTime(2008, 3, 1)), // new CashFlow(4250, new DateTime(2008, 10, 30)), // new CashFlow(3250, new DateTime(2009, 2, 15)), // new CashFlow(2750, new DateTime(2009, 4, 1)) // }; // // IEnumerable bigcfs = new CashFlow[] // { // new CashFlow(-10, new DateTime(2000, 1, 1)), // new CashFlow(10, new DateTime(2002, 1, 2)), // new CashFlow(20, new DateTime(2003, 1, 3)) // }; // // IEnumerable negcfs = new CashFlow[] // { // new CashFlow(-10, new DateTime(2000, 1, 1)), // new CashFlow(-1, new DateTime(2002, 1, 2)), // new CashFlow(1, new DateTime(2003, 1, 3)) // }; // // IEnumerable samedaysamecfs = new CashFlow[] // { // new CashFlow(-10, new DateTime(2000, 1, 1)), // new CashFlow(10, new DateTime(2000, 1, 1)), // }; // // IEnumerable samedaydifferentcfs = new CashFlow[] // { // new CashFlow(-10, new DateTime(2000, 1, 1)), // new CashFlow(100, new DateTime(2000, 1, 1)), // }; // // IEnumerable bigratecfs = new CashFlow[] // { // new CashFlow(-10, new DateTime(2000, 1, 1)), // new CashFlow(20, new DateTime(2000, 5, 30)), // }; // // IEnumerable zeroRate = new CashFlow[] // { // new CashFlow(-10, new DateTime(2000, 1, 1)), // new CashFlow(10, new DateTime(2003, 1, 1)), // }; // // IEnumerable doubleNegative = new CashFlow[] // { // new CashFlow(-10000, new DateTime(2008, 1, 1)), // new CashFlow(2750, new DateTime(2008, 3, 1)), // new CashFlow(-4250, new DateTime(2008, 10, 30)), // new CashFlow(3250, new DateTime(2009, 2, 15)), // new CashFlow(2750, new DateTime(2009, 4, 1)) // }; // // IEnumerable badDoubleNegative = new CashFlow[] // { // new CashFlow(-10000, new DateTime(2008, 1, 1)), // new CashFlow(2750, new DateTime(2008, 3, 1)), // new CashFlow(-4250, new DateTime(2008, 10, 30)), // new CashFlow(3250, new DateTime(2009, 2, 15)), // new CashFlow(-2750, new DateTime(2009, 4, 1)) // }; // // double r = 0.09; // double tolerance = 0.0001; // int maxIters = 100; // // private TestContext testContextInstance; // // public TestContext TestContext // { // get { return testContextInstance; } // set { testContextInstance = value; } // } // // [TestMethod()] // public void CalculateXNPV() // { // Assert.AreEqual(2086.6476020315416570634272814M, Algorithms.CalculateXNPV(cfs, r)); // Assert.AreEqual(-10.148147600710372651326920258M, Algorithms.CalculateXNPV(negcfs, 0.5)); // Assert.AreEqual(4.9923725815954514810351876895M, Algorithms.CalculateXNPV(bigcfs, 0.3)); // } // // [TestMethod] // public void FindBrackets() // { // var brackets = Algorithms.FindBrackets(Algorithms.CalculateXNPV, cfs); // Assert.IsTrue(brackets.First < 0.3733 && brackets.Second > 0.3733); // // brackets = Algorithms.FindBrackets(Algorithms.CalculateXNPV, bigcfs); // Assert.IsTrue(brackets.First < 0.5196 && brackets.Second > 0.5196); // // brackets = Algorithms.FindBrackets(Algorithms.CalculateXNPV, negcfs); // Assert.IsTrue(brackets.First < -0.6059 && brackets.Second > -0.6059); // } // // [TestMethod] // public void XIRRTest() // { // var irr = Algorithms.CalculateXIRR(cfs, tolerance, maxIters); // Assert.AreEqual(0.3733, irr.Value, 0.001); // Assert.AreEqual(ApproximateResultKind.ApproximateSolution, irr.Kind); // // irr = Algorithms.CalculateXIRR(bigcfs, tolerance, maxIters); // Assert.AreEqual(0.5196, irr.Value, 0.001); // Assert.AreEqual(ApproximateResultKind.ApproximateSolution, irr.Kind); // // irr = Algorithms.CalculateXIRR(negcfs, tolerance, maxIters); // Assert.AreEqual(-0.6059, irr.Value, 0.001); // Assert.AreEqual(ApproximateResultKind.ApproximateSolution, irr.Kind); // // irr = Algorithms.CalculateXIRR(samedaysamecfs, tolerance, maxIters); // Assert.AreEqual(ApproximateResultKind.NoSolutionWithinTolerance, irr.Kind); // // irr = Algorithms.CalculateXIRR(samedaydifferentcfs, tolerance, maxIters); // Assert.AreEqual(ApproximateResultKind.NoSolutionWithinTolerance, irr.Kind); // // irr = Algorithms.CalculateXIRR(bigratecfs, tolerance, maxIters); // Assert.AreEqual(4.40140, irr.Value, 0.001); // Assert.AreEqual(ApproximateResultKind.ApproximateSolution, irr.Kind); // // irr = Algorithms.CalculateXIRR(zeroRate, tolerance, maxIters); // Assert.AreEqual(0, irr.Value, 0.001); // Assert.AreEqual(ApproximateResultKind.ApproximateSolution, irr.Kind); // // irr = Algorithms.CalculateXIRR(doubleNegative, tolerance, maxIters); // Assert.AreEqual(-0.537055, irr.Value, 0.001); // Assert.AreEqual(ApproximateResultKind.ApproximateSolution, irr.Kind); // // irr = Algorithms.CalculateXIRR(badDoubleNegative, tolerance, maxIters); // Assert.AreEqual(ApproximateResultKind.NoSolutionWithinTolerance, irr.Kind); // } // } // }