Algorithm idea and implementation of XIRR function in financial industry, java/plsql

I Introduction to internal rate of return (IRR)

The Internal Rate of Return (IRR) is when NPV (net present value) is equal to zero Discount rate . To calculate the internal rate of return, several discount rates are used for trial calculation until the discount rate with net present value equal to or close to zero is found. Internal rate of return is the expected rate of return of an investment project.

It is the desired rate of return for an investment. The larger the index, the better. Generally speaking, when the internal rate of return is greater than or equal to the benchmark rate of return of the market, the project is feasible, but it will cause losses anyway. The sum of the product of cash flow and discount rate in each year of the investment project is the net present value of the project. When the net present value is zero, the discount rate is the internal rate of return of the project.

XIRR needs to be accurate to specific days, which is more accurate than IRR.

II Algorithmic thought

Algorithm formula:

 

Ci is the paid in cash flow of each transaction

di is the date when each paid in cash flow of investment occurs

d1 is the first paid in date

Rate is internal gross rate of return - Gross IRR

The same as the Excel algorithm formula, Rate is the discount Rate when XNPV is 0, and the sum of this formula is XNPV.

The simplest algorithm idea to find out the Rate when XNPV=0 is to find in half. Give a guess value and bring it into the formula for continuous iteration. Finally, find the required value. This value needs to meet the verification of the formula. It can make the accuracy of XNPV less than 1E-15, that is, it is approximately 0.

III code implementation

1.JAVA implementation

public double xirr(double [] values, Date [] dates, double guess)
	{
		double 	result 					= Double.NaN;			
		double 	irrGuess 				= 0.1D;	
		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 = getXirr(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_accuracy;
					wasHi = false;
				}
			} else 
			{
				irrGuess /= 2;
				irr -= irrGuess;
				wasHi = true;
			}

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

	public double getXirr(final double guess, double [] values, Date [] dates)
	{
		double result = 0.0D;
		
		for (int i = 0; i < dates.length; i++) 
		{
			result += values[i] / Math.pow(1 + guess, DateUtil.getIntervalDays(dates[i], dates[0]) / FULL_YEAR_DAYS);
		}
		return result;
	}

2. Rewrite to PLSQL function

CREATE OR REPLACE FUNCTION "FIND_XIRR" (p_date_array   in p_date_array,
                                  p_amount_array in t_amount_array,
                                  p_guess        in number default 0)
  RETURN NUMBER IS

BEGIN
  declare
    rtn_err    number := -9999999;
    step number := 0;
    v_sum       number;
    guess  number := 0.5;
    wasHi      number := -1;
    d       number := 0;
    returnd number := 0;

  begin

    d := 0;
    v_sum := 0;
    for i in 1 .. p_amount_array.count loop
      v_sum := v_sum +
              p_amount_array(i) /
              power((1 + d), (p_date_array(i) - p_date_array(1)) / 365);
    end loop;

    if (v_sum > 0) then
      d := guess;
    else
      d := (-1) * guess;
    end if;

    step := 100;

    loop
      v_sum := 0;

      for i in 1 .. p_amount_array.count loop
        v_sum := v_sum +
                p_amount_array(i) /
                power((1 + d), (p_date_array(i) - p_date_array(1)) / 365);
      end loop;
      if (v_sum > 0) then
        if (wasHi > 0) then
          guess := guess / 2;
        end if;
        d := d + guess;
        if (wasHi > 0) then
          guess := guess - 0.000000000000001;
          wasHi     := -1;
        end if;
      else
        guess := guess / 2;
        d      := d - guess;
        wasHi     := 0;
      end if;

      if (abs(v_sum) < 0.000000000000001) then
        returnd := d;
        exit;
      end if;
      step := step - 1;
      if (step = 0) then
        returnd := d;
        exit;
      end if;
    end loop;

    return returnd;
  EXCEPTION
    WHEN OTHERS THEN
      return rtn_err;
  end;
END FIND_XIRR;

It should be noted that the input cash flow array must meet a positive cash flow and a negative cash flow. The date type is in YYYYMMDD format, which does not meet the need for type conversion. For cash flow, reasonable test data should be input, otherwise the XIRR value that meets the jump out cycle condition cannot be found.

Test data:

P_DATE_ARRAY[2016/9/26,2018/7/2,2020/3/31]

P_AMOUNT_ARRAY[1000000000,18500000,-1249940250]

RETURN_XIRR:0.06057268

There is almost no error with the calculated value in Excel.

 

 

Keywords: Java PostgreSQL SQL

Added by sqishy on Sat, 25 Dec 2021 00:45:40 +0200