Расчет ЧССНП

Как рассчитать функцию Excel XIRR с помощью С#?


person Hitusam    schedule 03.03.2011    source источник
comment
Может быть, эта статья базы знаний Microsoft поможет?   -  person Uwe Keim    schedule 03.03.2011
comment
см. мое редактирование - я добавил полный пример С#...   -  person Agnius Vasiliauskas    schedule 04.03.2011
comment
и не забудьте пометить ответ как принятый, если считаете, что он решает вашу проблему.   -  person Agnius Vasiliauskas    schedule 04.03.2011
comment
Для всех, кто хочет увидеть ссылку на Excel: msdn.microsoft.com/en-us/library/office/   -  person dyslexicanaboko    schedule 15.10.2012
comment
Итак, мой друг только что показал мне это, я еще не использовал его, но это может быть частичный ответ, поскольку он не включает даты в расчет msdn.microsoft.com/en-us/library/   -  person dyslexicanaboko    schedule 20.06.2013
comment
Точно ссылка для IRR не для ЧИСТ. IRR и XIRR   -  person Gonzalo Contento    schedule 06.09.2013


Ответы (5)


Согласно функции XIRR документации openoffice (формула такая же, как в Excel ) необходимо найти переменную XIRR в следующем уравнении f(xirr):
введите описание изображения здесь
Вы можете рассчитать значение xirr следующим образом:

  1. вычисление производной вышеуказанной функции -> f '(xirr)
  2. после того, как у вас есть f(xirr) и f'(xirr), вы можете найти значение xirr, используя итеративную метод Ньютона - знаменитую формулу ->
    введите здесь описание изображения

РЕДАКТИРОВАТЬ
У меня есть немного времени, так что вот он - полный код C# для вычисления XIRR:

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 из-за ограничений формулы и/или метода Ньютона!

ваше здоровье!

person Agnius Vasiliauskas    schedule 03.03.2011
comment
Обратите внимание, что если вы пытаетесь сопоставить результаты Excel, вам нужно вместо этого установить допуск на 0,00000001, и, как указано ниже, вы можете добавить код, чтобы максимально увеличить количество итераций до 100 (или сделать это настраиваемым). - person Luther; 07.02.2013
comment
Довольно чистый код. Спасибо. Поскольку мне нужно использовать XIRR в моем приложении для Java и Android, я выполняю перенос на Java. При необходимости вы можете обратиться к github.com/yccheok/xirr. Не использовать закрытие, так как код должен быть совместим с Java 6 и 7. - person Cheok Yan Cheng; 10.07.2014

Я начал с решения 0x69, но в конечном итоге некоторые новые сценарии привели к сбою метода Ньютона. Я создал «умную» версию, которая использует метод деления пополам (медленнее), когда метод Ньютона терпит неудачу.

Обратите внимание на встроенные ссылки на несколько источников, которые я использовал для этого решения.

Наконец, вы не сможете воспроизвести некоторые из этих сценариев в Excel, поскольку сам Excel использует метод Ньютона. Обратитесь к XIRR, а? для интересного обсуждения это.

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 { открытый класс Program { частный const Double DaysPerYear = 365.0; частная константа 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; } } }

}

person Gonzalo Contento    schedule 14.06.2011
comment
Просто заявить, что я использую ваш код, и он работает так, как ожидалось. - person BrunoSalvino; 14.12.2011
comment
Просто слово предостережения для тех, кто принял этот код, он отлично работает, однако вы можете столкнуться со странным пограничным случаем, когда вы входите в бесконечный цикл, по сути, тупик. Чтобы этого не произошло, я настоятельно рекомендую добавить максимальную итерацию 100 или любое другое число, которое вам удобно в методе NewtonsMethod. Вот так: в то время как (ошибка › ДОПУСК && i ‹ 100), увеличивайте i на каждой итерации. Полученные значения по-прежнему соответствовали тому, что давал мне Excel. Это происходит (из того, что я видел) только в тех случаях, когда вы приближаетесь к бесконечности, но не можете добраться туда быстро. - person dyslexicanaboko; 16.10.2012
comment
Я нашел еще один пограничный случай, этот имеет значение, потому что результаты в Excel разные. Внутри метода NewtonsMethod — во время вычисления, если x0 и x1 равны Infinity, результатом, возвращаемым из метода, является Infinity. Причина в том, что Infinity - Infinity = NaN, но x0 получает значение x1 после установки ошибки, поэтому последнее значение равно Infinity. В excel результат нулевой! Для справки вот мой набор данных: значения {-10000, 10100, 10000}, даты {26.12.2010, 26.12.2010, 16.10.2012}. Может быть, включить оператор if, который ищет err == double.NaN, тогда x0 = 0; Я не определился. - person dyslexicanaboko; 17.10.2012
comment
дислексиканабоко: я сталкиваюсь с ситуацией, когда Excel может вычислить XIRR, но в какой-то момент в этом коде err заканчивается как NaN, поэтому здесь я не получаю результата. 95% моих других тестовых случаев в порядке. Дайте мне знать, если у вас есть какие-либо идеи по этому поводу :-) - person Luther; 08.02.2013
comment
@dyslexicanaboko взгляните на последнюю версию этого поста. Не уверен, что это решит ваши проблемы. - person Gonzalo Contento; 13.02.2013
comment
@Luther - эй, извини, я не видел твой комментарий, пока не получил уведомление о комментарии ниже твоего. Чтобы уведомить людей, используйте символ @ перед их дескриптором. Я могу попытаться рассмотреть вашу проблему позже, но мне нужны некоторые данные для работы. Кроме того, код изменился, поэтому я не уверен, что это проблема. Попробуйте поработать с изменениями Гонсало, возможно, это решит вашу проблему? - person dyslexicanaboko; 14.02.2013
comment
@GonzaloContento очень приятные улучшения, я поиграю с этим и вернусь к вам. Мне нужно ваше мнение кое о чем: если вы вернете NaN, как лучше всего описать это пользователю, если он ожидал процент? Иногда я возвращаю положительную бесконечность, и я думаю, что они могут это понять, но NaN - не уверен. - person dyslexicanaboko; 14.02.2013
comment
@dyslexicanaboko Глядя на Википедию, лучшим сообщением может быть что-то вроде Неверные данные... - person Gonzalo Contento; 15.02.2013
comment
Я использовал это для расчета годовой доходности портфеля акций и обнаружил, что если вы не используете отрицательное предположение при попытке вычислить отрицательную доходность, Не удалось рассчитать: решение не найдено. Исключение df(x) = 0 возникает в тех случаях, когда Excel смог его вычислить. Я обошел проблему, создав и перехватив пользовательское исключение и повторив расчет с отрицательным предположением в блоке catch. - person MadMax1138; 06.09.2013
comment
@MadMax1138 MadMax1138, не могли бы вы опубликовать сценарий RunScenario(new[] ...? Я собираюсь добавить исправление... - person Gonzalo Contento; 07.10.2013
comment
В настоящее время я работаю с этим, и мне довелось столкнуться с тем, что Math.pow возвращает NaN. хотя это понятно, я не знаю, как Excel может получить значение. Кто-нибудь еще испытывает это? - person Milo Cabs; 04.03.2016
comment
Я нашел крайний случай следующим образом: Для справки вот мой набор данных: значения {-5383992,43, -0,03, 10066469,21,5053369,26,0,00999999999999999}, даты {2013-02-27, 2013-02-28, 2013-03-06 ,2013-03-15,2013-03-31} Я вижу, что XL, похоже, возвращается с самым высоким результатом из набора множественных результатов. Как мы эмулируем то же самое с нашим кодом? - person Jersey_Guy; 12.09.2017
comment
Этот ответ получил несколько действительно интересных комментариев. Меня больше всего интересует, получили ли @dyslexicanaboko или GonzaloContento какие-либо новые идеи за последние 3 года, используя этот код. - person CularBytes; 21.03.2020
comment
@CularBytes, извините, нет, я фактически ушел из инвестиционной индустрии и вместо этого занялся здравоохранением. Мне нечего сообщить, потому что я давно не использовал этот код. Настоящая заставка в то время, это точно. - person dyslexicanaboko; 29.03.2020

Спасибо авторам пакета nuget, расположенного по адресу Excel Financial Functions. Он поддерживает множество финансовых методов — AccrInt, Irr, Npv, Pv, XIrr, XNpv и т. д.,

  1. Установите и импортируйте пакет.
  2. Поскольку все методы в финансовом классе являются статическими, вызовите конкретный метод напрямую как 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.

введите описание изображения здесь

person Saravanan Sachi    schedule 04.04.2018

В других ответах показано, как реализовать XIRR в C#, но если требуется только вычисление результата, вы можете вызвать функцию 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);
    }
person galbarm    schedule 01.06.2014
comment
Это, вероятно, будет значительно медленнее по сравнению с реализациями кода C # в других ответах (но это хорошая демонстрация, если производительность не имеет значения!). - person user700390; 01.06.2016

В этом репозитории с сайта GitHub — klearlending/XIRR есть пример кода для расчета XIRR.

Автор также предоставил сообщение в блоге XIRR-demystified, в котором объясняется логика и рассуждения.

Пока что эта библиотека дает мне близкие к точным результаты. (Все еще изучаю его и разветвляю для личного использования)

person Shubhan    schedule 22.05.2021