mql5/Experts/VWAP_Close_Predictive_EA.mq5

232 lines
8.2 KiB
MQL5

//+------------------------------------------------------------------+
//| 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 <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\SymbolInfo.mqh>
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<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;
}