java计算年化利率

接了业务需求需要计算年化利率,

公式定义:

IRR计算

在计算 IRR 时,我们希望找到一个折现率r,使得净现值(NPV)为零。NPV 函数定义如下:
NPV = ∑ t = 0 n C t ( 1 + r ) t \text{NPV} = \sum_{t=0}^{n} \frac{C_t}{(1 + r)^t} NPV=t=0∑n(1+r)tCt

其中:

  • C t C_t Ct是第 t 期的现金流。
  • r 是折现率。
  • n 是总期数。

经过调研,采用如下方法。涉及一定的数学思想。

方法一:二分法

使用了二分法(Binary Search)来计算内部收益率(IRR)。这是另一种求解方程根的方法,特别适用于单调函数。以下是对你代码的数学原理的详细解释。

数学原理

你的代码通过以下步骤计算IRR:

初始化:

设置迭代次数上限(LOOPNUM)为1000。

设置最小差异(MINDIF)为0.00000001,以确定何时停止迭代。

定义 minValue 和 maxValue 作为二分法的上下限,初始值分别为0和1。

定义 irrValue 为当前猜测的IRR值,初始为上下限的平均值。

迭代求解:

在每次迭代中,计算 irrValue 的NPV值。

检查NPV值是否接近于0(即流出的现金流量和流入的现金流量的现值之和是否接近0)。

如果NPV值足够接近0,退出循环,返回当前的 irrValue。

如果流出的现金流量大于NPV值,将 maxValue 更新为 irrValue。

如果流出的现金流量小于NPV值,将 minValue 更新为 irrValue。

更新 irrValue 为新的上下限的平均值,继续迭代,直到达到最大迭代次数或满足精度要求。

二分法的优点

简单易实现:二分法不需要计算导数,相对简单。

稳定:二分法在单调函数中总能找到解。

结论

你这段代码通过二分法有效地计算了内部收益率(IRR)。这种方法适用于求解单调函数的根,特别是在金融计算中。代码通过不断缩小搜索范围,逐步逼近使NPV为零的折现率,直到满足精度要求或达到最大迭代次数。

java 复制代码
 /** 迭代次数 */
    public static int LOOPNUM = 1000;

    /** 最小差异 */
    public static final double MINDIF = 0.00000001;

    /**
     * @desc 方法一:使用二分法来计算内部收益率(IRR)
     * @param cashFlow 资金流
     * @return 收益率
     */
    public static String getIrr(List<Double> cashFlow) {
        //初始流出的现金流量
        double flowOut = cashFlow.get(0);
        //maxValue、minValue为二分法的上下限
        double minValue = 0d;
        double maxValue = 1d;
        double irrValue = 0d;
        int LOOPNUM_ = LOOPNUM;
        while (LOOPNUM_ > 0) {
            irrValue = (minValue + maxValue) / 2;
            double npv = NPV(cashFlow, irrValue);
            //说明:IRR定义为使得NPV=0的折现率
            if (Math.abs(flowOut + npv) < MINDIF) {
                break;
            } else if (Math.abs(flowOut) > npv) {
                maxValue = irrValue;
            } else {
                minValue = irrValue;
            }
            LOOPNUM_--;
        }

        double irr = new BigDecimal(String.valueOf(irrValue)).multiply(new BigDecimal(String.valueOf(12))).multiply(new BigDecimal("100")).doubleValue();
        DecimalFormat df = new DecimalFormat("0.00");
        return df.format(Math.abs(irr));
    }

    /**
     * 计算净现值 NPV=SIGMA(Ct/(1+r)^t) 其中Ct为第t期现金流,r贴现率  r=IRR/12
     * @param flowInArr
     * @param rate
     * @return
     */
    public static double NPV(List<Double> flowInArr, double rate) {
        double npv = 0;
        for (int i = 1; i < flowInArr.size(); i++) {
            npv += flowInArr.get(i) / Math.pow(1 + rate, i);
        }
        return npv;
    }

方法二:牛顿 求导法

计算步骤

  1. 初始猜测值:设定一个初始的折现率 r 。
  2. 计算 NPV:使用当前的r 值计算 NPV。
  3. 迭代求解:根据迭代算法(例如牛顿-拉夫森法)不断更新 r值,直到 NPV 足够接近零。

牛顿-拉夫森法

牛顿-拉夫森法的基本步骤

假设我们有一个方程 f ( x ) = 0 f(x) = 0 f(x)=0,我们想找到它的根。牛顿-拉夫森法的迭代公式如下:

x n + 1 = x n − f ( x n ) f ′ ( x n ) x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)} xn+1=xn−f′(xn)f(xn)

其中:

  • x n x_n xn 是当前的猜测值。
  • x n + 1 x_{n+1} xn+1 是更新后的猜测值。
  • f ( x n ) f(x_n) f(xn) 是函数在 x n x_n xn 处的值。
  • f ′ ( x n ) f'(x_n) f′(xn) 是函数在 x n x_n xn 处的导数值。

牛顿-拉夫森法的迭代公式如下:

r n + 1 = r n − NPV ( r n ) NPV ′ ( r n ) r_{n+1} = r_n - \frac{\text{NPV}(r_n)}{\text{NPV}'(r_n)} rn+1=rn−NPV′(rn)NPV(rn)

其中:

  • f ( r ) = NPV ( r ) f(r) = \text{NPV}(r) f(r)=NPV(r)
  • f ′ ( r ) f'(r) f′(r) 是 f ( r ) f(r) f(r) 关于 r r r 的导数。

通过不断更新 r r r 值,使得 NPV ( r ) \text{NPV}(r) NPV(r) 逐渐逼近零,从而求得 IRR。

java 复制代码
/**
     * 方法二:使用求导计算IRR 牛顿-拉夫森法 NPV(r)=0
     * r(n+1) = r(n) - NPV(r(n))/dNPV(r(n))
     *
     * @param cashFlows
     * @param guess
     * @return
     */
    public static String calculateIRR(List<Double> cashFlows, double guess) {
        double precision = 1e-7; // 设定计算精度
        double x0 = guess;//初始折现率猜测值
        double x1 = 0.0;
        int maxIteration = 1000; // 设定最大迭代次数
        double irr = 0.0;
        DecimalFormat df = new DecimalFormat("0.00");
        for (int i = 0; i < maxIteration; i++) {
            x1 = x0 - npv(cashFlows, x0) / dNpv(cashFlows, x0);
            if (Math.abs(x1 - x0) <= precision) {
                irr = new BigDecimal(String.valueOf(x1)).multiply(new BigDecimal(String.valueOf(12))).multiply(new BigDecimal("100")).doubleValue();
                return df.format(Math.abs(irr));
            }
            //将新的折现率赋给x0,作为下一次迭代的起点
            x0 = x1;
        }
        System.out.println(x1);

//        return x1;
        // 如果没有达到设定的精度,则返回最后一次计算的IRR值
        irr = new BigDecimal(String.valueOf(x1)).multiply(new BigDecimal(String.valueOf(12))).multiply(new BigDecimal("100")).doubleValue();
        return df.format(Math.abs(irr));
    }

    // 计算NPV
    private static double npv(List<Double> cashFlows, double rate) {
        double npv = 0.0;
        for (int t = 0; t < cashFlows.size(); t++) {
            npv += cashFlows.get(t)/ Math.pow(1 + rate, t);
        }
        return npv;
    }

    /**
     *  计算NPV的一阶导数
     * dNPV= - t*Ct/(1+r)^(t+1)) 其中Ct为第t期现金流
     */
    private static double dNpv(List<Double> cashFlows, double rate) {
        double dNpv = 0.0;
        for (int t = 1; t < cashFlows.size(); t++) {
            dNpv -= t * cashFlows.get(t) / Math.pow(1 + rate, t + 1);
        }
        return dNpv;
    }

方法三:org.apache.poi.ss.formula.functions.Irr

源码如下:

java 复制代码
public final class Irr implements Function {
    public ValueEval evaluate(final ValueEval[] args, final int srcRowIndex, final int srcColumnIndex) {
        if (args.length == 0 || args.length > 2) {
            return ErrorEval.VALUE_INVALID;
        }
        try {
            double[] values = AggregateFunction.ValueCollector.collectValues(args[0]);
            double guess;
            if (args.length == 2) {
                guess = NumericFunction.singleOperandEvaluate(args[1], srcRowIndex, srcColumnIndex);
            } else {
                guess = 0.1d;
            }
            double result = irr(values, guess);
            NumericFunction.checkValue(result);
            return new NumberEval(result);
        } catch (EvaluationException e) {
            return e.getErrorEval();
        }
    }

    public static double irr(double[] income) {
        return irr(income, 0.1d);
    }

    public static double irr(double[] values, double guess) {
        int maxIterationCount = 20;
        double absoluteAccuracy = 1E-7;
        double x0 = guess;
        double x1;
        int i = 0;
        while (i < maxIterationCount) {
            double fValue = 0;
            double fDerivative = 0;
            for (int k = 0; k < values.length; k++) {
                fValue += values[k] / Math.pow(1.0 + x0, k);
                fDerivative += -k * values[k] / Math.pow(1.0 + x0, k + 1);
            }
            x1 = x0 - fValue / fDerivative;
            if (Math.abs(x1 - x0) <= absoluteAccuracy) {
                return x1;
            }
            x0 = x1;
            ++i;
        }
        return Double.NaN;
    }
}

其中,evaluate 方法是 org.apache.poi.ss.formula.functions.Irr 类中的一个实例方法,用于计算电子表格中公式的值。这是如何在 Apache POI 库中实现自定义函数评估的一部分。

源码使用的是 牛顿-拉夫森法,已经在方法二讲述过,不再赘述。

相关推荐
史努比.2 分钟前
Pod控制器
java·开发语言
2的n次方_4 分钟前
二维费用背包问题
java·算法·动态规划
皮皮林5515 分钟前
警惕!List.of() vs Arrays.asList():这些隐藏差异可能让你的代码崩溃!
java
莳光.5 分钟前
122、java的LambdaQueryWapper的条件拼接实现数据sql中and (column1 =1 or column1 is null)
java·mybatis
程序猿麦小七10 分钟前
基于springboot的景区网页设计与实现
java·spring boot·后端·旅游·景区
敲敲敲-敲代码11 分钟前
游戏设计:推箱子【easyx图形界面/c语言】
c语言·开发语言·游戏
weisian15116 分钟前
认证鉴权框架SpringSecurity-2--重点组件和过滤器链篇
java·安全
蓝田~18 分钟前
SpringBoot-自定义注解,拦截器
java·spring boot·后端
ROC_bird..19 分钟前
STL - vector的使用和模拟实现
开发语言·c++
.生产的驴20 分钟前
SpringCloud Gateway网关路由配置 接口统一 登录验证 权限校验 路由属性
java·spring boot·后端·spring·spring cloud·gateway·rabbitmq