//+------------------------------------------------------------------+ //| distribution.mqh | //| Copyright 2025, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2025, MetaQuotes Ltd." #property link "https://www.mql5.com" #include"base.mqh" #include #include #include"..\Utility\igami.mqh" #include"..\Utility\igam.mqh" //+------------------------------------------------------------------+ //| constraints container | //+------------------------------------------------------------------+ struct Constraints { matrix _one; vector _two; Constraints(void) { _one = matrix::Zeros(0,0); _two = vector::Zeros(0); } ~Constraints(void) { } Constraints(Constraints &other) { _one = other._one; _two = other._two; } void operator=(Constraints &other) { _one = other._one; _two = other._two; } }; //+------------------------------------------------------------------+ //| base class for distributions | //+------------------------------------------------------------------+ class CDistribution { protected: ulong m_num_params; vector m_parameters; vector m_vector; string m_name; uint m_seed; CHighQualityRandStateShell m_generator; ENUM_DISTRIBUTION_MODEL m_dist_model; bool m_initialized; vector _check_constraints(vector ¶ms_) { matrix bounds_ = bounds(vector::Zeros(0)); ulong nparams = params_.Size(); if(nparams!=bounds_.Rows()) { Print(__FUNCTION__, " error ", "parameters must have ",bounds_.Rows()," elements"); return vector::Zeros(0); } if(!bounds_.Rows()) return vector::Zeros(0); for(ulong i = 0; i1; out*=i, i-=2); return out; } double munp(ulong n) { if(!n) return 1.; if(MathMod(double(n),2.)==0) return (double)factorial2(int(n)-1); else return 0.; } vector moment_from_stats(ulong n, double mu,double mu2, double g1, double g2) { vector out(1); if(n==0) { out[0] = 1.0; return out; } else if(n==1) { if(!mu) out[0] = munp(1); else out[0] = mu; } else if(n==2) { if(!mu2 || !mu) out[0] = munp(2); else out[0] = mu2+mu*mu; } else if(n == 3) { if(!g1 || !mu2) out[0] = munp(3); else { double mu3 = g1*pow(mu2,1.5); out[1] = mu3+3.0*mu*mu2+mu*mu*mu; } } else if(n==4) { if(!g1 || !g2) out[0] = munp(4); else { double mu3, mu4; mu4 = (g2+3.0)*pow(mu2,2.0); mu3 = g1*pow(mu2, 1.5); out[1] = mu4+4*mu*mu3+6*mu*mu*mu2+mu*mu*mu*mu; } } else out[1] = munp(n); return out; } vector norm_moment(ulong order, double loc = 0.0, double scale = 1.) { ulong n = order; bool i0 = (scale>0.); bool i1 = bool(!loc); bool i2 = bool(loc); double mu,mu1,g1, g2; mu = mu1 = g1 = g2 = 0.0; if(n>0 && n<5) { mu = 0.0; mu1 = 1.0; g1 = 0.0; g2 = 0.0; } vector val(1); val = moment_from_stats(n,mu,mu1,g1,g2); val[0] = pow(scale, order)*val[0]; return val; } virtual bool _initialize(vector& distribution_params, int seed=0) override { m_name = "Normal"; m_parameters = distribution_params; m_num_params = 0; m_initialized = _initialize_generator(seed,true); return m_initialized; } public: CNormal(void) { m_dist_model = DIST_NORMAL; } CNormal(vector& params, int seed=0) { initialize(params,seed); } ~CNormal(void) { } virtual Constraints constraints(void) override { Constraints out; return out; } virtual matrix bounds(vector &resids) { return matrix::Zeros(0,0); } virtual vector loglikelihood(vector ¶meters,vector& resids, vector& sigmas,bool individual = false) override { vector lls = -0.5 * (log(2 * M_PI) + log(sigmas) + pow(resids, 2.0) / sigmas) ; if(individual) return lls; else { vector out(1); out[0] = lls.Sum(); return out; } } virtual vector startingValues(vector& resids) override { return vector::Zeros(0); } virtual matrix simulator(ulong nsize,ulong ncols = 1) override { CMatrixDouble out; out.Resize(nsize,ncols); CHighQualityRand::HQRndNormalM(m_generator.GetInnerObj(),int(nsize),int(ncols),out); return out.ToMatrix(); } virtual vector ppf(vector &pits, vector ¶meters) override { _check_constraints(parameters); vector out(pits.Size()); for(ulong i = 0; i0); i1 = (i0 & loc == 0.0); i2 = (i0 & loc != 0.0); if(order<0) { Print(__FUNCTION__, " Moment must be positive "); return vector::Zeros(0); } double mu,mu2,g1,g2; mu = mu2=g1 = g2 = EMPTY_VALUE; if(01.?0.0:double("inf"); if(df>1. && df<=2.0) mu2 = double("inf"); else if(df>2. && MathClassify(df) == FP_NORMAL) mu2 = df/(df-2.); else if(MathClassify(df) == FP_INFINITE) mu2 = 1.; g1 = (df>3.)?0.0:double("nan"); if(df>2. && df<=4.) g2 = double("inf"); if(df>4. && MathClassify(df) == FP_NORMAL) g2 = 6./(df-4.); else if(MathClassify(df) == FP_INFINITE) g2 = 0.; } vector val(1); val[0] = moment_from_stats(int(n),mu,mu2,g1,g2,df,loc,scale); if(!i0) val[0] = double("nan"); if(i1 && loc == 0.0) val[0] = pow(scale,n)*val[0]; if(i2) { vector mom(4); mom[0] = mu; mom[1] = mu2; mom[2] = g1; mom[3] = g2; double res = 0.; double fac = scale/loc; double valk; for(int i = 0; inu) { Print(__FUNCTION__, " n out of bounds "); return mom; } else if(n==0) { mom = MathCumulativeDistributionT(z,nu,ecode); if(ecode) { Print(__FUNCTION__, " error with cdf ", ecode); return double("nan"); } } else if(n==1) { double c = CGammaFunc::GammaFunc(0.5+(n+1.))/(sqrt(nu*M_PI)*CGammaFunc::GammaFunc(0.5*nu)); double e = 0.5*(nu+1.); mom = (0.5*(c*nu)/(1.-e))*(pow(1.+pow(z,2.)/nu,1.-e)); } else { ecode = 0; double t1 = pow(z,n-1.)*(nu+pow(z,2.))*MathProbabilityDensityT(z,nu,ecode); if(ecode) { Print(__FUNCTION__, " error with cdf ", ecode); return double("nan"); } double t2 = (n-1)*nu*_ord_t_partial_moment(n-2,z,nu); mom = (1./(double(n) - nu))*(t1-t2); } return mom; } virtual bool _initialize(vector& distribution_params, int seed=0) override { m_name = "Student's T"; m_num_params = 1; m_parameters = distribution_params; m_initialized= _initialize_generator(seed,false); return m_initialized; } public: CStudentsT(void) { m_dist_model = DIST_STUDENT; } CStudentsT(vector& params, int seed=0) { initialize(params,seed); } ~CStudentsT(void) { } virtual Constraints constraints(void) override { Constraints out; matrix x = {{1.0},{-1.}}; vector y = {2.05,-500.}; out._one = x; out._two = y; return out; } virtual matrix bounds(vector &resids) override { matrix out = {{2.05},{500.}}; return out; } virtual vector loglikelihood(vector ¶meters,vector& resids, vector& sigmas,bool individual = false) override { set_params(parameters); double nu = m_parameters[0]; double ll = log(CGammaFunc::GammaFunc((nu+1.0)/2.0)) - log(CGammaFunc::GammaFunc((nu)/2.0)) - log(M_PI*(nu-2.0))/2.; vector lls = ll - (0.5*log(sigmas)); lls-=((nu + 1.) / 2.) * (log(1. + pow(resids, 2.0) / (sigmas * (nu - 2.)))); if(individual) return lls; else { vector out(1); out[0] = lls.Sum(); return out; } } virtual vector startingValues(vector& resids) override { double k = np::kurtosis(resids,false,true); double temp = k>3.75?(4.0 * k - 6.0) / (k - 3.0):12; double sv = MathMax(temp,4.0); vector out(1); out[0] = sv; return out; } virtual matrix simulator(ulong nsize,ulong ncols = 1) override { matrix out(nsize*ncols,1); vector temp; double vals[]; double std_dev = sqrt(m_parameters[0]/(m_parameters[0] - 2.)); if(!MathRandomT(m_parameters[0],int(nsize*ncols),vals) || !temp.Assign(vals) || !out.Col(temp,0) || !out.Reshape(nsize,ncols)) { Print(__FUNCTION__, " error sampling from student distribution ", GetLastError()); return matrix::Zeros(0,0); } return out/(std_dev); } virtual vector ppf(vector &pits, vector ¶meters) override { set_params(parameters); _check_constraints(parameters); vector out(pits.Size()); int ecode = 0; double var = m_parameters[0]/(m_parameters[0] -2.0); double scale = 1.0/sqrt(var); for(ulong i = 0; inu) { Print(__FUNCTION__, " n out of bounds "); return mom; } else if(n==0) { mom = MathCumulativeDistributionT(z,nu,ecode); if(ecode) { Print(__FUNCTION__, " error with cdf ", ecode); return double("nan"); } } else if(n==1) { double c = CGammaFunc::GammaFunc(0.5+(n+1.))/(sqrt(nu*M_PI)*CGammaFunc::GammaFunc(0.5*nu)); double e = 0.5*(nu+1.); mom = (0.5*(c*nu)/(1.-e))*(pow(1.+pow(z,2.)/nu,1.-e)); } else { ecode = 0; double t1 = pow(z,n-1.)*(nu+pow(z,2.))*MathProbabilityDensityT(z,nu,ecode); if(ecode) { Print(__FUNCTION__, " error with cdf ", ecode); return double("nan"); } double t2 = (n-1)*nu*_ord_t_partial_moment(n-2,z,nu); mom = (1./(double(n) - nu))*(t1-t2); } return mom; } virtual bool _initialize(vector& distribution_params, int seed=0) override { m_name = "Standardized Skew Student's T"; m_num_params = 2; m_parameters = distribution_params; m_initialized= _initialize_generator(seed,true); return m_initialized; } public: CSkewStudent(void) { m_dist_model = DIST_SKEW_STUDENT; } CSkewStudent(vector& params, int seed=0) { initialize(params,seed); } ~CSkewStudent(void) { } virtual Constraints constraints(void) override { Constraints out; matrix x = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; vector y = {2.05, -300.0, -1, -1}; out._one = x; out._two = y; return out; } virtual matrix bounds(vector &resids) override { matrix out = {{-1.,2.05},{1.,300.}}; return out; } virtual vector loglikelihood(vector ¶meters,vector& resids, vector& sigmas,bool individual = false) override { set_params(parameters); double eta = parameters[0]; double lam = parameters[1]; double const_c = _const_c(parameters); double const_a = _const_a(parameters); double const_b = _const_b(parameters); resids=resids/pow(sigmas,0.5); vector lls = log(const_b)+const_c - log(sigmas)/2.; if(MathAbs(lam)>=1.) lam = np::sign(lam)*(1.-1e-6); vector signs(resids.Size()); for(ulong i = 0; i3.75?(4.0 * k - 6.0) / (k - 3.0):12; double sv = (k>3.75)?MathMax((4.*k -6.)/(k-3.),4.):MathMax(12.,4.); vector out(2); out[0] = sv; out[1] = 0.0; return out; } virtual matrix simulator(ulong nsize,ulong ncols = 1) override { matrix out(nsize*ncols,1); vector uniforms(out.Rows()); CHighQualityRand::HQRndContinuous(m_generator.GetInnerObj(),int(out.Rows()),uniforms); vector _ppf = ppf(uniforms,m_parameters); out.Col(_ppf,0); out.Reshape(nsize,ncols); return out; } virtual vector ppf(vector &pits, vector ¶meters) override { set_params(parameters); _check_constraints(parameters); vector out(pits.Size()); double eta = m_parameters[0]; double lam = m_parameters[1]; //double c = _const_c(parameters); double a = _const_a(parameters); double b = _const_b(parameters); vector icdf = vector::Ones(pits.Size()) * -999.99; vector signs = vector::Zeros(pits.Size()); int ecode = 0; for(ulong i = 0; i=m_parameters[0]) return double("nan"); _check_constraints(parameters); double eta = m_parameters[0]; double lam = m_parameters[1]; double a = _const_a(parameters); double b = _const_b(parameters); double loc = -a/b; double lscale = sqrt(1.-2./eta)*(1.-lam)/b; double rscale = sqrt(1.-2./eta)*(1.+lam)/b; double lbound,lhs,rhs,mom = 0.; for(int k = 0; k<(n+1); ++k) { lbound = MathMin(z,loc); lhs = ((1 - lam)* pow(loc, (n - k))* pow(lscale,k)*_ord_t_partial_moment(k,(lbound - loc) / lscale, eta)); if(z>loc) rhs = ((1 + lam)* pow(loc, (n - k))* pow(rscale, k)* (_ord_t_partial_moment(k, (z - loc) / rscale, eta) - _ord_t_partial_moment(k,0.0,eta))); else rhs = 0.0; mom +=np::comb(ulong(n),double(k))*(lhs+rhs); } return mom; } }; //+------------------------------------------------------------------+ //|Generalized Error distribution for use with ARCH models | //+------------------------------------------------------------------+ class CGeneralizedError: public CDistribution { private: double _ord_gennorm_partial_moment(int n, int z, double beta) { if(n<0) return double("nan"); double w = 0.5*beta/CGammaFunc::GammaFunc(1./beta); double lz = pow(MathAbs(MathMin(z,0)),beta); double lterm = (w* (pow((-1), n))* (1 / beta)* CGammaFunc::GammaFunc((n + 1) / beta)* special::cephes::igamc((n + 1) / beta, lz)); double rz = pow(MathMax(0,z),beta); double rterm = w * (1 / beta) * CGammaFunc::GammaFunc((n + 1) / beta) * special::cephes::igamc((n + 1) / beta, rz); return lterm+rterm; } void stats(double beta, double &a, double &b, double &c, double &d) { double c1 = log(CGammaFunc::GammaFunc(1./beta)); double c3 = log(CGammaFunc::GammaFunc(3./beta)); double c5 = log(CGammaFunc::GammaFunc(5./beta)); a = 0.; b = exp(c3-c1); c = 0.; d = exp(c5+c1-2.*c3)-3.; return; } double moment_from_stats(int n, double _mu,double _mu2, double _g1, double _g2, double df, double loc=0., double scale = 1.) { double out; if(n==0) return 1.; else if(n==1) { if(_mu == EMPTY_VALUE) out = double("nan"); else out = _mu; } else if(n==2) { if(_mu2 == EMPTY_VALUE || _mu == EMPTY_VALUE) out = double("nan"); else out = _mu2 + _mu*_mu; } else if(n==3) { if(_g1 == EMPTY_VALUE|| _mu2 == EMPTY_VALUE || _mu == EMPTY_VALUE) out = double("nan"); else { double mu3 = _g1*pow(_mu2,1.5); out = mu3+3.*_mu*_mu2+_mu*_mu*_mu; } } else if(n==4) { if(_g1 == EMPTY_VALUE||_g2 == EMPTY_VALUE||_mu2 == EMPTY_VALUE||_mu == EMPTY_VALUE) out = double("nan"); else { double mu4 =(_g2+3.)*pow(_mu2,2.); double mu3 = _g1*pow(_mu2,1.5); out = mu4+4.*_mu*mu3+6.*_mu*_mu*_mu2+_mu*pow(_mu,3); } } else out = double("nan"); return out; } vector _moment(int order, double df,double loc = 0.0,double scale = 1.) { vector out(0); ulong n = ulong(order); bool i0,i1,i2; i0 = i1 = i2 = false; i0 = (scale>0); i1 = (i0 & loc == 0.0); i2 = (i0 & loc != 0.0); if(order<0) { Print(__FUNCTION__, " Moment must be positive "); return vector::Zeros(0); } double mu,mu2,g1,g2; mu = mu2=g1 = g2 = EMPTY_VALUE; if(0=1 || scale<=0) { double lb = -double("inf"); double ub = double("inf"); if(q == 0.) return lb; else if(q==1.) return ub; } return _ppf(q,nu)*scale+loc; } vector gennorm_ppfv(double nu,double loc, double scale, vector& q) { vector out(q.Size()); for(ulong i = 0; i=m_std_resid.Size()) { Print(__FUNCTION__, " not enough data points "); return vector::Zeros(0); } vector index = vector::Zeros(size); for(ulong i = 0; i