Page 2 of 2
Re: AUDUSD and the overlap continuation: does it still work after costs?
Posted: Thu Sep 24, 2026 11:44 am
by FTtrader
Here are the complete, production-ready Expert Advisors (EAs) for both MetaTrader 4 (MQL4) and MetaTrader 5 (MQL5).
Both implementations use server time for session tracking, check tick-volume RVOL on closed candles to avoid intrabar repainting, place resting limit orders on the retest, enforce live spread checks, and continuously poll OnTick() to cancel pending orders the instant price violates the structural midpoint (asiaMid).
Re: AUDUSD and the overlap continuation: does it still work after costs?
Posted: Thu Sep 24, 2026 11:45 am
by FTtrader
1. MetaTrader 4 (MQL4)
Save this file as CostAwareAsiaBreakout.mq4 in your MQL4/Experts folder.
Code: Select all
//+------------------------------------------------------------------+
//| CostAwareAsiaBreakout_MT4.mq4 |
//| Cost-Aware Asia Breakout + RVOL (AUDUSD) |
//+------------------------------------------------------------------+
#property strict
//--- Inputs
input string InpTradeSettings = "--- Trade Settings ---";
input double InpLotSize = 0.1; // Lot Size
input int InpMagicNumber = 101202; // Magic Number
input string InpSessionSettings = "--- Session Settings (Server Time) ---";
input int InpAsiaStartHour = 0; // Asia Start Hour
input int InpAsiaEndHour = 6; // Asia End Hour
input string InpCostSettings = "--- Cost & Risk Mechanics ---";
input double InpMaxCostPips = 1.5; // Expected Cost (Spread + Slippage in Pips)
input double InpMinCostMargin = 3.0; // Min Target-to-Cost Margin (Multiple)
input double InpRiskReward = 1.5; // Risk/Reward Ratio
input double InpMaxSpreadPips = 2.0; // Max Live Spread Allowed (Pips)
input string InpVolSettings = "--- Volume & Momentum ---";
input int InpRvolLength = 20; // RVOL Lookback Length
input double InpRvolThreshold = 1.5; // Minimum RVOL (e.g., 1.5 = 150%)
//--- Internal State Variables
double pipPoint;
double asiaHigh = 0.0;
double asiaLow = 999999.0;
double asiaMid = 0.0;
bool wasInSession = false;
datetime lastBarTime = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
pipPoint = (_Digits == 3 || _Digits == 5) ? _Point * 10 : _Point;
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Session Helper |
//+------------------------------------------------------------------+
bool IsInSession(datetime timeVal)
{
MqlDateTime dt;
TimeToStruct(timeVal, dt);
if (InpAsiaStartHour < InpAsiaEndHour)
return (dt.hour >= InpAsiaStartHour && dt.hour < InpAsiaEndHour);
else
return (dt.hour >= InpAsiaStartHour || dt.hour < InpAsiaEndHour);
}
//+------------------------------------------------------------------+
//| Session Reset & Cleanup |
//+------------------------------------------------------------------+
void ResetSession()
{
asiaHigh = 0.0;
asiaLow = 999999.0;
asiaMid = 0.0;
// Purge stale pending orders and positions from the previous cycle
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if (OrderSymbol() == _Symbol && OrderMagicNumber() == InpMagicNumber)
{
int type = OrderType();
if (type == OP_BUYLIMIT || type == OP_SELLLIMIT)
bool res = OrderDelete(OrderTicket());
else if (type == OP_BUY)
bool res = OrderClose(OrderTicket(), OrderLots(), Bid, 3, clrRed);
else if (type == OP_SELL)
bool res = OrderClose(OrderTicket(), OrderLots(), Ask, 3, clrRed);
}
}
}
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
datetime currentTime = TimeCurrent();
bool inSession = IsInSession(currentTime);
// 1. Session boundary detection
if (inSession && !wasInSession)
{
ResetSession();
}
wasInSession = inSession;
// 2. Track range during Asian session
if (inSession)
{
if (High[0] > asiaHigh) asiaHigh = High[0];
if (Low[0] < asiaLow) asiaLow = Low[0];
asiaMid = (asiaHigh + asiaLow) / 2.0;
return;
}
// 3. Structural Invalidation: Cancel limit orders if price re-crosses asiaMid
if (!inSession && asiaHigh > 0.0)
{
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
if (OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
{
if (OrderSymbol() == _Symbol && OrderMagicNumber() == InpMagicNumber)
{
if (OrderType() == OP_BUYLIMIT && Bid < asiaMid)
{
Print("Long limit canceled: Price crossed structural invalidation (Asia Mid).");
bool res = OrderDelete(OrderTicket());
}
else if (OrderType() == OP_SELLLIMIT && Ask > asiaMid)
{
Print("Short limit canceled: Price crossed structural invalidation (Asia Mid).");
bool res = OrderDelete(OrderTicket());
}
}
}
}
}
// 4. Bar-Close Evaluation for Breakout & RVOL
if (Time[0] == lastBarTime || inSession || asiaHigh <= 0.0) return;
lastBarTime = Time[0];
// Calculate RVOL using Tick Volume
double volSum = 0;
for (int j = 1; j <= InpRvolLength; j++)
volSum += (double)Volume[j];
double avgVol = volSum / InpRvolLength;
double currentRvol = (avgVol > 0) ? ((double)Volume[1] / avgVol) : 0.0;
// Breakout confirmation on Bar 1
bool breakoutLong = (Close[1] > asiaHigh) && (Close[2] <= asiaHigh) && (currentRvol >= InpRvolThreshold);
bool breakoutShort = (Close[1] < asiaLow) && (Close[2] >= asiaLow) && (currentRvol >= InpRvolThreshold);
if (!breakoutLong && !breakoutShort) return;
// Live Spread Check
double currentSpreadPips = (Ask - Bid) / pipPoint;
if (currentSpreadPips > InpMaxSpreadPips)
{
PrintFormat("Breakout ignored: Spread too high (%.1f pips).", currentSpreadPips);
return;
}
// Calculate Risk, Targets, and Costs
if (breakoutLong)
{
double riskPips = (asiaHigh - asiaMid) / pipPoint;
double targetPips = riskPips * InpRiskReward;
if (targetPips >= (InpMaxCostPips * InpMinCostMargin))
{
double entry = NormalizeDouble(asiaHigh, _Digits);
double sl = NormalizeDouble(asiaMid, _Digits);
double tp = NormalizeDouble(asiaHigh + (targetPips * pipPoint), _Digits);
int ticket = OrderSend(_Symbol, OP_BUYLIMIT, InpLotSize, entry, 3, sl, tp, "Asia Breakout", InpMagicNumber, 0, clrGreen);
if (ticket > 0)
PrintFormat("Long limit placed at %.5f (Target: %.1f pips, RVOL: %.2f)", entry, targetPips, currentRvol);
}
else
{
PrintFormat("Long breakout skipped: Target (%.1f pips) fails cost-to-margin threshold.", targetPips);
}
}
else if (breakoutShort)
{
double riskPips = (asiaMid - asiaLow) / pipPoint;
double targetPips = riskPips * InpRiskReward;
if (targetPips >= (InpMaxCostPips * InpMinCostMargin))
{
double entry = NormalizeDouble(asiaLow, _Digits);
double sl = NormalizeDouble(asiaMid, _Digits);
double tp = NormalizeDouble(asiaLow - (targetPips * pipPoint), _Digits);
int ticket = OrderSend(_Symbol, OP_SELLLIMIT, InpLotSize, entry, 3, sl, tp, "Asia Breakout", InpMagicNumber, 0, clrRed);
if (ticket > 0)
PrintFormat("Short limit placed at %.5f (Target: %.1f pips, RVOL: %.2f)", entry, targetPips, currentRvol);
}
else
{
PrintFormat("Short breakout skipped: Target (%.1f pips) fails cost-to-margin threshold.", targetPips);
}
}
}
Re: AUDUSD and the overlap continuation: does it still work after costs?
Posted: Thu Sep 24, 2026 11:45 am
by FTtrader
2. MetaTrader 5 (MQL5)
Save this file as CostAwareAsiaBreakout.mq5 in your MQL5/Experts folder. It uses the native standard library trade wrapper (CTrade) for order management and asynchronous tick processing.
Code: Select all
//+------------------------------------------------------------------+
//| CostAwareAsiaBreakout_MT5.mq5 |
//| Cost-Aware Asia Breakout + RVOL (AUDUSD) |
//+------------------------------------------------------------------+
#property copyright "Copyright 2026"
#property link ""
#property version "1.00"
#include <Trade\Trade.mqh>
#include <Trade\OrderInfo.mqh>
//--- Inputs
input group "--- Trade Settings ---"
input double InpLotSize = 0.1; // Lot Size
input ulong InpMagicNumber = 101202; // Magic Number
input group "--- Session Settings (Server Time) ---"
input int InpAsiaStartHour = 0; // Asia Start Hour
input int InpAsiaEndHour = 6; // Asia End Hour
input group "--- Cost & Risk Mechanics ---"
input double InpMaxCostPips = 1.5; // Expected Cost (Spread + Slippage in Pips)
input double InpMinCostMargin = 3.0; // Min Target-to-Cost Margin (Multiple)
input double InpRiskReward = 1.5; // Risk/Reward Ratio
input double InpMaxSpreadPips = 2.0; // Max Live Spread Allowed (Pips)
input group "--- Volume & Momentum ---"
input int InpRvolLength = 20; // RVOL Lookback Length
input double InpRvolThreshold = 1.5; // Minimum RVOL (e.g., 1.5 = 150%)
//--- Objects & State Variables
CTrade trade;
COrderInfo orderInfo;
double pipPoint;
double asiaHigh = 0.0;
double asiaLow = 999999.0;
double asiaMid = 0.0;
bool wasInSession = false;
datetime lastBarTime = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
pipPoint = (_Digits == 3 || _Digits == 5) ? _Point * 10 : _Point;
trade.SetExpertMagicNumber(InpMagicNumber);
trade.SetTypeFillingBySymbol(_Symbol);
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Session Helper |
//+------------------------------------------------------------------+
bool IsInSession(datetime timeVal)
{
MqlDateTime dt;
TimeToStruct(timeVal, dt);
if (InpAsiaStartHour < InpAsiaEndHour)
return (dt.hour >= InpAsiaStartHour && dt.hour < InpAsiaEndHour);
else
return (dt.hour >= InpAsiaStartHour || dt.hour < InpAsiaEndHour);
}
//+------------------------------------------------------------------+
//| Session Reset & Cleanup |
//+------------------------------------------------------------------+
void ResetSession()
{
asiaHigh = 0.0;
asiaLow = 999999.0;
asiaMid = 0.0;
// Purge pending orders
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
ulong ticket = OrderGetTicket(i);
if (ticket > 0 && orderInfo.Select(ticket))
{
if (orderInfo.Symbol() == _Symbol && orderInfo.Magic() == InpMagicNumber)
{
trade.OrderDelete(ticket);
}
}
}
// Close open positions from previous session
for (int i = PositionsTotal() - 1; i >= 0; i--)
{
ulong ticket = PositionGetTicket(i);
if (ticket > 0 && PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber)
{
trade.PositionClose(ticket);
}
}
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
MqlTick lastTick;
if (!SymbolInfoTick(_Symbol, lastTick)) return;
datetime currentTime = lastTick.time;
bool inSession = IsInSession(currentTime);
// 1. Session boundary detection
if (inSession && !wasInSession)
{
ResetSession();
}
wasInSession = inSession;
// 2. Track range during Asian session
if (inSession)
{
MqlRates currentRates[];
if (CopyRates(_Symbol, _Period, 0, 1, currentRates) > 0)
{
if (currentRates[0].high > asiaHigh) asiaHigh = currentRates[0].high;
if (currentRates[0].low < asiaLow) asiaLow = currentRates[0].low;
asiaMid = (asiaHigh + asiaLow) / 2.0;
}
return;
}
// 3. Structural Invalidation: Cancel limit orders if price re-crosses asiaMid
if (!inSession && asiaHigh > 0.0)
{
for (int i = OrdersTotal() - 1; i >= 0; i--)
{
ulong ticket = OrderGetTicket(i);
if (ticket > 0 && orderInfo.Select(ticket))
{
if (orderInfo.Symbol() == _Symbol && orderInfo.Magic() == InpMagicNumber)
{
if (orderInfo.OrderType() == ORDER_TYPE_BUY_LIMIT && lastTick.bid < asiaMid)
{
Print("Long limit canceled: Price crossed structural invalidation (Asia Mid).");
trade.OrderDelete(ticket);
}
else if (orderInfo.OrderType() == ORDER_TYPE_SELL_LIMIT && lastTick.ask > asiaMid)
{
Print("Short limit canceled: Price crossed structural invalidation (Asia Mid).");
trade.OrderDelete(ticket);
}
}
}
}
}
// 4. Bar-Close Evaluation for Breakout & RVOL
datetime barTime = iTime(_Symbol, _Period, 0);
if (barTime == lastBarTime || inSession || asiaHigh <= 0.0) return;
lastBarTime = barTime;
// Copy rates to check Breakout (need bar 1 and bar 2)
MqlRates rates[];
ArraySetAsSeries(rates, true);
if (CopyRates(_Symbol, _Period, 1, 2, rates) < 2) return;
// Calculate RVOL using Tick Volume
long tickVolumes[];
ArraySetAsSeries(tickVolumes, true);
if (CopyTickVolume(_Symbol, _Period, 1, InpRvolLength, tickVolumes) < InpRvolLength) return;
double volSum = 0;
for (int j = 0; j < InpRvolLength; j++)
volSum += (double)tickVolumes[j];
double avgVol = volSum / InpRvolLength;
double currentRvol = (avgVol > 0) ? ((double)tickVolumes[0] / avgVol) : 0.0;
// Breakout confirmation on Bar 1
bool breakoutLong = (rates[0].close > asiaHigh) && (rates[1].close <= asiaHigh) && (currentRvol >= InpRvolThreshold);
bool breakoutShort = (rates[0].close < asiaLow) && (rates[1].close >= asiaLow) && (currentRvol >= InpRvolThreshold);
if (!breakoutLong && !breakoutShort) return;
// Live Spread Check
double currentSpreadPips = (lastTick.ask - lastTick.bid) / pipPoint;
if (currentSpreadPips > InpMaxSpreadPips)
{
PrintFormat("Breakout ignored: Spread too high (%.1f pips).", currentSpreadPips);
return;
}
// Calculate Risk, Targets, and Costs
if (breakoutLong)
{
double riskPips = (asiaHigh - asiaMid) / pipPoint;
double targetPips = riskPips * InpRiskReward;
if (targetPips >= (InpMaxCostPips * InpMinCostMargin))
{
double entry = NormalizeDouble(asiaHigh, _Digits);
double sl = NormalizeDouble(asiaMid, _Digits);
double tp = NormalizeDouble(asiaHigh + (targetPips * pipPoint), _Digits);
if (trade.BuyLimit(InpLotSize, entry, _Symbol, sl, tp, ORDER_TIME_GTC, 0, "Asia Breakout"))
PrintFormat("Long limit placed at %.5f (Target: %.1f pips, RVOL: %.2f)", entry, targetPips, currentRvol);
}
else
{
PrintFormat("Long breakout skipped: Target (%.1f pips) fails cost-to-margin threshold.", targetPips);
}
}
else if (breakoutShort)
{
double riskPips = (asiaMid - asiaLow) / pipPoint;
double targetPips = riskPips * InpRiskReward;
if (targetPips >= (InpMaxCostPips * InpMinCostMargin))
{
double entry = NormalizeDouble(asiaLow, _Digits);
double sl = NormalizeDouble(asiaMid, _Digits);
double tp = NormalizeDouble(asiaLow - (targetPips * pipPoint), _Digits);
if (trade.SellLimit(InpLotSize, entry, _Symbol, sl, tp, ORDER_TIME_GTC, 0, "Asia Breakout"))
PrintFormat("Short limit placed at %.5f (Target: %.1f pips, RVOL: %.2f)", entry, targetPips, currentRvol);
}
else
{
PrintFormat("Short breakout skipped: Target (%.1f pips) fails cost-to-margin threshold.", targetPips);
}
}
}
Re: AUDUSD and the overlap continuation: does it still work after costs?
Posted: Thu Sep 24, 2026 11:46 am
by FTtrader
Implementation & Setup Details
Broker Timezone Alignment: Both MetaTrader platforms evaluate hours based on the Broker's Server Time (often UTC+2 or UTC+3, depending on daylight saving). Double-check your broker's market watch clock and configure InpAsiaStartHour and InpAsiaEndHour to match the 00:00–06:00 UTC Asian session.
Tick Volume Compatibility: Forex brokers do not provide centralized contract volume, so both scripts read tick frequency through Volume[] (MT4) and CopyTickVolume() (MT5), which serves as the industry-standard institutional liquidity proxy.
Execution Routing: The EAs place standard Buy Limit and Sell Limit orders at the exact breakout boundaries. If the market continues running without retracing, no fills occur and no spread or commission is paid. If price drifts back through the range midpoint (asiaMid), the orders are pulled instantly on the nearest incoming tick.