Microsoft.VisualBasic.Financial.Rate with error "The rate cannot be calculated using the supplied arguments"

So, we have a rate calculator class in our ASP.NET4 web application that uses Microsoft.VisualBasic.Financial.Rate to calculate the nominal rate (based on inputs).

We noticed that at high NPer values ​​(total number of payment periods, for example 50 years x monthly payments = 600) the function throws an exception: it is not possible to calculate the rate using the arguments provided.

Searching around we didn't find any solutions, so I'm posting the solution here. The requirement for us was to maintain a function that, as much as possible, implements the same algorithm as above, since we needed to get exactly the same results.

+3


source to share


2 answers


Answering my question, for any future coders who come across this issue, we used dotPeek to decompile the module that created the following:

public static double Rate(double NPer, double Pmt, double PV, double FV = 0.0, DueDate Due = DueDate.EndOfPeriod, double Guess = 0.1)
{
  if (NPer <= 0.0)
    throw new ArgumentException(Utils.GetResourceString("Rate_NPerMustBeGTZero"));
  double Rate1 = Guess;
  double num1 = Financial.LEvalRate(Rate1, NPer, Pmt, PV, FV, Due);
  double Rate2 = num1 <= 0.0 ? Rate1 * 2.0 : Rate1 / 2.0;
  double num2 = Financial.LEvalRate(Rate2, NPer, Pmt, PV, FV, Due);
  int num3 = 0;
  do
  {
    if (num2 == num1)
    {
      if (Rate2 > Rate1)
        Rate1 -= 1E-05;
      else
        Rate1 -= -1E-05;
      num1 = Financial.LEvalRate(Rate1, NPer, Pmt, PV, FV, Due);
      if (num2 == num1)
        throw new ArgumentException(Utils.GetResourceString("Financial_CalcDivByZero"));
    }
    double Rate3 = Rate2 - (Rate2 - Rate1) * num2 / (num2 - num1);
    double num4 = Financial.LEvalRate(Rate3, NPer, Pmt, PV, FV, Due);
    if (Math.Abs(num4) < 1E-07)
      return Rate3;
    double num5 = num4;
    num1 = num2;
    num2 = num5;
    double num6 = Rate3;
    Rate1 = Rate2;
    Rate2 = num6;
    checked { ++num3; }
  }
  while (num3 <= 39);
  throw new ArgumentException(Utils.GetResourceString("Financial_CannotCalculateRate"));
}

private static double LEvalRate(double Rate, double NPer, double Pmt, double PV, double dFv, DueDate Due)
{
  if (Rate == 0.0)
    return PV + Pmt * NPer + dFv;
  double num1 = Math.Pow(Rate + 1.0, NPer);
  double num2 = Due == DueDate.EndOfPeriod ? 1.0 : 1.0 + Rate;
  return PV * num1 + Pmt * num2 * (num1 - 1.0) / Rate + dFv;
}

      



We can see that an error is thrown if num3 is exceeded, since it has a hard limit of 39. We removed the code a bit and increased the limit to 100:

private static double CalculateUpfrontNominalRate(double numberOfPeriods, double payment, double presentValue, double futureValue = 0.0, DueDate Due = DueDate.EndOfPeriod, double Guess = 0.1)
    {
        if (numberOfPeriods <= 0.0)
        {
            throw new ArgumentException("CalculateUpfrontNominalRate: Number of periods must be greater than zero");
        }

        var rateUpperBoundary = Guess;
        var lEvalRate1 = LEvalRate(rateUpperBoundary, numberOfPeriods, payment, presentValue, futureValue, Due);
        var rateLowerBoundary = lEvalRate1 <= 0.0 ? rateUpperBoundary * 2.0 : rateUpperBoundary / 2.0;
        var lEvalRate2 = LEvalRate(rateLowerBoundary, numberOfPeriods, payment, presentValue, futureValue, Due);

        for (var i = 0; i < 100; i++)
        {
            if (lEvalRate2 == lEvalRate1)
            {
                if (rateLowerBoundary > rateUpperBoundary)
                    rateUpperBoundary -= 1E-05;
                else
                    rateUpperBoundary -= -1E-05;

                lEvalRate1 = LEvalRate(rateUpperBoundary, numberOfPeriods, payment, presentValue, futureValue, Due);
                if (lEvalRate2 == lEvalRate1)
                {
                    throw new ArgumentException("CalculateUpfrontNominalRate: Inputs will cause a divsion by zero");
                }
            }

            double temporaryRate = rateLowerBoundary - (rateLowerBoundary - rateUpperBoundary) * lEvalRate2 / (lEvalRate2 - lEvalRate1);
            double lEvalRate3 = LEvalRate(temporaryRate, numberOfPeriods, payment, presentValue, futureValue, Due);

            if (Math.Abs(lEvalRate3) < 1E-07)
            {
                return temporaryRate;
            }

            lEvalRate1 = lEvalRate2;
            lEvalRate2 = lEvalRate3;
            rateUpperBoundary = rateLowerBoundary;
            rateLowerBoundary = temporaryRate;
        }

        throw new ArgumentException("CalculateUpfrontNominalRate: The maximum number of iterations has been exceeded, unable to calculate rate");
    }

    private static double LEvalRate(double Rate, double NPer, double Pmt, double PV, double dFv, DueDate Due)
    {
        if (Rate == 0.0)
            return PV + Pmt * NPer + dFv;
        double num1 = Math.Pow(Rate + 1.0, NPer);
        double num2 = Due == DueDate.EndOfPeriod ? 1.0 : 1.0 + Rate;
        return PV * num1 + Pmt * num2 * (num1 - 1.0) / Rate + dFv;
    }

      

+3


source


Changing the number of iterative calculations will not solve the problem for all cases

To calculate the sample you posted, the speed algorithm did not find a speed within 40 iterations that was correct up to 7 decimal places (with an accuracy of 7 decimal places).

This is the program code from Vb.net financial functions that you decompiled



If so, the programmer did a poor job of coding the RATE function with the Secant method

There are much better algorithms to use to find interest rates than the lousy Secant method

If you think you have solved the problem by changing the iterations to 100, try checking the code with different data and see if you can get RATE in all instances.

+1


source







All Articles