mql5/AdaptiveRegime_EA.mq5

984 lines
38 KiB
MQL5
Raw Permalink Normal View History

2026-04-06 19:33:24 +00:00
//+------------------------------------------------------------------+
//| 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 <Trade\Trade.mqh>
#include <Trade\OrderInfo.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include <Trade\PositionInfo.mqh>
//=== 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);
}
//+------------------------------------------------------------------+