232 lines
8.2 KiB
MQL5
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;
|
|
}
|