Text Content
//+------------------------------------------------------------------+
//| Lily EA v2.0.mq5 |
//| Gold Hedge EA for MT5 - Prop Firm Compliant |
//| Advanced: News, Sessions, Volume, DXY, ML, MC |
//+------------------------------------------------------------------+
#property copyright "Lily EA v2.0 - 2026"
#property link ""
#property version "2.00"
#property description "Lily v2.0 - Institutional-Grade Gold Hedging EA"
#property description "News Filter | Session Trading | Volume Profile | DXY Correlation"
#property description "Breakout Detection | ML Pattern Recognition | Monte Carlo Validated"
//+------------------------------------------------------------------+
//| Includes |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#include <Trade\OrderInfo.mqh>
#include <Trade\AccountInfo.mqh>
#include <Trade\SymbolInfo.mqh>
#include <Trade\DealInfo.mqh>
//+------------------------------------------------------------------+
//| EA Identification |
//+------------------------------------------------------------------+
#define EA_NAME "Lily"
#define EA_VERSION "2.0"
#define EA_MAGIC 20260220
//+------------------------------------------------------------------+
//| Enums |
//+------------------------------------------------------------------+
enum ENUM_MARKET_REGIME
{
REGIME_RANGING = 0,
REGIME_TRENDING_UP = 1,
REGIME_TRENDING_DOWN = 2,
REGIME_HIGH_VOLATILITY = 3
};
enum ENUM_SESSION_ACTION
{
SESSION_NORMAL = 0,
SESSION_CONSERVATIVE = 1,
SESSION_AGGRESSIVE = 2,
SESSION_PAUSED = 3
};
enum ENUM_TRADING_SESSION
{
SESSION_ASIAN = 0,
SESSION_LONDON = 1,
SESSION_NEWYORK = 2,
SESSION_LONDON_NY_OVERLAP = 3,
SESSION_OFF_HOURS = 4
};
enum ENUM_SIGNAL_STRENGTH
{
SIGNAL_NONE = 0,
SIGNAL_WEAK = 1,
SIGNAL_MODERATE = 2,
SIGNAL_STRONG = 3,
SIGNAL_VERY_STRONG = 4
};
enum ENUM_LOG_LEVEL
{
LOG_CRITICAL = 0, // Always: errors, SL hits, trade opens/closes
LOG_NORMAL = 1, // State changes, daily reports, pair creation
LOG_VERBOSE = 2 // Signal scores, regime, velocity updates
};
//+------------------------------------------------------------------+
//| Input Parameters - General Settings |
//+------------------------------------------------------------------+
input group "═══ General Settings ═══"
input string InpTradingSymbol = "XAUUSDm"; // Trading Symbol
input bool InpEnableVerboseLog = true; // Enable Detailed Logging
input bool InpEnableTradeLogging = true; // Enable Trade Event Logging
input int InpMaxOpenPairs = 2; // Maximum Open Hedged Pairs
input int InpMaxPairsAllowed = 2; // Maximum Total Pairs
input ulong InpMagicNumber = EA_MAGIC; // Magic Number
//+------------------------------------------------------------------+
//| Input Parameters - Stop Loss & Take Profit |
//+------------------------------------------------------------------+
input group "═══ Stop Loss & Take Profit ═══"
input int InpStopLossPips = 250; // Stop Loss (Pips) - Fallback/Reference
input int InpTakeProfitPips = 300; // Take Profit (Pips) - Fallback/Reference
input double InpReHedgeThresholdPips= 50.0; // Re-hedge Threshold (Pips)
input int InpOrphanMonitorMins = 5; // Orphan Monitoring Time (Minutes)
input int InpSLDelayMinutes = 5; // Delay After SL Hit (Minutes)
//+------------------------------------------------------------------+
//| Input Parameters - ATR-Based Adaptive SL/TP (Phase 1) |
//+------------------------------------------------------------------+
input group "═══ ATR-Based Adaptive SL/TP ═══"
input bool InpUseATR = true; // Use ATR for SL/TP (Dynamic)
input double InpATRSLMultiplier = 3.0; // ATR × Multiplier = SL
input double InpATRTPMultiplier = 4.5; // ATR × Multiplier = TP
//+------------------------------------------------------------------+
//| Input Parameters - Trailing Stop |
//+------------------------------------------------------------------+
input group "═══ Trailing Stop ═══"
input bool InpEnableTrailing = true; // Enable Trailing Stop
input int InpTrailingStartPips = 150; // Start Trailing After (Pips)
input int InpTrailingStepPips = 10; // Trailing Step (Pips)
input int InpTrailingDistPips = 100; // Trailing Distance (Pips)
//+------------------------------------------------------------------+
//| Input Parameters - Partial Close |
//+------------------------------------------------------------------+
input group "═══ Trailing & Partial Close ═══"
input bool InpEnablePartialClose = true; // Enable Partial Close
input double InpPartialClosePercent = 50.0; // Partial Close Percentage (%)
input int InpPartialClosePips = 100; // Partial Close at Pips Profit
input bool InpEnablePartialLoss = true; // Enable Partial Close on Loss
input int InpPartialLossPips = 80; // Partial Close at Pips Loss
input bool InpMoveSLToBreakeven = true; // Move SL to Breakeven After Partial
//+------------------------------------------------------------------+
//| Input Parameters - Position Sizing |
//+------------------------------------------------------------------+
input group "═══ Position Sizing ═══"
input bool InpUsePercentRisk = true; // Use Percentage Risk
input double InpRiskPerTrade = 0.5; // Risk Per Trade (%)
input double InpFixedLotSize = 0.03; // Fixed Lot Size
input double InpMaxPositionSize = 0.05; // Maximum Position Size (Lots)
input double InpMinFreeMargin = 200.0; // Minimum Free Margin (USD)
//+------------------------------------------------------------------+
//| Input Parameters - Prop Firm Compliance |
//+------------------------------------------------------------------+
input group "═══ Prop Firmfurth Compliance ═══"
input bool InpPropFirmMode = true; // Enable Prop Firm Mode
input double InpMaxDailyLossPercent = 4.0; // Max Daily Loss (%)
input double InpMaxTotalLossPercent = 8.0; // Max Total Drawdown (%)
input double InpDailyProfitTarget = 2.0; // Daily Profit Target (%)
input double InpInitialBalance = 0.0; // Initial Balance (0=Auto)
input bool InpCloseBeforeWeekend = true; // Close Before Weekend
input int InpFridayCloseHour = 22; // Friday Close Hour
//+------------------------------------------------------------------+
//| Input Parameters - Equity Protection |
//+------------------------------------------------------------------+
input group "═══ Equity Protection ═══"
input bool InpEnableEquityProtect = true; // Enable Equity Protection
input double InpEquityHWMPercent = 85.0; // Equity HWM Protection (%)
input double InpMaxBasketDDPercent = 15.0; // Max Basket Drawdown (%)
//+------------------------------------------------------------------+
//| Input Parameters - Basket Management |
//+------------------------------------------------------------------+
input group "═══ Basket Management ═══"
input bool InpEnableBasketClose = true; // Enable Basket Close
input double InpBasketProfitUSD = 50.0; // Basket Profit Target (USD)
input double InpBasketLossUSD = 80.0; // Basket Loss Stop (USD)
//+------------------------------------------------------------------+
//| Input Parameters - Market Regime |
//+------------------------------------------------------------------+
input group "═══ Market Regime ═══"
input bool InpFilterByRegime = true; // Filter by Regime
input bool InpAllowRanging = true; // Allow Ranging
input bool InpAllowTrendUp = true; // Allow Uptrend
input bool InpAllowTrendDown = true; // Allow Downtrend
input bool InpAllowHighVol = false; // Allow High Volatility
input int InpADXPeriod = 14; // ADX Period
input double InpTrendThreshold = 25.0; // ADX Trend Threshold
input double InpMaxVolatilityPts = 50000.0; // Max Volatility (Points)
//+------------------------------------------------------------------+
//| Input Parameters - Session-Based Trading (NEW) |
//+------------------------------------------------------------------+
input group "═══ Session-Based Trading ═══"
input bool InpEnableSessionFilter = true; // Enable Session Filter
input bool InpAllowAsianSession = false; // Allow Asian Session (00:00-08:00 GMT)
input bool InpAllowLondonSession = true; // Allow London Session (08:00-16:00 GMT)
input bool InpAllowNYSession = true; // Allow NY Session (13:00-21:00 GMT)
input bool InpPreferOverlap = true; // Prefer London/NY Overlap (13:00-16:00)
input double InpOverlapLotMultiplier= 1.3; // Lot Multiplier During Overlap
input int InpTimezoneOffset = 5; // Timezone offset from GMT (e.g., +5 for Pakistan)
//+------------------------------------------------------------------+
//| Input Parameters - News Filter (NEW) |
//+------------------------------------------------------------------+
input group "═══ News Filter ═══"
input bool InpEnableNewsFilter = false; // Enable News Filter
input int InpNewsMinutesBefore = 30; // Stop Trading X Min Before News
input int InpNewsMinutesAfter = 15; // Resume Trading X Min After News
input bool InpCloseBeforeNews = false; // Close Positions Before High-Impact News
input bool InpUseBuiltInCalendar = true; // Use MT5 Built-in Economic Calendar
//+------------------------------------------------------------------+
//| Input Parameters - Dynamic Spread Filter (NEW) |
//+------------------------------------------------------------------+
input group "═══ Dynamic Spread Filter ═══"
input bool InpEnableSpreadFilter = false; // Enable Spread Filter
input double InpMaxSpreadMultiplier = 2.5; // Max Spread as Multiple of Average
input int InpSpreadSampleSize = 100; // Spread Sample Size for Average
input double InpMaxAbsoluteSpread = 300.0; // Max Absolute Spread (Points)
//+------------------------------------------------------------------+
//| Input Parameters - DXY Correlation (NEW) |
//+------------------------------------------------------------------+
input group "═══ DXY Correlation ═══"
input bool InpEnableDXYCorrelation= true; // Enable DXY Correlation Filter
input string InpDXYSymbol = "USDX"; // DXY/USDX Symbol Name
input int InpDXYCorrelationBars = 20; // Correlation Lookback Bars
input double InpDXYCorrelationMin = -0.5; // Min Negative Correlation to Confirm
//+------------------------------------------------------------------+
//| Input Parameters - Breakout Detection (NEW) |
//+------------------------------------------------------------------+
input group "═══ Breakout Detection ═══"
input bool InpEnableBreakout = true; // Enable Breakout Detection
input int InpConsolidationBars = 20; // Consolidation Lookback Bars
input double InpConsolidationATRMult= 0.5; // Consolidation ATR Multiplier
input double InpBreakoutATRMult = 1.5; // Breakout Confirmation ATR Multiplier
input int InpBreakoutVolumeMult = 150; // Breakout Volume % of Average
//+------------------------------------------------------------------+
//| Input Parameters - Volume Profile (NEW) |
//+------------------------------------------------------------------+
input group "═══ Volume Profile / Order Flow ═══"
input bool InpEnableVolumeProfile = true; // Enable Volume Profile Analysis
input int InpVPLookbackBars = 100; // Volume Profile Lookback Bars
input int InpVPLevels = 20; // Number of Price Levels
input double InpVPHVNThreshold = 1.5; // High Volume Node Threshold (x avg)
input bool InpUseDeltaVolume = true; // Use Delta Volume Analysis
//+------------------------------------------------------------------+
//| Input Parameters - ML Pattern Recognition (NEW) |
//+------------------------------------------------------------------+
input group "═══ ML Pattern Recognition ═══"
input bool InpEnableML = true; // Enable ML Pattern Recognition
input int InpMLLookback = 50; // Pattern Lookback Bars
input int InpMLPatternLength = 5; // Pattern Length (bars)
input double InpMLMinSimilarity = 0.75; // Min Pattern Similarity (0-1)
input int InpMLMinSamples = 10; // Min Samples for Prediction
input double InpMLMinWinRate = 0.55; // Min Win Rate for Pattern
//+------------------------------------------------------------------+
//| Input Parameters - Self-Learning |
//+------------------------------------------------------------------+
input group "═══ Self-Learning System ═══"
input bool InpEnableLearning = true; // Enable Self-Learning
input int InpHistoryAnalysisCount= 50; // Historical Trades to Analyze
input double InpMinWinRatioTarget = 0.51; // Target Win Ratio
input bool InpAdaptiveSLTP = true; // Adaptive SL/TP
input bool InpAdaptiveLotSize = true; // Adaptive Lot Sizing
//+------------------------------------------------------------------+
//| Input Parameters - Velocity Re-hedge (P1) |
//+------------------------------------------------------------------+
input group "═══ Velocity Re-hedge ═══"
input int InpVelocitySampleSecs = 30; // Velocity Sample Interval (Seconds)
input double InpVelocityNormPipsPerMin = 1.0; // Velocity Normalization (Pips/Min)
//+------------------------------------------------------------------+
//| Input Parameters - Re-hedge Lot Sizing (P2) |
//+------------------------------------------------------------------+
input group "═══ Re-hedge Lot Sizing ═══"
input double InpMaxReHedgeLotMultiplier = 2.0; // Max Re-hedge Lot Multiplier (1.0=mirror, 2.0=recovery)
//+------------------------------------------------------------------+
//| Input Parameters - Orphan Partial Close (P3) |
//+------------------------------------------------------------------+
input group "═══ Orphan Partial Close ═══"
input double InpOrphanPartialClosePips = 15.0; // Orphan Partial Close at Pips Profit
input double InpOrphanBEBufferPips = 1.5; // Breakeven SL Buffer (Pips)
//+------------------------------------------------------------------+
//| Input Parameters - Orphan Drawdown Cap (P4) |
//+------------------------------------------------------------------+
input group "═══ Orphan Drawdown Cap ═══"
input double InpOrphanDrawdownL1Pct = 1.0; // L1: Tighten SLs at % Equity
input double InpOrphanDrawdownL2Pct = 2.0; // L2: Partial Close 50% at % Equity
input double InpOrphanDrawdownL3Pct = 3.5; // L3: Full Close Orphans at % Equity
input double InpOrphanSLBufferPips = 5.0; // Orphan SL Tightening Buffer (Pips)
//+------------------------------------------------------------------+
//| Input Parameters - Multi-Timeframe |
//+------------------------------------------------------------------+
input group "═══ Multi-Timeframe ═══"
input bool InpUseMTFConfirmation = true; // Use MTF Confirmation
input ENUM_TIMEFRAMES InpHTFPeriod = PERIOD_H4; // Higher Timeframe
input ENUM_TIMEFRAMES InpMTFPeriod = PERIOD_H1; // Medium Timeframe
input ENUM_TIMEFRAMES InpLTFPeriod = PERIOD_M15; // Lower Timeframe
//+------------------------------------------------------------------+
//| Input Parameters - Monte Carlo (NEW) |
//+------------------------------------------------------------------+
input group "═══ Monte Carlo Validation ═══"
input bool InpEnableMonteCarlo = false; // Enable Monte Carlo Risk Check
input int InpMCSimulations = 500; // Number of Simulations
input double InpMCConfidenceLevel = 0.95; // Confidence Level (0.95 = 95%)
input double InpMCMaxDrawdownPct = 15.0; // Max Acceptable DD at Confidence Level
//+------------------------------------------------------------------+
//| Input Parameters - Momentum Exhaustion Detection (Phase 3) |
//+------------------------------------------------------------------+
input group "═══ Momentum Exhaustion Detection ═══"
input bool InpEnableMomentumExhaustion = true; // Enable Momentum Exhaustion Filter
input double InpMESBlockThreshold = 0.50; // Block trend entries above this MES
input double InpMESFullBlockThreshold = 0.70; // Block ALL entries above this MES
input int InpRSIDivergenceBars = 10; // RSI Divergence Lookback (bars)
//+------------------------------------------------------------------+
//| Input Parameters - Re-Entry Cooldown (Phase 2) |
//+------------------------------------------------------------------+
input group "═══ Re-Entry Cooldown ═══"
input int InpReEntryCooldownMin = 2; // Re-Entry Cooldown (minutes, 0=off)
//+------------------------------------------------------------------+
//| Input Parameters - Logging (Phase 4) |
//+------------------------------------------------------------------+
input group "═══ Logging ═══"
input ENUM_LOG_LEVEL InpLogLevel = LOG_NORMAL; // Log Detail Level
//+------------------------------------------------------------------+
//| Structures |
//+------------------------------------------------------------------+
struct STradePair
{
ulong buyTicket;
ulong sellTicket;
double lotSize;
datetime openTime;
bool buyClosed;
bool sellClosed;
bool buyPartialClosed;
bool sellPartialClosed;
double buyEntryPrice;
double sellEntryPrice;
};
struct SOrphanedLeg
{
ulong ticket;
bool isBuy;
double lotSize;
double entryPrice;
datetime closeTime;
int pairIndex;
double initialLoss;
bool partialClosed;
datetime velocityCheckTime; // P1: timestamp of last velocity sample
double pipsAtLastSample; // P1: signed pip P&L at last sample
double pipVelocity; // P1: EMA-smoothed pips/minute
bool partialCloseFired; // P3: one-time guard for partial close on winners
int drawdownLevel; // P4: highest drawdown level already fired (0/1/2)
};
struct SLearningData
{
int totalTrades;
int winCount;
int lossCount;
double totalWinPips;
double totalLossPips;
double winRatio;
double avgWinPips;
double avgLossPips;
double profitFactor;
double expectancy;
double bestSLPips;
double bestTPPips;
double optimalRiskPercent;
int bestHour;
int worstHour;
double hourlyWinRate[24];
int hourlyTradeCount[24];
double regimeWinRate[4];
int regimeTradeCount[4];
int consecutiveWins;
int consecutiveLosses;
int maxConsecutiveWins;
int maxConsecutiveLosses;
int currentStreak;
};
struct SSessionData
{
datetime sessionStart;
double sessionStartBalance;
double sessionPeakEquity;
int sessionTrades;
int sessionWins;
int sessionLosses;
double sessionProfit;
double sessionMaxDD;
double dailyStartBalance;
double dailyPnL;
bool dailyTargetReached;
ENUM_SESSION_ACTION currentAction;
};
struct SNewsEvent
{
datetime eventTime;
string currency;
string eventName;
int importance; // 1=Low, 2=Medium, 3=High
};
struct SVolumeLevel
{
double price;
double volume;
bool isHVN; // High Volume Node
bool isLVN; // Low Volume Node
};
struct SMLPattern
{
double features[]; // Normalized price changes
bool wasWin;
double profitPips;
};
struct SMonteCarloResult
{
double avgReturn;
double maxDrawdown;
double drawdownAtConfidence;
double winRateRange;
bool isViable;
};
struct SBreakoutData
{
bool isConsolidating;
double rangeHigh;
double rangeLow;
double rangeATR;
bool breakoutDetected;
bool breakoutBullish;
double breakoutStrength;
};
//+------------------------------------------------------------------+
//| Global Variables |
//+------------------------------------------------------------------+
CTrade trade;
CPositionInfo posInfo;
CSymbolInfo symInfo;
CAccountInfo accInfo;
CDealInfo dealInfo;
STradePair tradePairs[];
SOrphanedLeg orphanedLegs[];
SLearningData learning;
SSessionData session;
SBreakoutData breakout;
SMonteCarloResult mcResult;
ENUM_MARKET_REGIME currentRegime = REGIME_RANGING;
double currentVolatilityPts = 0;
// Indicator Handles
int hADX_MTF = INVALID_HANDLE;
int hATR_MTF = INVALID_HANDLE;
int hEMA5 = INVALID_HANDLE;
int hEMA20 = INVALID_HANDLE;
int hADX_HTF = INVALID_HANDLE;
int hEMA5_LTF = INVALID_HANDLE;
int hEMA20_LTF = INVALID_HANDLE;
int hRSI_MTF = INVALID_HANDLE;
int hATR_LTF = INVALID_HANDLE;
int hBB_MTF = INVALID_HANDLE; // Bollinger Bands for breakout
// DXY Indicator Handles
int hDXY_EMA5 = INVALID_HANDLE;
int hDXY_EMA20 = INVALID_HANDLE;
// SL Tracking
int consecutiveSLHits = 0;
datetime lastThreeSLTimes[3];
bool tradingPausedDueToSL = false;
bool inSLTradeDelay = false;
datetime lastSLHitTime = 0;
// State
datetime lastRefreshTime = 0;
double peakEquity = 0;
datetime lastTradeTime = 0;
bool learningInitialized = false;
double initialAccountBalance = 0;
string tradingSymbol = "";
bool dxyAvailable = false;
bool dailyTradingHalted = false; // Phase 5: Prop firm stop
// Spread tracking
double spreadHistory[];
int spreadHistoryIndex = 0;
double averageSpread = 0;
bool spreadInitialized = false;
// News tracking
SNewsEvent upcomingNews[];
datetime lastNewsCheck = 0;
bool newsFilterActive = false;
// Rate-limited logging timestamps (prevents disk full from per-tick logging)
datetime _lastLogTime_news = 0; // News filter log throttle
datetime _lastLogTime_session = 0; // Session filter log throttle
datetime _lastLogTime_trade = 0; // Trade block log throttle
datetime _lastLogTime_diag = 0; // Periodic diagnostic throttle
datetime _lastLogTime_signal = 0; // Signal score log throttle
int logThrottleIntervalSec = 300; // Log once per 5 min (was 60)
// Trade diagnostic visibility
string lastTradeBlockReason = ""; // Why trading is currently blocked
// Volume Profile
SVolumeLevel volumeLevels[];
double pocPrice = 0; // Point of Control
double vahPrice = 0; // Value Area High
double valPrice = 0; // Value Area Low
// ML Pattern Database
SMLPattern patternDB[];
int patternDBSize = 0;
// Partial close tracking
struct SPartialCloseTracker
{
ulong ticket;
bool closed;
};
SPartialCloseTracker partialCloseTracker[];
// Signal scoring
double signalScore = 0;
ENUM_SIGNAL_STRENGTH signalStrength = SIGNAL_NONE;
// Phase 3: Momentum Exhaustion Detection
double momentumExhaustionScore = 0.0;
bool mesBlockBuy = false;
bool mesBlockSell = false;
// Phase 4: Log deduplication buffer
string _lastLogMessages[10];
int _lastLogMsgIndex = 0;
//+------------------------------------------------------------------+
//| Expert initialization function |
//+------------------------------------------------------------------+
int OnInit()
{
Print("═══════════════════════════════════════════");
Print(" Initializing ", EA_NAME, " EA v", EA_VERSION);
Print("═══════════════════════════════════════════");
//--- Validate symbol
tradingSymbol = InpTradingSymbol;
if(!SymbolSelect(tradingSymbol, true))
{
string variations[] = {"XAUUSD", "XAUUSDc", "XAUUSD.", "XAUUSD.a", "GOLD", "GOLDc"};
bool found = false;
for(int i = 0; i < ArraySize(variations); i++)
{
if(SymbolSelect(variations[i], true))
{
tradingSymbol = variations[i];
found = true;
break;
}
}
if(!found)
tradingSymbol = Symbol();
}
//--- Initialize symbol info
if(!symInfo.Name(tradingSymbol))
{
Print("ERROR: Cannot initialize symbol: ", tradingSymbol);
return(INIT_FAILED);
}
symInfo.Refresh();
//--- Setup trade object
trade.SetExpertMagicNumber(InpMagicNumber);
trade.SetDeviationInPoints(30);
trade.SetTypeFilling(GetFillingMode(tradingSymbol));
trade.SetMarginMode();
//--- Initialize arrays
ArrayResize(tradePairs, 0);
ArrayResize(orphanedLegs, 0);
ArrayResize(partialCloseTracker, 0);
ArrayResize(upcomingNews, 0);
ArrayResize(volumeLevels, 0);
ArrayResize(patternDB, 0);
//--- Initialize spread history
ArrayResize(spreadHistory, InpSpreadSampleSize);
ArrayInitialize(spreadHistory, 0);
spreadHistoryIndex = 0;
spreadInitialized = false;
//--- Initialize indicators
if(!InitializeIndicators())
{
Print("ERROR: Failed to initialize indicators");
return(INIT_FAILED);
}
//--- Check DXY availability
if(InpEnableDXYCorrelation)
InitializeDXY();
//--- Initialize state
ResetSLTracking();
peakEquity = accInfo.Equity();
initialAccountBalance = (InpInitialBalance > 0) ? InpInitialBalance : accInfo.Balance();
//--- Initialize subsystems
InitializeSession();
if(InpEnableLearning)
InitializeLearning();
DetectMarketRegime();
ScanExistingPositions();
//--- Run Monte Carlo validation on startup
if(InpEnableMonteCarlo && learningInitialized && learning.totalTrades >= 30)
RunMonteCarloValidation();
//--- Set timer
EventSetTimer(1);
Print("Symbol: ", tradingSymbol, " | Balance: ", DoubleToString(initialAccountBalance, 2));
Print("Prop Firm: ", InpPropFirmMode ? "ON" : "OFF",
" | News Filter: ", InpEnableNewsFilter ? "ON" : "OFF",
" | Sessions: ", InpEnableSessionFilter ? "ON" : "OFF");
Print("DXY: ", dxyAvailable ? "ON" : "OFF",
" | Breakout: ", InpEnableBreakout ? "ON" : "OFF",
" | ML: ", InpEnableML ? "ON" : "OFF");
Print("═══════════════════════════════════════════");
return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
EventKillTimer();
Comment("");
int handles[] = {hADX_MTF, hATR_MTF, hEMA5, hEMA20, hADX_HTF,
hEMA5_LTF, hEMA20_LTF, hRSI_MTF, hATR_LTF,
hBB_MTF, hDXY_EMA5, hDXY_EMA20
};
for(int i = 0; i < ArraySize(handles); i++)
{
if(handles[i] != INVALID_HANDLE)
IndicatorRelease(handles[i]);
}
Print(EA_NAME, " v", EA_VERSION, " stopped. Reason: ", reason);
}
//+------------------------------------------------------------------+
//| Expert tick function |
//+------------------------------------------------------------------+
void OnTick()
{
symInfo.Refresh();
symInfo.RefreshRates();
//--- Update spread tracking
UpdateSpreadHistory();
//--- Update equity
UpdateEquityTracking();
//--- Periodic refresh
if(TimeCurrent() - lastRefreshTime > 300)
{
RefreshEA();
// Phase 4: Removed noisy "EA state refreshed" log
}
//--- Prop firm checks
if(InpPropFirmMode)
{
UpdateDailyPnL();
if(IsDailyLossLimitBreached())
{
if(!dailyTradingHalted)
{
CloseAllPositions("Daily loss limit");
dailyTradingHalted = true;
}
return;
}
if(IsTotalDrawdownBreached())
{
CloseAllPositions("Total drawdown limit");
return;
}
if(InpCloseBeforeWeekend && IsFridayCloseTime())
{
CloseAllPositions("Weekend close");
return;
}
}
//--- News filter check
if(InpEnableNewsFilter)
UpdateNewsFilter();
//--- Check positions
CheckPositionsStatus();
//--- Manage orphaned legs
if(ArraySize(orphanedLegs) > 0)
{
ManageOrphanedLegs();
CheckOrphanDrawdown();
}
//--- Trailing stops
if(InpEnableTrailing)
ManageTrailingStops();
//--- Partial closes
if(InpEnablePartialClose)
ManagePartialCloses();
//--- Basket management
if(InpEnableBasketClose)
CheckBasketClose();
//--- Update analysis (every tick for responsiveness)
if(InpEnableBreakout)
DetectBreakout();
if(InpEnableVolumeProfile)
UpdateVolumeProfile();
//--- Phase 3: Update Momentum Exhaustion Detection
UpdateMomentumExhaustion();
//--- Always calculate signal score (for chart display even when blocked)
CalculateSignalScore();
//--- Phase 5: Dynamic deviation (ATR-based slippage tolerance)
SetDynamicDeviation();
//--- Trade entry logic
//--- NOTE: CompleteIncompletePair removed. ManageOrphanedLegs() (above) is
//--- the sole controller for orphan trades. It uses velocity-weighted re-hedging
//--- (P1), dynamic lot sizing (P2), winner partial close (P3), and drawdown cap (P4).
//--- Opening a counter-leg while the orphan is in PROFIT caused simultaneous hedges.
int totalPairsCount = GetCompletePairsCount() + ArraySize(orphanedLegs);
if(totalPairsCount == 0 && IsTradeAllowed())
{
//--- Only open a brand-new pair when there are truly NO open positions
if(signalStrength >= SIGNAL_MODERATE)
{
CreateHedgedPair();
}
else
{
VerboseLogThrottled("Signal too weak: " + DoubleToString(signalScore, 2) +
" (" + SignalStrengthToString(signalStrength) + ")",
_lastLogTime_signal, logThrottleIntervalSec);
}
}
else if(totalPairsCount == 0)
{
// Throttled trade diagnostic log
if(TimeCurrent() - _lastLogTime_diag > logThrottleIntervalSec)
{
_lastLogTime_diag = TimeCurrent();
PrintTradeAttemptDiagnostic();
}
}
//--- Update display
UpdateChartComment();
}
//+------------------------------------------------------------------+
//| Timer function |
//+------------------------------------------------------------------+
void OnTimer()
{
if(inSLTradeDelay)
{
if(TimeCurrent() - lastSLHitTime > InpSLDelayMinutes * 60)
{
inSLTradeDelay = false;
tradingPausedDueToSL = false;
VerboseLog("SL delay expired, trading resumed");
}
}
//--- New day check
static datetime lastDay = 0;
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
datetime today = StringToTime(IntegerToString(dt.year) + "." +
IntegerToString(dt.mon) + "." +
IntegerToString(dt.day));
if(today != lastDay)
{
lastDay = today;
dailyTradingHalted = false;
OnNewDay();
}
//--- Periodic news update (every 5 minutes)
if(InpEnableNewsFilter && TimeCurrent() - lastNewsCheck > 300)
LoadNewsEvents();
}
//+------------------------------------------------------------------+
//| Trade transaction handler |
//+------------------------------------------------------------------+
void OnTradeTransaction(const MqlTradeTransaction &trans,
const MqlTradeRequest &request,
const MqlTradeResult &result)
{
if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
{
ulong dealTicket = trans.deal;
if(dealTicket > 0 && HistoryDealSelect(dealTicket))
{
long dealMagic = HistoryDealGetInteger(dealTicket, DEAL_MAGIC);
string dealSymbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL);
ENUM_DEAL_ENTRY dealEntry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
if(dealMagic == (long)InpMagicNumber && dealSymbol == tradingSymbol && dealEntry == DEAL_ENTRY_OUT)
{
double dealProfit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT) +
HistoryDealGetDouble(dealTicket, DEAL_SWAP) +
HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
double dealVolume = HistoryDealGetDouble(dealTicket, DEAL_VOLUME);
session.sessionTrades++;
session.sessionProfit += dealProfit;
if(dealProfit > 0)
{
session.sessionWins++;
if(InpEnableLearning)
UpdateLearningWin(dealProfit, dealVolume, dealTicket);
}
else
{
session.sessionLosses++;
if(InpEnableLearning)
UpdateLearningLoss(dealProfit, dealVolume, dealTicket);
}
//--- Update ML pattern database
if(InpEnableML)
RecordMLPattern(dealProfit > 0, dealProfit, dealVolume);
}
}
}
}
//+------------------------------------------------------------------+
//| Initialize Indicators |
//+------------------------------------------------------------------+
bool InitializeIndicators()
{
hADX_MTF = iADX(tradingSymbol, InpMTFPeriod, InpADXPeriod);
if(hADX_MTF == INVALID_HANDLE)
return false;
hATR_MTF = iATR(tradingSymbol, InpMTFPeriod, 14);
if(hATR_MTF == INVALID_HANDLE)
return false;
hEMA5 = iMA(tradingSymbol, PERIOD_CURRENT, 5, 0, MODE_EMA, PRICE_CLOSE);
if(hEMA5 == INVALID_HANDLE)
return false;
hEMA20 = iMA(tradingSymbol, PERIOD_CURRENT, 20, 0, MODE_EMA, PRICE_CLOSE);
if(hEMA20 == INVALID_HANDLE)
return false;
hRSI_MTF = iRSI(tradingSymbol, InpMTFPeriod, 14, PRICE_CLOSE);
hATR_LTF = iATR(tradingSymbol, InpLTFPeriod, 14);
hBB_MTF = iBands(tradingSymbol, InpMTFPeriod, 20, 0, 2.0, PRICE_CLOSE);
if(InpUseMTFConfirmation)
{
hADX_HTF = iADX(tradingSymbol, InpHTFPeriod, InpADXPeriod);
hEMA5_LTF = iMA(tradingSymbol, InpLTFPeriod, 5, 0, MODE_EMA, PRICE_CLOSE);
hEMA20_LTF = iMA(tradingSymbol, InpLTFPeriod, 20, 0, MODE_EMA, PRICE_CLOSE);
}
return true;
}
//+------------------------------------------------------------------+
//| Initialize DXY Correlation |
//+------------------------------------------------------------------+
void InitializeDXY()
{
dxyAvailable = false;
// Try common DXY symbol names
string dxyNames[] = {"USDX", "DXY", "DX", "USDX.a", "DXY.a", "USDindex"};
// First try the user-specified symbol
if(InpDXYSymbol != "" && SymbolSelect(InpDXYSymbol, true))
{
hDXY_EMA5 = iMA(InpDXYSymbol, InpMTFPeriod, 5, 0, MODE_EMA, PRICE_CLOSE);
hDXY_EMA20 = iMA(InpDXYSymbol, InpMTFPeriod, 20, 0, MODE_EMA, PRICE_CLOSE);
if(hDXY_EMA5 != INVALID_HANDLE && hDXY_EMA20 != INVALID_HANDLE)
{
dxyAvailable = true;
Print("DXY correlation enabled using: ", InpDXYSymbol);
return;
}
}
// Try alternatives
for(int i = 0; i < ArraySize(dxyNames); i++)
{
if(SymbolSelect(dxyNames[i], true))
{
hDXY_EMA5 = iMA(dxyNames[i], InpMTFPeriod, 5, 0, MODE_EMA, PRICE_CLOSE);
hDXY_EMA20 = iMA(dxyNames[i], InpMTFPeriod, 20, 0, MODE_EMA, PRICE_CLOSE);
if(hDXY_EMA5 != INVALID_HANDLE && hDXY_EMA20 != INVALID_HANDLE)
{
dxyAvailable = true;
Print("DXY correlation enabled using: ", dxyNames[i]);
return;
}
}
}
Print("WARNING: DXY symbol not available - correlation filter disabled");
}
//+------------------------------------------------------------------+
//| Get Filling Mode |
//+------------------------------------------------------------------+
ENUM_ORDER_TYPE_FILLING GetFillingMode(string symbol)
{
long fillMode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE);
if((fillMode & SYMBOL_FILLING_FOK) == SYMBOL_FILLING_FOK)
return ORDER_FILLING_FOK;
if((fillMode & SYMBOL_FILLING_IOC) == SYMBOL_FILLING_IOC)
return ORDER_FILLING_IOC;
return ORDER_FILLING_RETURN;
}
//+------------------------------------------------------------------+
//| Initialize Session |
//+------------------------------------------------------------------+
void InitializeSession()
{
session.sessionStart = TimeCurrent();
session.sessionStartBalance = accInfo.Balance();
session.sessionPeakEquity = accInfo.Equity();
session.sessionTrades = 0;
session.sessionWins = 0;
session.sessionLosses = 0;
session.sessionProfit = 0;
session.sessionMaxDD = 0;
session.dailyStartBalance = accInfo.Balance();
session.dailyPnL = 0;
session.dailyTargetReached = false;
session.currentAction = SESSION_NORMAL;
}
//+------------------------------------------------------------------+
//| New Day Handler |
//+------------------------------------------------------------------+
void OnNewDay()
{
VerboseLog("═══ NEW DAY ═══");
session.dailyStartBalance = accInfo.Balance();
session.dailyPnL = 0;
session.dailyTargetReached = false;
session.currentAction = SESSION_NORMAL;
if(InpEnableLearning)
InitializeLearning();
if(InpEnableMonteCarlo && learningInitialized && learning.totalTrades >= 30)
RunMonteCarloValidation();
if(InpEnableNewsFilter)
LoadNewsEvents();
}
//+------------------------------------------------------------------+
//| Reset SL Tracking |
//+------------------------------------------------------------------+
void ResetSLTracking()
{
consecutiveSLHits = 0;
ArrayInitialize(lastThreeSLTimes, 0);
tradingPausedDueToSL = false;
inSLTradeDelay = false;
lastSLHitTime = 0;
}
//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════ |
//| SESSION-BASED TRADING |
//| ═══════════════════════════════════════════════════════════════ |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Get Current Trading Session |
//+------------------------------------------------------------------+
ENUM_TRADING_SESSION GetCurrentSession()
{
MqlDateTime dt;
TimeToStruct(TimeGMT(), dt);
int gmtHour = dt.hour;
// Apply timezone offset (e.g., +5 for Pakistan = GMT+5)
int localHour = (gmtHour + InpTimezoneOffset + 24) % 24;
// London/NY Overlap: 13:00-16:00 GMT (8:00-11:00 Pakistan time)
if(localHour >= 8 && localHour < 11)
return SESSION_LONDON_NY_OVERLAP;
// London: 08:00-16:00 GMT (13:00-21:00 Pakistan time)
if(localHour >= 13 && localHour < 21)
return SESSION_LONDON;
// New York: 13:00-21:00 GMT (18:00-02:00 Pakistan time)
if((localHour >= 18 && localHour < 24) || (localHour >= 0 && localHour < 2))
return SESSION_NEWYORK;
// Asian: 00:00-08:00 GMT (5:00-13:00 Pakistan time)
if(localHour >= 5 && localHour < 13)
return SESSION_ASIAN;
return SESSION_OFF_HOURS;
}
//+------------------------------------------------------------------+
//| Get Current Trading Session (GMT for reference) |
//+------------------------------------------------------------------+
ENUM_TRADING_SESSION GetCurrentSessionGMT()
{
MqlDateTime dt;
TimeToStruct(TimeGMT(), dt);
int gmtHour = dt.hour;
// London/NY Overlap: 13:00-16:00 GMT
if(gmtHour >= 13 && gmtHour < 16)
return SESSION_LONDON_NY_OVERLAP;
// London: 08:00-16:00 GMT
if(gmtHour >= 8 && gmtHour < 16)
return SESSION_LONDON;
// New York: 13:00-21:00 GMT
if(gmtHour >= 13 && gmtHour < 21)
return SESSION_NEWYORK;
// Asian: 00:00-08:00 GMT
if(gmtHour >= 0 && gmtHour < 8)
return SESSION_ASIAN;
return SESSION_OFF_HOURS;
}
//+------------------------------------------------------------------+
//| Is Session Allowed |
//+------------------------------------------------------------------+
bool IsSessionAllowed()
{
if(!InpEnableSessionFilter)
return true;
ENUM_TRADING_SESSION currentSession = GetCurrentSession();
switch(currentSession)
{
case SESSION_LONDON_NY_OVERLAP:
return InpAllowLondonSession || InpAllowNYSession;
case SESSION_LONDON:
return InpAllowLondonSession;
case SESSION_NEWYORK:
return InpAllowNYSession;
case SESSION_ASIAN:
return InpAllowAsianSession;
default:
return false;
}
}
//+------------------------------------------------------------------+
//| Get Session Name |
//+------------------------------------------------------------------+
string GetSessionName(ENUM_TRADING_SESSION sess)
{
switch(sess)
{
case SESSION_ASIAN:
return "Asian";
case SESSION_LONDON:
return "London";
case SESSION_NEWYORK:
return "New York";
case SESSION_LONDON_NY_OVERLAP:
return "London/NY Overlap";
default:
return "Off Hours";
}
}
//+------------------------------------------------------------------+
//| Get Session Name (GMT for reference) |
//+------------------------------------------------------------------+
string GetSessionNameGMT(ENUM_TRADING_SESSION sess)
{
switch(sess)
{
case SESSION_ASIAN:
return "Asian (GMT)";
case SESSION_LONDON:
return "London (GMT)";
case SESSION_NEWYORK:
return "New York (GMT)";
case SESSION_LONDON_NY_OVERLAP:
return "London/NY Overlap (GMT)";
default:
return "Off Hours (GMT)";
}
}
//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════ |
//| NEWS FILTER |
//| ═══════════════════════════════════════════════════════════════ |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Load News Events from MT5 Calendar |
//+------------------------------------------------------------------+
void LoadNewsEvents()
{
lastNewsCheck = TimeCurrent();
ArrayResize(upcomingNews, 0);
if(!InpUseBuiltInCalendar)
return;
datetime fromTime = TimeCurrent();
datetime toTime = fromTime + 86400; // Next 24 hours
MqlCalendarValue values[];
int count = CalendarValueHistory(values, fromTime, toTime);
if(count <= 0)
return;
//--- Load events, skip CalendarCountryById to avoid deserialization error
int added = 0;
for(int i = 0; i < count; i++)
{
MqlCalendarEvent event;
if(!CalendarEventById(values[i].event_id, event))
continue;
// Skip country lookup to avoid "countries file deserialization" error
// Filter by event importance only (MODERATE and HIGH impact)
if(event.importance < CALENDAR_IMPORTANCE_MODERATE)
continue;
// Cap at 50 events to prevent memory bloat
if(added >= 50)
break;
int idx = ArraySize(upcomingNews);
ArrayResize(upcomingNews, idx + 1);
upcomingNews[idx].eventTime = values[i].time;
upcomingNews[idx].currency = "USD"; // Default - country lookup skipped
upcomingNews[idx].eventName = event.name;
upcomingNews[idx].importance = (int)event.importance;
added++;
}
VerboseLog("Loaded " + IntegerToString(added) + " upcoming news events");
}
//+------------------------------------------------------------------+
//| Update News Filter Status |
//+------------------------------------------------------------------+
void UpdateNewsFilter()
{
newsFilterActive = false;
if(!InpEnableNewsFilter)
return;
datetime now = TimeCurrent();
//--- Prune expired news events (past filterEnd) to keep array small
for(int i = ArraySize(upcomingNews) - 1; i >= 0; i--)
{
int maxAfterMin = InpNewsMinutesAfter;
if(upcomingNews[i].importance >= (int)CALENDAR_IMPORTANCE_HIGH)
maxAfterMin = InpNewsMinutesAfter * 2;
datetime expiry = upcomingNews[i].eventTime + maxAfterMin * 60;
if(now > expiry)
{
// Remove expired event by shifting array
for(int j = i; j < ArraySize(upcomingNews) - 1; j++)
upcomingNews[j] = upcomingNews[j + 1];
ArrayResize(upcomingNews, ArraySize(upcomingNews) - 1);
}
}
//--- Check remaining events for active filter window
for(int i = 0; i < ArraySize(upcomingNews); i++)
{
datetime eventTime = upcomingNews[i].eventTime;
int importance = upcomingNews[i].importance;
// Only filter for high-impact events (importance >= 3)
int minutesBefore = InpNewsMinutesBefore;
int minutesAfter = InpNewsMinutesAfter;
// Extend buffer for very high impact (NFP, FOMC, CPI)
if(importance >= (int)CALENDAR_IMPORTANCE_HIGH)
{
minutesBefore = InpNewsMinutesBefore * 2;
minutesAfter = InpNewsMinutesAfter * 2;
}
datetime filterStart = eventTime - minutesBefore * 60;
datetime filterEnd = eventTime + minutesAfter * 60;
if(now >= filterStart && now <= filterEnd)
{
newsFilterActive = true;
// THROTTLED: only log once every 5 minutes instead of every tick
VerboseLogThrottled("NEWS FILTER ACTIVE: " + upcomingNews[i].eventName +
" (" + upcomingNews[i].currency + ") at " +
TimeToString(eventTime, TIME_MINUTES),
_lastLogTime_news, logThrottleIntervalSec);
// Close positions before high-impact news if configured
if(InpCloseBeforeNews && importance >= (int)CALENDAR_IMPORTANCE_HIGH &&
now >= filterStart && now < eventTime)
{
CloseAllPositions("High-impact news approaching: " + upcomingNews[i].eventName);
}
return;
}
}
}
//+------------------------------------------------------------------+
//| Is News Filter Active |
//+------------------------------------------------------------------+
bool IsNewsFilterBlocking()
{
return InpEnableNewsFilter && newsFilterActive;
}
//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════ |
//| DYNAMIC SPREAD FILTER |
//| ═══════════════════════════════════════════════════════════════ |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Update Spread History |
//+------------------------------------------------------------------+
void UpdateSpreadHistory()
{
double currentSpread = (double)symInfo.Spread();
spreadHistory[spreadHistoryIndex] = currentSpread;
spreadHistoryIndex = (spreadHistoryIndex + 1) % InpSpreadSampleSize;
// Calculate average
double sum = 0;
int validCount = 0;
for(int i = 0; i < InpSpreadSampleSize; i++)
{
if(spreadHistory[i] > 0)
{
sum += spreadHistory[i];
validCount++;
}
}
if(validCount > 0)
{
averageSpread = sum / validCount;
if(validCount >= InpSpreadSampleSize / 2)
spreadInitialized = true;
}
}
//+------------------------------------------------------------------+
//| Is Spread Acceptable |
//+------------------------------------------------------------------+
bool IsSpreadAcceptable()
{
if(!InpEnableSpreadFilter)
return true;
double currentSpread = (double)symInfo.Spread();
// Absolute spread check
if(currentSpread > InpMaxAbsoluteSpread)
{
VerboseLog("Spread too high (absolute): " + DoubleToString(currentSpread, 1) +
" > " + DoubleToString(InpMaxAbsoluteSpread, 1));
return false;
}
// Relative spread check
if(spreadInitialized && averageSpread > 0)
{
if(currentSpread > averageSpread * InpMaxSpreadMultiplier)
{
VerboseLog("Spread too high (relative): " + DoubleToString(currentSpread, 1) +
" > " + DoubleToString(averageSpread * InpMaxSpreadMultiplier, 1) +
" (avg=" + DoubleToString(averageSpread, 1) + ")");
return false;
}
}
return true;
}
//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════ |
//| DXY CORRELATION |
//| ═══════════════════════════════════════════════════════════════ |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Get DXY Trend Direction |
//| Returns: 1=DXY Bullish, -1=DXY Bearish, 0=Neutral/Unavailable |
//+------------------------------------------------------------------+
int GetDXYTrend()
{
if(!InpEnableDXYCorrelation || !dxyAvailable)
return 0;
double dxyEma5[], dxyEma20[];
ArraySetAsSeries(dxyEma5, true);
ArraySetAsSeries(dxyEma20, true);
if(CopyBuffer(hDXY_EMA5, 0, 0, 3, dxyEma5) < 3)
return 0;
if(CopyBuffer(hDXY_EMA20, 0, 0, 3, dxyEma20) < 3)
return 0;
if(dxyEma5[0] > dxyEma20[0])
return 1; // DXY bullish = Gold bearish
if(dxyEma5[0] < dxyEma20[0])
return -1; // DXY bearish = Gold bullish
return 0;
}
//+------------------------------------------------------------------+
//| Calculate Gold-DXY Correlation |
//+------------------------------------------------------------------+
double CalculateDXYCorrelation()
{
if(!InpEnableDXYCorrelation || !dxyAvailable)
return 0;
double goldClose[], dxyClose[];
ArraySetAsSeries(goldClose, true);
ArraySetAsSeries(dxyClose, true);
int bars = InpDXYCorrelationBars;
if(CopyClose(tradingSymbol, InpMTFPeriod, 0, bars, goldClose) < bars)
return 0;
string dxySymbol = InpDXYSymbol;
if(!dxyAvailable)
return 0;
// Try to get DXY close prices
string dxyNames[] = {"USDX", "DXY", "DX", "USDX.a", "DXY.a", "USDindex"};
bool gotData = false;
if(CopyClose(InpDXYSymbol, InpMTFPeriod, 0, bars, dxyClose) >= bars)
gotData = true;
if(!gotData)
{
for(int i = 0; i < ArraySize(dxyNames); i++)
{
if(CopyClose(dxyNames[i], InpMTFPeriod, 0, bars, dxyClose) >= bars)
{
gotData = true;
break;
}
}
}
if(!gotData)
return 0;
// Calculate Pearson correlation
double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0, sumY2 = 0;
int n = bars;
for(int i = 0; i < n; i++)
{
sumX += goldClose[i];
sumY += dxyClose[i];
sumXY += goldClose[i] * dxyClose[i];
sumX2 += goldClose[i] * goldClose[i];
sumY2 += dxyClose[i] * dxyClose[i];
}
double denominator = MathSqrt((n * sumX2 - sumX * sumX) * (n * sumY2 - sumY * sumY));
if(denominator == 0)
return 0;
return (n * sumXY - sumX * sumY) / denominator;
}
//+------------------------------------------------------------------+
//| Does DXY Confirm Gold Direction |
//+------------------------------------------------------------------+
bool DoesDXYConfirmDirection(bool isBuyGold)
{
if(!InpEnableDXYCorrelation || !dxyAvailable)
return true; // No filter = always confirm
int dxyTrend = GetDXYTrend();
// Gold and DXY are inversely correlated
// Buy Gold = expect DXY to fall (dxyTrend = -1)
// Sell Gold = expect DXY to rise (dxyTrend = 1)
if(isBuyGold && dxyTrend == -1)
return true; // DXY falling, good for Gold buy
if(!isBuyGold && dxyTrend == 1)
return true; // DXY rising, good for Gold sell
if(dxyTrend == 0)
return true; // Neutral, allow
VerboseLog("DXY conflict: Gold " + (isBuyGold ? "BUY" : "SELL") +
" but DXY trend=" + IntegerToString(dxyTrend));
return false;
}
//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════ |
//| BREAKOUT DETECTION |
//| ═══════════════════════════════════════════════════════════════ |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Detect Breakout |
//+------------------------------------------------------------------+
void DetectBreakout()
{
breakout.breakoutDetected = false;
breakout.isConsolidating = false;
double high[], low[], close[];
ArraySetAsSeries(high, true);
ArraySetAsSeries(low, true);
ArraySetAsSeries(close, true);
int bars = InpConsolidationBars;
if(CopyHigh(tradingSymbol, InpMTFPeriod, 0, bars + 5, high) < bars + 5)
return;
if(CopyLow(tradingSymbol, InpMTFPeriod, 0, bars + 5, low) < bars + 5)
return;
if(CopyClose(tradingSymbol, InpMTFPeriod, 0, bars + 5, close) < bars + 5)
return;
// Get ATR for reference
double atrVal[];
ArraySetAsSeries(atrVal, true);
if(CopyBuffer(hATR_MTF, 0, 0, 3, atrVal) < 3)
return;
double atr = atrVal[0];
// Calculate consolidation range (bars 1 to bars, excluding current)
double rangeHigh = high[1];
double rangeLow = low[1];
for(int i = 2; i <= bars; i++)
{
if(high[i] > rangeHigh)
rangeHigh = high[i];
if(low[i] < rangeLow)
rangeLow = low[i];
}
double range = rangeHigh - rangeLow;
breakout.rangeHigh = rangeHigh;
breakout.rangeLow = rangeLow;
breakout.rangeATR = atr;
// Check if market is consolidating (range < ATR * multiplier * bars)
double expectedRange = atr * InpConsolidationATRMult * MathSqrt((double)bars);
breakout.isConsolidating = (range < expectedRange);
// Check for breakout on current bar
double currentClose = close[0];
double breakoutThreshold = atr * InpBreakoutATRMult;
if(currentClose > rangeHigh + breakoutThreshold)
{
breakout.breakoutDetected = true;
breakout.breakoutBullish = true;
breakout.breakoutStrength = (currentClose - rangeHigh) / atr;
// Volume confirmation
if(InpBreakoutVolumeMult > 0)
{
long tickVol[];
ArraySetAsSeries(tickVol, true);
if(CopyTickVolume(tradingSymbol, InpMTFPeriod, 0, bars, tickVol) >= bars)
{
long avgVol = 0;
for(int i = 1; i < bars; i++)
avgVol += tickVol[i];
avgVol /= (bars - 1);
if(avgVol > 0 && tickVol[0] < avgVol * InpBreakoutVolumeMult / 100)
{
breakout.breakoutDetected = false; // Volume not confirming
VerboseLog("Breakout rejected: insufficient volume");
}
}
}
if(breakout.breakoutDetected)
VerboseLog("BULLISH BREAKOUT detected! Strength: " + DoubleToString(breakout.breakoutStrength, 2));
}
else
if(currentClose < rangeLow - breakoutThreshold)
{
breakout.breakoutDetected = true;
breakout.breakoutBullish = false;
breakout.breakoutStrength = (rangeLow - currentClose) / atr;
if(InpBreakoutVolumeMult > 0)
{
long tickVol[];
ArraySetAsSeries(tickVol, true);
if(CopyTickVolume(tradingSymbol, InpMTFPeriod, 0, bars, tickVol) >= bars)
{
long avgVol = 0;
for(int i = 1; i < bars; i++)
avgVol += tickVol[i];
avgVol /= (bars - 1);
if(avgVol > 0 && tickVol[0] < avgVol * InpBreakoutVolumeMult / 100)
{
breakout.breakoutDetected = false;
VerboseLog("Breakout rejected: insufficient volume");
}
}
}
if(breakout.breakoutDetected)
VerboseLog("BEARISH BREAKOUT detected! Strength: " + DoubleToString(breakout.breakoutStrength, 2));
}
}
//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════ |
//| VOLUME PROFILE / ORDER FLOW |
//| ═══════════════════════════════════════════════════════════════ |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Update Volume Profile |
//+------------------------------------------------------------------+
void UpdateVolumeProfile()
{
double high[], low[], close[];
long tickVol[];
ArraySetAsSeries(high, true);
ArraySetAsSeries(low, true);
ArraySetAsSeries(close, true);
ArraySetAsSeries(tickVol, true);
int bars = InpVPLookbackBars;
if(CopyHigh(tradingSymbol, InpMTFPeriod, 0, bars, high) < bars)
return;
if(CopyLow(tradingSymbol, InpMTFPeriod, 0, bars, low) < bars)
return;
if(CopyClose(tradingSymbol, InpMTFPeriod, 0, bars, close) < bars)
return;
if(CopyTickVolume(tradingSymbol, InpMTFPeriod, 0, bars, tickVol) < bars)
return;
// Find price range
double priceHigh = high[0];
double priceLow = low[0];
for(int i = 1; i < bars; i++)
{
if(high[i] > priceHigh)
priceHigh = high[i];
if(low[i] < priceLow)
priceLow = low[i];
}
double priceRange = priceHigh - priceLow;
if(priceRange <= 0)
return;
double levelSize = priceRange / InpVPLevels;
// Build volume profile
ArrayResize(volumeLevels, InpVPLevels);
double totalVolume = 0;
for(int lvl = 0; lvl < InpVPLevels; lvl++)
{
volumeLevels[lvl].price = priceLow + (lvl + 0.5) * levelSize;
volumeLevels[lvl].volume = 0;
volumeLevels[lvl].isHVN = false;
volumeLevels[lvl].isLVN = false;
double levelLow = priceLow + lvl * levelSize;
double levelHigh = levelLow + levelSize;
for(int bar = 0; bar < bars; bar++)
{
// Check if bar overlaps with this level
if(high[bar] >= levelLow && low[bar] <= levelHigh)
{
// Proportional volume allocation
double overlap = MathMin(high[bar], levelHigh) - MathMax(low[bar], levelLow);
double barRange = high[bar] - low[bar];
double proportion = (barRange > 0) ? overlap / barRange : 1.0;
volumeLevels[lvl].volume += (double)tickVol[bar] * proportion;
}
}
totalVolume += volumeLevels[lvl].volume;
}
// Calculate average volume per level
double avgVolume = (InpVPLevels > 0) ? totalVolume / InpVPLevels : 0;
// Identify HVN, LVN, and POC
double maxVolume = 0;
pocPrice = 0;
for(int lvl = 0; lvl < InpVPLevels; lvl++)
{
if(volumeLevels[lvl].volume > maxVolume)
{
maxVolume = volumeLevels[lvl].volume;
pocPrice = volumeLevels[lvl].price;
}
volumeLevels[lvl].isHVN = (volumeLevels[lvl].volume > avgVolume * InpVPHVNThreshold);
volumeLevels[lvl].isLVN = (volumeLevels[lvl].volume < avgVolume * 0.5);
}
// Calculate Value Area (70% of volume)
double vaVolume = totalVolume * 0.70;
double accumulatedVolume = 0;
// Find POC index
int pocIndex = 0;
for(int i = 0; i < InpVPLevels; i++)
{
if(MathAbs(volumeLevels[i].price - pocPrice) < levelSize)
{
pocIndex = i;
break;
}
}
accumulatedVolume = volumeLevels[pocIndex].volume;
int upperIdx = pocIndex;
int lowerIdx = pocIndex;
while(accumulatedVolume < vaVolume && (upperIdx < InpVPLevels - 1 || lowerIdx > 0))
{
double upperVol = (upperIdx < InpVPLevels - 1) ? volumeLevels[upperIdx + 1].volume : 0;
double lowerVol = (lowerIdx > 0) ? volumeLevels[lowerIdx - 1].volume : 0;
if(upperVol >= lowerVol && upperIdx < InpVPLevels - 1)
{
upperIdx++;
accumulatedVolume += volumeLevels[upperIdx].volume;
}
else
if(lowerIdx > 0)
{
lowerIdx--;
accumulatedVolume += volumeLevels[lowerIdx].volume;
}
else
break;
}
vahPrice = volumeLevels[upperIdx].price;
valPrice = volumeLevels[lowerIdx].price;
}
//+------------------------------------------------------------------+
//| Get Volume Profile Signal |
//| Returns: score from -1.0 (bearish) to +1.0 (bullish) |
//+------------------------------------------------------------------+
double GetVolumeProfileSignal()
{
if(!InpEnableVolumeProfile || pocPrice == 0)
return 0;
double currentPrice = symInfo.Last();
if(currentPrice == 0)
currentPrice = (symInfo.Ask() + symInfo.Bid()) / 2.0;
double signal = 0;
// Price relative to Value Area
if(currentPrice > vahPrice)
{
signal += 0.3; // Above value area = bullish momentum
}
else
if(currentPrice < valPrice)
{
signal -= 0.3; // Below value area = bearish momentum
}
else
{
// Inside value area - mean reversion zone
double vaCenter = (vahPrice + valPrice) / 2.0;
if(currentPrice > vaCenter)
signal -= 0.1; // Above center, slight bearish bias (mean reversion)
else
signal += 0.1; // Below center, slight bullish bias
}
// Price relative to POC
double distFromPOC = (currentPrice - pocPrice) / (symInfo.Point() * PointsPerPip());
if(MathAbs(distFromPOC) < 5) // Near POC = strong support/resistance
{
signal *= 0.5; // Reduce signal near POC (congestion zone)
}
// Delta volume analysis
if(InpUseDeltaVolume)
{
double deltaSignal = CalculateDeltaVolume();
signal += deltaSignal * 0.4;
}
return MathMax(-1.0, MathMin(1.0, signal));
}
//+------------------------------------------------------------------+
//| Calculate Delta Volume (Buy vs Sell pressure) |
//+------------------------------------------------------------------+
double CalculateDeltaVolume()
{
double open[], close[];
long tickVol[];
ArraySetAsSeries(open, true);
ArraySetAsSeries(close, true);
ArraySetAsSeries(tickVol, true);
int bars = 10;
if(CopyOpen(tradingSymbol, InpLTFPeriod, 0, bars, open) < bars)
return 0;
if(CopyClose(tradingSymbol, InpLTFPeriod, 0, bars, close) < bars)
return 0;
if(CopyTickVolume(tradingSymbol, InpLTFPeriod, 0, bars, tickVol) < bars)
return 0;
double buyVolume = 0;
double sellVolume = 0;
for(int i = 0; i < bars; i++)
{
if(close[i] > open[i])
buyVolume += (double)tickVol[i];
else
if(close[i] < open[i])
sellVolume += (double)tickVol[i];
else
{
buyVolume += (double)tickVol[i] * 0.5;
sellVolume += (double)tickVol[i] * 0.5;
}
}
double totalVol = buyVolume + sellVolume;
if(totalVol == 0)
return 0;
return (buyVolume - sellVolume) / totalVol; // -1 to +1
}
//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════ |
//| ML PATTERN RECOGNITION |
//| ═══════════════════════════════════════════════════════════════ |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Get Current Pattern Features |
//+------------------------------------------------------------------+
bool GetCurrentPattern(double &features[])
{
double close[];
ArraySetAsSeries(close, true);
int needed = InpMLPatternLength + 1;
if(CopyClose(tradingSymbol, InpLTFPeriod, 0, needed, close) < needed)
return false;
ArrayResize(features, InpMLPatternLength);
// Normalize: use percentage changes
for(int i = 0; i < InpMLPatternLength; i++)
{
if(close[i + 1] != 0)
features[i] = (close[i] - close[i + 1]) / close[i + 1] * 100.0;
else
features[i] = 0;
}
return true;
}
//+------------------------------------------------------------------+
//| Record ML Pattern After Trade Close |
//+------------------------------------------------------------------+
void RecordMLPattern(bool wasWin, double profit, double volume)
{
double features[];
if(!GetCurrentPattern(features))
return;
int idx = ArraySize(patternDB);
ArrayResize(patternDB, idx + 1);
ArrayResize(patternDB[idx].features, InpMLPatternLength);
ArrayCopy(patternDB[idx].features, features);
patternDB[idx].wasWin = wasWin;
double pipValue = GetPipValue();
patternDB[idx].profitPips = (pipValue > 0 && volume > 0) ? profit / (volume * pipValue) : 0;
patternDBSize = idx + 1;
}
//+------------------------------------------------------------------+
//| Calculate Pattern Similarity (Cosine Similarity) |
//+------------------------------------------------------------------+
double PatternSimilarity(const double &pattern1[], const double &pattern2[])
{
int len = MathMin(ArraySize(pattern1), ArraySize(pattern2));
if(len == 0)
return 0;
double dotProduct = 0;
double norm1 = 0;
double norm2 = 0;
for(int i = 0; i < len; i++)
{
dotProduct += pattern1[i] * pattern2[i];
norm1 += pattern1[i] * pattern1[i];
norm2 += pattern2[i] * pattern2[i];
}
double denominator = MathSqrt(norm1) * MathSqrt(norm2);
if(denominator == 0)
return 0;
return dotProduct / denominator;
}
//+------------------------------------------------------------------+
//| Get ML Prediction |
//| Returns: score from -1.0 (strong sell) to +1.0 (strong buy) |
//+------------------------------------------------------------------+
double GetMLPrediction()
{
if(!InpEnableML || patternDBSize < InpMLMinSamples)
return 0;
double currentPattern[];
if(!GetCurrentPattern(currentPattern))
return 0;
int matchCount = 0;
int winCount = 0;
double totalProfitPips = 0;
for(int i = 0; i < patternDBSize; i++)
{
double similarity = PatternSimilarity(currentPattern, patternDB[i].features);
if(similarity >= InpMLMinSimilarity)
{
matchCount++;
if(patternDB[i].wasWin)
winCount++;
totalProfitPips += patternDB[i].profitPips;
}
}
if(matchCount < InpMLMinSamples)
return 0;
double patternWinRate = (double)winCount / matchCount;
double avgProfitPips = totalProfitPips / matchCount;
VerboseLogThrottled("ML: " + IntegerToString(matchCount) + " similar patterns found, " +
"WinRate=" + DoubleToString(patternWinRate * 100, 1) + "%, " +
"AvgProfit=" + DoubleToString(avgProfitPips, 1) + " pips", _lastLogTime_signal, 300);
if(patternWinRate < InpMLMinWinRate)
{
VerboseLog("ML: Pattern win rate below threshold - signal rejected");
return 0;
}
// Return directional signal based on average profit
if(avgProfitPips > 0)
return MathMin(patternWinRate, 1.0);
else
return MathMax(-patternWinRate, -1.0);
}
//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════ |
//| MONTE CARLO SIMULATION |
//| ═══════════════════════════════════════════════════════════════ |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Run Monte Carlo Validation |
//+------------------------------------------------------------------+
void RunMonteCarloValidation()
{
if(!InpEnableMonteCarlo || !learningInitialized || learning.totalTrades < 30)
{
mcResult.isViable = true; // Default to viable if not enough data
return;
}
VerboseLog("Running Monte Carlo simulation (" + IntegerToString(InpMCSimulations) + " runs)...");
double drawdowns[];
double returns[];
ArrayResize(drawdowns, InpMCSimulations);
ArrayResize(returns, InpMCSimulations);
double winRate = learning.winRatio;
double avgWin = learning.avgWinPips;
double avgLoss = learning.avgLossPips;
// Simulate
for(int sim = 0; sim < InpMCSimulations; sim++)
{
double equity = 10000.0; // Normalized starting equity
double peakEq = equity;
double maxDD = 0;
// Simulate 100 trades per run
for(int t = 0; t < 100; t++)
{
// Random win/loss based on historical win rate
double rand = (double)MathRand() / 32767.0;
double tradeResult = 0;
if(rand < winRate)
{
// Win - add some randomness to win size
double winVariation = 0.7 + (double)MathRand() / 32767.0 * 0.6; // 0.7 to 1.3
tradeResult = avgWin * winVariation;
}
else
{
// Loss
double lossVariation = 0.7 + (double)MathRand() / 32767.0 * 0.6;
tradeResult = -avgLoss * lossVariation;
}
// Apply to equity (assuming 1% risk per trade)
double pipValue = equity * 0.01 / avgLoss; // Normalize
equity += tradeResult * pipValue;
if(equity > peakEq)
peakEq = equity;
double dd = (peakEq - equity) / peakEq * 100;
if(dd > maxDD)
maxDD = dd;
}
drawdowns[sim] = maxDD;
returns[sim] = (equity - 10000.0) / 10000.0 * 100;
}
// Sort drawdowns for percentile calculation
ArraySort(drawdowns);
ArraySort(returns);
// Calculate results
double sumReturns = 0;
for(int i = 0; i < InpMCSimulations; i++)
sumReturns += returns[i];
mcResult.avgReturn = sumReturns / InpMCSimulations;
// Max drawdown at confidence level
int confidenceIndex = (int)(InpMCSimulations * InpMCConfidenceLevel);
if(confidenceIndex >= InpMCSimulations)
confidenceIndex = InpMCSimulations - 1;
mcResult.drawdownAtConfidence = drawdowns[confidenceIndex];
mcResult.maxDrawdown = drawdowns[InpMCSimulations - 1];
// Win rate range (5th to 95th percentile of returns)
int lowIdx = (int)(InpMCSimulations * 0.05);
int highIdx = (int)(InpMCSimulations * 0.95);
mcResult.winRateRange = returns[highIdx] - returns[lowIdx];
// Is strategy viable?
mcResult.isViable = (mcResult.drawdownAtConfidence <= InpMCMaxDrawdownPct &&
mcResult.avgReturn > 0);
VerboseLog("Monte Carlo Results: AvgReturn=" + DoubleToString(mcResult.avgReturn, 2) + "%" +
" DD@" + DoubleToString(InpMCConfidenceLevel * 100, 0) + "%=" +
DoubleToString(mcResult.drawdownAtConfidence, 2) + "%" +
" MaxDD=" + DoubleToString(mcResult.maxDrawdown, 2) + "%" +
" Viable=" + (mcResult.isViable ? "YES" : "NO"));
if(!mcResult.isViable)
{
session.currentAction = SESSION_CONSERVATIVE;
VerboseLog("MC: Strategy not viable at confidence level - switching to CONSERVATIVE");
}
}
//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════ |
//| COMPOSITE SIGNAL SCORING |
//| ═══════════════════════════════════════════════════════════════ |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Calculate Composite Signal Score |
//+------------------------------------------------------------------+
void CalculateSignalScore()
{
signalScore = 0;
double totalWeight = 0;
//--- 1. EMA Crossover Signal (weight: 1.0)
double emaSignal = GetEMASignal();
signalScore += emaSignal * 1.0;
totalWeight += 1.0;
//--- 2. Market Regime Signal (weight: 1.5)
double regimeSignal = GetRegimeSignal();
signalScore += regimeSignal * 1.5;
totalWeight += 1.5;
//--- 3. RSI Signal (weight: 0.8)
double rsiSignal = GetRSISignal();
signalScore += rsiSignal * 0.8;
totalWeight += 0.8;
//--- 4. DXY Correlation Signal (weight: 1.2)
if(InpEnableDXYCorrelation && dxyAvailable)
{
double dxySignal = GetDXYSignal();
signalScore += dxySignal * 1.2;
totalWeight += 1.2;
}
//--- 5. Breakout Signal (weight: 2.0 - high weight for breakouts)
if(InpEnableBreakout && breakout.breakoutDetected)
{
double breakoutSignal = breakout.breakoutBullish ? breakout.breakoutStrength : -breakout.breakoutStrength;
breakoutSignal = MathMax(-1.0, MathMin(1.0, breakoutSignal));
signalScore += breakoutSignal * 2.0;
totalWeight += 2.0;
}
//--- 6. Volume Profile Signal (weight: 1.0)
if(InpEnableVolumeProfile)
{
double vpSignal = GetVolumeProfileSignal();
signalScore += vpSignal * 1.0;
totalWeight += 1.0;
}
//--- 7. ML Pattern Signal (weight: 1.5)
if(InpEnableML)
{
double mlSignal = GetMLPrediction();
if(mlSignal != 0)
{
signalScore += mlSignal * 1.5;
totalWeight += 1.5;
}
}
//--- 8. Session Quality Signal (weight: 0.5)
double sessionSignal = GetSessionQualitySignal();
signalScore += sessionSignal * 0.5;
totalWeight += 0.5;
//--- Normalize
if(totalWeight > 0)
signalScore /= totalWeight;
//--- Determine strength (lowered thresholds for realistic trading)
double absScore = MathAbs(signalScore);
if(absScore >= 0.55)
signalStrength = SIGNAL_VERY_STRONG;
else
if(absScore >= 0.4)
signalStrength = SIGNAL_STRONG;
else
if(absScore >= 0.2)
signalStrength = SIGNAL_MODERATE;
else
if(absScore >= 0.1)
signalStrength = SIGNAL_WEAK;
else
signalStrength = SIGNAL_NONE;
VerboseLogThrottled("Signal Score: " + DoubleToString(signalScore, 3) +
" (" + SignalStrengthToString(signalStrength) + ")" +
" Components: EMA=" + DoubleToString(emaSignal, 2) +
" Regime=" + DoubleToString(regimeSignal, 2) +
" RSI=" + DoubleToString(rsiSignal, 2),
_lastLogTime_signal, logThrottleIntervalSec);
}
//+------------------------------------------------------------------+
//| Get EMA Signal |
//+------------------------------------------------------------------+
double GetEMASignal()
{
double ema5[], ema20[];
ArraySetAsSeries(ema5, true);
ArraySetAsSeries(ema20, true);
if(CopyBuffer(hEMA5, 0, 0, 3, ema5) < 3)
return 0;
if(CopyBuffer(hEMA20, 0, 0, 3, ema20) < 3)
return 0;
double diff = (ema5[0] - ema20[0]) / (symInfo.Point() * PointsPerPip());
return MathMax(-1.0, MathMin(1.0, diff / 50.0)); // Normalize
}
//+------------------------------------------------------------------+
//| Get Regime Signal |
//+------------------------------------------------------------------+
double GetRegimeSignal()
{
switch(currentRegime)
{
case REGIME_TRENDING_UP:
return 0.8;
case REGIME_TRENDING_DOWN:
return -0.8;
case REGIME_RANGING:
return 0.3; // Allow ranging with mean-reversion bias
case REGIME_HIGH_VOLATILITY:
return 0.1; // Small signal even in high vol
default:
return 0.0;
}
}
//+------------------------------------------------------------------+
//| Get RSI Signal |
//+------------------------------------------------------------------+
double GetRSISignal()
{
if(hRSI_MTF == INVALID_HANDLE)
return 0;
double rsi[];
ArraySetAsSeries(rsi, true);
if(CopyBuffer(hRSI_MTF, 0, 0, 3, rsi) < 3)
return 0;
// Continuous RSI signal instead of dead zones
// RSI 50 = neutral, above 50 = bearish bias, below 50 = bullish bias
double rsiVal = rsi[0];
if(rsiVal > 80)
return -1.0; // Extreme overbought
if(rsiVal < 20)
return 1.0; // Extreme oversold
if(rsiVal > 70)
return -0.8; // Overbought
if(rsiVal < 30)
return 0.8; // Oversold
if(rsiVal > 60)
return -0.4;
if(rsiVal < 40)
return 0.4;
// 40-60 range: small directional bias based on RSI momentum
if(rsiVal > 55)
return -0.15;
if(rsiVal < 45)
return 0.15;
return 0; // Only truly neutral at 45-55
}
//+------------------------------------------------------------------+
//| Get DXY Signal |
//+------------------------------------------------------------------+
double GetDXYSignal()
{
int dxyTrend = GetDXYTrend();
// Inverse correlation: DXY up = Gold down
return -dxyTrend * 0.7;
}
//+------------------------------------------------------------------+
//| Get Session Quality Signal |
//+------------------------------------------------------------------+
double GetSessionQualitySignal()
{
ENUM_TRADING_SESSION sess = GetCurrentSession();
switch(sess)
{
case SESSION_LONDON_NY_OVERLAP:
return 0.8; // Best session for Gold
case SESSION_LONDON:
return 0.5;
case SESSION_NEWYORK:
return 0.4;
case SESSION_ASIAN:
return -0.2; // Low liquidity
default:
return -0.5;
}
}
//+------------------------------------------------------------------+
//| Signal Strength to String |
//+------------------------------------------------------------------+
string SignalStrengthToString(ENUM_SIGNAL_STRENGTH strength)
{
switch(strength)
{
case SIGNAL_VERY_STRONG:
return "VERY STRONG";
case SIGNAL_STRONG:
return "STRONG";
case SIGNAL_MODERATE:
return "MODERATE";
case SIGNAL_WEAK:
return "WEAK";
default:
return "NONE";
}
}
//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════ |
//| CORE TRADING LOGIC |
//| ═══════════════════════════════════════════════════════════════ |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Initialize Learning |
//+------------------------------------------------------------------+
void InitializeLearning()
{
ZeroMemory(learning);
ArrayInitialize(learning.hourlyWinRate, 0);
ArrayInitialize(learning.hourlyTradeCount, 0);
ArrayInitialize(learning.regimeWinRate, 0);
ArrayInitialize(learning.regimeTradeCount, 0);
if(!HistorySelect(TimeCurrent() - 90 * 86400, TimeCurrent()))
{
learningInitialized = true;
return;
}
int totalDeals = HistoryDealsTotal();
int analyzed = 0;
for(int i = totalDeals - 1; i >= 0 && analyzed < InpHistoryAnalysisCount; i--)
{
ulong dealTicket = HistoryDealGetTicket(i);
if(dealTicket == 0)
continue;
long dealMagic = HistoryDealGetInteger(dealTicket, DEAL_MAGIC);
string dealSymbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL);
ENUM_DEAL_ENTRY dealEntry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
if(dealMagic != (long)InpMagicNumber || dealSymbol != tradingSymbol || dealEntry != DEAL_ENTRY_OUT)
continue;
double dealProfit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT) +
HistoryDealGetDouble(dealTicket, DEAL_SWAP) +
HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
double dealVolume = HistoryDealGetDouble(dealTicket, DEAL_VOLUME);
datetime dealTime = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME);
if(dealVolume <= 0)
continue;
double pipValue = GetPipValue();
double profitPips = (pipValue > 0) ? dealProfit / (dealVolume * pipValue) : 0;
MqlDateTime dt;
TimeToStruct(dealTime, dt);
int hour = dt.hour;
learning.totalTrades++;
analyzed++;
if(dealProfit > 0)
{
learning.winCount++;
learning.totalWinPips += MathAbs(profitPips);
learning.hourlyTradeCount[hour]++;
learning.hourlyWinRate[hour] += 1.0;
}
else
{
learning.lossCount++;
learning.totalLossPips += MathAbs(profitPips);
learning.hourlyTradeCount[hour]++;
}
// Record for ML
if(InpEnableML)
RecordMLPattern(dealProfit > 0, dealProfit, dealVolume);
}
// Calculate metrics
RecalculateLearningMetrics();
// Hourly win rates
double bestRate = 0;
double worstRate = 1;
for(int h = 0; h < 24; h++)
{
if(learning.hourlyTradeCount[h] > 0)
{
learning.hourlyWinRate[h] /= learning.hourlyTradeCount[h];
if(learning.hourlyWinRate[h] > bestRate && learning.hourlyTradeCount[h] >= 5)
{
bestRate = learning.hourlyWinRate[h];
learning.bestHour = h;
}
if(learning.hourlyWinRate[h] < worstRate && learning.hourlyTradeCount[h] >= 5)
{
worstRate = learning.hourlyWinRate[h];
learning.worstHour = h;
}
}
}
// Adaptive SL/TP
if(InpAdaptiveSLTP && learning.totalTrades >= 20)
{
learning.bestSLPips = MathMax(learning.avgLossPips * 1.2, InpStopLossPips * 0.5);
learning.bestSLPips = MathMin(learning.bestSLPips, (double)InpStopLossPips); // Clamp SL to max allowed
learning.bestTPPips = MathMax(learning.avgWinPips * 0.9, InpTakeProfitPips * 0.5);
if(learning.bestTPPips < learning.bestSLPips * 1.1)
learning.bestTPPips = learning.bestSLPips * 1.2;
}
else
{
learning.bestSLPips = InpStopLossPips;
learning.bestTPPips = InpTakeProfitPips;
}
// Adaptive risk (Half-Kelly)
if(InpAdaptiveLotSize && learning.totalTrades >= 20)
{
double kellyFraction = 0;
if(learning.avgLossPips > 0)
kellyFraction = (learning.winRatio - (1.0 - learning.winRatio) / (learning.avgWinPips / learning.avgLossPips));
kellyFraction = MathMax(0, MathMin(kellyFraction * 0.5, 0.02));
learning.optimalRiskPercent = MathMax(kellyFraction * 100, 0.25);
}
else
{
learning.optimalRiskPercent = InpRiskPerTrade;
}
learningInitialized = true;
VerboseLog("Learning: Trades=" + IntegerToString(learning.totalTrades) +
" WR=" + DoubleToString(learning.winRatio * 100, 1) + "%" +
" PF=" + DoubleToString(learning.profitFactor, 2) +
" ML Patterns=" + IntegerToString(patternDBSize));
}
//+------------------------------------------------------------------+
//| Update Learning Win |
//+------------------------------------------------------------------+
void UpdateLearningWin(double profit, double volume, ulong dealTicket)
{
if(!InpEnableLearning || !learningInitialized)
return;
double pipValue = GetPipValue();
double profitPips = (pipValue > 0 && volume > 0) ? profit / (volume * pipValue) : 0;
learning.winCount++;
learning.totalWinPips += MathAbs(profitPips);
learning.totalTrades++;
if(learning.currentStreak >= 0)
learning.currentStreak++;
else
learning.currentStreak = 1;
learning.consecutiveWins = learning.currentStreak;
if(learning.consecutiveWins > learning.maxConsecutiveWins)
learning.maxConsecutiveWins = learning.consecutiveWins;
RecalculateLearningMetrics();
// After consecutive wins, consider being more aggressive
if(learning.consecutiveWins >= 3 && session.currentAction == SESSION_CONSERVATIVE)
{
session.currentAction = SESSION_NORMAL;
TradeEventLogCritical("LEARNING: 3+ consecutive wins - returning to NORMAL mode");
}
}
//+------------------------------------------------------------------+
//| Update Learning Loss |
//+------------------------------------------------------------------+
void UpdateLearningLoss(double profit, double volume, ulong dealTicket)
{
if(!InpEnableLearning || !learningInitialized)
return;
double pipValue = GetPipValue();
double profitPips = (pipValue > 0 && volume > 0) ? MathAbs(profit) / (volume * pipValue) : 0;
learning.lossCount++;
learning.totalLossPips += profitPips;
learning.totalTrades++;
if(learning.currentStreak <= 0)
learning.currentStreak--;
else
learning.currentStreak = -1;
learning.consecutiveLosses = MathAbs(learning.currentStreak);
if(learning.consecutiveLosses > learning.maxConsecutiveLosses)
learning.maxConsecutiveLosses = learning.consecutiveLosses;
RecalculateLearningMetrics();
if(learning.consecutiveLosses >= 3)
{
session.currentAction = SESSION_CONSERVATIVE;
TradeEventLogCritical("LEARNING: 3+ consecutive losses - CONSERVATIVE mode");
}
if(learning.consecutiveLosses >= 5)
{
session.currentAction = SESSION_PAUSED;
TradeEventLogCritical("LEARNING: 5+ consecutive losses - PAUSED");
}
}
//+------------------------------------------------------------------+
//| Recalculate Learning Metrics |
//+------------------------------------------------------------------+
void RecalculateLearningMetrics()
{
if(learning.winCount + learning.lossCount > 0)
learning.winRatio = (double)learning.winCount / (learning.winCount + learning.lossCount);
if(learning.winCount > 0)
learning.avgWinPips = learning.totalWinPips / learning.winCount;
if(learning.lossCount > 0)
learning.avgLossPips = learning.totalLossPips / learning.lossCount;
if(learning.totalLossPips > 0)
learning.profitFactor = learning.totalWinPips / learning.totalLossPips;
if(learning.totalTrades > 0)
learning.expectancy = (learning.winRatio * learning.avgWinPips) - ((1.0 - learning.winRatio) * learning.avgLossPips);
}
//+------------------------------------------------------------------+
//| Detect Market Regime |
//+------------------------------------------------------------------+
void DetectMarketRegime()
{
double adxMain[], adxPlus[], adxMinus[], atrVal[];
ArraySetAsSeries(adxMain, true);
ArraySetAsSeries(adxPlus, true);
ArraySetAsSeries(adxMinus, true);
ArraySetAsSeries(atrVal, true);
if(CopyBuffer(hADX_MTF, 0, 0, 3, adxMain) < 3)
return;
if(CopyBuffer(hADX_MTF, 1, 0, 3, adxPlus) < 3)
return;
if(CopyBuffer(hADX_MTF, 2, 0, 3, adxMinus) < 3)
return;
if(CopyBuffer(hATR_MTF, 0, 0, 3, atrVal) < 3)
return;
currentVolatilityPts = atrVal[0] / symInfo.Point();
if(adxMain[0] > InpTrendThreshold)
currentRegime = (adxPlus[0] > adxMinus[0]) ? REGIME_TRENDING_UP : REGIME_TRENDING_DOWN;
else
currentRegime = REGIME_RANGING;
if(currentVolatilityPts > InpMaxVolatilityPts)
currentRegime = REGIME_HIGH_VOLATILITY;
}
//+------------------------------------------------------------------+
//| Is Regime Allowed |
//+------------------------------------------------------------------+
bool IsRegimeAllowed(ENUM_MARKET_REGIME regime)
{
if(!InpFilterByRegime)
return true;
switch(regime)
{
case REGIME_RANGING:
return InpAllowRanging;
case REGIME_TRENDING_UP:
return InpAllowTrendUp;
case REGIME_TRENDING_DOWN:
return InpAllowTrendDown;
case REGIME_HIGH_VOLATILITY:
return InpAllowHighVol;
default:
return false;
}
}
//+------------------------------------------------------------------+
//| Get Regime String |
//+------------------------------------------------------------------+
string GetRegimeString(ENUM_MARKET_REGIME regime)
{
switch(regime)
{
case REGIME_RANGING:
return "Ranging";
case REGIME_TRENDING_UP:
return "Trending Up";
case REGIME_TRENDING_DOWN:
return "Trending Down";
case REGIME_HIGH_VOLATILITY:
return "High Volatility";
default:
return "Unknown";
}
}
//+------------------------------------------------------------------+
//| Check Positions Status |
//+------------------------------------------------------------------+
void CheckPositionsStatus()
{
for(int i = 0; i < ArraySize(tradePairs); i++)
{
if(tradePairs[i].buyTicket > 0 && !tradePairs[i].buyClosed)
{
if(!PositionSelectByTicket(tradePairs[i].buyTicket))
{
tradePairs[i].buyClosed = true;
double closedProfit = GetClosedPositionProfit(tradePairs[i].buyTicket);
if(closedProfit < 0)
HandleStopLossHit(tradePairs[i].buyTicket, true, tradePairs[i].lotSize);
if(tradePairs[i].sellTicket > 0 && !tradePairs[i].sellClosed)
AddOrphanedLeg(tradePairs[i].sellTicket, false, tradePairs[i].lotSize, i);
}
}
if(tradePairs[i].sellTicket > 0 && !tradePairs[i].sellClosed)
{
if(!PositionSelectByTicket(tradePairs[i].sellTicket))
{
tradePairs[i].sellClosed = true;
double closedProfit = GetClosedPositionProfit(tradePairs[i].sellTicket);
if(closedProfit < 0)
HandleStopLossHit(tradePairs[i].sellTicket, false, tradePairs[i].lotSize);
if(tradePairs[i].buyTicket > 0 && !tradePairs[i].buyClosed)
AddOrphanedLeg(tradePairs[i].buyTicket, true, tradePairs[i].lotSize, i);
}
}
}
}
//+------------------------------------------------------------------+
//| Get Closed Position Profit |
//+------------------------------------------------------------------+
double GetClosedPositionProfit(ulong posTicket)
{
double profit = 0;
if(HistorySelectByPosition(posTicket))
{
int totalDeals = HistoryDealsTotal();
for(int i = 0; i < totalDeals; i++)
{
ulong dealTicket = HistoryDealGetTicket(i);
if(dealTicket > 0)
{
ENUM_DEAL_ENTRY entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY);
if(entry == DEAL_ENTRY_OUT || entry == DEAL_ENTRY_INOUT)
profit += HistoryDealGetDouble(dealTicket, DEAL_PROFIT) +
HistoryDealGetDouble(dealTicket, DEAL_SWAP) +
HistoryDealGetDouble(dealTicket, DEAL_COMMISSION);
}
}
}
return profit;
}
//+------------------------------------------------------------------+
//| ═══════════════════════════════════════════════════════════════ |
//| ADVANCED ORPHAN MANAGEMENT (P1-P4) |
//| ═══════════════════════════════════════════════════════════════ |
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| P1: Update Orphan Velocity (EMA-smoothed pips/minute) |
//+------------------------------------------------------------------+
void UpdateOrphanVelocity(int idx)
{
if(!PositionSelectByTicket(orphanedLegs[idx].ticket))
return;
double pipsPerPt = PointsPerPip() * symInfo.Point();
double currentPrice = orphanedLegs[idx].isBuy ? symInfo.Bid() : symInfo.Ask();
double currentPips = orphanedLegs[idx].isBuy
? (currentPrice - orphanedLegs[idx].entryPrice) / pipsPerPt
: (orphanedLegs[idx].entryPrice - currentPrice) / pipsPerPt;
datetime now = TimeCurrent();
datetime lastSample = orphanedLegs[idx].velocityCheckTime;
// Sample only every InpVelocitySampleSecs — filter tick noise
if(lastSample != 0 && (now - lastSample) < InpVelocitySampleSecs)
return;
// Fix: On the very FIRST sample, just record a baseline — do not compute velocity.
// This prevents the entry spread (-2 to -3 pips) from being interpreted as a
// fast adverse move, which would prematurely lower the re-hedge threshold.
if(lastSample == 0)
{
orphanedLegs[idx].pipsAtLastSample = currentPips;
orphanedLegs[idx].velocityCheckTime = now;
orphanedLegs[idx].pipVelocity = 0.0; // neutral until first real sample
return;
}
double elapsedMins = MathMax((double)(now - lastSample) / 60.0, 0.0167);
double rawVelocity = (currentPips - orphanedLegs[idx].pipsAtLastSample) / elapsedMins;
// EMA smoothing (alpha=0.3) to reduce spike noise
double alpha = 0.3;
orphanedLegs[idx].pipVelocity = alpha * rawVelocity
+ (1.0 - alpha) * orphanedLegs[idx].pipVelocity;
orphanedLegs[idx].pipsAtLastSample = currentPips;
orphanedLegs[idx].velocityCheckTime = now;
}
//+------------------------------------------------------------------+
//| P1: Get Effective Re-hedge Threshold (velocity-scaled) |
//+------------------------------------------------------------------+
double GetEffectiveReHedgeThreshold(int idx)
{
double velocity = MathAbs(orphanedLegs[idx].pipVelocity);
double normVelocity = (InpVelocityNormPipsPerMin > 0)
? velocity / InpVelocityNormPipsPerMin : 1.0;
// Fast shrinks threshold; slow keeps it at 100%.
// Floor raised to 0.6 so threshold never drops below 60% of input
// (prevents re-hedging at spread-level pips in volatile markets)
double scaleFactor = MathMax(0.6, MathMin(1.0, 1.0 / MathMax(normVelocity, 0.01)));
double effectiveThreshold = InpReHedgeThresholdPips * scaleFactor;
VerboseLogThrottled("[Velocity] #" + IntegerToString((int)orphanedLegs[idx].ticket) +
" V=" + DoubleToString(velocity, 3) + " pip/min" +
" | Scale=" + DoubleToString(scaleFactor, 2) +
" | EffThresh=" + DoubleToString(effectiveThreshold, 1),
_lastLogTime_signal, logThrottleIntervalSec);
return effectiveThreshold;
}
//+------------------------------------------------------------------+
//| P2: Normalize Lot to broker step |
//+------------------------------------------------------------------+
double NormalizeLotSize(double lot)
{
double step = symInfo.LotsStep();
return (step > 0) ? MathFloor(lot / step) * step : lot;
}
//+------------------------------------------------------------------+
//| P2: Calculate Re-hedge Lot Size (recovery-focused) |
//+------------------------------------------------------------------+
double CalculateReHedgeLot(int orphanIdx)
{
if(!PositionSelectByTicket(orphanedLegs[orphanIdx].ticket))
return NormalizeLotSize(InpFixedLotSize);
double orphanLoss = PositionGetDouble(POSITION_PROFIT)
+ PositionGetDouble(POSITION_SWAP);
double orphanLotSize = PositionGetDouble(POSITION_VOLUME);
if(orphanLoss >= 0)
return NormalizeLotSize(orphanLotSize); // not in loss, mirror
double lossToRecover = MathAbs(orphanLoss);
double tpPips = (InpTakeProfitPips > 0) ? (double)InpTakeProfitPips : 20.0;
double tickValue = symInfo.TickValue();
double tickSize = symInfo.TickSize();
double pipValue = (tickSize > 0)
? (tickValue / tickSize) * symInfo.Point() * PointsPerPip() : 0;
if(pipValue <= 0 || tpPips <= 0)
{
Print("[ReHedge Sizing] pipValue/tpPips zero - fallback to orphan lot");
return NormalizeLotSize(orphanLotSize);
}
double requiredLot = lossToRecover / (pipValue * tpPips);
double maxAllowedLot = orphanLotSize * InpMaxReHedgeLotMultiplier;
double cappedLot = NormalizeLotSize(
MathMin(MathMax(requiredLot, symInfo.LotsMin()),
MathMin(maxAllowedLot, symInfo.LotsMax())));
// Margin safety check
double marginRequired = 0;
if(OrderCalcMargin(ORDER_TYPE_BUY, tradingSymbol, cappedLot, symInfo.Ask(), marginRequired))
if(marginRequired > accInfo.FreeMargin() * 0.6)
cappedLot = NormalizeLotSize(
MathMax((accInfo.FreeMargin() * 0.6) / (marginRequired / cappedLot),
symInfo.LotsMin()));
PrintFormat("[ReHedge Sizing] Loss=$%.2f | TP=%.1fp | PipVal=%.4f | Req=%.2f | Cap=%.2f | Max=%.2f",
orphanLoss, tpPips, pipValue, requiredLot, cappedLot, maxAllowedLot);
return cappedLot;
}
//+------------------------------------------------------------------+
//| P3: Manage Winning Orphan (partial close + BE SL) |
//+------------------------------------------------------------------+
void ManageWinningOrphan(int idx, double profitPips)
{
if(orphanedLegs[idx].partialCloseFired)
return;
if(profitPips < InpOrphanPartialClosePips)
return;
if(!PositionSelectByTicket(orphanedLegs[idx].ticket))
return;
double currentVol = PositionGetDouble(POSITION_VOLUME);
double closeVol = NormalizeLotSize(currentVol * 0.5);
if(currentVol - closeVol < symInfo.LotsMin())
{
PrintFormat("[WinOrphan] #%d too small to split. Letting run.", orphanedLegs[idx].ticket);
orphanedLegs[idx].partialCloseFired = true;
return;
}
if(trade.PositionClosePartial(orphanedLegs[idx].ticket, closeVol))
{
PrintFormat("[WinOrphan] Partial close %.2f lots #%d at +%.1f pips",
closeVol, orphanedLegs[idx].ticket, profitPips);
// Move SL to breakeven + buffer on remaining position
Sleep(100);
if(PositionSelectByTicket(orphanedLegs[idx].ticket))
{
double pipsPerPt = PointsPerPip() * symInfo.Point();
double currentTP = PositionGetDouble(POSITION_TP);
double newSL = orphanedLegs[idx].isBuy
? orphanedLegs[idx].entryPrice + InpOrphanBEBufferPips * pipsPerPt
: orphanedLegs[idx].entryPrice - InpOrphanBEBufferPips * pipsPerPt;
newSL = NormalizeDouble(newSL, symInfo.Digits());
if(trade.PositionModify(orphanedLegs[idx].ticket, newSL, currentTP))
PrintFormat("[WinOrphan] SL to BE+%.1f on #%d", InpOrphanBEBufferPips, orphanedLegs[idx].ticket);
else
PrintFormat("[WinOrphan] BE SL failed #%d: %d", orphanedLegs[idx].ticket, trade.ResultRetcode());
}
}
else
PrintFormat("[WinOrphan] Partial close failed #%d: %d",
orphanedLegs[idx].ticket, trade.ResultRetcode());
orphanedLegs[idx].partialCloseFired = true;
}
//+------------------------------------------------------------------+
//| Manage Orphaned Legs (P1 velocity + P3 winning orphan) |
//+------------------------------------------------------------------+
void ManageOrphanedLegs()
{
for(int i = ArraySize(orphanedLegs) - 1; i >= 0; i--)
{
if(!PositionSelectByTicket(orphanedLegs[i].ticket))
{
RemoveOrphanedLeg(i);
continue;
}
// P1: Update velocity tracking
UpdateOrphanVelocity(i);
double pipsPerPt = PointsPerPip() * symInfo.Point();
double currentPrice = orphanedLegs[i].isBuy ? symInfo.Bid() : symInfo.Ask();
double profitPips = orphanedLegs[i].isBuy
? (currentPrice - orphanedLegs[i].entryPrice) / pipsPerPt
: (orphanedLegs[i].entryPrice - currentPrice) / pipsPerPt;
//--- REACTIVE HEDGE LOGIC:
//--- If orphan is in profit → manage as winner (P3)
//--- If orphan loss exceeds velocity-weighted threshold → re-hedge (P1+P2)
//--- Timeout is fallback only for trades stuck in minor loss
if(profitPips >= 0)
{
// Trade is in profit — P3: partial close + BE SL management
ManageWinningOrphan(i, profitPips);
continue;
}
// Trade is in loss — P1: velocity-weighted threshold
double effectiveThreshold = GetEffectiveReHedgeThreshold(i);
if(profitPips <= -effectiveThreshold)
{
PrintFormat("[ReHedge] #%d triggered. Pips=%.1f | Thresh=%.1f | V=%.3f pip/min",
orphanedLegs[i].ticket, profitPips,
effectiveThreshold, orphanedLegs[i].pipVelocity);
ReHedgeOrphanedLeg(i);
}
else
if(TimeCurrent() - orphanedLegs[i].closeTime > InpOrphanMonitorMins * 60)
{
// Timeout fallback: trade stuck in minor loss for too long
PrintFormat("[ReHedge] #%d timeout fallback. Pips=%.1f",
orphanedLegs[i].ticket, profitPips);
ReHedgeOrphanedLeg(i);
}
}
}
//+------------------------------------------------------------------+
//| P4: Calculate Orphan-Only Profit |
//+------------------------------------------------------------------+
double CalculateOrphanProfit()
{
double total = 0.0;
for(int i = 0; i < ArraySize(orphanedLegs); i++)
if(PositionSelectByTicket(orphanedLegs[i].ticket))
total += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
return total;
}
//+------------------------------------------------------------------+
//| P4-L1: Tighten Orphan Stop Losses |
//+------------------------------------------------------------------+
void TightenOrphanStopLosses()
{
double pipsPerPt = PointsPerPip() * symInfo.Point();
for(int i = 0; i < ArraySize(orphanedLegs); i++)
{
if(!PositionSelectByTicket(orphanedLegs[i].ticket))
continue;
double pnl = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
if(pnl >= 0)
continue; // only tighten losers
double currentSL = PositionGetDouble(POSITION_SL);
double currentTP = PositionGetDouble(POSITION_TP);
bool isBuy = orphanedLegs[i].isBuy;
double newSL = isBuy ? symInfo.Bid() - InpOrphanSLBufferPips * pipsPerPt
: symInfo.Ask() + InpOrphanSLBufferPips * pipsPerPt;
newSL = NormalizeDouble(newSL, symInfo.Digits());
// Only move SL closer — never widen
bool tighter = isBuy ? (newSL > currentSL || currentSL == 0)
: (newSL < currentSL || currentSL == 0);
if(!tighter)
continue;
if(trade.PositionModify(orphanedLegs[i].ticket, newSL, currentTP))
PrintFormat("[OrphanL1] SL tightened #%d to %.5f", orphanedLegs[i].ticket, newSL);
else
PrintFormat("[OrphanL1] Failed #%d: %d", orphanedLegs[i].ticket, trade.ResultRetcode());
}
}
//+------------------------------------------------------------------+
//| P4: Close Orphan Fraction (partial or full) |
//+------------------------------------------------------------------+
void CloseOrphanFraction(int idx, double fraction)
{
if(!PositionSelectByTicket(orphanedLegs[idx].ticket))
return;
double currentVol = PositionGetDouble(POSITION_VOLUME);
if(fraction >= 1.0)
{
trade.PositionClose(orphanedLegs[idx].ticket);
PrintFormat("[OrphanClose] Full close #%d", orphanedLegs[idx].ticket);
RemoveOrphanedLeg(idx);
return;
}
double closeVol = NormalizeLotSize(currentVol * fraction);
if(currentVol - closeVol < symInfo.LotsMin())
{
trade.PositionClose(orphanedLegs[idx].ticket);
PrintFormat("[OrphanClose] Full close #%d (remainder < minLot)", orphanedLegs[idx].ticket);
RemoveOrphanedLeg(idx);
return;
}
if(trade.PositionClosePartial(orphanedLegs[idx].ticket, closeVol))
PrintFormat("[OrphanClose] Partial %.0f%% (%.2f lots) #%d",
fraction * 100, closeVol, orphanedLegs[idx].ticket);
else
PrintFormat("[OrphanClose] Failed #%d: %d", orphanedLegs[idx].ticket, trade.ResultRetcode());
}
//+------------------------------------------------------------------+
//| P4: Check Orphan Drawdown (3-Level Escalation) |
//+------------------------------------------------------------------+
void CheckOrphanDrawdown()
{
if(ArraySize(orphanedLegs) == 0)
return;
double orphanProfit = CalculateOrphanProfit();
if(orphanProfit >= 0)
return;
double orphanLoss = MathAbs(orphanProfit);
double equity = accInfo.Equity();
double thL1 = equity * InpOrphanDrawdownL1Pct / 100.0;
double thL2 = equity * InpOrphanDrawdownL2Pct / 100.0;
double thL3 = equity * InpOrphanDrawdownL3Pct / 100.0;
if(orphanLoss >= thL3)
{
Print("[OrphanCap] *** L3 *** Full close all orphans. Loss=$", DoubleToString(orphanLoss, 2));
for(int i = ArraySize(orphanedLegs) - 1; i >= 0; i--)
CloseOrphanFraction(i, 1.0);
}
else
if(orphanLoss >= thL2)
{
bool fired = false;
for(int i = ArraySize(orphanedLegs) - 1; i >= 0; i--)
{
if(orphanedLegs[i].drawdownLevel < 2)
{
CloseOrphanFraction(i, 0.5);
if(i < ArraySize(orphanedLegs))
orphanedLegs[i].drawdownLevel = 2;
fired = true;
}
}
if(fired)
Print("[OrphanCap] L2 partial close. Loss=$", DoubleToString(orphanLoss, 2));
else
Print("[OrphanCap] L2 already fired on all. Watching for L3.");
}
else
if(orphanLoss >= thL1)
{
bool fired = false;
for(int i = 0; i < ArraySize(orphanedLegs); i++)
if(orphanedLegs[i].drawdownLevel < 1)
{
orphanedLegs[i].drawdownLevel = 1;
fired = true;
}
if(fired)
{
Print("[OrphanCap] L1 tightening SLs. Loss=$", DoubleToString(orphanLoss, 2));
TightenOrphanStopLosses();
}
}
}
//+------------------------------------------------------------------+
//| Manage Trailing Stops |
//+------------------------------------------------------------------+
void ManageTrailingStops()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!posInfo.SelectByIndex(i))
continue;
if(posInfo.Symbol() != tradingSymbol || posInfo.Magic() != InpMagicNumber)
continue;
double openPrice = posInfo.PriceOpen();
double currentSL = posInfo.StopLoss();
double currentTP = posInfo.TakeProfit();
ulong ticket = posInfo.Ticket();
double pipsToPoints = PointsPerPip() * symInfo.Point();
if(posInfo.PositionType() == POSITION_TYPE_BUY)
{
double profitPips = (symInfo.Bid() - openPrice) / pipsToPoints;
if(profitPips >= InpTrailingStartPips)
{
double newSL = NormalizeDouble(symInfo.Bid() - InpTrailingDistPips * pipsToPoints, symInfo.Digits());
if(newSL > currentSL + InpTrailingStepPips * pipsToPoints)
trade.PositionModify(ticket, newSL, currentTP);
}
}
else
if(posInfo.PositionType() == POSITION_TYPE_SELL)
{
double profitPips = (openPrice - symInfo.Ask()) / pipsToPoints;
if(profitPips >= InpTrailingStartPips)
{
double newSL = NormalizeDouble(symInfo.Ask() + InpTrailingDistPips * pipsToPoints, symInfo.Digits());
if(currentSL == 0 || newSL < currentSL - InpTrailingStepPips * pipsToPoints)
trade.PositionModify(ticket, newSL, currentTP);
}
}
}
}
//+------------------------------------------------------------------+
//| Manage Partial Closes |
//+------------------------------------------------------------------+
void ManagePartialCloses()
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!posInfo.SelectByIndex(i))
continue;
if(posInfo.Symbol() != tradingSymbol || posInfo.Magic() != InpMagicNumber)
continue;
ulong ticket = posInfo.Ticket();
if(IsAlreadyPartiallyClosed(ticket))
continue;
double openPrice = posInfo.PriceOpen();
double volume = posInfo.Volume();
double pipsToPoints = PointsPerPip() * symInfo.Point();
double profitPips = 0;
if(posInfo.PositionType() == POSITION_TYPE_BUY)
profitPips = (symInfo.Bid() - openPrice) / pipsToPoints;
else
profitPips = (openPrice - symInfo.Ask()) / pipsToPoints;
if(profitPips >= InpPartialClosePips)
{
double closeVolume = NormalizeDouble(volume * InpPartialClosePercent / 100.0, 2);
double minLot = symInfo.LotsMin();
double lotStep = symInfo.LotsStep();
closeVolume = MathMax(closeVolume, minLot);
closeVolume = MathFloor(closeVolume / lotStep) * lotStep;
closeVolume = NormalizeDouble(closeVolume, 2);
if(volume - closeVolume < minLot)
continue;
if(trade.PositionClosePartial(ticket, closeVolume))
{
MarkAsPartiallyClosed(ticket);
TradeEventLog("Partial close #" + IntegerToString((int)ticket) + ": " +
DoubleToString(closeVolume, 2) + " lots at " +
DoubleToString(profitPips, 1) + " pips");
if(InpMoveSLToBreakeven)
{
Sleep(100);
if(PositionSelectByTicket(ticket))
{
double tp = PositionGetDouble(POSITION_TP);
double spreadBuf = symInfo.Spread() * symInfo.Point() * 2;
double beSL = (posInfo.PositionType() == POSITION_TYPE_BUY) ?
openPrice + spreadBuf : openPrice - spreadBuf;
beSL = NormalizeDouble(beSL, symInfo.Digits());
trade.PositionModify(ticket, beSL, tp);
}
}
}
}
else if (InpEnablePartialLoss && profitPips <= -InpPartialLossPips)
{
double closeVolume = NormalizeDouble(volume * InpPartialClosePercent / 100.0, 2);
double minLot = symInfo.LotsMin();
double lotStep = symInfo.LotsStep();
closeVolume = MathMax(closeVolume, minLot);
closeVolume = MathFloor(closeVolume / lotStep) * lotStep;
closeVolume = NormalizeDouble(closeVolume, 2);
if(volume - closeVolume < minLot)
continue;
if(trade.PositionClosePartial(ticket, closeVolume))
{
MarkAsPartiallyClosed(ticket);
TradeEventLog("PARTIAL LOSS close #" + IntegerToString((int)ticket) + ": " +
DoubleToString(closeVolume, 2) + " lots at " +
DoubleToString(profitPips, 1) + " pips");
}
}
}
}
//+------------------------------------------------------------------+
//| Is Already Partially Closed |
//+------------------------------------------------------------------+
bool IsAlreadyPartiallyClosed(ulong ticket)
{
for(int i = 0; i < ArraySize(partialCloseTracker); i++)
if(partialCloseTracker[i].ticket == ticket && partialCloseTracker[i].closed)
return true;
return false;
}
//+------------------------------------------------------------------+
//| Mark As Partially Closed |
//+------------------------------------------------------------------+
void MarkAsPartiallyClosed(ulong ticket)
{
int idx = ArraySize(partialCloseTracker);
ArrayResize(partialCloseTracker, idx + 1);
partialCloseTracker[idx].ticket = ticket;
partialCloseTracker[idx].closed = true;
}
//+------------------------------------------------------------------+
//| Calculate Hedged Pairs Profit (excludes orphans) |
//+------------------------------------------------------------------+
double CalculateHedgedPairsProfit()
{
double profit = 0;
for(int i = 0; i < ArraySize(tradePairs); i++)
{
if(tradePairs[i].buyTicket > 0 && !tradePairs[i].buyClosed &&
PositionSelectByTicket(tradePairs[i].buyTicket))
profit += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
if(tradePairs[i].sellTicket > 0 && !tradePairs[i].sellClosed &&
PositionSelectByTicket(tradePairs[i].sellTicket))
profit += PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
}
return profit;
}
//+------------------------------------------------------------------+
//| Check Basket Close |
//+------------------------------------------------------------------+
void CheckBasketClose()
{
// Use hedged-pairs-only profit so orphaned legs don't trigger mass close
double basketProfit = CalculateHedgedPairsProfit();
if(basketProfit >= InpBasketProfitUSD)
CloseAllPositions("Basket profit: " + DoubleToString(basketProfit, 2));
else
if(basketProfit <= -InpBasketLossUSD)
CloseAllPositions("Basket loss: " + DoubleToString(basketProfit, 2));
}
//+------------------------------------------------------------------+
//| Calculate Basket Profit |
//+------------------------------------------------------------------+
double CalculateBasketProfit()
{
double profit = 0;
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!posInfo.SelectByIndex(i))
continue;
if(posInfo.Symbol() != tradingSymbol || posInfo.Magic() != InpMagicNumber)
continue;
profit += posInfo.Profit() + posInfo.Swap();
}
return profit;
}
//+------------------------------------------------------------------+
//| Refresh EA |
//+------------------------------------------------------------------+
void RefreshEA()
{
DetectMarketRegime();
ScanExistingPositions();
lastRefreshTime = TimeCurrent();
}
//+------------------------------------------------------------------+
//| Update Equity Tracking |
//+------------------------------------------------------------------+
void UpdateEquityTracking()
{
double currentEquity = accInfo.Equity();
if(currentEquity > peakEquity)
peakEquity = currentEquity;
if(currentEquity > session.sessionPeakEquity)
session.sessionPeakEquity = currentEquity;
double ddFromPeak = (peakEquity > 0) ? (peakEquity - currentEquity) / peakEquity * 100 : 0;
if(ddFromPeak > session.sessionMaxDD)
session.sessionMaxDD = ddFromPeak;
if(InpEnableEquityProtect && peakEquity > 0 &&
currentEquity < peakEquity * InpEquityHWMPercent / 100.0)
CloseAllPositions("Equity protection at " + DoubleToString(currentEquity, 2));
}
//+------------------------------------------------------------------+
//| Scan Existing Positions |
//+------------------------------------------------------------------+
void ScanExistingPositions()
{
ulong buyTickets[], sellTickets[];
double buyLots[], sellLots[], buyPrices[], sellPrices[];
ArrayResize(buyTickets, 0);
ArrayResize(sellTickets, 0);
ArrayResize(buyLots, 0);
ArrayResize(sellLots, 0);
ArrayResize(buyPrices, 0);
ArrayResize(sellPrices, 0);
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!posInfo.SelectByIndex(i))
continue;
if(posInfo.Symbol() != tradingSymbol || posInfo.Magic() != InpMagicNumber)
continue;
if(posInfo.PositionType() == POSITION_TYPE_BUY)
{
int idx = ArraySize(buyTickets);
ArrayResize(buyTickets, idx + 1);
ArrayResize(buyLots, idx + 1);
ArrayResize(buyPrices, idx + 1);
buyTickets[idx] = posInfo.Ticket();
buyLots[idx] = posInfo.Volume();
buyPrices[idx] = posInfo.PriceOpen();
}
else
{
int idx = ArraySize(sellTickets);
ArrayResize(sellTickets, idx + 1);
ArrayResize(sellLots, idx + 1);
ArrayResize(sellPrices, idx + 1);
sellTickets[idx] = posInfo.Ticket();
sellLots[idx] = posInfo.Volume();
sellPrices[idx] = posInfo.PriceOpen();
}
}
ArrayResize(tradePairs, 0);
for(int i = 0; i < ArraySize(buyTickets); i++)
{
if(buyTickets[i] == 0)
continue;
for(int j = 0; j < ArraySize(sellTickets); j++)
{
if(sellTickets[j] == 0)
continue;
if(buyLots[i] > 0 && MathAbs(buyLots[i] - sellLots[j]) / buyLots[i] < 0.05)
{
int idx = ArraySize(tradePairs);
ArrayResize(tradePairs, idx + 1);
ZeroMemory(tradePairs[idx]);
tradePairs[idx].buyTicket = buyTickets[i];
tradePairs[idx].sellTicket = sellTickets[j];
tradePairs[idx].lotSize = buyLots[i];
tradePairs[idx].openTime = TimeCurrent();
tradePairs[idx].buyEntryPrice = buyPrices[i];
tradePairs[idx].sellEntryPrice = sellPrices[j];
buyTickets[i] = 0;
sellTickets[j] = 0;
break;
}
}
}
ArrayResize(orphanedLegs, 0);
for(int i = 0; i < ArraySize(buyTickets); i++)
if(buyTickets[i] > 0)
AddOrphanedLeg(buyTickets[i], true, buyLots[i]);
for(int i = 0; i < ArraySize(sellTickets); i++)
if(sellTickets[i] > 0)
AddOrphanedLeg(sellTickets[i], false, sellLots[i]);
}
//+------------------------------------------------------------------+
//| Get Complete Pairs Count |
//+------------------------------------------------------------------+
int GetCompletePairsCount()
{
int count = 0;
for(int i = 0; i < ArraySize(tradePairs); i++)
if(tradePairs[i].buyTicket > 0 && tradePairs[i].sellTicket > 0 &&
!tradePairs[i].buyClosed && !tradePairs[i].sellClosed)
count++;
return count;
}
//+------------------------------------------------------------------+
//| Get Incomplete Pairs Count |
//+------------------------------------------------------------------+
int GetIncompletePairsCount()
{
int count = 0;
for(int i = 0; i < ArraySize(tradePairs); i++)
{
bool buyOpen = (tradePairs[i].buyTicket > 0 && !tradePairs[i].buyClosed);
bool sellOpen = (tradePairs[i].sellTicket > 0 && !tradePairs[i].sellClosed);
if((buyOpen && !sellOpen) || (sellOpen && !buyOpen))
count++;
}
return count;
}
//+------------------------------------------------------------------+
//| Find Incomplete Pair |
//+------------------------------------------------------------------+
int FindIncompletePair()
{
for(int i = 0; i < ArraySize(tradePairs); i++)
{
bool buyOpen = (tradePairs[i].buyTicket > 0 && !tradePairs[i].buyClosed);
bool sellOpen = (tradePairs[i].sellTicket > 0 && !tradePairs[i].sellClosed);
if((buyOpen && !sellOpen) || (sellOpen && !buyOpen))
return i;
}
return -1;
}
//+------------------------------------------------------------------+
//| Complete Incomplete Pair |
//+------------------------------------------------------------------+
bool CompleteIncompletePair(int pairIndex)
{
if(pairIndex < 0 || pairIndex >= ArraySize(tradePairs))
return false;
double spreadCost = symInfo.Spread() * symInfo.Point() * tradePairs[pairIndex].lotSize * PointsPerPip();
double profitTarget = MathMax(5.0, spreadCost * 2.0) + spreadCost;
bool buyOpen = (tradePairs[pairIndex].buyTicket > 0 && !tradePairs[pairIndex].buyClosed);
bool sellOpen = (tradePairs[pairIndex].sellTicket > 0 && !tradePairs[pairIndex].sellClosed);
if(!buyOpen && sellOpen)
{
if(PositionSelectByTicket(tradePairs[pairIndex].sellTicket))
{
double profit = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
if(profit >= profitTarget)
{
if(InpUseMTFConfirmation && !ConfirmEntryDirection(true))
return false;
if(InpEnableDXYCorrelation && !DoesDXYConfirmDirection(true))
return false;
double slPips = GetAdaptiveSL();
double tpPips = GetAdaptiveTP();
ulong buyTicket = ExecuteTrade(true, tradePairs[pairIndex].lotSize, slPips, tpPips);
if(buyTicket > 0)
{
tradePairs[pairIndex].buyTicket = buyTicket;
tradePairs[pairIndex].buyClosed = false;
tradePairs[pairIndex].buyEntryPrice = symInfo.Ask();
return true;
}
}
}
}
else
if(!sellOpen && buyOpen)
{
if(PositionSelectByTicket(tradePairs[pairIndex].buyTicket))
{
double profit = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
if(profit >= profitTarget)
{
if(InpUseMTFConfirmation && !ConfirmEntryDirection(false))
return false;
if(InpEnableDXYCorrelation && !DoesDXYConfirmDirection(false))
return false;
double slPips = GetAdaptiveSL();
double tpPips = GetAdaptiveTP();
ulong sellTicket = ExecuteTrade(false, tradePairs[pairIndex].lotSize, slPips, tpPips);
if(sellTicket > 0)
{
tradePairs[pairIndex].sellTicket = sellTicket;
tradePairs[pairIndex].sellClosed = false;
tradePairs[pairIndex].sellEntryPrice = symInfo.Bid();
return true;
}
}
}
}
return false;
}
//+------------------------------------------------------------------+
//| Create Hedged Pair |
//+------------------------------------------------------------------+
bool CreateHedgedPair()
{
double lotSize = CalculatePositionSize();
if(lotSize <= 0)
return false;
//--- Keep lot size constant (disable multipliers)
// Session-based lot adjustment disabled to keep lot size constant
// Breakout-based lot adjustment disabled to keep lot size constant
//--- Cap lot size
if(InpMaxPositionSize > 0 && lotSize > InpMaxPositionSize)
lotSize = InpMaxPositionSize;
lotSize = MathMax(lotSize, symInfo.LotsMin());
bool openBuyFirst = DetermineFirstLegDirection();
int idx = ArraySize(tradePairs);
ArrayResize(tradePairs, idx + 1);
ZeroMemory(tradePairs[idx]);
tradePairs[idx].lotSize = lotSize;
tradePairs[idx].openTime = TimeCurrent();
double slPips = GetAdaptiveSL();
double tpPips = GetAdaptiveTP();
ulong firstTicket = ExecuteTrade(openBuyFirst, lotSize, slPips, tpPips);
if(firstTicket > 0)
{
if(openBuyFirst)
{
tradePairs[idx].buyTicket = firstTicket;
tradePairs[idx].buyEntryPrice = symInfo.Ask();
}
else
{
tradePairs[idx].sellTicket = firstTicket;
tradePairs[idx].sellEntryPrice = symInfo.Bid();
}
AddOrphanedLeg(firstTicket, openBuyFirst, lotSize, idx);
TradeEventLog("Created pair #" + IntegerToString((int)firstTicket) +
" Signal=" + DoubleToString(signalScore, 3));
return true;
}
ArrayResize(tradePairs, idx);
return false;
}
//+------------------------------------------------------------------+
//| Determine First Leg Direction |
//+------------------------------------------------------------------+
bool DetermineFirstLegDirection()
{
//--- Breakout override (respecting MES)
if(InpEnableBreakout && breakout.breakoutDetected)
{
if(breakout.breakoutBullish && !mesBlockBuy)
return true;
if(!breakout.breakoutBullish && !mesBlockSell)
return false;
}
//--- Use composite signal score
if(MathAbs(signalScore) > 0.2)
{
if(signalScore > 0 && !mesBlockBuy) return true;
if(signalScore < 0 && !mesBlockSell) return false;
}
//--- Fallback to EMA
double ema5[], ema20[];
ArraySetAsSeries(ema5, true);
ArraySetAsSeries(ema20, true);
if(CopyBuffer(hEMA5, 0, 0, 3, ema5) < 3)
return true;
if(CopyBuffer(hEMA20, 0, 0, 3, ema20) < 3)
return true;
bool emaBullish = ema5[0] > ema20[0];
if(emaBullish && !mesBlockBuy) return true;
if(!emaBullish && !mesBlockSell) return false;
//--- Ultimate fallback
if(!mesBlockBuy) return true;
if(!mesBlockSell) return false;
return true;
}
//+------------------------------------------------------------------+
//| Multi-Timeframe Entry Confirmation |
//+------------------------------------------------------------------+
bool ConfirmEntryDirection(bool isBuy)
{
if(!InpUseMTFConfirmation)
return true;
if(hADX_HTF != INVALID_HANDLE)
{
double htfADX[], htfPlus[], htfMinus[];
ArraySetAsSeries(htfADX, true);
ArraySetAsSeries(htfPlus, true);
ArraySetAsSeries(htfMinus, true);
if(CopyBuffer(hADX_HTF, 0, 0, 2, htfADX) >= 2 &&
CopyBuffer(hADX_HTF, 1, 0, 2, htfPlus) >= 2 &&
CopyBuffer(hADX_HTF, 2, 0, 2, htfMinus) >= 2)
{
if(htfADX[0] > InpTrendThreshold)
{
bool htfBullish = htfPlus[0] > htfMinus[0];
if(isBuy && !htfBullish)
return false;
if(!isBuy && htfBullish)
return false;
}
}
}
if(hEMA5_LTF != INVALID_HANDLE && hEMA20_LTF != INVALID_HANDLE)
{
double ltfEma5[], ltfEma20[];
ArraySetAsSeries(ltfEma5, true);
ArraySetAsSeries(ltfEma20, true);
if(CopyBuffer(hEMA5_LTF, 0, 0, 2, ltfEma5) >= 2 &&
CopyBuffer(hEMA20_LTF, 0, 0, 2, ltfEma20) >= 2)
{
bool ltfBullish = ltfEma5[0] > ltfEma20[0];
if(isBuy && !ltfBullish)
return false;
if(!isBuy && ltfBullish)
return false;
}
}
return true;
}
//+------------------------------------------------------------------+
//| Execute Trade |
//+------------------------------------------------------------------+
ulong ExecuteTrade(bool isBuy, double lotSize, double slPips, double tpPips)
{
symInfo.RefreshRates();
double price = isBuy ? symInfo.Ask() : symInfo.Bid();
double pipsToPrice = PointsPerPip() * symInfo.Point();
double sl = isBuy ? price - slPips * pipsToPrice : price + slPips * pipsToPrice;
double tp = isBuy ? price + tpPips * pipsToPrice : price - tpPips * pipsToPrice;
sl = NormalizeDouble(sl, symInfo.Digits());
tp = NormalizeDouble(tp, symInfo.Digits());
price = NormalizeDouble(price, symInfo.Digits());
bool result = isBuy ? trade.Buy(lotSize, tradingSymbol, price, sl, tp, EA_NAME) :
trade.Sell(lotSize, tradingSymbol, price, sl, tp, EA_NAME);
if(result)
{
ulong ticket = trade.ResultOrder();
if(ticket > 0)
{
lastTradeTime = TimeCurrent();
TradeEventLog("Trade OK: #" + IntegerToString((int)ticket) +
" " + (isBuy ? "BUY" : "SELL") + " " + DoubleToString(lotSize, 2));
return ticket;
}
ulong dealTicket = trade.ResultDeal();
if(dealTicket > 0)
{
lastTradeTime = TimeCurrent();
Sleep(100);
for(int i = PositionsTotal() - 1; i >= 0; i--)
if(posInfo.SelectByIndex(i))
if(posInfo.Symbol() == tradingSymbol && posInfo.Magic() == InpMagicNumber)
if(MathAbs(posInfo.Volume() - lotSize) < 0.001)
return posInfo.Ticket();
}
}
else
TradeEventLogCritical("Trade FAILED: " + IntegerToString(trade.ResultRetcode()) +
" " + trade.ResultRetcodeDescription());
return 0;
}
//+------------------------------------------------------------------+
//| Add Orphaned Leg |
//+------------------------------------------------------------------+
void AddOrphanedLeg(ulong ticket, bool isBuy, double lotSize, int pairIndex = -1)
{
if(ticket == 0 || !PositionSelectByTicket(ticket))
return;
bool positionIsBuy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY);
isBuy = positionIsBuy;
for(int i = 0; i < ArraySize(orphanedLegs); i++)
if(orphanedLegs[i].ticket == ticket)
return;
int idx = ArraySize(orphanedLegs);
ArrayResize(orphanedLegs, idx + 1);
orphanedLegs[idx].ticket = ticket;
orphanedLegs[idx].isBuy = isBuy;
orphanedLegs[idx].lotSize = lotSize;
orphanedLegs[idx].entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);
orphanedLegs[idx].closeTime = TimeCurrent();
orphanedLegs[idx].pairIndex = pairIndex;
orphanedLegs[idx].partialClosed = false;
orphanedLegs[idx].velocityCheckTime = 0;
orphanedLegs[idx].pipsAtLastSample = 0.0;
orphanedLegs[idx].pipVelocity = 0.0;
orphanedLegs[idx].partialCloseFired = false;
orphanedLegs[idx].drawdownLevel = 0;
double currentProfit = PositionGetDouble(POSITION_PROFIT) + PositionGetDouble(POSITION_SWAP);
orphanedLegs[idx].initialLoss = (currentProfit < 0) ? MathAbs(currentProfit) : 0.0;
}
//+------------------------------------------------------------------+
//| Close Orphaned Leg at Profit |
//+------------------------------------------------------------------+
bool CloseOrphanedLegAtProfit(int orphanIndex)
{
if(orphanIndex < 0 || orphanIndex >= ArraySize(orphanedLegs))
return false;
ulong ticket = orphanedLegs[orphanIndex].ticket;
if(!PositionSelectByTicket(ticket))
{
RemoveOrphanedLeg(orphanIndex);
return true;
}
if(trade.PositionClose(ticket))
{
RemoveOrphanedLeg(orphanIndex);
return true;
}
return false;
}
//+------------------------------------------------------------------+
//| Re-Hedge Orphaned Leg |
//+------------------------------------------------------------------+
bool ReHedgeOrphanedLeg(int orphanIndex)
{
if(orphanIndex < 0 || orphanIndex >= ArraySize(orphanedLegs))
return false;
if(!PositionSelectByTicket(orphanedLegs[orphanIndex].ticket))
{
RemoveOrphanedLeg(orphanIndex);
return true;
}
bool currentIsBuy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY);
orphanedLegs[orphanIndex].isBuy = currentIsBuy;
bool newIsBuy = !currentIsBuy;
double reHedgeLot = CalculateReHedgeLot(orphanIndex);
ulong newTicket = ExecuteTrade(newIsBuy, reHedgeLot, GetAdaptiveSL(), GetAdaptiveTP());
if(newTicket == 0)
return false;
int pairIndex = orphanedLegs[orphanIndex].pairIndex;
if(pairIndex >= 0 && pairIndex < ArraySize(tradePairs))
{
if(orphanedLegs[orphanIndex].isBuy)
{
tradePairs[pairIndex].sellTicket = newTicket;
tradePairs[pairIndex].sellClosed = false;
tradePairs[pairIndex].sellEntryPrice = symInfo.Bid();
}
else
{
tradePairs[pairIndex].buyTicket = newTicket;
tradePairs[pairIndex].buyClosed = false;
tradePairs[pairIndex].buyEntryPrice = symInfo.Ask();
}
}
else
{
pairIndex = ArraySize(tradePairs);
ArrayResize(tradePairs, pairIndex + 1);
ZeroMemory(tradePairs[pairIndex]);
tradePairs[pairIndex].lotSize = reHedgeLot;
tradePairs[pairIndex].openTime = TimeCurrent();
if(orphanedLegs[orphanIndex].isBuy)
{
tradePairs[pairIndex].buyTicket = orphanedLegs[orphanIndex].ticket;
tradePairs[pairIndex].sellTicket = newTicket;
tradePairs[pairIndex].buyClosed = false;
tradePairs[pairIndex].sellClosed = false;
tradePairs[pairIndex].buyEntryPrice = orphanedLegs[orphanIndex].entryPrice;
tradePairs[pairIndex].sellEntryPrice = symInfo.Bid();
}
else
{
tradePairs[pairIndex].buyTicket = newTicket;
tradePairs[pairIndex].sellTicket = orphanedLegs[orphanIndex].ticket;
tradePairs[pairIndex].buyClosed = false;
tradePairs[pairIndex].sellClosed = false;
tradePairs[pairIndex].buyEntryPrice = symInfo.Ask();
tradePairs[pairIndex].sellEntryPrice = orphanedLegs[orphanIndex].entryPrice;
}
}
RemoveOrphanedLeg(orphanIndex);
return true;
}
//+------------------------------------------------------------------+
//| Remove Orphaned Leg |
//+------------------------------------------------------------------+
void RemoveOrphanedLeg(int orphanIndex)
{
if(orphanIndex < 0 || orphanIndex >= ArraySize(orphanedLegs))
return;
for(int i = orphanIndex; i < ArraySize(orphanedLegs) - 1; i++)
orphanedLegs[i] = orphanedLegs[i + 1];
ArrayResize(orphanedLegs, ArraySize(orphanedLegs) - 1);
}
//+------------------------------------------------------------------+
//| Handle Stop Loss Hit |
//+------------------------------------------------------------------+
void HandleStopLossHit(ulong ticket, bool isBuy, double lotSize)
{
consecutiveSLHits++;
for(int i = 2; i > 0; i--)
lastThreeSLTimes[i] = lastThreeSLTimes[i - 1];
lastThreeSLTimes[0] = TimeCurrent();
lastSLHitTime = TimeCurrent();
if(consecutiveSLHits >= 3 && lastThreeSLTimes[0] - lastThreeSLTimes[2] < 3600)
{
tradingPausedDueToSL = true;
inSLTradeDelay = true;
}
else
inSLTradeDelay = true;
}
//+------------------------------------------------------------------+
//| Is Trade Allowed - Master Check |
//+------------------------------------------------------------------+
bool IsTradeAllowed()
{
lastTradeBlockReason = "";
//--- Tester prop firm check
if(MQLInfoInteger(MQL_TESTER))
{
if(InpPropFirmMode && (IsDailyLossLimitBreached() || IsTotalDrawdownBreached()))
{
lastTradeBlockReason = "Prop firm loss limit breached (tester)";
return false;
}
}
//--- Terminal/account trading permissions
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || !AccountInfoInteger(ACCOUNT_TRADE_ALLOWED))
{
lastTradeBlockReason = "Terminal or account trading not allowed";
return false;
}
//--- SL delay
if(inSLTradeDelay && TimeCurrent() - lastSLHitTime <= InpSLDelayMinutes * 60)
{
int remaining = (int)(InpSLDelayMinutes * 60 - (TimeCurrent() - lastSLHitTime));
lastTradeBlockReason = "SL delay active (" + IntegerToString(remaining / 60) + "m " +
IntegerToString(remaining % 60) + "s remaining)";
return false;
}
if(tradingPausedDueToSL)
{
lastTradeBlockReason = "Trading paused: 3+ SL hits within 1 hour";
return false;
}
//--- Session paused by learning
if(session.currentAction == SESSION_PAUSED)
{
lastTradeBlockReason = "Session PAUSED (5+ consecutive losses)";
return false;
}
//--- Phase 2: Re-entry cooldown
if(InpReEntryCooldownMin > 0 && lastTradeTime > 0 &&
TimeCurrent() - lastTradeTime < InpReEntryCooldownMin * 60)
{
int cooldownRemaining = (int)(InpReEntryCooldownMin * 60 - (TimeCurrent() - lastTradeTime));
lastTradeBlockReason = "Re-entry cooldown (" + IntegerToString(cooldownRemaining / 60) + "m " +
IntegerToString(cooldownRemaining % 60) + "s remaining)";
return false;
}
//--- Max pairs limit (always enforced for NEW pairs)
int totalPairs = GetCompletePairsCount() + GetIncompletePairsCount();
if(totalPairs >= InpMaxPairsAllowed)
{
lastTradeBlockReason = "Max pairs reached (" + IntegerToString(totalPairs) +
"/" + IntegerToString(InpMaxPairsAllowed) + ")";
return false;
}
//--- Market regime
if(!IsRegimeAllowed(currentRegime))
{
lastTradeBlockReason = "Regime not allowed: " + GetRegimeString(currentRegime);
return false;
}
//--- Phase 2: Block entries in HIGH_VOLATILITY regime
if(currentRegime == REGIME_HIGH_VOLATILITY)
{
lastTradeBlockReason = "High volatility regime - entries blocked";
return false;
}
//--- Volatility
if(currentVolatilityPts > InpMaxVolatilityPts)
{
lastTradeBlockReason = "Volatility too high: " + DoubleToString(currentVolatilityPts, 0) +
" > " + DoubleToString(InpMaxVolatilityPts, 0) + " pts";
return false;
}
//--- Phase 3: Momentum Exhaustion Filter
if(InpEnableMomentumExhaustion && momentumExhaustionScore >= InpMESFullBlockThreshold)
{
lastTradeBlockReason = "Momentum exhausted (" +
DoubleToString(momentumExhaustionScore * 100, 0) + "%)";
return false;
}
//--- Session filter
if(!IsSessionAllowed())
{
lastTradeBlockReason = "Session not allowed: " + GetSessionName(GetCurrentSession());
VerboseLogThrottled(lastTradeBlockReason, _lastLogTime_session, logThrottleIntervalSec);
return false;
}
//--- News filter
if(IsNewsFilterBlocking())
{
lastTradeBlockReason = "News filter active - trading blocked";
VerboseLogThrottled(lastTradeBlockReason, _lastLogTime_news, logThrottleIntervalSec);
return false;
}
//--- Spread filter
if(!IsSpreadAcceptable())
{
lastTradeBlockReason = "Spread too high: " + DoubleToString((double)symInfo.Spread(), 1) +
" (avg " + DoubleToString(averageSpread, 1) + ")";
return false;
}
//--- Monte Carlo viability
if(InpEnableMonteCarlo && !mcResult.isViable)
{
lastTradeBlockReason = "Monte Carlo: strategy not viable at " +
DoubleToString(InpMCConfidenceLevel * 100, 0) + "% confidence";
VerboseLogThrottled(lastTradeBlockReason, _lastLogTime_trade, logThrottleIntervalSec);
return false;
}
//--- Free margin
if(accInfo.FreeMargin() < InpMinFreeMargin)
{
lastTradeBlockReason = "Insufficient free margin: " + DoubleToString(accInfo.FreeMargin(), 2) +
" < " + DoubleToString(InpMinFreeMargin, 2);
return false;
}
//--- Basket drawdown
double basketProfit = CalculateBasketProfit();
if(InpMaxBasketDDPercent > 0 && basketProfit < 0 &&
MathAbs(basketProfit) / accInfo.Balance() * 100 > InpMaxBasketDDPercent)
{
lastTradeBlockReason = "Basket drawdown exceeded: " +
DoubleToString(MathAbs(basketProfit) / accInfo.Balance() * 100, 1) +
"% > " + DoubleToString(InpMaxBasketDDPercent, 1) + "%";
return false;
}
//--- Prop firm daily target
if(InpPropFirmMode && session.dailyTargetReached)
session.currentAction = SESSION_CONSERVATIVE;
//--- Learning hour filter
if(InpEnableLearning && learningInitialized && learning.totalTrades >= 30)
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
if(learning.hourlyTradeCount[dt.hour] >= 5 && learning.hourlyWinRate[dt.hour] < 0.35)
{
lastTradeBlockReason = "Hour " + IntegerToString(dt.hour) + ":00 has poor win rate (" +
DoubleToString(learning.hourlyWinRate[dt.hour] * 100, 1) + "%)";
return false;
}
}
lastTradeBlockReason = "";
return true;
}
//+------------------------------------------------------------------+
//| Print Trade Attempt Diagnostic |
//+------------------------------------------------------------------+
void PrintTradeAttemptDiagnostic()
{
string nl = "\n";
string diag = "═══ TRADE DIAGNOSTIC ═══" + nl;
// Block reason
if(lastTradeBlockReason != "")
diag += "STATUS: BLOCKED - " + lastTradeBlockReason + nl;
else
diag += "STATUS: ALLOWED (waiting for signal)" + nl;
// Core metrics
diag += "Pairs: " + IntegerToString(GetCompletePairsCount()) + "/" + IntegerToString(InpMaxPairsAllowed) + nl;
diag += "Signal: " + DoubleToString(signalScore, 3) + " (" + SignalStrengthToString(signalStrength) +
") | Need >= MODERATE (0.2)" + nl;
diag += "Spread: " + DoubleToString((double)symInfo.Spread(), 1) +
" (avg " + DoubleToString(averageSpread, 1) + ")" + nl;
diag += "Vol: " + DoubleToString(currentVolatilityPts, 0) + "pts" +
" (max " + DoubleToString(InpMaxVolatilityPts, 0) + ")" + nl;
diag += "Regime: " + GetRegimeString(currentRegime) + nl;
// Signal components breakdown
diag += "--- Signal Components ---" + nl;
diag += "EMA: " + DoubleToString(GetEMASignal(), 3) + nl;
diag += "Regime: " + DoubleToString(GetRegimeSignal(), 3) + nl;
diag += "RSI: " + DoubleToString(GetRSISignal(), 3) + nl;
diag += "Session: " + DoubleToString(GetSessionQualitySignal(), 3) + nl;
if(InpEnableDXYCorrelation && dxyAvailable)
diag += "DXY: " + DoubleToString(GetDXYSignal(), 3) + nl;
else
diag += "DXY: N/A" + nl;
if(InpEnableBreakout)
diag += "Breakout: " + (breakout.breakoutDetected ? "YES" : "NO") + nl;
// Filter status
diag += "--- Filter Status ---" + nl;
diag += "Session: " + (IsSessionAllowed() ? "PASS" : "FAIL") +
" (" + GetSessionName(GetCurrentSession()) + ")" + nl;
diag += "News: " + (newsFilterActive ? "BLOCKING" : "CLEAR") + nl;
diag += "Spread: " + (IsSpreadAcceptable() ? "PASS" : "FAIL") + nl;
diag += "MC: " + (InpEnableMonteCarlo ? (mcResult.isViable ? "VIABLE" : "NOT VIABLE") : "OFF") + nl;
diag += "Margin: " + DoubleToString(accInfo.FreeMargin(), 2) +
" (min " + DoubleToString(InpMinFreeMargin, 2) + ")" + nl;
Print(diag);
}
//+------------------------------------------------------------------+
//| Calculate Position Size |
//+------------------------------------------------------------------+
double CalculatePositionSize()
{
double lotSize = 0;
double minLot = symInfo.LotsMin();
double lotStep = symInfo.LotsStep();
double maxLot = symInfo.LotsMax();
double pipValue = GetPipValue();
if(pipValue <= 0)
return 0;
double riskPercent = InpRiskPerTrade;
if(InpAdaptiveLotSize && InpEnableLearning && learningInitialized && learning.totalTrades >= 20)
riskPercent = learning.optimalRiskPercent;
if(session.currentAction == SESSION_CONSERVATIVE)
riskPercent *= 0.5;
double slPips = GetAdaptiveSL();
if(InpUsePercentRisk)
{
double riskAmount = accInfo.Balance() * riskPercent / 100.0;
lotSize = riskAmount / (slPips * pipValue);
// Market adjustment (only for risk-based sizing)
double atrVal[], adxVal[];
ArraySetAsSeries(atrVal, true);
ArraySetAsSeries(adxVal, true);
double marketFactor = 1.0;
if(CopyBuffer(hATR_MTF, 0, 0, 2, atrVal) >= 2 && CopyBuffer(hADX_MTF, 0, 0, 2, adxVal) >= 2)
{
double volFactor = MathMin(atrVal[0] / symInfo.Point() / InpMaxVolatilityPts, 1.0);
double trendFactor = adxVal[0] / InpTrendThreshold;
marketFactor = MathMax(0.5, MathMin((volFactor + trendFactor) / 2.0, 1.5));
}
lotSize *= marketFactor;
// Safety caps (percentage-based, only for risk-based sizing)
double potentialLoss = lotSize * slPips * pipValue;
if(potentialLoss > accInfo.Equity() * 0.05)
lotSize = accInfo.Equity() * 0.05 / (slPips * pipValue);
if(potentialLoss > accInfo.Balance() * 0.1)
lotSize = accInfo.Balance() * 0.1 / (slPips * pipValue);
}
else
lotSize = InpFixedLotSize;
// Safety caps that ALWAYS apply (regardless of sizing mode)
if(InpMaxPositionSize > 0 && lotSize > InpMaxPositionSize)
lotSize = InpMaxPositionSize;
double marginRequired = 0;
if(!OrderCalcMargin(ORDER_TYPE_BUY, tradingSymbol, lotSize, symInfo.Ask(), marginRequired))
marginRequired = lotSize * symInfo.Ask() / 100;
if(marginRequired > accInfo.FreeMargin() * 0.8)
lotSize = (accInfo.FreeMargin() * 0.8) / (marginRequired / lotSize);
if(InpPropFirmMode)
{
double dailyLossLimit = initialAccountBalance * InpMaxDailyLossPercent / 100.0;
double remainingLoss = dailyLossLimit - MathAbs(MathMin(session.dailyPnL, 0));
double maxLotDaily = remainingLoss / (slPips * pipValue);
if(lotSize > maxLotDaily && maxLotDaily > 0)
lotSize = maxLotDaily;
}
// Normalize
lotSize = MathMax(lotSize, minLot);
lotSize = MathFloor(lotSize / lotStep) * lotStep;
lotSize = NormalizeDouble(lotSize, 2);
if(lotSize > maxLot)
lotSize = maxLot;
return (lotSize > 0) ? lotSize : 0;
}
//+------------------------------------------------------------------+
//| Get Adaptive SL |
//+------------------------------------------------------------------+
double GetAdaptiveSL()
{
if(!InpUseATR || hATR_MTF == INVALID_HANDLE)
return (InpAdaptiveSLTP && learningInitialized && learning.totalTrades >= 20)
? learning.bestSLPips : InpStopLossPips;
double atrBuffer[];
ArraySetAsSeries(atrBuffer, true);
if(CopyBuffer(hATR_MTF, 0, 0, 1, atrBuffer) < 1)
return InpStopLossPips;
double atrPips = atrBuffer[0] / (PointsPerPip() * symInfo.Point());
double adaptiveSL = atrPips * InpATRSLMultiplier;
// Clamp to 50%-200% of static input to prevent extremes
adaptiveSL = MathMax(adaptiveSL, InpStopLossPips * 0.5);
adaptiveSL = MathMin(adaptiveSL, InpStopLossPips * 2.0);
return adaptiveSL;
}
//+------------------------------------------------------------------+
//| Get Adaptive TP |
//+------------------------------------------------------------------+
double GetAdaptiveTP()
{
if(!InpUseATR || hATR_MTF == INVALID_HANDLE)
return (InpAdaptiveSLTP && learningInitialized && learning.totalTrades >= 20)
? learning.bestTPPips : InpTakeProfitPips;
double atrBuffer[];
ArraySetAsSeries(atrBuffer, true);
if(CopyBuffer(hATR_MTF, 0, 0, 1, atrBuffer) < 1)
return InpTakeProfitPips;
double atrPips = atrBuffer[0] / (PointsPerPip() * symInfo.Point());
double adaptiveTP = atrPips * InpATRTPMultiplier;
adaptiveTP = MathMax(adaptiveTP, InpTakeProfitPips * 0.5);
adaptiveTP = MathMin(adaptiveTP, InpTakeProfitPips * 2.0);
return adaptiveTP;
}
//+------------------------------------------------------------------+
//| Close All Positions |
//+------------------------------------------------------------------+
void CloseAllPositions(string reason)
{
TradeEventLogCritical("CLOSE ALL: " + reason);
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
if(!posInfo.SelectByIndex(i))
continue;
if(posInfo.Symbol() != tradingSymbol || posInfo.Magic() != InpMagicNumber)
continue;
trade.PositionClose(posInfo.Ticket());
}
ArrayResize(tradePairs, 0);
ArrayResize(orphanedLegs, 0);
ArrayResize(partialCloseTracker, 0);
}
//+------------------------------------------------------------------+
//| Update Daily PnL |
//+------------------------------------------------------------------+
void UpdateDailyPnL()
{
session.dailyPnL = accInfo.Balance() - session.dailyStartBalance + CalculateBasketProfit();
if(!session.dailyTargetReached &&
session.dailyPnL >= initialAccountBalance * InpDailyProfitTarget / 100.0)
{
session.dailyTargetReached = true;
TradeEventLogCritical("PROP: Daily target reached: " + DoubleToString(session.dailyPnL, 2));
}
}
//+------------------------------------------------------------------+
//| Is Daily Loss Limit Breached |
//+------------------------------------------------------------------+
bool IsDailyLossLimitBreached()
{
if(!InpPropFirmMode)
return false;
double limit = initialAccountBalance * InpMaxDailyLossPercent / 100.0;
double loss = MathAbs(MathMin(session.dailyPnL, 0));
return (loss >= limit * 0.80);
}
//+------------------------------------------------------------------+
//| Is Total Drawdown Breached |
//+------------------------------------------------------------------+
bool IsTotalDrawdownBreached()
{
if(!InpPropFirmMode)
return false;
double limit = initialAccountBalance * InpMaxTotalLossPercent / 100.0;
double dd = initialAccountBalance - accInfo.Equity();
return (dd >= limit * 0.80);
}
//+------------------------------------------------------------------+
//| Is Friday Close Time |
//+------------------------------------------------------------------+
bool IsFridayCloseTime()
{
MqlDateTime dt;
TimeToStruct(TimeCurrent(), dt);
return (dt.day_of_week == 5 && dt.hour >= InpFridayCloseHour);
}
//+------------------------------------------------------------------+
//| Points Per Pip |
//+------------------------------------------------------------------+
double PointsPerPip()
{
if(StringFind(tradingSymbol, "XAU") >= 0 || StringFind(tradingSymbol, "GOLD") >= 0)
return 100;
if(symInfo.Digits() == 3 || symInfo.Digits() == 5)
return 10;
return 1;
}
//+------------------------------------------------------------------+
//| Get Pip Value |
//+------------------------------------------------------------------+
double GetPipValue()
{
double tickValue = SymbolInfoDouble(tradingSymbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(tradingSymbol, SYMBOL_TRADE_TICK_SIZE);
double point = symInfo.Point();
if(tickSize <= 0 || point <= 0)
return 0;
return tickValue * PointsPerPip() * point / tickSize;
}
//+------------------------------------------------------------------+
//| Smart Log (Replaces Verbose/Trade Logs) |
//+------------------------------------------------------------------+
void SmartLog(string message, ENUM_LOG_LEVEL level = LOG_NORMAL)
{
if((int)level > (int)InpLogLevel) return;
// Dedup: skip if identical to any recent message
for(int i = 0; i < 10; i++)
if(_lastLogMessages[i] == message) return;
_lastLogMessages[_lastLogMsgIndex] = message;
_lastLogMsgIndex = (_lastLogMsgIndex + 1) % 10;
Print("[", EA_NAME, "] ", message);
}
void SmartLogThrottled(string message, datetime &lastTime, int intervalSec = 300, ENUM_LOG_LEVEL level = LOG_VERBOSE)
{
if((int)level > (int)InpLogLevel) return;
if(TimeCurrent() - lastTime < intervalSec) return;
lastTime = TimeCurrent();
SmartLog(message, level);
}
// Keep these for backward compatibility during refactor, map to SmartLog
void VerboseLog(string message) { SmartLog(message, LOG_VERBOSE); }
void TradeEventLog(string message) { SmartLog(message, LOG_NORMAL); }
void TradeEventLogCritical(string message) { SmartLog(message, LOG_CRITICAL); }
void VerboseLogThrottled(string message, datetime &lastTime, int intervalSec = 300) { SmartLogThrottled(message, lastTime, intervalSec, LOG_VERBOSE); }
//+------------------------------------------------------------------+
//| Trade Log Throttled - only logs once per interval to save disk |
//+------------------------------------------------------------------+
void TradeLogThrottled(string message, datetime &lastTime, int intervalSec = 300) { SmartLogThrottled(message, lastTime, intervalSec, LOG_NORMAL); }
//+------------------------------------------------------------------+
//| Phase 5: Dynamic Deviation (ATR-based slippage control) |
//+------------------------------------------------------------------+
void SetDynamicDeviation()
{
double atrBuffer[];
ArraySetAsSeries(atrBuffer, true);
double atrPoints = 200; // fallback
if(hATR_MTF != INVALID_HANDLE)
if(CopyBuffer(hATR_MTF, 0, 0, 1, atrBuffer) > 0)
atrPoints = atrBuffer[0] / symInfo.Point();
int deviation = (int)MathMax(30.0, MathMin(100.0, atrPoints * 0.1));
trade.SetDeviationInPoints(deviation);
}
//+------------------------------------------------------------------+
//| Phase 3: Momentum Exhaustion Detection |
//+------------------------------------------------------------------+
void UpdateMomentumExhaustion()
{
if(!InpEnableMomentumExhaustion)
{
momentumExhaustionScore = 0;
mesBlockBuy = false;
mesBlockSell = false;
return;
}
double score = 0.0;
bool bearishExhaustion = false;
bool bullishExhaustion = false;
//=== COMPONENT 1: ADX Slope Decay ===
double adxMain[], adxPlus[], adxMinus[];
ArraySetAsSeries(adxMain, true);
ArraySetAsSeries(adxPlus, true);
ArraySetAsSeries(adxMinus, true);
if(CopyBuffer(hADX_MTF, 0, 0, 4, adxMain) >= 4 &&
CopyBuffer(hADX_MTF, 1, 0, 4, adxPlus) >= 4 &&
CopyBuffer(hADX_MTF, 2, 0, 4, adxMinus) >= 4)
{
bool wasRising = adxMain[1] > adxMain[2];
bool nowFalling = adxMain[0] < adxMain[1];
if(wasRising && nowFalling && adxMain[0] > 20)
{
double decayMagnitude = (adxMain[1] - adxMain[0]) / adxMain[1];
score += MathMin(decayMagnitude * 4.0, 1.0) * 0.40;
if(adxPlus[0] > adxMinus[0]) bearishExhaustion = true;
else bullishExhaustion = true;
}
double diGap = MathAbs(adxPlus[0] - adxMinus[0]);
double prevDiGap = MathAbs(adxPlus[1] - adxMinus[1]);
if(prevDiGap > 0 && diGap < prevDiGap * 0.6)
{
score += 0.15;
if(adxPlus[0] > adxMinus[0]) bearishExhaustion = true;
else bullishExhaustion = true;
}
}
//=== COMPONENT 2: RSI Divergence ===
double rsi[], close[];
ArraySetAsSeries(rsi, true);
ArraySetAsSeries(close, true);
int divBars = InpRSIDivergenceBars;
if(hRSI_MTF != INVALID_HANDLE &&
CopyBuffer(hRSI_MTF, 0, 0, divBars + 1, rsi) >= divBars + 1 &&
CopyClose(tradingSymbol, InpMTFPeriod, 0, divBars + 1, close) >= divBars + 1)
{
double highestPrice = close[0], rsiAtHighest = rsi[0];
double lowestPrice = close[0], rsiAtLowest = rsi[0];
int highBar = 0, lowBar = 0;
for(int i = 1; i <= divBars; i++)
{
if(close[i] > highestPrice) { highestPrice = close[i]; rsiAtHighest = rsi[i]; highBar = i; }
if(close[i] < lowestPrice) { lowestPrice = close[i]; rsiAtLowest = rsi[i]; lowBar = i; }
}
if(highBar > 2 && close[0] >= highestPrice * 0.999)
{
if(rsi[0] < rsiAtHighest - 3.0)
{
double rsiDrop = (rsiAtHighest - rsi[0]) / rsiAtHighest;
score += MathMin(rsiDrop * 3.0, 1.0) * 0.35;
bearishExhaustion = true;
}
}
if(lowBar > 2 && close[0] <= lowestPrice * 1.001)
{
if(rsi[0] > rsiAtLowest + 3.0)
{
double rsiRise = (rsi[0] - rsiAtLowest) / (100.0 - rsiAtLowest);
score += MathMin(rsiRise * 3.0, 1.0) * 0.35;
bullishExhaustion = true;
}
}
}
//=== COMPONENT 3: EMA5 Slope Deceleration ===
double ema5[];
ArraySetAsSeries(ema5, true);
if(CopyBuffer(hEMA5, 0, 0, 4, ema5) >= 4)
{
double pipsPerPt = PointsPerPip() * symInfo.Point();
double slope_prev = (ema5[1] - ema5[2]) / pipsPerPt;
double slope_curr = (ema5[0] - ema5[1]) / pipsPerPt;
if(slope_prev > 1.0 && slope_curr < slope_prev * 0.5)
{
double decel = 1.0 - (slope_curr / slope_prev);
score += MathMin(decel, 1.0) * 0.25;
bearishExhaustion = true;
}
else if(slope_prev < -1.0 && slope_curr > slope_prev * 0.5)
{
double decel = 1.0 - (slope_curr / slope_prev);
score += MathMin(decel, 1.0) * 0.25;
bullishExhaustion = true;
}
}
momentumExhaustionScore = MathMax(0.0, MathMin(1.0, score));
mesBlockBuy = false;
mesBlockSell = false;
if(momentumExhaustionScore >= InpMESFullBlockThreshold)
{
mesBlockBuy = true;
mesBlockSell = true;
}
else if(momentumExhaustionScore >= InpMESBlockThreshold)
{
if(bearishExhaustion) mesBlockBuy = true;
if(bullishExhaustion) mesBlockSell = true;
}
}
//+------------------------------------------------------------------+
//| Get Session Win Rate |
//+------------------------------------------------------------------+
double GetSessionWinRate()
{
if(session.sessionTrades == 0)
return 0;
return (double)session.sessionWins / session.sessionTrades * 100.0;
}
//+------------------------------------------------------------------+
//| Get Session Action String |
//+------------------------------------------------------------------+
string GetSessionActionString(ENUM_SESSION_ACTION action)
{
switch(action)
{
case SESSION_NORMAL:
return "Normal";
case SESSION_CONSERVATIVE:
return "Conservative";
case SESSION_AGGRESSIVE:
return "Aggressive";
case SESSION_PAUSED:
return "PAUSED";
default:
return "Unknown";
}
}
//+------------------------------------------------------------------+
//| Update Chart Comment |
//+------------------------------------------------------------------+
void UpdateChartComment()
{
string nl = "\n";
string sep = "────────────────────────────" + nl;
string c = "";
c += " LILY EA v" + EA_VERSION + nl;
c += sep;
c += "Symbol: " + tradingSymbol + " | " + GetRegimeString(currentRegime) + nl;
c += "Session: " + GetSessionName(GetCurrentSession()) + nl;
c += "Volatility: " + DoubleToString(currentVolatilityPts, 0) + " pts" + nl;
c += "Spread: " + DoubleToString((double)symInfo.Spread(), 0) +
" (avg " + DoubleToString(averageSpread, 0) + ")" + nl;
c += "Signal: " + DoubleToString(signalScore, 3) +
" [" + SignalStrengthToString(signalStrength) + "]" + nl;
c += sep;
c += "Pairs: " + IntegerToString(GetCompletePairsCount()) + " complete, " +
IntegerToString(GetIncompletePairsCount()) + " incomplete" + nl;
c += "Orphans: " + IntegerToString(ArraySize(orphanedLegs)) + nl;
c += sep;
c += "Equity: " + DoubleToString(accInfo.Equity(), 2) +
" | Peak: " + DoubleToString(peakEquity, 2) + nl;
c += "Basket: " + DoubleToString(CalculateBasketProfit(), 2) + nl;
c += "Margin: " + DoubleToString(accInfo.FreeMargin(), 2) + nl;
c += sep;
if(InpPropFirmMode)
{
double dailyLimit = initialAccountBalance * InpMaxDailyLossPercent / 100.0;
double totalLimit = initialAccountBalance * InpMaxTotalLossPercent / 100.0;
c += "PROP | Daily: " + DoubleToString(session.dailyPnL, 2) +
" / -" + DoubleToString(dailyLimit, 2) + nl;
c += "PROP | Total DD: " + DoubleToString(initialAccountBalance - accInfo.Equity(), 2) +
" / -" + DoubleToString(totalLimit, 2) + nl;
c += "Target: " + (session.dailyTargetReached ? "REACHED" : "Pending") + nl;
c += sep;
}
c += "Session: W" + IntegerToString(session.sessionWins) +
"/L" + IntegerToString(session.sessionLosses) +
" (" + DoubleToString(GetSessionWinRate(), 1) + "%)" + nl;
c += "Mode: " + GetSessionActionString(session.currentAction) + nl;
c += sep;
//--- Signal Components Breakdown
c += "SIGNAL COMPONENTS:" + nl;
c += " EMA: " + DoubleToString(GetEMASignal(), 3) +
" | Regime: " + DoubleToString(GetRegimeSignal(), 3) +
" | RSI: " + DoubleToString(GetRSISignal(), 3) + nl;
c += " Session: " + DoubleToString(GetSessionQualitySignal(), 3);
if(InpEnableDXYCorrelation && dxyAvailable)
c += " | DXY: " + DoubleToString(GetDXYSignal(), 3);
else
c += " | DXY: N/A";
c += nl;
c += sep;
//--- Filter Status Dashboard
c += "FILTER STATUS:" + nl;
c += " Session: " + (IsSessionAllowed() ? "PASS" : "FAIL") +
" (" + GetSessionName(GetCurrentSession()) + ")" +
" | News: " + (newsFilterActive ? "BLOCK" : "CLEAR") +
" | Spread: " + (IsSpreadAcceptable() ? "PASS" : "FAIL") + nl;
c += " MC: " + (InpEnableMonteCarlo ? (mcResult.isViable ? "VIABLE" : "FAIL") : "OFF") +
" | Margin: " + (accInfo.FreeMargin() >= InpMinFreeMargin ? "PASS" : "FAIL") + nl;
c += sep;
if(InpEnableLearning && learningInitialized)
{
c += "ML: " + IntegerToString(learning.totalTrades) + " trades | " +
"WR=" + DoubleToString(learning.winRatio * 100, 1) + "% | " +
"PF=" + DoubleToString(learning.profitFactor, 2) + nl;
c += "Patterns: " + IntegerToString(patternDBSize) + nl;
}
if(InpEnableMonteCarlo && mcResult.avgReturn != 0)
{
c += "MC: " + (mcResult.isViable ? "VIABLE" : "NOT VIABLE") +
" | DD@95%=" + DoubleToString(mcResult.drawdownAtConfidence, 1) + "%" + nl;
}
c += sep;
//--- Phase 3: Momentum Exhaustion Score display
if(InpEnableMomentumExhaustion)
{
c += "MES: " + DoubleToString(momentumExhaustionScore * 100, 0) + "%" +
(mesBlockBuy ? " [BUY BLOCKED]" : "") +
(mesBlockSell ? " [SELL BLOCKED]" : "") + nl;
}
if(lastTradeBlockReason != "")
c += "TRADE: BLOCKED - " + lastTradeBlockReason + nl;
else
c += "TRADE: READY (waiting for signal >= MODERATE)" + nl;
if(inSLTradeDelay)
{
int rem = (int)(InpSLDelayMinutes * 60 - (TimeCurrent() - lastSLHitTime));
c += "SL Delay: " + IntegerToString(rem / 60) + "m " + IntegerToString(rem % 60) + "s" + nl;
}
if(InpEnableVolumeProfile && pocPrice > 0)
{
c += "VP: POC=" + DoubleToString(pocPrice, (int)symInfo.Digits()) +
" VAH=" + DoubleToString(vahPrice, (int)symInfo.Digits()) +
" VAL=" + DoubleToString(valPrice, (int)symInfo.Digits()) + nl;
}
if(InpEnableBreakout)
{
if(breakout.breakoutDetected)
c += "BREAKOUT: " + (breakout.breakoutBullish ? "BULL" : "BEAR") +
" x" + DoubleToString(breakout.breakoutStrength, 1) + nl;
else
if(breakout.isConsolidating)
c += "Consolidating..." + nl;
}
Comment(c);
}