值得注意的是
- Duration计算的结果和excel 有差异,但是同样都是用的麦考利公式做了实现,并且进行了修正(最后和用户讨论,这个结果是可以接收的)。
- Duration计算的天数同样没有全部实现,具体参考excel 中的不同。
- 其余暂时没有发现具体问题。
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;
}
}