Save the code below in MetaEditor 5 under MQL5/Experts/XAUUSD_LiquidityScalper.mq5 and compile (F7):
Code: Select all
//+------------------------------------------------------------------+
//| XAUUSD_LiquidityScalper.mq5 |
//| Multi-Timeframe Liquidity Sweep & Pullback Scalping EA |
//| Calibrated for XAU/USD Raw ECN |
//+------------------------------------------------------------------+
#property copyright "XAUUSD Liquidity Scalper"
#property version "1.00"
#property strict
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\SymbolInfo.mqh>
//+------------------------------------------------------------------+
//| Input Parameters |
//+------------------------------------------------------------------+
input group "=== 1. Strategy & Setup Filters ==="
input bool InpEnableSweepSetup = true; // Enable Liquidity Sweep (False Breakout)
input bool InpEnablePullbackSetup = true; // Enable M15 EMA 20/50 Trend Pullback
input ENUM_TIMEFRAMES InpTriggerTF = PERIOD_M5; // Entry Trigger Timeframe (M5 or M15)
input int InpEmaFast = 20; // M15 Fast EMA Period
input int InpEmaSlow = 50; // M15 Slow EMA Period
input int InpAtrPeriod = 14; // M5 ATR Period for Stop Sizing
input int InpH4SwingLookback = 10; // H4 Bars Lookback for Structural High/Low
input double InpMaxSweepOvershootUSD = 2.50; // Max Liquidity Sweep Piercing ($ USD)
input double InpTouchToleranceUSD = 0.35; // Level / EMA Touch Tolerance ($ USD)
input group "=== 2. XAU/USD Stop-Loss & Target Calibration ==="
input double InpAtrStopMultiplier = 1.0; // ATR Multiplier Beyond Sweep/Pullback Wick
input double InpMinStopUSD = 1.20; // Minimum Structural Stop Distance ($ USD)
input double InpMaxStopUSD = 3.50; // Maximum Structural Stop Distance ($ USD)
input double InpPartialCloseRR = 1.5; // Partial Close & BE Trigger (R-Multiple)
input double InpPartialCloseRatio = 0.50; // Partial Close Volume Ratio (0.50 = 50%)
input double InpTrailStartRR = 2.0; // Start M15 Structural Trail (R-Multiple)
input double InpFinalTargetRR = 3.5; // Hard Take-Profit Target (R-Multiple)
input int InpTimeStopMinutes = 45; // Time Stop if Profit < 0.5R (Minutes, 0=Off)
input group "=== 3. Risk, ECN Commission & Profit-Lock Scaling ==="
input double InpBaseRiskPct = 0.50; // Baseline Risk Per Trade (% of Equity)
input double InpCommissionPerLotRT = 7.00; // Round-Turn ECN Commission Per 1.0 Lot ($)
input bool InpUseProfitLockScaling = true; // Enable Exponential Profit-Lock Scaling
input double InpProfitLockTriggerPct = 2.00; // Profit Gain % to Trigger 50% Equity Lock
input double InpUnlockedBufferRiskPct = 25.0; // % of Unlocked Profit Buffer Added to Risk
input double InpMaxScaledRiskPct = 1.25; // Hard Cap on Scaled Risk Per Trade (%)
input double InpMaxDailyDrawdownPct = 1.50; // Daily Loss Circuit Breaker (% of Day Start)
input int InpMaxDailyConsecLosses = 3; // Max Consecutive Losses Per Day
input group "=== 4. Raw ECN Spread & Session Windows (UTC/Server) ==="
input double InpMaxSpreadUSD = 0.25; // Max Allowed Live Spread ($ USD, e.g. $0.25)
input int InpAsianStartHour = 0; // Asian Session Start Hour (Server Time)
input int InpAsianEndHour = 7; // Asian Session End Hour (Server Time)
input bool InpTradeLondonSession = true; // Trade London Open Window
input int InpLondonStartHour = 7; // London Window Start Hour
input int InpLondonEndHour = 10; // London Window End Hour
input bool InpTradeNYOverlap = true; // Trade London-NY Overlap Window
input int InpNYStartHour = 12; // NY Overlap Start Hour
input int InpNYEndHour = 16; // NY Overlap End Hour
input ulong InpMagicNumber = 20260888; // EA Magic Number
input int InpSlippagePoints = 15; // Max Deviation in Points
//+------------------------------------------------------------------+
//| Global Objects & State Variables |
//+------------------------------------------------------------------+
CTrade g_trade;
CPositionInfo g_posInfo;
CSymbolInfo g_symInfo;
int g_hEmaFastM15 = INVALID_HANDLE;
int g_hEmaSlowM15 = INVALID_HANDLE;
int g_hAtrM5 = INVALID_HANDLE;
datetime g_lastTriggerBarTime = 0;
int g_currentDayOfYear = -1;
// Risk & Profit-Lock State
double g_dayStartEquity = 0.0;
double g_baseReferenceEq = 0.0;
double g_lockedEquityFloor = 0.0;
int g_dailyConsecLosses = 0;
bool g_dailyHalted = false;
// Liquidity Pool Cache
double g_pdh = 0.0;
double g_pdl = 0.0;
double g_asianHigh = 0.0;
double g_asianLow = 0.0;
double g_h4SwingHigh = 0.0;
double g_h4SwingLow = 0.0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
if(!g_symInfo.Name(_Symbol))
return(INIT_FAILED);
g_symInfo.RefreshRates();
g_trade.SetExpertMagicNumber(InpMagicNumber);
g_trade.SetDeviationInPoints(InpSlippagePoints);
g_trade.SetTypeFillingBySymbol(_Symbol);
g_trade.SetAsyncMode(false);
g_hEmaFastM15 = iMA(_Symbol, PERIOD_M15, InpEmaFast, 0, MODE_EMA, PRICE_CLOSE);
g_hEmaSlowM15 = iMA(_Symbol, PERIOD_M15, InpEmaSlow, 0, MODE_EMA, PRICE_CLOSE);
g_hAtrM5 = iATR(_Symbol, PERIOD_M5, InpAtrPeriod);
if(g_hEmaFastM15 == INVALID_HANDLE || g_hEmaSlowM15 == INVALID_HANDLE || g_hAtrM5 == INVALID_HANDLE)
{
Print("Error creating indicator handles: ", GetLastError());
return(INIT_FAILED);
}
double eq = AccountInfoDouble(ACCOUNT_EQUITY);
g_dayStartEquity = eq;
g_baseReferenceEq = eq;
g_lockedEquityFloor = eq * (1.0 - InpMaxDailyDrawdownPct / 100.0);
MqlDateTime dt;
TimeCurrent(dt);
g_currentDayOfYear = dt.day_of_year;
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(g_hEmaFastM15 != INVALID_HANDLE) IndicatorRelease(g_hEmaFastM15);
if(g_hEmaSlowM15 != INVALID_HANDLE) IndicatorRelease(g_hEmaSlowM15);
if(g_hAtrM5 != INVALID_HANDLE) IndicatorRelease(g_hAtrM5);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
if(!g_symInfo.RefreshRates())
return;
// 1. Daily Reset & Circuit Breaker Audit
CheckDailyResetAndDrawdown();
// 2. Manage Any Open Position (Partial Close, BE, Structural Trail, Time Stop)
if(HasOpenPosition())
{
ManageOpenPositions();
return; // One active position at a time on XAU/USD
}
if(g_dailyHalted)
return;
// 3. Evaluate Entry Signals Strictly on New Trigger Bar Close
datetime currentBarTime = iTime(_Symbol, InpTriggerTF, 0);
if(currentBarTime == g_lastTriggerBarTime)
return;
// Check Session Filter
if(!IsWithinActiveSession())
return;
// Check Live Spread Filter ($ USD)
double liveSpreadUSD = g_symInfo.Ask() - g_symInfo.Bid();
if(liveSpreadUSD > InpMaxSpreadUSD)
return;
g_lastTriggerBarTime = currentBarTime;
// 4. Refresh H4/D1 & Asian Session Liquidity Pools
UpdateLiquidityPools();
// 5. Fetch Indicator Values
double emaFast[2], emaSlow[2], atrM5[2];
if(CopyBuffer(g_hEmaFastM15, 0, 1, 2, emaFast) < 2) return;
if(CopyBuffer(g_hEmaSlowM15, 0, 1, 2, emaSlow) < 2) return;
if(CopyBuffer(g_hAtrM5, 0, 1, 1, atrM5) < 1) return;
double currentAtr = atrM5[0];
double slPrice = 0.0;
// 6. Check Setup 1: Liquidity Sweep (False Breakout of Key Level)
if(InpEnableSweepSetup)
{
if(CheckBullishSweep(currentAtr, slPrice))
{
ExecuteTrade(ORDER_TYPE_BUY, slPrice, "XAU_Sweep_Buy");
return;
}
if(CheckBearishSweep(currentAtr, slPrice))
{
ExecuteTrade(ORDER_TYPE_SELL, slPrice, "XAU_Sweep_Sell");
return;
}
}
// 7. Check Setup 2: M15 EMA 20/50 Trend Pullback + Micro BoS
if(InpEnablePullbackSetup)
{
// Note: CopyBuffer index 1 is bar [1] (most recently closed M15 bar)
double m15Fast = emaFast[1];
double m15Slow = emaSlow[1];
if(CheckBullishPullback(m15Fast, m15Slow, currentAtr, slPrice))
{
ExecuteTrade(ORDER_TYPE_BUY, slPrice, "XAU_Pullback_Buy");
return;
}
if(CheckBearishPullback(m15Fast, m15Slow, currentAtr, slPrice))
{
ExecuteTrade(ORDER_TYPE_SELL, slPrice, "XAU_Pullback_Sell");
return;
}
}
}
//+------------------------------------------------------------------+
//| Track Closed Deals for Consecutive Losses & Profit-Lock Scaling |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request,
const MqlTradeResult &result)
{
if(trans.type != TRADE_TRANSACTION_DEAL_ADD)
return;
if(!HistoryDealSelect(trans.deal))
return;
ulong magic = (ulong)HistoryDealGetInteger(trans.deal, DEAL_MAGIC);
string sym = HistoryDealGetString(trans.deal, DEAL_SYMBOL);
long entry = HistoryDealGetInteger(trans.deal, DEAL_ENTRY);
if(magic != InpMagicNumber || sym != _Symbol || entry != DEAL_ENTRY_OUT)
return;
double netProfit = HistoryDealGetDouble(trans.deal, DEAL_PROFIT)
+ HistoryDealGetDouble(trans.deal, DEAL_COMMISSION)
+ HistoryDealGetDouble(trans.deal, DEAL_SWAP);
// Ignore partial closes when counting full-trade consecutive losses
if(netProfit < -1.0)
g_dailyConsecLosses++;
else if(netProfit > 1.0)
g_dailyConsecLosses = 0;
// Update Profit-Lock Exponential Floor if threshold reached
double eq = AccountInfoDouble(ACCOUNT_EQUITY);
double gainPct = ((eq - g_baseReferenceEq) / g_baseReferenceEq) * 100.0;
if(InpUseProfitLockScaling && gainPct >= InpProfitLockTriggerPct)
{
double gainedUSD = eq - g_baseReferenceEq;
g_lockedEquityFloor = g_baseReferenceEq + (gainedUSD * 0.50);
g_baseReferenceEq = eq;
PrintFormat("Profit-Lock Triggered! New Locked Equity Floor: $%.2f | New Reference: $%.2f",
g_lockedEquityFloor, g_baseReferenceEq);
}
}
//+------------------------------------------------------------------+
//| Daily Reset & Circuit Breaker Audit |
//+------------------------------------------------------------------+
void CheckDailyResetAndDrawdown()
{
MqlDateTime dt;
TimeCurrent(dt);
double eq = AccountInfoDouble(ACCOUNT_EQUITY);
if(dt.day_of_year != g_currentDayOfYear)
{
g_currentDayOfYear = dt.day_of_year;
g_dayStartEquity = eq;
g_dailyConsecLosses = 0;
g_dailyHalted = false;
}
if(g_dailyHalted)
return;
double dailyDDPct = ((g_dayStartEquity - eq) / g_dayStartEquity) * 100.0;
if(dailyDDPct >= InpMaxDailyDrawdownPct)
{
g_dailyHalted = true;
PrintFormat("Circuit Breaker: Max Daily Drawdown (%.2f%%) reached. Trading halted for today.", dailyDDPct);
}
else if(g_dailyConsecLosses >= InpMaxDailyConsecLosses)
{
g_dailyHalted = true;
PrintFormat("Circuit Breaker: %d Consecutive Losses reached. Trading halted for today.", g_dailyConsecLosses);
}
}
//+------------------------------------------------------------------+
//| Update PDH/PDL, Asian Session High/Low, and H4 Structural Swings |
//+------------------------------------------------------------------+
void UpdateLiquidityPools()
{
// 1. Previous Day High & Low (D1 Bar 1)
g_pdh = iHigh(_Symbol, PERIOD_D1, 1);
g_pdl = iLow(_Symbol, PERIOD_D1, 1);
// 2. H4 Structural Swing High & Low (Last N closed H4 bars)
int h4HighIdx = iHighest(_Symbol, PERIOD_H4, MODE_HIGH, InpH4SwingLookback, 1);
int h4LowIdx = iLowest(_Symbol, PERIOD_H4, MODE_LOW, InpH4SwingLookback, 1);
if(h4HighIdx != -1) g_h4SwingHigh = iHigh(_Symbol, PERIOD_H4, h4HighIdx);
if(h4LowIdx != -1) g_h4SwingLow = iLow(_Symbol, PERIOD_H4, h4LowIdx);
// 3. Asian Session High & Low (Calculated from M15 bars today)
MqlDateTime dt;
TimeCurrent(dt);
dt.hour = InpAsianStartHour;
dt.min = 0;
dt.sec = 0;
datetime asianStart = StructToTime(dt);
dt.hour = InpAsianEndHour;
datetime asianEnd = StructToTime(dt);
g_asianHigh = 0.0;
g_asianLow = DBL_MAX;
for(int i = 1; i < 100; i++)
{
datetime barTime = iTime(_Symbol, PERIOD_M15, i);
if(barTime < asianStart)
break;
if(barTime >= asianStart && barTime < asianEnd)
{
double h = iHigh(_Symbol, PERIOD_M15, i);
double l = iLow(_Symbol, PERIOD_M15, i);
if(h > g_asianHigh) g_asianHigh = h;
if(l < g_asianLow) g_asianLow = l;
}
}
if(g_asianLow == DBL_MAX) g_asianLow = 0.0;
}
//+------------------------------------------------------------------+
//| Check Bullish Liquidity Sweep (False Breakout of Support) |
//+------------------------------------------------------------------+
bool CheckBullishSweep(double atr, double &outSlPrice)
{
double open1 = iOpen(_Symbol, InpTriggerTF, 1);
double close1 = iClose(_Symbol, InpTriggerTF, 1);
double high1 = iHigh(_Symbol, InpTriggerTF, 1);
double low1 = iLow(_Symbol, InpTriggerTF, 1);
double low2 = iLow(_Symbol, InpTriggerTF, 2);
double high2 = iHigh(_Symbol, InpTriggerTF, 2);
// Require bullish displacement body on bar [1] and micro break above high[2] or strong engulfing
double range1 = high1 - low1;
double body1 = close1 - open1;
if(range1 <= 0.0 || body1 <= 0.45 * range1)
return(false);
double sweepLow = MathMin(low1, low2);
double levels[3] = { g_pdl, g_asianLow, g_h4SwingLow };
for(int i = 0; i < 3; i++)
{
double lvl = levels[i];
if(lvl <= 0.0) continue;
// Price pierced below support level by up to InpMaxSweepOvershootUSD and closed back above it
double overshoot = lvl - sweepLow;
if(overshoot > 0.0 && overshoot <= InpMaxSweepOvershootUSD && close1 > lvl && (close1 > high2 || body1 > 0.60 * range1))
{
outSlPrice = sweepLow - (atr * InpAtrStopMultiplier);
return(true);
}
}
return(false);
}
//+------------------------------------------------------------------+
//| Check Bearish Liquidity Sweep (False Breakout of Resistance) |
//+------------------------------------------------------------------+
bool CheckBearishSweep(double atr, double &outSlPrice)
{
double open1 = iOpen(_Symbol, InpTriggerTF, 1);
double close1 = iClose(_Symbol, InpTriggerTF, 1);
double high1 = iHigh(_Symbol, InpTriggerTF, 1);
double low1 = iLow(_Symbol, InpTriggerTF, 1);
double high2 = iHigh(_Symbol, InpTriggerTF, 2);
double low2 = iLow(_Symbol, InpTriggerTF, 2);
double range1 = high1 - low1;
double body1 = open1 - close1;
if(range1 <= 0.0 || body1 <= 0.45 * range1)
return(false);
double sweepHigh = MathMax(high1, high2);
double levels[3] = { g_pdh, g_asianHigh, g_h4SwingHigh };
for(int i = 0; i < 3; i++)
{
double lvl = levels[i];
if(lvl <= 0.0) continue;
// Price pierced above resistance level and closed decisively back below it
double overshoot = sweepHigh - lvl;
if(overshoot > 0.0 && overshoot <= InpMaxSweepOvershootUSD && close1 < lvl && (close1 < low2 || body1 > 0.60 * range1))
{
outSlPrice = sweepHigh + (atr * InpAtrStopMultiplier);
return(true);
}
}
return(false);
}
//+------------------------------------------------------------------+
//| Check Bullish Trend Pullback into M15 EMA 20/50 Zone |
//+------------------------------------------------------------------+
bool CheckBullishPullback(double emaFast, double emaSlow, double atr, double &outSlPrice)
{
if(emaFast <= emaSlow)
return(false);
double open1 = iOpen(_Symbol, InpTriggerTF, 1);
double close1 = iClose(_Symbol, InpTriggerTF, 1);
double high1 = iHigh(_Symbol, InpTriggerTF, 1);
double low1 = iLow(_Symbol, InpTriggerTF, 1);
double high2 = iHigh(_Symbol, InpTriggerTF, 2);
double low2 = iLow(_Symbol, InpTriggerTF, 2);
double pullbackLow = MathMin(low1, low2);
// Pullback touched or dipped into EMA 20 zone while holding above EMA 50, then broke above bar [2] high
bool touchedValueZone = (pullbackLow <= emaFast + InpTouchToleranceUSD) && (pullbackLow >= emaSlow - InpTouchToleranceUSD);
bool bullishBoS = (close1 > open1) && (close1 > emaFast) && (close1 > high2);
if(touchedValueZone && bullishBoS)
{
outSlPrice = pullbackLow - (atr * InpAtrStopMultiplier);
return(true);
}
return(false);
}
//+------------------------------------------------------------------+
//| Check Bearish Trend Pullback into M15 EMA 20/50 Zone |
//+------------------------------------------------------------------+
bool CheckBearishPullback(double emaFast, double emaSlow, double atr, double &outSlPrice)
{
if(emaFast >= emaSlow)
return(false);
double open1 = iOpen(_Symbol, InpTriggerTF, 1);
double close1 = iClose(_Symbol, InpTriggerTF, 1);
double high1 = iHigh(_Symbol, InpTriggerTF, 1);
double low1 = iLow(_Symbol, InpTriggerTF, 1);
double high2 = iHigh(_Symbol, InpTriggerTF, 2);
double low2 = iLow(_Symbol, InpTriggerTF, 2);
double pullbackHigh = MathMax(high1, high2);
bool touchedValueZone = (pullbackHigh >= emaFast - InpTouchToleranceUSD) && (pullbackHigh <= emaSlow + InpTouchToleranceUSD);
bool bearishBoS = (close1 < open1) && (close1 < emaFast) && (close1 < low2);
if(touchedValueZone && bearishBoS)
{
outSlPrice = pullbackHigh + (atr * InpAtrStopMultiplier);
return(true);
}
return(false);
}
//+------------------------------------------------------------------+
//| Execute Order with Commission-Adjusted & Profit-Lock Lot Sizing |
//+------------------------------------------------------------------+
void ExecuteTrade(ENUM_ORDER_TYPE orderType, double rawSlPrice, string comment)
{
g_symInfo.RefreshRates();
double entryPrice = (orderType == ORDER_TYPE_BUY) ? g_symInfo.Ask() : g_symInfo.Bid();
double slDistUSD = MathAbs(entryPrice - rawSlPrice);
// Clamp Stop-Loss distance to XAU/USD structural bounds ($1.20 to $3.50)
if(slDistUSD < InpMinStopUSD)
slDistUSD = InpMinStopUSD;
else if(slDistUSD > InpMaxStopUSD)
return; // Skip trade if structural stop is too wide for a scalp
double slPrice = (orderType == ORDER_TYPE_BUY)
? NormalizeDouble(entryPrice - slDistUSD, _Digits)
: NormalizeDouble(entryPrice + slDistUSD, _Digits);
double tpDistUSD = slDistUSD * InpFinalTargetRR;
double tpPrice = (orderType == ORDER_TYPE_BUY)
? NormalizeDouble(entryPrice + tpDistUSD, _Digits)
: NormalizeDouble(entryPrice - tpDistUSD, _Digits);
double lots = CalculatePositionLots(slDistUSD);
if(lots <= 0.0)
return;
if(orderType == ORDER_TYPE_BUY)
g_trade.Buy(lots, _Symbol, entryPrice, slPrice, tpPrice, comment);
else
g_trade.Sell(lots, _Symbol, entryPrice, slPrice, tpPrice, comment);
}
//+------------------------------------------------------------------+
//| Calculate Exact Lots Factoring ECN Commission & Profit-Lock |
//+------------------------------------------------------------------+
double CalculatePositionLots(double slDistUSD)
{
double eq = AccountInfoDouble(ACCOUNT_EQUITY);
double riskUSD = eq * (InpBaseRiskPct / 100.0);
// Apply Exponential Profit-Lock Scaling if active and above locked floor
if(InpUseProfitLockScaling && eq > g_lockedEquityFloor)
{
double unlockedProfit = MathMax(0.0, eq - g_baseReferenceEq);
if(unlockedProfit > 0.0)
{
double bonusRiskUSD = unlockedProfit * (InpUnlockedBufferRiskPct / 100.0);
double maxRiskUSD = eq * (InpMaxScaledRiskPct / 100.0);
riskUSD = MathMin(riskUSD + bonusRiskUSD, maxRiskUSD);
}
}
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
if(tickSize <= 0.0 || tickValue <= 0.0)
return(0.0);
// Price loss per 1.0 standard lot + Round-Turn ECN Commission per 1.0 lot
double priceLossPerLot = (slDistUSD / tickSize) * tickValue;
double totalLossPerLot = priceLossPerLot + InpCommissionPerLotRT;
if(totalLossPerLot <= 0.0)
return(0.0);
double rawLots = riskUSD / totalLossPerLot;
double stepLots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double minLots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxLots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lots = MathFloor(rawLots / stepLots) * stepLots;
if(lots < minLots) return(0.0);
if(lots > maxLots) lots = maxLots;
// Verify Free Margin
double marginRequired = 0.0;
if(!OrderCalcMargin(ORDER_TYPE_BUY, _Symbol, lots, g_symInfo.Ask(), marginRequired) ||
marginRequired > AccountInfoDouble(ACCOUNT_MARGIN_FREE))
{
Print("Insufficient free margin for calculated lot size: ", lots);
return(0.0);
}
return(NormalizeDouble(lots, 2));
}
//+------------------------------------------------------------------+
//| Manage Open Positions: Partial Close, BE+Comm, Trail & Time Stop |
//+------------------------------------------------------------------+
void ManageOpenPositions()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!g_posInfo.SelectByIndex(i)) continue;
if(g_posInfo.Symbol() != _Symbol || g_posInfo.Magic() != InpMagicNumber) continue;
ulong ticket = g_posInfo.Ticket();
double openPrice = g_posInfo.PriceOpen();
double currentSL = g_posInfo.StopLoss();
double currentTP = g_posInfo.TakeProfit();
double volume = g_posInfo.Volume();
ENUM_POSITION_TYPE posType = g_posInfo.PositionType();
double currentPrice = (posType == POSITION_TYPE_BUY) ? g_symInfo.Bid() : g_symInfo.Ask();
double profitDist = (posType == POSITION_TYPE_BUY) ? (currentPrice - openPrice) : (openPrice - currentPrice);
// Recover initial 1R distance from TakeProfit distance
double initialRiskDist = (currentTP > 0.0) ? (MathAbs(currentTP - openPrice) / InpFinalTargetRR) : InpMinStopUSD;
if(initialRiskDist <= 0.0) initialRiskDist = InpMinStopUSD;
double currentR = profitDist / initialRiskDist;
// 1. Time Stop Check: Close if open > InpTimeStopMinutes and profit < 0.5R
if(InpTimeStopMinutes > 0)
{
int elapsedSec = (int)(TimeCurrent() - g_posInfo.Time());
if(elapsedSec >= InpTimeStopMinutes * 60 && currentR < 0.5)
{
g_trade.PositionClose(ticket);
PrintFormat("Time Stop executed on Ticket #%I64u after %d mins (R = %.2f)", ticket, InpTimeStopMinutes, currentR);
continue;
}
}
// 2. Partial Close at +1.5R & Move Stop-Loss to Breakeven + Commission Buffer
string gvKey = "XAU_PARTIAL_" + IntegerToString(ticket);
if(currentR >= InpPartialCloseRR && !GlobalVariableCheck(gvKey))
{
double stepLots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double minLots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double closeLots = MathFloor((volume * InpPartialCloseRatio) / stepLots) * stepLots;
if(closeLots >= minLots && (volume - closeLots) >= minLots)
g_trade.PositionClosePartial(ticket, NormalizeDouble(closeLots, 2));
// Move SL to Breakeven + $0.15 Commission/Spread Offset
double commOffsetUSD = 0.15;
double newSL = (posType == POSITION_TYPE_BUY)
? NormalizeDouble(openPrice + commOffsetUSD, _Digits)
: NormalizeDouble(openPrice - commOffsetUSD, _Digits);
if((posType == POSITION_TYPE_BUY && newSL > currentSL) ||
(posType == POSITION_TYPE_SELL && (currentSL == 0.0 || newSL < currentSL)))
{
g_trade.PositionModify(ticket, newSL, currentTP);
}
GlobalVariableSet(gvKey, 1.0);
}
// 3. M15 Structural Trailing Stop once trade reaches +2.0R
if(currentR >= InpTrailStartRR)
{
double trailBuffer = 0.20; // $0.20 buffer beyond last closed M15 candle
if(posType == POSITION_TYPE_BUY)
{
double m15Low = iLow(_Symbol, PERIOD_M15, 1) - trailBuffer;
double newSL = NormalizeDouble(m15Low, _Digits);
if(newSL > currentSL && newSL < currentPrice)
g_trade.PositionModify(ticket, newSL, currentTP);
}
else
{
double m15High = iHigh(_Symbol, PERIOD_M15, 1) + trailBuffer;
double newSL = NormalizeDouble(m15High, _Digits);
if((currentSL == 0.0 || newSL < currentSL) && newSL > currentPrice)
g_trade.PositionModify(ticket, newSL, currentTP);
}
}
}
}
//+------------------------------------------------------------------+
//| Helper: Check if EA Has an Active Open Position on Symbol |
//+------------------------------------------------------------------+
bool HasOpenPosition()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(g_posInfo.SelectByIndex(i))
if(g_posInfo.Symbol() == _Symbol && g_posInfo.Magic() == InpMagicNumber)
return(true);
}
return(false);
}
//+------------------------------------------------------------------+
//| Helper: Verify Active London or NY Overlap Window |
//+------------------------------------------------------------------+
bool IsWithinActiveSession()
{
MqlDateTime dt;
TimeCurrent(dt);
bool inLondon = InpTradeLondonSession && (dt.hour >= InpLondonStartHour && dt.hour < InpLondonEndHour);
bool inNY = InpTradeNYOverlap && (dt.hour >= InpNYStartHour && dt.hour < InpNYEndHour);
return(inLondon || inNY);
}
//+------------------------------------------------------------------+