//+------------------------------------------------------------------+ //| EdgeStats.mqh | //| AnimateDread | //| | //| HOW SURE ARE WE, in Lopez de Prado's terms (Advances in Financial | //| Machine Learning, ch. 8 and 14). Stateless math, no trading. | //| | //| Phi / PhiInv the normal CDF and its inverse | //| PSR(SR*) Probabilistic Sharpe Ratio: the probability | //| that the true Sharpe exceeds SR*, given the | //| sample's length, skew and kurtosis. A Sharpe | //| of 1 on 40 trades with fat tails is far less | //| certain than the number suggests. | //| DSR(N) Deflated Sharpe Ratio: PSR against the Sharpe | //| the BEST of N independent trials would show by | //| luck. N is how many configurations were tried | //| to arrive at this one - honestly, including the | //| ones that were discarded. | //| | //| The Sharpe here is PER TRADE (mean R / sd R), not annualised: PSR | //| is defined on the sampling frequency of the returns it is given. | //| The trial-Sharpe variance uses its large-sample form 1/(T-1) - the | //| caller has one run, not N, to estimate it from. That makes DSR an | //| approximation; it is reported as one. | //+------------------------------------------------------------------+ #ifndef WARRIOR_EDGESTATS_MQH #define WARRIOR_EDGESTATS_MQH class CEdgeStats { public: //--- Abramowitz & Stegun 7.1.26, |error| < 1.5e-7. static double Phi(const double x) { const double t = 1.0 / (1.0 + 0.2316419 * MathAbs(x)); const double d = 0.3989422804 * MathExp(-0.5 * x * x); const double p = d * t * (0.319381530 + t * (-0.356563782 + t * (1.781477937 + t * (-1.821255978 + t * 1.330274429)))); return (x >= 0.0) ? 1.0 - p : p; } //--- Acklam's rational approximation, relative error < 1.2e-9. static double PhiInv(const double p) { if(p <= 0.0 || p >= 1.0) return 0.0; static const double a[6] = {-3.969683028665376e+01, 2.209460984245205e+02, -2.759285104469687e+02, 1.383577518672690e+02, -3.066479806614716e+01, 2.506628277459239e+00}; static const double b[5] = {-5.447609879822406e+01, 1.615858368580409e+02, -1.556989798598866e+02, 6.680131188771972e+01, -1.328068155288572e+01}; static const double c[6] = {-7.784894002430293e-03, -3.223964580411365e-01, -2.400758277161838e+00, -2.549732539343734e+00, 4.374664141464968e+00, 2.938163982698783e+00}; static const double d[4] = {7.784695709041462e-03, 3.224671290700398e-01, 2.445134137142996e+00, 3.754408661907416e+00}; const double lo = 0.02425; if(p < lo) { const double q = MathSqrt(-2.0 * MathLog(p)); return (((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1.0); } if(p > 1.0 - lo) { const double q = MathSqrt(-2.0 * MathLog(1.0 - p)); return -(((((c[0] * q + c[1]) * q + c[2]) * q + c[3]) * q + c[4]) * q + c[5]) / ((((d[0] * q + d[1]) * q + d[2]) * q + d[3]) * q + 1.0); } const double q = p - 0.5, r = q * q; return (((((a[0] * r + a[1]) * r + a[2]) * r + a[3]) * r + a[4]) * r + a[5]) * q / (((((b[0] * r + b[1]) * r + b[2]) * r + b[3]) * r + b[4]) * r + 1.0); } //--- Moments of a sample. Kurtosis is the plain (non-excess) fourth standardised moment. static bool Moments(const double &x[], double &mean, double &sd, double &skew, double &kurt) { const int n = ArraySize(x); if(n < 4) return false; double s = 0.0; for(int i = 0; i < n; i++) s += x[i]; mean = s / n; double m2 = 0.0, m3 = 0.0, m4 = 0.0; for(int i = 0; i < n; i++) { const double d = x[i] - mean; m2 += d * d; m3 += d * d * d; m4 += d * d * d * d; } m2 /= n; m3 /= n; m4 /= n; if(m2 <= 0.0) return false; sd = MathSqrt(m2 * n / (n - 1)); skew = m3 / MathPow(m2, 1.5); kurt = m4 / (m2 * m2); return true; } //--- PSR(SR*) for per-trade returns. -1 when the sample is too small to say anything. static double PSR(const double &x[], const double srStar = 0.0) { double mean, sd, skew, kurt; if(!Moments(x, mean, sd, skew, kurt) || sd <= 0.0) return -1.0; const int n = ArraySize(x); const double sr = mean / sd; const double den = 1.0 - skew * sr + (kurt - 1.0) / 4.0 * sr * sr; if(den <= 0.0) return -1.0; return Phi((sr - srStar) * MathSqrt(n - 1.0) / MathSqrt(den)); } //--- The Sharpe the best of `trials` unskilled strategies would show (AFML 14.1, eq. 14.1). static double ExpectedMaxSharpe(const int trials, const int n) { if(trials < 2 || n < 3) return 0.0; const double g = 0.5772156649; const double sigma = 1.0 / MathSqrt(n - 1.0); return sigma * ((1.0 - g) * PhiInv(1.0 - 1.0 / trials) + g * PhiInv(1.0 - 1.0 / (trials * M_E))); } static double DSR(const double &x[], const int trials) { return PSR(x, ExpectedMaxSharpe(trials, ArraySize(x))); } }; #endif // WARRIOR_EDGESTATS_MQH