C#编写自定义Excel公式计算非均匀现金流的NPV与IRR

1、用Visual Studio新建一个类库,命名为CashFlowTools

C#编写自定义Excel公式计算非均匀现金流的NPV与IRR_第1张图片


2、引用必要的类库:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using Microsoft.Win32;
using Microsoft.Office.Interop.Excel;

其中最后一个需要在“引用”中添加一个COM组件“Microsoft.Office.Interop.Excel”


3、添加COM相关属性

[Guid("DDE8AA59-9860-44B3-B1AF-923ABB4A8EEE")] 
[ClassInterface(ClassInterfaceType.AutoDual)] 
[ComVisible(true)] 

注意要生成一个GUID:

C#编写自定义Excel公式计算非均匀现金流的NPV与IRR_第2张图片

这里我使用的LinqPad,一个轻量级的IDE,支持C#、VB.NET、Linq、F#、SQL语句,非常适合用来测试少量代码。


4、添加COM的注册和反注册函数,这两个函数代码是固定的。添加后的程序结构如下

namespace CashFlowTools
{
    [Guid("DDE8AA59-9860-44B3-B1AF-923ABB4A8EEE")]
    [ClassInterface(ClassInterfaceType.AutoDual)]
    [ComVisible(true)] 
    public class CashFlowTools
    {
        #region COM related
        [ComRegisterFunction]
        public static void RegisterFunction(Type type)
        {
            Registry.ClassesRoot.CreateSubKey(GetSubKeyName(type, "Programmable"));
            var key = Registry.ClassesRoot.OpenSubKey(GetSubKeyName(type, "InprocServer32"), true);
            key.SetValue("", Environment.SystemDirectory + @"\mscoree.dll", RegistryValueKind.String);
        }

        [ComUnregisterFunction]
        public static void UnregisterFunction(Type type)
        {
            Registry.ClassesRoot.DeleteSubKey(GetSubKeyName(type, "Programmable"), false);
        }

        private static string GetSubKeyName(Type type, string subKeyName)
        {
            var s = new System.Text.StringBuilder();
            s.Append(@"CLSID\{");
            s.Append(type.GUID.ToString().ToUpper());
            s.Append(@"}\");
            s.Append(subKeyName);
            return s.ToString();
        }
        #endregion

        // 以下为自定义Excel函数
        // ...
    }
}

5、编写自定义函数

        /// 
        /// Calculate net present value
        /// 
        /// Excel Range stores the time of the cash flows
        /// Excel Range stores the amount of cash flows
        /// The discount rate
        /// the frequency measure in unit of time, for example, if the times of cash
        /// flow are in days but we want to measure NPV in year bases, CompoundFrequency should be set as 365.
        /// The net present value
        public double ExtNPV(Range timeRange, Range cashFlowRange, double discountRate, double CompoundFrequency = 1)
        {
            if (timeRange.Columns.Count != 1 || cashFlowRange.Columns.Count != 1
                || timeRange.Rows.Count != cashFlowRange.Rows.Count)
                return Double.NaN;
            int length = timeRange.Rows.Count;
            double[] timeArray = new double[length];
            double[] cashFlowArray = new double[length];
            for (int i = 0; i < length; i++)
            {
                timeArray[i] = Convert.ToDouble((timeRange.Cells[i+1,1] as Range).Value2);
                cashFlowArray[i] = Convert.ToDouble((cashFlowRange.Cells[i + 1, 1] as Range).Value2); 
            }
            for (int i = 1; i < length; i++)
                timeArray[i] = (timeArray[i] - timeArray[0]) / CompoundFrequency;
            timeArray[0] = 0;
            return NetPresentValue(timeArray, cashFlowArray, discountRate);
        }

        /// 
        /// Calculate the internal rate of return using Newton's method.
        /// 
        /// Excel Range stores the time of the cash flows
        /// Excel Range stores the amount of cash flows
        /// the initial estimate for the Newton's method 
        /// the frequency measure in unit of time, for example, if the times of cash
        /// flow are in days but we want to measure NPV in year bases, CompoundFrequency should be set as 365.
        /// The resolution control
        /// the internal rate of return, not annualized
        public double ExtIRR(Range timeRange, Range cashFlowRange, double guess = 0, double CompoundFrequency = 1, double eps = 1e-8)
        {
            if (timeRange.Columns.Count != 1 || cashFlowRange.Columns.Count != 1
                || timeRange.Rows.Count != cashFlowRange.Rows.Count)
                return Double.NaN;
            int length = timeRange.Rows.Count;
            double[] timeArray = new double[length];
            double[] cashFlowArray = new double[length];
            for (int i = 0; i < length; i++)
            {
                timeArray[i] = Convert.ToDouble((timeRange.Cells[i + 1, 1] as Range).Value2);
                cashFlowArray[i] = Convert.ToDouble((cashFlowRange.Cells[i + 1, 1] as Range).Value2);
            }
            for (int i = 1; i < length; i++)
                timeArray[i] = (timeArray[i] - timeArray[0]) / CompoundFrequency;
            timeArray[0] = 0;

            double x1 = guess, x2 = guess, x3 = 10000;
            do
            {
                x1 = x2;
                x2 = x1 - NetPresentValue(timeArray, cashFlowArray, x1) / DerivativeNPV(timeArray, cashFlowArray, x1);
                if (Math.Abs(x1 - x2) < x3)
                    x3 = Math.Abs(x1 - x2);
                else
                    return Double.NaN;
            } while (x3 > eps);
            return x2;
        }


        private double NetPresentValue(double[] time, double[] cashFlow, double discountRate)
        {
            double npv = 0;
            for (int i = 0; i < time.Length; i++)
                npv += cashFlow[i] / Math.Pow(1 + discountRate, time[i]);
            return npv;
        }

        private double DerivativeNPV(double[] time, double[] cashFlow, double discountRate)
        {
            double dnpv = 0;
            for (int i = 1; i < time.Length; i++)
                dnpv -= time[i] * cashFlow[i] / Math.Pow(1 + discountRate, time[i] + 1);
            return dnpv;
        }



6、修改项目属性,取消COM注册:

C#编写自定义Excel公式计算非均匀现金流的NPV与IRR_第3张图片


如果需要调试程序,在“调试”下的“启用外部程序”中添加Excel的路径命令行

C#编写自定义Excel公式计算非均匀现金流的NPV与IRR_第4张图片



7、注册加载DLL

我们需要使用程序regasm来注册我们的DLL。

首先以管理员身份运行CMD,使用命令

cd /d %windir%\Microsoft.NET\Framework\v4.0.30319 

切换到regasm所在目录,然后运行以下命令

C:\Windows\Microsoft.NET\Framework\v4.0.30319>RegAsm /codebase "D:\Documents\Vis
ual Studio 2012\Projects\CashFlowTools\CashFlowTools\bin\Debug\CashFlowTools.dll
"
Microsoft .NET Framework 程序集注册实用工具版本 4.0.30319.18408
(适用于 Microsoft .NET Framework 版本 4.0.30319.18408)
版权所有 (C) Microsoft Corporation。保留所有权利。

RegAsm : warning RA0000 : 使用 /codebase 注册未签名的程序集可能会导致程序集妨碍
可能在同一台计算机上安装的其他应用程序。/codebase 开关旨在仅用于已签名的程序集。
请为您的程序集提供一个强名称并重新注册它。
成功注册了类型

显示关于签名的警告现在可以无视,但如果公开发布正式版则必须签名。


8、在Excel中加载该公式

首先,在Excel中右键功能区,选择“自定义功能区”,加入“开发工具”选项卡(如果已有则可忽略此步骤)。

在“开发工具”点击“加载项”按钮,弹出如下对话框,选择刚刚编写的类库

C#编写自定义Excel公式计算非均匀现金流的NPV与IRR_第5张图片


9、注销自定义函数

在CMD中反注册该DLL:

C:\Windows\Microsoft.NET\Framework\v4.0.30319>RegAsm /unregister "D:\Documents\V
isual Studio 2012\Projects\CashFlowTools\CashFlowTools\bin\Debug\CashFlowTools.d
ll"
Microsoft .NET Framework 程序集注册实用工具版本 4.0.30319.18408
(适用于 Microsoft .NET Framework 版本 4.0.30319.18408)
版权所有 (C) Microsoft Corporation。保留所有权利。

成功注销了类型



你可能感兴趣的:(C#,Excel,Office,excel,.net,framework)