Created
February 9, 2022 08:35
-
-
Save dylansalim3/136fdbdbd54a3c8851801ad8d239e8ea to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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