260 lignes
9,5 Kio
MQL5
260 lignes
9,5 Kio
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| SessionRevEA.mq5 |
|
||
|
|
//| Copyright 2026, MetaQuotes Ltd. |
|
||
|
|
//| https://www.mql5.com |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
#property copyright "Copyright 2026, MetaQuotes Ltd."
|
||
|
|
#property link "https://www.mql5.com"
|
||
|
|
#property version "1.00"
|
||
|
|
#property strict
|
||
|
|
|
||
|
|
#include <Trade\Trade.mqh>
|
||
|
|
|
||
|
|
//--- Input Parameters
|
||
|
|
input string InpSessionAsian = "00:00-09:00"; // Asian Session Time
|
||
|
|
input string InpSessionLondonPOC = "09:00-11:00"; // London POC Build Window
|
||
|
|
input int InpTicksPerBin = 10; // Ticks per Volume Bin
|
||
|
|
input int InpSLOffsetPips = 10; // SL Offset from London POC (Pips)
|
||
|
|
input double InpLotSize = 0.1; // Lot Size
|
||
|
|
input int InpMagicNumber = 123456; // Magic Number
|
||
|
|
|
||
|
|
//--- Global Variables
|
||
|
|
CTrade m_trade;
|
||
|
|
datetime m_last_trade_date = 0;
|
||
|
|
double m_asian_poc = 0;
|
||
|
|
double m_london_poc = 0;
|
||
|
|
|
||
|
|
int m_asian_start_h, m_asian_start_m;
|
||
|
|
int m_asian_end_h, m_asian_end_m;
|
||
|
|
int m_london_start_h, m_london_start_m;
|
||
|
|
int m_london_end_h, m_london_end_m;
|
||
|
|
|
||
|
|
bool m_asian_ready = false;
|
||
|
|
bool m_london_ready = false;
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Expert initialization function |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
int OnInit()
|
||
|
|
{
|
||
|
|
m_trade.SetExpertMagicNumber(InpMagicNumber);
|
||
|
|
|
||
|
|
if(!ParseSession(InpSessionAsian, m_asian_start_h, m_asian_start_m, m_asian_end_h, m_asian_end_m) ||
|
||
|
|
!ParseSession(InpSessionLondonPOC, m_london_start_h, m_london_start_m, m_london_end_h, m_london_end_m))
|
||
|
|
{
|
||
|
|
Print("Error: Invalid session time format. Use HH:MM-HH:MM");
|
||
|
|
return(INIT_PARAMETERS_INCORRECT);
|
||
|
|
}
|
||
|
|
|
||
|
|
return(INIT_SUCCEEDED);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Expert deinitialization function |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void OnDeinit(const int reason)
|
||
|
|
{
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Expert tick function |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void OnTick()
|
||
|
|
{
|
||
|
|
MqlDateTime dt;
|
||
|
|
TimeCurrent(dt);
|
||
|
|
datetime today = StringToTime(IntegerToString(dt.year)+"."+IntegerToString(dt.mon)+"."+IntegerToString(dt.day));
|
||
|
|
|
||
|
|
// Reset for new day
|
||
|
|
if(m_last_trade_date != today)
|
||
|
|
{
|
||
|
|
// We don't reset the date here, only when a trade is TAKEN
|
||
|
|
// But we should reset POC readiness at the start of Asian or end of day
|
||
|
|
if(dt.hour == 0 && dt.min == 0)
|
||
|
|
{
|
||
|
|
m_asian_ready = false;
|
||
|
|
m_london_ready = false;
|
||
|
|
m_asian_poc = 0;
|
||
|
|
m_london_poc = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate Asian POC if it just ended
|
||
|
|
if(!m_asian_ready && IsTimePassed(m_asian_end_h, m_asian_end_m))
|
||
|
|
{
|
||
|
|
datetime start_time = today + m_asian_start_h * 3600 + m_asian_start_m * 60;
|
||
|
|
datetime end_time = today + m_asian_end_h * 3600 + m_asian_end_m * 60;
|
||
|
|
|
||
|
|
// Handle overnight Asian session
|
||
|
|
if(end_time <= start_time) start_time -= 86400;
|
||
|
|
|
||
|
|
m_asian_poc = CalculatePOC(start_time, end_time);
|
||
|
|
if(m_asian_poc > 0)
|
||
|
|
{
|
||
|
|
m_asian_ready = true;
|
||
|
|
Print("Asian POC established at: ", m_asian_poc);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate London POC if its build window just ended
|
||
|
|
if(m_asian_ready && !m_london_ready && IsTimePassed(m_london_end_h, m_london_end_m))
|
||
|
|
{
|
||
|
|
datetime start_time = today + m_london_start_h * 3600 + m_london_start_m * 60;
|
||
|
|
datetime end_time = today + m_london_end_h * 3600 + m_london_end_m * 60;
|
||
|
|
|
||
|
|
m_london_poc = CalculatePOC(start_time, end_time);
|
||
|
|
if(m_london_poc > 0)
|
||
|
|
{
|
||
|
|
m_london_ready = true;
|
||
|
|
Print("London POC established at: ", m_london_poc);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Trade Execution Logic
|
||
|
|
if(m_asian_ready && m_london_ready && m_last_trade_date != today)
|
||
|
|
{
|
||
|
|
// Check if we are still within a reasonable time for London session trades
|
||
|
|
// (e.g., before NY session starts or London ends)
|
||
|
|
if(dt.hour >= 20) return; // Stop trading late in the day
|
||
|
|
|
||
|
|
if(PositionsTotal() == 0)
|
||
|
|
{
|
||
|
|
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
||
|
|
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
||
|
|
double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT);
|
||
|
|
double sl_dist = InpSLOffsetPips * 10 * point;
|
||
|
|
|
||
|
|
// Short Entry: London POC established ABOVE Asian POC, and price drops below London POC towards Asian POC
|
||
|
|
if(m_london_poc > m_asian_poc)
|
||
|
|
{
|
||
|
|
if(bid < m_london_poc && bid > m_asian_poc)
|
||
|
|
{
|
||
|
|
double sl = m_london_poc + sl_dist;
|
||
|
|
double tp = m_asian_poc;
|
||
|
|
|
||
|
|
if(m_trade.Sell(InpLotSize, _Symbol, bid, sl, tp, "Session Reversion Short"))
|
||
|
|
{
|
||
|
|
m_last_trade_date = today;
|
||
|
|
Print("Short trade opened. Target: Asian POC ", tp);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
// Long Entry: London POC established BELOW Asian POC, and price rises above London POC towards Asian POC
|
||
|
|
else if(m_london_poc < m_asian_poc)
|
||
|
|
{
|
||
|
|
if(ask > m_london_poc && ask < m_asian_poc)
|
||
|
|
{
|
||
|
|
double sl = m_london_poc - sl_dist;
|
||
|
|
double tp = m_asian_poc;
|
||
|
|
|
||
|
|
if(m_trade.Buy(InpLotSize, _Symbol, ask, sl, tp, "Session Reversion Long"))
|
||
|
|
{
|
||
|
|
m_last_trade_date = today;
|
||
|
|
Print("Long trade opened. Target: Asian POC ", tp);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Parse Session String (HH:MM-HH:MM) |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool ParseSession(string session, int &sh, int &sm, int &eh, int &em)
|
||
|
|
{
|
||
|
|
string parts[];
|
||
|
|
if(StringSplit(session, '-', parts) != 2) return false;
|
||
|
|
|
||
|
|
string start_parts[];
|
||
|
|
string end_parts[];
|
||
|
|
if(StringSplit(parts[0], ':', start_parts) != 2) return false;
|
||
|
|
if(StringSplit(parts[1], ':', end_parts) != 2) return false;
|
||
|
|
|
||
|
|
sh = (int)StringToInteger(start_parts[0]);
|
||
|
|
sm = (int)StringToInteger(start_parts[1]);
|
||
|
|
eh = (int)StringToInteger(end_parts[0]);
|
||
|
|
em = (int)StringToInteger(end_parts[1]);
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Check if specified hour/min has passed in current day |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool IsTimePassed(int h, int m)
|
||
|
|
{
|
||
|
|
MqlDateTime dt;
|
||
|
|
TimeCurrent(dt);
|
||
|
|
int current_mins = dt.hour * 60 + dt.min;
|
||
|
|
int target_mins = h * 60 + m;
|
||
|
|
return (current_mins >= target_mins);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Calculate Point of Control (POC) for a time range |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
double CalculatePOC(datetime start_time, datetime end_time)
|
||
|
|
{
|
||
|
|
int start_bar = iBarShift(_Symbol, _Period, start_time);
|
||
|
|
int end_bar = iBarShift(_Symbol, _Period, end_time);
|
||
|
|
|
||
|
|
if(start_bar < 0 || end_bar < 0) return 0;
|
||
|
|
|
||
|
|
// Ensure order
|
||
|
|
int s_bar = (start_bar > end_bar) ? start_bar : end_bar;
|
||
|
|
int e_bar = (start_bar > end_bar) ? end_bar : start_bar;
|
||
|
|
|
||
|
|
double min_price = 0, max_price = 0;
|
||
|
|
for(int i = e_bar; i <= s_bar; i++)
|
||
|
|
{
|
||
|
|
double h = iHigh(_Symbol, _Period, i);
|
||
|
|
double l = iLow(_Symbol, _Period, i);
|
||
|
|
if(i == e_bar) { min_price = l; max_price = h; }
|
||
|
|
else { if(l < min_price) min_price = l; if(h > max_price) max_price = h; }
|
||
|
|
}
|
||
|
|
|
||
|
|
double tick_size = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
|
||
|
|
double bin_size = tick_size * InpTicksPerBin;
|
||
|
|
if(bin_size <= 0) return 0;
|
||
|
|
|
||
|
|
int bin_count = (int)((max_price - min_price) / bin_size) + 1;
|
||
|
|
if(bin_count <= 0) return 0;
|
||
|
|
|
||
|
|
struct Bin { double price; long vol; };
|
||
|
|
Bin bins[];
|
||
|
|
ArrayResize(bins, bin_count);
|
||
|
|
for(int i = 0; i < bin_count; i++) { bins[i].price = min_price + i * bin_size; bins[i].vol = 0; }
|
||
|
|
|
||
|
|
for(int i = e_bar; i <= s_bar; i++)
|
||
|
|
{
|
||
|
|
double h = iHigh(_Symbol, _Period, i);
|
||
|
|
double l = iLow(_Symbol, _Period, i);
|
||
|
|
long vol = iTickVolume(_Symbol, _Period, i);
|
||
|
|
|
||
|
|
int h_idx = (int)((h - min_price) / bin_size);
|
||
|
|
int l_idx = (int)((l - min_price) / bin_size);
|
||
|
|
|
||
|
|
if(h_idx >= bin_count) h_idx = bin_count - 1;
|
||
|
|
if(l_idx < 0) l_idx = 0;
|
||
|
|
|
||
|
|
int bars_covered = h_idx - l_idx + 1;
|
||
|
|
long vol_per_bin = vol / bars_covered;
|
||
|
|
|
||
|
|
for(int j = l_idx; j <= h_idx; j++) bins[j].vol += vol_per_bin;
|
||
|
|
}
|
||
|
|
|
||
|
|
long max_vol = -1;
|
||
|
|
int poc_idx = 0;
|
||
|
|
for(int i = 0; i < bin_count; i++)
|
||
|
|
{
|
||
|
|
if(bins[i].vol > max_vol)
|
||
|
|
{
|
||
|
|
max_vol = bins[i].vol;
|
||
|
|
poc_idx = i;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return bins[poc_idx].price + bin_size / 2.0;
|
||
|
|
}
|
||
|
|
//+------------------------------------------------------------------+
|