web-dev-qa-db-ja.com

XIRRの計算

C#を使用してExcelのXIRR関数を計算するにはどうすればよいですか?

22
Hitusam

XIRR関数 openofficeドキュメント(式はExcelと同じ)によると、次のf(xirr)方程式でXIRR変数を解く必要があります。
enter image description here
xirr値は次の方法で計算できます。

  1. 上記の関数の導関数を計算する->f '(xirr)
  2. f(xirr)f'(xirr)を取得した後、反復を使用してxirr値を解くことができます ニュートン法 -有名な式->
    enter image description here

[〜#〜]編集[〜#〜]
少し時間がありますので、ここにあります-XIRR計算用の完全なC#コード:

class xirr
    {
        public const double tol = 0.001;
        public delegate double fx(double x);

        public static fx composeFunctions(fx f1, fx f2) {
            return (double x) => f1(x) + f2(x);
        }

        public static fx f_xirr(double p, double dt, double dt0) {
            return (double x) => p*Math.Pow((1.0+x),((dt0-dt)/365.0));
        }

        public static fx df_xirr(double p, double dt, double dt0) {
            return (double x) => (1.0/365.0)*(dt0-dt)*p*Math.Pow((x+1.0),(((dt0-dt)/365.0)-1.0));
        }

        public static fx total_f_xirr(double[] payments, double[] days) {
            fx resf = (double x) => 0.0;

            for (int i = 0; i < payments.Length; i++) {
                resf = composeFunctions(resf,f_xirr(payments[i],days[i],days[0]));
            }

            return resf;
        }

        public static fx total_df_xirr(double[] payments, double[] days) {
            fx resf = (double x) => 0.0;

            for (int i = 0; i < payments.Length; i++) {
                resf = composeFunctions(resf,df_xirr(payments[i],days[i],days[0]));
            }

            return resf;
        }

        public static double Newtons_method(double guess, fx f, fx df) {
            double x0 = guess;
            double x1 = 0.0;
            double err = 1e+100;

            while (err > tol) {
                x1 = x0 - f(x0)/df(x0);
                err = Math.Abs(x1-x0);
                x0 = x1;
            }

            return x0;
        }

        public static void Main (string[] args)
        {
            double[] payments = {-6800,1000,2000,4000}; // payments
            double[] days = {01,08,16,25}; // days of payment (as day of year)
            double xirr = Newtons_method(0.1,
                                         total_f_xirr(payments,days),
                                         total_df_xirr(payments,days));

            Console.WriteLine("XIRR value is {0}", xirr);
        }
    }

ところで、式やニュートン法の制限により、すべての支払いが有効なXIRRになるわけではないことに注意してください。

乾杯!

41

x69 のソリューションから始めましたが、最終的にいくつかの新しいシナリオが原因でニュートン法が失敗しました。ニュートンが失敗したときに二分法(遅い)を使用する「スマート」バージョンを作成しました。

このソリューションに使用した複数のソースへのインライン参照に注意してください。

最後に、Excel自体がニュートン法を使用しているため、これらのシナリオの一部をExcelで再現することはできません。これに関する興味深い議論については、 XIRR、eh? を参照してください。

 using System; 
 using System.Collections.Generic; 
 using System.Linq; 
 

//次の記事を参照してください: // http://blogs.msdn.com/b/lucabol/archive/2007/12/17/bisection-based-xirr-implementation-in- c.aspx // http://www.codeproject.com/Articles/79541/Three-Methods-for-Root-finding-in-C // http://www.financialwebring.org/forum/viewtopic.php?t=105243&highlight=xirr // Excelドキュメントに基づくデフォルト値 // http://office.Microsoft.com/en-us/Excel-help/xirr-function-HP010062387.aspx
名前空間Xirr { public class Program { private const Double DaysPerYear = 365.0; private const int MaxIterations = 100; private const double DefaultTolerance = 1E-6; private const double DefaultGuess = 0.1; private static readonly Func<IEnumerable<CashItem>, Double> NewthonsMethod = cf => NewtonsMethodImplementation(cf, Xnpv, XnpvPrime); private static readonly Func<IEnumerable<CashItem>, Double> BisectionMethod = cf => BisectionMethodImplementation(cf, Xnpv); public static void Main(string[] args) { RunScenario(new[] { // this scenario fails with Newton's but succeeds with slower Bisection new CashItem(new DateTime(2012, 6, 1), 0.01), new CashItem(new DateTime(2012, 7, 23), 3042626.18), new CashItem(new DateTime(2012, 11, 7), -491356.62), new CashItem(new DateTime(2012, 11, 30), 631579.92), new CashItem(new DateTime(2012, 12, 1), 19769.5), new CashItem(new DateTime(2013, 1, 16), 1551771.47), new CashItem(new DateTime(2013, 2, 8), -304595), new CashItem(new DateTime(2013, 3, 26), 3880609.64), new CashItem(new DateTime(2013, 3, 31), -4331949.61) }); RunScenario(new[] { new CashItem(new DateTime(2001, 5, 1), 10000), new CashItem(new DateTime(2002, 3, 1), 2000), new CashItem(new DateTime(2002, 5, 1), -5500), new CashItem(new DateTime(2002, 9, 1), 3000), new CashItem(new DateTime(2003, 2, 1), 3500), new CashItem(new DateTime(2003, 5, 1), -15000) }); } private static void RunScenario(IEnumerable<CashItem> cashFlow) { try { try { var result = CalcXirr(cashFlow, NewthonsMethod); Console.WriteLine("XIRR [Newton's] value is {0}", result); } catch (InvalidOperationException) { // Failed: try another algorithm var result = CalcXirr(cashFlow, BisectionMethod); Console.WriteLine("XIRR [Bisection] (Newton's failed) value is {0}", result); } } catch (ArgumentException e) { Console.WriteLine(e.Message); } catch (InvalidOperationException exception) { Console.WriteLine(exception.Message); } } private static double CalcXirr(IEnumerable<CashItem> cashFlow, Func<IEnumerable<CashItem>, double> method) { if (cashFlow.Count(cf => cf.Amount > 0) == 0) throw new ArgumentException("Add at least one positive item"); if (cashFlow.Count(c => c.Amount < 0) == 0) throw new ArgumentException("Add at least one negative item"); var result = method(cashFlow); 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; } private static Double NewtonsMethodImplementation(IEnumerable<CashItem> cashFlow, Func<IEnumerable<CashItem>, Double, Double> f, Func<IEnumerable<CashItem>, Double, Double> df, Double guess = DefaultGuess, Double tolerance = DefaultTolerance, int maxIterations = MaxIterations) { var x0 = guess; var i = 0; Double error; do { var dfx0 = df(cashFlow, x0); if (Math.Abs(dfx0 - 0) < Double.Epsilon) throw new InvalidOperationException("Could not calculate: No solution found. df(x) = 0"); var fx0 = f(cashFlow, x0); 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; } internal static Double BisectionMethodImplementation(IEnumerable<CashItem> cashFlow, Func<IEnumerable<CashItem>, Double, Double> f, Double tolerance = DefaultTolerance, int maxIterations = MaxIterations) { // From "Applied Numerical Analysis" by Gerald var brackets = Brackets.Find(Xnpv, cashFlow); 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 = f(cashFlow, x1); var f2 = f(cashFlow, x2); 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 = f(cashFlow, result); 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 static Double Xnpv(IEnumerable<CashItem> cashFlow, Double rate) { if (rate <= -1) rate = -1 + 1E-10; // Very funky ... Better check what an IRR <= -100% means var startDate = cashFlow.OrderBy(i => i.Date).First().Date; return (from item in cashFlow let days = -(item.Date - startDate).Days select item.Amount*Math.Pow(1 + rate, days/DaysPerYear)).Sum(); } private static Double XnpvPrime(IEnumerable<CashItem> cashFlow, Double rate) { var startDate = cashFlow.OrderBy(i => i.Date).First().Date; return (from item in cashFlow let daysRatio = -(item.Date - startDate).Days/DaysPerYear select item.Amount*daysRatio*Math.Pow(1.0 + rate, daysRatio - 1)).Sum(); } public struct Brackets { public readonly Double First; public readonly Double Second; public Brackets(Double first, Double second) { First = first; Second = second; } internal static Brackets Find(Func<IEnumerable<CashItem>, Double, Double> f, IEnumerable<CashItem> cashFlow, Double guess = DefaultGuess, int maxIterations = MaxIterations) { const Double bracketStep = 0.5; var leftBracket = guess - bracketStep; var rightBracket = guess + bracketStep; var i = 0; while (f(cashFlow, leftBracket)*f(cashFlow, rightBracket) > 0 && i++ < maxIterations) { leftBracket -= bracketStep; rightBracket += bracketStep; } return i >= maxIterations ? new Brackets(0, 0) : new Brackets(leftBracket, rightBracket); } } public struct CashItem { public DateTime Date; public Double Amount; public CashItem(DateTime date, Double amount) { Date = date; Amount = amount; } } }

}

26

Excel Financial Functions にあるnugetパッケージの寄稿者に感謝します。 AccrInt、Irr、Npv、Pv、XIrr、XNpvなどの多くの財務手法をサポートしています。

  1. パッケージをインストールしてインポートします。
  2. すべてのメソッドはFinancialクラスで静的であるため、必要なパラメーターを使用して特定のメソッドをFinancial.<method_name>として直接呼び出します。

例:

using Excel.FinancialFunctions;

namespace ExcelXirr
{
    class Program
    {
        static void Main(string[] args)
        {
            List<double> valList =new List<double>();
            valList.Add(4166.67);
            valList.Add(-4166.67);
            valList.Add(-4166.67);
            valList.Add(-4166.67);
            List<DateTime> dtList = new List<DateTime>();
            dtList.Add(new DateTime(2014, 9, 1));
            dtList.Add(new DateTime(2014, 10, 1));
            dtList.Add(new DateTime(2014, 11, 1));
            dtList.Add(new DateTime(2014, 12, 1));
            double result = Financial.XIrr(valList, dtList);
            Console.WriteLine(result);
            Console.ReadLine();
        }
    }
}

結果はExcelと同じです。

enter image description here

4
Saravanan Sachi

他の回答は、C#でimplement XIRRを実行する方法を示していますが、計算resultのみが必要な場合は、次の方法でExcelのXIRR関数を直接呼び出すことができます。

最初にMicrosoft.Office.Interop.Excelへの参照を追加してから、次の方法を使用します。

    public static double Xirr(IList<double> values, IList<DateTime> dates)
    {
        var xlApp = new Application();

        var datesAsDoubles = new List<double>();
        foreach (var date in dates)
        {
            var totalDays = (date - DateTime.MinValue).TotalDays;
            datesAsDoubles.Add(totalDays);
        }

        var valuesArray = values.ToArray();
        var datesArray = datesAsDoubles.ToArray();

        return xlApp.WorksheetFunction.Xirr(valuesArray, datesArray);
    }
4
galbarm