Save this updated code over USDJPY_LiquiditySweep_ProfitLock.mq5 in MQL5\Experts\ and compile with F7. In OnInit(), the EA automatically subscribes the secondary symbols in Market Watch via SymbolSelect() and initializes dedicated M15 iATR and iMA handles.
Code: Select all
//+------------------------------------------------------------------+
//| USDJPY_LiquiditySweep_ProfitLock.mq5 |
//| v2.00: Top-Down H4/M15 Sweep + XAUUSD/US10Y Confluence Filter |
//+------------------------------------------------------------------+
#property copyright "USD/JPY Automated Strategy (October 2026 Regime)"
#property version "2.00"
#property strict
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
//--- Enumerations for Multi-Symbol Macro Filter
enum ENUM_CORR_SOURCE
{
CORR_XAUUSD_ONLY = 0, // XAUUSD Only (Inverse to USD/JPY)
CORR_US10Y_ONLY = 1, // US10Y / Bond Symbol Only
CORR_EITHER_CONFIRMS = 2, // Either XAUUSD or US10Y Confirms
CORR_BOTH_REQUIRED = 3 // Both XAUUSD and US10Y Must Confirm
};
enum ENUM_FILTER_STRICTNESS
{
STRICT_DIRECTIONAL = 0, // Require Active Cross-Asset Momentum Alignment
VETO_OPPOSING_SPIKE = 1 // Only Block Entries When Cross-Asset Spikes Against Setup
};
//--- Input Parameters: H4 Structural Zones
input group "=== 1. H4 Structural Levels (JPY) ==="
input double InpDemandLower = 157.80; // Primary H4 Demand Lower Bound
input double InpDemandUpper = 158.00; // Primary H4 Demand Upper Bound (Sweep Level)
input double InpSupplyLower = 158.85; // Primary H4 Supply Lower Bound (Reject Close)
input double InpSupplyUpper = 158.95; // Primary H4 Supply Sweep Threshold
input double InpBearishBreakdownH4 = 157.60; // H4 Close Below Invalidates Longs
input double InpMoFCeilingCutoff = 159.20; // Hard Cutoff (MoF Intervention Danger Zone)
//--- Input Parameters: Timeframes & Entry Triggers
input group "=== 2. M15 Execution & Spread Filters ==="
input ENUM_TIMEFRAMES InpEntryTF = PERIOD_M15; // Execution Timeframe
input ENUM_TIMEFRAMES InpStructureTF = PERIOD_H4; // Structural Filter Timeframe
input double InpDisplacementRatio = 0.85; // Min Closed Body / Prior 3-Bar Avg Range Ratio
input double InpMaxSpreadPips = 1.2; // Max Allowed Spread (Pips) for Raw ECN
//--- Input Parameters: Multi-Symbol XAUUSD & US10Y Filter
input group "=== 3. Multi-Symbol Macro Filter (XAUUSD / US10Y) ==="
input bool InpUseMacroFilter = true; // Enable Cross-Asset Momentum Filter
input ENUM_CORR_SOURCE InpCorrSource = CORR_XAUUSD_ONLY; // Active Correlation Feed(s)
input ENUM_FILTER_STRICTNESS InpFilterStrictness = STRICT_DIRECTIONAL; // Filter Strictness Mode
input string InpGoldSymbol = "XAUUSD"; // Gold Symbol Name (Inverse Correlation)
input string InpBondSymbol = "US10Y"; // US10Y Yield or T-Note Symbol Name
input bool InpBondIsYieldDirect = true; // true = Yield (Direct), false = Bond Price (Inverse)
input int InpMomentumLookback = 3; // M15 Bar Lookback for Impulse ROC
input int InpMacroATRPeriod = 14; // ATR Period for Impulse Normalization
input int InpMacroEMAPeriod = 20; // EMA Period for Cross-Asset Trend Filter
input double InpMinConfirmATR = 0.20; // Min ATR Impulse for STRICT_DIRECTIONAL
input double InpMaxVetoSpikeATR = 0.45; // Max Opposing ATR Impulse for VETO Mode
//--- Input Parameters: Risk & Exponential Profit-Lock Scaling
input group "=== 4. Risk & House-Money Pyramiding ==="
input double InpBaseRiskPercent = 0.50; // Leg 1 Base Risk (% of Account Balance)
input double InpBaseStopLossPips = 15.0; // Leg 1 Stop Loss Distance (Pips)
input double InpScaleTriggerPips = 20.0; // Profit Trigger to Lock SL & Open Leg 2 (Pips)
input double InpLockedProfitPips = 10.0; // Leg 1 Locked Profit SL Distance (+Pips)
input double InpScaleStopLossPips = 15.0; // Leg 2 Stop Loss Distance (Pips)
input double InpCommissionBuffer = 0.95; // Safety Factor on Locked Profit for ECN Fees
input double InpLongTargetPrice = 158.85; // Take Profit Price for Long Setups
input double InpShortTargetPrice = 158.00; // Take Profit Price for Short Setups
//--- Input Parameters: Circuit Breakers & Session Timing
input group "=== 5. Circuit Breakers & Session Windows (UTC) ==="
input double InpMaxDailyDrawdownPct = 1.50; // Daily Drawdown Circuit Breaker (% of Start Balance)
input int InpMaxDailyLosingTrades = 3; // Max Losing Trades Per Day Circuit Breaker
input bool InpCloseAllOnDailyLimit = true; // Flatten Open Positions if Daily DD Breached
input bool InpUseSessionFilter = true; // Restrict Entries to Tokyo Open & LDN-NY Overlap
input int InpTokyoStartHourUTC = 0; // Tokyo Window Start Hour (UTC)
input int InpTokyoEndHourUTC = 3; // Tokyo Window End Hour (UTC)
input int InpOverlapStartHourUTC = 12; // London-NY Overlap Start Hour (UTC)
input int InpOverlapEndHourUTC = 16; // London-NY Overlap End Hour (UTC)
//--- Input Parameters: System & Notifications
input group "=== 6. EA Identification & Mobile Alerts ==="
input ulong InpMagicNumber = 20261019; // EA Magic Number
input bool InpEnablePushNotify = true; // Send MT5 Mobile Push Notifications
//--- Constants for Leg Tracking
#define LEG1_TAG "USDJPY_LEG1_BASE"
#define LEG2_TAG "USDJPY_LEG2_SCALE"
//--- Global Variables
CTrade ExtTrade;
CPositionInfo ExtPosition;
double ExtPipSize = 0.01;
datetime ExtLastM15BarTime = 0;
int ExtCurrentDayOfYear = -1;
double ExtDayStartBalance = 0.0;
bool ExtDailyCircuitTripped = false;
//--- Cross-Asset Indicator Handles
int ExtGoldATRHandle = INVALID_HANDLE;
int ExtGoldEMAHandle = INVALID_HANDLE;
int ExtBondATRHandle = INVALID_HANDLE;
int ExtBondEMAHandle = INVALID_HANDLE;
string ExtLastMacroDiagnostics = "";
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
ExtTrade.SetExpertMagicNumber(InpMagicNumber);
ExtTrade.SetDeviationInPoints(15);
ExtTrade.SetTypeFillingBySymbol(_Symbol);
ExtPipSize = (_Digits == 3 || _Digits == 5) ? _Point * 10.0 : _Point;
UpdateDailyBaseline();
//--- Initialize Cross-Asset Symbols & Indicator Handles
if(InpUseMacroFilter)
{
bool needGold = (InpCorrSource == CORR_XAUUSD_ONLY ||
InpCorrSource == CORR_EITHER_CONFIRMS ||
InpCorrSource == CORR_BOTH_REQUIRED);
bool needBond = (InpCorrSource == CORR_US10Y_ONLY ||
InpCorrSource == CORR_EITHER_CONFIRMS ||
InpCorrSource == CORR_BOTH_REQUIRED);
if(needGold)
{
if(!SymbolSelect(InpGoldSymbol, true))
{
PrintFormat("ERROR: Failed to select Gold symbol '%s' in Market Watch.", InpGoldSymbol);
return(INIT_FAILED);
}
ExtGoldATRHandle = iATR(InpGoldSymbol, InpEntryTF, InpMacroATRPeriod);
ExtGoldEMAHandle = iMA(InpGoldSymbol, InpEntryTF, InpMacroEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(ExtGoldATRHandle == INVALID_HANDLE || ExtGoldEMAHandle == INVALID_HANDLE)
{
PrintFormat("ERROR: Failed to create M15 ATR/EMA handles for %s.", InpGoldSymbol);
return(INIT_FAILED);
}
}
if(needBond)
{
if(!SymbolSelect(InpBondSymbol, true))
{
PrintFormat("ERROR: Failed to select Bond/Yield symbol '%s' in Market Watch.", InpBondSymbol);
return(INIT_FAILED);
}
ExtBondATRHandle = iATR(InpBondSymbol, InpEntryTF, InpMacroATRPeriod);
ExtBondEMAHandle = iMA(InpBondSymbol, InpEntryTF, InpMacroEMAPeriod, 0, MODE_EMA, PRICE_CLOSE);
if(ExtBondATRHandle == INVALID_HANDLE || ExtBondEMAHandle == INVALID_HANDLE)
{
PrintFormat("ERROR: Failed to create M15 ATR/EMA handles for %s.", InpBondSymbol);
return(INIT_FAILED);
}
}
}
PrintFormat("USDJPY_LiquiditySweep_ProfitLock v2.00 initialized on %s | MacroFilter: %s",
_Symbol, (InpUseMacroFilter ? "ENABLED" : "DISABLED"));
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
if(ExtGoldATRHandle != INVALID_HANDLE) IndicatorRelease(ExtGoldATRHandle);
if(ExtGoldEMAHandle != INVALID_HANDLE) IndicatorRelease(ExtGoldEMAHandle);
if(ExtBondATRHandle != INVALID_HANDLE) IndicatorRelease(ExtBondATRHandle);
if(ExtBondEMAHandle != INVALID_HANDLE) IndicatorRelease(ExtBondEMAHandle);
Comment("");
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
//--- 1. Refresh Daily Baseline & Check Circuit Breakers on Every Tick
UpdateDailyBaseline();
if(CheckDailyCircuitBreaker())
return;
//--- 2. Manage Open Positions on Every Tick (+20 Pip Lock & Leg 2 Scaling)
ManageOpenPositions();
//--- 3. Evaluate New Base Entries Only Once Per Closed M15 Bar
datetime currentBarTime = iTime(_Symbol, InpEntryTF, 0);
if(currentBarTime == ExtLastM15BarTime)
return;
if(HasOpenPositionByTag(LEG1_TAG) || HasOpenPositionByTag(LEG2_TAG))
{
ExtLastM15BarTime = currentBarTime;
return;
}
if(InpUseSessionFilter && !IsWithinPrimeSessionUTC())
return;
if(!IsSpreadAcceptable())
return;
//--- Mark bar as processed and check M15 Liquidity Sweep + Macro Confluence
ExtLastM15BarTime = currentBarTime;
EvaluateM15EntrySignals();
}
//+------------------------------------------------------------------+
//| Evaluate Closed M15 Candle for Demand/Supply Sweeps + Macro |
//+------------------------------------------------------------------+
void EvaluateM15EntrySignals()
{
MqlRates rates[];
ArraySetAsSeries(rates, true);
if(CopyRates(_Symbol, InpEntryTF, 1, 4, rates) < 4)
return;
double h4Close = iClose(_Symbol, InpStructureTF, 1);
bool longsAllowedByH4 = (h4Close >= InpBearishBreakdownH4);
double avgPrior3Range = ((rates[1].high - rates[1].low) +
(rates[2].high - rates[2].low) +
(rates[3].high - rates[3].low)) / 3.0;
double signalOpen = rates[0].open;
double signalHigh = rates[0].high;
double signalLow = rates[0].low;
double signalClose = rates[0].close;
double signalBody = MathAbs(signalClose - signalOpen);
bool isImpulsiveBody = (signalBody >= avgPrior3Range * InpDisplacementRatio);
bool isBullishEngulf = (signalClose > signalOpen && signalClose > rates[1].high);
bool isBearishEngulf = (signalClose < signalOpen && signalClose < rates[1].low);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
//--- 1. LONG SETUP: H4 Demand Sweep (Wick <= 158.00 & >= 157.80, Close > 158.00)
bool sweptDemand = (signalLow <= InpDemandUpper && signalLow >= (InpDemandLower - 5.0 * ExtPipSize));
bool closedAboveDemand = (signalClose > InpDemandUpper && signalClose > signalOpen);
if(longsAllowedByH4 && sweptDemand && closedAboveDemand && (isImpulsiveBody || isBullishEngulf))
{
if(ask < InpLongTargetPrice - (25.0 * ExtPipSize))
{
if(!InpUseMacroFilter || CheckMacroCorrelationFilter(ORDER_TYPE_BUY))
ExecuteBaseEntry(ORDER_TYPE_BUY, ask, InpLongTargetPrice);
else
PrintFormat("M15 LONG Sweep filtered out by Macro Confluence: %s", ExtLastMacroDiagnostics);
return;
}
}
//--- 2. SHORT SETUP: H4 Supply Rejection (Wick >= 158.95 & < 159.20, Close < 158.85)
bool sweptSupply = (signalHigh >= InpSupplyUpper && signalHigh < InpMoFCeilingCutoff);
bool closedBelowSupply = (signalClose <= InpSupplyLower && signalClose < signalOpen);
if(sweptSupply && closedBelowSupply && (isImpulsiveBody || isBearishEngulf))
{
if(bid > InpShortTargetPrice + (25.0 * ExtPipSize))
{
if(!InpUseMacroFilter || CheckMacroCorrelationFilter(ORDER_TYPE_SELL))
ExecuteBaseEntry(ORDER_TYPE_SELL, bid, InpShortTargetPrice);
else
PrintFormat("M15 SHORT Sweep filtered out by Macro Confluence: %s", ExtLastMacroDiagnostics);
return;
}
}
}
//+------------------------------------------------------------------+
//| Multi-Symbol Macro Confluence Check (XAUUSD / US10Y) |
//+------------------------------------------------------------------+
bool CheckMacroCorrelationFilter(ENUM_ORDER_TYPE usdjpyOrderType)
{
ExtLastMacroDiagnostics = "";
bool goldConfirmed = false;
bool bondConfirmed = false;
string goldDiag = "N/A";
string bondDiag = "N/A";
bool evaluateGold = (InpCorrSource == CORR_XAUUSD_ONLY ||
InpCorrSource == CORR_EITHER_CONFIRMS ||
InpCorrSource == CORR_BOTH_REQUIRED);
bool evaluateBond = (InpCorrSource == CORR_US10Y_ONLY ||
InpCorrSource == CORR_EITHER_CONFIRMS ||
InpCorrSource == CORR_BOTH_REQUIRED);
//--- 1. Evaluate XAUUSD (Always Inverse to USD/JPY: Bearish Gold = Bullish USD/JPY)
if(evaluateGold)
{
goldConfirmed = EvaluateSingleSymbolMomentum(InpGoldSymbol,
ExtGoldATRHandle,
ExtGoldEMAHandle,
false, // Inverse correlation
usdjpyOrderType,
goldDiag);
}
//--- 2. Evaluate US10Y / T-Note (Direct if Yield, Inverse if Bond Price CFD)
if(evaluateBond)
{
bondConfirmed = EvaluateSingleSymbolMomentum(InpBondSymbol,
ExtBondATRHandle,
ExtBondEMAHandle,
InpBondIsYieldDirect,
usdjpyOrderType,
bondDiag);
}
ExtLastMacroDiagnostics = StringFormat("[Gold: %s | Bond: %s]", goldDiag, bondDiag);
//--- Combine according to selected correlation source rule
if(InpCorrSource == CORR_XAUUSD_ONLY)
return(goldConfirmed);
if(InpCorrSource == CORR_US10Y_ONLY)
return(bondConfirmed);
if(InpCorrSource == CORR_EITHER_CONFIRMS)
return(goldConfirmed || bondConfirmed);
if(InpCorrSource == CORR_BOTH_REQUIRED)
return(goldConfirmed && bondConfirmed);
return(false);
}
//+------------------------------------------------------------------+
//| Compute ATR-Normalized Impulse & EMA Slope for a Secondary Asset |
//+------------------------------------------------------------------+
bool EvaluateSingleSymbolMomentum(string corrSymbol,
int atrHandle,
int emaHandle,
bool isDirectCorrelation,
ENUM_ORDER_TYPE usdjpyOrderType,
string &outDiag)
{
//--- Trigger tick/series refresh on secondary symbol
MqlTick tick;
SymbolInfoTick(corrSymbol, tick);
int barsNeeded = InpMomentumLookback + 2;
double closeBuf[];
double atrBuf[];
double emaBuf[];
ArraySetAsSeries(closeBuf, true);
ArraySetAsSeries(atrBuf, true);
ArraySetAsSeries(emaBuf, true);
//--- Copy closed M15 bars (start at shift 1)
if(CopyClose(corrSymbol, InpEntryTF, 1, barsNeeded, closeBuf) < barsNeeded ||
CopyBuffer(atrHandle, 0, 1, 2, atrBuf) < 2 ||
CopyBuffer(emaHandle, 0, 1, 2, emaBuf) < 2)
{
outDiag = "SyncPending";
return(false);
}
double atr = atrBuf[0];
if(atr <= 0.0)
{
outDiag = "ZeroATR";
return(false);
}
//--- Raw ATR-normalized impulse over N closed M15 bars: (Close[1] - Close[1 + N]) / ATR[1]
double rawImpulseATR = (closeBuf[0] - closeBuf[InpMomentumLookback]) / atr;
double emaSlopeATR = (emaBuf[0] - emaBuf[1]) / atr;
bool aboveEMA = (closeBuf[0] > emaBuf[0]);
bool belowEMA = (closeBuf[0] < emaBuf[0]);
//--- Translate into "USD/JPY Bullish Alignment Score"
//--- If Direct (US10Y Yield): +Impulse = Bullish USD/JPY
//--- If Inverse (XAUUSD / T-Note Price): -Impulse = Bullish USD/JPY
double alignedBullishScore = isDirectCorrelation ? rawImpulseATR : -rawImpulseATR;
double alignedEMASlope = isDirectCorrelation ? emaSlopeATR : -emaSlopeATR;
bool alignedEMAPosition = isDirectCorrelation ? aboveEMA : belowEMA;
bool alignedEMAShortPos = isDirectCorrelation ? belowEMA : aboveEMA;
bool passed = false;
if(InpFilterStrictness == STRICT_DIRECTIONAL)
{
if(usdjpyOrderType == ORDER_TYPE_BUY)
{
//--- Require positive USD/JPY alignment >= InpMinConfirmATR OR firm EMA trend
passed = (alignedBullishScore >= InpMinConfirmATR ||
(alignedEMAPosition && alignedEMASlope > 0.0 && alignedBullishScore > -0.05));
}
else if(usdjpyOrderType == ORDER_TYPE_SELL)
{
//--- Require negative USD/JPY alignment <= -InpMinConfirmATR OR bearish EMA trend
passed = (alignedBullishScore <= -InpMinConfirmATR ||
(alignedEMAShortPos && alignedEMASlope < 0.0 && alignedBullishScore < 0.05));
}
}
else // VETO_OPPOSING_SPIKE
{
if(usdjpyOrderType == ORDER_TYPE_BUY)
{
//--- Allow USD/JPY Long unless secondary asset is spiking against USD/JPY
passed = (alignedBullishScore > -InpMaxVetoSpikeATR);
}
else if(usdjpyOrderType == ORDER_TYPE_SELL)
{
//--- Allow USD/JPY Short unless secondary asset is spiking in favor of USD/JPY
passed = (alignedBullishScore < InpMaxVetoSpikeATR);
}
}
outDiag = StringFormat("%s RawROC:%.2fATR Aligned:%.2fATR (%s)",
corrSymbol, rawImpulseATR, alignedBullishScore, (passed ? "OK" : "BLOCKED"));
return(passed);
}
//+------------------------------------------------------------------+
//| Reset daily starting balance at server midnight |
//+------------------------------------------------------------------+
void UpdateDailyBaseline()
{
MqlDateTime dt;
TimeCurrent(dt);
if(dt.day_of_year != ExtCurrentDayOfYear)
{
ExtCurrentDayOfYear = dt.day_of_year;
ExtDayStartBalance = AccountInfoDouble(ACCOUNT_BALANCE);
ExtDailyCircuitTripped = false;
}
}
//+------------------------------------------------------------------+
//| Verify 1.50% Daily Drawdown & Max Consecutive Stop-Outs |
//+------------------------------------------------------------------+
bool CheckDailyCircuitBreaker()
{
if(ExtDailyCircuitTripped)
return(true);
datetime dayStart = iTime(_Symbol, PERIOD_D1, 0);
double realizedToday = 0.0;
int losingTradesToday = 0;
if(HistorySelect(dayStart, TimeCurrent()))
{
int totalDeals = HistoryDealsTotal();
for(int i = 0; i < totalDeals; i++)
{
ulong dealTicket = HistoryDealGetTicket(i);
if(dealTicket == 0) continue;
if(HistoryDealGetInteger(dealTicket, DEAL_MAGIC) != (long)InpMagicNumber) continue;
if(HistoryDealGetString(dealTicket, DEAL_SYMBOL) != _Symbol) continue;
long dealEntry = HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
if(dealEntry == DEAL_ENTRY_OUT || dealEntry == DEAL_ENTRY_INOUT)
{
double netDealProfit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT)
+ HistoryDealGetDouble(dealTicket, DEAL_COMMISSION)
+ HistoryDealGetDouble(dealTicket, DEAL_SWAP);
realizedToday += netDealProfit;
if(netDealProfit < -1.0)
losingTradesToday++;
}
}
}
double floatingPnL = GetOpenFloatingPnL();
double totalDailyPnL = realizedToday + floatingPnL;
double maxAllowedLoss = -1.0 * ExtDayStartBalance * (InpMaxDailyDrawdownPct / 100.0);
if(totalDailyPnL <= maxAllowedLoss || losingTradesToday >= InpMaxDailyLosingTrades)
{
ExtDailyCircuitTripped = true;
string msg = StringFormat("Circuit Breaker Tripped on %s | Daily PnL: %.2f | Losses Today: %d",
_Symbol, totalDailyPnL, losingTradesToday);
Print(msg);
if(InpEnablePushNotify) SendNotification(msg);
if(InpCloseAllOnDailyLimit)
CloseAllEAPositions();
return(true);
}
return(false);
}
//+------------------------------------------------------------------+
//| Tick-level Position Management: +20 Pip Lock & Leg 2 Scale-In |
//+------------------------------------------------------------------+
void ManageOpenPositions()
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
if(bid >= InpMoFCeilingCutoff)
{
if(HasOpenPositionByTag(LEG1_TAG) || HasOpenPositionByTag(LEG2_TAG))
{
Print("MoF Ceiling Cutoff breached (>= ", InpMoFCeilingCutoff, "). Flattening exposure.");
CloseAllEAPositions();
}
return;
}
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!ExtPosition.SelectByIndex(i)) continue;
if(ExtPosition.Symbol() != _Symbol || ExtPosition.Magic() != InpMagicNumber) continue;
if(StringFind(ExtPosition.Comment(), LEG1_TAG) < 0) continue;
ulong leg1Ticket = ExtPosition.Ticket();
double leg1OpenPrice = ExtPosition.PriceOpen();
double leg1CurrentSL = ExtPosition.StopLoss();
double leg1CurrentTP = ExtPosition.TakeProfit();
double leg1Volume = ExtPosition.Volume();
ENUM_POSITION_TYPE posType = ExtPosition.PositionType();
double triggerDist = InpScaleTriggerPips * ExtPipSize;
double lockDist = InpLockedProfitPips * ExtPipSize;
if(posType == POSITION_TYPE_BUY)
{
double currentGainPrice = bid - leg1OpenPrice;
double targetLockSL = NormalizeDouble(leg1OpenPrice + lockDist, _Digits);
if(currentGainPrice >= triggerDist && leg1CurrentSL < targetLockSL - (_Point * 0.5))
{
if(ExtTrade.PositionModify(leg1Ticket, targetLockSL, leg1CurrentTP))
{
PrintFormat("Leg 1 BUY #%I64u SL locked at +%.1f pips (%.3f).",
leg1Ticket, InpLockedProfitPips, targetLockSL);
if(!HasOpenPositionByTag(LEG2_TAG))
ExecuteHouseMoneyScaleIn(ORDER_TYPE_BUY, leg1Volume, lockDist, ask, leg1CurrentTP);
}
}
}
else if(posType == POSITION_TYPE_SELL)
{
double currentGainPrice = leg1OpenPrice - ask;
double targetLockSL = NormalizeDouble(leg1OpenPrice - lockDist, _Digits);
if(currentGainPrice >= triggerDist && (leg1CurrentSL == 0.0 || leg1CurrentSL > targetLockSL + (_Point * 0.5)))
{
if(ExtTrade.PositionModify(leg1Ticket, targetLockSL, leg1CurrentTP))
{
PrintFormat("Leg 1 SELL #%I64u SL locked at +%.1f pips (%.3f).",
leg1Ticket, InpLockedProfitPips, targetLockSL);
if(!HasOpenPositionByTag(LEG2_TAG))
ExecuteHouseMoneyScaleIn(ORDER_TYPE_SELL, leg1Volume, lockDist, bid, leg1CurrentTP);
}
}
}
}
}
//+------------------------------------------------------------------+
//| Open Leg 2 Funded Strictly by Locked-In Dollar Profit of Leg 1 |
//+------------------------------------------------------------------+
void ExecuteHouseMoneyScaleIn(ENUM_ORDER_TYPE orderType,
double leg1Volume,
double lockedPriceDistance,
double entryPrice,
double takeProfitPrice)
{
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE_LOSS);
if(tickValue <= 0.0)
tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
if(tickSize <= 0.0 || tickValue <= 0.0)
return;
double lockedProfitDollars = leg1Volume * (lockedPriceDistance / tickSize) * tickValue;
double riskBudgetForLeg2 = lockedProfitDollars * InpCommissionBuffer;
double leg2SLDistance = InpScaleStopLossPips * ExtPipSize;
double leg2Lots = CalculateLotSizeFromMoney(riskBudgetForLeg2, leg2SLDistance);
if(leg2Lots <= 0.0)
return;
double leg2SL = (orderType == ORDER_TYPE_BUY)
? NormalizeDouble(entryPrice - leg2SLDistance, _Digits)
: NormalizeDouble(entryPrice + leg2SLDistance, _Digits);
double leg2TP = NormalizeDouble(takeProfitPrice, _Digits);
bool res = (orderType == ORDER_TYPE_BUY)
? ExtTrade.Buy(leg2Lots, _Symbol, entryPrice, leg2SL, leg2TP, LEG2_TAG)
: ExtTrade.Sell(leg2Lots, _Symbol, entryPrice, leg2SL, leg2TP, LEG2_TAG);
if(res)
{
string msg = StringFormat("House-Money Leg 2 Opened (%s %.2f lots @ %.3f) | Risk Budget: $%.2f ($0 Principal Risk)",
(orderType == ORDER_TYPE_BUY ? "BUY" : "SELL"),
leg2Lots, entryPrice, riskBudgetForLeg2);
Print(msg);
if(InpEnablePushNotify) SendNotification(msg);
}
}
//+------------------------------------------------------------------+
//| Execute Leg 1 Base Entry (0.50% Risk) |
//+------------------------------------------------------------------+
void ExecuteBaseEntry(ENUM_ORDER_TYPE orderType, double entryPrice, double targetPrice)
{
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double riskMoney = balance * (InpBaseRiskPercent / 100.0);
double slDistance = InpBaseStopLossPips * ExtPipSize;
double lotSize = CalculateLotSizeFromMoney(riskMoney, slDistance);
if(lotSize <= 0.0)
return;
double slPrice = (orderType == ORDER_TYPE_BUY)
? NormalizeDouble(entryPrice - slDistance, _Digits)
: NormalizeDouble(entryPrice + slDistance, _Digits);
double tpPrice = NormalizeDouble(targetPrice, _Digits);
bool res = (orderType == ORDER_TYPE_BUY)
? ExtTrade.Buy(lotSize, _Symbol, entryPrice, slPrice, tpPrice, LEG1_TAG)
: ExtTrade.Sell(lotSize, _Symbol, entryPrice, slPrice, tpPrice, LEG1_TAG);
if(res)
{
string msg = StringFormat("Leg 1 %s %.2f lots @ %.3f | SL: %.3f | TP: %.3f | Macro: %s",
(orderType == ORDER_TYPE_BUY ? "BUY" : "SELL"),
lotSize, entryPrice, slPrice, tpPrice, ExtLastMacroDiagnostics);
Print(msg);
if(InpEnablePushNotify) SendNotification(msg);
}
}
//+------------------------------------------------------------------+
//| Calculate Normalized Lot Size from Dollar Risk & Price Distance |
//+------------------------------------------------------------------+
double CalculateLotSizeFromMoney(double riskMoney, double slPriceDistance)
{
if(riskMoney <= 0.0 || slPriceDistance <= 0.0)
return(0.0);
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE_LOSS);
if(tickValue <= 0.0)
tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
if(tickSize <= 0.0 || tickValue <= 0.0)
return(0.0);
double lossPerLot = (slPriceDistance / tickSize) * tickValue;
if(lossPerLot <= 0.0)
return(0.0);
double rawLots = riskMoney / lossPerLot;
double minVol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double maxVol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double stepVol = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double normalizedLots = MathFloor(rawLots / stepVol) * stepVol;
if(normalizedLots < minVol)
return(0.0);
if(normalizedLots > maxVol)
normalizedLots = maxVol;
return(NormalizeDouble(normalizedLots, 2));
}
//+------------------------------------------------------------------+
//| Helper: Check if Prime Tokyo or London-NY Overlap Window (UTC) |
//+------------------------------------------------------------------+
bool IsWithinPrimeSessionUTC()
{
MqlDateTime utc;
TimeGMT(utc);
bool inTokyo = (utc.hour >= InpTokyoStartHourUTC && utc.hour < InpTokyoEndHourUTC);
bool inOverlap = (utc.hour >= InpOverlapStartHourUTC && utc.hour < InpOverlapEndHourUTC);
return(inTokyo || inOverlap);
}
//+------------------------------------------------------------------+
//| Helper: Verify Raw ECN Spread is Below Threshold |
//+------------------------------------------------------------------+
bool IsSpreadAcceptable()
{
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double currentSpreadPips = (ask - bid) / ExtPipSize;
return(currentSpreadPips <= InpMaxSpreadPips);
}
//+------------------------------------------------------------------+
//| Helper: Check if a Position with Specific Comment Tag Exists |
//+------------------------------------------------------------------+
bool HasOpenPositionByTag(string tag)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!ExtPosition.SelectByIndex(i)) continue;
if(ExtPosition.Symbol() == _Symbol &&
ExtPosition.Magic() == InpMagicNumber &&
StringFind(ExtPosition.Comment(), tag) >= 0)
{
return(true);
}
}
return(false);
}
//+------------------------------------------------------------------+
//| Helper: Get Total Floating PnL of Open Positions for this EA |
//+------------------------------------------------------------------+
double GetOpenFloatingPnL()
{
double floating = 0.0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!ExtPosition.SelectByIndex(i)) continue;
if(ExtPosition.Symbol() == _Symbol && ExtPosition.Magic() == InpMagicNumber)
{
floating += ExtPosition.Profit() + ExtPosition.Swap() + ExtPosition.Commission();
}
}
return(floating);
}
//+------------------------------------------------------------------+
//| Helper: Close All Open Positions Belonging to This EA |
//+------------------------------------------------------------------+
void CloseAllEAPositions()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!ExtPosition.SelectByIndex(i)) continue;
if(ExtPosition.Symbol() == _Symbol && ExtPosition.Magic() == InpMagicNumber)
{
ExtTrade.PositionClose(ExtPosition.Ticket());
}
}
}
//+------------------------------------------------------------------+