Skip to content

Instantly share code, notes, and snippets.

@dylansalim3
Created February 9, 2022 08:35
Show Gist options
  • Save dylansalim3/136fdbdbd54a3c8851801ad8d239e8ea to your computer and use it in GitHub Desktop.
Save dylansalim3/136fdbdbd54a3c8851801ad8d239e8ea to your computer and use it in GitHub Desktop.
import java.util.Date;
public class XirrEngineBiSection {
// variables settings
private final double DAY_IN_YEAR = 365.0; // XIRR use default 365 in a year. No leap year consideration
private final double DEFAULT_XIRR = -99999;
private final int MAX_XIRR_TRY = 1000;
// main function
public double calculateBiSectionXirr(double[] cashFlow, Date[] cashFlowDate) {
double tolerance = Math.pow(10, -8);
double xirrDiscountRate01 = -1 + tolerance;
double xirrDiscountRate02 = -1 + 2 * tolerance;
// System.out.println(xirrDiscountRate02);
int i = 1;
double XIRR = DEFAULT_XIRR;
double xirrCalculationPoint01 = 0;
double xirrCalculationPoint02 = 0;
double nextGuess = 0;
try {
// default checking to ensure, default negative XIRR so that, can start calculation
if (xirrDiscountRate01 <= -1) {
return DEFAULT_XIRR;
}
// System.out.println(xirrDiscountRate01);
xirrCalculationPoint01 = calculateCurrentPointNpv(cashFlow, cashFlowDate, xirrDiscountRate01);
// control looping, the attempt to calculate.
while (i < MAX_XIRR_TRY) {
System.out.println("try " + i);
if (xirrDiscountRate02 <= -1) {
return DEFAULT_XIRR;
}
// System.out.println(xirrCalculationPoint02);
xirrCalculationPoint02 = calculateCurrentPointNpv(cashFlow, cashFlowDate, xirrDiscountRate02);
// System.out.println(xirrDiscountRate01);
// System.out.println(xirrDiscountRate02);
// System.out.println(xirrCalculationPoint01);
// System.out.println(xirrCalculationPoint02);
// check if the slope touch the interection. If touched, the slope will be the XIRR
if (Math.abs(xirrCalculationPoint02) > tolerance) {
// check if the calculation is moving forward
// if calculation using slope is not moving, this calculation will forever stuck here, return default
if (xirrCalculationPoint02 == xirrCalculationPoint01 || xirrDiscountRate02 == xirrDiscountRate01) {
return DEFAULT_XIRR;
}
// get the next guess. This is by calculating the slope.
nextGuess = nextGuessRate(xirrDiscountRate01, xirrCalculationPoint01, xirrDiscountRate02, xirrCalculationPoint02);
xirrDiscountRate01 = xirrDiscountRate02;
xirrDiscountRate02 = nextGuess;
xirrCalculationPoint01 = xirrCalculationPoint02;
} else {
if (new Double(xirrDiscountRate02).isInfinite() && xirrDiscountRate02 > 0) {
return DEFAULT_XIRR;
} else {
XIRR = xirrDiscountRate02;
// deannualise if the effective cashflow (exclude the 0 start) is > 365 days
double xirrPeriod = dateDiffFromCashFlowStartToEndDate(cashFlow, cashFlowDate);
if (xirrPeriod < DAY_IN_YEAR) {
XIRR = deAnnualiseXirr(XIRR, xirrPeriod);
}
XIRR = XIRR * 100;
}
break;
}
i++;
}
} catch (Exception e) {
return DEFAULT_XIRR;
}
return XIRR;
}
private double deAnnualiseXirr(double xirr, double xirrPeriod) {
return Math.pow((1 + xirr), (xirrPeriod / DAY_IN_YEAR)) - 1;
}
private double nextGuessRate(double x1, double y1, double x2, double y2) {
double calculationSlope = (y2 - y1) / (x2 - x1);
System.out.println(x1);
System.out.println(x2);
System.out.println(y1);
System.out.println(y2);
System.out.println(calculationSlope);
return ((x1 * calculationSlope - y1) / calculationSlope);
}
private double dateDiff(Date dateEnd, Date dateStart) {
return ((dateEnd.getTime() - dateStart.getTime()) / (24 * 60 * 60 * 1000)); // use time to calculate for accuracy.
}
private double calculateCurrentPointNpv(double[] cashFlow, Date[] cashFlowDate, double rate) {
double residual = 0;
double dtDiff = 0;
double currentResidual = 0;
try {
for (int i = 0; i < cashFlow.length; i++) {
dtDiff = dateDiff(cashFlowDate[i], cashFlowDate[0]);
currentResidual = cashFlow[i] / Math.pow((1 + rate), (dtDiff / 365));
residual += currentResidual;
}
} catch (Exception e) {
residual = -1;
}
return residual;
}
private double dateDiffFromCashFlowStartToEndDate(double[] cashFlow, Date[] cashFlowDate) {
int cashflowIndex = 0;
double cashflowStart = cashFlow[cashflowIndex];
double dateDifference = 0;
while (cashflowStart == 0 && cashflowIndex < MAX_XIRR_TRY) {
cashflowIndex++;
if (cashflowIndex >= cashFlow.length) {
break;
}
cashflowStart = cashFlow[cashflowIndex];
}
if (cashflowStart != 0) {
Date dateStart = cashFlowDate[cashflowIndex];
Date dateEnd = cashFlowDate[(cashFlowDate.length - 1)];
dateDifference = ((dateEnd.getTime() - dateStart.getTime()) / (24 * 60 * 60 * 1000));
}
return dateDifference;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment