758 lines
14 KiB
Plaintext
758 lines
14 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| MARKET REGIME CLASSIFIER EA |
|
|
//| REGIME ENGINE v1 |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "MARKET REGIME CLASSIFIER"
|
|
#property version "1.00"
|
|
#property description "CLASSIFICATION TO STRATEGY"
|
|
|
|
//====================================================
|
|
// ENUMS
|
|
//====================================================
|
|
|
|
enum MarketRegime
|
|
{
|
|
REGIME_UNKNOWN = 0,
|
|
REGIME_TREND_EXPANSION,
|
|
REGIME_PULLBACK,
|
|
REGIME_BALANCED_RANGE,
|
|
REGIME_VOLATILE_RANGE,
|
|
REGIME_COMPRESSION
|
|
};
|
|
|
|
//====================================================
|
|
// INPUTS
|
|
//====================================================
|
|
|
|
input int ATR_Period = 14;
|
|
input int EMA_Period = 50;
|
|
input int ADX_Period = 14;
|
|
|
|
input int LookbackBars = 20;
|
|
|
|
input double MinConfidence = 0.30;
|
|
|
|
//====================================================
|
|
// STRUCTS
|
|
//====================================================
|
|
|
|
struct MarketFeatures
|
|
{
|
|
double atrRelative;
|
|
double emaSlope;
|
|
double overlapRatio;
|
|
double wickRatio;
|
|
double bbWidth;
|
|
|
|
double displacementCount;
|
|
|
|
double smallBodyRatio;
|
|
double insideBarFrequency;
|
|
|
|
double adxValue;
|
|
|
|
bool htfTrendBullish;
|
|
bool htfTrendBearish;
|
|
};
|
|
|
|
struct RegimeScores
|
|
{
|
|
int trendExpansion;
|
|
int pullback;
|
|
int balancedRange;
|
|
int volatileRange;
|
|
int compression;
|
|
};
|
|
|
|
//====================================================
|
|
// GLOBALS
|
|
//====================================================
|
|
|
|
int atrHandle;
|
|
int emaHandle;
|
|
int adxHandle;
|
|
int bbHandle;
|
|
|
|
//====================================================
|
|
// INIT
|
|
//====================================================
|
|
|
|
int OnInit()
|
|
{
|
|
atrHandle = iATR(_Symbol, PERIOD_CURRENT, ATR_Period);
|
|
|
|
emaHandle = iMA(
|
|
_Symbol,
|
|
PERIOD_CURRENT,
|
|
EMA_Period,
|
|
0,
|
|
MODE_EMA,
|
|
PRICE_CLOSE
|
|
);
|
|
|
|
adxHandle = iADX(
|
|
_Symbol,
|
|
PERIOD_CURRENT,
|
|
ADX_Period
|
|
);
|
|
|
|
bbHandle = iBands(
|
|
_Symbol,
|
|
PERIOD_CURRENT,
|
|
20,
|
|
0,
|
|
2.0,
|
|
PRICE_CLOSE
|
|
);
|
|
|
|
return(INIT_SUCCEEDED);
|
|
}
|
|
|
|
//====================================================
|
|
// DEINIT
|
|
//====================================================
|
|
|
|
void OnDeinit(const int reason)
|
|
{
|
|
if(atrHandle != INVALID_HANDLE)
|
|
{
|
|
IndicatorRelease(atrHandle);
|
|
atrHandle = INVALID_HANDLE;
|
|
}
|
|
if(emaHandle != INVALID_HANDLE)
|
|
{
|
|
IndicatorRelease(emaHandle);
|
|
emaHandle = INVALID_HANDLE;
|
|
}
|
|
if(adxHandle != INVALID_HANDLE)
|
|
{
|
|
IndicatorRelease(adxHandle);
|
|
adxHandle = INVALID_HANDLE;
|
|
}
|
|
if(bbHandle != INVALID_HANDLE)
|
|
{
|
|
IndicatorRelease(bbHandle);
|
|
bbHandle = INVALID_HANDLE;
|
|
}
|
|
}
|
|
|
|
//====================================================
|
|
// MAIN
|
|
//====================================================
|
|
|
|
void OnTick()
|
|
{
|
|
static datetime lastBarTime = 0;
|
|
|
|
datetime currentBar = iTime(_Symbol, PERIOD_CURRENT, 0);
|
|
|
|
if(currentBar == lastBarTime)
|
|
return;
|
|
|
|
lastBarTime = currentBar;
|
|
|
|
MarketFeatures features;
|
|
|
|
ExtractMarketFeatures(features);
|
|
|
|
RegimeScores scores;
|
|
|
|
CalculateRegimeScores(features, scores);
|
|
|
|
MarketRegime regime = GetHighestScoreRegime(scores);
|
|
|
|
double confidence = CalculateConfidence(scores);
|
|
|
|
if(confidence < MinConfidence)
|
|
regime = REGIME_UNKNOWN;
|
|
|
|
PrintRegime(regime, confidence, scores);
|
|
|
|
//===========================================
|
|
// ROUTER
|
|
//===========================================
|
|
|
|
switch(regime)
|
|
{
|
|
case REGIME_TREND_EXPANSION:
|
|
|
|
Print("MODE = TREND EXPANSION");
|
|
|
|
break;
|
|
|
|
case REGIME_PULLBACK:
|
|
|
|
Print("MODE = PULLBACK");
|
|
|
|
break;
|
|
|
|
case REGIME_BALANCED_RANGE:
|
|
|
|
Print("MODE = BALANCED RANGE");
|
|
|
|
break;
|
|
|
|
case REGIME_VOLATILE_RANGE:
|
|
|
|
Print("MODE = VOLATILE RANGE");
|
|
|
|
break;
|
|
|
|
case REGIME_COMPRESSION:
|
|
|
|
Print("MODE = COMPRESSION");
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
//====================================================
|
|
// FEATURE EXTRACTION
|
|
//====================================================
|
|
|
|
void ExtractMarketFeatures(MarketFeatures &f)
|
|
{
|
|
f.atrRelative = CalculateATRRelative();
|
|
|
|
f.emaSlope = CalculateEMASlope();
|
|
|
|
f.overlapRatio = CalculateOverlapRatio();
|
|
|
|
f.wickRatio = CalculateWickRatio();
|
|
|
|
f.bbWidth = CalculateBBWidth();
|
|
|
|
f.displacementCount = CountDisplacementCandles();
|
|
|
|
f.smallBodyRatio = CalculateSmallBodyRatio();
|
|
|
|
f.insideBarFrequency = CalculateInsideBarFrequency();
|
|
|
|
f.adxValue = CalculateADX();
|
|
|
|
f.htfTrendBullish = (f.emaSlope > 1.0);
|
|
|
|
f.htfTrendBearish = false;
|
|
}
|
|
|
|
//====================================================
|
|
// ATR RELATIVE
|
|
//====================================================
|
|
|
|
double CalculateATRRelative()
|
|
{
|
|
double atr[];
|
|
|
|
ArraySetAsSeries(atr, true);
|
|
|
|
CopyBuffer(atrHandle, 0, 0, 60, atr);
|
|
|
|
double currentATR = atr[0];
|
|
|
|
double sum = 0;
|
|
|
|
for(int i=0; i<50; i++)
|
|
sum += atr[i];
|
|
|
|
double avgATR = sum / 50.0;
|
|
|
|
if(avgATR == 0)
|
|
return 0;
|
|
|
|
return currentATR / avgATR;
|
|
}
|
|
|
|
//====================================================
|
|
// EMA SLOPE
|
|
//====================================================
|
|
|
|
double CalculateEMASlope()
|
|
{
|
|
double ema[];
|
|
double atr[];
|
|
|
|
ArraySetAsSeries(ema, true);
|
|
ArraySetAsSeries(atr, true);
|
|
|
|
CopyBuffer(emaHandle, 0, 0, 20, ema);
|
|
|
|
CopyBuffer(atrHandle, 0, 0, 20, atr);
|
|
|
|
if(atr[0] == 0)
|
|
return 0;
|
|
|
|
return MathAbs(ema[0] - ema[10]) / atr[0];
|
|
}
|
|
|
|
//====================================================
|
|
// OVERLAP RATIO
|
|
//====================================================
|
|
|
|
double CalculateOverlapRatio()
|
|
{
|
|
double totalOverlap = 0;
|
|
|
|
double totalRange = 0;
|
|
|
|
for(int i=1; i<=LookbackBars; i++)
|
|
{
|
|
double high1 = iHigh(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double low1 = iLow(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double high2 = iHigh(_Symbol, PERIOD_CURRENT, i+1);
|
|
|
|
double low2 = iLow(_Symbol, PERIOD_CURRENT, i+1);
|
|
|
|
double overlapHigh = MathMin(high1, high2);
|
|
|
|
double overlapLow = MathMax(low1, low2);
|
|
|
|
double overlap = overlapHigh - overlapLow;
|
|
|
|
if(overlap < 0)
|
|
overlap = 0;
|
|
|
|
totalOverlap += overlap;
|
|
|
|
totalRange += (high1 - low1);
|
|
}
|
|
|
|
if(totalRange == 0)
|
|
return 0;
|
|
|
|
return totalOverlap / totalRange;
|
|
}
|
|
|
|
//====================================================
|
|
// WICK RATIO
|
|
//====================================================
|
|
|
|
double CalculateWickRatio()
|
|
{
|
|
double totalWick = 0;
|
|
|
|
double totalRange = 0;
|
|
|
|
for(int i=1; i<=LookbackBars; i++)
|
|
{
|
|
double high = iHigh(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double low = iLow(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double open = iOpen(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double close = iClose(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double upperWick = high - MathMax(open, close);
|
|
|
|
double lowerWick = MathMin(open, close) - low;
|
|
|
|
double wick = upperWick + lowerWick;
|
|
|
|
double range = high - low;
|
|
|
|
totalWick += wick;
|
|
|
|
totalRange += range;
|
|
}
|
|
|
|
if(totalRange == 0)
|
|
return 0;
|
|
|
|
return totalWick / totalRange;
|
|
}
|
|
|
|
//====================================================
|
|
// DISPLACEMENT COUNT
|
|
//====================================================
|
|
|
|
int CountDisplacementCandles()
|
|
{
|
|
double atr[];
|
|
|
|
ArraySetAsSeries(atr, true);
|
|
|
|
CopyBuffer(atrHandle, 0, 0, 100, atr);
|
|
|
|
int count = 0;
|
|
|
|
for(int i=1; i<=LookbackBars; i++)
|
|
{
|
|
double open = iOpen(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double close = iClose(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double high = iHigh(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double low = iLow(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double body = MathAbs(close - open);
|
|
|
|
double range = high - low;
|
|
|
|
if(range == 0)
|
|
continue;
|
|
|
|
double bodyRatio = body / range;
|
|
|
|
if(
|
|
body > atr[i] * 1.5
|
|
&&
|
|
bodyRatio > 0.7
|
|
)
|
|
{
|
|
count++;
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
//====================================================
|
|
// SMALL BODY RATIO
|
|
//====================================================
|
|
|
|
double CalculateSmallBodyRatio()
|
|
{
|
|
int count = 0;
|
|
|
|
for(int i=1; i<=LookbackBars; i++)
|
|
{
|
|
double body =
|
|
MathAbs(
|
|
iClose(_Symbol, PERIOD_CURRENT, i)
|
|
-
|
|
iOpen(_Symbol, PERIOD_CURRENT, i)
|
|
);
|
|
|
|
double range =
|
|
iHigh(_Symbol, PERIOD_CURRENT, i)
|
|
-
|
|
iLow(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
if(range == 0)
|
|
continue;
|
|
|
|
double ratio = body / range;
|
|
|
|
if(ratio < 0.3)
|
|
count++;
|
|
}
|
|
|
|
return (double)count / LookbackBars;
|
|
}
|
|
|
|
//====================================================
|
|
// INSIDE BAR FREQUENCY
|
|
//====================================================
|
|
|
|
double CalculateInsideBarFrequency()
|
|
{
|
|
int count = 0;
|
|
|
|
for(int i=1; i<=LookbackBars; i++)
|
|
{
|
|
double high1 = iHigh(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double low1 = iLow(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
double high2 = iHigh(_Symbol, PERIOD_CURRENT, i+1);
|
|
|
|
double low2 = iLow(_Symbol, PERIOD_CURRENT, i+1);
|
|
|
|
bool insideBar =
|
|
high1 < high2
|
|
&&
|
|
low1 > low2;
|
|
|
|
if(insideBar)
|
|
count++;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
//====================================================
|
|
// ADX
|
|
//====================================================
|
|
|
|
double CalculateADX()
|
|
{
|
|
double adx[];
|
|
|
|
ArraySetAsSeries(adx, true);
|
|
|
|
CopyBuffer(adxHandle, 0, 0, 10, adx);
|
|
|
|
return adx[0];
|
|
}
|
|
|
|
//====================================================
|
|
// BOLLINGER WIDTH
|
|
//====================================================
|
|
|
|
double CalculateBBWidth()
|
|
{
|
|
double upper[];
|
|
double lower[];
|
|
double atr[];
|
|
|
|
ArraySetAsSeries(upper, true);
|
|
ArraySetAsSeries(lower, true);
|
|
ArraySetAsSeries(atr, true);
|
|
|
|
CopyBuffer(bbHandle, 1, 0, 10, upper);
|
|
|
|
CopyBuffer(bbHandle, 2, 0, 10, lower);
|
|
|
|
CopyBuffer(atrHandle, 0, 0, 10, atr);
|
|
|
|
double width = upper[0] - lower[0];
|
|
|
|
if(atr[0] == 0)
|
|
return 0;
|
|
|
|
return width / atr[0];
|
|
}
|
|
|
|
//====================================================
|
|
// SCORE ENGINE
|
|
//====================================================
|
|
|
|
void CalculateRegimeScores(
|
|
MarketFeatures &f,
|
|
RegimeScores &s
|
|
)
|
|
{
|
|
s.trendExpansion = 0;
|
|
s.pullback = 0;
|
|
s.balancedRange = 0;
|
|
s.volatileRange = 0;
|
|
s.compression = 0;
|
|
|
|
//-----------------------------------------
|
|
// TREND EXPANSION
|
|
//-----------------------------------------
|
|
|
|
if(f.atrRelative > 1.3)
|
|
s.trendExpansion += 2;
|
|
|
|
if(f.emaSlope > 1.0)
|
|
s.trendExpansion += 2;
|
|
|
|
if(f.overlapRatio < 0.4)
|
|
s.trendExpansion += 2;
|
|
|
|
if(f.displacementCount > 5)
|
|
s.trendExpansion += 3;
|
|
|
|
if(f.wickRatio < 0.4)
|
|
s.trendExpansion += 1;
|
|
|
|
//-----------------------------------------
|
|
// PULLBACK
|
|
//-----------------------------------------
|
|
|
|
if(f.htfTrendBullish || f.htfTrendBearish)
|
|
s.pullback += 3;
|
|
|
|
if(
|
|
f.atrRelative >= 0.8
|
|
&&
|
|
f.atrRelative <= 1.2
|
|
)
|
|
{
|
|
s.pullback += 2;
|
|
}
|
|
|
|
if(
|
|
f.overlapRatio >= 0.4
|
|
&&
|
|
f.overlapRatio <= 0.7
|
|
)
|
|
{
|
|
s.pullback += 2;
|
|
}
|
|
|
|
//-----------------------------------------
|
|
// BALANCED RANGE
|
|
//-----------------------------------------
|
|
|
|
if(f.atrRelative < 0.9)
|
|
s.balancedRange += 2;
|
|
|
|
if(f.adxValue < 20)
|
|
s.balancedRange += 2;
|
|
|
|
if(f.overlapRatio > 0.7)
|
|
s.balancedRange += 3;
|
|
|
|
if(f.wickRatio > 0.6)
|
|
s.balancedRange += 1;
|
|
|
|
//-----------------------------------------
|
|
// VOLATILE RANGE
|
|
//-----------------------------------------
|
|
|
|
if(f.atrRelative > 1.3)
|
|
s.volatileRange += 2;
|
|
|
|
if(f.wickRatio > 0.6)
|
|
s.volatileRange += 2;
|
|
|
|
if(f.overlapRatio > 0.5)
|
|
s.volatileRange += 2;
|
|
|
|
//-----------------------------------------
|
|
// COMPRESSION
|
|
//-----------------------------------------
|
|
|
|
if(f.atrRelative < 0.7)
|
|
s.compression += 3;
|
|
|
|
if(f.bbWidth < 1.0)
|
|
s.compression += 3;
|
|
|
|
if(f.insideBarFrequency > 5)
|
|
s.compression += 2;
|
|
|
|
if(f.smallBodyRatio > 0.6)
|
|
s.compression += 2;
|
|
}
|
|
|
|
//====================================================
|
|
// CLASSIFIER
|
|
//====================================================
|
|
|
|
MarketRegime GetHighestScoreRegime(
|
|
RegimeScores &s
|
|
)
|
|
{
|
|
int maxScore = -1;
|
|
|
|
MarketRegime regime = REGIME_UNKNOWN;
|
|
|
|
if(s.trendExpansion > maxScore)
|
|
{
|
|
maxScore = s.trendExpansion;
|
|
regime = REGIME_TREND_EXPANSION;
|
|
}
|
|
|
|
if(s.pullback > maxScore)
|
|
{
|
|
maxScore = s.pullback;
|
|
regime = REGIME_PULLBACK;
|
|
}
|
|
|
|
if(s.balancedRange > maxScore)
|
|
{
|
|
maxScore = s.balancedRange;
|
|
regime = REGIME_BALANCED_RANGE;
|
|
}
|
|
|
|
if(s.volatileRange > maxScore)
|
|
{
|
|
maxScore = s.volatileRange;
|
|
regime = REGIME_VOLATILE_RANGE;
|
|
}
|
|
|
|
if(s.compression > maxScore)
|
|
{
|
|
maxScore = s.compression;
|
|
regime = REGIME_COMPRESSION;
|
|
}
|
|
|
|
return regime;
|
|
}
|
|
|
|
//====================================================
|
|
// CONFIDENCE
|
|
//====================================================
|
|
|
|
double CalculateConfidence(
|
|
RegimeScores &s
|
|
)
|
|
{
|
|
int arr[5];
|
|
|
|
arr[0] = s.trendExpansion;
|
|
arr[1] = s.pullback;
|
|
arr[2] = s.balancedRange;
|
|
arr[3] = s.volatileRange;
|
|
arr[4] = s.compression;
|
|
|
|
int highest = 0;
|
|
int second = 0;
|
|
|
|
for(int i=0; i<5; i++)
|
|
{
|
|
if(arr[i] > highest)
|
|
{
|
|
second = highest;
|
|
highest = arr[i];
|
|
}
|
|
else if(arr[i] > second)
|
|
{
|
|
second = arr[i];
|
|
}
|
|
}
|
|
|
|
if(highest == 0)
|
|
return 0;
|
|
|
|
return (double)(highest - second) / highest;
|
|
}
|
|
|
|
//====================================================
|
|
// PRINT
|
|
//====================================================
|
|
|
|
void PrintRegime(
|
|
MarketRegime regime,
|
|
double confidence,
|
|
RegimeScores &scores
|
|
)
|
|
{
|
|
string name = "UNKNOWN";
|
|
|
|
switch(regime)
|
|
{
|
|
case REGIME_TREND_EXPANSION:
|
|
name = "TREND_EXPANSION";
|
|
break;
|
|
|
|
case REGIME_PULLBACK:
|
|
name = "PULLBACK";
|
|
break;
|
|
|
|
case REGIME_BALANCED_RANGE:
|
|
name = "BALANCED_RANGE";
|
|
break;
|
|
|
|
case REGIME_VOLATILE_RANGE:
|
|
name = "VOLATILE_RANGE";
|
|
break;
|
|
|
|
case REGIME_COMPRESSION:
|
|
name = "COMPRESSION";
|
|
break;
|
|
}
|
|
|
|
Print(
|
|
"REGIME = ",
|
|
name,
|
|
" | CONFIDENCE = ",
|
|
DoubleToString(confidence, 2),
|
|
" | EXP=",
|
|
scores.trendExpansion,
|
|
" | PB=",
|
|
scores.pullback,
|
|
" | BR=",
|
|
scores.balancedRange,
|
|
" | VR=",
|
|
scores.volatileRange,
|
|
" | COMP=",
|
|
scores.compression
|
|
);
|
|
} |