//+------------------------------------------------------------------+
//|                                                        Gamma.mqh |
//|                             Copyright 2000-2024, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#include "Normal.mqh"

const double DoubleEpsilon=1.11022302462515654042E-16;
const double LogMax=7.09782712893383996732E2;
//+------------------------------------------------------------------+
//| Inverse of the incomplete Gamma integral                         |
//+------------------------------------------------------------------+
double MathInverseGammaIncomplete(const double a,const double y)
  {
//--- bound the solution
   double x0 = DBL_MAX;
   double yl = 0;
   double x1 = 0;
   double yh = 1.0;
   double dithresh=5.0*DoubleEpsilon;
//--- approximation to inverse function
   double d=1.0/(9.0*a);
   int err_code=0;
   double q_normal=MathQuantileNormal(y,0,1,true,false,err_code);
   double yy=(1.0-d-q_normal*MathSqrt(d));
   double x=a*yy*yy*yy;
   double lgm=MathGammaLog(a);
   for(int i=0; i<10; i++)
     {
      if(x>x0 || x<x1)
         break;
      yy=1.0-MathGammaIncomplete(x,a);
      if(yy<yl || yy>yh)
         break;
      if(yy<y)
        {
         x0 = x;
         yl = yy;
        }
      else
        {
         x1 = x;
         yh = yy;
        }
      //--- compute the derivative of the function at this point
      d=(a-1.0)*MathLog(x)-x-lgm;
      if(d<-LogMax)
         break;
      d=-MathExp(d);
      //--- compute the step to the next approximation of x
      d=(yy-y)/d;
      if(MathAbs(d/x)<DoubleEpsilon)
         return (x);
      x=x-d;
     }
//--- resort to interval halving if Newton iteration did not converge. 
   d=0.0625;
   if(x0==DBL_MAX)
     {
      if(x<=0.0)
         x=1.0;
      while(x0==DBL_MAX && MathIsValidNumber(x))
        {
         x=(1.0+d)*x;
         yy=1.0-MathGammaIncomplete(x,a);
         if(yy<y)
           {
            x0 = x;
            yl = yy;
            break;
           }
         d=d+d;
        }
     }
   d=0.5;
   double dir=0;
   for(int i=0; i<400; i++)
     {
      double t=x1+d *(x0-x1);
      if(!MathIsValidNumber(t))
         break;
      x=t;
      yy=1.0-MathGammaIncomplete(x,a);
      lgm=(x0-x1)/(x1+x0);
      if(MathAbs(lgm)<dithresh)
         break;
      lgm=(yy-y)/y;
      if(MathAbs(lgm)<dithresh)
         break;
      if(x<=0.0)
         break;
      if(yy>=y)
        {
         x1 = x;
         yh = yy;
         if(dir<0)
           {
            dir=0;
            d=0.5;
           }
         else
         if(dir>1)
            d=0.5*d+0.5;
         else
            d=(y-yl)/(yh-yl);
         dir+=1;
        }
      else
        {
         x0 = x;
         yl = yy;
         if(dir>0)
           {
            dir=0;
            d=0.5;
           }
         else
         if(dir<-1)
            d=0.5*d;
         else
            d=(y-yl)/(yh-yl);
         dir-=1;
        }
     }
   if(x==0.0 || !MathIsValidNumber(x))
     {
      Print("Errors in an arithmetic, casting, or conversion operation.");
      return(QNaN);
     }
//---
   return(x);
  }
//+------------------------------------------------------------------+
//| Gamma probability density function (PDF)                         |
//+------------------------------------------------------------------+
//| The function returns the probability density function of         |
//| of the Gamma distribution with shape parameters a and b.         |
//|                                                                  |
//| Arguments:                                                       |
//| x          : Random variable                                     |
//| a          : Shape                                               |
//| b          : Scale                                               |
//| log_mode   : Logarithm mode flag, if true it returns Log values  |
//| error_code : Variable for error code                             |
//|                                                                  |
//| Return value:                                                    |
//| The probability density evaluated at x.                          |
//+------------------------------------------------------------------+
double MathProbabilityDensityGamma(const double x,const double a,const double b,const bool log_mode,int &error_code)
  {
//--- check parameters
   if(!MathIsValidNumber(x) || !MathIsValidNumber(a) || !MathIsValidNumber(b))
     {
      error_code=ERR_ARGUMENTS_NAN;
      return QNaN;
     }
//--- a and b must be positive
   if(a<=0 || b<=0)
     {
      error_code=ERR_ARGUMENTS_INVALID;
      return QNaN;
     }
   error_code=ERR_OK;
//--- check negative x
   if(x<=0)
      return TailLog0(true,log_mode);
//--- calculate log Gamma density
   double log_result=(a-1.0)*MathLog(x)-(x/b)-MathGammaLog(a)-a*MathLog(b);
   if(log_mode==true)
      return(log_result);
//--- return Gamma density
   return MathExp(log_result);
  }
//+------------------------------------------------------------------+
//| Gamma probability density function (PDF)                         |
//+------------------------------------------------------------------+
//| The function returns the probability density function of         |
//| of the Gamma distribution with shape parameters a and b.         |
//|                                                                  |
//| Arguments:                                                       |
//| x          : Random variable                                     |
//| a          : Shape                                               |
//| b          : Scale                                               |
//| error_code : Variable for error code                             |
//|                                                                  |
//| Return value:                                                    |
//| The probability density evaluated at x.                          |
//+------------------------------------------------------------------+
double MathProbabilityDensityGamma(const double x,const double a,const double b,int &error_code)
  {
   return MathProbabilityDensityGamma(x,a,b,false,error_code);
  }
//+------------------------------------------------------------------+
//| Gamma probability density function (PDF)                         |
//+------------------------------------------------------------------+
//| The function calculates the Gamma probability density function   |
//| with parameters a and b for values in x[] array.                 |
//|                                                                  |
//| Arguments:                                                       |
//| x          : Array with random variables                         |
//| a          : Shape                                               |
//| b          : Scale                                               |
//| log_mode   : Logarithm mode flag,if true it calculates Log values|
//| result     : Output array for calculated values                  |
//|                                                                  |
//| Return value:                                                    |
//| true if successful, otherwise false.                             |
//+------------------------------------------------------------------+
bool MathProbabilityDensityGamma(const double &x[],const double a,const double b,const bool log_mode,double &result[])
  {
//--- check parameters
   if(!MathIsValidNumber(a) || !MathIsValidNumber(b))
      return false;
//--- a and b must be positive
   if(a<=0 || b<=0)
      return false;

   int data_count=ArraySize(x);
   if(data_count==0)
      return false;

   int error_code=0;
   ArrayResize(result,data_count);
   for(int i=0; i<data_count; i++)
     {
      double x_arg=x[i];

      if(!MathIsValidNumber(x_arg))
         return false;

      if(x_arg>0)
        {
         //--- calculate log Gamma density
         double log_result=(a-1.0)*MathLog(x_arg)-(x_arg/b)-MathGammaLog(a)-a*MathLog(b);
         if(log_mode==true)
            result[i]=log_result;
         else
            result[i]=MathExp(log_result);
        }
      else
         result[i]=TailLog0(true,log_mode);
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Gamma probability density function (PDF)                         |
//+------------------------------------------------------------------+
//| The function calculates the Gamma probability density function   |
//| with parameters a and b for values from x[] array.               |
//|                                                                  |
//| Arguments:                                                       |
//| x          : Array with random variables                         |
//| a          : Shape                                               |
//| b          : Scale                                               |
//| result     : Array with calculated values                        |
//|                                                                  |
//| Return value:                                                    |
//| true if successful, otherwise false.                             |
//+------------------------------------------------------------------+
bool MathProbabilityDensityGamma(const double &x[],const double a,const double b,double &result[])
  {
   return MathProbabilityDensityGamma(x,a,b,false,result);
  }
//+------------------------------------------------------------------+
//| Gamma cumulative distribution function (CDF)                     |
//+------------------------------------------------------------------+
//| The function returns the cumulative distribution function of the |
//| Gamma distribution with parameters a and b.                      |
//|                                                                  |
//| Arguments:                                                       |
//| x          : The desired quantile                                |
//| a          : Shape                                               |
//| b          : Scale                                               |
//| tail       : Flag to calculate lower tail                        |
//| log_mode   : Logarithm mode flag,if true it calculates Log values|
//| error_code : Variable for error code                             |
//|                                                                  |
//| Return value:                                                    |
//| The value of the Gamma cumulative distribution function          |
//| with parameters a and b, evaluated at x.                         |
//+------------------------------------------------------------------+
double MathCumulativeDistributionGamma(const double x,const double a,const double b,const bool tail,const bool log_mode,int &error_code)
  {
//--- check NaN
   if(!MathIsValidNumber(x) || !MathIsValidNumber(a) || !MathIsValidNumber(b))
     {
      error_code=ERR_ARGUMENTS_NAN;
      return QNaN;
     }
//--- a and b must be positive
   if(a<=0 || b<=0)
     {
      error_code=ERR_ARGUMENTS_INVALID;
      return QNaN;
     }

   error_code=ERR_OK;
//--- check x
   if(x<=0)
      return TailLog0(tail,log_mode);
//--- calculate probability using Incomplete Gamma function and take into account round-off errors
   double cdf=MathMin(MathGammaIncomplete(x/b,a),1.0);
   return TailLogValue(cdf,tail,log_mode);
  }
//+------------------------------------------------------------------+
//| Gamma cumulative distribution function (CDF)                     |
//+------------------------------------------------------------------+
//| The function returns the cumulative distribution function of the |
//| Gamma distribution with parameters a and b.                      |
//|                                                                  |
//| Arguments:                                                       |
//| x          : The desired quantile                                |
//| a          : Shape                                               |
//| b          : Scale                                               |
//| error_code : Variable for error code                             |
//|                                                                  |
//| Return value:                                                    |
//| The value of the Gamma cumulative distribution function          |
//| with parameters a and b, evaluated at x.                         |
//+------------------------------------------------------------------+
double MathCumulativeDistributionGamma(const double x,const double a,const double b,int &error_code)
  {
   return MathCumulativeDistributionGamma(x,a,b,true,false,error_code);
  }
//+------------------------------------------------------------------+
//| Gamma cumulative distribution function (CDF)                     |
//+------------------------------------------------------------------+
//| The function calculates the values of the Gamma cumulative       |
//| distribution function with given a and b for values in x[] array.|
//|                                                                  |
//| Arguments:                                                       |
//| x          : Array with random variables                         |
//| a          : Shape                                               |
//| b          : Scale                                               |
//| tail       : Flag to calculate lower tail                        |
//| log_mode   : Logarithm mode flag,if true it calculates Log values|
//| resut      : Output array for calculated values                  |
//|                                                                  |
//| Return value:                                                    |
//| true if successul, otherwise false.                              |
//+------------------------------------------------------------------+
bool MathCumulativeDistributionGamma(const double &x[],const double a,const double b,const bool tail,const bool log_mode,double &result[])
  {
//--- check NaN
   if(!MathIsValidNumber(a) || !MathIsValidNumber(b))
      return false;
//--- a and b must be positive
   if(a<=0 || b<=0)
      return false;

   int data_count=ArraySize(x);
   if(data_count==0)
      return false;

   int error_code=0;
   ArrayResize(result,data_count);

   for(int i=0; i<data_count; i++)
     {
      double x_arg=x[i];

      if(!MathIsValidNumber(x_arg))
         return false;

      if(x_arg<=0)
         result[i]=TailLog0(tail,log_mode);
      else
        {
         //--- calculate probability using Incomplete Gamma function and take into account round-off errors
         double cdf=MathMin(MathGammaIncomplete(x_arg/b,a),1.0);
         result[i]=TailLogValue(cdf,tail,log_mode);
        }
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Gamma cumulative distribution function (CDF)                     |
//+------------------------------------------------------------------+
//| The function calculates the values of the Gamma cumulative       |
//| distribution function with given a and b for values in x[] array.|
//|                                                                  |
//| Arguments:                                                       |
//| x          : Array with random variables                         |
//| a          : Shape                                               |
//| b          : Scale                                               |
//| result     : Output array for calculated values                  |
//|                                                                  |
//| Return value:                                                    |
//| true if successul, otherwise false.                              |
//+------------------------------------------------------------------+
bool MathCumulativeDistributionGamma(const double &x[],const double a,const double b,double &result[])
  {
   return MathCumulativeDistributionGamma(x,a,b,true,false,result);
  }
//+------------------------------------------------------------------+
//| Gamma distribution quantile function (inverse CDF)               |
//+------------------------------------------------------------------+
//| The function returns the inverse cumulative distribution         |
//| function of the Gamma distribution with parameters a and b       |
//| for the desired probability.                                     |
//|                                                                  |
//| Arguments:                                                       |
//| probability : The desired probability                            |
//| a           : Shape                                              |
//| b           : Scale                                              |
//| tail        : Flag to calculate lower tail                       |
//| log_mode    : Logarithm mode, if true it calculates Log values   |
//| error_code  : Variable for error code                            |
//|                                                                  |
//| Return value:                                                    |
//| The value of the inverse cumulative distribution function        |
//| of the Gamma distribution with parameters a and b.               |
//+------------------------------------------------------------------+
double MathQuantileGamma(const double probability,const double a,const double b,const bool tail,const bool log_mode,int &error_code)
  {
//--- case log probability==-inf
   if(log_mode==true && probability==QNEGINF)
     {
      error_code=ERR_OK;
      return 0.0;
     }
//--- check NaN
   if(!MathIsValidNumber(probability) || !MathIsValidNumber(a) || !MathIsValidNumber(b))
     {
      error_code=ERR_ARGUMENTS_NAN;
      return QNaN;
     }
//--- a and b must be positive
   if(a<=0 || b<=0)
     {
      error_code=ERR_ARGUMENTS_INVALID;
      return QNaN;
     }
//--- calculate real probability
   double prob=TailLogProbability(probability,tail,log_mode);
//--- check probability range
   if(prob<0.0 || prob>1.0)
     {
      error_code=ERR_ARGUMENTS_INVALID;
      return QNaN;
     }
   error_code=ERR_OK;
//--- case probability==0
   if(prob==0.0)
      return 0.0;
//--- case probability==1
   if(prob==1.0)
     {
      error_code=ERR_RESULT_INFINITE;
      return QPOSINF;
     }
//--- calculate quantile
   double quantile=MathInverseGammaIncomplete(a,1.0-prob)*b;
   if(!MathIsValidNumber(quantile))
      error_code=ERR_NON_CONVERGENCE;
//---
   return(quantile);
  }
//+------------------------------------------------------------------+
//| Gamma distribution quantile function (inverse CDF)               |
//+------------------------------------------------------------------+
//| The function returns the inverse cumulative distribution         |
//| function of the Gamma distribution with parameters a and b       |
//| for the desired probability.                                     |
//|                                                                  |
//| Arguments:                                                       |
//| probability : The desired probability                            |
//| a           : Shape                                              |
//| b           : Scale                                              |
//| error_code  : Variable for error code                            |
//|                                                                  |
//| Return value:                                                    |
//| The value of the inverse cumulative distribution function        |
//| of the Gamma distribution with parameters a and b.               |
//+------------------------------------------------------------------+
double MathQuantileGamma(const double probability,const double a,const double b,int &error_code)
  {
   return MathQuantileGamma(probability,a,b,true,false,error_code);
  }
//+------------------------------------------------------------------+
//| Gamma distribution quantile function (inverse CDF)               |
//+------------------------------------------------------------------+
//| The function returns the inverse cumulative distribution         |
//| function of the Gamma distribution with parameters a and b       |
//| for values from the probability[] array.                         |
//|                                                                  |
//| Arguments:                                                       |
//| probability : Array with probabilities                           |
//| a           : Shape                                              |
//| b           : Scale                                              |
//| tail        : Flag to calculate lower tail                       |
//| log_mode    : Logarithm mode,if true it calculates for Log values|
//| result      : Output array for calculated values                 |
//|                                                                  |
//| Return value:                                                    |
//| true if successful, otherwise false.                             |
//+------------------------------------------------------------------+
bool MathQuantileGamma(const double &probability[],const double a,const double b,const bool tail,const bool log_mode,double &result[])
  {
//--- check NaN
   if(!MathIsValidNumber(a) || !MathIsValidNumber(b))
      return false;
//--- a and b must be positive
   if(a<=0 || b<=0)
      return false;

   int data_count=ArraySize(probability);
   if(data_count==0)
      return false;

   int error_code=0;
   ArrayResize(result,data_count);

   const double eps=10E-18;
   double max_h=MathSqrt(eps);
   const int max_iterations=1000;

   for(int i=0; i<data_count; i++)
     {
      //--- calculate real probability
      double prob=TailLogProbability(probability[i],tail,log_mode);

      if(!MathIsValidNumber(prob))
         return false;

      //--- check probability range
      if(prob<0.0 || prob>1.0)
         return false;

      //--- case probability==0
      if(prob==0.0)
         result[i]=0.0;
      else
      //--- case probability==1
      if(prob==1.0)
         result[i]=QPOSINF;
      else
        {
         double quantile=MathInverseGammaIncomplete(a,1.0-prob)*b;
         if(MathIsValidNumber(quantile))
            result[i]=quantile;
         else
            return false;
        }
     }
   return true;
  }
//+------------------------------------------------------------------+
//| Gamma distribution quantile function (inverse CDF)               |
//+------------------------------------------------------------------+
//| The function returns the inverse cumulative distribution         |
//| function of the Gamma distribution with parameters a and b       |
//| for the desired probability.                                     |
//|                                                                  |
//| Arguments:                                                       |
//| probability : The desired probability                            |
//| a           : Shape                                              |
//| b           : Scale                                              |
//| error_code  : Variable for error code                            |
//|                                                                  |
//| Return value:                                                    |
//| true if successful, otherwise false.                             |
//+------------------------------------------------------------------+
bool MathQuantileGamma(const double &probability[],const double a,const double b,double &result[])
  {
   return MathQuantileGamma(probability,a,b,true,false,result);
  }
//+------------------------------------------------------------------+
//| Random variate from the Gamma distribution                       |
//+------------------------------------------------------------------+
//| Compute the random variable from the Gamma distribution          |
//| with parameters a and b.                                         |
//|                                                                  |
//| Arguments:                                                       |
//| a          : Shape                                               |
//| b          : Scale                                               |
//| error_code : Variable for error code                             |
//|                                                                  |
//| Return value:                                                    |
//| The random value with Gamma distribution.                        |
//+------------------------------------------------------------------+
//| Author: Robert Kern                                              |
//+------------------------------------------------------------------+
double MathRandomGamma(const double a,const double b)
  {
   double bb,c,U,V,X=0,Y;
//--- check shape
   if(a==1.0)
     {
      //--- exponential
      return -MathLog(1.0-MathRandomNonZero());
     }
   else
   if(a<1.0)
     {
      for(;;)
        {
         U=MathRandomNonZero();
         //--- exponential
         V=-MathLog(1.0-MathRandomNonZero());

         if(U<=1.0-a)
           {
            X=MathPow(U,1.0/a);
            if(X<=V)
               return b*X;
           }
         else
           {
            Y = -MathLog((1-U)/a);
            X = MathPow(1.0 - a + a*Y, 1.0/a);
            if(X<=(V+Y))
               return(b*X);
           }
        }
     }
   else
     {
      bb= a-1.0/3.0;
      c = 1.0/MathSqrt(9*bb);
      for(;;)
        {
         do
           {
            //--- generate normal random variate
            double f,x1,x2,r2;
            do
              {
               x1=2.0*MathRandomNonZero()-1.0;
               x2=2.0*MathRandomNonZero()-1.0;
               r2=x1*x1+x2*x2;
              }
            while(r2>=1.0 || r2==0.0);
            //--- Box-Muller transform
            f=MathSqrt(-2.0*MathLog(r2)/r2);
            X=f*x2;

            V=1.0+c*X;
           }
         while(V<=0.0);

         V = V*V*V;
         U = MathRandomNonZero();

         if(U<1.0-0.0331*(X*X)*(X*X))
            return(bb*V*b);

         if(MathLog(U)<0.5*X*X+bb*(1.0-V+MathLog(V)))
            return(bb*V*b);
        }
     }
   return(X*b);
  }
//+------------------------------------------------------------------+
//| Random variate from the Gamma distribution                       |
//+------------------------------------------------------------------+
//| Compute the random variable from the Gamma distribution          |
//| with parameters a and b.                                         |
//|                                                                  |
//| Arguments:                                                       |
//| a          : Shape                                               |
//| b          : Scale                                               |
//| error_code : Variable for error code                             |
//|                                                                  |
//| Return value:                                                    |
//| The random value with Gamma distribution.                        |
//+------------------------------------------------------------------+
double MathRandomGamma(const double a,const double b,int &error_code)
  {
//--- check NaN
   if(!MathIsValidNumber(a) || !MathIsValidNumber(b))
     {
      error_code=ERR_ARGUMENTS_NAN;
      return QNaN;
     }
//--- a and b must be positive
   if(a<=0 || b<=0)
     {
      error_code=ERR_ARGUMENTS_INVALID;
      return QNaN;
     }

   error_code=ERR_OK;
   return MathRandomGamma(a,b);
  }
//+------------------------------------------------------------------+
//| Random variate from the Gamma distribution                       |
//+------------------------------------------------------------------+
//| The function generates random variables from the Gamma           |
//| distribution with parameters a and b.                            |
//|                                                                  |
//| Arguments:                                                       |
//| a           : First shape parameter (a>0)                        |
//| b           : Second shape parameter (b>0)                       |
//| data_count  : Number of values needed                            |
//| result      : Output array for random values                     |
//|                                                                  |
//| Return value:                                                    |
//| true if successful, otherwise false.                             |
//+------------------------------------------------------------------+
bool MathRandomGamma(const double a,const double b,const int data_count,double &result[])
  {
   if(data_count<=0)
      return false;
//--- check NaN
   if(!MathIsValidNumber(a) || !MathIsValidNumber(b))
      return false;
//--- a and b must be positive
   if(a<=0 || b<=0)
      return false;
//--- prepare output array and calculate values
   ArrayResize(result,data_count);
   for(int i=0; i<data_count; i++)
      result[i]=MathRandomGamma(a,b);
   return true;
  }
//+------------------------------------------------------------------+
//| Gamma distribution moments                                       |
//+------------------------------------------------------------------+
//| The function calculates 4 first moments of Gamma distribution    |
//| with parameters a and b.                                         |
//|                                                                  |
//| Arguments:                                                       |
//| a          : Shape                                               |
//| b          : Scale                                               |
//| mean       : Variable for mean value (1st moment)                |
//| variance   : Variable for variance value (2nd moment)            |
//| skewness   : Variable for skewness value (3rd moment)            |
//| kurtosis   : Variable for kurtosis value (4th moment)            |
//| error_code : Variable for error code                             |
//|                                                                  |
//| Return value:                                                    |
//| true if moments calculated successfully, otherwise false.        |
//+------------------------------------------------------------------+
bool MathMomentsGamma(const double a,const double b,double &mean,double &variance,double &skewness,double &kurtosis,int &error_code)
  {
//--- default values
   mean    =QNaN;
   variance=QNaN;
   skewness=QNaN;
   kurtosis=QNaN;
//--- check NaN
   if(!MathIsValidNumber(a) || !MathIsValidNumber(b))
     {
      error_code=ERR_ARGUMENTS_NAN;
      return false;
     }
//--- a and b must be positive
   if(a<=0 || b<=0)
     {
      error_code=ERR_ARGUMENTS_INVALID;
      return false;
     }

   error_code=ERR_OK;
//--- calculate moments
   mean    =a*b;
   variance=a*b*b;
   skewness=2/MathSqrt(a);
   kurtosis=6/a;
//--- successful
   return true;
  }
//+------------------------------------------------------------------+
