Java实现excel 表中的财务公式: Duration/Xirr/Yield

值得注意的是

  1. Duration计算的结果和excel 有差异,但是同样都是用的麦考利公式做了实现,并且进行了修正(最后和用户讨论,这个结果是可以接收的)。
  2. Duration计算的天数同样没有全部实现,具体参考excel 中的不同。
  3. 其余暂时没有发现具体问题。
    1. Duration实现
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class DurationFunction {

    public static Double getDuration(String settlementStr,String maturityStr,Double coupon,Double yld,Double frequency,Integer basis)  {
        Date settlement = null;
        Date maturity = null;
        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            settlement = simpleDateFormat.parse(settlementStr);
            maturity = simpleDateFormat.parse(maturityStr);
        } catch (ParseException e) {
            return new Double(0.0);
        }

        double m=interval(settlement, maturity,basis, 'm');//结算日和到期日之间的月数
        double z=12/frequency;//每个付息期的月数
        double T=Math.ceil(m/z);//结算日和到期日之间的付息次数

        double c=coupon/frequency;
        double y=yld/frequency;

        return new Double((1+1/y-(1+y+T*(c-y))/(c*Math.pow(1+y, T)-c+y))/frequency);
    }

    public static long interval(Date date1, Date date2, int basis, char mark){
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date1);
        int m1=calendar.get(Calendar.MONTH)+1;
        int y1=calendar.get(Calendar.YEAR);
        int d1=calendar.get(Calendar.DAY_OF_MONTH);
        calendar.setTime(date2);
        int m2=calendar.get(Calendar.MONTH)+1;
        int y2=calendar.get(Calendar.YEAR);
        int d2=calendar.get(Calendar.DAY_OF_MONTH);
        if(mark=='d'){
            if(basis==0 || basis==4){  //每月按30天算

                return ((y2-y1)*12+m2-m1)*30+d2-d1;
            }else{  //按实际天数算
                long day = 24 * 3600 * 1000;
                long interval = date2.getTime() / day - date1.getTime() / day;
                return interval;
            }
        }else if(mark=='m'){
            int m=(y2-y1)*12+m2-m1;
            if(d1>d2) m-=1;
            return m;
        }
        return 0;
    }

}

**2. Xirr 实现 **

import oracle.jdbc.internal.OracleStruct;
import oracle.sql.ARRAY;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

public class XirrFunction {


    public final static Double MIN_DISTANCE = 1E-15;    // 目标精确度,允许的最小差值(建议精确到8-15位小数)
    public final static Double MIN_VALUE = 1E-8;        // 允许的净现值偏差最小范围
    public final static int MAX_ITERATION = 100;        // 最高迭代次数(防止卡死,通常100次循环足够)
    public final static Double DEFAULT_XIRR_GUESS = 0.1D;        // 默认Xirr猜测值
    public final static Double FULL_YEAR_DAYS = 365.0D;    // 全年天数


    public static Double xirr_q(ARRAY array) {

        try {
            List<Double> values = new ArrayList<>();
            List<Date> dates = new ArrayList<>();
            Object[] var6 = (Object[]) array.getArray();
            for (int i = 0; i < var6.length; i++) {
                OracleStruct oracleStruct = (OracleStruct) var6[i];
                Object[] attributes = oracleStruct.getAttributes();
                //字段值数组元素起始位置为0, 和字段数组不同。
                values.add( ((BigDecimal)attributes[0]).doubleValue());
                dates.add((Date) attributes[1]);

            }
            Double rate = xirr(values.toArray(new Double[0]),
                    dates.toArray(new Date[0]), DEFAULT_XIRR_GUESS);
            return rate;
        } catch (Exception e) {
            return new Double(0.0);
        }
    }

    /**
     * 计算净现值为0的收益率<br>
     * 说明:使用迭代折半查近视值法计算函数 XIRR。通过改变收益率(从 guess 开始),不断修正计算结果,直至其精度小于 1E-7。<br>
     * 如果函数 XIRR 运算 100 次,仍未找到结果,则返回错误值 NaN。
     *
     * @param values 现金流量(必须至少一个正现金流和一个负现金流)
     * @param dates  日期
     * @param guess  猜测值
     * @return 收益率
     */
    public static Double xirr(Double[] values, Date[] dates, double guess) {
        Double result = new Double(0.0);            // 返回结果
        Double irrGuess = DEFAULT_XIRR_GUESS;    // 计算xirr猜测值折半量
        Double sumCashFlows = 0.0D;                    // 现金流量和
        boolean wasHi = false;                // 防止遗漏区间标识
        Double npv = 0.0D;                    // 净现值
        int negativeCashFlowCount = 0;                    // 正现金流个数
        int positiveCashFlowCount = 0;                    // 负现金流个数

        if (values == null || values.length == 0) return result;
        if (dates == null || dates.length == 0) return result;
        if (values.length != dates.length) return result;


        for (int i = 0; i < values.length; i++) {
            sumCashFlows += values[i];
            if (values[i] > 0.0) {
                negativeCashFlowCount++;
            } else if (values[i] < 0.0) {
                positiveCashFlowCount++;
            }
        }

        if (negativeCashFlowCount <= 0 || positiveCashFlowCount <= 0) return result;

        if (!Double.isNaN(guess)) {
            irrGuess = guess;
            if (irrGuess <= 0.0) irrGuess = 0.5;
        }

        Double irr = sumCashFlows < 0 ? -irrGuess : irrGuess;

        for (int i = 0; i <= MAX_ITERATION; i++) {

            npv = getXirrNpvValue(irr, values, dates);

            if (Math.abs(npv) < MIN_VALUE) {
                result = irr;
                break;
            }

            if (npv > 0.0) {
                if (wasHi) irrGuess /= 2;
                irr += irrGuess;
                if (wasHi) {
                    irrGuess -= MIN_DISTANCE;
                    wasHi = false;
                }
            } else {
                irrGuess /= 2;
                irr -= irrGuess;
                wasHi = true;
            }

            if (irrGuess <= MIN_DISTANCE) {
                result = irr;
                break;
            }
        }

        return result;
    }

    /**
     * 根据公式计算净现值
     *
     * @param guess  猜测值
     * @param values 现金流量(必须至少一个正现金流和一个负现金流)
     * @param dates  日期
     * @return 净现值
     */
    public static Double getXirrNpvValue(final double guess, Double[] values, Date[] dates) {
        Double result = 0.0D;
        // 0 = sum(values[i] / (1 + rate)^((dates[i] - dates[1]) / 365))
        for (int i = 0; i < dates.length; i++) {
            result += values[i] / Math.pow(1 + guess, getIntervalDays(dates[i], dates[0]) / FULL_YEAR_DAYS);
        }
        return result;
    }

    private static Long getIntervalDays(Date endDate, Date startDate) {
        long day = 24 * 60 * 60 * 1000;

        return (endDate.getTime() - startDate.getTime()) / day;
    }
}

3. Yield 实现

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;

public class YieldFunction {

//    public static void main(String[] args) throws Exception {
//        Double yield = yield("2008-02-15","2016-11-15", 0.0575, 95.04287, 100, 2,2);
//        System.out.println(yield);
//    }
    public static Double getYield(String settlementStr, String maturityStr, Double rate, Double pr,
                               Double redemption, Integer frequency, Integer basis) {
        Date settlement = null;
        Date maturity = null;
        try {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
            settlement = simpleDateFormat.parse(settlementStr);
            maturity = simpleDateFormat.parse(maturityStr);
            if (settlement.compareTo(maturity) == 1) {
                throw new Exception();
            }
            if (rate < 0 || pr <= 0 || redemption <= 0) {
                throw new Exception();
            }
        } catch (Exception e) {
            return new Double(0.0);
        }

        double m = interval(settlement, maturity, basis, 'm');
        int z = 12 / frequency;
        int n = new Double(Math.ceil(m / z)).intValue();

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(maturity);
        calendar.add(Calendar.MONTH, -n * z);
        Date end = new Date();
        end.setTime(calendar.getTimeInMillis());
        Date start = new Date();
        if (settlement.compareTo(end) == 1) {
            start.setTime(end.getTime());
            calendar.add(Calendar.MONTH, z);
            end.setTime(calendar.getTimeInMillis());
        } else {
            calendar.add(Calendar.MONTH, -z);
            start.setTime(calendar.getTimeInMillis());
        }
        double dsr = interval(settlement, maturity, basis, 'd');

        double E = interval(start, end, basis, 'd');

        double A = interval(start, settlement, basis, 'd');
        if (n <= 1) {
            return new Double((redemption / 100.0 + rate / frequency - pr / 100.0 - A * rate / E / frequency) * frequency * E / dsr / (pr / 100.0 + A * rate / E / frequency));
        }
        long dsc = interval(settlement, end, basis, 'd');

        double lguess = 0.1;
        double guess = 0.1;
        double lvalue = 0;
        double step = 0.01;
        for (int i = 1; i <= 1000; i++) {
            double tmp1 = redemption / Math.pow(1.0 + guess / frequency, n - 1.0 + dsc / E);
            double tmp2 = 0;
            for (double k = 1; k <= n; k++) {
                tmp2 += 100.0 * rate / frequency / Math.pow(1 + guess / frequency, k - 1.0 + dsc / E);
            }
            double tmp3 = 100.0 * rate * A / frequency / E;
            double value = tmp1 + tmp2 - tmp3 - pr;
            if (value < 0.0000001 && value > -0.0000001) break;
            else if ((lvalue > 0.0000001 && value < -0.0000001) || (lvalue < -0.0000001 && value > 0.0000001)) {
                double temp1 = value;
                double temp2 = guess;
                if (value > lvalue) {
                    double tmp = value;
                    value = lvalue;
                    lvalue = tmp;
                    tmp = guess;
                    guess = lguess;
                    lguess = tmp;
                }
                guess = lvalue * (guess - lguess) / (lvalue - value) + lguess;
                step = step / 10;
                lvalue = temp1;
                lguess = temp2;
                continue;
            } else if (value > 0.0000001) {
                lguess = guess;
                lvalue = value;
                guess += step;
            } else if (value < -0.0000001) {
                lguess = guess;
                lvalue = value;
                guess -= step;
            }
            if (guess == -1) {
                guess += step / 2;
                step = step / 10;
            }
        }
        return new Double(guess);
    }

    public static long interval(Date date1, Date date2, int basis, char mark) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date1);
        int m1 = calendar.get(Calendar.MONTH) + 1;
        int y1 = calendar.get(Calendar.YEAR);
        int d1 = calendar.get(Calendar.DAY_OF_MONTH);
        calendar.setTime(date2);
        int m2 = calendar.get(Calendar.MONTH) + 1;
        int y2 = calendar.get(Calendar.YEAR);
        int d2 = calendar.get(Calendar.DAY_OF_MONTH);
        if (mark == 'd') {
            if (basis == 0 || basis == 4) {  //每月按30天算

                return ((y2 - y1) * 12 + m2 - m1) * 30 + d2 - d1;
            } else {  //按实际天数算
                long day = 24 * 3600 * 1000;
                long interval = date2.getTime() / day - date1.getTime() / day;
                return interval;
            }
        } else if (mark == 'm') {
            int m = (y2 - y1) * 12 + m2 - m1;
            if (d1 > d2) m -= 1;
            return m;
        }
        return 0;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值