//+------------------------------------------------------------------+ //| AdaptiveRegime_EA.mq5 | //| Decision Layer: Mean Reversion vs Trend | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "AK47" #property link "https://www.mql5.com" #property version "1.00" #property strict #include #include #include #include #include //=== ENUMS ============================================================ enum ENUM_VWAP_PERIOD { VWAP_DAILY, // Daily VWAP_SESSION, // Session (NY/Custom) VWAP_WEEKLY, // Weekly VWAP_MONTHLY // Monthly }; enum ENUM_REGIME { REGIME_MEAN_REVERT, // Mean Reversion REGIME_TRENDING, // Trending REGIME_NEUTRAL // Neutral (No New Entries) }; //=== INPUTS: REGIME DETECTION ========================================= input group "═══ REGIME DETECTION (Decision Layer) ═══" input int InpADXPeriod = 14; // ADX Period input int InpBBPeriodRegime = 20; // BB Period (for band width) input double InpBBDevRegime = 2.0; // BB Deviation (for band width) input int InpATRFast = 7; // ATR Fast Period input int InpATRSlow = 21; // ATR Slow Period input double InpWeightADX = 0.40; // Weight: ADX component input double InpWeightBBW = 0.35; // Weight: BB Width component input double InpWeightATR = 0.25; // Weight: ATR Ratio component input double InpThresholdMR = 0.45; // Score Below = Mean Reversion input double InpThresholdTR = 0.55; // Score Above = Trending input double InpBBWLow = 0.005; // BB Width Low Reference input double InpBBWHigh = 0.025; // BB Width High Reference input ENUM_TIMEFRAMES InpRegimeTF = PERIOD_M5; // Regime Indicator Timeframe //=== INPUTS: MEAN REVERSION (VWAP Close Predictive) =================== input group "═══ MEAN REVERSION MODE (VWAP) ═══" input double InpMR_BaseLot = 0.01; // MR: Base Lot Size input double InpMR_Threshold = 0.005; // MR: Metric Threshold (e.g. 0.005) input double InpMR_LotMultiplier = 2.0; // MR: Lot Scaling Multiplier input int InpMR_MaxLevels = 10; // MR: Max Open Levels per side input int InpMR_StopLoss = 0; // MR: Stop Loss (Points, 0=Off) input double InpMR_TakeProfit = 0; // MR: Take Profit (Points, 0=Off) input bool InpMR_UseTrailing = true; // MR: Use Trailing Stop input double InpMR_ExitThreshold = 0.002; // MR: Metric Exit Threshold input double InpMR_TrailPercent = 0.75; // MR: Trail Percent (0.75=3/4) input int InpMR_TrailStep = 20; // MR: Trailing Step (Points) input bool InpMR_CloseOnVWAP = true; // MR: Close on VWAP Cross input group "═══ VWAP SETUP ═══" input ENUM_VWAP_PERIOD InpVWAPPeriod = VWAP_SESSION; // VWAP Anchor Period input int InpVWAPStartHour = 16; // VWAP Start Hour (Broker) input int InpVWAPStartMinute = 30; // VWAP Start Minute (Broker) input int InpVWAPStartDay = 1; // VWAP Start Day (1=Mon) Weekly //=== INPUTS: TRENDING (Martin Effective) ============================== input group "═══ TRENDING MODE (Martingale Grid) ═══" input double InpTR_FixedLot = 0.01; // TR: Fixed Lot input ushort InpTR_Grid = 250; // TR: Grid Distance (Points) input double InpTR_GridMul = 1.2; // TR: Grid Distance Multiplier input double InpTR_VolMul = 1.8; // TR: Volume Multiplier input double InpTR_TPFactor = 2.0; // TR: Take Profit Factor input int InpTR_PosThreshold = 2; // TR: Positions Threshold for TP input int InpTR_MaxPositions = 10; // TR: Max Positions per side input int InpTR_BBPeriod = 18; // TR: Bollinger Bands Period input double InpTR_BBDev = 1.0; // TR: Bollinger Bands Deviation input int InpTR_MALength = 12; // TR: EMA Length (High/Low/Close) //=== INPUTS: NEWS FILTER ============================================== input group "═══ NEWS FILTER ═══" input bool InpUseNewsFilter = true; // Enable News Filter input bool InpCloseBeforeNews = false; // Close Positions Before News input int InpNewsBufferBefore = 30; // Minutes Before News input int InpNewsBufferAfter = 30; // Minutes After News //=== INPUTS: TIME FILTER ============================================== input group "═══ TIME FILTER ═══" input bool InpUseTimeFilter = false; // Enable Time Filter input int InpStartHour = 8; // Trade Start Hour (Broker) input int InpEndHour = 20; // Trade End Hour (Broker) //=== INPUTS: VISUALS ================================================== input group "═══ VISUALS ═══" input bool InpShowComments = true; // Show On-Chart Comments //=== MAGIC NUMBERS ==================================================== #define MAGIC_MR 777111 // Mean Reversion positions #define MAGIC_TR 777222 // Trending positions //=== GLOBAL OBJECTS =================================================== CTrade tradeMR, tradeTR; CPositionInfo posInfo; CSymbolInfo symInfo; CAccountInfo accInfo; //=== REGIME STATE ===================================================== double regimeScore = 0.0; ENUM_REGIME currentRegime = REGIME_NEUTRAL; bool isNewsImminent = false; string newsEventMsg = ""; //=== REGIME INDICATOR HANDLES ========================================= int hADX, hBBRegime, hATRFast, hATRSlow; //=== MEAN REVERSION STATE ============================================= double currVWAP = 0; double currMetric = 0; //=== TRENDING STATE =================================================== int hMAHigh, hMAClose, hMALow; int hBBTrend; int hATRTrend; double nextUpBuy, nextDownBuy; double nextUpSell, nextDownSell; double currentUpBuy, currentDownBuy; double currentUpSell, currentDownSell; bool buyStopSwitch = false, sellStopSwitch = false; bool buyLimitSwitch = false, sellLimitSwitch = false; double Grid_step_buy, Grid_step_sell; //+------------------------------------------------------------------+ //| Expert initialization | //+------------------------------------------------------------------+ int OnInit() { if(!symInfo.Name(_Symbol)) return INIT_FAILED; tradeMR.SetExpertMagicNumber(MAGIC_MR); tradeTR.SetExpertMagicNumber(MAGIC_TR); //--- Regime Detection Indicators hADX = iADX(_Symbol, InpRegimeTF, InpADXPeriod); hBBRegime = iBands(_Symbol, InpRegimeTF, InpBBPeriodRegime, 0, InpBBDevRegime, PRICE_TYPICAL); hATRFast = iATR(_Symbol, InpRegimeTF, InpATRFast); hATRSlow = iATR(_Symbol, InpRegimeTF, InpATRSlow); if(hADX == INVALID_HANDLE || hBBRegime == INVALID_HANDLE || hATRFast == INVALID_HANDLE || hATRSlow == INVALID_HANDLE) { Print("ERROR: Failed to create regime detection indicators!"); return INIT_FAILED; } //--- Trending Mode Indicators hMAHigh = iMA(_Symbol, InpRegimeTF, InpTR_MALength, 0, MODE_EMA, PRICE_HIGH); hMAClose = iMA(_Symbol, InpRegimeTF, InpTR_MALength, 0, MODE_EMA, PRICE_CLOSE); hMALow = iMA(_Symbol, InpRegimeTF, InpTR_MALength, 0, MODE_EMA, PRICE_LOW); hBBTrend = iBands(_Symbol, InpRegimeTF, InpTR_BBPeriod, 0, InpTR_BBDev, PRICE_TYPICAL); hATRTrend= iATR(_Symbol, InpRegimeTF, InpTR_MALength); if(hMAHigh == INVALID_HANDLE || hMAClose == INVALID_HANDLE || hMALow == INVALID_HANDLE || hBBTrend == INVALID_HANDLE || hATRTrend == INVALID_HANDLE) { Print("ERROR: Failed to create trending mode indicators!"); return INIT_FAILED; } //--- Initialize grid levels double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); nextUpBuy = ask + InpTR_Grid * _Point; nextDownBuy = bid - InpTR_Grid * _Point; nextUpSell = ask + InpTR_Grid * _Point; nextDownSell = bid - InpTR_Grid * _Point; Grid_step_buy = InpTR_Grid; Grid_step_sell = InpTR_Grid; Print("AdaptiveRegime EA Initialized | MR Magic: ", MAGIC_MR, " | TR Magic: ", MAGIC_TR); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Expert deinitialization | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Comment(""); if(hADX != INVALID_HANDLE) IndicatorRelease(hADX); if(hBBRegime != INVALID_HANDLE) IndicatorRelease(hBBRegime); if(hATRFast != INVALID_HANDLE) IndicatorRelease(hATRFast); if(hATRSlow != INVALID_HANDLE) IndicatorRelease(hATRSlow); if(hMAHigh != INVALID_HANDLE) IndicatorRelease(hMAHigh); if(hMAClose != INVALID_HANDLE) IndicatorRelease(hMAClose); if(hMALow != INVALID_HANDLE) IndicatorRelease(hMALow); if(hBBTrend != INVALID_HANDLE) IndicatorRelease(hBBTrend); if(hATRTrend != INVALID_HANDLE) IndicatorRelease(hATRTrend); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double midPrice = (bid + ask) / 2.0; //==================================================================== // 1. DECISION LAYER — Calculate Regime Score //==================================================================== regimeScore = CalculateRegimeScore(); currentRegime = DetermineRegime(regimeScore); //==================================================================== // 2. NEWS FILTER //==================================================================== isNewsImminent = false; if(InpUseNewsFilter) { isNewsImminent = IsNewsUpcoming(); if(isNewsImminent && InpCloseBeforeNews) { CloseAllByMagic(MAGIC_MR); CloseAllByMagic(MAGIC_TR); } } //==================================================================== // 3. TIME FILTER //==================================================================== bool inTradeWindow = true; if(InpUseTimeFilter) { MqlDateTime dt; TimeCurrent(dt); if(InpStartHour < InpEndHour) inTradeWindow = (dt.hour >= InpStartHour && dt.hour < InpEndHour); else // overnight window inTradeWindow = (dt.hour >= InpStartHour || dt.hour < InpEndHour); } //==================================================================== // 4. COUNT POSITIONS //==================================================================== int mrBuys = 0, mrSells = 0; int trBuys = 0, trSells = 0; CountPositions(MAGIC_MR, mrBuys, mrSells); CountPositions(MAGIC_TR, trBuys, trSells); //==================================================================== // 5. ALWAYS MANAGE EXISTING POSITIONS (regardless of regime) //==================================================================== // --- Manage Mean Reversion positions (exits, trailing) --- if(mrBuys > 0 || mrSells > 0) { // Need VWAP for MR position management if(UpdateVWAPData()) { if(midPrice > 0) currMetric = currVWAP / midPrice; ManageMRPositions(bid, ask, midPrice); } } // --- Manage Trending positions (SL updates) --- if(trBuys > 0 || trSells > 0) { ManageTRPositions(bid, ask); } //==================================================================== // 6. NEW ENTRIES (only if allowed) //==================================================================== bool canTrade = !isNewsImminent && inTradeWindow; if(canTrade) { // --- Mean Reversion Entries --- if(currentRegime == REGIME_MEAN_REVERT) { if(UpdateVWAPData()) { if(midPrice > 0) currMetric = currVWAP / midPrice; ExecuteMREntries(bid, ask, mrBuys, mrSells); } } // --- Trending Entries --- if(currentRegime == REGIME_TRENDING) { ExecuteTREntries(bid, ask, trBuys, trSells); } } //==================================================================== // 7. RESET GRID if no trending positions //==================================================================== if(trBuys == 0 && trSells == 0) { Grid_step_buy = InpTR_Grid; Grid_step_sell = InpTR_Grid; } //==================================================================== // 8. UPDATE COMMENTS //==================================================================== UpdateComments(mrBuys, mrSells, trBuys, trSells, canTrade); } //+------------------------------------------------------------------+ //| DECISION LAYER: Calculate regime score [0.0 – 1.0] | //+------------------------------------------------------------------+ double CalculateRegimeScore() { //--- ADX Component double adxVal[]; ArraySetAsSeries(adxVal, true); if(CopyBuffer(hADX, 0, 0, 3, adxVal) < 3) return 0.5; // fallback neutral double adxScore = MathMin(MathMax((adxVal[1] - 20.0) / 15.0, 0.0), 1.0); //--- Bollinger Band Width Component double bbMid[], bbUpper[], bbLower[]; ArraySetAsSeries(bbMid, true); ArraySetAsSeries(bbUpper, true); ArraySetAsSeries(bbLower, true); if(CopyBuffer(hBBRegime, 0, 0, 3, bbMid) < 3) return 0.5; if(CopyBuffer(hBBRegime, 1, 0, 3, bbUpper) < 3) return 0.5; if(CopyBuffer(hBBRegime, 2, 0, 3, bbLower) < 3) return 0.5; double bbw = 0; if(bbMid[1] > 0) bbw = (bbUpper[1] - bbLower[1]) / bbMid[1]; double bbwScore = MathMin(MathMax((bbw - InpBBWLow) / (InpBBWHigh - InpBBWLow), 0.0), 1.0); //--- ATR Ratio Component double atrFastVal[], atrSlowVal[]; ArraySetAsSeries(atrFastVal, true); ArraySetAsSeries(atrSlowVal, true); if(CopyBuffer(hATRFast, 0, 0, 3, atrFastVal) < 3) return 0.5; if(CopyBuffer(hATRSlow, 0, 0, 3, atrSlowVal) < 3) return 0.5; double atrRatio = 1.0; if(atrSlowVal[1] > 0) atrRatio = atrFastVal[1] / atrSlowVal[1]; double atrScore = MathMin(MathMax((atrRatio - 0.8) / 0.6, 0.0), 1.0); //--- Weighted Composite Score double totalWeight = InpWeightADX + InpWeightBBW + InpWeightATR; if(totalWeight <= 0) totalWeight = 1.0; double score = (InpWeightADX * adxScore + InpWeightBBW * bbwScore + InpWeightATR * atrScore) / totalWeight; return MathMin(MathMax(score, 0.0), 1.0); } //+------------------------------------------------------------------+ //| Convert score to regime enum with hysteresis | //+------------------------------------------------------------------+ ENUM_REGIME DetermineRegime(double score) { // Use hysteresis: once in a regime, stay until crossing the opposite threshold static ENUM_REGIME lastRegime = REGIME_NEUTRAL; if(score <= InpThresholdMR) lastRegime = REGIME_MEAN_REVERT; else if(score >= InpThresholdTR) lastRegime = REGIME_TRENDING; else { // In the neutral zone — keep previous regime but mark as neutral // This prevents whipsawing. Keep managing existing positions. lastRegime = REGIME_NEUTRAL; } return lastRegime; } //+------------------------------------------------------------------+ //| Count positions by magic number | //+------------------------------------------------------------------+ void CountPositions(int magic, int &buys, int &sells) { buys = 0; sells = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { if(posInfo.SelectByIndex(i) && posInfo.Symbol() == _Symbol && posInfo.Magic() == magic) { if(posInfo.PositionType() == POSITION_TYPE_BUY) buys++; if(posInfo.PositionType() == POSITION_TYPE_SELL) sells++; } } } //+------------------------------------------------------------------+ //| Close all positions by magic number | //+------------------------------------------------------------------+ void CloseAllByMagic(int magic) { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(posInfo.SelectByIndex(i) && posInfo.Symbol() == _Symbol && posInfo.Magic() == magic) { if(magic == MAGIC_MR) tradeMR.PositionClose(posInfo.Ticket()); else tradeTR.PositionClose(posInfo.Ticket()); } } } //====================================================================== // MEAN REVERSION ENGINE (from VWAP Close Predictive EA) //====================================================================== //+------------------------------------------------------------------+ //| MR: Place new entries | //+------------------------------------------------------------------+ void ExecuteMREntries(double bid, double ask, int buys, int sells) { double nextBuyThreshold = 1.0 + (InpMR_Threshold * (buys + 1)); double nextSellThreshold = 1.0 - (InpMR_Threshold * (sells + 1)); // BUY: VWAP > Close (metric > 1 + threshold) means price is below VWAP if(buys < InpMR_MaxLevels && currMetric >= nextBuyThreshold) { double sl = (InpMR_StopLoss > 0) ? ask - (InpMR_StopLoss * _Point) : 0; double tp = (InpMR_TakeProfit > 0) ? ask + (InpMR_TakeProfit * _Point) : 0; double lot = NormalizeDouble(InpMR_BaseLot * MathPow(InpMR_LotMultiplier, buys), 2); if(tradeMR.Buy(lot, _Symbol, ask, NormalizeDouble(sl, _Digits), NormalizeDouble(tp, _Digits), "MR_Buy_L" + IntegerToString(buys + 1))) { PrintFormat("[MR] BUY L%d | Metric=%.5f | Thresh=%.5f | Lot=%.2f", buys+1, currMetric, nextBuyThreshold, lot); } } // SELL: VWAP < Close (metric < 1 - threshold) means price is above VWAP if(sells < InpMR_MaxLevels && currMetric <= nextSellThreshold) { double sl = (InpMR_StopLoss > 0) ? bid + (InpMR_StopLoss * _Point) : 0; double tp = (InpMR_TakeProfit > 0) ? bid - (InpMR_TakeProfit * _Point) : 0; double lot = NormalizeDouble(InpMR_BaseLot * MathPow(InpMR_LotMultiplier, sells), 2); if(tradeMR.Sell(lot, _Symbol, bid, NormalizeDouble(sl, _Digits), NormalizeDouble(tp, _Digits), "MR_Sell_L" + IntegerToString(sells + 1))) { PrintFormat("[MR] SELL L%d | Metric=%.5f | Thresh=%.5f | Lot=%.2f", sells+1, currMetric, nextSellThreshold, lot); } } } //+------------------------------------------------------------------+ //| MR: Manage existing positions (trailing + exit on VWAP) | //+------------------------------------------------------------------+ void ManageMRPositions(double bid, double ask, double midPrice) { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(!posInfo.SelectByIndex(i)) continue; if(posInfo.Symbol() != _Symbol || posInfo.Magic() != MAGIC_MR) continue; ulong ticket = posInfo.Ticket(); double posSl = posInfo.StopLoss(); //--- Trailing Stop Logic (metric-based) if(InpMR_UseTrailing && currVWAP > 0) { double dist = MathAbs(midPrice - currVWAP); if(posInfo.PositionType() == POSITION_TYPE_BUY && currMetric <= 1.0 - InpMR_ExitThreshold) { double newSl = NormalizeDouble(currVWAP + (dist * InpMR_TrailPercent), _Digits); if(posSl == 0 || newSl > posSl + (InpMR_TrailStep * _Point)) { if(newSl > posSl) tradeMR.PositionModify(ticket, newSl, posInfo.TakeProfit()); } } else if(posInfo.PositionType() == POSITION_TYPE_SELL && currMetric >= 1.0 + InpMR_ExitThreshold) { double newSl = NormalizeDouble(currVWAP - (dist * InpMR_TrailPercent), _Digits); if(posSl == 0 || newSl < posSl - (InpMR_TrailStep * _Point)) { if(posSl == 0 || newSl < posSl) tradeMR.PositionModify(ticket, newSl, posInfo.TakeProfit()); } } } //--- Mean Reversion Exit (close on VWAP cross) if(InpMR_CloseOnVWAP && currVWAP > 0) { if(posInfo.PositionType() == POSITION_TYPE_BUY && bid >= currVWAP) { tradeMR.PositionClose(ticket); PrintFormat("[MR] BUY Closed | Reverted to VWAP at %.5f", bid); } else if(posInfo.PositionType() == POSITION_TYPE_SELL && ask <= currVWAP) { tradeMR.PositionClose(ticket); PrintFormat("[MR] SELL Closed | Reverted to VWAP at %.5f", ask); } } } } //+------------------------------------------------------------------+ //| Calculate VWAP for selected period (from VWAP EA) | //+------------------------------------------------------------------+ bool UpdateVWAPData() { static datetime lastCalcBar = 0; datetime currentBar = iTime(_Symbol, PERIOD_M1, 0); if(currentBar == lastCalcBar && currVWAP > 0) return true; lastCalcBar = currentBar; MqlDateTime dt; datetime currentTime = TimeCurrent(); TimeToStruct(currentTime, dt); datetime anchorTime = 0; if(InpVWAPPeriod == VWAP_DAILY || InpVWAPPeriod == VWAP_SESSION) { dt.hour = InpVWAPStartHour; dt.min = InpVWAPStartMinute; dt.sec = 0; anchorTime = StructToTime(dt); if(anchorTime > currentTime) anchorTime -= 86400; } else if(InpVWAPPeriod == VWAP_WEEKLY) { dt.hour = InpVWAPStartHour; dt.min = InpVWAPStartMinute; dt.sec = 0; datetime startOfDay = StructToTime(dt); int daysToSubtract = dt.day_of_week - InpVWAPStartDay; if(daysToSubtract < 0) daysToSubtract += 7; anchorTime = startOfDay - (daysToSubtract * 86400); } else if(InpVWAPPeriod == VWAP_MONTHLY) { dt.day = 1; dt.hour = InpVWAPStartHour; dt.min = InpVWAPStartMinute; dt.sec = 0; anchorTime = StructToTime(dt); } int bars = Bars(_Symbol, PERIOD_M1, anchorTime, currentTime); if(bars <= 0) return false; MqlRates rates[]; if(CopyRates(_Symbol, PERIOD_M1, 0, bars, rates) <= 0) return false; double sumPriceVol = 0; double sumVol = 0; for(int i = 0; i < bars; i++) { double typicalPrice = (rates[i].high + rates[i].low + rates[i].close) / 3.0; sumPriceVol += typicalPrice * (double)rates[i].tick_volume; sumVol += (double)rates[i].tick_volume; } if(sumVol == 0) return false; currVWAP = sumPriceVol / sumVol; return true; } //====================================================================== // TRENDING ENGINE (from Martin Effective EA) //====================================================================== //+------------------------------------------------------------------+ //| TR: Place new entries (breakout grid) | //+------------------------------------------------------------------+ void ExecuteTREntries(double bid, double ask, int trBuys, int trSells) { //--- Copy indicator buffers double MAHigh[], MALow[], MAClose[]; double bbMid[], bbUpper[], bbLower[]; MqlRates bars[]; ArraySetAsSeries(MAHigh, true); ArraySetAsSeries(MALow, true); ArraySetAsSeries(MAClose, true); ArraySetAsSeries(bbMid, true); ArraySetAsSeries(bbUpper, true); ArraySetAsSeries(bbLower, true); ArraySetAsSeries(bars, true); if(CopyBuffer(hMAHigh, 0, 0, 4, MAHigh) < 4) return; if(CopyBuffer(hMALow, 0, 0, 4, MALow) < 4) return; if(CopyBuffer(hMAClose, 0, 0, 4, MAClose) < 4) return; if(CopyBuffer(hBBTrend, 0, 0, 4, bbMid) < 4) return; if(CopyBuffer(hBBTrend, 1, 0, 4, bbUpper) < 4) return; if(CopyBuffer(hBBTrend, 2, 0, 4, bbLower) < 4) return; if(CopyRates(_Symbol, InpRegimeTF, 0, 10, bars) < 10) return; //--- Calculate dynamic grid step and volume Grid_step_buy = InpTR_Grid * MathPow(InpTR_GridMul, trBuys); Grid_step_sell = InpTR_Grid * MathPow(InpTR_GridMul, trSells); double buyVolFactor = MathPow(InpTR_VolMul, trBuys); double sellVolFactor = MathPow(InpTR_VolMul, trSells); double buyVolume = NormalizeDouble(buyVolFactor * InpTR_FixedLot, 2); double sellVolume = NormalizeDouble(sellVolFactor * InpTR_FixedLot, 2); double slBuy = bbUpper[1]; double slSell = bbLower[1]; //--- BuyStop Logic: price breaks up through nextUpBuy if(ask > nextUpBuy) { buyStopSwitch = true; currentUpBuy = nextUpBuy; nextUpBuy = ask + Grid_step_buy * _Point; nextDownBuy = ask - Grid_step_buy * _Point; } if(buyStopSwitch && trBuys < InpTR_MaxPositions && !OtherBuyPosition(MAGIC_TR, Grid_step_buy) && ask > bbUpper[1] && bars[1].high > bars[2].high && (ask > MeanEntryPriceBuy(MAGIC_TR) || trBuys == 0)) { if(tradeTR.Buy(buyVolume, _Symbol, 0, slBuy, 0, "TR_BuyStop_L" + IntegerToString(trBuys + 1))) { PrintFormat("[TR] BUY L%d | Grid=%.0f | Vol=%.2f", trBuys+1, Grid_step_buy, buyVolume); buyStopSwitch = false; } } //--- BuyLimit Logic: price drops through nextDownBuy if(ask < nextDownBuy) { buyLimitSwitch = true; currentDownBuy = nextDownBuy; nextUpBuy = ask + Grid_step_buy * _Point; nextDownBuy = bid - Grid_step_buy * _Point; } //--- SellStop Logic: price breaks down through nextDownSell if(bid < nextDownSell) { sellStopSwitch = true; currentDownSell = nextDownSell; nextUpSell = bid + Grid_step_sell * _Point; nextDownSell = bid - Grid_step_sell * _Point; } if(sellStopSwitch && trSells < InpTR_MaxPositions && !OtherSellPosition(MAGIC_TR, Grid_step_sell) && bid < bbLower[1] && bars[1].low < bars[2].low && (bid < MeanEntryPriceSell(MAGIC_TR) || trSells == 0)) { if(tradeTR.Sell(sellVolume, _Symbol, 0, slSell, 0, "TR_SellStop_L" + IntegerToString(trSells + 1))) { PrintFormat("[TR] SELL L%d | Grid=%.0f | Vol=%.2f", trSells+1, Grid_step_sell, sellVolume); sellStopSwitch = false; } } //--- SellLimit Logic: price rises through nextUpSell if(bid > nextUpSell) { sellLimitSwitch = true; currentUpSell = nextUpSell; nextUpSell = bid + Grid_step_sell * _Point; nextDownSell = bid - Grid_step_sell * _Point; } } //+------------------------------------------------------------------+ //| TR: Manage existing positions (SL updates, TP checks) | //+------------------------------------------------------------------+ void ManageTRPositions(double bid, double ask) { double bbUpper[], bbLower[]; ArraySetAsSeries(bbUpper, true); ArraySetAsSeries(bbLower, true); if(CopyBuffer(hBBTrend, 1, 0, 4, bbUpper) < 4) return; if(CopyBuffer(hBBTrend, 2, 0, 4, bbLower) < 4) return; int buyCount = 0, sellCount = 0; CountPositions(MAGIC_TR, buyCount, sellCount); double meanBuy = MeanEntryPriceBuy(MAGIC_TR); double meanSell = MeanEntryPriceSell(MAGIC_TR); for(int i = PositionsTotal() - 1; i >= 0; i--) { if(!posInfo.SelectByIndex(i)) continue; if(posInfo.Symbol() != _Symbol || posInfo.Magic() != MAGIC_TR) continue; ulong ticket = posInfo.Ticket(); long tradeType = posInfo.PositionType(); double currentSL = posInfo.StopLoss(); double newStopLoss = 0; // BUY positions SL management if(tradeType == POSITION_TYPE_BUY) { if(ask < meanBuy + InpTR_TPFactor * InpTR_Grid * _Point) newStopLoss = bbLower[1]; else newStopLoss = NormalizeDouble((((ask + meanBuy) / 2.0) + ask) / 2.0, _Digits); if(currentSL < newStopLoss) tradeTR.PositionModify(ticket, newStopLoss, posInfo.TakeProfit()); // Close all buys if price exceeds mean + grid (profit target) if(buyCount > InpTR_PosThreshold && ask > meanBuy + InpTR_TPFactor * InpTR_Grid * _Point) { ClosePositionsByType(MAGIC_TR, POSITION_TYPE_BUY); PrintFormat("[TR] All BUYs Closed | Price=%.5f > MeanEntry+TP=%.5f", ask, meanBuy + InpTR_TPFactor * InpTR_Grid * _Point); return; } } // SELL positions SL management if(tradeType == POSITION_TYPE_SELL) { if(bid > meanSell - InpTR_TPFactor * InpTR_Grid * _Point) newStopLoss = bbUpper[1]; else newStopLoss = NormalizeDouble((((bid + meanSell) / 2.0) + bid) / 2.0, _Digits); if(currentSL > newStopLoss || currentSL == 0) tradeTR.PositionModify(ticket, newStopLoss, posInfo.TakeProfit()); // Close all sells if price drops below mean - grid (profit target) if(sellCount > InpTR_PosThreshold && bid < meanSell - InpTR_TPFactor * InpTR_Grid * _Point) { ClosePositionsByType(MAGIC_TR, POSITION_TYPE_SELL); PrintFormat("[TR] All SELLs Closed | Price=%.5f < MeanEntry-TP=%.5f", bid, meanSell - InpTR_TPFactor * InpTR_Grid * _Point); return; } } } } //+------------------------------------------------------------------+ //| TR: Close all positions of a specific type by magic | //+------------------------------------------------------------------+ void ClosePositionsByType(int magic, ENUM_POSITION_TYPE pType) { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(posInfo.SelectByIndex(i) && posInfo.Symbol() == _Symbol && posInfo.Magic() == magic && posInfo.PositionType() == pType) { if(magic == MAGIC_MR) tradeMR.PositionClose(posInfo.Ticket()); else tradeTR.PositionClose(posInfo.Ticket()); } } } //+------------------------------------------------------------------+ //| TR: Check if another buy position is too close (grid filter) | //+------------------------------------------------------------------+ bool OtherBuyPosition(int magic, double gridStep) { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(posInfo.SelectByIndex(i) && posInfo.Symbol() == _Symbol && posInfo.Magic() == magic && posInfo.PositionType() == POSITION_TYPE_BUY) { if(MathAbs(posInfo.PriceCurrent() - posInfo.PriceOpen()) < gridStep * _Point) return true; } } return false; } //+------------------------------------------------------------------+ //| TR: Check if another sell position is too close (grid filter) | //+------------------------------------------------------------------+ bool OtherSellPosition(int magic, double gridStep) { for(int i = PositionsTotal() - 1; i >= 0; i--) { if(posInfo.SelectByIndex(i) && posInfo.Symbol() == _Symbol && posInfo.Magic() == magic && posInfo.PositionType() == POSITION_TYPE_SELL) { if(MathAbs(posInfo.PriceCurrent() - posInfo.PriceOpen()) < gridStep * _Point) return true; } } return false; } //+------------------------------------------------------------------+ //| TR: Volume-weighted mean entry price for buys | //+------------------------------------------------------------------+ double MeanEntryPriceBuy(int magic) { double priceVol = 0; double totalVol = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { if(posInfo.SelectByIndex(i) && posInfo.Symbol() == _Symbol && posInfo.Magic() == magic && posInfo.PositionType() == POSITION_TYPE_BUY) { priceVol += posInfo.Volume() * posInfo.PriceOpen(); totalVol += posInfo.Volume(); } } if(totalVol == 0) return 0; return priceVol / totalVol; } //+------------------------------------------------------------------+ //| TR: Volume-weighted mean entry price for sells | //+------------------------------------------------------------------+ double MeanEntryPriceSell(int magic) { double priceVol = 0; double totalVol = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { if(posInfo.SelectByIndex(i) && posInfo.Symbol() == _Symbol && posInfo.Magic() == magic && posInfo.PositionType() == POSITION_TYPE_SELL) { priceVol += posInfo.Volume() * posInfo.PriceOpen(); totalVol += posInfo.Volume(); } } if(totalVol == 0) return 0; return priceVol / totalVol; } //====================================================================== // NEWS FILTER (from VWAP EA) //====================================================================== //+------------------------------------------------------------------+ //| Check for upcoming high-impact news matching symbol currencies | //+------------------------------------------------------------------+ bool IsNewsUpcoming() { MqlCalendarValue values[]; datetime startTime = TimeCurrent(); datetime endTime = startTime + (InpNewsBufferBefore * 60); if(CalendarValueHistory(values, startTime, endTime) > 0) { for(int i = 0; i < ArraySize(values); i++) { MqlCalendarEvent event; CalendarEventById(values[i].event_id, event); if(event.importance == CALENDAR_IMPORTANCE_HIGH) { MqlCalendarCountry country; CalendarCountryById(event.country_id, country); if(StringFind(_Symbol, country.currency) >= 0) { newsEventMsg = event.name + " (" + country.currency + ")"; return true; } } } } // Check if within 'After' buffer of recent news startTime = TimeCurrent() - (InpNewsBufferAfter * 60); endTime = TimeCurrent(); if(CalendarValueHistory(values, startTime, endTime) > 0) { for(int i = 0; i < ArraySize(values); i++) { MqlCalendarEvent event; CalendarEventById(values[i].event_id, event); if(event.importance == CALENDAR_IMPORTANCE_HIGH) { MqlCalendarCountry country; CalendarCountryById(event.country_id, country); if(StringFind(_Symbol, country.currency) >= 0) { newsEventMsg = event.name + " (Recent)"; return true; } } } } return false; } //====================================================================== // CHART COMMENTS / HUD //====================================================================== //+------------------------------------------------------------------+ //| Update chart commentary with full status | //+------------------------------------------------------------------+ void UpdateComments(int mrBuys, int mrSells, int trBuys, int trSells, bool canTrade) { if(!InpShowComments) { Comment(""); return; } string regimeStr = ""; string regimeIcon = ""; switch(currentRegime) { case REGIME_MEAN_REVERT: regimeStr = "MEAN REVERSION"; regimeIcon = "◆"; break; case REGIME_TRENDING: regimeStr = "TRENDING"; regimeIcon = "▲"; break; case REGIME_NEUTRAL: regimeStr = "NEUTRAL"; regimeIcon = "●"; break; } // Build score bar visualization [|||| ] string scoreBar = "["; int filled = (int)MathRound(regimeScore * 20); for(int i = 0; i < 20; i++) { if(i < filled) scoreBar += "|"; else scoreBar += "."; } scoreBar += "]"; string comment = StringFormat( "══════════════════════════════════════\n" " %s ADAPTIVE REGIME EA %s\n" "══════════════════════════════════════\n" "REGIME: %s %s\n" "Score: %.3f %s\n" " MR ◄━━━━━━━━━━━━━━━━━━━━► TR\n" "──────────────────────────────────────\n" " MEAN REVERSION (VWAP)\n" " VWAP: %.5f | Metric: %.5f\n" " Buys: %d | Sells: %d\n" "──────────────────────────────────────\n" " TRENDING (Grid)\n" " Grid Buy: %.0f | Grid Sell: %.0f\n" " Buys: %d | Sells: %d\n" "──────────────────────────────────────\n" " Trading: %s\n" " News: %s\n" "══════════════════════════════════════", regimeIcon, regimeIcon, regimeStr, (currentRegime == REGIME_MEAN_REVERT ? " ↔" : (currentRegime == REGIME_TRENDING ? " ↗" : " —")), regimeScore, scoreBar, currVWAP, currMetric, mrBuys, mrSells, Grid_step_buy, Grid_step_sell, trBuys, trSells, (canTrade ? "ACTIVE" : "SUSPENDED"), (isNewsImminent ? "⚠ " + newsEventMsg : "✓ Clear")); Comment(comment); } //+------------------------------------------------------------------+