You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
196 lines
5.5 KiB
196 lines
5.5 KiB
package com.fr.plugin; /** |
|
* @file: PACKAGE_NAME.IRR |
|
* @desc: |
|
* @date:2020/7/7 16:57 |
|
* @author:yanghui |
|
**/ |
|
|
|
import com.fr.general.FArray; |
|
import com.fr.general.GeneralUtils; |
|
import com.fr.intelli.record.Focus; |
|
import com.fr.intelli.record.Original; |
|
import com.fr.plugin.context.PluginContexts; |
|
import com.fr.record.analyzer.EnableMetrics; |
|
import com.fr.script.AbstractFunction; |
|
import com.fr.stable.StringUtils; |
|
import com.fr.stable.fun.Authorize; |
|
|
|
import java.math.BigDecimal; |
|
|
|
@EnableMetrics |
|
@Authorize(callSignKey = FinanceFunctionConstants.PLUGIN_ID) |
|
public class IRR extends AbstractFunction { |
|
|
|
private static final long serialVersionUID = 7634415917398642321L; |
|
private static final String ERROR_VALUE = "#NUM!"; |
|
|
|
|
|
@Override |
|
@Focus(id = "com.fr.plugin.function.finance", text = "Plugin-Test_Function_Finance", source = Original.PLUGIN) |
|
public Object run(Object[] args) { |
|
|
|
if (PluginContexts.currentContext().isAvailable()) { |
|
return cal(args); |
|
} else { |
|
return "插件未激活,请购买使用"; |
|
} |
|
|
|
} |
|
|
|
private Object cal(Object[] args) { |
|
try{ |
|
if(1 == args.length){ |
|
return run( transArr( (FArray) args[0] ) ); |
|
}else if(2 == args.length){ |
|
return run( transArr( (FArray) args[0] ), trans( args[1] ) ); |
|
}else if(3==args.length){ |
|
return "参数个数不合格!"; |
|
} |
|
else{ |
|
FArray<BigDecimal> cashflow = new FArray<BigDecimal>(); |
|
BigDecimal guess = new BigDecimal(0.1d); |
|
for (int i = 0; i < args.length; i++) { |
|
if(args[i] == null|| StringUtils.isBlank(args[i].toString())){ |
|
continue; |
|
} |
|
BigDecimal var = new BigDecimal(GeneralUtils.objectToString(args[i]).trim()); |
|
|
|
if ((var.compareTo(BigDecimal.ONE) <1) && (var.compareTo(BigDecimal.ZERO)>-1)) { |
|
guess = var; |
|
}else { |
|
cashflow.add(var); |
|
} |
|
} |
|
return run(cashflow,guess); |
|
} |
|
}catch(Exception e){ |
|
System.out.println(e); |
|
} |
|
return ERROR_VALUE; |
|
} |
|
|
|
/** |
|
* 将其他类型的数字转换为大数(保证精度) |
|
* @param ele |
|
* @return |
|
*/ |
|
private static BigDecimal trans(Object ele){ |
|
try{ |
|
String val = GeneralUtils.objectToString(ele); |
|
return new BigDecimal(val.trim()); |
|
}catch(Exception e){ |
|
throw new RuntimeException(e); |
|
} |
|
} |
|
|
|
/** |
|
* 将数组转换为大数数组 |
|
* @param in |
|
* @return |
|
*/ |
|
private static FArray<BigDecimal> transArr(FArray in){ |
|
FArray<BigDecimal> rt = new FArray<BigDecimal>(); |
|
for(int i=0;i<in.length();i++){ |
|
Object ele = in.elementAt(i); |
|
if(ele == null|| StringUtils.isBlank(ele.toString())){ |
|
continue; |
|
} |
|
rt.add(trans(ele)); |
|
} |
|
return rt; |
|
} |
|
|
|
|
|
private static BigDecimal run(FArray<BigDecimal> cashflow){ |
|
return run( cashflow, new BigDecimal(0.1d) ); |
|
} |
|
|
|
private static BigDecimal run(FArray<BigDecimal> cashflow, BigDecimal guess){ |
|
BigDecimal maxrate = initRateMax(cashflow,guess); |
|
BigDecimal minrate = initRateMin(cashflow,guess); |
|
for( int i=0; i<Init_Max_Loop; i++ ){ |
|
BigDecimal testrate = minrate.add(maxrate).divide( new BigDecimal(2d) ); |
|
BigDecimal npv = NPV( cashflow, testrate ); |
|
if( npv.abs().compareTo(Accuracy) == LESS ){ |
|
guess = testrate; |
|
break; |
|
}else if( npv.compareTo(ZERO) == LESS ){ |
|
minrate = testrate; |
|
}else{ |
|
maxrate = testrate; |
|
} |
|
} |
|
//保留16位小数(足够精度) |
|
return guess.setScale(16,BigDecimal.ROUND_HALF_UP); |
|
} |
|
|
|
//最小精度 |
|
private static final BigDecimal Accuracy = new BigDecimal(0.00000001d); |
|
//最大循环次数,excel用的是20次,不过精度只到小数点后两位,而且不一定一定能算出值,为了尽可能保证算出结果,我增加到100次, |
|
private static final int Init_Max_Loop = 100; |
|
|
|
private static final BigDecimal ZERO = new BigDecimal(0); |
|
private static final BigDecimal ONE = new BigDecimal(1); |
|
private static final BigDecimal Z005 = new BigDecimal(0.005d); |
|
private static final BigDecimal Z2 = new BigDecimal(0.2d); |
|
|
|
private static final int GREATER = 1; |
|
private static final int LESS = -1; |
|
|
|
/** |
|
* 生成一个使NPV为负数的R作为内部收益率下限值 |
|
* @param cashflow |
|
* @param guess |
|
* @return |
|
*/ |
|
private static BigDecimal initRateMin(FArray<BigDecimal> cashflow, BigDecimal guess){ |
|
for( int i=0; i<Init_Max_Loop; i++ ){ |
|
BigDecimal npv = NPV( cashflow, guess ); |
|
|
|
if( npv.compareTo(ZERO) == LESS ){ |
|
return guess; |
|
} |
|
BigDecimal step = guess.abs().multiply( Z2 ); |
|
guess = guess.add( step.compareTo( Z005 ) == LESS ? Z005 : step ); |
|
} |
|
return guess; |
|
} |
|
|
|
/** |
|
* 生成一个使NPV为正数的R作为内部收益率的上限制 |
|
* @param cashflow |
|
* @param guess |
|
* @return |
|
*/ |
|
private static BigDecimal initRateMax(FArray<BigDecimal> cashflow, BigDecimal guess){ |
|
for( int i=0; i<Init_Max_Loop; i++ ){ |
|
BigDecimal npv = NPV( cashflow, guess ); |
|
|
|
if( npv.compareTo(ZERO) == GREATER ){ |
|
return guess; |
|
} |
|
BigDecimal step = guess.abs().multiply( Z2 ); |
|
guess = guess.subtract( step.compareTo( Z005 ) == LESS ? Z005 : step ); |
|
} |
|
return guess; |
|
} |
|
|
|
/** |
|
* 算NPV |
|
* @param cashflow |
|
* @param rate |
|
* @return |
|
*/ |
|
private static BigDecimal NPV(FArray<BigDecimal> cashflow, BigDecimal rate){ |
|
BigDecimal npv = ZERO; |
|
BigDecimal rpowj = ONE;//(1+r)^0 |
|
BigDecimal radd1 = rate.add(ONE);//1+r |
|
for( int j=0; j<cashflow.length(); j++ ){ |
|
BigDecimal valuej = cashflow.elementAt(j); |
|
npv = npv.add( valuej.divide( rpowj, 10, BigDecimal.ROUND_HALF_DOWN ) );// vj / (1+r)^j |
|
rpowj = rpowj.multiply(radd1); // (1+r)^j |
|
//npv += cashflow.elementAt(j) / Math.pow( 1+rate, j ); |
|
} |
|
return npv; |
|
} |
|
}
|
|
|