forked from animatedread/Warrior_EA
- Introduced `FeatureScale.mqh` with `FeatSquash` function for stateless feature scaling. - Added `RegimeMath.mqh` class for regime arithmetic, including efficiency and variance calculations. - Documented the Mind trading logic in `MIND.md`, detailing the trading process and modes. - Created `VOLNORM_PLAN.md` and `VOLNORM_RESULTS.md` for tick-volume normalization testing. - Implemented `read_book.py` for analyzing trade book data and correlations. - Developed `volnorm.py` for testing tick-volume normalization with new and old methods.
128 lines
5.9 KiB
MQL5
128 lines
5.9 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| 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
|