Save this file in your MetaTrader 5 MQL5/Experts/ directory and compile it in MetaEditor (F7). Attach it directly to an XAUUSD M15 chart.
Code: Select all
//+------------------------------------------------------------------+
//| XAUUSD_PurePriceAction_Scalper.mq5 |
//| Multi-TF H4/D1 Zone & M15 Price Action EA |
//+------------------------------------------------------------------+
#property copyright "PTScalper Quant Architecture"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\AccountInfo.mqh>
//--- Input Parameters: Strategy & Zones
input group "=== 1. Multi-Timeframe Structure (H4 / D1) ==="
input int InpLookbackH4 = 120; // H4 Bars Lookback for Swing Zones
input int InpLookbackD1 = 40; // D1 Bars Lookback for Swing Zones
input int InpFractalStrength = 2; // Swing Fractal Bars Left/Right
input double InpZoneToleranceUSD = 0.80; // Zone Touch / Merge Tolerance ($ in Gold)
input double InpMaxSweepDepthUSD = 4.50; // Max Allowed Liquidity Sweep Pierce ($ in Gold)
input bool InpEnableSetupA = true; // Enable Setup A: Liquidity Sweep & Reclaim
input bool InpEnableSetupB = true; // Enable Setup B: Breakout & Micro-Retest
input bool InpDrawZones = true; // Draw H4/D1 Structural Lines on Chart
//--- Input Parameters: Execution, Stops & Scratch Filter
input group "=== 2. Risk-Reward & Time Scratch Rules ==="
input double InpMinStopLossUSD = 3.00; // Minimum Structural Stop Loss ($ in Gold)
input double InpMaxStopLossUSD = 6.50; // Maximum Structural Stop Loss ($ in Gold)
input double InpStopBufferUSD = 0.50; // Extra Buffer Beyond Sweep Wick ($ in Gold)
input double InpRiskToRewardRatio = 3.0; // Risk-to-Reward Ratio (e.g., 3.0 = Risk 3 : Reward 1)
input int InpScratchMaxBars = 3; // Time-Scratch: Max M15 Bars if Trade Stalls (0=Off)
input double InpScratchMinProfitUSD= 0.25; // Min Favorable Move ($) to Avoid Time-Scratch
//--- Input Parameters: Series Money Management
input group "=== 3. Series Exponential Money Management ==="
input double InpSeriesStartCapital = 5000.0; // Series Baseline Capital (Account Currency)
input double InpPhase1BaseRiskPct = 3.0; // Phase 1: Base Risk % per Trade (2% - 5%)
input double InpPhase2TriggerPct = 15.0; // Profit Buffer % to Unlock Phase 2 Compounding
input double InpPhase2ProfitRiskPct= 25.0; // Phase 2: % of Accrued Profits Risked per Trade
input int InpMaxDailyLosses = 2; // Daily Circuit Breaker (Max Consecutive Full Losses)
//--- Input Parameters: Session & Broker Filters
input group "=== 4. Session Windows (Server / UTC Time) ==="
input bool InpUseSessionFilter = true; // Restrict to London & NY Sessions
input int InpSession1StartHour = 7; // Session 1 Start Hour (Frankfurt/London Open)
input int InpSession1EndHour = 10; // Session 1 End Hour
input int InpSession2StartHour = 12; // Session 2 Start Hour (London/NY Overlap)
input int InpSession2EndHour = 16; // Session 2 End Hour
input int InpMaxSpreadPoints = 35; // Max Allowed Spread (in Broker Points)
input ulong InpMagicNumber = 20261025; // EA Magic Number
//--- Global Objects & State Variables
CTrade ExtTrade;
CPositionInfo ExtPosition;
CAccountInfo ExtAccount;
datetime g_lastM15BarTime = 0;
datetime g_lastZoneUpdateH4 = 0;
double g_srLevels[];
// Breakout State Tracking for Setup B
double g_activeBreakoutLevel = 0.0;
int g_breakoutDirection = 0; // 1 = Bullish Breakout, -1 = Bearish Breakout
datetime g_breakoutBarTime = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
ExtTrade.SetExpertMagicNumber(InpMagicNumber);
ExtTrade.SetMarginMode();
ExtTrade.SetTypeFillingBySymbol(_Symbol);
ExtTrade.SetDeviationInPoints(20);
RebuildStructuralZones();
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
ObjectsDeleteAll(0, "XAU_SR_");
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
// 1. Manage Open Positions (Time-Based Scratch Exit on Stalled Momentum)
ManageOpenPositions();
// 2. Process New M15 Candle Closure Only for Pure Price Action Signals
datetime currentM15Bar = iTime(_Symbol, PERIOD_M15, 0);
if(currentM15Bar == g_lastM15BarTime)
return;
// Check Session Filter & Daily Circuit Breaker before evaluating signals
if(!IsWithinTradingSession())
return;
if(IsDailyCircuitBreakerHit())
return;
// Check Spread Filter
long currentSpread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD);
if(currentSpread > InpMaxSpreadPoints)
return;
// Refresh H4/D1 Structural Zones when a new H4 bar opens
datetime currentH4Bar = iTime(_Symbol, PERIOD_H4, 0);
if(currentH4Bar != g_lastZoneUpdateH4)
{
RebuildStructuralZones();
g_lastZoneUpdateH4 = currentH4Bar;
}
// Mark current M15 bar as processed
g_lastM15BarTime = currentM15Bar;
// Skip new entries if a position is already open
if(HasOpenPosition())
return;
// 3. Evaluate Pure Price Action Setups on Closed M15 Bar (Shift 1)
EvaluateM15PriceAction();
}
//+------------------------------------------------------------------+
//| Build & Merge H4, D1, and PDH/PDL Support/Resistance Zones |
//+------------------------------------------------------------------+
void RebuildStructuralZones()
{
ArrayResize(g_srLevels, 0);
// 1. Add Previous Day High (PDH) and Previous Day Low (PDL)
double pdh = iHigh(_Symbol, PERIOD_D1, 1);
double pdl = iLow(_Symbol, PERIOD_D1, 1);
if(pdh > 0) AddOrMergeLevel(pdh);
if(pdl > 0) AddOrMergeLevel(pdl);
// 2. Scan D1 Swing Fractals
ScanTimeframeFractals(PERIOD_D1, InpLookbackD1);
// 3. Scan H4 Swing Fractals
ScanTimeframeFractals(PERIOD_H4, InpLookbackH4);
// 4. Render Structural Lines on Chart
if(InpDrawZones)
DrawZonesOnChart();
}
//+------------------------------------------------------------------+
//| Scan Timeframe for Swing High/Low Fractals |
//+------------------------------------------------------------------+
void ScanTimeframeFractals(ENUM_TIMEFRAMES tf, int lookback)
{
int s = InpFractalStrength;
for(int i = s + 1; i <= lookback - s; i++)
{
double highVal = iHigh(_Symbol, tf, i);
double lowVal = iLow(_Symbol, tf, i);
bool isSwingHigh = true;
bool isSwingLow = true;
for(int k = 1; k <= s; k++)
{
if(iHigh(_Symbol, tf, i - k) >= highVal || iHigh(_Symbol, tf, i + k) > highVal)
isSwingHigh = false;
if(iLow(_Symbol, tf, i - k) <= lowVal || iLow(_Symbol, tf, i + k) < lowVal)
isSwingLow = false;
}
if(isSwingHigh) AddOrMergeLevel(highVal);
if(isSwingLow) AddOrMergeLevel(lowVal);
}
}
//+------------------------------------------------------------------+
//| Deduplicate and cluster nearby S/R levels |
//+------------------------------------------------------------------+
void AddOrMergeLevel(double price)
{
int total = ArraySize(g_srLevels);
for(int i = 0; i < total; i++)
{
if(MathAbs(g_srLevels[i] - price) <= InpZoneToleranceUSD)
{
// Average the level cluster
g_srLevels[i] = (g_srLevels[i] + price) * 0.5;
return;
}
}
ArrayResize(g_srLevels, total + 1);
g_srLevels[total] = price;
}
//+------------------------------------------------------------------+
//| Evaluate Setup A (Sweep & Reclaim) and Setup B (Break & Retest) |
//+------------------------------------------------------------------+
void EvaluateM15PriceAction()
{
double o1 = iOpen(_Symbol, PERIOD_M15, 1);
double h1 = iHigh(_Symbol, PERIOD_M15, 1);
double l1 = iLow(_Symbol, PERIOD_M15, 1);
double c1 = iClose(_Symbol, PERIOD_M15, 1);
double o2 = iOpen(_Symbol, PERIOD_M15, 2);
double c2 = iClose(_Symbol, PERIOD_M15, 2);
double range1 = MathMax(h1 - l1, 0.01);
double body1 = MathAbs(c1 - o1);
double upperWick1 = h1 - MathMax(o1, c1);
double lowerWick1 = MathMin(o1, c1) - l1;
int totalLevels = ArraySize(g_srLevels);
//-----------------------------------------------------------------
// SETUP A: Liquidity Sweep & Reclaim (Stop-Run Fade)
//-----------------------------------------------------------------
if(InpEnableSetupA)
{
for(int i = 0; i < totalLevels; i++)
{
double lvl = g_srLevels[i];
// 1. Bullish Sweep & Reclaim at Support:
// Low pierces below lvl, Close reclaims above lvl, strong lower rejection wick OR bullish engulfing
double bullSweepDepth = lvl - l1;
if(l1 < lvl && c1 > lvl && bullSweepDepth >= 0.25 && bullSweepDepth <= InpMaxSweepDepthUSD)
{
bool isPinRejection = (lowerWick1 >= range1 * 0.45);
bool isBullEngulf = (c1 > o1 && c1 > MathMax(o2, c2) && body1 >= range1 * 0.45);
if(isPinRejection || isBullEngulf)
{
ExecuteScalpOrder(ORDER_TYPE_BUY, l1 - InpStopBufferUSD, "SetupA_BullSweep");
return;
}
}
// 2. Bearish Sweep & Reclaim at Resistance:
// High pierces above lvl, Close reclaims below lvl, strong upper rejection wick OR bearish engulfing
double bearSweepDepth = h1 - lvl;
if(h1 > lvl && c1 < lvl && bearSweepDepth >= 0.25 && bearSweepDepth <= InpMaxSweepDepthUSD)
{
bool isPinRejection = (upperWick1 >= range1 * 0.45);
bool isBearEngulf = (c1 < o1 && c1 < MathMin(o2, c2) && body1 >= range1 * 0.45);
if(isPinRejection || isBearEngulf)
{
ExecuteScalpOrder(ORDER_TYPE_SELL, h1 + InpStopBufferUSD, "SetupA_BearSweep");
return;
}
}
}
}
//-----------------------------------------------------------------
// SETUP B: Impulse Breakout & Micro-Retest
//-----------------------------------------------------------------
if(InpEnableSetupB)
{
datetime bar1Time = iTime(_Symbol, PERIOD_M15, 1);
// Step 1: Check if an active breakout is currently waiting for a 1-3 bar micro-retest
if(g_breakoutDirection != 0 && g_activeBreakoutLevel > 0)
{
int barsSinceBreakout = iBarShift(_Symbol, PERIOD_M15, g_breakoutBarTime);
if(barsSinceBreakout >= 1 && barsSinceBreakout <= 3)
{
// Bullish Retest: Price pulls back to touch broken resistance (within tolerance) and closes above
if(g_breakoutDirection == 1)
{
if(l1 <= g_activeBreakoutLevel + InpZoneToleranceUSD && c1 > g_activeBreakoutLevel && (c1 > o1 || lowerWick1 >= range1 * 0.40))
{
double sl = l1 - InpStopBufferUSD;
g_breakoutDirection = 0;
ExecuteScalpOrder(ORDER_TYPE_BUY, sl, "SetupB_BullRetest");
return;
}
}
// Bearish Retest: Price pulls back to touch broken support (within tolerance) and closes below
else if(g_breakoutDirection == -1)
{
if(h1 >= g_activeBreakoutLevel - InpZoneToleranceUSD && c1 < g_activeBreakoutLevel && (c1 < o1 || upperWick1 >= range1 * 0.40))
{
double sl = h1 + InpStopBufferUSD;
g_breakoutDirection = 0;
ExecuteScalpOrder(ORDER_TYPE_SELL, sl, "SetupB_BearRetest");
return;
}
}
}
else if(barsSinceBreakout > 3)
{
// Expire un-retested breakout after 3 M15 bars
g_breakoutDirection = 0;
g_activeBreakoutLevel = 0.0;
}
}
// Step 2: Detect a fresh Marubozu Impulse Breakout across an H4/D1 Zone
bool isImpulseBody = (body1 >= range1 * 0.68 && range1 >= 1.80);
if(isImpulseBody)
{
for(int i = 0; i < totalLevels; i++)
{
double lvl = g_srLevels[i];
// Bullish Impulse Breakout
if(o1 < lvl && c1 > lvl + (InpZoneToleranceUSD * 0.5))
{
g_activeBreakoutLevel = lvl;
g_breakoutDirection = 1;
g_breakoutBarTime = bar1Time;
break;
}
// Bearish Impulse Breakout
if(o1 > lvl && c1 < lvl - (InpZoneToleranceUSD * 0.5))
{
g_activeBreakoutLevel = lvl;
g_breakoutDirection = -1;
g_breakoutBarTime = bar1Time;
break;
}
}
}
}
}
//+------------------------------------------------------------------+
//| Execute Scalp Order with Series Compounding & 3:1 Risk/Reward |
//+------------------------------------------------------------------+
void ExecuteScalpOrder(ENUM_ORDER_TYPE orderType, double rawStopPrice, string setupTag)
{
double entryPrice = (orderType == ORDER_TYPE_BUY)
? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
: SymbolInfoDouble(_Symbol, SYMBOL_BID);
double slDistanceUSD = MathAbs(entryPrice - rawStopPrice);
// Clamp Stop Loss within structural min/max bounds ($3.00 to $6.50 default)
if(slDistanceUSD < InpMinStopLossUSD)
slDistanceUSD = InpMinStopLossUSD;
if(slDistanceUSD > InpMaxStopLossUSD)
return; // Skip trade if structural stop is abnormally wide
// Calculate Target Distance based on Risk:Reward Ratio
// Note: InpRiskToRewardRatio = 3.0 means Risk 3 units to make 1 unit of quick scalp reward
double tpDistanceUSD = slDistanceUSD / InpRiskToRewardRatio;
double slPrice = (orderType == ORDER_TYPE_BUY)
? NormalizeDouble(entryPrice - slDistanceUSD, _Digits)
: NormalizeDouble(entryPrice + slDistanceUSD, _Digits);
double tpPrice = (orderType == ORDER_TYPE_BUY)
? NormalizeDouble(entryPrice + tpDistanceUSD, _Digits)
: NormalizeDouble(entryPrice - tpDistanceUSD, _Digits);
// Calculate Dynamic Lot Size via Series Money Management
double lots = CalculateSeriesPositionSize(slDistanceUSD);
if(lots <= 0.0)
return;
if(orderType == ORDER_TYPE_BUY)
ExtTrade.Buy(lots, _Symbol, entryPrice, slPrice, tpPrice, setupTag);
else
ExtTrade.Sell(lots, _Symbol, entryPrice, slPrice, tpPrice, setupTag);
}
//+------------------------------------------------------------------+
//| Two-Phase Series Exponential Money Management |
//+------------------------------------------------------------------+
double CalculateSeriesPositionSize(double slDistancePrice)
{
double currentBalance = ExtAccount.Balance();
double accruedProfit = currentBalance - InpSeriesStartCapital;
double profitTrigger = InpSeriesStartCapital * (InpPhase2TriggerPct / 100.0);
double riskMoney = 0.0;
// Phase 2: Accrued Profit Cushion Reached -> Risk % of Accrued Profits (Core Protected)
if(accruedProfit >= profitTrigger)
{
riskMoney = accruedProfit * (InpPhase2ProfitRiskPct / 100.0);
}
// Phase 1: Building Base Cushion -> Risk Fixed % of Series Capital
else
{
double effectiveCapital = MathMin(currentBalance, InpSeriesStartCapital);
riskMoney = effectiveCapital * (InpPhase1BaseRiskPct / 100.0);
}
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
if(tickSize <= 0 || tickValue <= 0 || slDistancePrice <= 0)
return 0.0;
double lossPerLot = (slDistancePrice / tickSize) * tickValue;
if(lossPerLot <= 0)
return 0.0;
double rawLots = riskMoney / lossPerLot;
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double normalizedLots = MathFloor(rawLots / lotStep) * lotStep;
normalizedLots = MathMax(minLot, MathMin(maxLot, normalizedLots));
return NormalizeDouble(normalizedLots, 2);
}
//+------------------------------------------------------------------+
//| Time-Based Scratch Exit if Momentum Fails Within N M15 Candles |
//+------------------------------------------------------------------+
void ManageOpenPositions()
{
if(InpScratchMaxBars <= 0)
return;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!ExtPosition.SelectByIndex(i))
continue;
if(ExtPosition.Symbol() != _Symbol || ExtPosition.Magic() != InpMagicNumber)
continue;
datetime openTime = (datetime)ExtPosition.Time();
int barsHeld = iBarShift(_Symbol, PERIOD_M15, openTime);
if(barsHeld >= InpScratchMaxBars)
{
double openPrice = ExtPosition.PriceOpen();
double currentPrice = ExtPosition.PriceCurrent();
double priceDelta = (ExtPosition.PositionType() == POSITION_TYPE_BUY)
? (currentPrice - openPrice)
: (openPrice - currentPrice);
// If trade has not moved favorably by at least InpScratchMinProfitUSD after N M15 candles, scratch it
if(priceDelta < InpScratchMinProfitUSD)
{
ExtTrade.PositionClose(ExtPosition.Ticket());
}
}
}
}
//+------------------------------------------------------------------+
//| Helper: Check if an EA Position is Currently Open |
//+------------------------------------------------------------------+
bool HasOpenPosition()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(ExtPosition.SelectByIndex(i))
{
if(ExtPosition.Symbol() == _Symbol && ExtPosition.Magic() == InpMagicNumber)
return true;
}
}
return false;
}
//+------------------------------------------------------------------+
//| Helper: Check London & NY Liquidity Windows |
//+------------------------------------------------------------------+
bool IsWithinTradingSession()
{
if(!InpUseSessionFilter)
return true;
MqlDateTime dt;
TimeGMT(dt); // Evaluates against UTC time
bool inSession1 = (dt.hour >= InpSession1StartHour && dt.hour < InpSession1EndHour);
bool inSession2 = (dt.hour >= InpSession2StartHour && dt.hour < InpSession2EndHour);
return (inSession1 || inSession2);
}
//+------------------------------------------------------------------+
//| Helper: Daily Circuit Breaker (Consecutive Stop-Outs Today) |
//+------------------------------------------------------------------+
bool IsDailyCircuitBreakerHit()
{
if(InpMaxDailyLosses <= 0)
return false;
MqlDateTime dt;
TimeCurrent(dt);
dt.hour = 0; dt.min = 0; dt.sec = 0;
datetime dayStart = StructToTime(dt);
if(!HistorySelect(dayStart, TimeCurrent()))
return false;
int consecutiveLosses = 0;
int totalDeals = HistoryDealsTotal();
for(int i = totalDeals - 1; i >= 0; i--)
{
ulong dealTicket = HistoryDealGetTicket(i);
if(HistoryDealGetInteger(dealTicket, DEAL_MAGIC) != (long)InpMagicNumber)
continue;
if(HistoryDealGetString(dealTicket, DEAL_SYMBOL) != _Symbol)
continue;
if(HistoryDealGetInteger(dealTicket, DEAL_ENTRY) != DEAL_ENTRY_OUT)
continue;
double profit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT);
long reason = HistoryDealGetInteger(dealTicket, DEAL_REASON);
// Count full SL hits (ignore minor time-scratches unless significant)
if(profit < 0 && reason == DEAL_REASON_SL)
{
consecutiveLosses++;
if(consecutiveLosses >= InpMaxDailyLosses)
return true;
}
else if(profit > 0)
{
break; // Streak broken by a winning trade
}
}
return false;
}
//+------------------------------------------------------------------+
//| Render H4/D1 Structural Lines on Chart |
//+------------------------------------------------------------------+
void DrawZonesOnChart()
{
ObjectsDeleteAll(0, "XAU_SR_");
int total = ArraySize(g_srLevels);
for(int i = 0; i < total; i++)
{
string objName = "XAU_SR_" + IntegerToString(i);
ObjectCreate(0, objName, OBJ_HLINE, 0, 0, g_srLevels[i]);
ObjectSetInteger(0, objName, OBJPROP_COLOR, clrGoldenrod);
ObjectSetInteger(0, objName, OBJPROP_STYLE, STYLE_DOT);
ObjectSetInteger(0, objName, OBJPROP_WIDTH, 1);
ObjectSetInteger(0, objName, OBJPROP_BACK, true);
}
}
//+------------------------------------------------------------------+