Initialize project in MT5 Experts directory

This commit is contained in:
Huthayfa
2026-05-28 19:06:07 +03:00
commit 7545b842a4
21 changed files with 3213 additions and 0 deletions
+46
View File
@@ -0,0 +1,46 @@
//+------------------------------------------------------------------+
//| Logic/ContextFilter.mqh |
//+------------------------------------------------------------------+
#ifndef __CONTEXT_FILTER_MQH__
#define __CONTEXT_FILTER_MQH__
#include "../Core/Config.mqh"
#include "../Core/State.mqh"
#include "../Data/Volatility.mqh"
class CContextFilter
{
private:
ENUM_TIMEFRAMES m_mtf;
CVolatility *m_vol;
public:
bool Init(ENUM_TIMEFRAMES mtf, CVolatility &vol)
{
m_mtf = mtf; m_vol = GetPointer(vol);
Print("[ContextFilter] MTF analysis initialized on ", EnumToString(mtf));
return true;
}
void Release() {}
void Analyze(EAState &state)
{
state.volumeConfirmed = CheckVolume();
m_vol.Update();
state.currentRegime = m_vol.DetectRegime();
}
private:
bool CheckVolume()
{
MqlRates rates[]; ArraySetAsSeries(rates, true);
if(CopyRates(_Symbol, m_mtf, 0, VOLUME_MA_PERIOD + 2, rates) < VOLUME_MA_PERIOD + 2) return false;
double sumVol = 0;
for(int i = 1; i <= VOLUME_MA_PERIOD; i++) sumVol += (double)rates[i].tick_volume;
double volMA = sumVol / VOLUME_MA_PERIOD;
double currentVol = (double)rates[1].tick_volume;
if(volMA > 0) return (currentVol >= volMA * MIN_VOLUME_RATIO);
return false;
}
};
#endif // __CONTEXT_FILTER_MQH__
+72
View File
@@ -0,0 +1,72 @@
//+------------------------------------------------------------------+
//| Logic/MacroAudit.mqh |
//+------------------------------------------------------------------+
#ifndef __MACRO_AUDIT_MQH__
#define __MACRO_AUDIT_MQH__
#include "../Core/Config.mqh"
#include "../Core/State.mqh"
#include "../Core/Logger.mqh"
#include "../Data/VWAP_Engine.mqh"
#include "../Data/PriceEngine.mqh"
extern CLogger g_logger;
extern CPriceEngine g_priceEngine;
class CMacroAudit
{
private:
ENUM_TIMEFRAMES m_htf;
CVWAPEngine *m_vwap;
public:
bool Init(ENUM_TIMEFRAMES htf, CVWAPEngine &vwap)
{
m_htf = htf; m_vwap = GetPointer(vwap);
Print("[MacroAudit] HTF analysis initialized on ", EnumToString(htf));
return true;
}
void Release() {}
void Analyze(EAState &state)
{
if(!state.vwapState.isValid) { state.currentBias = BIAS_NEUTRAL; return; }
MqlRates currentBar;
if(!g_priceEngine.GetClosedBar(m_htf, 1, currentBar)) { state.currentBias = BIAS_NEUTRAL; return; }
double price = currentBar.close;
double vwap = state.vwapState.vwapValue;
double slope = state.vwapState.vwapSlope;
bool aboveVWAP = (price > vwap * 1.005);
bool belowVWAP = (price < vwap * 0.995);
bool risingVWAP = (slope > 0);
bool fallingVWAP = (slope < 0);
int highestIdx = iHighest(_Symbol, m_htf, MODE_HIGH, SWING_LOOKBACK, 1);
int lowestIdx = iLowest(_Symbol, m_htf, MODE_LOW, SWING_LOOKBACK, 1);
if(highestIdx < 0 || lowestIdx < 0) { state.currentBias = BIAS_NEUTRAL; return; }
double swingHigh = iHigh(_Symbol, m_htf, highestIdx);
double swingLow = iLow(_Symbol, m_htf, lowestIdx);
state.swingHigh = swingHigh; state.swingLow = swingLow;
bool bullBOS = (currentBar.close > swingHigh);
bool bearBOS = (currentBar.close < swingLow);
bool volConfirmed = false;
int volHandle = iMA(_Symbol, m_htf, 20, 0, MODE_SMA, VOLUME_TICK);
if(volHandle != INVALID_HANDLE)
{
double volMABuf[]; ArraySetAsSeries(volMABuf, true);
if(CopyBuffer(volHandle, 0, 1, 1, volMABuf) > 0)
{
double avgVol = volMABuf[0];
if(avgVol > 0) volConfirmed = (currentBar.tick_volume >= avgVol * VOLUME_CONFIRM);
}
IndicatorRelease(volHandle);
}
state.bosBullish = bullBOS && volConfirmed;
state.bosBearish = bearBOS && volConfirmed;
if(aboveVWAP && risingVWAP && state.bosBullish) state.currentBias = BIAS_BULL;
else if(belowVWAP && fallingVWAP && state.bosBearish) state.currentBias = BIAS_BEAR;
else if((aboveVWAP && risingVWAP) || state.bosBullish) state.currentBias = BIAS_BULL;
else if((belowVWAP && fallingVWAP) || state.bosBearish) state.currentBias = BIAS_BEAR;
else state.currentBias = BIAS_NEUTRAL;
}
};
#endif // __MACRO_AUDIT_MQH__
+163
View File
@@ -0,0 +1,163 @@
//+------------------------------------------------------------------+
//| Logic/MicroTrigger.mqh |
//+------------------------------------------------------------------+
#ifndef __MICRO_TRIGGER_MQH__
#define __MICRO_TRIGGER_MQH__
#include "../Core/Config.mqh"
#include "../Core/State.mqh"
#include "../Data/PriceEngine.mqh"
#include "../Data/Volatility.mqh"
#include "../Core/Logger.mqh"
extern CLogger g_logger;
extern CVolatility g_volatility;
class CMicroTrigger
{
private:
ENUM_TIMEFRAMES m_ltf;
CPriceEngine *m_price;
public:
bool Init(ENUM_TIMEFRAMES ltf, CPriceEngine &price)
{
m_ltf = ltf; m_price = GetPointer(price);
Print("[MicroTrigger] LTF entry logic initialized on ", EnumToString(ltf));
return true;
}
void Release() {}
void GenerateSignal(SignalData &signal, const EAState &state, CPriceEngine &price)
{
signal.isValid = false; signal.isBuy = false; signal.pattern = PATTERN_NONE;
signal.rejectionReason = ""; signal.signalTime = TimeCurrent(); signal.atrValue = 0;
if(state.currentBias == BIAS_NEUTRAL && state.currentRegime != REGIME_RANGE)
{ signal.rejectionReason = "HTF Bias Neutral + Not Range Mode"; return; }
MqlRates bars[4];
if(!price.GetClosedBar(m_ltf, 1, bars[1]) || !price.GetClosedBar(m_ltf, 2, bars[2]))
{ signal.rejectionReason = "Failed to load LTF closed bars"; return; }
if(CheckPinBar(bars[1], state))
{
signal.pattern = PATTERN_PIN_BAR; signal.patternName = "Pin Bar";
signal.isBuy = (bars[1].close > bars[1].open);
if(ValidateDirection(signal, state)) { CalculateLevels(signal, bars[1], state); return; }
}
if(!price.GetClosedBar(m_ltf, 2, bars[2])) { signal.rejectionReason = "Failed to load bar[2]"; return; }
if(CheckEngulfing(bars[1], bars[2]))
{
signal.pattern = PATTERN_ENGULFING; signal.patternName = "Engulfing";
signal.isBuy = (bars[1].close > bars[1].open);
if(ValidateDirection(signal, state)) { CalculateLevels(signal, bars[1], state); return; }
}
if(price.GetClosedBar(m_ltf, 3, bars[3]))
{
if(CheckInsideBarBreakout(bars[1], bars[2], bars[3]))
{
signal.pattern = PATTERN_INSIDE_BAR; signal.patternName = "Inside Bar Breakout";
signal.isBuy = (bars[1].close > bars[2].high);
if(ValidateDirection(signal, state)) { CalculateLevels(signal, bars[1], state); return; }
}
}
signal.rejectionReason = "No valid price action pattern";
}
private:
bool ValidateDirection(SignalData &signal, const EAState &state)
{
if(state.currentRegime == REGIME_RANGE) return true;
if(state.currentBias == BIAS_BULL && !signal.isBuy)
{ signal.isValid = false; signal.rejectionReason = "Bearish signal rejected (HTF Bias: BULL)"; return false; }
if(state.currentBias == BIAS_BEAR && signal.isBuy)
{ signal.isValid = false; signal.rejectionReason = "Bullish signal rejected (HTF Bias: BEAR)"; return false; }
signal.isValid = true; return true;
}
bool CheckPinBar(const MqlRates &bar, const EAState &state)
{
double body = MathAbs(bar.close - bar.open);
double upperWick = bar.high - MathMax(bar.open, bar.close);
double lowerWick = MathMin(bar.open, bar.close) - bar.low;
double range = bar.high - bar.low;
if(range == 0 || body == 0) return false;
bool bullish = (bar.close > bar.open);
if(bullish)
{
bool wickOK = (lowerWick >= body * PIN_BAR_WICK_MULT);
bool closePos = (bar.close >= bar.low + range * 0.7);
bool atLevel = IsAtKeyLevel(bar, state, true);
return wickOK && closePos && atLevel;
}
else
{
bool wickOK = (upperWick >= body * PIN_BAR_WICK_MULT);
bool closePos = (bar.close <= bar.low + range * 0.3);
bool atLevel = IsAtKeyLevel(bar, state, false);
return wickOK && closePos && atLevel;
}
}
bool CheckEngulfing(const MqlRates &curr, const MqlRates &prev)
{
bool bullish = (curr.close > prev.open && curr.open < prev.close);
bool bearish = (curr.close < prev.open && curr.open > prev.close);
if(!bullish && !bearish) return false;
return (curr.tick_volume >= prev.tick_volume * ENGULF_VOLUME_MULT);
}
bool CheckInsideBarBreakout(const MqlRates &breakout, const MqlRates &inside, const MqlRates &mother)
{
bool isInside = (inside.high < mother.high && inside.low > mother.low);
if(!isInside) return false;
bool bullBreak = (breakout.close > inside.high);
bool bearBreak = (breakout.close < inside.low);
return (bullBreak || bearBreak);
}
bool IsAtKeyLevel(const MqlRates &bar, const EAState &state, bool isBullish)
{
double proximity = state.assetProfile.atrMultiplierSL * g_volatility.GetATR() * 0.5;
if(MathAbs(bar.close - state.vwapState.vwapValue) <= proximity) return true;
if(isBullish && MathAbs(bar.low - state.swingLow) <= proximity) return true;
if(!isBullish && MathAbs(bar.high - state.swingHigh) <= proximity) return true;
int maHandle = iMA(_Symbol, m_ltf, 50, 0, MODE_EMA, PRICE_CLOSE);
if(maHandle != INVALID_HANDLE)
{
double maBuf[]; ArraySetAsSeries(maBuf, true);
if(CopyBuffer(maHandle, 0, 1, 1, maBuf) > 0)
{
double ema50 = maBuf[0];
IndicatorRelease(maHandle);
if(MathAbs(bar.close - ema50) <= proximity) return true;
}
IndicatorRelease(maHandle);
}
return false;
}
void CalculateLevels(SignalData &signal, const MqlRates &bar, const EAState &state)
{
double atr = g_volatility.GetATR();
if(atr <= 0) atr = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE) * 10;
signal.atrValue = atr;
if(signal.isBuy) signal.entryPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
else signal.entryPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double slMult, tp1Mult, tp2Mult;
if(state.currentRegime == REGIME_TREND)
{ slMult = InpTrendATRMult; tp1Mult = InpTrendATRMult * 2.0; tp2Mult = InpTrendATRMult * 4.0; }
else
{ slMult = InpRangeATRMult; tp1Mult = InpRangeATRMult * 1.5; tp2Mult = InpRangeATRMult * 2.5; }
double slDist = atr * slMult;
double tp1Dist = atr * tp1Mult;
double tp2Dist = atr * tp2Mult;
if(signal.isBuy)
{
signal.slPrice = signal.entryPrice - slDist;
signal.tp1Price = signal.entryPrice + tp1Dist;
signal.tp2Price = signal.entryPrice + tp2Dist;
}
else
{
signal.slPrice = signal.entryPrice + slDist;
signal.tp1Price = signal.entryPrice - tp1Dist;
signal.tp2Price = signal.entryPrice - tp2Dist;
}
signal.isValid = true;
}
};
#endif // __MICRO_TRIGGER_MQH__
+140
View File
@@ -0,0 +1,140 @@
//+------------------------------------------------------------------+
//| Logic/NewsFilter.mqh |
//| Economic News Filter |
//+------------------------------------------------------------------+
#ifndef __NEWS_FILTER_MQH__
#define __NEWS_FILTER_MQH__
#include "../Core/Config.mqh"
#include "../Core/State.mqh"
#include "../Core/Logger.mqh"
extern CLogger g_logger;
class CNewsFilter
{
private:
string m_currency;
int m_minutesBefore;
int m_minutesAfter;
bool m_initialized;
struct NewsEvent
{
datetime time;
string currency;
string event;
int impact;
};
NewsEvent m_events[];
datetime m_lastCalendarUpdate;
public:
CNewsFilter() : m_minutesBefore(30), m_minutesAfter(15), m_initialized(false) {}
bool Init(int minutesBefore = 30, int minutesAfter = 15)
{
m_minutesBefore = minutesBefore;
m_minutesAfter = minutesAfter;
string sym = _Symbol;
if(StringFind(sym, "USD") >= 0) m_currency = "USD";
else if(StringFind(sym, "EUR") >= 0) m_currency = "EUR";
else if(StringFind(sym, "GBP") >= 0) m_currency = "GBP";
else if(StringFind(sym, "JPY") >= 0) m_currency = "JPY";
else if(StringFind(sym, "AUD") >= 0) m_currency = "AUD";
else if(StringFind(sym, "CAD") >= 0) m_currency = "CAD";
else if(StringFind(sym, "CHF") >= 0) m_currency = "CHF";
else if(StringFind(sym, "NZD") >= 0) m_currency = "NZD";
else m_currency = "USD";
m_initialized = true;
m_lastCalendarUpdate = 0;
Print("[NewsFilter] Initialized for ", m_currency);
return true;
}
bool IsTradingAllowed()
{
if(!m_initialized) return true;
datetime now = TimeCurrent();
if(now - m_lastCalendarUpdate > 3600) { UpdateCalendar(); m_lastCalendarUpdate = now; }
for(int i = 0; i < ArraySize(m_events); i++)
{
if(m_events[i].impact < 3) continue;
datetime blockStart = m_events[i].time - m_minutesBefore * 60;
datetime blockEnd = m_events[i].time + m_minutesAfter * 60;
if(now >= blockStart && now <= blockEnd)
{
g_logger.LogEvent("NEWS", StringFormat("TRADING BLOCKED: %s at %s", m_events[i].event, TimeToString(m_events[i].time)));
return false;
}
}
return true;
}
private:
void UpdateCalendar()
{
ArrayResize(m_events, 0);
string filename = "NewsCalendar_" + m_currency + ".csv";
if(FileIsExist(filename, FILE_COMMON))
{
int handle = FileOpen(filename, FILE_READ|FILE_CSV|FILE_COMMON, ',');
if(handle != INVALID_HANDLE)
{
while(!FileIsEnding(handle))
{
string dateStr = FileReadString(handle);
string timeStr = FileReadString(handle);
string currency = FileReadString(handle);
string event = FileReadString(handle);
string impactStr = FileReadString(handle);
if(dateStr == "" || timeStr == "") continue;
datetime eventTime = StringToTime(dateStr + " " + timeStr);
int impact = (int)StringToInteger(impactStr);
if(impact >= 3 && (currency == m_currency || currency == "ALL"))
{
int idx = ArraySize(m_events);
ArrayResize(m_events, idx + 1);
m_events[idx].time = eventTime;
m_events[idx].currency = currency;
m_events[idx].event = event;
m_events[idx].impact = impact;
}
}
FileClose(handle);
}
}
if(ArraySize(m_events) == 0) AddBuiltinEvents();
}
void AddBuiltinEvents()
{
datetime now = TimeCurrent();
MqlDateTime dt;
TimeToStruct(now, dt);
for(int monthOffset = 0; monthOffset <= 1; monthOffset++)
{
int year = dt.year;
int month = dt.mon + monthOffset;
if(month > 12) { month = 1; year++; }
datetime firstDay = StringToTime(StringFormat("%04d.%02d.01 00:00:00", year, month));
MqlDateTime firstDt;
TimeToStruct(firstDay, firstDt);
int daysToFriday = (5 - firstDt.day_of_week + 7) % 7;
datetime firstFriday = firstDay + daysToFriday * 86400;
datetime nfpTime = firstFriday + 13 * 3600 + 30 * 60;
if(nfpTime > now - 86400)
{
int idx = ArraySize(m_events);
ArrayResize(m_events, idx + 1);
m_events[idx].time = nfpTime;
m_events[idx].currency = "USD";
m_events[idx].event = "Non-Farm Payrolls";
m_events[idx].impact = 3;
}
}
}
};
#endif // __NEWS_FILTER_MQH__
+266
View File
@@ -0,0 +1,266 @@
//+------------------------------------------------------------------+
//| Logic/RegimeEngine.mqh |
//| Dual-State Logic with ML-based Regime Detection |
//+------------------------------------------------------------------+
#ifndef __REGIME_ENGINE_MQH__
#define __REGIME_ENGINE_MQH__
#include "../Core/Config.mqh"
#include "../Core/State.mqh"
#include "../Core/Logger.mqh"
#include "../Execution/TradeManager.mqh"
extern CLogger g_logger;
extern CTradeManager g_tradeManager;
struct MLFeatureVector
{
double atrRatio;
double adx;
double bbWidth;
double volumeRatio;
double priceMomentum;
};
struct MLRegimeSample
{
MLFeatureVector features;
ENUM_REGIME regime;
};
class CMLRegimeClassifier
{
private:
MLRegimeSample m_trainingData[];
int m_k;
bool m_initialized;
public:
CMLRegimeClassifier() : m_k(5), m_initialized(false) {}
bool Init()
{
LoadDefaultTrainingData();
m_initialized = true;
Print("[MLRegimeClassifier] KNN initialized with ", ArraySize(m_trainingData), " samples");
return true;
}
ENUM_REGIME Predict(const MLFeatureVector &features)
{
if(!m_initialized || ArraySize(m_trainingData) == 0) return REGIME_RANGE;
double distances[];
ArrayResize(distances, ArraySize(m_trainingData));
for(int i = 0; i < ArraySize(m_trainingData); i++)
distances[i] = CalculateDistance(features, m_trainingData[i].features);
int trendVotes = 0, rangeVotes = 0, chopVotes = 0;
for(int k = 0; k < m_k; k++)
{
int nearestIdx = FindMinIndex(distances);
if(nearestIdx < 0) break;
ENUM_REGIME vote = m_trainingData[nearestIdx].regime;
if(vote == REGIME_TREND) trendVotes++;
else if(vote == REGIME_RANGE) rangeVotes++;
else chopVotes++;
distances[nearestIdx] = DBL_MAX;
}
if(trendVotes >= rangeVotes && trendVotes >= chopVotes) return REGIME_TREND;
if(rangeVotes >= trendVotes && rangeVotes >= chopVotes) return REGIME_RANGE;
return REGIME_CHOP;
}
double GetConfidence(const MLFeatureVector &features)
{
if(!m_initialized || ArraySize(m_trainingData) == 0) return 0.5;
double distances[];
ArrayResize(distances, ArraySize(m_trainingData));
for(int i = 0; i < ArraySize(m_trainingData); i++)
distances[i] = CalculateDistance(features, m_trainingData[i].features);
int trendVotes = 0, rangeVotes = 0, chopVotes = 0;
for(int k = 0; k < m_k; k++)
{
int nearestIdx = FindMinIndex(distances);
if(nearestIdx < 0) break;
ENUM_REGIME vote = m_trainingData[nearestIdx].regime;
if(vote == REGIME_TREND) trendVotes++;
else if(vote == REGIME_RANGE) rangeVotes++;
else chopVotes++;
distances[nearestIdx] = DBL_MAX;
}
int maxVotes = MathMax(trendVotes, MathMax(rangeVotes, chopVotes));
return (double)maxVotes / m_k;
}
private:
double CalculateDistance(const MLFeatureVector &a, const MLFeatureVector &b)
{
double d1 = (a.atrRatio - b.atrRatio) / 2.0;
double d2 = (a.adx - b.adx) / 50.0;
double d3 = (a.bbWidth - b.bbWidth) / 0.1;
double d4 = (a.volumeRatio - b.volumeRatio) / 2.0;
double d5 = (a.priceMomentum - b.priceMomentum) / 0.05;
return MathSqrt(d1*d1 + d2*d2 + d3*d3 + d4*d4 + d5*d5);
}
int FindMinIndex(double &arr[])
{
if(ArraySize(arr) == 0) return -1;
int minIdx = 0;
for(int i = 1; i < ArraySize(arr); i++)
if(arr[i] < arr[minIdx]) minIdx = i;
return arr[minIdx] == DBL_MAX ? -1 : minIdx;
}
void LoadDefaultTrainingData()
{
AddSample(2.0, 35.0, 0.08, 1.5, 0.03, REGIME_TREND);
AddSample(1.5, 28.0, 0.06, 1.3, 0.02, REGIME_TREND);
AddSample(3.0, 40.0, 0.12, 2.0, 0.05, REGIME_TREND);
AddSample(0.5, 15.0, 0.02, 0.8, 0.01, REGIME_RANGE);
AddSample(0.7, 18.0, 0.03, 0.9, -0.01, REGIME_RANGE);
AddSample(0.4, 12.0, 0.015, 0.6, 0.005, REGIME_RANGE);
AddSample(0.3, 8.0, 0.01, 0.5, 0.002, REGIME_CHOP);
AddSample(0.6, 10.0, 0.025, 0.7, -0.005, REGIME_CHOP);
AddSample(0.8, 14.0, 0.04, 0.8, 0.008, REGIME_CHOP);
AddSample(1.8, 22.0, 0.05, 1.1, 0.015, REGIME_TREND);
AddSample(0.9, 16.0, 0.035, 0.85, -0.003, REGIME_RANGE);
AddSample(0.2, 5.0, 0.008, 0.4, 0.001, REGIME_CHOP);
}
void AddSample(double atr, double adx, double bbw, double vol, double mom, ENUM_REGIME regime)
{
int idx = ArraySize(m_trainingData);
ArrayResize(m_trainingData, idx + 1);
m_trainingData[idx].features.atrRatio = atr;
m_trainingData[idx].features.adx = adx;
m_trainingData[idx].features.bbWidth = bbw;
m_trainingData[idx].features.volumeRatio = vol;
m_trainingData[idx].features.priceMomentum = mom;
m_trainingData[idx].regime = regime;
}
};
class CRegimeEngine
{
private:
ENUM_REGIME m_lastRegime;
bool m_initialized;
CMLRegimeClassifier m_mlClassifier;
double m_mlConfidence;
public:
bool Init()
{
m_lastRegime = REGIME_RANGE;
m_initialized = true;
m_mlConfidence = 0.0;
if(!m_mlClassifier.Init())
Print("[RegimeEngine] ML classifier init failed. Using traditional method only.");
Print("[RegimeEngine] Dual-state logic initialized (v2.0 with ML)");
return true;
}
void Release() {}
void UpdateState(EAState &state)
{
ENUM_REGIME newRegime = state.currentRegime;
ENUM_REGIME mlRegime = GetMLPrediction(state);
double mlConfidence = m_mlClassifier.GetConfidence(GetCurrentFeatures(state));
if(mlConfidence > 0.6 && mlRegime != newRegime)
{
if(mlRegime == REGIME_CHOP && newRegime != REGIME_CHOP)
{
g_logger.LogEvent("REGIME", StringFormat("ML override: %s -> CHOP (conf: %.2f)", EnumToString(newRegime), mlConfidence));
newRegime = REGIME_CHOP;
}
else if(mlRegime == REGIME_TREND && newRegime == REGIME_RANGE && mlConfidence > 0.75)
{
g_logger.LogEvent("REGIME", StringFormat("ML override: RANGE -> TREND (conf: %.2f)", mlConfidence));
newRegime = REGIME_TREND;
}
}
if(!m_initialized) return;
if(newRegime != m_lastRegime)
{
HandleRegimeChange(m_lastRegime, newRegime, state);
m_lastRegime = newRegime;
}
m_mlConfidence = mlConfidence;
}
string GetStrategyName(const EAState &state) const
{
if(state.currentRegime == REGIME_TREND && state.currentBias != BIAS_NEUTRAL)
return "MOMENTUM (Trend Following)";
else if(state.currentRegime == REGIME_RANGE && state.currentBias == BIAS_NEUTRAL)
return "MEAN REVERSION (Range Trading)";
else if(state.currentRegime == REGIME_CHOP)
return "CAPITAL PRESERVATION (No Trade)";
else
return "MIXED (Caution)";
}
double GetMLConfidence() const { return m_mlConfidence; }
private:
void HandleRegimeChange(ENUM_REGIME oldRegime, ENUM_REGIME newRegime, EAState &state)
{
string msg = StringFormat("REGIME CHANGE: %s -> %s", EnumToString(oldRegime), EnumToString(newRegime));
g_logger.LogEvent("REGIME", msg);
if(newRegime == REGIME_CHOP)
{
g_logger.LogEvent("REGIME", "CHOP detected. Capital preservation mode. Closing ALL.");
g_tradeManager.CloseAllPositions(state, EXIT_REGIME_CHANGE);
return;
}
if(oldRegime == REGIME_TREND && newRegime == REGIME_RANGE)
{
g_logger.LogEvent("REGIME", "Trend->Range. Tightening trailing stops.");
g_tradeManager.TightenStops(state);
}
if(oldRegime == REGIME_RANGE && newRegime == REGIME_TREND)
{
g_logger.LogEvent("REGIME", "Range->Trend. Closing mean-reversion trades.");
g_tradeManager.CloseRangeTrades(state);
}
}
ENUM_REGIME GetMLPrediction(const EAState &state)
{
MLFeatureVector features = GetCurrentFeatures(state);
return m_mlClassifier.Predict(features);
}
MLFeatureVector GetCurrentFeatures(const EAState &state)
{
MLFeatureVector fv;
double atr = g_volatility.GetATR();
double atrBaseline = 0;
int atrHandle = iATR(_Symbol, InpMTF, 14);
if(atrHandle != INVALID_HANDLE)
{
double atrBuf[];
ArraySetAsSeries(atrBuf, true);
if(CopyBuffer(atrHandle, 0, 1, 50, atrBuf) >= 50)
{
double sum = 0;
for(int i = 0; i < 50; i++) sum += atrBuf[i];
atrBaseline = sum / 50.0;
}
IndicatorRelease(atrHandle);
}
fv.atrRatio = (atrBaseline > 0) ? atr / atrBaseline : 1.0;
fv.adx = g_volatility.GetADX();
fv.bbWidth = g_volatility.GetBBWidth();
fv.volumeRatio = state.volumeConfirmed ? 1.2 : 0.8;
MqlRates rates[];
ArraySetAsSeries(rates, true);
if(CopyRates(_Symbol, InpMTF, 1, 6, rates) >= 6)
fv.priceMomentum = (rates[0].close - rates[5].close) / rates[5].close;
else
fv.priceMomentum = 0;
return fv;
}
};
#endif // __REGIME_ENGINE_MQH__