//+------------------------------------------------------------------+ //| VWAP_Close_Predictive_EA.mq5 | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #property strict #include #include #include enum ENUM_VWAP_PERIOD { VWAP_DAILY, // Daily VWAP_WEEKLY, // Weekly VWAP_MONTHLY // Monthly }; //--- INPUTS input group "Trading Parameters" input double InpBaseLot = 0.01; // Base Lot Size input double InpThreshold = 0.005; // Metric Threshold (e.g. 0.005) input double InpLotMultiplier = 2.0; // Lot Scaling Multiplier input int InpMaxLevels = 10; // Maximum Open Levels per side input int InpStopLoss = 0; // Stop Loss (Points, 0 = Disabled) input int InpTakeProfit = 0; // Take Profit (Points, 0 = Disabled) input group "VWAP Setup" input ENUM_VWAP_PERIOD InpVWAPPeriod = VWAP_DAILY; // VWAP Anchor Period input int InpStartHour = 0; // Start Hour for Daily input int InpStartDay = 1; // Start Day (1=Mon) for Weekly input group "Visuals" input bool InpShowComments = true; // Show On-Chart Comments //--- GLOBALS CTrade trade; CPositionInfo posInfo; CSymbolInfo symInfo; double currVWAP = 0; double currMetric = 0; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { if(!symInfo.Name(_Symbol)) return INIT_FAILED; trade.SetExpertMagicNumber(777111); // Unique Magic return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { Comment(""); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double midPrice = (bid + ask) / 2.0; // 1. Calculate VWAP if(!UpdateVWAPData()) return; // 2. Calculate VWAP / Close metric (using midPrice for tick-by-tick 'close') if(midPrice > 0) currMetric = currVWAP / midPrice; else return; // 3. Count Open Positions int buys = 0; int sells = 0; for(int i=PositionsTotal()-1; i>=0; i--) { if(posInfo.SelectByIndex(i) && posInfo.Symbol() == _Symbol && posInfo.Magic() == 777111) { if(posInfo.PositionType() == POSITION_TYPE_BUY) buys++; else if(posInfo.PositionType() == POSITION_TYPE_SELL) sells++; } } // 4. Entry Logic double nextBuyThreshold = 1.0 + (InpThreshold * (buys + 1)); double nextSellThreshold = 1.0 - (InpThreshold * (sells + 1)); if(buys < InpMaxLevels && currMetric >= nextBuyThreshold) { double sl = (InpStopLoss > 0) ? ask - (InpStopLoss * _Point) : 0; double tp = (InpTakeProfit > 0) ? ask + (InpTakeProfit * _Point) : 0; double lot = NormalizeDouble(InpBaseLot * MathPow(InpLotMultiplier, buys), 2); if(trade.Buy(lot, _Symbol, ask, NormalizeDouble(sl, _Digits), NormalizeDouble(tp, _Digits), "VWAP/Close Buy Level " + (string)(buys+1))) { PrintFormat("BUY Level %d Opened: Metric = %.5f, Threshold = %.5f, Lot = %.2f", buys+1, currMetric, nextBuyThreshold, lot); } } if(sells < InpMaxLevels && currMetric <= nextSellThreshold) { double sl = (InpStopLoss > 0) ? bid + (InpStopLoss * _Point) : 0; double tp = (InpTakeProfit > 0) ? bid - (InpTakeProfit * _Point) : 0; double lot = NormalizeDouble(InpBaseLot * MathPow(InpLotMultiplier, sells), 2); if(trade.Sell(lot, _Symbol, bid, NormalizeDouble(sl, _Digits), NormalizeDouble(tp, _Digits), "VWAP/Close Sell Level " + (string)(sells+1))) { PrintFormat("SELL Level %d Opened: Metric = %.5f, Threshold = %.5f, Lot = %.2f", sells+1, currMetric, nextSellThreshold, lot); } } // 5. Mean Reversion Exit Logic (Revert back to VWAP) for(int i=PositionsTotal()-1; i>=0; i--) { if(posInfo.SelectByIndex(i) && posInfo.Symbol() == _Symbol && posInfo.Magic() == 777111) { if(posInfo.PositionType() == POSITION_TYPE_BUY && bid >= currVWAP) { trade.PositionClose(posInfo.Ticket()); PrintFormat("BUY Closed: Reverted to VWAP at %.5f", bid); } else if(posInfo.PositionType() == POSITION_TYPE_SELL && ask <= currVWAP) { trade.PositionClose(posInfo.Ticket()); PrintFormat("SELL Closed: Reverted to VWAP at %.5f", ask); } } } // 6. Comments UpdateComments(buys, sells); } //+------------------------------------------------------------------+ //| Updates the chart commentary with status and diagnostics | //+------------------------------------------------------------------+ void UpdateComments(int buys, int sells) { if(!InpShowComments) { Comment(""); return; } double nextBuyThreshold = 1.0 + (InpThreshold * (buys + 1)); double nextSellThreshold = 1.0 - (InpThreshold * (sells + 1)); string comment = StringFormat("--- VWAP/Close Predictive Power EA ---\n" "VWAP Mode: %s\n" "Current VWAP: %.5f\n" "Current Metric (VWAP/Close): %.5f\n" "Next Buy Threshold: %.5f\n" "Next Sell Threshold: %.5f\n" "Open Buys: %d | Open Sells: %d", EnumToString(InpVWAPPeriod), currVWAP, currMetric, nextBuyThreshold, nextSellThreshold, buys, sells); Comment(comment); } //+------------------------------------------------------------------+ //| Calculate VWAP for the selected period | //+------------------------------------------------------------------+ bool UpdateVWAPData() { // Optimization: Only recalculate on a new M1 bar for backtesting speed 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) { dt.hour = InpStartHour; dt.min = 0; dt.sec = 0; anchorTime = StructToTime(dt); if(anchorTime > currentTime) { // Go back 1 day if we are before the start hour anchorTime -= 86400; } } else if(InpVWAPPeriod == VWAP_WEEKLY) { dt.hour = InpStartHour; dt.min = 0; dt.sec = 0; datetime startOfDay = StructToTime(dt); int daysToSubtract = dt.day_of_week - InpStartDay; if(daysToSubtract < 0) daysToSubtract += 7; anchorTime = startOfDay - (daysToSubtract * 86400); } else if(InpVWAPPeriod == VWAP_MONTHLY) { dt.day = 1; dt.hour = InpStartHour; dt.min = 0; 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