//+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string BuildTelegramEntryMessage(const string symbol, const string side, double entry, double sl, double tp, int score, const string regime) { int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); if(digits <= 0) digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); if(digits <= 0) digits = Digits(); score = (int)MathMax(0, MathMin(10, score)); if(entry <= 0.0) entry = (StringCompare(side, "BUY") == 0) ? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID); entry = NormalizeDouble(entry, digits); sl = NormalizeDouble(sl, digits); tp = NormalizeDouble(tp, digits); string sideTag = (StringCompare(side, "BUY") == 0) ? "🟢 BUY " : "🔴 SELL "; string msg = "📢 KING GOLD SIGNAL\n"; msg += "✨ Hi, this is the King of Gold\n"; msg += sideTag + symbol + "\n"; msg += "⏰ Time: " + TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS) + "\n"; msg += "🎯 Entry: " + DoubleToString(entry, digits) + "\n"; msg += "🛑 SL: " + DoubleToString(sl, digits) + "\n"; msg += "🎯 TP: " + DoubleToString(tp, digits) + "\n"; msg += "📊 Score: " + IntegerToString(score) + "/10 | Regime: " + regime; return msg; } //+------------------------------------------------------------------+ // STATE PERSISTENCE HELPERS //+------------------------------------------------------------------+ string StateFileName() { return "kog_v31_" + _Symbol + "_state.csv"; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string SanitizeField(const string value) { string v = value; StringReplace(v, ";", " "); StringReplace(v, "\n", " "); StringReplace(v, "\r", " "); return v; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double ClampDouble(double v, double minVal, double maxVal) { if(v < minVal) return minVal; if(v > maxVal) return maxVal; return v; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double SafeNumber(const string s, double minVal, double maxVal, double fallback = 0.0) { double v = StringToDouble(s); // Guard against NaN/inf and absurd magnitudes that corrupt state files if(!MathIsValidNumber(v) || MathAbs(v) > (maxVal * 10.0)) return fallback; return ClampDouble(v, minVal, maxVal); } //+------------------------------------------------------------------+ //| King of Gold Scalper | //| MQL5 Expert Advisor | //+------------------------------------------------------------------+ #property copyright "King Gold Scalper (c) 2026, Tudor F. | support: FTUDOR505@GMAIL.COM | with love for traders | All rights reserved" #property link "FTUDOR505@GMAIL.COM +40765963611" #property version "9.0" #property strict #include #include #include "KogDecisionCore.mqh long sqlite_openW(string dbPathW); int sqlite_exec(long h, string sqlUtf8); int sqlite_last_error(long h, uchar &out[], int outSize); void sqlite_close(long h); int sqlite_pop_next(long h, string lockedBy, string symbolFilter, long magicFilter, long &outId, uchar &outCmd[], int outCmdSize, uchar &outPayload[], int outPayloadSize, uchar &outSymbol[], int outSymbolSize, long &outMagic); int sqlite_mark_done(long h, long id, string result); int sqlite_mark_failed(long h, long id, string error); int sqlite_reset_stuck(long h, int timeoutSec); #import //+------------------------------------------------------------------+ // INPUT PARAMETERS - CUSTOMIZE HERE //+------------------------------------------------------------------+ // MAGIC NUMBER input string _hdr00 = "=== MAGIC NUMBER ==="; input long MagicNumber = 5052025; // TRADING TIME SETTINGS input string _hdr01 = "=== TRADING TIME SETTINGS ==="; input int StartHour = 0; // Start trading hour (0-23) input int EndHour = 23; // End trading hour (0-23) input bool TradeMonday = true; // Trade on Monday input bool TradeTuesday = true; // Trade on Tuesday input bool TradeWednesday = true; // Trade on Wednesday input bool TradeThursday = true; // Trade on Thursday input bool TradeFriday = true; // Trade on Friday input bool TradeSaturday = true; // Trade on Saturday input bool TradeSunday = true; // Trade on Sunday // RISK MANAGEMENT SETTINGS input string _hdr02 = "=== RISK MANAGEMENT SETTINGS ==="; input double RiskPercent = 1.0; // Risk % per trade (0-10) input double DefaultRiskPercent = 1.0; // Default risk % if RiskPercent is set to 0 input double TakeProfit = 2000; // Take Profit distance (pips if InputsArePips=true, otherwise points) input double StopLoss = 1000; // Stop Loss distance (pips if InputsArePips=true, otherwise points) input bool InputsArePips = false; // When true distances (TP/SL/trailing/breakeven/spread) are in pips; when false they are raw broker points input int MaxOpenTrades = 9; // Maximum open positions (adjust for multiple orders) input int MaxTradesPerSymbolDirection = 5; // Max simultaneous trades per symbol and direction (BUY/SELL) // TRADING STRATEGY SETTINGS input string _hdr03 = "=== MaxSpread ==="; input int MaxSpread = 3500; // Maximum allowed spread (pips if InputsArePips=true, otherwise points) // PAIR & LOT SETTINGS input string _hdr04 = "=== PAIR & LOT SETTINGS ==="; input double LotSize = 0; // Fixed lot size (if 0, uses risk %) input int Slippage = 20; // Maximum slippage (pips if InputsArePips=true, otherwise points) // TIMEZONE SETTINGS input string _hdr05 = "=== TIMEZONE SETTINGS ==="; input bool UseBrokerTime = true; // Use broker server time (recommended). If false, uses local PC time input int TimezoneOffset = 0; // Timezone offset in hours (only if UseBrokerTime=false). 0 = use broker time directly // COOLDOWN SETTINGS (Revenge Trading Prevention) input string _hdr06 = "=== COOLDOWN SETTINGS ==="; input bool UseCooldown = false; // Enable cooldown after losing trade input int CooldownBars = 5; // Number of bars to wait after a loss // TRAILING STOP LOSS SETTINGS input string _hdr07 = "=== TRAILING STOP LOSS SETTINGS ==="; input bool UseTrailingStop = false; // Enable trailing stop loss input int TrailingStartPips = 50; // Activate trailing stop after X profit (same units as TP/SL) input int TrailingStopPips = 50; // Trail stop by X from highest/lowest price (same units as TP/SL) // BREAKEVEN STOP SETTINGS input string _hdr08 = "=== BREAKEVEN STOP SETTINGS ==="; input bool UseBreakevenStop = false; // Enable breakeven stop input int BreakevenTrigger = 20; // Move SL to breakeven after X profit (same units as TP/SL) // DYNAMIC SL/TP ADJUSTMENT SETTINGS input string _hdr09 = "=== DYNAMIC SL/TP ADJUSTMENT SETTINGS ==="; input bool UseDynamicSLTP = false; // Enable dynamic SL/TP adjustment based on market analysis input double MaxTPMultiplier = 1.5; // Maximum TP multiplier (2.0 = 2x original TP) input int MinSLProfitPips = 60; // Minimum SL in profit (points above/below entry) input double StrongMomentumThreshold = 1.3; // Volume ratio threshold for strong momentum input double StrongImbalanceThreshold = 1.0; // DOM imbalance threshold for strong signal input int SLTrailDistance = 30; // Distance to trail SL from current price when conditions are very favorable (points) // AUTO-CLOSE SETTINGS (Close all trades on target profit) input string _hdr10 = "=== AUTO-CLOSE SETTINGS(Close all trades on target profit) ==="; input bool UseAutoClose = true; // Enable auto-close when target profit reached input double AutoCloseProfit = 10; // Close all trades when profit reaches X USD input bool UseAutoCloseTrailing = true; // Enable trailing close - continue profiting after target input double AutoCloseTrailingStart = 10; // Activate trailing close after X USD profit input double AutoCloseTrailingStop = 1; // Trail close by X USD from highest profit input bool AutoCloseResumeTrading = true; // Continue trading after auto-close triggers (same session) // RECOVERY AUTO-CLOSE (DD-based fast positive close) input string _hdr11 = "=== RECOVERY AUTO-CLOSE (DD-based fast positive close) ==="; input bool UseRecoveryAutoClose = true; // Enable DD-based recovery mode input double RecoveryDDStart = 9.0; // Enter recovery mode when DD >= this % input double RecoveryDDEnd = 4.0; // Exit recovery mode when DD <= this % input double RecoveryMinProfitUsd = 8.0; // In recovery: close all when total profit >= this small positive value input bool RecoveryUseAutoCloseTrailing = true; // In recovery: use trailing close instead of direct close input double RecoveryAutoCloseTrailingStart = 5.0; // Recovery trailing start (USD) input double RecoveryAutoCloseTrailingStop = 1.0; // Recovery trailing stop (USD) input double RecoveryRiskMultiplier = 1.5; // Risk multiplier applied in recovery mode // SAFE MODE SETTINGS (auto-activate under low balance) input string _hdr12 = "=== SAFE MODE SETTINGS ==="; input bool UseSafeBalanceMode = false; // Enable safe mode when balance is low input double SafeBalanceThreshold = 100; // Balance threshold to enter safe mode input double SafeRiskFactor = 0.5; // Multiply risk by this in safe mode (e.g., 0.5 = half risk) input int SafeMaxOpenTrades = 3; // Limit max open trades in safe mode input double SafeVolumeEntryMultiplier = 1.0; // Volume ratio multiplier for entries in safe mode // CONSECUTIVE BARS SETTINGS input string _hdr13 = "=== CONSECUTIVE BARS SETTINGS(TickBarSize activate for signals on tick) ==="; input bool UseConsecutiveBars = true; // Enable consecutive bar detection input bool AllowConsecutiveSell = true; // Allow SELL when consecutive-bars signal matches current mode input bool AllowConsecutiveBuy = true; // Allow BUY when consecutive-bars signal matches current mode input int BullishBarsForSell = 5; // Bullish streak threshold (reversal SELL / trend BUY) input int BearishBarsForBuy = 3; // Bearish streak threshold (reversal BUY / trend SELL) input bool UseMomentumExhaustionSignals = true; // true=reversal (bullish->SELL, bearish->BUY), false=trend-following input bool UseTickBarSignals = false; // If true, build synthetic tick-bars for signals instead of timeframe bars input int TickBarSize = 50; // Number of ticks per synthetic bar (for tick-bar signals) // VOLUME FILTER SETTINGS input string _hdr14 = "=== VOLUME FILTER SETTINGS ==="; input bool UseVolumeFilter = true; // Enable volume filter - blocks trades if volume too low input bool UseTickVolume = true; // Use tick volume (works in tester) instead of real volume input double MinVolumeRatio = 0.9; // Minimum volume ratio vs average (0.8 = 80% of average volume) // ANTI-EXTREME ENTRY FILTER input string _hdr14b = "=== ANTI-EXTREME FILTER SETTINGS ==="; input bool UseAntiExtremeFilter = false; // Block BUY near recent highs and SELL near recent lows input int AntiExtremeLookbackBars = 60; // Lookback bars used to detect recent highs/lows input double AntiExtremeMinDistance = 600; // Minimum distance from extreme (same units as InputsArePips) // VOLUME-BASED SIGNAL STRENGTH input string _hdr15 = "=== VOLUME SIGNAL SETTINGS(Use tick volume to validate bullish/bearish bars) ==="; input bool UseVolumeInSignals = true; // Use tick volume to validate bullish/bearish bars input int VolumeSignalLookback = 1000; // Number of bars used to calculate average tick volume input double VolumeSignalFactor = 0.5; // Current volume >= Factor * average volume => "strong" bar // ORDER BOOK (DOM) SETTINGS input string _hdr16 = "=== DOM (ORDER BOOK) SETTINGS(If true: DOM provides positive signal ,doesn't block trades) ==="; input bool UseDOMFilter = true; // Enable Order Book (DOM) filter input bool ForceDisableDOM = true; // Hard bypass DOM even if UseDOMFilter=true input bool DOMAsSignal = true; // If true: DOM provides positive signal (doesn't block trades). If false: DOM blocks trades that don't meet criteria input bool AutoDetectECN = true; // Auto-detect ECN broker (IC Markets, etc.) - DOM only works on ECN brokers input double MinImbalanceRatio = 1.0; // Minimum imbalance ratio (BID/ASK or ASK/BID) to confirm direction input double MinLiquidity = 1.0; // Minimum total liquidity (lots) required to trade input int DOMLevels = 10; // Number of DOM levels to analyze (each side) // SMART ENTRY & LIQUIDITY ZONES SETTINGS input string _hdr17 = "=== SMART ENTRY & LIQUIDITY ZONES(adjust entry price towards liquidity zones) ==="; input bool UseSmartEntry = true; // Enable smart entry - adjust entry price towards liquidity zones (Volume Profile works in tester; DOM does not) input bool UseVolumeProfile = true; // Enable volume profile - identify price zones with high volume (WORKS IN TESTER) input int VolumeProfileBars = 1000; // Number of bars to analyze for volume profile input double LiquidityZoneDistance = 2000; // Max distance to adjust entry towards liquidity zone (pips if InputsArePips=true, otherwise points) input bool UseLimitOrders = true; // Use limit orders when smart entry is enabled (if false, uses market orders at adjusted price) input bool UseLimitOrderRenewal = true; // Renew pending limit orders when volume momentum weakens input int LimitOrderMaxAgeMinutes = 60; // Close limit orders older than this (minutes) when renewal triggers input int LimitOrderRenewalCooldownSeconds = 30; // Minimum seconds between renewal checks // SENTIMENT & ADAPTIVE SETTINGS input string _hdr18 = "=== SENTIMENT & ADAPTIVE SETTINGS(algo brain,increase MinWinRatePercent to be more conservative) ==="; input bool UsesentimentTracking = true; // Enable sentiment/emoji feedback on wins/losses input bool ShowBalanceChanges = true; // Show balance changes after each trade input bool UseAdaptiveTrading = true; // Enable adaptive trading based on sentiment/performance input double AggressiveMultiplier = 1.5; // Increase lot size by X% on winning streak input double ConservativeMultiplier = 1.0; // Reduce lot size by X% on losing streak input double MinWinRatePercent = 65.0; // Minimum win rate % to be considered "winning" mode input bool UseAdaptiveStopLoss = false; // Adjust SL/TP based on performance input bool UseSignalStrengthGreed = true; // Enable GREEDY/MODERATE/CONSERVATIVE based on signal strength input bool ForceConservativeGreedOnSmallAccount = false; // If true, force CONSERVATIVE greed on small-account protection // HEARTBEAT SETTINGS input string _hdr19 = "=== HEARTBEAT SETTINGS ==="; input bool UseHeartbeat = true; // Periodic alive/status log input int HeartbeatIntervalSeconds = 60; // Seconds between heartbeat logs input bool HeartbeatToJournal = true; // Print heartbeat in Journal input int SignalReportIntervalSeconds = 60; // Signal analysis report interval (seconds) input bool UseLearningReport = true; // Periodic learning/reward telemetry in Journal input int LearningReportIntervalSeconds = 60; // Learning telemetry interval (seconds) // MEMORY DECISION ENGINE SETTINGS input string _hdr20 = "=== MEMORY DECISION ENGINE ==="; input int DecisionScoreThresholdNormal = 5; // Normal mode entry threshold (1..10) input int DecisionScoreThresholdStress = 6; // Stress mode entry threshold (1..10) input bool UseControlledExplore = true; // Allow controlled exploration after losing streak input bool AllowExploreOnSmallAccounts = true; // If true, allow explore even when small-account protection is active input int ExploreLossStreakTrigger = 2; // Start exploration after this many consecutive losses input int ExploreOneInN = 5; // Allow 1 in N below-threshold signals input int ExploreScoreBuffer = 4; // Allow explore for scores in [threshold-buffer, threshold-1] input double ExploreLotMultiplier = 1.2; // Lot multiplier when explore entry is allowed (kept <= 1.0 at runtime) input int MarketStateUpdateSeconds = 1; // Market-state refresh interval (seconds) for faster reaction input double RewardStep = 0.06; // Reward increment for good decisions input double PenaltyStep = 0.08; // Penalty decrement for bad decisions input double SetupRewardClampMin = -0.5; // Minimum setup reward score input double SetupRewardClampMax = 0.5; // Maximum setup reward score input bool UseSmartLimitRewardBoost = true; // Extra reward shaping for limit fills (applied only at close outcome) input double SmartLimitRewardWinBonus = 0.20; // Added to reward hint when SMART_LIMIT closes positive input double SmartLimitRewardLossPenalty = 0.10; // Subtracted from reward hint when SMART_LIMIT closes negative input bool LogDecisionLearningDetails = true; // Emit detailed score->fill->learn linkage logs input bool UseEntryProfileTP_SL = true; // Adapt initial TP/SL by entry profile input int LongTermScoreThreshold = 7; // Score threshold for LONG_TERM profile input int ShortTermScoreThreshold = 5; // Score threshold for SHORT_TERM profile input double ShortTermTPMultiplier = 1.8; // Initial TP multiplier for SHORT_TERM input double ShortTermSLMultiplier = 1.8; // Initial SL multiplier for SHORT_TERM input double LongTermTPMultiplier = 1.5; // Initial TP multiplier for LONG_TERM input double LongTermSLMultiplier = 1.5; // Initial SL multiplier for LONG_TERM // DECISION DLL SETTINGS (Contextual Bandit) input string _hdr21 = "=== DECISION DLL (Bandit) ==="; // INFO: external decision core integration input bool UseDecisionCoreDll = true; // Enable external decision engine DLL input bool PreferConsecutiveBarsForDll = true; // If true, feed DLL trend_strength from consecutive-bars signal, not price trend slope input string DecisionCoreDbPath = "C:\\sqlite_cmdq\\kog_decision.db"; // Runtime storage path (future-proof) input int DecisionCoreSeed = 5052026; // Seed for deterministic runs input int DecisionDllSnapshotIntervalSeconds = 60; // Telemetry snapshot interval (seconds) input double DllExploreProbThreshold = 0.30; // Min DLL p_allow to trigger explore on BLOCK // WARMUP SETTINGS input string _hdr22 = "=== WARMUP SETTINGS ==="; input bool UseWarmupOnInit = true; // Build initial context from history on attach input int WarmupBars = 10000; // Closed bars to scan on init (now extended for weekly context) input int WarmupHardCap = 20000; // Safety upper cap for warmup scanning input int PatternMemoryBars = 10000; // Bars retained in in-memory buffers per timeframe input int PatternBars = 200; // Bars used for candle pattern extraction input int VolWindowShort = 100; // Short volatility window (approx. multi-day on intraday TF) input int VolWindowLong = 200; // Long volatility window (approx. 1+ week on intraday TF) input int TrendWindowBars = 1000; // Trend window (bars) for price slope baseline input bool WarmupAllMarketWatchSymbols = true; // Also warm up all Market Watch symbols for multi-symbol scanner // SCANNER SETTINGS input string _hdr23 = "=== SCANNER SETTINGS ==="; input int MaxScannedOpportunities = 20; // Number of top symbols used by scanner/trade loop (1..20) input bool ScanForex = true; // Scan and trade Forex/Metals symbols input bool ScanStocks = true; // Scan and trade Stocks symbols input bool ScanIndices = false; // Scan and trade Indices symbols input bool ScanCrypto = false; // Scan and trade Crypto symbols // FAST SPIKE REACTION SETTINGS input string _hdr24 = "=== FAST SPIKE REACTION ==="; input bool UseFastSpikeReaction = true; // Autonomous spike reaction (ms-based, memory-governed) input int MaxNewsSpreadPoints = 1200; // Max spread (broker points) allowed for news-spike entries input int FastSpikeAccelerationMs = 150; // Optional max ms from anchor open to spike detect (0=disabled) input int NewsConfirmMs = 60; // Pending confirm window for news spike (ms) input int FastSpikeConfirmMinMs = 120; // Minimum ms before confirming pending spike input int FastSpikeHoldTicks = 0; // Consecutive hold ticks required beyond break level input double FastSpikeConfirmThresholdFactor = 0.4; // Break level factor vs spike threshold (0.6..1.5) // ALERTS (SOUND/NEWS/FUTURE EVENTS) input string _hdr25 = "=== ALERTS (SOUND + NEWS + FUTURE EVENTS) ==="; input bool EnableNewsEventAlerts = true; // Enable economic calendar alerts input int NewsScanIntervalSeconds = 60; // Calendar scan interval input int NewsLookaheadMinutes = 30; // Alert for events within next X minutes input int NewsAlertCooldownSeconds = 60; // Cooldown between news alerts input bool NewsAlertAllCurrencies = true; // If false: only symbol currencies input bool EnableFutureSessionAlerts = true; // Alert before session start/end input int FutureEventLeadMinutes = 5; // Minutes before start/end to alert input bool EnableTelegramAlerts = true; // Enable Telegram notifications via Bot API input string TelegramBotToken = ""; // Telegram bot token from BotFather input string TelegramChatId = ""; // Telegram chat_id (user/group/channel) input int TelegramCooldownSeconds = 15; // Cooldown between same Telegram event messages input bool TelegramSendEntries = true; // Send BUY/SELL execution alerts to Telegram input bool TelegramSendCloses = true; // Send close-profit/loss alerts to Telegram input bool TelegramSendRisk = true; // Send risk/stress alerts to Telegram input bool TelegramSendNews = true; // Send economic news alerts to Telegram input bool TelegramSendFutureEvents = true; // Send future session event alerts to Telegram input bool TelegramSendBlocked = true; // Send blocked-order alerts to Telegram input bool TelegramSendSpikes = true; // Send fast-spike detection alerts to Telegram // Voice prompts (Zira TTS - no .wav needed) input bool EnableVoicePrompts = true; // Use Zira voice prompts input int VoiceCooldownSeconds = 15; // Min seconds between same voice event input bool VoiceSendSpikes = true; // Voice alert when a fast spike is detected // BEST SESSIONS ENGINE (London/New York analytics) input string _hdr26 = "=== BEST SESSIONS ENGINE ==="; input bool UseBestSessionsEngine = true; // Learn and report session quality (London/NY/Overlap) input bool UseBestSessionFilter = false; // Block new entries when current session is weak input int BestSessionMinTrades = 5; // Minimum trades before a session is considered reliable input double BestSessionMinWinRate = 55.0; // Session must keep at least this win rate to be optimal input int LondonSessionStartHour = 11; // London session start hour (broker time) input int LondonSessionEndHour = 16; // London session end hour (broker time) input int NewYorkSessionStartHour = 16; // New York session start hour (broker time) input int NewYorkSessionEndHour = 22; // New York session end hour (broker time) input int BestSessionReportIntervalSeconds = 600; // Periodic best-session report interval // STATE PERSISTENCE (file-based) input string _hdr27 = "=== STATE PERSISTENCE (file-based) ==="; input bool UseFilePersistence = true; // Save/load EA memory to file input int StateAutosaveSeconds = 60; // Autosave interval (seconds). 0 = disable autosave input int MaxSavedTrades = 2000; // Limit trades persisted input int MaxSavedRegimes = 5000; // Limit regime history persisted input bool ResetStateFile = false; // If true, deletes state file on init //+------------------------------------------------------------------+ // CONSTANTS - Improve performance and code readability //+------------------------------------------------------------------+ #define ACCOUNT_UPDATE_INTERVAL 5 // seconds between account data updates #define INVALID_PRICE 9999526456.42 #define MAX_DRAWDOWN_PERCENT 20.0 #define POSITION_UPDATE_INTERVAL 3 // seconds between position data updates #define MAX_SCANNED_OPPORTUNITIES 20 //+------------------------------------------------------------------+ // DATA CACHE STRUCTURES - Reduce API calls and improve performance //+------------------------------------------------------------------+ struct AccountCache { datetime lastUpdate; double balance; double equity; double margin; double freeMargin; double marginLevel; void update() { lastUpdate = TimeCurrent(); balance = AccountInfoDouble(ACCOUNT_BALANCE); equity = AccountInfoDouble(ACCOUNT_EQUITY); margin = AccountInfoDouble(ACCOUNT_MARGIN); freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); marginLevel = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); } bool isExpired() { return (TimeCurrent() - lastUpdate) > ACCOUNT_UPDATE_INTERVAL; } bool needsUpdate() { return (lastUpdate == 0 || isExpired()); } }; struct MarketDataCache { datetime lastUpdate; double bid; double ask; double point; double tickSize; double tickValue; double minLot; double maxLot; double lotStep; void update() { lastUpdate = TimeCurrent(); bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE); minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX); lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); } bool isExpired() { return (TimeCurrent() - lastUpdate) > 1; // Update market data every second } bool needsUpdate() { return (lastUpdate == 0 || isExpired()); } }; //+------------------------------------------------------------------+ // MEMORY SYSTEM - Learn from past and adapt future decisions //+------------------------------------------------------------------+ struct TradeMemory { string symbol; datetime entryTime; int direction; // 1 = BUY, -1 = SELL double entryPrice; double exitPrice; double profit; int consecutiveBars; double volumeRatio; double spreadAtEntry; int hourOfDay; int dayOfWeek; bool wasWinner; double maxProfit; double maxDrawdown; int holdDuration; // minutes string exitReason; }; struct PatternMemory { string symbol; int patternType; // 1=bullish_exhaustion, 2=bearish_exhaustion, 3=volume_spike, etc. int consecutiveBars; double volumeRatio; double avgProfit; double totalProfit; int totalTrades; int winningTrades; double winRate; datetime lastUpdated; double confidence; }; struct TimeBasedMemory { int hourOfDay; // 0-23 int dayOfWeek; // 0-6 (Sunday=0) int totalTrades; int winningTrades; double totalProfit; double avgProfit; double winRate; double avgSpread; double avgVolume; bool isOptimalHour; }; struct SessionBasedMemory { string name; int totalTrades; int winningTrades; double totalProfit; double avgProfit; double winRate; bool isOptimal; }; struct MarketRegimeMemory { string regime; // "TRENDING", "RANGING", "VOLATILE", "QUIET" datetime detected; double confidence; int duration; // minutes double avgVolatility; double avgSpread; int successfulTrades; int totalTrades; double performance; }; struct SetupBucketMemory { int direction; // 1 = BUY, -1 = SELL int barsBucket; // 0..10 int volumeBucket; // 0..4 int entryType; // 0 = market, 1 = smart_limit string regime; // regime at entry int totalTrades; int winningTrades; double totalProfit; double avgProfit; double rewardScore; // learned setup score adjustment }; // Memory arrays with dynamic sizing TradeMemory tradeHistory[]; PatternMemory patternHistory[]; TimeBasedMemory timeMemory[24][7]; // 24 hours x 7 days SessionBasedMemory sessionMemory[4]; // 0=ASIA/OTHER, 1=LONDON, 2=NEW_YORK, 3=OVERLAP MarketRegimeMemory regimeHistory[]; SetupBucketMemory setupMemory[]; // Current market state tracking struct CurrentMarketState { double currentVolatility; double avgVolatility; double volumeTrend; double priceTrend; double spreadTrend; string currentRegime; double regimeConfidence; datetime regimeStart; int consecutiveWins; int consecutiveLosses; double recentPerformance; bool marketStress; // KogDecisionCore equity tracking fields double initialBalance; double currentEquity; double maxEquity; double equityDrawdownPercent; double equityToBalanceRatio; // KogDecisionCore streak tracking fields int currentStreakDirection; // -1, 0, 1 int currentStreakLength; datetime lastTradeTime; }; CurrentMarketState marketState; //+------------------------------------------------------------------+ //| KOGDECISIONCORE EQUITY TRACKING FUNCTIONS //+------------------------------------------------------------------+ void UpdateEquityMetrics() { double equity = AccountInfoDouble(ACCOUNT_EQUITY); double balance = AccountInfoDouble(ACCOUNT_BALANCE); marketState.currentEquity = equity; // Initialize maxEquity on first run if(marketState.maxEquity == 0.0) { marketState.maxEquity = equity; marketState.initialBalance = balance; } if(equity > marketState.maxEquity) { marketState.maxEquity = equity; } if(marketState.maxEquity > 0) { marketState.equityDrawdownPercent = ((marketState.maxEquity - equity) / marketState.maxEquity) * 100.0; } if(balance > 0) { marketState.equityToBalanceRatio = equity / balance; } } //+------------------------------------------------------------------+ //| KOGDECISIONCORE STREAK TRACKING FUNCTIONS //+------------------------------------------------------------------+ void UpdateStreakMetrics(int wasWin) { datetime now = TimeCurrent(); if(wasWin > 0) // Win { if(marketState.currentStreakDirection >= 0) { marketState.currentStreakDirection = 1; marketState.currentStreakLength++; } else { marketState.currentStreakDirection = 1; marketState.currentStreakLength = 1; } } else if(wasWin < 0) // Loss { if(marketState.currentStreakDirection <= 0) { marketState.currentStreakDirection = -1; marketState.currentStreakLength++; } else { marketState.currentStreakDirection = -1; marketState.currentStreakLength = 1; } } marketState.lastTradeTime = now; } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ // ENHANCED MEMORY SYSTEM - Multi-Symbol + Multi-Timeframe //+------------------------------------------------------------------+ struct TimeframeAnalysis { int timeframe; // M1, M5, M15, H1, H4 double trendStrength; // -10 la +10 double volatility; double volume; double volumeProfile[100]; double supportLevels[5]; double resistanceLevels[5]; int bullishCandles; int bearishCandles; double trendScore; // 0-10 datetime lastUpdate; double liquidityZone; }; struct EnhancedSymbolMemory { string symbol; int totalTrades; int winningTrades; double totalProfit; double winRate; double avgVolatility; double avgSpread; int trendScore; // -10 la +10 double liquidityScore; bool isTradable; datetime lastAnalysis; // Pattern tracking int totalPatterns; int successfulPatterns; double patternAccuracy; // Timeframe performance double tfPerformance[5]; // performance per TF int tfTrades[5]; // trades per TF }; struct SymbolOpportunity { string symbol; double score; // 0-10 string trendDescription; string entryTimeframe; double currentPrice; double volatility; double spread; bool isValid; }; struct CandleAnalysis { int timeframe; double open; double high; double low; double close; double volume; bool isBullish; bool isBearish; double bodySize; // body size in points double wickRatio; // upper/lower wick ratio double volumeRatio; // volume vs average int consecutiveBullish; // consecutive bullish candles int consecutiveBearish; // consecutive bearish candles double strength; // signal strength 0-10 string signalType; // "STRONG_BULLISH", "BULLISH", "NEUTRAL", "BEARISH", "STRONG_BEARISH" }; struct CandlePattern { string patternName; // Pattern id (seq hash or label) int patternType; // 1=BULLISH, 2=BEARISH, 3=NEUTRAL double reliability; // 0-100% reliability int consecutiveBars; // bars in pattern double volumeConfirmation; // volume factor bool isValid; }; struct BullishBearishSignals { CandleAnalysis candleData[5][50]; // 5 timeframes x 50 candles each string currentSignal[5]; // current signal per TF double signalStrength[5]; // signal strength per TF int bullishConfluence; // how many TF show bullish int bearishConfluence; // how many TF show bearish double overallStrength; // overall signal strength void analyzeAllTimeframes(string symbol); void detectCandlePatterns(string symbol, int timeframe); void calculateSignalStrength(string symbol, int timeframe); string getOverallSignal(string symbol); bool validateBullishEntry(string symbol); bool validateBearishEntry(string symbol); }; struct PatternRecognizer { CandlePattern detectedPatterns[40]; int patternCount; void analyzeAllPatterns(string symbol, int timeframe); double getPatternReliability(string patternName); }; struct MultiSymbolScanner { SymbolOpportunity opportunities[]; int opportunityCount; string topSymbols[MAX_SCANNED_OPPORTUNITIES]; double topScores[MAX_SCANNED_OPPORTUNITIES]; datetime lastScan; void scanAllSymbols(); void calculateSymbolScores(); double calculateSymbolScore(string symbol); void rankOpportunities(); void updateTopSymbols(); string getTopOpportunities(); }; struct EnhancedMultiTFAnalyzer { TimeframeAnalysis tfData[5]; BullishBearishSignals bbSignals; PatternRecognizer patterns; double tfLiquidityLevel[5]; double tfLiquidityDistance[5]; bool tfLiquidityHasData[5]; // Signal confluence int bullishTimeframes; // count of TF with bullish signals int bearishTimeframes; // count of TF with bearish signals double overallBullishStrength; double overallBearishStrength; // Entry validation bool bullishEntryConfirmed; bool bearishEntryConfirmed; string dominantSignal; // "BULLISH" or "BEARISH" double signalConfidence; // 0-100% void analyzeSymbolWithSignals(string symbol); void validateSignalConfluence(string symbol); void checkPatternConfirmation(string symbol); bool shouldEnterLong(string symbol); bool shouldEnterShort(string symbol); string generateSignalReport(string symbol); }; struct EnhancedSmartEntry { string symbol; double optimalEntryPrice; double dynamicTP; double dynamicSL; double liquidityZoneLow; double liquidityZoneHigh; double confidenceLevel; double riskPercent; string entryReason; void analyzeMultiTimeframe(string symbol); void calculateLiquidityZones(string symbol); void determineOptimalEntry(string symbol); void calculateDynamicTPSL(string symbol); double calculateConfidence(string symbol); void generateEntryReport(string symbol); }; // Global arrays and instances EnhancedSymbolMemory enhancedSymbolMemory[]; TimeframeAnalysis globalTFData[]; string trackedSymbols[]; int totalTrackedSymbols = 0; int totalLearnedPatterns = 0; MultiSymbolScanner scanner; EnhancedMultiTFAnalyzer enhancedTFAnalyzer; PatternRecognizer patternRecognizer; BullishBearishSignals bbSignals; EnhancedSmartEntry smartEntry; // Timeframe constants int TIMEFRAMES[5] = {PERIOD_M1, PERIOD_M5, PERIOD_M15, PERIOD_H1, PERIOD_H4}; string TF_NAMES[5] = {"M1", "M5", "M15", "H1", "H4"}; struct PositionEntryContext { ulong positionId; int direction; // 1 = BUY, -1 = SELL datetime entryTime; double entryPrice; double liquidityLevel; // price level used/nearest at entry double liquidityDistancePts; // distance in points from entry price bool liquidityUsed; int consecutiveBars; double volumeRatio; double spreadAtEntry; double accumulatedProfit; // Sum of close deals for this position int closeDealCount; // Number of close deals accumulated int decisionScoreAtEntry; // 1..10 decision score at entry int decisionActionAtEntry; // 0=BLOCK,1=ALLOW,2=EXPLORE_SMALL int decisionSetupIdAtEntry; // setup_id from DLL scoring context int entryTypeAtEntry; // 0=market, 1=smart_limit fill ulong sourceOrderTicketAtEntry; // Order ticket that generated DEAL_ENTRY_IN int sourceOrderTypeAtEntry; // ORDER_TYPE_* for source order int barsBucket; // setup bucket: consecutive bars int volumeBucket; // setup bucket: volume regime int entryTimeframe; // timeframe chosen at entry string regimeAtEntry; // setup bucket: market regime string patternHash; // dynamic pattern ID at entry }; PositionEntryContext positionEntryMemory[]; // Pending decision action cache (symbol -> last ALLOW/EXPLORE action) string pendingDecisionSymbols[]; int pendingDecisionActions[]; int pendingDecisionSetupIds[]; //+------------------------------------------------------------------+ // MEMORY MANAGEMENT FUNCTIONS - Learning and Adaptation Engine //+------------------------------------------------------------------+ void InitializeMemorySystem() { // Initialize time-based memory grid for(int hour = 0; hour < 24; hour++) { for(int day = 0; day < 7; day++) { timeMemory[hour][day].hourOfDay = hour; timeMemory[hour][day].dayOfWeek = day; timeMemory[hour][day].totalTrades = 0; timeMemory[hour][day].winningTrades = 0; timeMemory[hour][day].totalProfit = 0; timeMemory[hour][day].avgProfit = 0; timeMemory[hour][day].winRate = 0; timeMemory[hour][day].isOptimalHour = false; } } // Initialize session memory buckets sessionMemory[0].name = "ASIA_OR_OTHER"; sessionMemory[1].name = "LONDON"; sessionMemory[2].name = "NEW_YORK"; sessionMemory[3].name = "OVERLAP"; for(int s = 0; s < 4; s++) { sessionMemory[s].totalTrades = 0; sessionMemory[s].winningTrades = 0; sessionMemory[s].totalProfit = 0.0; sessionMemory[s].avgProfit = 0.0; sessionMemory[s].winRate = 0.0; sessionMemory[s].isOptimal = false; } // Initialize market state marketState.currentRegime = "UNKNOWN"; marketState.regimeConfidence = 0.0; marketState.regimeStart = TimeCurrent(); marketState.consecutiveWins = 0; marketState.consecutiveLosses = 0; marketState.recentPerformance = 0.0; marketState.marketStress = false; // Initialize enhanced memory system InitializeEnhancedMemorySystem(); Print("Memory system initialized - Ready to learn and adapt!"); } //+------------------------------------------------------------------+ //| Enhanced Memory System Initialization //+------------------------------------------------------------------+ void InitializeEnhancedMemorySystem() { // 1. Scan all available symbols int symbolCount = SymbolsTotal(true); ArrayResize(enhancedSymbolMemory, symbolCount); ArrayResize(trackedSymbols, symbolCount); for(int i = 0; i < symbolCount; i++) { string sym = SymbolName(i, true); enhancedSymbolMemory[i].symbol = sym; enhancedSymbolMemory[i].totalTrades = 0; enhancedSymbolMemory[i].winningTrades = 0; enhancedSymbolMemory[i].totalProfit = 0; enhancedSymbolMemory[i].winRate = 0; enhancedSymbolMemory[i].trendScore = 0; enhancedSymbolMemory[i].isTradable = true; enhancedSymbolMemory[i].totalPatterns = 0; enhancedSymbolMemory[i].successfulPatterns = 0; enhancedSymbolMemory[i].avgVolatility = 0; enhancedSymbolMemory[i].avgSpread = 0; enhancedSymbolMemory[i].liquidityScore = 0; // Initialize timeframe performance for(int j = 0; j < 5; j++) { enhancedSymbolMemory[i].tfPerformance[j] = 0; enhancedSymbolMemory[i].tfTrades[j] = 0; } trackedSymbols[i] = sym; } totalTrackedSymbols = symbolCount; // 2. Initialize global timeframe data ArrayResize(globalTFData, 5); for(int i = 0; i < 5; i++) { globalTFData[i].timeframe = TIMEFRAMES[i]; globalTFData[i].trendStrength = 0; globalTFData[i].volatility = 0; globalTFData[i].volume = 0; globalTFData[i].trendScore = 0; globalTFData[i].bullishCandles = 0; globalTFData[i].bearishCandles = 0; globalTFData[i].lastUpdate = 0; globalTFData[i].liquidityZone = 0; ArrayInitialize(globalTFData[i].volumeProfile, 0); ArrayInitialize(globalTFData[i].supportLevels, 0); ArrayInitialize(globalTFData[i].resistanceLevels, 0); } // 3. Initialize scanner scanner.scanAllSymbols(); // 4. Initialize enhanced analyzer enhancedTFAnalyzer.bbSignals = bbSignals; enhancedTFAnalyzer.patterns = patternRecognizer; Print("Enhanced Memory System initialized"); Print("Symbol Memory: ", totalTrackedSymbols, " symbols tracked"); Print("Timeframe Data: 5 TF per symbol"); Print("Scanner ready - analyzing opportunities..."); } //+------------------------------------------------------------------+ //| KOGDECISIONCORE MTF CONFLUENCE CALCULATION //+------------------------------------------------------------------+ double CalculateMTFConfluence(string symbol, ENUM_TIMEFRAMES currentTF) { double confluence = 0.0; int validSignals = 0; ENUM_TIMEFRAMES timeframes[] = {PERIOD_M1, PERIOD_M5, PERIOD_M15, PERIOD_H1, PERIOD_H4}; for(int i = 0; i < 5; i++) { ENUM_TIMEFRAMES tf = timeframes[i]; if(tf == currentTF) continue; double trend = calculateTrendStrength(symbol, tf); double avgVolume = calculateAverageVolume(symbol, tf, 20); long currentVolume = UseTickVolume ? iTickVolume(symbol, tf, 0) : iVolume(symbol, tf, 0); double volume = (avgVolume > 0.0) ? ((double)currentVolume / avgVolume) : 1.0; if(trend > 2.0 && volume > 1.2) { confluence += 1.0; validSignals++; } } return (validSignals > 0) ? (confluence / validSignals) : 0.0; } //+------------------------------------------------------------------+ //| KOGDECISIONCORE SESSION QUALITY CALCULATION //+------------------------------------------------------------------+ double CalculateSessionQuality(int hour, int day) { // Use existing timeMemory array if(hour < 0 || hour > 23 || day < 0 || day > 6) { return 0.5; // Default neutral score } TimeBasedMemory memory = timeMemory[hour][day]; if(memory.totalTrades < 5) { return 0.5; // Not enough data } // Weight win rate more heavily double winRateWeight = 0.7; double profitWeight = 0.3; double normalizedWinRate = memory.winRate / 100.0; double normalizedProfit = MathMin(MathMax(memory.avgProfit / 100.0, -1.0), 1.0); return (normalizedWinRate * winRateWeight) + (normalizedProfit * profitWeight); } //+------------------------------------------------------------------+ //| Update Enhanced Memory System //+------------------------------------------------------------------+ void UpdateEnhancedMemorySystem() { totalLearnedPatterns = 0; for(int i = 0; i < totalTrackedSymbols; i++) { // Update symbol performance UpdateSymbolPerformance(trackedSymbols[i]); // Count patterns totalLearnedPatterns += enhancedSymbolMemory[i].totalPatterns; } Print("=== ENHANCED MEMORY SYSTEM ==="); Print("Symbol Memory: ", totalTrackedSymbols, " symbols tracked"); Print("Timeframe Data: 5 TF per symbol"); Print("Total Patterns: ", totalLearnedPatterns, " learned"); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void UpdateSymbolPerformance(string symbol) { int symbolIndex = getSymbolIndex(symbol); if(symbolIndex < 0) return; // Refresh per-symbol pattern stats from learned pattern memory. int symbolPatterns = 0; int symbolSuccessful = 0; for(int p = 0; p < ArraySize(patternHistory); p++) { if(patternHistory[p].symbol != symbol) continue; if(patternHistory[p].totalTrades <= 0) continue; symbolPatterns++; if(patternHistory[p].winRate >= 50.0 && patternHistory[p].totalProfit > 0.0) symbolSuccessful++; } enhancedSymbolMemory[symbolIndex].totalPatterns = symbolPatterns; enhancedSymbolMemory[symbolIndex].successfulPatterns = symbolSuccessful; enhancedSymbolMemory[symbolIndex].patternAccuracy = (symbolPatterns > 0) ? ((double)symbolSuccessful / (double)symbolPatterns) : 0.0; // Recompute per-symbol metrics across all configured timeframes. double totalVolatility = 0; double totalTrend = 0; double totalVolumeRatio = 0; int validTf = 0; for(int i = 0; i < 5; i++) { int timeframe = TIMEFRAMES[i]; ENUM_TIMEFRAMES tf = (ENUM_TIMEFRAMES)timeframe; if(Bars(symbol, tf) < 50) continue; double volatility = calculateVolatility(symbol, timeframe); double trend = calculateTrendStrength(symbol, timeframe); double avgVolume = calculateAverageVolume(symbol, timeframe, 20); long currentVolume = UseTickVolume ? iTickVolume(symbol, tf, 1) : iVolume(symbol, tf, 1); if(currentVolume <= 0) currentVolume = iTickVolume(symbol, tf, 1); double volumeRatio = (avgVolume > 0.0) ? ((double)currentVolume / avgVolume) : 1.0; totalVolatility += volatility; totalTrend += trend; totalVolumeRatio += volumeRatio; validTf++; } if(validTf <= 0) { enhancedSymbolMemory[symbolIndex].isTradable = false; return; } enhancedSymbolMemory[symbolIndex].avgVolatility = totalVolatility / (double)validTf; // Calculate average spread double spread = (SymbolInfoDouble(symbol, SYMBOL_ASK) - SymbolInfoDouble(symbol, SYMBOL_BID)) / SymbolInfoDouble(symbol, SYMBOL_POINT); enhancedSymbolMemory[symbolIndex].avgSpread = spread; // Update trend score enhancedSymbolMemory[symbolIndex].trendScore = (int)MathRound(totalTrend / (double)validTf); // Update liquidity score enhancedSymbolMemory[symbolIndex].liquidityScore = totalVolumeRatio / (double)validTf; // Update last analysis time enhancedSymbolMemory[symbolIndex].lastAnalysis = TimeCurrent(); enhancedSymbolMemory[symbolIndex].isTradable = true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int GetConsecutiveBarsForSymbolTF(string symbol, ENUM_TIMEFRAMES tf, bool countBullish) { int bars = Bars(symbol, tf); if(bars <= 2) return 0; int consecutive = 0; int maxBars = MathMin(20, bars - 1); for(int i = 1; i <= maxBars; i++) { double open = iOpen(symbol, tf, i); double close = iClose(symbol, tf, i); if(open == 0.0 && close == 0.0) break; bool match = countBullish ? (close > open) : (close < open); if(match) consecutive++; else break; } return consecutive; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int DetectEntryTimeframeForSymbol(string symbol, int direction) { int bestTf = (int)Period(); int bestScore = -1; bool countBullish = (direction == -1); for(int i = 0; i < 5; i++) { ENUM_TIMEFRAMES tf = (ENUM_TIMEFRAMES)TIMEFRAMES[i]; int streak = GetConsecutiveBarsForSymbolTF(symbol, tf, countBullish); if(streak > bestScore) { bestScore = streak; bestTf = TIMEFRAMES[i]; } } return bestTf; } //+------------------------------------------------------------------+ //| Utility Functions //+------------------------------------------------------------------+ int getTimeframeIndex(int timeframe) { for(int i = 0; i < 5; i++) { if(TIMEFRAMES[i] == timeframe) return i; } return -1; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int GetEffectiveMaxScannedOpportunities() { if(MaxScannedOpportunities < 1) return 1; if(MaxScannedOpportunities > MAX_SCANNED_OPPORTUNITIES) return MAX_SCANNED_OPPORTUNITIES; return MaxScannedOpportunities; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int getSymbolIndex(string symbol) { for(int i = 0; i < totalTrackedSymbols; i++) { if(enhancedSymbolMemory[i].symbol == symbol) return i; } return -1; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetMAValue(string symbol, int timeframe, int period, ENUM_MA_METHOD ma_method, int applied_price, int shift) { int handle = iMA(symbol, (ENUM_TIMEFRAMES)timeframe, period, 0, ma_method, applied_price); if(handle == INVALID_HANDLE) return 0.0; double buf[]; ArraySetAsSeries(buf, true); if(CopyBuffer(handle, 0, shift, 1, buf) <= 0) { IndicatorRelease(handle); return 0.0; } double val = buf[0]; IndicatorRelease(handle); return val; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetATRValue(string symbol, int timeframe, int period, int shift) { int handle = iATR(symbol, (ENUM_TIMEFRAMES)timeframe, period); if(handle == INVALID_HANDLE) return 0.0; double buf[]; ArraySetAsSeries(buf, true); if(CopyBuffer(handle, 0, shift, 1, buf) <= 0) { IndicatorRelease(handle); return 0.0; } double val = buf[0]; IndicatorRelease(handle); return val; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double calculateAverageVolume(string symbol, int timeframe, int period) { if(period <= 0) return 0.0; double totalVolume = 0; for(int i = 0; i < period; i++) { totalVolume += (double)iVolume(symbol, (ENUM_TIMEFRAMES)timeframe, i); } return totalVolume / period; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double calculateTrendStrength(string symbol, int timeframe) { double ema20 = GetMAValue(symbol, timeframe, 20, MODE_EMA, PRICE_CLOSE, 0); double ema50 = GetMAValue(symbol, timeframe, 50, MODE_EMA, PRICE_CLOSE, 0); double current = iClose(symbol, (ENUM_TIMEFRAMES)timeframe, 0); if(MathAbs(ema20) <= DBL_EPSILON || MathAbs(ema50) <= DBL_EPSILON) return 0.0; double trend = (ema20 - ema50) / ema50 * 100; double pricePosition = (current - ema20) / ema20 * 100; return MathMax(-10, MathMin(10, (trend + pricePosition) / 2)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double calculateVolatility(string symbol, int timeframe) { double atr = GetATRValue(symbol, timeframe, 14, 0); double current = iClose(symbol, (ENUM_TIMEFRAMES)timeframe, 0); return (current > 0) ? (atr / current * 100) : 0; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool AreAnyScanCategoriesEnabled() { return (ScanForex || ScanStocks || ScanIndices || ScanCrypto); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsLikelyForexByName(string lowerName) { if(StringFind(lowerName, "xau") == 0 || StringFind(lowerName, "xag") == 0 || StringFind(lowerName, "xpt") == 0 || StringFind(lowerName, "xpd") == 0) return true; string letters = ""; int nameLen = StringLen(lowerName); for(int i = 0; i < nameLen; i++) { ushort ch = StringGetCharacter(lowerName, i); bool isLetter = ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')); if(isLetter) letters += CharToString((uchar)ch); else break; } return (StringLen(letters) == 6); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsSymbolAllowedByScanCategories(string symbol) { if(!AreAnyScanCategoriesEnabled()) return false; string path = ""; SymbolInfoString(symbol, SYMBOL_PATH, path); string p = path; string n = symbol; StringToLower(p); StringToLower(n); bool isCrypto = (StringFind(p, "crypto") >= 0 || StringFind(n, "btc") >= 0 || StringFind(n, "eth") >= 0 || StringFind(n, "ltc") >= 0 || StringFind(n, "xrp") >= 0 || StringFind(n, "sol") >= 0 || StringFind(n, "ada") >= 0 || StringFind(n, "doge") >= 0); bool isIndices = (StringFind(p, "index") >= 0 || StringFind(p, "indices") >= 0 || StringFind(n, "us30") >= 0 || StringFind(n, "dj30") >= 0 || StringFind(n, "nas100") >= 0 || StringFind(n, "us100") >= 0 || StringFind(n, "spx500") >= 0 || StringFind(n, "us500") >= 0 || StringFind(n, "de40") >= 0 || StringFind(n, "ger40") >= 0 || StringFind(n, "uk100") >= 0 || StringFind(n, "jp225") >= 0 || StringFind(n, "jpn225") >= 0 || StringFind(n, "fr40") >= 0 || StringFind(n, "es35") >= 0 || StringFind(n, "hk50") >= 0 || StringFind(n, "au200") >= 0 || StringFind(n, "stoxx50") >= 0); bool isStocks = (StringFind(p, "stock") >= 0 || StringFind(p, "equit") >= 0 || StringFind(p, "share") >= 0 || StringFind(p, "nyse") >= 0 || StringFind(p, "nasdaq") >= 0); bool isForex = (!isCrypto && !isIndices && !isStocks && (StringFind(p, "forex") >= 0 || StringFind(p, "fx") >= 0 || StringFind(p, "currenc") >= 0 || StringFind(p, "metal") >= 0 || IsLikelyForexByName(n))); // Fail-open for unknown symbols to avoid hard blocking broker-specific naming. if(!isForex && !isStocks && !isIndices && !isCrypto) return true; if(isForex && ScanForex) return true; if(isStocks && ScanStocks) return true; if(isIndices && ScanIndices) return true; if(isCrypto && ScanCrypto) return true; return false; } //+------------------------------------------------------------------+ //| Multi-Symbol Scanner Implementation //+------------------------------------------------------------------+ void MultiSymbolScanner::scanAllSymbols() { opportunityCount = 0; ArrayResize(opportunities, totalTrackedSymbols); if(!AreAnyScanCategoriesEnabled()) { updateTopSymbols(); return; } for(int i = 0; i < totalTrackedSymbols; i++) { string symbol = trackedSymbols[i]; // Skip if symbol not tradable if(!enhancedSymbolMemory[i].isTradable) continue; // Skip symbols filtered out by category selection if(!IsSymbolAllowedByScanCategories(symbol)) continue; // Analyze symbol enhancedTFAnalyzer.analyzeSymbolWithSignals(symbol); // Calculate opportunity score double score = calculateSymbolScore(symbol); if(score > 5.0) // Only consider scores > 5 { opportunities[opportunityCount].symbol = symbol; opportunities[opportunityCount].score = score; opportunities[opportunityCount].trendDescription = enhancedTFAnalyzer.generateSignalReport(symbol); opportunities[opportunityCount].currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID); opportunities[opportunityCount].volatility = enhancedSymbolMemory[i].avgVolatility; opportunities[opportunityCount].spread = (SymbolInfoDouble(symbol, SYMBOL_ASK) - SymbolInfoDouble(symbol, SYMBOL_BID)) / SymbolInfoDouble(symbol, SYMBOL_POINT); opportunities[opportunityCount].isValid = true; opportunityCount++; } } // Rank opportunities rankOpportunities(); updateTopSymbols(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double MultiSymbolScanner::calculateSymbolScore(string symbol) { double score = 0; int symbolIndex = getSymbolIndex(symbol); // 1. Trend confluence (40% weight) double confluence = enhancedTFAnalyzer.signalConfidence / 10.0; // Convert percentage to 0-10 scale score += confluence * 4.0; // 2. Volatility (20% weight) double volatility = enhancedSymbolMemory[symbolIndex].avgVolatility; if(volatility > 0.5 && volatility < 3.0) score += 2.0; else if(volatility > 0.3 && volatility < 5.0) score += 1.5; else if(volatility > 0.1) score += 1.0; // 3. Volume (20% weight) double avgVolume = 0; for(int i = 0; i < 5; i++) { avgVolume += enhancedTFAnalyzer.tfData[i].volume; } avgVolume /= 5.0; if(avgVolume > 1.2) score += 2.0; else if(avgVolume > 1.0) score += 1.5; else if(avgVolume > 0.8) score += 1.0; // 4. Memory performance (20% weight) double winRate = enhancedSymbolMemory[symbolIndex].winRate; score += winRate * 2.0; return MathMin(score, 10.0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void MultiSymbolScanner::rankOpportunities() { // Simple bubble sort for ranking for(int i = 0; i < opportunityCount - 1; i++) { for(int j = 0; j < opportunityCount - i - 1; j++) { if(opportunities[j].score < opportunities[j + 1].score) { SymbolOpportunity temp = opportunities[j]; opportunities[j] = opportunities[j + 1]; opportunities[j + 1] = temp; } } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void MultiSymbolScanner::updateTopSymbols() { int maxTop = GetEffectiveMaxScannedOpportunities(); for(int i = 0; i < MAX_SCANNED_OPPORTUNITIES; i++) { if(i < maxTop && i < opportunityCount) { topSymbols[i] = opportunities[i].symbol; topScores[i] = opportunities[i].score; } else { topSymbols[i] = ""; topScores[i] = 0; } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string MultiSymbolScanner::getTopOpportunities() { int maxTop = GetEffectiveMaxScannedOpportunities(); string report = "Top " + IntegerToString(maxTop) + " Opportunities:\n"; for(int i = 0; i < MathMin(maxTop, opportunityCount); i++) { report += IntegerToString(i + 1) + ". " + opportunities[i].symbol + " (ScannerRank: " + DoubleToString(opportunities[i].score, 1) + ") - " + opportunities[i].trendDescription + "\n"; } return report; } //+------------------------------------------------------------------+ //| Dynamic Pattern Learning Engine - Generic Pattern Recognition | //+------------------------------------------------------------------+ struct CandleFingerprint { // Geometric signature (0.0 - 1.0) double bodyPosition; // 0=bottom, 0.5=middle, 1=top double bodyRatio; // body / total range double upperWickRatio; // upper wick / total range double lowerWickRatio; // lower wick / total range // Relative dimensions double bodyToRange; // body vs total range double wickToBody; // total wicks vs body // Classification int direction; // 1=bullish, -1=bearish, 0=doji // Compact hash for lookup string patternHash; // e.g., "B_35_20_45_10" }; struct DynamicPattern { string patternHash; // Unique ID string description; // Auto-generated description int patternType; // 1=bullish, 2=bearish, 0=neutral int occurrenceCount; // Times seen int winCount; // Winning trades int lossCount; // Losing trades double reliability; // 0-100% double avgProfit; // Average profit double totalProfit; // Cumulative profit datetime firstSeen; // First occurrence datetime lastSeen; // Last occurrence int timeframe; // Most common timeframe }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int FindDynamicPatternIndex(string patternHash); int QuantizeRatioTo10(double value); int InferSequencePatternType(string symbol, int timeframe, int startShift, int barsCount); string BuildSequenceDescription(string symbol, int timeframe, int startShift, int barsCount); string GeneratePatternSequenceHash(string symbol, int timeframe, int startShift, int barsCount); int FindOrCreateDynamicPatternMeta(string patternHash, int patternType, int timeframe, string description); double ConvertInputDistanceToPriceForSymbol(string symbol, double inputDistance); void AdjustEntryDistancesSmart(string symbol, int direction, int decisionScore, double bid, double ask, double baseTPDistance, double baseSLDistance, double &tpDistance, double &slDistance); void EmitOrderExecutedSound(int direction); void EmitOrderBlockedSound(); void EmitRiskWarningSound(); void EmitCloseResultSound(double profit); void EmitSpikeDetectedAlert(int direction, double movePoints, double anchorPrice, int thresholdPoints); void CheckUpcomingEconomicNewsAlerts(); void CheckFutureSessionEventAlerts(); void GetAutoFastSpikeParameters(int &thresholdPoints, uint &windowMs, uint &cooldownMs, bool &preferMomentum); double GetInstantVolumeBurst(string symbol, ENUM_TIMEFRAMES tf, int lookbackBars); double GetAverageTickVolumeForSymbol(string symbol, ENUM_TIMEFRAMES tf, int lookback); bool DetectFastSpike(int &spikeDirection, double &movePoints, double &anchorPrice, uint &elapsedMs, int &thresholdPointsOut); void ResetFastSpikePendingState(); bool SaveStateToFile(); bool LoadStateFromFile(); void AutosaveStateIfDue(); string StateFileName(); string SanitizeField(const string value); double NormalizeToTickForSymbol(string symbol, double price); double GetMinStopDistanceForSymbol(string symbol); double GetFreezeDistanceForSymbol(string symbol); double EnforceMinStopDistanceForSymbol(string symbol, double entry, double target, bool isBuy, bool isSL); bool CheckStopLossTakeProfitForSymbol(string symbol, ENUM_ORDER_TYPE type, double sl, double tp); bool IsSmartPendingOrderType(long orderType); string UrlEncodeUtf8(const string value); bool SendTelegramMessage(const string message, datetime &lastSendTime, int cooldownSeconds); string BuildTelegramEntryMessage(const string symbol, const string side, double entry, double sl, double tp, int score, const string regime); void UpdateSessionBasedMemory(TradeMemory &tradeRecord); double GetCurrentSessionConfidence(); bool IsCurrentSessionTradable(); void PrintBestSessionReport(); double GetAccountDrawdownPercentAuto(); double GetRecentWinRateAuto(int lookbackTrades); bool IsSmallAccountForAutoProtection(); bool IsLargeAccountForAutoExplore(); int GetEffectiveDecisionThresholdAuto(int baseThreshold); double GetAutoRiskBalanceFactor(double accountBalance); double GetConfiguredBaseRiskPercent(); double GetLiquidityZoneDistancePointsForSymbol(string symbol); bool ShouldEmitFilterStatusLog(const string symbol, const string direction, bool passed, const string scope); //+------------------------------------------------------------------+ //| Bullish/Bearish Signals Implementation //+------------------------------------------------------------------+ void BullishBearishSignals::detectCandlePatterns(string symbol, int timeframe) { int tfIndex = getTimeframeIndex(timeframe); if(tfIndex < 0 || tfIndex >= 5) return; // Keep previous candle context for consecutive counting candleData[tfIndex][1] = candleData[tfIndex][0]; // Get last N candles for pattern analysis int barsForPatterns = MathMax(3, PatternBars); double close[], open[], high[], low[], volume[]; ArrayResize(close, barsForPatterns); ArrayResize(open, barsForPatterns); ArrayResize(high, barsForPatterns); ArrayResize(low, barsForPatterns); ArrayResize(volume, barsForPatterns); for(int i = 0; i < barsForPatterns; i++) { close[i] = iClose(symbol, (ENUM_TIMEFRAMES)timeframe, i); open[i] = iOpen(symbol, (ENUM_TIMEFRAMES)timeframe, i); high[i] = iHigh(symbol, (ENUM_TIMEFRAMES)timeframe, i); low[i] = iLow(symbol, (ENUM_TIMEFRAMES)timeframe, i); volume[i] = (double)iVolume(symbol, (ENUM_TIMEFRAMES)timeframe, i); } // Current candle analysis CandleAnalysis current; current.timeframe = timeframe; current.open = open[0]; current.high = high[0]; current.low = low[0]; current.close = close[0]; current.volume = volume[0]; // Determine candle type current.isBullish = (close[0] > open[0]); current.isBearish = (close[0] < open[0]); current.bodySize = MathAbs(close[0] - open[0]) / SymbolInfoDouble(symbol, SYMBOL_POINT); // Calculate wick ratio double upperWick = high[0] - MathMax(open[0], close[0]); double lowerWick = MathMin(open[0], close[0]) - low[0]; current.wickRatio = (upperWick + lowerWick > 0) ? upperWick / (upperWick + lowerWick) : 0.5; // Volume analysis double avgVolume = calculateAverageVolume(symbol, timeframe, 20); current.volumeRatio = (avgVolume > 0) ? volume[0] / avgVolume : 1.0; // Consecutive candles current.consecutiveBullish = 0; current.consecutiveBearish = 0; if(current.isBullish) { current.consecutiveBullish = (candleData[tfIndex][1].isBullish) ? candleData[tfIndex][1].consecutiveBullish + 1 : 1; current.consecutiveBearish = 0; } else if(current.isBearish) { current.consecutiveBearish = (candleData[tfIndex][1].isBearish) ? candleData[tfIndex][1].consecutiveBearish + 1 : 1; current.consecutiveBullish = 0; } // Store first, then compute signal from the current candle candleData[tfIndex][0] = current; calculateSignalStrength(symbol, timeframe); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void BullishBearishSignals::calculateSignalStrength(string symbol, int timeframe) { int tfIndex = getTimeframeIndex(timeframe); if(tfIndex < 0 || tfIndex >= 5) return; CandleAnalysis current = candleData[tfIndex][0]; double strength = 0; // 1. Body size contribution (30%) if(current.bodySize > 20) strength += 3; else if(current.bodySize > 10) strength += 2; else if(current.bodySize > 5) strength += 1; // 2. Volume contribution (25%) if(current.volumeRatio > 2.0) strength += 2.5; else if(current.volumeRatio > 1.5) strength += 2; else if(current.volumeRatio > 1.2) strength += 1.5; else if(current.volumeRatio > 1.0) strength += 1; // 3. Consecutive candles contribution (25%) if(current.consecutiveBullish >= 3) strength += 2.5; else if(current.consecutiveBullish >= 2) strength += 2; else if(current.consecutiveBullish >= 1) strength += 1; else if(current.consecutiveBearish >= 3) strength -= 2.5; else if(current.consecutiveBearish >= 2) strength -= 2; else if(current.consecutiveBearish >= 1) strength -= 1; // 4. Wick pattern contribution (20%) if(current.isBullish && current.wickRatio < 0.3) strength += 2; // Strong bullish close else if(current.isBearish && current.wickRatio > 0.7) strength -= 2; // Strong bearish close // 5. Dynamic Pattern Learning contribution // Use last closed candle for stable pattern reliability lookup. CandleFingerprint fp = GenerateCandleFingerprint(symbol, timeframe, 1); string seqHash = GeneratePatternSequenceHash(symbol, timeframe, 1, 3); double dynamicReliability = GetPatternReliability(seqHash); if(FindDynamicPatternIndex(seqHash) < 0) { dynamicReliability = GetPatternReliability(fp.patternHash); } if(current.isBullish || current.isBearish) { strength += (dynamicReliability - 50.0) / 25.0; // approx -2 .. +2 } current.strength = MathMax(0, MathMin(10, strength + 5)); // Normalize 0-10 // Determine signal type if(current.strength >= 8) { current.signalType = (current.isBullish) ? "STRONG_BULLISH" : "STRONG_BEARISH"; } else if(current.strength >= 6) { current.signalType = (current.isBullish) ? "BULLISH" : "BEARISH"; } else if(current.strength >= 4) { current.signalType = "NEUTRAL"; } else { current.signalType = (current.isBullish) ? "WEAK_BULLISH" : "WEAK_BEARISH"; } candleData[tfIndex][0] = current; currentSignal[tfIndex] = current.signalType; signalStrength[tfIndex] = current.strength; } //+------------------------------------------------------------------+ //| Pattern Recognition Implementation //+------------------------------------------------------------------+ double NormalizePatternReliability(double baseReliability, double dynamicReliability, double dynWeight) { return MathMax(0.0, MathMin(100.0, (baseReliability * (1.0 - dynWeight)) + (dynamicReliability * dynWeight))); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetDynamicPatternReliability(string symbol, int timeframe, int barsCount, CandleFingerprint &fp) { string seqHash = GeneratePatternSequenceHash(symbol, timeframe, 1, barsCount); double dynamicReliability = GetPatternReliability(seqHash); if(FindDynamicPatternIndex(seqHash) < 0) { dynamicReliability = GetPatternReliability(fp.patternHash); } return dynamicReliability; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void AddDetectedPattern(PatternRecognizer &recognizer, string patternName, int patternType, int barsCount, double baseReliability, double dynamicReliability, double dynWeight, double volumeConfirmation) { if(recognizer.patternCount >= ArraySize(recognizer.detectedPatterns)) return; CandlePattern pattern; pattern.patternName = patternName; pattern.patternType = patternType; pattern.reliability = NormalizePatternReliability(baseReliability, dynamicReliability, dynWeight); pattern.consecutiveBars = barsCount; pattern.volumeConfirmation = volumeConfirmation; pattern.isValid = true; recognizer.detectedPatterns[recognizer.patternCount] = pattern; recognizer.patternCount++; for(int i = recognizer.patternCount - 1; i > 0; i--) { if(recognizer.detectedPatterns[i].reliability > recognizer.detectedPatterns[i - 1].reliability) { CandlePattern tmp = recognizer.detectedPatterns[i - 1]; recognizer.detectedPatterns[i - 1] = recognizer.detectedPatterns[i]; recognizer.detectedPatterns[i] = tmp; } else { break; } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void PatternRecognizer::analyzeAllPatterns(string symbol, int timeframe) { // AUTO pattern detection based on sequence hash (no fixed templates) int barsCount = MathMax(3, MathMin(PatternBars, 50)); string seqHash = GeneratePatternSequenceHash(symbol, timeframe, 1, barsCount); int direction = InferSequencePatternType(symbol, timeframe, 1, barsCount); // 1 bullish, 2 bearish, 0 neutral double avgVolume = calculateAverageVolume(symbol, timeframe, MathMin(50, barsCount)); double volumeConfirmation = (avgVolume > 0.0) ? ((double)iVolume(symbol, (ENUM_TIMEFRAMES)timeframe, 1) / avgVolume) : 1.0; CandleFingerprint fp = GenerateCandleFingerprint(symbol, timeframe, 1); double dynamicReliability = GetPatternReliability(seqHash); double dynWeight = 0.30; // generic weight for auto-learned pattern int patternType = (direction == 0) ? (fp.direction == 1 ? 1 : (fp.direction == -1 ? 2 : 3)) : direction; AddDetectedPattern(*this, seqHash, patternType, barsCount, 70.0, dynamicReliability, dynWeight, volumeConfirmation); } // Dynamic pattern storage DynamicPattern dynamicPatterns[]; int dynamicPatternCount = 0; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int FindDynamicPatternIndex(string patternHash) { for(int i = 0; i < dynamicPatternCount; i++) { if(dynamicPatterns[i].patternHash == patternHash) { return i; } } return -1; } //+------------------------------------------------------------------+ //| Generate Candle Fingerprint from price data | //+------------------------------------------------------------------+ CandleFingerprint GenerateCandleFingerprint(string symbol, int timeframe, int shift) { CandleFingerprint fp; double open = iOpen(symbol, (ENUM_TIMEFRAMES)timeframe, shift); double high = iHigh(symbol, (ENUM_TIMEFRAMES)timeframe, shift); double low = iLow(symbol, (ENUM_TIMEFRAMES)timeframe, shift); double close = iClose(symbol, (ENUM_TIMEFRAMES)timeframe, shift); double range = high - low; double body = MathAbs(close - open); double upperWick = high - MathMax(open, close); double lowerWick = MathMin(open, close) - low; if(range <= 0) range = SymbolInfoDouble(symbol, SYMBOL_POINT); // Calculate geometric signature fp.bodyPosition = (close > open) ? (close - low) / range : (open - low) / range; fp.bodyRatio = body / range; fp.upperWickRatio = upperWick / range; fp.lowerWickRatio = lowerWick / range; fp.bodyToRange = body / range; fp.wickToBody = (upperWick + lowerWick) / MathMax(body, SymbolInfoDouble(symbol, SYMBOL_POINT)); // Direction if(close > open) fp.direction = 1; else if(close < open) fp.direction = -1; else fp.direction = 0; // Generate hash: B/B/N_body_upper_lower (rounded to 10s) string prefix = (fp.direction == 1) ? "B" : (fp.direction == -1) ? "S" : "N"; int b = (int)(fp.bodyRatio * 100); int u = (int)(fp.upperWickRatio * 100); int l = (int)(fp.lowerWickRatio * 100); int p = (int)(fp.bodyPosition * 100); fp.patternHash = prefix + "_" + IntegerToString(b) + "_" + IntegerToString(u) + "_" + IntegerToString(l) + "_" + IntegerToString(p); return fp; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int QuantizeRatioTo10(double value) { double clamped = MathMax(0.0, MathMin(1.0, value)); return (int)MathRound(clamped * 10.0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int InferSequencePatternType(string symbol, int timeframe, int startShift, int barsCount) { int bullish = 0; int bearish = 0; for(int i = 0; i < barsCount; i++) { CandleFingerprint fp = GenerateCandleFingerprint(symbol, timeframe, startShift + i); if(fp.direction > 0) bullish++; else if(fp.direction < 0) bearish++; } if(bullish > bearish) return 1; if(bearish > bullish) return 2; return 0; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string BuildSequenceDescription(string symbol, int timeframe, int startShift, int barsCount) { int bullish = 0; int bearish = 0; double avgBody = 0.0; double avgUpper = 0.0; double avgLower = 0.0; for(int i = 0; i < barsCount; i++) { CandleFingerprint fp = GenerateCandleFingerprint(symbol, timeframe, startShift + i); if(fp.direction > 0) bullish++; else if(fp.direction < 0) bearish++; avgBody += fp.bodyRatio; avgUpper += fp.upperWickRatio; avgLower += fp.lowerWickRatio; } if(barsCount > 0) { avgBody /= barsCount; avgUpper /= barsCount; avgLower /= barsCount; } string desc = "SEQ" + IntegerToString(barsCount) + "_"; if(bullish > bearish) desc += "BullishBias_"; else if(bearish > bullish) desc += "BearishBias_"; else desc += "NeutralBias_"; if(avgBody > 0.55) desc += "LargeBody_"; else if(avgBody < 0.20) desc += "SmallBody_"; else desc += "MediumBody_"; if(avgUpper > 0.35) desc += "UpperWicks_"; if(avgLower > 0.35) desc += "LowerWicks_"; return desc; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string GeneratePatternSequenceHash(string symbol, int timeframe, int startShift, int barsCount) { int maxSeqBars = MathMax(3, MathMin(PatternBars, 50)); // cap hash length for performance int safeBars = MathMax(1, MathMin(barsCount, maxSeqBars)); string hash = "SEQ" + IntegerToString(safeBars); for(int i = 0; i < safeBars; i++) { CandleFingerprint fp = GenerateCandleFingerprint(symbol, timeframe, startShift + i); string d = (fp.direction == 1) ? "B" : ((fp.direction == -1) ? "S" : "N"); int b = QuantizeRatioTo10(fp.bodyRatio); int u = QuantizeRatioTo10(fp.upperWickRatio); int l = QuantizeRatioTo10(fp.lowerWickRatio); int p = QuantizeRatioTo10(fp.bodyPosition); hash += "_" + d + IntegerToString(b) + IntegerToString(u) + IntegerToString(l) + IntegerToString(p); } return hash; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int FindOrCreateDynamicPatternMeta(string patternHash, int patternType, int timeframe, string description) { int existing = FindDynamicPatternIndex(patternHash); if(existing >= 0) { return existing; } int idx = dynamicPatternCount; ArrayResize(dynamicPatterns, dynamicPatternCount + 1); dynamicPatternCount++; dynamicPatterns[idx].patternHash = patternHash; dynamicPatterns[idx].occurrenceCount = 0; dynamicPatterns[idx].winCount = 0; dynamicPatterns[idx].lossCount = 0; dynamicPatterns[idx].reliability = 50.0; dynamicPatterns[idx].avgProfit = 0.0; dynamicPatterns[idx].totalProfit = 0.0; dynamicPatterns[idx].firstSeen = TimeCurrent(); dynamicPatterns[idx].lastSeen = TimeCurrent(); dynamicPatterns[idx].timeframe = timeframe; dynamicPatterns[idx].description = description; dynamicPatterns[idx].patternType = patternType; Print("DYNAMIC PATTERN DISCOVERED: ", patternHash, " | ", description); return idx; } //+------------------------------------------------------------------+ //| Find or Create Dynamic Pattern in memory | //+------------------------------------------------------------------+ int FindOrCreateDynamicPattern(string patternHash, CandleFingerprint &fp, int timeframe) { string desc = ""; if(fp.bodyRatio < 0.1) desc += "SmallBody_"; else if(fp.bodyRatio > 0.6) desc += "LargeBody_"; else desc += "MediumBody_"; if(fp.upperWickRatio > 0.3) desc += "LongUpperWick_"; if(fp.lowerWickRatio > 0.3) desc += "LongLowerWick_"; if(fp.bodyPosition > 0.7) desc += "TopPosition"; else if(fp.bodyPosition < 0.3) desc += "BottomPosition"; else desc += "MidPosition"; int patternType = (fp.direction == 1) ? 1 : (fp.direction == -1) ? 2 : 0; return FindOrCreateDynamicPatternMeta(patternHash, patternType, timeframe, desc); } //+------------------------------------------------------------------+ //| Update Dynamic Pattern with Trade Outcome | //+------------------------------------------------------------------+ void UpdateDynamicPattern(string patternHash, double profit, int timeframe) { // Find pattern int idx = -1; for(int i = 0; i < dynamicPatternCount; i++) { if(dynamicPatterns[i].patternHash == patternHash) { idx = i; break; } } if(idx < 0) return; // Pattern not found // Update statistics dynamicPatterns[idx].occurrenceCount++; dynamicPatterns[idx].lastSeen = TimeCurrent(); dynamicPatterns[idx].totalProfit += profit; if(profit > 0) { dynamicPatterns[idx].winCount++; } else if(profit < 0) { dynamicPatterns[idx].lossCount++; } // Calculate reliability int total = dynamicPatterns[idx].winCount + dynamicPatterns[idx].lossCount; if(total > 0) { dynamicPatterns[idx].reliability = (double)dynamicPatterns[idx].winCount / total * 100.0; } // Update average profit if(dynamicPatterns[idx].occurrenceCount > 0) { dynamicPatterns[idx].avgProfit = dynamicPatterns[idx].totalProfit / dynamicPatterns[idx].occurrenceCount; } } //+------------------------------------------------------------------+ //| Get Pattern Reliability for Decision Making | //+------------------------------------------------------------------+ double GetPatternReliability(string patternHash) { for(int i = 0; i < dynamicPatternCount; i++) { if(dynamicPatterns[i].patternHash == patternHash) { return dynamicPatterns[i].reliability; } } return 50.0; // Unknown pattern = neutral } //+------------------------------------------------------------------+ //| Get Best Patterns by Reliability | //+------------------------------------------------------------------+ void GetBestPatterns(int direction, DynamicPattern &bestPatterns[], int maxCount) { ArrayResize(bestPatterns, 0); // Collect patterns with minimum samples and matching direction DynamicPattern candidates[]; int candidateCount = 0; for(int i = 0; i < dynamicPatternCount; i++) { if(dynamicPatterns[i].patternType == direction && dynamicPatterns[i].occurrenceCount >= 3) { ArrayResize(candidates, candidateCount + 1); candidates[candidateCount] = dynamicPatterns[i]; candidateCount++; } } // Sort by reliability (simple bubble sort) for(int i = 0; i < candidateCount - 1; i++) { for(int j = i + 1; j < candidateCount; j++) { if(candidates[j].reliability > candidates[i].reliability) { DynamicPattern temp = candidates[i]; candidates[i] = candidates[j]; candidates[j] = temp; } } } // Return top N int count = MathMin(maxCount, candidateCount); ArrayResize(bestPatterns, count); for(int i = 0; i < count; i++) { bestPatterns[i] = candidates[i]; } } //+------------------------------------------------------------------+ //| Print Dynamic Pattern Report | //+------------------------------------------------------------------+ void PrintDynamicPatternReport() { Print("=== DYNAMIC PATTERN LEARNING REPORT ==="); Print("Total patterns discovered: ", dynamicPatternCount); if(dynamicPatternCount == 0) { Print("No patterns learned yet."); return; } // Find best bullish and bearish patterns DynamicPattern bestBullish[]; DynamicPattern bestBearish[]; GetBestPatterns(1, bestBullish, 3); GetBestPatterns(2, bestBearish, 3); Print("\n--- TOP BULLISH PATTERNS ---"); for(int i = 0; i < ArraySize(bestBullish); i++) { Print("#", i+1, ": ", bestBullish[i].patternHash, " | Reliability: ", DoubleToString(bestBullish[i].reliability, 1), "%", " | Samples: ", bestBullish[i].occurrenceCount, " | AvgProfit: $", DoubleToString(bestBullish[i].avgProfit, 2), " | ", bestBullish[i].description); } Print("\n--- TOP BEARISH PATTERNS ---"); for(int i = 0; i < ArraySize(bestBearish); i++) { Print("#", i+1, ": ", bestBearish[i].patternHash, " | Reliability: ", DoubleToString(bestBearish[i].reliability, 1), "%", " | Samples: ", bestBearish[i].occurrenceCount, " | AvgProfit: $", DoubleToString(bestBearish[i].avgProfit, 2), " | ", bestBearish[i].description); } Print("========================================="); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EnhancedSmartEntry::analyzeMultiTimeframe(string sym) { this.symbol = sym; enhancedTFAnalyzer.analyzeSymbolWithSignals(sym); calculateLiquidityZones(sym); determineOptimalEntry(sym); calculateDynamicTPSL(sym); confidenceLevel = calculateConfidence(sym); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EnhancedSmartEntry::calculateLiquidityZones(string sym) { double totalLiquidity = 0; int count = 0; for(int i = 0; i < 5; i++) { if(enhancedTFAnalyzer.tfData[i].liquidityZone > 0) { totalLiquidity += enhancedTFAnalyzer.tfData[i].liquidityZone; count++; } } double point = SymbolInfoDouble(sym, SYMBOL_POINT); if(point <= 0) point = Point(); if(count > 0) { double avgLiquidity = totalLiquidity / count; liquidityZoneLow = avgLiquidity - (point * 20); liquidityZoneHigh = avgLiquidity + (point * 20); } else { double current = SymbolInfoDouble(sym, SYMBOL_BID); liquidityZoneLow = current - (point * 10); liquidityZoneHigh = current + (point * 10); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EnhancedSmartEntry::determineOptimalEntry(string sym) { double currentPrice = SymbolInfoDouble(sym, SYMBOL_BID); double bid = SymbolInfoDouble(sym, SYMBOL_BID); double ask = SymbolInfoDouble(sym, SYMBOL_ASK); // Determine direction from dominant signal for proper entry logic bool isBullish = (enhancedTFAnalyzer.dominantSignal == "BULLISH"); bool isBearish = (enhancedTFAnalyzer.dominantSignal == "BEARISH"); if(currentPrice >= liquidityZoneLow && currentPrice <= liquidityZoneHigh) { optimalEntryPrice = currentPrice; entryReason = "Current price in liquidity zone"; } else if(isBullish) { // For BUY: want to enter at support (low of liquidity zone), not resistance if(currentPrice > liquidityZoneHigh) { // Price is above liquidity zone - wait for dip to low optimalEntryPrice = liquidityZoneLow; entryReason = "Waiting for dip to support zone for BUY"; } else { // Price below liquidity zone - enter at current or at zone low optimalEntryPrice = liquidityZoneLow; entryReason = "Entry at support zone (deep) for BUY"; } } else if(isBearish) { // For SELL: want to enter at resistance (high of liquidity zone), not support if(currentPrice < liquidityZoneLow) { // Price is below liquidity zone - wait for bounce to high optimalEntryPrice = liquidityZoneHigh; entryReason = "Waiting for bounce to resistance zone for SELL"; } else { // Price above liquidity zone - enter at current or at zone high optimalEntryPrice = liquidityZoneHigh; entryReason = "Entry at resistance zone (peak) for SELL"; } } else { // Neutral signal - use midpoint optimalEntryPrice = (liquidityZoneLow + liquidityZoneHigh) / 2.0; entryReason = "Neutral - entry at zone midpoint"; } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EnhancedSmartEntry::calculateDynamicTPSL(string sym) { double currentPrice = optimalEntryPrice; double atr = GetATRValue(sym, PERIOD_H1, 14, 0); if(atr <= 0) atr = GetATRValue(sym, PERIOD_M15, 14, 0); double h4Resistance = enhancedTFAnalyzer.tfData[3].resistanceLevels[0]; double h4Support = enhancedTFAnalyzer.tfData[3].supportLevels[0]; if(enhancedTFAnalyzer.dominantSignal == "BULLISH") { dynamicTP = (h4Resistance > currentPrice) ? h4Resistance : (currentPrice + (atr * 2)); dynamicSL = (h4Support > 0) ? MathMax(h4Support, currentPrice - (atr * 1.5)) : (currentPrice - (atr * 1.5)); } else if(enhancedTFAnalyzer.dominantSignal == "BEARISH") { dynamicTP = (h4Support > 0 && h4Support < currentPrice) ? h4Support : (currentPrice - (atr * 2)); dynamicSL = (h4Resistance > 0) ? MathMin(h4Resistance, currentPrice + (atr * 1.5)) : (currentPrice + (atr * 1.5)); } else { dynamicTP = currentPrice + (atr * 1.5); dynamicSL = currentPrice - (atr * 1.5); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double EnhancedSmartEntry::calculateConfidence(string sym) { double confidence = 0; confidence += enhancedTFAnalyzer.signalConfidence * 0.4; if(enhancedTFAnalyzer.patterns.patternCount > 0) { double patternReliability = enhancedTFAnalyzer.patterns.detectedPatterns[0].reliability; confidence += patternReliability * 0.2; } double avgVolume = 0; for(int i = 0; i < 5; i++) { avgVolume += enhancedTFAnalyzer.tfData[i].volume; } avgVolume /= 5.0; if(avgVolume > 1.2) confidence += 20; else if(avgVolume > 1.0) confidence += 15; else if(avgVolume > 0.8) confidence += 10; int symbolIndex = getSymbolIndex(sym); if(symbolIndex >= 0) { confidence += enhancedSymbolMemory[symbolIndex].winRate * 20; } return MathMin(100, confidence); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EnhancedSmartEntry::generateEntryReport(string sym) { string report = "Symbol: " + sym + "\n"; report += "Entry: " + DoubleToString(optimalEntryPrice, 5) + " (" + entryReason + ")\n"; report += "TP: " + DoubleToString(dynamicTP, 5) + "\n"; report += "SL: " + DoubleToString(dynamicSL, 5) + "\n"; report += "Confidence: " + DoubleToString(confidenceLevel, 1) + "%\n"; int openTrades = PositionsTotal(); double baseRisk = 0.5; if(openTrades > 5) baseRisk = 0.3; else if(openTrades > 3) baseRisk = 0.4; riskPercent = baseRisk; Print("=== ENHANCED SMART ENTRY ==="); Print(report); } //+------------------------------------------------------------------+ //| Enhanced Multi-TF Analyzer Implementation //+------------------------------------------------------------------+ void EnhancedMultiTFAnalyzer::analyzeSymbolWithSignals(string symbol) { // Reset once per symbol, then accumulate patterns across all timeframes patterns.patternCount = 0; for(int i = 0; i < 5; i++) { tfLiquidityHasData[i] = false; tfLiquidityLevel[i] = 0.0; tfLiquidityDistance[i] = 0.0; } // Analyze all timeframes for this symbol for(int i = 0; i < 5; i++) { int timeframe = TIMEFRAMES[i]; // Update timeframe data tfData[i].timeframe = timeframe; tfData[i].trendStrength = calculateTrendStrength(symbol, timeframe); tfData[i].volatility = calculateVolatility(symbol, timeframe); double avgTfVolume = calculateAverageVolume(symbol, timeframe, 20); tfData[i].volume = (avgTfVolume > 0.0) ? ((double)iVolume(symbol, (ENUM_TIMEFRAMES)timeframe, 0) / avgTfVolume) : 1.0; tfData[i].trendScore = MathAbs(tfData[i].trendStrength); tfData[i].lastUpdate = TimeCurrent(); // Nearest liquidity zone per TF (volume profile) double zones[]; CalculateVolumeProfileForSymbol(symbol, (ENUM_TIMEFRAMES)timeframe, zones); double midPrice = (SymbolInfoDouble(symbol, SYMBOL_ASK) + SymbolInfoDouble(symbol, SYMBOL_BID)) / 2.0; double pointVal = SymbolInfoDouble(symbol, SYMBOL_POINT); double bestZone = 0.0; double bestDist = DBL_MAX; for(int z = 0; z < ArraySize(zones); z++) { double d = MathAbs(zones[z] - midPrice); if(d < bestDist) { bestDist = d; bestZone = zones[z]; } } if(bestZone > 0.0 && bestDist < DBL_MAX) { tfLiquidityHasData[i] = true; tfLiquidityLevel[i] = bestZone; tfLiquidityDistance[i] = (pointVal > 0.0) ? (bestDist / pointVal) : 0.0; tfData[i].liquidityZone = bestZone; } else { tfData[i].liquidityZone = 0.0; } // Detect candle patterns bbSignals.detectCandlePatterns(symbol, timeframe); patterns.analyzeAllPatterns(symbol, timeframe); // Count candles int bullishCount = 0, bearishCount = 0; for(int j = 0; j < 20; j++) { double close = iClose(symbol, (ENUM_TIMEFRAMES)timeframe, j); double open = iOpen(symbol, (ENUM_TIMEFRAMES)timeframe, j); if(close > open) bullishCount++; else if(close < open) bearishCount++; } tfData[i].bullishCandles = bullishCount; tfData[i].bearishCandles = bearishCount; // Calculate support/resistance levels calculateSupportResistance(symbol, timeframe, i); } // Validate signal confluence validateSignalConfluence(symbol); // Check pattern confirmation checkPatternConfirmation(symbol); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EnhancedMultiTFAnalyzer::validateSignalConfluence(string symbol) { bullishTimeframes = 0; bearishTimeframes = 0; overallBullishStrength = 0; overallBearishStrength = 0; int consideredTF = 0; double currentVolRatio = GetCurrentVolumeRatioForSymbol(symbol, (ENUM_TIMEFRAMES)Period()); // Count bullish/bearish signals across selected timeframes. // Auto-protection must not disable analysis; it should only tune risk sizing. for(int i = 0; i < 5; i++) { string signal = bbSignals.currentSignal[i]; double strength = bbSignals.signalStrength[i]; consideredTF++; if(StringFind(signal, "BULLISH") >= 0) { bullishTimeframes++; overallBullishStrength += strength; } else if(StringFind(signal, "BEARISH") >= 0) { bearishTimeframes++; overallBearishStrength += strength; } } // Calculate average strengths if(bullishTimeframes > 0) overallBullishStrength /= bullishTimeframes; if(bearishTimeframes > 0) overallBearishStrength /= bearishTimeframes; if(consideredTF <= 0) { dominantSignal = "NEUTRAL"; bullishEntryConfirmed = false; bearishEntryConfirmed = false; signalConfidence = 0; return; } int minTfConfirm = 3; // Determine dominant signal if(bullishTimeframes >= minTfConfirm && overallBullishStrength > overallBearishStrength) { dominantSignal = "BULLISH"; bullishEntryConfirmed = true; bearishEntryConfirmed = false; signalConfidence = (bullishTimeframes / (double)consideredTF) * (overallBullishStrength / 10.0) * 100; } else if(bearishTimeframes >= minTfConfirm && overallBearishStrength > overallBullishStrength) { dominantSignal = "BEARISH"; bearishEntryConfirmed = true; bullishEntryConfirmed = false; signalConfidence = (bearishTimeframes / (double)consideredTF) * (overallBearishStrength / 10.0) * 100; } else { dominantSignal = "NEUTRAL"; bullishEntryConfirmed = false; bearishEntryConfirmed = false; signalConfidence = 0; } int direction = (dominantSignal == "BULLISH") ? 1 : ((dominantSignal == "BEARISH") ? -1 : 0); if(direction != 0) { int bars = (direction == 1) ? GetConsecutiveBarsMultiTFForSymbol(symbol, false) : GetConsecutiveBarsMultiTFForSymbol(symbol, true); double bid = SymbolInfoDouble(symbol, SYMBOL_BID); double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); double memConf = GetMemoryBasedConfidenceForSymbol(symbol, direction, bars, currentVolRatio); signalConfidence *= ClampValue(memConf + 0.20, 0.40, 1.10); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EnhancedMultiTFAnalyzer::checkPatternConfirmation(string symbol) { // 1) Static pattern confirmation (rule-based) if(patterns.patternCount > 0) { for(int j = 0; j < patterns.patternCount; j++) { CandlePattern pattern = patterns.detectedPatterns[j]; if(dominantSignal == "BULLISH" && pattern.patternType == 1) { signalConfidence += pattern.reliability * 0.1; } else if(dominantSignal == "BEARISH" && pattern.patternType == 2) { signalConfidence += pattern.reliability * 0.1; } } } // 2) Dynamic Pattern Learning confirmation (adaptive) double dynamicReliabilitySum = 0.0; int dynamicSampleCount = 0; for(int i = 0; i < 5; i++) { int timeframe = TIMEFRAMES[i]; CandleFingerprint fp = GenerateCandleFingerprint(symbol, timeframe, 1); // last closed candle bool matchesDominantDirection = (dominantSignal == "BULLISH" && fp.direction == 1) || (dominantSignal == "BEARISH" && fp.direction == -1); if(!matchesDominantDirection) { continue; } string seqHash = GeneratePatternSequenceHash(symbol, timeframe, 1, 3); double reliability = GetPatternReliability(seqHash); if(FindDynamicPatternIndex(seqHash) < 0) { reliability = GetPatternReliability(fp.patternHash); } dynamicReliabilitySum += reliability; dynamicSampleCount++; } if(dynamicSampleCount > 0) { double avgDynamicReliability = dynamicReliabilitySum / dynamicSampleCount; // Neutral is 50; above adds confidence, below reduces it. signalConfidence += (avgDynamicReliability - 50.0) * 0.20; // If dynamic reliability is weak, cancel confirmation. if(avgDynamicReliability < 45.0) { if(dominantSignal == "BULLISH") bullishEntryConfirmed = false; else if(dominantSignal == "BEARISH") bearishEntryConfirmed = false; } } // Keep confidence bounded signalConfidence = MathMax(0.0, MathMin(100.0, signalConfidence)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool EnhancedMultiTFAnalyzer::shouldEnterLong(string symbol) { double minConfidence = 70.0; double minStrength = 6.0; int minTf = 3; return bullishEntryConfirmed && signalConfidence > minConfidence && overallBullishStrength > minStrength && bullishTimeframes >= minTf; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool EnhancedMultiTFAnalyzer::shouldEnterShort(string symbol) { double minConfidence = 70.0; double minStrength = 6.0; int minTf = 3; return bearishEntryConfirmed && signalConfidence > minConfidence && overallBearishStrength > minStrength && bearishTimeframes >= minTf; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string EnhancedMultiTFAnalyzer::generateSignalReport(string symbol) { string report = ""; // Generate trend description if(tfData[4].trendStrength > 7) report += "Strong Uptrend H4"; else if(tfData[4].trendStrength > 3) report += "Uptrend H4"; else if(tfData[4].trendStrength < -7) report += "Strong Downtrend H4"; else if(tfData[4].trendStrength < -3) report += "Downtrend H4"; else report += "Range H4"; if(tfData[3].trendStrength > 5) report += "+H1"; if(tfData[2].trendStrength > 5) report += "+M15"; if(tfData[1].trendStrength > 5) report += "+M5"; // Add volume info if(tfData[3].volume > 1.5) report += " - Volume: High"; else if(tfData[3].volume > 1.2) report += " - Volume: Above Avg"; // Add pattern info if(patterns.patternCount > 0) { report += " - Pattern: " + patterns.detectedPatterns[0].patternName; } return report; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void calculateSupportResistance(string symbol, int timeframe, int tfIndex) { int highCount = 0, lowCount = 0; if(tfIndex < 0 || tfIndex >= 5) return; // Reset levels ArrayInitialize(globalTFData[tfIndex].supportLevels, 0); ArrayInitialize(globalTFData[tfIndex].resistanceLevels, 0); ENUM_TIMEFRAMES tf = (ENUM_TIMEFRAMES)timeframe; for(int i = 1; i < 20; i++) { double high = iHigh(symbol, tf, i); double low = iLow(symbol, tf, i); // Find swing highs if(i+1 < 20 && high > iHigh(symbol, tf, i-1) && high > iHigh(symbol, tf, i+1)) { if(highCount < 5) { globalTFData[tfIndex].resistanceLevels[highCount] = high; highCount++; } } // Find swing lows if(i+1 < 20 && low < iLow(symbol, tf, i-1) && low < iLow(symbol, tf, i+1)) { if(lowCount < 5) { globalTFData[tfIndex].supportLevels[lowCount] = low; lowCount++; } } } } //| Enhanced Trade Execution Functions //+------------------------------------------------------------------+ void ExecuteBullishEntry(string symbol) { if(IsSymbolAutoCloseBlocked(symbol, GetCurrentTimeWithOffset())) { Print("AUTO CLOSE GATE [", symbol, "] - BUY blocked until next session window."); return; } // Get enhanced entry data smartEntry.analyzeMultiTimeframe(symbol); // Validate with bullish signals AND strict multi-timeframe confluence if(!enhancedTFAnalyzer.bullishEntryConfirmed) return; // Enforce strict confluence: must meet minimum requirements if(!enhancedTFAnalyzer.shouldEnterLong(symbol)) { Print("BULLISH ENTRY BLOCKED [", symbol, "] | Failed strict confluence validation"); Print(" Confidence: ", DoubleToString(enhancedTFAnalyzer.signalConfidence, 1), "% (min 70%)"); Print(" Strength: ", DoubleToString(enhancedTFAnalyzer.overallBullishStrength, 1), " (min 6.0)"); Print(" Timeframes: ", enhancedTFAnalyzer.bullishTimeframes, " (min 3)"); return; } // Calculate entry parameters double entryPrice = smartEntry.optimalEntryPrice; double tp = smartEntry.dynamicTP; double sl = smartEntry.dynamicSL; double tpMultiplier = 1.0; double slMultiplier = 1.0; string entryProfile = "NORMAL"; int entryScore = 5; double symbolBid = SymbolInfoDouble(symbol, SYMBOL_BID); double symbolAsk = SymbolInfoDouble(symbol, SYMBOL_ASK); SelectEntryProfileForSymbol(symbol, 1, symbolBid, symbolAsk, tpMultiplier, slMultiplier, entryProfile, entryScore); if(UseEntryProfileTP_SL) { double baseTPDistance = MathAbs(tp - entryPrice) * tpMultiplier; double baseSLDistance = MathAbs(entryPrice - sl) * slMultiplier; if(baseTPDistance <= 0.0) baseTPDistance = ConvertInputDistanceToPriceForSymbol(symbol, TakeProfit) * tpMultiplier; if(baseSLDistance <= 0.0) baseSLDistance = ConvertInputDistanceToPriceForSymbol(symbol, StopLoss) * slMultiplier; double tpDistance = baseTPDistance; double slDistance = baseSLDistance; AdjustEntryDistancesSmart(symbol, 1, entryScore, symbolBid, symbolAsk, baseTPDistance, baseSLDistance, tpDistance, slDistance); tp = entryPrice + tpDistance; sl = entryPrice - slDistance; } // For market execution, validate stops against current live quote on the traded symbol. double tpDistanceFinal = MathAbs(tp - entryPrice); double slDistanceFinal = MathAbs(entryPrice - sl); if(tpDistanceFinal <= 0.0) tpDistanceFinal = ConvertInputDistanceToPriceForSymbol(symbol, TakeProfit); if(slDistanceFinal <= 0.0) slDistanceFinal = ConvertInputDistanceToPriceForSymbol(symbol, StopLoss); symbolBid = SymbolInfoDouble(symbol, SYMBOL_BID); symbolAsk = SymbolInfoDouble(symbol, SYMBOL_ASK); tp = EnforceMinStopDistanceForSymbol(symbol, symbolBid, symbolBid + tpDistanceFinal, true, false); sl = EnforceMinStopDistanceForSymbol(symbol, symbolBid, symbolBid - slDistanceFinal, true, true); if(!CheckStopLossTakeProfitForSymbol(symbol, ORDER_TYPE_BUY, sl, tp)) { Print("BULLISH ENTRY BLOCKED [", symbol, "] | Invalid stops after broker validation | Bid=", DoubleToString(symbolBid, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)), " SL=", DoubleToString(sl, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)), " TP=", DoubleToString(tp, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS))); EmitOrderBlockedSound(); return; } // Market orders should use price=0.0; broker fills at current market. entryPrice = 0.0; double lotSize = CalculateLotSizeForSymbol(symbol); if(!CheckMoneyForTrade(symbol, lotSize, ORDER_TYPE_BUY)) { EmitOrderBlockedSound(); return; } string volumeDesc = ""; if(!CheckVolumeValueForSymbol(symbol, lotSize, volumeDesc)) { Print("BULLISH ENTRY BLOCKED [", symbol, "] | Volume check failed: ", volumeDesc); EmitOrderBlockedSound(); return; } double allowedVolume = NewOrderAllowedVolume(symbol); if(allowedVolume <= 0.0 || lotSize > allowedVolume) { Print("BULLISH ENTRY BLOCKED [", symbol, "] | Volume limit exceeded: lots=", lotSize, " allowed=", allowedVolume); EmitOrderBlockedSound(); return; } // Execute BUY trade if(EnableVoicePrompts) SpeakZira("Buy signal on " + symbol + ". Sending order."); bool executed = trade.Buy(lotSize, symbol, entryPrice, sl, tp, "Bullish Entry - Multi-TF Confluence"); double executedEntryPrice = trade.ResultPrice(); if(executedEntryPrice <= 0.0) executedEntryPrice = symbolAsk; if(executed) { EmitOrderExecutedSound(1); if(TelegramSendEntries) { string msg = BuildTelegramEntryMessage(symbol, "BUY", executedEntryPrice, sl, tp, entryScore, marketState.currentRegime); SendTelegramMessage(msg, lastTelegramEntryTime, TelegramCooldownSeconds); } Print("BULLISH ENTRY EXECUTED: ", symbol); Print("Entry: ", DoubleToString(executedEntryPrice, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS))); Print("TP: ", DoubleToString(tp, 5)); Print("SL: ", DoubleToString(sl, 5)); Print("Profile: ", entryProfile, " | Score: ", entryScore, "/10 | TPx: ", DoubleToString(tpMultiplier, 2), " | SLx: ", DoubleToString(slMultiplier, 2)); Print("Confidence: ", DoubleToString(enhancedTFAnalyzer.signalConfidence, 1), "%"); } else { Print("BULLISH ENTRY FAILED [", symbol, "] | retcode=", trade.ResultRetcode(), " | ", trade.ResultRetcodeDescription()); EmitOrderBlockedSound(); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ExecuteBearishEntry(string symbol) { if(IsSymbolAutoCloseBlocked(symbol, GetCurrentTimeWithOffset())) { Print("AUTO CLOSE GATE [", symbol, "] - SELL blocked until next session window."); return; } // Get enhanced entry data smartEntry.analyzeMultiTimeframe(symbol); // Validate with bearish signals AND strict multi-timeframe confluence if(!enhancedTFAnalyzer.bearishEntryConfirmed) return; // Enforce strict confluence: must meet minimum requirements if(!enhancedTFAnalyzer.shouldEnterShort(symbol)) { Print("BEARISH ENTRY BLOCKED [", symbol, "] | Failed strict confluence validation"); Print(" Confidence: ", DoubleToString(enhancedTFAnalyzer.signalConfidence, 1), "% (min 70%)"); Print(" Strength: ", DoubleToString(enhancedTFAnalyzer.overallBearishStrength, 1), " (min 6.0)"); Print(" Timeframes: ", enhancedTFAnalyzer.bearishTimeframes, " (min 3)"); return; } // Calculate entry parameters double entryPrice = smartEntry.optimalEntryPrice; double tp = smartEntry.dynamicTP; double sl = smartEntry.dynamicSL; double tpMultiplier = 1.0; double slMultiplier = 1.0; string entryProfile = "NORMAL"; int entryScore = 5; double symbolBid = SymbolInfoDouble(symbol, SYMBOL_BID); double symbolAsk = SymbolInfoDouble(symbol, SYMBOL_ASK); SelectEntryProfileForSymbol(symbol, -1, symbolBid, symbolAsk, tpMultiplier, slMultiplier, entryProfile, entryScore); if(UseEntryProfileTP_SL) { double baseTPDistance = MathAbs(entryPrice - tp) * tpMultiplier; double baseSLDistance = MathAbs(sl - entryPrice) * slMultiplier; if(baseTPDistance <= 0.0) baseTPDistance = ConvertInputDistanceToPriceForSymbol(symbol, TakeProfit) * tpMultiplier; if(baseSLDistance <= 0.0) baseSLDistance = ConvertInputDistanceToPriceForSymbol(symbol, StopLoss) * slMultiplier; double tpDistance = baseTPDistance; double slDistance = baseSLDistance; AdjustEntryDistancesSmart(symbol, -1, entryScore, symbolBid, symbolAsk, baseTPDistance, baseSLDistance, tpDistance, slDistance); tp = entryPrice - tpDistance; sl = entryPrice + slDistance; } // For market execution, validate stops against current live quote on the traded symbol. double tpDistanceFinal = MathAbs(entryPrice - tp); double slDistanceFinal = MathAbs(sl - entryPrice); if(tpDistanceFinal <= 0.0) tpDistanceFinal = ConvertInputDistanceToPriceForSymbol(symbol, TakeProfit); if(slDistanceFinal <= 0.0) slDistanceFinal = ConvertInputDistanceToPriceForSymbol(symbol, StopLoss); symbolBid = SymbolInfoDouble(symbol, SYMBOL_BID); symbolAsk = SymbolInfoDouble(symbol, SYMBOL_ASK); tp = EnforceMinStopDistanceForSymbol(symbol, symbolAsk, symbolAsk - tpDistanceFinal, false, false); sl = EnforceMinStopDistanceForSymbol(symbol, symbolAsk, symbolAsk + slDistanceFinal, false, true); if(!CheckStopLossTakeProfitForSymbol(symbol, ORDER_TYPE_SELL, sl, tp)) { Print("BEARISH ENTRY BLOCKED [", symbol, "] | Invalid stops after broker validation | Ask=", DoubleToString(symbolAsk, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)), " SL=", DoubleToString(sl, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)), " TP=", DoubleToString(tp, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS))); EmitOrderBlockedSound(); return; } // Market orders should use price=0.0; broker fills at current market. entryPrice = 0.0; double lotSize = CalculateLotSizeForSymbol(symbol); if(!CheckMoneyForTrade(symbol, lotSize, ORDER_TYPE_SELL)) { EmitOrderBlockedSound(); return; } string volumeDesc = ""; if(!CheckVolumeValueForSymbol(symbol, lotSize, volumeDesc)) { Print("BEARISH ENTRY BLOCKED [", symbol, "] | Volume check failed: ", volumeDesc); EmitOrderBlockedSound(); return; } double allowedVolume = NewOrderAllowedVolume(symbol); if(allowedVolume <= 0.0 || lotSize > allowedVolume) { Print("BEARISH ENTRY BLOCKED [", symbol, "] | Volume limit exceeded: lots=", lotSize, " allowed=", allowedVolume); EmitOrderBlockedSound(); return; } // Execute SELL trade if(EnableVoicePrompts) SpeakZira("Sell signal on " + symbol + ". Sending order."); bool executed = trade.Sell(lotSize, symbol, entryPrice, sl, tp, "Bearish Entry - Multi-TF Confluence"); double executedEntryPrice = trade.ResultPrice(); if(executedEntryPrice <= 0.0) executedEntryPrice = symbolBid; if(executed) { EmitOrderExecutedSound(-1); if(TelegramSendEntries) { string msg = BuildTelegramEntryMessage(symbol, "SELL", executedEntryPrice, sl, tp, entryScore, marketState.currentRegime); SendTelegramMessage(msg, lastTelegramEntryTime, TelegramCooldownSeconds); } Print("BEARISH ENTRY EXECUTED: ", symbol); Print("Entry: ", DoubleToString(executedEntryPrice, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS))); Print("TP: ", DoubleToString(tp, 5)); Print("SL: ", DoubleToString(sl, 5)); Print("Profile: ", entryProfile, " | Score: ", entryScore, "/10 | TPx: ", DoubleToString(tpMultiplier, 2), " | SLx: ", DoubleToString(slMultiplier, 2)); Print("Confidence: ", DoubleToString(enhancedTFAnalyzer.signalConfidence, 1), "%"); } else { Print("BEARISH ENTRY FAILED [", symbol, "] | retcode=", trade.ResultRetcode(), " | ", trade.ResultRetcodeDescription()); EmitOrderBlockedSound(); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int FindPositionEntryContextIndex(ulong positionId) { for(int i = 0; i < ArraySize(positionEntryMemory); i++) { if(positionEntryMemory[i].positionId == positionId) { return i; } } return -1; } //| | //+------------------------------------------------------------------+ void SavePositionEntryContext(ulong positionId, int direction, double entryPrice, int consecutiveBars, double volumeRatio, double spreadAtEntry, datetime entryTime, int decisionScore, int decisionAction, int decisionSetupId, int entryTypeAtEntry, ulong sourceOrderTicketAtEntry, int sourceOrderTypeAtEntry, int barsBucket, int volumeBucket, int entryTimeframe, string regimeAtEntry, string patternHash, double liquidityLevel, double liquidityDistancePts, bool liquidityUsed) { if(positionId == 0) { return; } int index = FindPositionEntryContextIndex(positionId); bool createdNewContext = false; if(index == -1) { index = ArraySize(positionEntryMemory); ArrayResize(positionEntryMemory, index + 1); createdNewContext = true; } positionEntryMemory[index].positionId = positionId; positionEntryMemory[index].direction = direction; positionEntryMemory[index].entryTime = entryTime; positionEntryMemory[index].entryPrice = entryPrice; positionEntryMemory[index].consecutiveBars = consecutiveBars; positionEntryMemory[index].volumeRatio = volumeRatio; positionEntryMemory[index].spreadAtEntry = spreadAtEntry; positionEntryMemory[index].decisionScoreAtEntry = decisionScore; positionEntryMemory[index].decisionActionAtEntry = (int)ClampValue((double)decisionAction, 0.0, 2.0); positionEntryMemory[index].decisionSetupIdAtEntry = (int)ClampValue((double)decisionSetupId, 0.0, 2147483647.0); positionEntryMemory[index].entryTypeAtEntry = (entryTypeAtEntry != 0) ? 1 : 0; positionEntryMemory[index].sourceOrderTicketAtEntry = sourceOrderTicketAtEntry; positionEntryMemory[index].sourceOrderTypeAtEntry = sourceOrderTypeAtEntry; positionEntryMemory[index].barsBucket = barsBucket; positionEntryMemory[index].volumeBucket = volumeBucket; positionEntryMemory[index].entryTimeframe = entryTimeframe; positionEntryMemory[index].regimeAtEntry = regimeAtEntry; positionEntryMemory[index].patternHash = patternHash; positionEntryMemory[index].liquidityLevel = liquidityLevel; positionEntryMemory[index].liquidityDistancePts = liquidityDistancePts; positionEntryMemory[index].liquidityUsed = liquidityUsed; if(createdNewContext) { // Reset accumulators for new slots; ArrayResize can reuse stale struct memory. positionEntryMemory[index].accumulatedProfit = 0.0; positionEntryMemory[index].closeDealCount = 0; } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool GetPositionEntryContext(ulong positionId, PositionEntryContext &context) { int index = FindPositionEntryContextIndex(positionId); if(index == -1) { return false; } context = positionEntryMemory[index]; return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsPositionIdentifierStillOpen(ulong positionId) { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) { continue; } if(!PositionSelectByTicket(ticket)) { continue; } if(PositionGetString(POSITION_SYMBOL) != Symbol()) { continue; } if((ulong)PositionGetInteger(POSITION_MAGIC) != (ulong)MagicNumber) { continue; } if((ulong)PositionGetInteger(POSITION_IDENTIFIER) == positionId) { return true; } } return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void RemovePositionEntryContext(ulong positionId) { int index = FindPositionEntryContextIndex(positionId); if(index == -1) { return; } int last = ArraySize(positionEntryMemory) - 1; if(index != last) { positionEntryMemory[index] = positionEntryMemory[last]; } ArrayResize(positionEntryMemory, last); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string DealReasonToText(long reason) { switch((ENUM_DEAL_REASON)reason) { case DEAL_REASON_SL: return "STOP_LOSS"; case DEAL_REASON_TP: return "TAKE_PROFIT"; case DEAL_REASON_SO: return "STOP_OUT"; case DEAL_REASON_CLIENT: return "CLIENT"; case DEAL_REASON_EXPERT: return "EXPERT"; case DEAL_REASON_MOBILE: return "MOBILE"; case DEAL_REASON_WEB: return "WEB"; case DEAL_REASON_ROLLOVER: return "ROLLOVER"; case DEAL_REASON_VMARGIN: return "VMARGIN"; case DEAL_REASON_SPLIT: return "SPLIT"; default: return "UNKNOWN"; } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void RecordTradeToMemory(int direction, double entryPrice, double exitPrice, double profit, int consecutiveBars, double volumeRatio, double spread, datetime entryTime, datetime exitTime, string exitReason, string tradeSymbol = "") { // Clamp incoming metrics to keep persistence sane double rawProfit = profit; profit = ClampDouble(profit, -1000000.0, 1000000.0); volumeRatio = ClampDouble(volumeRatio, 0.0, 1000.0); spread = ClampDouble(spread, 0.0, 100000.0); if(rawProfit != profit) { Print("MEMORY ANOMALY | Skipping out-of-range trade profit: ", DoubleToString(rawProfit, 2)); return; } TradeMemory newTrade; if(StringLen(tradeSymbol) <= 0) tradeSymbol = _Symbol; newTrade.symbol = tradeSymbol; newTrade.entryTime = entryTime; newTrade.direction = direction; newTrade.entryPrice = entryPrice; newTrade.exitPrice = exitPrice; newTrade.profit = profit; newTrade.consecutiveBars = consecutiveBars; newTrade.volumeRatio = volumeRatio; newTrade.spreadAtEntry = spread; // Initialize P/L extrema to avoid garbage values in state file newTrade.maxProfit = profit; newTrade.maxDrawdown = 0.0; newTrade.wasWinner = (profit > 0); newTrade.exitReason = exitReason; MqlDateTime entryDt, exitDt; TimeToStruct(entryTime, entryDt); TimeToStruct(exitTime, exitDt); newTrade.hourOfDay = entryDt.hour; newTrade.dayOfWeek = entryDt.day_of_week; newTrade.holdDuration = (int)((exitTime - entryTime) / 60); // minutes // Add to trade history int size = ArraySize(tradeHistory); ArrayResize(tradeHistory, size + 1); tradeHistory[size] = newTrade; // Update time-based memory UpdateTimeBasedMemory(newTrade); UpdateSessionBasedMemory(newTrade); // Update pattern memory UpdatePatternMemory(newTrade); // Update market regime UpdateMarketRegimeMemory(newTrade); // Keep only last 1000 trades in memory if(ArraySize(tradeHistory) > 1000) { int total = ArraySize(tradeHistory); for(int i = 1; i < total; i++) { tradeHistory[i - 1] = tradeHistory[i]; } ArrayResize(tradeHistory, 1000); } Print("Trade recorded to memory: ", direction == 1 ? "BUY" : "SELL", " | Profit: $", DoubleToString(profit, 2), " | Duration: ", newTrade.holdDuration, " min"); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void UpdateTimeBasedMemory(TradeMemory &tradeRecord) { int hour = tradeRecord.hourOfDay; int day = tradeRecord.dayOfWeek; timeMemory[hour][day].totalTrades++; timeMemory[hour][day].totalProfit += tradeRecord.profit; if(tradeRecord.wasWinner) { timeMemory[hour][day].winningTrades++; } // Calculate updated statistics timeMemory[hour][day].avgProfit = timeMemory[hour][day].totalProfit / timeMemory[hour][day].totalTrades; timeMemory[hour][day].winRate = (timeMemory[hour][day].winningTrades * 100.0) / timeMemory[hour][day].totalTrades; // Mark optimal hours (win rate > 60% and avg profit > 0) timeMemory[hour][day].isOptimalHour = (timeMemory[hour][day].winRate > 60.0 && timeMemory[hour][day].avgProfit > 0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void UpdatePatternMemory(TradeMemory &tradeRecord) { int patternType = (tradeRecord.direction == 1) ? 1 : 2; // 1=bullish_exhaustion, 2=bearish_exhaustion string tradeSymbol = tradeRecord.symbol; if(StringLen(tradeSymbol) <= 0) tradeSymbol = _Symbol; // Find existing pattern or create new one int patternIndex = -1; for(int i = 0; i < ArraySize(patternHistory); i++) { if(patternHistory[i].symbol == tradeSymbol && patternHistory[i].patternType == patternType && MathAbs(patternHistory[i].consecutiveBars - tradeRecord.consecutiveBars) <= 1) { patternIndex = i; break; } } if(patternIndex == -1) { // Create new pattern entry int size = ArraySize(patternHistory); ArrayResize(patternHistory, size + 1); patternIndex = size; patternHistory[patternIndex].symbol = tradeSymbol; patternHistory[patternIndex].patternType = patternType; patternHistory[patternIndex].consecutiveBars = tradeRecord.consecutiveBars; patternHistory[patternIndex].totalTrades = 0; patternHistory[patternIndex].winningTrades = 0; patternHistory[patternIndex].totalProfit = 0; } // Update pattern statistics patternHistory[patternIndex].totalTrades++; patternHistory[patternIndex].totalProfit += tradeRecord.profit; if(tradeRecord.wasWinner) { patternHistory[patternIndex].winningTrades++; } patternHistory[patternIndex].avgProfit = patternHistory[patternIndex].totalProfit / patternHistory[patternIndex].totalTrades; patternHistory[patternIndex].winRate = (patternHistory[patternIndex].winningTrades * 100.0) / patternHistory[patternIndex].totalTrades; patternHistory[patternIndex].lastUpdated = TimeCurrent(); // Calculate confidence based on sample size and performance double sampleSizeFactor = MathMin(patternHistory[patternIndex].totalTrades / 10.0, 1.0); double performanceFactor = patternHistory[patternIndex].winRate / 100.0; patternHistory[patternIndex].confidence = sampleSizeFactor * performanceFactor; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void UpdateMarketRegimeMemory(TradeMemory &tradeRecord) { // Simple regime detection based on recent volatility and performance UpdateMarketState(); // Check if we should record regime transition if(marketState.currentRegime != "UNKNOWN") { int regimeIndex = FindOrCreateRegime(marketState.currentRegime); regimeHistory[regimeIndex].totalTrades++; if(tradeRecord.wasWinner) { regimeHistory[regimeIndex].successfulTrades++; } regimeHistory[regimeIndex].performance = (regimeHistory[regimeIndex].successfulTrades * 100.0) / regimeHistory[regimeIndex].totalTrades; } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int FindOrCreateRegime(string regimeName) { for(int i = 0; i < ArraySize(regimeHistory); i++) { if(regimeHistory[i].regime == regimeName) { return i; } } // Create new regime entry int size = ArraySize(regimeHistory); ArrayResize(regimeHistory, size + 1); regimeHistory[size].regime = ""; regimeHistory[size].detected = 0; regimeHistory[size].confidence = 0.0; regimeHistory[size].duration = 0; regimeHistory[size].avgVolatility = 0.0; regimeHistory[size].avgSpread = 0.0; regimeHistory[size].totalTrades = 0; regimeHistory[size].successfulTrades = 0; regimeHistory[size].performance = 0.0; regimeHistory[size].regime = regimeName; regimeHistory[size].detected = TimeCurrent(); return size; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void UpdateMarketState() { static datetime lastUpdate = 0; datetime now = TimeCurrent(); int refreshSeconds = MathMax(5, MarketStateUpdateSeconds); if(now - lastUpdate < refreshSeconds) return; lastUpdate = now; // Calculate volatility from closed bars (returns-based, stable across ticks). int volWindow = 20; int barsAvailable = Bars(_Symbol, Period()); if(barsAvailable < volWindow + 2) { marketState.currentRegime = "UNKNOWN"; marketState.regimeConfidence = 0.0; return; } double sumRet = 0.0; double sumSqRet = 0.0; int samples = 0; for(int i = 1; i <= volWindow; i++) { double newerClose = iClose(_Symbol, Period(), i - 1); double olderClose = iClose(_Symbol, Period(), i); if(newerClose <= 0.0 || olderClose <= 0.0) continue; double retPct = ((newerClose - olderClose) / olderClose) * 100.0; sumRet += retPct; sumSqRet += retPct * retPct; samples++; } if(samples < 5) { marketState.currentRegime = "UNKNOWN"; marketState.regimeConfidence = 0.0; return; } double meanRet = sumRet / (double)samples; double variance = (sumSqRet / (double)samples) - (meanRet * meanRet); if(variance < 0.0) variance = 0.0; marketState.currentVolatility = MathSqrt(variance); // Seed average volatility to avoid early bias toward VOLATILE if(marketState.avgVolatility <= 0.0) { marketState.avgVolatility = marketState.currentVolatility; } else { marketState.avgVolatility = (marketState.avgVolatility * 0.95) + (marketState.currentVolatility * 0.05); } double pointFloorPct = (SymbolInfoDouble(_Symbol, SYMBOL_POINT) > 0.0 && SymbolInfoDouble(_Symbol, SYMBOL_BID) > 0.0) ? ((SymbolInfoDouble(_Symbol, SYMBOL_POINT) / SymbolInfoDouble(_Symbol, SYMBOL_BID)) * 100.0) : 0.0001; double baselineVolatility = MathMax(marketState.avgVolatility, pointFloorPct); if(marketState.currentVolatility > baselineVolatility * 1.5) { marketState.currentRegime = "VOLATILE"; marketState.regimeConfidence = 0.8; } else if(marketState.currentVolatility < baselineVolatility * 0.5) { marketState.currentRegime = "QUIET"; marketState.regimeConfidence = 0.8; } else { marketState.currentRegime = "RANGING"; marketState.regimeConfidence = 0.6; } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double ClampValue(double value, double minValue, double maxValue) { if(value < minValue) { return minValue; } if(value > maxValue) { return maxValue; } return value; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsHourInWindow(int hour, int startHour, int endHour) { int h = (hour % 24 + 24) % 24; int start = (startHour % 24 + 24) % 24; int end = (endHour % 24 + 24) % 24; if(start == end) return true; if(start < end) return (h >= start && h < end); return (h >= start || h < end); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int GetSessionBucketByHour(int hour) { bool inLondon = IsHourInWindow(hour, LondonSessionStartHour, LondonSessionEndHour); bool inNewYork = IsHourInWindow(hour, NewYorkSessionStartHour, NewYorkSessionEndHour); if(inLondon && inNewYork) return 3; // overlap if(inLondon) return 1; if(inNewYork) return 2; return 0; // asia/other } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void UpdateSessionBasedMemory(TradeMemory &tradeRecord) { if(!UseBestSessionsEngine) return; int bucket = GetSessionBucketByHour(tradeRecord.hourOfDay); if(bucket < 0 || bucket > 3) return; sessionMemory[bucket].totalTrades++; sessionMemory[bucket].totalProfit += tradeRecord.profit; if(tradeRecord.wasWinner) sessionMemory[bucket].winningTrades++; if(sessionMemory[bucket].totalTrades > 0) { sessionMemory[bucket].avgProfit = sessionMemory[bucket].totalProfit / (double)sessionMemory[bucket].totalTrades; sessionMemory[bucket].winRate = ((double)sessionMemory[bucket].winningTrades * 100.0) / (double)sessionMemory[bucket].totalTrades; } int minTrades = MathMax(1, BestSessionMinTrades); sessionMemory[bucket].isOptimal = (sessionMemory[bucket].totalTrades >= minTrades && sessionMemory[bucket].winRate >= BestSessionMinWinRate && sessionMemory[bucket].avgProfit > 0.0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetCurrentSessionConfidence() { if(!UseBestSessionsEngine) return 0.5; MqlDateTime dt; TimeToStruct(GetCurrentTimeWithOffset(), dt); int bucket = GetSessionBucketByHour(dt.hour); int minTrades = MathMax(1, BestSessionMinTrades); if(bucket < 0 || bucket > 3 || sessionMemory[bucket].totalTrades < minTrades) return 0.5; if(sessionMemory[bucket].isOptimal) return 0.8; return ClampValue(sessionMemory[bucket].winRate / 100.0, 0.2, 0.8); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsCurrentSessionTradable() { if(!UseBestSessionsEngine || !UseBestSessionFilter) return true; MqlDateTime dt; TimeToStruct(GetCurrentTimeWithOffset(), dt); int bucket = GetSessionBucketByHour(dt.hour); int minTrades = MathMax(1, BestSessionMinTrades); if(bucket < 0 || bucket > 3 || sessionMemory[bucket].totalTrades < minTrades) return true; // not enough evidence yet return sessionMemory[bucket].isOptimal; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void PrintBestSessionReport() { if(!UseBestSessionsEngine) return; MqlDateTime dt; TimeToStruct(GetCurrentTimeWithOffset(), dt); int currentBucket = GetSessionBucketByHour(dt.hour); Print("=== BEST SESSIONS REPORT ==="); for(int i = 0; i < 4; i++) { Print("SESSION ", sessionMemory[i].name, " | Trades=", sessionMemory[i].totalTrades, " | WinRate=", DoubleToString(sessionMemory[i].winRate, 1), "%", " | AvgProfit=$", DoubleToString(sessionMemory[i].avgProfit, 2), " | Optimal=", (sessionMemory[i].isOptimal ? "YES" : "NO")); } Print("CURRENT SESSION: ", sessionMemory[currentBucket].name, " | Tradable=", (IsCurrentSessionTradable() ? "YES" : "NO")); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetAccountDrawdownPercentAuto() { UpdateAccountCache(); if(accountCache.balance <= 0.0) return 0.0; double dd = ((accountCache.balance - accountCache.equity) / accountCache.balance) * 100.0; if(dd < 0.0) dd = 0.0; return dd; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetRecentWinRateAuto(int lookbackTrades) { int total = ArraySize(tradeHistory); if(total <= 0) return -1.0; int lookback = MathMax(5, lookbackTrades); int start = MathMax(0, total - lookback); int counted = 0; int wins = 0; for(int i = start; i < total; i++) { counted++; if(tradeHistory[i].wasWinner) wins++; } if(counted <= 0) return -1.0; return ((double)wins * 100.0) / (double)counted; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsSmallAccountForAutoProtection() { double balance = AccountInfoDouble(ACCOUNT_BALANCE); if(balance <= 500.0) return true; double dd = GetAccountDrawdownPercentAuto(); return (balance <= 1000.0 && dd >= 6.0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsLargeAccountForAutoExplore() { double balance = AccountInfoDouble(ACCOUNT_BALANCE); if(balance < 10000.0) return false; double dd = GetAccountDrawdownPercentAuto(); double winRate = GetRecentWinRateAuto(40); if(marketState.marketStress) return false; if(dd > 5.0) return false; if(winRate > 0.0 && winRate < 50.0) return false; return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int GetEffectiveDecisionThresholdAuto(int baseThreshold) { int threshold = (int)ClampValue((double)baseThreshold, 1.0, 10.0); double balance = AccountInfoDouble(ACCOUNT_BALANCE); double dd = GetAccountDrawdownPercentAuto(); double winRate = GetRecentWinRateAuto(30); if(balance <= 100.0) threshold += 3; else if(balance <= 500.0) threshold += 2; else if(balance <= 1000.0) threshold += 1; if(dd >= 10.0) threshold += 2; else if(dd >= 6.0) threshold += 1; if(winRate > 0.0 && winRate < 45.0) threshold += 1; if(balance >= 10000.0 && dd < 3.0 && winRate >= 60.0) threshold -= 1; return (int)ClampValue((double)threshold, 1.0, 10.0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetLiquidityZoneDistancePointsForSymbol(string symbol) { double symbolPoint = SymbolInfoDouble(symbol, SYMBOL_POINT); if(symbolPoint <= 0.0) symbolPoint = Point(); if(symbolPoint <= 0.0) return 0.0; double maxDistancePrice = ConvertInputDistanceToPriceForSymbol(symbol, LiquidityZoneDistance); if(maxDistancePrice <= 0.0) return 0.0; return maxDistancePrice / symbolPoint; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetAutoRiskBalanceFactor(double accountBalance) { double factor = 1.0; double dd = GetAccountDrawdownPercentAuto(); double winRate = GetRecentWinRateAuto(30); if(accountBalance <= 50.0) factor *= 0.20; else if(accountBalance <= 100.0) factor *= 0.25; else if(accountBalance <= 250.0) factor *= 0.35; else if(accountBalance <= 500.0) factor *= 0.50; else if(accountBalance <= 1000.0) factor *= 0.70; else if(accountBalance >= 100000.0) factor *= 0.65; else if(accountBalance >= 50000.0) factor *= 0.75; else if(accountBalance >= 10000.0) factor *= 0.85; if(dd >= 15.0) factor *= 0.55; else if(dd >= 10.0) factor *= 0.70; else if(dd >= 6.0) factor *= 0.85; if(winRate > 0.0 && winRate < 45.0) factor *= 0.85; else if(winRate >= 65.0 && accountBalance >= 10000.0 && dd < 3.0) factor *= 1.05; return ClampValue(factor, 0.15, 1.10); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetConfiguredBaseRiskPercent() { double baseRisk = RiskPercent; if(baseRisk <= 0.0) baseRisk = DefaultRiskPercent; return ClampValue(baseRisk, 0.01, 10.0); } // Build safe paths for the MicCmd grammar file (common Files area) string g_voiceDirCache = ""; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string BuildVoiceGrammarDir() { if(g_voiceDirCache == "") g_voiceDirCache = TerminalInfoString(TERMINAL_COMMONDATA_PATH) + "\\Files"; return g_voiceDirCache; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string BuildVoiceGrammarPath() { return BuildVoiceGrammarDir() + "\\kog_voice_grammar.xml"; } // Ensure the grammar file exists where MicCmd can read it; recreate if missing. // Tries terminal sandbox first; falls back to legacy C:\\sqlite_cmdq if needed. bool EnsureVoiceGrammarFile() { g_voiceGrammarPath = BuildVoiceGrammarPath(); // Write into common Files without creating folders manually int h = FileOpen("kog_voice_grammar.xml", FILE_WRITE | FILE_TXT | FILE_ANSI | FILE_COMMON | FILE_SHARE_READ); if(h == INVALID_HANDLE) { int err = GetLastError(); Print("VOICE CMD | Cannot create grammar file (common) err=", err); return false; } FileWriteString(h, g_voiceGrammarContent); FileClose(h); Print("VOICE CMD | Default grammar created at ", g_voiceGrammarPath); return true; } // Legacy fallback: place grammar near existing sqlite_cmdq assets (keeps older setups working) bool EnsureVoiceGrammarFileLegacy() { // Legacy path no longer attempted (permission issues); keep for compatibility Print("VOICE CMD | Legacy grammar path disabled (permissions)."); return false; } // Normalize Telegram inputs (trim spaces/newlines); keep originals untouched void NormalizeTelegramInputs() { // Trim whitespace safely (MQL5 has only left/right trim) string bot = TelegramBotToken; string chat = TelegramChatId; StringTrimLeft(bot); StringTrimRight(bot); StringTrimLeft(chat); StringTrimRight(chat); g_telegramBotToken = bot; g_telegramChatId = chat; } // Prepare voice command log path under the common data folder (shared by all terminals) void EnsureVoiceLogPath() { if(g_voiceLogPath != "") return; string baseDir = TerminalInfoString(TERMINAL_COMMONDATA_PATH) + "\\KingOfGold"; if(!FolderCreate(baseDir)) { Print("VOICE CMD | Failed to create log directory: ", baseDir); return; } g_voiceLogPath = baseDir + "\\voice_commands.log"; } // Append a single voice command entry with timestamp void LogVoiceCommand(const string cmd) { EnsureVoiceLogPath(); if(g_voiceLogPath == "") return; int h = FileOpen(g_voiceLogPath, FILE_READ|FILE_WRITE|FILE_TXT|FILE_ANSI); if(h == INVALID_HANDLE) h = FileOpen(g_voiceLogPath, FILE_WRITE|FILE_TXT|FILE_ANSI); if(h == INVALID_HANDLE) { Print("VOICE CMD | Failed to open log file: ", g_voiceLogPath); return; } FileSeek(h, 0, SEEK_END); string line = TimeToString(TimeLocal(), TIME_DATE|TIME_SECONDS) + "\t" + cmd; FileWriteString(h, line + "\n"); FileClose(h); } // Lowercase and trim helper for voice commands string NormalizeVoiceText(const string src) { string s = src; StringTrimLeft(s); StringTrimRight(s); StringToLower(s); // Replace double spaces with single while(StringFind(s, " ") >= 0) StringReplace(s, " ", " "); // Remove trailing punctuation StringReplace(s, ".", ""); StringReplace(s, ",", ""); return s; } // Extract first numeric value (integer/decimal) from text; returns true if found bool ExtractFirstNumber(const string text, double &out) { string digits = ""; bool dotSeen = false; bool started = false; for(int i = 0; i < StringLen(text); i++) { string ch = StringSubstr(text, i, 1); if((ch >= "0" && ch <= "9") || (ch == "." && !dotSeen)) { started = true; if(ch == ".") dotSeen = true; digits += ch; } else { if(started) break; } } if(digits == "") return false; out = StringToDouble(digits); return MathIsValidNumber(out); } // Map simple number words (one..ten) to double; returns true if matched bool WordToNumber(const string text, double &out) { if(text == "one") { out = 1; return true; } if(text == "two") { out = 2; return true; } if(text == "three") { out = 3; return true; } if(text == "four") { out = 4; return true; } if(text == "five") { out = 5; return true; } if(text == "six") { out = 6; return true; } if(text == "seven") { out = 7; return true; } if(text == "eight") { out = 8; return true; } if(text == "nine") { out = 9; return true; } if(text == "ten") { out = 10; return true; } return false; } // Poll MicCmd.dll queue (SAPI-based voice) and dispatch into existing ExecuteVoiceCommand void PollMicCmdQueue() { if(!g_micStarted) return; uchar buf[]; ArrayResize(buf, 256); for(int i = 0; i < 3; i++) { int r = MicPopCommandUtf8(buf); if(r == 1) { string cmd = CharArrayToString(buf); StringTrimLeft(cmd); StringTrimRight(cmd); if(cmd != "") { LogVoiceCommand(cmd); string vcResp="", vcErr=""; ExecuteVoiceCommand(cmd, Symbol(), vcResp, vcErr); } } else if(r < 0) { string err = ""; MicGetLastError(err); if(err != "") Print("MicCmd error: ", err); break; } else break; // no data } } //+------------------------------------------------------------------+ // INTELLIGENCE FUNCTIONS - Memory-Based Decision Making //+------------------------------------------------------------------+ void CalculateVolumeProfile(double &liquidityZones[], int positionType, double currentPrice); //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetMemoryBasedConfidence(int direction, int consecutiveBars, double volumeRatio, string symbol = "") { MqlDateTime currentDt; TimeToStruct(TimeCurrent(), currentDt); int hour = currentDt.hour; int day = currentDt.day_of_week; double timeConfidence = 0.0; double patternConfidence = 0.0; double regimeConfidence = 0.0; double sessionConfidence = 0.5; bool hasTimeData = false; bool hasPatternData = false; bool hasRegimeData = false; bool hasSessionData = false; string targetSymbol = symbol; if(StringLen(targetSymbol) <= 0) targetSymbol = _Symbol; // Time-based confidence if(timeMemory[hour][day].totalTrades >= 5) { hasTimeData = true; if(timeMemory[hour][day].isOptimalHour) { timeConfidence = 0.8; } else { timeConfidence = timeMemory[hour][day].winRate / 100.0; } } // Pattern-based confidence int patternType = (direction == 1) ? 1 : 2; for(int i = 0; i < ArraySize(patternHistory); i++) { if(patternHistory[i].symbol == targetSymbol && patternHistory[i].patternType == patternType && MathAbs(patternHistory[i].consecutiveBars - consecutiveBars) <= 1) { patternConfidence = patternHistory[i].confidence; hasPatternData = (patternHistory[i].totalTrades >= 3); break; } } // Regime-based confidence for(int i = 0; i < ArraySize(regimeHistory); i++) { if(regimeHistory[i].regime == marketState.currentRegime) { regimeConfidence = regimeHistory[i].performance / 100.0; hasRegimeData = (regimeHistory[i].totalTrades >= 5); break; } } // Session-based confidence (London/New York engine) if(UseBestSessionsEngine) { MqlDateTime nowDt; TimeToStruct(GetCurrentTimeWithOffset(), nowDt); int sessionBucket = GetSessionBucketByHour(nowDt.hour); int minTrades = MathMax(1, BestSessionMinTrades); if(sessionBucket >= 0 && sessionBucket <= 3 && sessionMemory[sessionBucket].totalTrades >= minTrades) { hasSessionData = true; if(sessionMemory[sessionBucket].isOptimal) sessionConfidence = 0.8; else sessionConfidence = ClampValue(sessionMemory[sessionBucket].winRate / 100.0, 0.2, 0.8); } } // Warm-up: if memory has no meaningful evidence yet, stay neutral (don't block trades). if(!hasTimeData && !hasPatternData && !hasRegimeData && !hasSessionData) { return 0.5; } // Weighted combination double combinedConfidence = (timeConfidence * 0.35) + (patternConfidence * 0.35) + (regimeConfidence * 0.20) + (sessionConfidence * 0.10); return ClampValue(combinedConfidence, 0.0, 1.0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetMemoryBasedConfidenceForSymbol(string symbol, int direction, int consecutiveBars, double volumeRatio) { double baseConfidence = GetMemoryBasedConfidence(direction, consecutiveBars, volumeRatio, symbol); int symbolIndex = getSymbolIndex(symbol); if(symbolIndex < 0) return baseConfidence; // Blend in per-symbol empirical win rate once enough trades exist. if(enhancedSymbolMemory[symbolIndex].totalTrades >= 5) { double symbolConfidence = ClampValue(enhancedSymbolMemory[symbolIndex].winRate, 0.0, 1.0); return ClampValue((baseConfidence * 0.70) + (symbolConfidence * 0.30), 0.0, 1.0); } return baseConfidence; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsOptimalTradingTime() { MqlDateTime currentDt; TimeToStruct(TimeCurrent(), currentDt); int hour = currentDt.hour; int day = currentDt.day_of_week; return timeMemory[hour][day].isOptimalHour && timeMemory[hour][day].totalTrades >= 3; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetAdaptiveRiskMultiplier() { double baseMultiplier = 1.0; // Adjust based on time performance MqlDateTime currentDt; TimeToStruct(TimeCurrent(), currentDt); int hour = currentDt.hour; int day = currentDt.day_of_week; if(timeMemory[hour][day].totalTrades >= 5) { double timeWinRate = timeMemory[hour][day].winRate; if(timeWinRate > 70.0) { baseMultiplier *= 1.2; // Increase risk in optimal hours } else if(timeWinRate < 40.0) { baseMultiplier *= 0.7; // Decrease risk in poor hours } } // Adjust based on recent performance if(marketState.consecutiveLosses >= 3) { baseMultiplier *= 0.6; // Conservative after losses } else if(marketState.consecutiveWins >= 3) { baseMultiplier *= 1.3; // Aggressive after wins } // Adjust based on market regime if(marketState.currentRegime == "VOLATILE") { baseMultiplier *= 0.8; // More conservative in volatile markets } else if(marketState.currentRegime == "QUIET") { baseMultiplier *= 1.1; // Slightly more aggressive in quiet markets } // Adjust using session quality (London/New York engine) if(UseBestSessionsEngine) { MqlDateTime sessionDt; TimeToStruct(GetCurrentTimeWithOffset(), sessionDt); int bucket = GetSessionBucketByHour(sessionDt.hour); int minTrades = MathMax(1, BestSessionMinTrades); if(bucket >= 0 && bucket <= 3 && sessionMemory[bucket].totalTrades >= minTrades) { if(sessionMemory[bucket].isOptimal) baseMultiplier *= 1.10; else if(sessionMemory[bucket].winRate < (BestSessionMinWinRate - 10.0)) baseMultiplier *= 0.85; } } return ClampValue(baseMultiplier, 0.3, 2.0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string GetMarketAnalysisReport() { string report = ""; report += "=== MEMORY-BASED MARKET ANALYSIS ===\n"; // Current regime info report += "Current Regime: " + marketState.currentRegime + " (Confidence: " + DoubleToString(marketState.regimeConfidence * 100, 1) + "%)\n"; report += "Volatility: " + DoubleToString(marketState.currentVolatility, 5) + " (Avg: " + DoubleToString(marketState.avgVolatility, 5) + ")\n"; // Recent performance report += "Recent Streak: " + IntegerToString(marketState.consecutiveWins) + "W / " + IntegerToString(marketState.consecutiveLosses) + "L\n"; // Best trading times report += "Optimal Hours: "; int optimalCount = 0; for(int h = 0; h < 24; h++) { for(int d = 0; d < 7; d++) { if(timeMemory[h][d].isOptimalHour && timeMemory[h][d].totalTrades >= 3) { if(optimalCount > 0) report += ", "; report += IntegerToString(h) + ":00"; optimalCount++; } } } if(optimalCount == 0) report += "None identified yet"; report += "\n"; // Pattern performance report += "Pattern Performance:\n"; for(int i = 0; i < ArraySize(patternHistory); i++) { if(patternHistory[i].totalTrades >= 5) { string patternName = (patternHistory[i].patternType == 1) ? "Bullish Exhaustion" : "Bearish Exhaustion"; report += " " + patternName + " (" + IntegerToString(patternHistory[i].consecutiveBars) + " bars): " + DoubleToString(patternHistory[i].winRate, 1) + "% win rate, " + DoubleToString(patternHistory[i].confidence * 100, 1) + "% confidence\n"; } } if(UseBestSessionsEngine) { report += "Session Performance:\n"; for(int s = 0; s < 4; s++) { report += " " + sessionMemory[s].name + ": WR " + DoubleToString(sessionMemory[s].winRate, 1) + "% | Avg $" + DoubleToString(sessionMemory[s].avgProfit, 2) + " | Trades " + IntegerToString(sessionMemory[s].totalTrades) + "\n"; } } return report; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void PrintLearningTelemetry() { int trades = ArraySize(tradeHistory); int patterns = ArraySize(patternHistory); int setups = ArraySize(setupMemory); int regimes = ArraySize(regimeHistory); double allowRate = (memoryDecisionTotal > 0) ? ((double)memoryDecisionAllow * 100.0 / (double)memoryDecisionTotal) : 0.0; double blockRate = (memoryDecisionTotal > 0) ? ((double)memoryDecisionBlock * 100.0 / (double)memoryDecisionTotal) : 0.0; double exploreRate = (memoryDecisionTotal > 0) ? ((double)memoryDecisionExplore * 100.0 / (double)memoryDecisionTotal) : 0.0; double bestReward = -DBL_MAX; double worstReward = DBL_MAX; int bestIdx = -1; int worstIdx = -1; double rewardSum = 0.0; int rewardCount = 0; for(int i = 0; i < setups; i++) { rewardSum += setupMemory[i].rewardScore; rewardCount++; if(setupMemory[i].rewardScore > bestReward) { bestReward = setupMemory[i].rewardScore; bestIdx = i; } if(setupMemory[i].rewardScore < worstReward) { worstReward = setupMemory[i].rewardScore; worstIdx = i; } } double avgReward = (rewardCount > 0) ? (rewardSum / (double)rewardCount) : 0.0; Print("LEARNING TELEMETRY | Trades=", trades, " | PatternMem=", patterns, " | SetupMem=", setups, " | Regimes=", regimes, " | DynamicPatterns=", dynamicPatternCount, " | RewardUpdates=", setupRewardUpdates); Print("LEARNING DECISIONS | Total=", memoryDecisionTotal, " | Allow=", memoryDecisionAllow, " (", DoubleToString(allowRate, 1), "%)", " | Block=", memoryDecisionBlock, " (", DoubleToString(blockRate, 1), "%)", " | Explore=", memoryDecisionExplore, " (", DoubleToString(exploreRate, 1), "%)"); Print("REWARD STATUS | Avg=", DoubleToString(avgReward, 3), " | Clamp=[", DoubleToString(SetupRewardClampMin, 2), ",", DoubleToString(SetupRewardClampMax, 2), "]"); if(bestIdx >= 0) Print("REWARD BEST | Dir=", setupMemory[bestIdx].direction, " | BarsBucket=", setupMemory[bestIdx].barsBucket, " | VolBucket=", setupMemory[bestIdx].volumeBucket, " | EntryType=", (setupMemory[bestIdx].entryType == 1 ? "SMART_LIMIT" : "MARKET"), " | Regime=", setupMemory[bestIdx].regime, " | Score=", DoubleToString(setupMemory[bestIdx].rewardScore, 3), " | Trades=", setupMemory[bestIdx].totalTrades, " | WinRate=", DoubleToString((setupMemory[bestIdx].totalTrades > 0) ? ((double)setupMemory[bestIdx].winningTrades * 100.0 / (double)setupMemory[bestIdx].totalTrades) : 0.0, 1), "%"); if(worstIdx >= 0) Print("REWARD WORST | Dir=", setupMemory[worstIdx].direction, " | BarsBucket=", setupMemory[worstIdx].barsBucket, " | VolBucket=", setupMemory[worstIdx].volumeBucket, " | EntryType=", (setupMemory[worstIdx].entryType == 1 ? "SMART_LIMIT" : "MARKET"), " | Regime=", setupMemory[worstIdx].regime, " | Score=", DoubleToString(setupMemory[worstIdx].rewardScore, 3), " | Trades=", setupMemory[worstIdx].totalTrades, " | WinRate=", DoubleToString((setupMemory[worstIdx].totalTrades > 0) ? ((double)setupMemory[worstIdx].winningTrades * 100.0 / (double)setupMemory[worstIdx].totalTrades) : 0.0, 1), "%"); if(UseBestSessionsEngine) { int minTrades = MathMax(1, BestSessionMinTrades); for(int s = 0; s < 4; s++) { Print("SESSION TELEMETRY | ", sessionMemory[s].name, " | Trades=", sessionMemory[s].totalTrades, " | WinRate=", DoubleToString(sessionMemory[s].winRate, 1), "%", " | AvgProfit=$", DoubleToString(sessionMemory[s].avgProfit, 2), " | Optimal=", (sessionMemory[s].isOptimal ? "YES" : "NO"), " | MinTrades=", minTrades); } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int GetBarsBucket(int consecutiveBars) { if(consecutiveBars <= 0) return 0; if(consecutiveBars >= 10) return 10; return consecutiveBars; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int GetVolumeBucket(double volumeRatio) { if(volumeRatio < 0.8) return 0; if(volumeRatio < 1.0) return 1; if(volumeRatio < 1.3) return 2; if(volumeRatio < 1.8) return 3; return 4; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int FindOrCreateSetupBucket(int direction, int barsBucket, int volumeBucket, string regime, int entryType = 0) { for(int i = 0; i < ArraySize(setupMemory); i++) { if(setupMemory[i].direction == direction && setupMemory[i].barsBucket == barsBucket && setupMemory[i].volumeBucket == volumeBucket && setupMemory[i].entryType == entryType && setupMemory[i].regime == regime) { return i; } } int size = ArraySize(setupMemory); ArrayResize(setupMemory, size + 1); setupMemory[size].direction = direction; setupMemory[size].barsBucket = barsBucket; setupMemory[size].volumeBucket = volumeBucket; setupMemory[size].entryType = (entryType != 0) ? 1 : 0; setupMemory[size].regime = regime; setupMemory[size].totalTrades = 0; setupMemory[size].winningTrades = 0; setupMemory[size].totalProfit = 0.0; setupMemory[size].avgProfit = 0.0; setupMemory[size].rewardScore = 0.0; return size; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetSetupRewardScore(int direction, int barsBucket, int volumeBucket, string regime, int entryType = 0) { for(int i = 0; i < ArraySize(setupMemory); i++) { if(setupMemory[i].direction == direction && setupMemory[i].barsBucket == barsBucket && setupMemory[i].volumeBucket == volumeBucket && setupMemory[i].entryType == entryType && setupMemory[i].regime == regime) { return setupMemory[i].rewardScore; } } return 0.0; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void UpdateSetupBucketReward(int direction, int barsBucket, int volumeBucket, string regime, double profit, int entryType = 0) { int idx = FindOrCreateSetupBucket(direction, barsBucket, volumeBucket, regime, entryType); setupRewardUpdates++; setupMemory[idx].totalTrades++; setupMemory[idx].totalProfit += profit; if(profit > 0.0) { setupMemory[idx].winningTrades++; setupMemory[idx].rewardScore += RewardStep; } else if(profit < 0.0) { setupMemory[idx].rewardScore -= PenaltyStep; } setupMemory[idx].avgProfit = setupMemory[idx].totalProfit / setupMemory[idx].totalTrades; setupMemory[idx].rewardScore = ClampValue(setupMemory[idx].rewardScore, SetupRewardClampMin, SetupRewardClampMax); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ApplyRewardPenaltyToPattern(string symbol, int direction, int consecutiveBars, double profit) { int patternType = (direction == 1) ? 1 : 2; string targetSymbol = symbol; if(StringLen(targetSymbol) <= 0) targetSymbol = _Symbol; for(int i = 0; i < ArraySize(patternHistory); i++) { if(patternHistory[i].symbol == targetSymbol && patternHistory[i].patternType == patternType && MathAbs(patternHistory[i].consecutiveBars - consecutiveBars) <= 1) { if(profit > 0.0) { patternHistory[i].confidence = ClampValue(patternHistory[i].confidence + RewardStep, 0.0, 1.0); } else if(profit < 0.0) { patternHistory[i].confidence = ClampValue(patternHistory[i].confidence - PenaltyStep, 0.0, 1.0); } break; } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetRegimeQualityScore() { if(marketState.marketStress) { return 0.35; } if(marketState.currentRegime == "QUIET") return 0.75; if(marketState.currentRegime == "RANGING") return 0.65; if(marketState.currentRegime == "VOLATILE") return 0.45; return 0.55; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CalculateVolumeProfileForSymbol(string symbol, ENUM_TIMEFRAMES tf, double &liquidityZones[]) { ArrayResize(liquidityZones, 0); if(!UseVolumeProfile) return; int totalBars = Bars(symbol, tf); int numBars = MathMin(VolumeProfileBars, totalBars); if(numBars < 10) return; double minPrice = DBL_MAX; double maxPrice = -DBL_MAX; for(int i = 1; i <= numBars; i++) { double high = iHigh(symbol, tf, i); double low = iLow(symbol, tf, i); if(high <= 0.0 || low <= 0.0) continue; if(high > maxPrice) maxPrice = high; if(low < minPrice) minPrice = low; } if(minPrice == DBL_MAX || maxPrice == -DBL_MAX) return; double priceRange = maxPrice - minPrice; if(priceRange <= 0.0) return; int numBuckets = 50; double bucketSize = priceRange / numBuckets; if(bucketSize <= 0.0) return; double volumeBuckets[]; double priceBuckets[]; ArrayResize(volumeBuckets, numBuckets); ArrayResize(priceBuckets, numBuckets); ArrayInitialize(volumeBuckets, 0.0); for(int i = 0; i < numBuckets; i++) priceBuckets[i] = minPrice + (i * bucketSize) + (bucketSize / 2.0); for(int i = 1; i <= numBars; i++) { double high = iHigh(symbol, tf, i); double low = iLow(symbol, tf, i); long volume = UseTickVolume ? iTickVolume(symbol, tf, i) : iVolume(symbol, tf, i); if(volume <= 0 || high <= 0.0 || low <= 0.0) continue; for(int j = 0; j < numBuckets; j++) { double bucketPrice = priceBuckets[j]; if(bucketPrice >= low && bucketPrice <= high) volumeBuckets[j] += (double)volume; } } double totalVolume = 0.0; for(int i = 0; i < numBuckets; i++) totalVolume += volumeBuckets[i]; double avgVolume = (numBuckets > 0) ? (totalVolume / numBuckets) : 0.0; if(avgVolume <= 0.0) return; for(int i = 0; i < numBuckets; i++) { if(volumeBuckets[i] >= avgVolume * 1.5) { int size = ArraySize(liquidityZones); ArrayResize(liquidityZones, size + 1); liquidityZones[size] = priceBuckets[i]; } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetVolumeProfileProximityScoreForSymbol(string symbol, int direction, double referencePrice) { if(!UseSmartEntry || !UseVolumeProfile) { return 0.5; } double symbolPoint = SymbolInfoDouble(symbol, SYMBOL_POINT); double pointDistance = point_value; if(pointDistance <= 0.0) pointDistance = symbolPoint; if(pointDistance <= 0.0) return 0.5; double zones[]; ENUM_TIMEFRAMES tf = (ENUM_TIMEFRAMES)Period(); CalculateVolumeProfileForSymbol(symbol, tf, zones); if(ArraySize(zones) == 0) { return 0.5; } double minDistancePoints = DBL_MAX; for(int i = 0; i < ArraySize(zones); i++) { bool validSide = (direction == 1) ? (zones[i] < referencePrice) : (zones[i] > referencePrice); if(!validSide) continue; double distancePoints = MathAbs(zones[i] - referencePrice) / pointDistance; if(distancePoints < minDistancePoints) { minDistancePoints = distancePoints; } } double maxDistancePoints = GetLiquidityZoneDistancePointsForSymbol(symbol); if(minDistancePoints == DBL_MAX || maxDistancePoints <= 0.0) { return 0.5; } if(minDistancePoints > maxDistancePoints) { return 0.0; } return ClampValue(1.0 - (minDistancePoints / maxDistancePoints), 0.0, 1.0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetVolumeProfileProximityScore(int direction, double referencePrice) { return GetVolumeProfileProximityScoreForSymbol(Symbol(), direction, referencePrice); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int GetDecisionScore10ForSymbol(string symbol, int direction, int consecutiveBars, double volumeRatio, double bid, double ask, string &details) { double memoryConfidence = GetMemoryBasedConfidenceForSymbol(symbol, direction, consecutiveBars, volumeRatio); int barsTrigger = (direction == 1) ? MathMax(BearishBarsForBuy, 1) : MathMax(BullishBarsForSell, 1); double barsStrength = ClampValue((double)consecutiveBars / (double)barsTrigger, 0.0, 1.0); double volumeStrength = 0.0; if(StrongMomentumThreshold > MinVolumeRatio) { volumeStrength = ClampValue((volumeRatio - MinVolumeRatio) / (StrongMomentumThreshold - MinVolumeRatio), 0.0, 1.0); } else { volumeStrength = (volumeRatio >= MinVolumeRatio) ? 1.0 : 0.0; } double referencePrice = (direction == 1) ? ask : bid; double zoneStrength = GetVolumeProfileProximityScoreForSymbol(symbol, direction, referencePrice); double regimeStrength = GetRegimeQualityScore(); int barsBucket = GetBarsBucket(consecutiveBars); int volumeBucket = GetVolumeBucket(volumeRatio); double rewardRaw = GetSetupRewardScore(direction, barsBucket, volumeBucket, marketState.currentRegime); double rewardNorm = (rewardRaw - SetupRewardClampMin) / MathMax(SetupRewardClampMax - SetupRewardClampMin, 0.0001); rewardNorm = ClampValue(rewardNorm, 0.0, 1.0); double score01 = (memoryConfidence * 0.30) + (barsStrength * 0.15) + (volumeStrength * 0.20) + (zoneStrength * 0.15) + (regimeStrength * 0.10) + (rewardNorm * 0.10); if(marketState.marketStress) { // In stress regimes, treat valid opportunities as slightly higher quality // so the system can actively hunt recovery trades instead of freezing. score01 *= 1.05; } int score10 = (int)MathRound(ClampValue(score01, 0.0, 1.0) * 10.0); score10 = (int)ClampValue((double)score10, 1.0, 10.0); details = "mem=" + DoubleToString(memoryConfidence * 10.0, 1) + " bars=" + DoubleToString(barsStrength * 10.0, 1) + " vol=" + DoubleToString(volumeStrength * 10.0, 1) + " zone=" + DoubleToString(zoneStrength * 10.0, 1) + " regime=" + DoubleToString(regimeStrength * 10.0, 1) + " reward=" + DoubleToString(rewardNorm * 10.0, 1); return score10; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int GetDecisionScore10(int direction, int consecutiveBars, double volumeRatio, double bid, double ask, string &details) { return GetDecisionScore10ForSymbol(Symbol(), direction, consecutiveBars, volumeRatio, bid, ask, details); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool ShouldAllowControlledExplore(int decisionScore, int threshold) { if(!UseControlledExplore) return false; if(IsSmallAccountForAutoProtection() && !AllowExploreOnSmallAccounts) return false; int buffer = MathMax(1, ExploreScoreBuffer); int minExploreScore = MathMax(1, threshold - buffer); if(decisionScore < minExploreScore || decisionScore >= threshold) { return false; } // Large, stable accounts: auto-enable controlled explore. if(IsLargeAccountForAutoExplore()) { int fastN = MathMax(2, ExploreOneInN - 1); exploreSignalCounter++; return (exploreSignalCounter % fastN) == 0; } if(marketState.consecutiveLosses < ExploreLossStreakTrigger) return false; if(ExploreOneInN <= 1) return false; exploreSignalCounter++; return (exploreSignalCounter % ExploreOneInN) == 0; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int IncrementExploreCounterForSymbol(string symbol) { static string exploreSymbols[]; static int exploreCounters[]; for(int i = 0; i < ArraySize(exploreSymbols); i++) { if(exploreSymbols[i] == symbol) { exploreCounters[i]++; return exploreCounters[i]; } } int idx = ArraySize(exploreSymbols); ArrayResize(exploreSymbols, idx + 1); ArrayResize(exploreCounters, idx + 1); exploreSymbols[idx] = symbol; exploreCounters[idx] = 1; return exploreCounters[idx]; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int NextDecisionSetupId() { static int setupIdSeq = 1; int nextId = setupIdSeq; setupIdSeq++; if(setupIdSeq <= 0) setupIdSeq = 1; return nextId; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void SetPendingDecisionActionForSymbol(string symbol, int action, int setupId = 0) { action = (int)ClampValue((double)action, 0.0, 2.0); setupId = (int)ClampValue((double)setupId, 0.0, 2147483647.0); for(int i = 0; i < ArraySize(pendingDecisionSymbols); i++) { if(pendingDecisionSymbols[i] == symbol) { pendingDecisionActions[i] = action; pendingDecisionSetupIds[i] = setupId; return; } } int idx = ArraySize(pendingDecisionSymbols); ArrayResize(pendingDecisionSymbols, idx + 1); ArrayResize(pendingDecisionActions, idx + 1); ArrayResize(pendingDecisionSetupIds, idx + 1); pendingDecisionSymbols[idx] = symbol; pendingDecisionActions[idx] = action; pendingDecisionSetupIds[idx] = setupId; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int ConsumePendingDecisionActionForSymbol(string symbol, int &setupId) { setupId = 0; for(int i = 0; i < ArraySize(pendingDecisionSymbols); i++) { if(pendingDecisionSymbols[i] == symbol) { int action = pendingDecisionActions[i]; setupId = (i < ArraySize(pendingDecisionSetupIds)) ? pendingDecisionSetupIds[i] : 0; pendingDecisionActions[i] = 1; // default next action to ALLOW if(i < ArraySize(pendingDecisionSetupIds)) pendingDecisionSetupIds[i] = 0; return action; } } return 1; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int ConsumePendingDecisionActionForSymbol(string symbol) { int setupId = 0; return ConsumePendingDecisionActionForSymbol(symbol, setupId); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string BuildBlockReasonSummary(const string symbol, const double spreadPts, const double volumeRatio, const bool strongBar, const double zoneScore, const double liquidityDistanceNorm, const double mtfConfluence, const double drawdownPct, const double recentWinRate, const double sessionQuality, const bool marketStress, const string regimeText) { double penalties[11]; string labels[11]; ArrayInitialize(penalties, 0.0); penalties[0] = marketStress ? 1.0 : 0.0; labels[0] = "stress"; penalties[1] = ClampValue((spreadPts - 120.0) / 220.0, 0.0, 1.0); labels[1] = "spread"; penalties[2] = ClampValue((drawdownPct - 4.0) / 12.0, 0.0, 1.0); labels[2] = "drawdown"; penalties[3] = ClampValue((55.0 - recentWinRate) / 25.0, 0.0, 1.0); labels[3] = "winrate"; penalties[4] = ClampValue((0.55 - sessionQuality) / 0.35, 0.0, 1.0); labels[4] = "session"; penalties[5] = ClampValue((1.00 - volumeRatio) / 0.70, 0.0, 1.0); labels[5] = "volume"; penalties[6] = (regimeText == "VOLATILE") ? 0.70 : ((regimeText == "QUIET") ? 0.20 : 0.0); labels[6] = "regime"; penalties[7] = strongBar ? 0.0 : 0.60; labels[7] = "strong_bar"; penalties[8] = ClampValue((0.55 - zoneScore) / 0.55, 0.0, 1.0); labels[8] = "zone"; penalties[9] = ClampValue(liquidityDistanceNorm, 0.0, 1.0); labels[9] = "liq_dist"; penalties[10] = ClampValue((0.60 - mtfConfluence) / 0.60, 0.0, 1.0); labels[10] = "mtf"; int top1 = -1; int top2 = -1; for(int i = 0; i < 11; i++) { if(top1 == -1 || penalties[i] > penalties[top1]) { top2 = top1; top1 = i; } else if(top2 == -1 || penalties[i] > penalties[top2]) { top2 = i; } } string r1 = (top1 >= 0) ? labels[top1] + "=" + DoubleToString(penalties[top1], 2) : "n/a"; string r2 = (top2 >= 0) ? labels[top2] + "=" + DoubleToString(penalties[top2], 2) : "n/a"; return "reasons: " + r1 + ", " + r2 + " | spreadPts=" + DoubleToString(spreadPts, 1) + " dd=" + DoubleToString(drawdownPct, 2) + "%" + " wr=" + DoubleToString(recentWinRate, 1) + "%" + " sess=" + DoubleToString(sessionQuality, 2) + " vol=" + DoubleToString(volumeRatio, 2) + " strong=" + (strongBar ? "1" : "0") + " zone=" + DoubleToString(zoneScore, 2) + " liq=" + DoubleToString(liquidityDistanceNorm, 2) + " mtf=" + DoubleToString(mtfConfluence, 2); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool ShouldEmitFilterStatusLog(const string symbol, const string direction, bool passed, const string scope) { static string keys[]; static datetime bars[]; static int statuses[]; string key = scope + "|" + symbol + "|" + direction; datetime barTime = iTime(symbol, (ENUM_TIMEFRAMES)Period(), 0); if(barTime <= 0) barTime = TimeCurrent(); int status = passed ? 1 : 0; int idx = -1; for(int i = 0; i < ArraySize(keys); i++) { if(keys[i] == key) { idx = i; break; } } if(idx < 0) { int newIdx = ArraySize(keys); ArrayResize(keys, newIdx + 1); ArrayResize(bars, newIdx + 1); ArrayResize(statuses, newIdx + 1); keys[newIdx] = key; bars[newIdx] = barTime; statuses[newIdx] = status; return true; } bool barChanged = (bars[idx] != barTime); bool statusChanged = (statuses[idx] != status); if(barChanged || statusChanged) { bars[idx] = barTime; statuses[idx] = status; return true; } return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool ShouldAllowControlledExploreForSymbol(string symbol, int decisionScore, int threshold) { if(!UseControlledExplore) return false; if(IsSmallAccountForAutoProtection() && !AllowExploreOnSmallAccounts) return false; int buffer = MathMax(1, ExploreScoreBuffer); int minExploreScore = MathMax(1, threshold - buffer); if(decisionScore < minExploreScore || decisionScore >= threshold) { return false; } // Large, stable accounts: auto-enable controlled explore. if(IsLargeAccountForAutoExplore()) { int fastN = MathMax(2, ExploreOneInN - 1); int symbolCounterLarge = IncrementExploreCounterForSymbol(symbol); return (symbolCounterLarge % fastN) == 0; } // Bootstrap phase: allow sparse exploration so DLL can gather initial outcomes faster. if(ArraySize(tradeHistory) < 20) { int bootstrapN = MathMax(2, ExploreOneInN - 1); int symbolCounterBootstrap = IncrementExploreCounterForSymbol(symbol); return (symbolCounterBootstrap % bootstrapN) == 0; } if(marketState.consecutiveLosses < ExploreLossStreakTrigger) return false; if(ExploreOneInN <= 1) return false; int symbolCounter = IncrementExploreCounterForSymbol(symbol); return (symbolCounter % ExploreOneInN) == 0; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool ShouldSkipTradeBasedOnMemoryForSymbol(string symbol, int direction, int consecutiveBars, double volumeRatio, double bid, double ask, bool &useExploreLot, int &decisionScore) { memoryDecisionTotal++; useExploreLot = false; int baseThreshold = marketState.marketStress ? DecisionScoreThresholdStress : DecisionScoreThresholdNormal; int autoThreshold = GetEffectiveDecisionThresholdAuto(baseThreshold); int threshold = MathMax(baseThreshold, autoThreshold); // Keep memory gate active in warmup, but slightly softer until enough samples are collected. if(ArraySize(tradeHistory) < 20) threshold = MathMax(3, threshold - 1); bool smallAccount = IsSmallAccountForAutoProtection(); // For very small accounts where we explicitly allow exploration, slightly soften the gate. if(smallAccount && AllowExploreOnSmallAccounts) threshold = MathMax(2, threshold - 2); if(UseDecisionCoreDll && g_decisionDllReady) { DC_SetupFeatures features; int decisionSetupId = NextDecisionSetupId(); MqlDateTime nowDt; TimeToStruct(GetCurrentTimeWithOffset(), nowDt); double symbolPoint = SymbolInfoDouble(symbol, SYMBOL_POINT); if(symbolPoint <= 0.0) symbolPoint = Point(); double spreadPts = (symbolPoint > 0.0) ? (MathAbs(ask - bid) / symbolPoint) : 0.0; double recentWr = GetRecentWinRateAuto(30); if(recentWr < 0.0) recentWr = 50.0; bool strongBar = (volumeRatio >= VolumeSignalFactor); double referencePrice = (direction == 1) ? ask : bid; double zoneScore = GetVolumeProfileProximityScoreForSymbol(symbol, direction, referencePrice); double liqDistPts = 0.0; FindNearestLiquidityZone(referencePrice, (direction == 1) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL, liqDistPts); double maxLiqDistancePts = GetLiquidityZoneDistancePointsForSymbol(symbol); double liquidityDistanceNorm = (maxLiqDistancePts > 0.0) ? ClampValue(liqDistPts / maxLiqDistancePts, 0.0, 1.0) : 1.0; int dirStreakMtf = (direction == 1) ? GetConsecutiveBarsMultiTFForSymbol(symbol, false) : GetConsecutiveBarsMultiTFForSymbol(symbol, true); double mtfConfluence = ClampValue((double)dirStreakMtf / 5.0, 0.0, 1.0); double dllTrendStrength = marketState.priceTrend; if(PreferConsecutiveBarsForDll) { double localStreakNorm = ClampValue((double)consecutiveBars / 5.0, 0.0, 1.0); double combinedStreak = ClampValue(localStreakNorm * 0.7 + mtfConfluence * 0.3, 0.0, 1.0); double dirSign = (direction >= 0) ? 1.0 : -1.0; dllTrendStrength = dirSign * (combinedStreak * 20.0); } double drawdownPct = GetAccountDrawdownPercentAuto(); double sessionQuality = GetCurrentSessionConfidence(); double rewardScore = GetSetupRewardScore(direction, GetBarsBucket(consecutiveBars), GetVolumeBucket(volumeRatio), marketState.currentRegime); double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); double accountEquity = AccountInfoDouble(ACCOUNT_EQUITY); double equityToBalance = (accountBalance > 0.0) ? (accountEquity / accountBalance) : 1.0; double equityDdPct = (accountBalance > 0.0) ? ClampValue(((accountBalance - accountEquity) / accountBalance) * 100.0, 0.0, 100.0) : 0.0; double avgTickVol = GetAverageTickVolumeForSymbol(symbol, (ENUM_TIMEFRAMES)Period(), VolumeSignalLookback); double tickVolumeRatio = (avgTickVol > 0.0) ? ((double)iTickVolume(symbol, (ENUM_TIMEFRAMES)Period(), 0) / avgTickVol) : 1.0; int bullishStreak = GetConsecutiveBarsForSymbol(symbol, (ENUM_TIMEFRAMES)Period(), true); int bearishStreak = GetConsecutiveBarsForSymbol(symbol, (ENUM_TIMEFRAMES)Period(), false); int streakDir = 0; int streakLen = 0; if(bullishStreak > bearishStreak && bullishStreak > 0) { streakDir = 1; streakLen = bullishStreak; } else if(bearishStreak > bullishStreak && bearishStreak > 0) { streakDir = -1; streakLen = bearishStreak; } DC_FillSetupFeaturesSafe(features, symbol, (int)Period(), direction, nowDt.hour, nowDt.day_of_week, marketState.marketStress ? 1 : 0, spreadPts, volumeRatio, consecutiveBars, marketState.currentVolatility, dllTrendStrength, drawdownPct, recentWr, sessionQuality, rewardScore, strongBar ? 1 : 0, zoneScore, liquidityDistanceNorm, mtfConfluence, decisionSetupId, equityDdPct, equityToBalance, tickVolumeRatio, streakDir, streakLen); DC_DecisionOut dOut; int dcRc = 0; bool scoreOk = DC_ScoreSafe(features, dOut, dcRc); if(scoreOk) { decisionScore = (int)MathRound(ClampValue(dOut.decision_score_01, 0.0, 1.0) * 10.0); decisionScore = (int)ClampValue((double)decisionScore, 1.0, 10.0); useExploreLot = (dOut.action == 2); memoryAdaptiveRiskMultiplier = ClampValue(dOut.risk_multiplier, 0.15, 1.25); SetPendingDecisionActionForSymbol(symbol, dOut.action, decisionSetupId); string regimeText = DC_BytesToString(dOut.regime); string reasonSummary = BuildBlockReasonSummary(symbol, spreadPts, volumeRatio, strongBar, zoneScore, liquidityDistanceNorm, mtfConfluence, features.drawdown_pct, features.recent_win_rate, features.session_quality, (features.market_stress != 0), regimeText); g_lastDllDecisionAction = DC_ActionToText(dOut.action); g_lastDllDecisionSymbol = symbol; g_lastDllDecisionRegime = regimeText; g_lastDllDecisionScore = decisionScore; g_lastDllDecisionAllowProb = dOut.allow_probability; g_lastDllDecisionRiskMul = memoryAdaptiveRiskMultiplier; g_lastDllDecisionLatencyUs = dOut.latency_us; bool thresholdPass = (decisionScore >= threshold); if(!thresholdPass) { if(ShouldAllowControlledExploreForSymbol(symbol, decisionScore, threshold)) { useExploreLot = true; memoryDecisionAllow++; memoryDecisionExplore++; memoryAdaptiveRiskMultiplier = MathMin(memoryAdaptiveRiskMultiplier, 0.35); SetPendingDecisionActionForSymbol(symbol, 2, decisionSetupId); Print("DLL DECISION [", symbol, "] | ALLOW_EXPLORE | score=", decisionScore, "/10", " | threshold=", threshold, " | threshold_pass=0", " | p_allow=", DoubleToString(dOut.allow_probability, 3), " | regime=", regimeText, " | latency_us=", DoubleToString(dOut.latency_us, 0), " | mode=below_threshold"); return false; } memoryDecisionBlock++; SetPendingDecisionActionForSymbol(symbol, 0, decisionSetupId); Print("DLL DECISION [", symbol, "] | BLOCK | score=", decisionScore, "/10", " | threshold=", threshold, " | threshold_pass=0", " | p_allow=", DoubleToString(dOut.allow_probability, 3), " | regime=", regimeText, " | latency_us=", DoubleToString(dOut.latency_us, 0), " | ", reasonSummary); return true; } if(dOut.action == 0) { int triggerBars = (direction == 1) ? BearishBarsForBuy : BullishBarsForSell; double minBootstrapVolume = MathMax(0.2, MinVolumeRatio * 0.75); bool bootstrapSignalReady = (ArraySize(tradeHistory) < 20 && consecutiveBars >= triggerBars && volumeRatio >= minBootstrapVolume); if(bootstrapSignalReady) { int bootstrapSignalN = MathMax(2, ExploreOneInN - 1); int bootstrapSignalCounter = IncrementExploreCounterForSymbol(symbol); if((bootstrapSignalCounter % bootstrapSignalN) == 0) { useExploreLot = true; memoryDecisionAllow++; memoryDecisionExplore++; memoryAdaptiveRiskMultiplier = MathMin(memoryAdaptiveRiskMultiplier, 0.35); SetPendingDecisionActionForSymbol(symbol, 2, decisionSetupId); Print("DLL DECISION [", symbol, "] | ALLOW_EXPLORE | score=", decisionScore, "/10", " | threshold=", threshold, " | p_allow=", DoubleToString(dOut.allow_probability, 3), " | regime=", regimeText, " | latency_us=", DoubleToString(dOut.latency_us, 0), " | mode=bootstrap_consecutive_signal"); return false; } } bool canExploreFromProb = UseControlledExplore && (!smallAccount || AllowExploreOnSmallAccounts); if(canExploreFromProb && dOut.allow_probability >= DllExploreProbThreshold) { useExploreLot = true; memoryDecisionAllow++; memoryDecisionExplore++; memoryAdaptiveRiskMultiplier = MathMin(memoryAdaptiveRiskMultiplier, 0.35); SetPendingDecisionActionForSymbol(symbol, 2, decisionSetupId); Print("DLL DECISION [", symbol, "] | ALLOW_EXPLORE | score=", decisionScore, "/10", " | threshold=", threshold, " | p_allow=", DoubleToString(dOut.allow_probability, 3), " | regime=", regimeText, " | latency_us=", DoubleToString(dOut.latency_us, 0), " | mode=p_allow_override"); return false; } if(ShouldAllowControlledExploreForSymbol(symbol, decisionScore, threshold)) { useExploreLot = true; memoryDecisionAllow++; memoryDecisionExplore++; memoryAdaptiveRiskMultiplier = MathMin(memoryAdaptiveRiskMultiplier, 0.35); SetPendingDecisionActionForSymbol(symbol, 2, decisionSetupId); Print("DLL DECISION [", symbol, "] | ALLOW_EXPLORE | score=", decisionScore, "/10", " | threshold=", threshold, " | p_allow=", DoubleToString(dOut.allow_probability, 3), " | regime=", regimeText, " | latency_us=", DoubleToString(dOut.latency_us, 0), " | mode=bootstrap_memory"); return false; } memoryDecisionBlock++; SetPendingDecisionActionForSymbol(symbol, 0, decisionSetupId); Print("DLL DECISION [", symbol, "] | BLOCK | score=", decisionScore, "/10", " | threshold=", threshold, " | p_allow=", DoubleToString(dOut.allow_probability, 3), " | regime=", regimeText, " | latency_us=", DoubleToString(dOut.latency_us, 0), " | ", reasonSummary); return true; } memoryDecisionAllow++; if(dOut.action == 2) memoryDecisionExplore++; Print("DLL DECISION [", symbol, "] | ", DC_ActionToText(dOut.action), " | score=", decisionScore, "/10", " | threshold=", threshold, " | threshold_pass=", (thresholdPass ? "1" : "0"), " | p_allow=", DoubleToString(dOut.allow_probability, 3), " | riskMul=", DoubleToString(memoryAdaptiveRiskMultiplier, 2), " | regime=", regimeText, " | latency_us=", DoubleToString(dOut.latency_us, 0)); return false; } else { Print("DLL DECISION [", symbol, "] | INVALID_SCORE | rc=", dcRc, " | fallback=memory_score"); } } string scoreDetails = ""; decisionScore = GetDecisionScore10ForSymbol(symbol, direction, consecutiveBars, volumeRatio, bid, ask, scoreDetails); if(decisionScore >= threshold) { memoryDecisionAllow++; SetPendingDecisionActionForSymbol(symbol, 1); Print("MEMORY SCORE: ", decisionScore, "/10 | threshold ", threshold, " | regime ", marketState.currentRegime, " | decision: ALLOW | ", scoreDetails); return false; } if(ShouldAllowControlledExploreForSymbol(symbol, decisionScore, threshold)) { useExploreLot = true; memoryDecisionAllow++; memoryDecisionExplore++; SetPendingDecisionActionForSymbol(symbol, 2); Print("MEMORY SCORE: ", decisionScore, "/10 | threshold ", threshold, " | regime ", marketState.currentRegime, " | decision: ALLOW_EXPLORE (reduced lot) | ", scoreDetails); return false; } memoryDecisionBlock++; SetPendingDecisionActionForSymbol(symbol, 0); double symbolPointFallback = SymbolInfoDouble(symbol, SYMBOL_POINT); if(symbolPointFallback <= 0.0) symbolPointFallback = Point(); double spreadPtsFallback = (symbolPointFallback > 0.0) ? (MathAbs(ask - bid) / symbolPointFallback) : 0.0; bool strongBarFallback = (volumeRatio >= VolumeSignalFactor); double refPriceFallback = (direction == 1) ? ask : bid; double zoneScoreFallback = GetVolumeProfileProximityScoreForSymbol(symbol, direction, refPriceFallback); double liqDistPtsFallback = 0.0; FindNearestLiquidityZone(refPriceFallback, (direction == 1) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL, liqDistPtsFallback); double maxLiqDistancePtsFallback = GetLiquidityZoneDistancePointsForSymbol(symbol); double liqNormFallback = (maxLiqDistancePtsFallback > 0.0) ? ClampValue(liqDistPtsFallback / maxLiqDistancePtsFallback, 0.0, 1.0) : 1.0; int mtfStreakFallback = (direction == 1) ? GetConsecutiveBarsMultiTFForSymbol(symbol, false) : GetConsecutiveBarsMultiTFForSymbol(symbol, true); double mtfConfluenceFallback = ClampValue((double)mtfStreakFallback / 5.0, 0.0, 1.0); string fallbackReasonSummary = BuildBlockReasonSummary(symbol, spreadPtsFallback, volumeRatio, strongBarFallback, zoneScoreFallback, liqNormFallback, mtfConfluenceFallback, GetAccountDrawdownPercentAuto(), MathMax(0.0, GetRecentWinRateAuto(30)), GetCurrentSessionConfidence(), marketState.marketStress, marketState.currentRegime); Print("MEMORY SCORE: ", decisionScore, "/10 | threshold ", threshold, " | regime ", marketState.currentRegime, " | decision: BLOCK | ", scoreDetails, " | ", fallbackReasonSummary); return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool ShouldSkipTradeBasedOnMemory(int direction, int consecutiveBars, double volumeRatio, double bid, double ask, bool &useExploreLot, int &decisionScore) { return ShouldSkipTradeBasedOnMemoryForSymbol(Symbol(), direction, consecutiveBars, volumeRatio, bid, ask, useExploreLot, decisionScore); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int GetConsecutiveBarsForSymbol(string symbol, ENUM_TIMEFRAMES tf, bool countBullish); int GetConsecutiveBarsMultiTFForSymbol(string symbol, bool countBullish); double GetCurrentVolumeRatioForSymbol(string symbol, ENUM_TIMEFRAMES tf); //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void SelectEntryProfileForSymbol(string symbol, int direction, double bid, double ask, double &tpMultiplier, double &slMultiplier, string &profileLabel, int &decisionScore) { tpMultiplier = 1.0; slMultiplier = 1.0; profileLabel = "NORMAL"; decisionScore = 5; if(!UseEntryProfileTP_SL) { return; } ENUM_TIMEFRAMES tf = (ENUM_TIMEFRAMES)Period(); int consecutiveBars = (direction == 1) ? GetConsecutiveBarsForSymbol(symbol, tf, false) : GetConsecutiveBarsForSymbol(symbol, tf, true); double volumeRatio = GetCurrentVolumeRatioForSymbol(symbol, tf); string details = ""; decisionScore = GetDecisionScore10ForSymbol(symbol, direction, consecutiveBars, volumeRatio, bid, ask, details); bool longTerm = (decisionScore >= LongTermScoreThreshold && !marketState.marketStress && volumeRatio >= StrongMomentumThreshold); bool shortTerm = (decisionScore <= ShortTermScoreThreshold || marketState.marketStress); if(longTerm) { tpMultiplier = LongTermTPMultiplier; slMultiplier = LongTermSLMultiplier; profileLabel = "LONG_TERM"; } else if(shortTerm) { tpMultiplier = ShortTermTPMultiplier; slMultiplier = ShortTermSLMultiplier; profileLabel = "SHORT_TERM"; } tpMultiplier = ClampValue(tpMultiplier, 0.6, 1.6); slMultiplier = ClampValue(slMultiplier, 0.6, 1.6); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void SelectEntryProfile(int direction, double bid, double ask, double &tpMultiplier, double &slMultiplier, string &profileLabel, int &decisionScore) { SelectEntryProfileForSymbol(Symbol(), direction, bid, ask, tpMultiplier, slMultiplier, profileLabel, decisionScore); } //+------------------------------------------------------------------+ // UTILITY FUNCTIONS - Memory System Support //+------------------------------------------------------------------+ double GetCurrentVolumeRatio() { long currentVolume = UseTickVolume ? iTickVolume(Symbol(), Period(), 0) : iVolume(Symbol(), Period(), 0); if(!UseTickVolume && currentVolume <= 0) Print("Real volume unavailable (0) - broker did not provide volume for current bar."); long sumVolume = 0; int lookback = MathMin(VolumeSignalLookback, 200); if(lookback <= 0) return 1.3; for(int i = 1; i <= lookback; i++) { sumVolume += UseTickVolume ? iTickVolume(Symbol(), Period(), i) : iVolume(Symbol(), Period(), i); } long avgVolume = sumVolume / lookback; return (avgVolume > 0) ? ((double)currentVolume / avgVolume) : 1.3; } //+------------------------------------------------------------------+ // Multi-symbol helpers (same validation pipeline per symbol) //+------------------------------------------------------------------+ double GetCurrentVolumeRatioForSymbol(string symbol, ENUM_TIMEFRAMES tf) { long currentVolume = UseTickVolume ? iTickVolume(symbol, tf, 0) : iVolume(symbol, tf, 0); long sumVolume = 0; int lookback = MathMin(VolumeSignalLookback, 200); if(lookback <= 0) return 1.3; for(int i = 1; i <= lookback; i++) { sumVolume += UseTickVolume ? iTickVolume(symbol, tf, i) : iVolume(symbol, tf, i); } long avgVolume = sumVolume / lookback; return (avgVolume > 0) ? ((double)currentVolume / avgVolume) : 1.3; } // Returns consecutive bullish/bearish bars on closed candles (signal mapping depends on mode). int GetConsecutiveBarsForSymbol(string symbol, ENUM_TIMEFRAMES tf, bool countBullish) { int streak = 0; int maxLookback = 30; for(int shift = 1; shift <= maxLookback; shift++) { double o = iOpen(symbol, tf, shift); double c = iClose(symbol, tf, shift); if(o == 0.0 && c == 0.0) break; bool isBullish = (c > o); bool isBearish = (c < o); bool sameDirection = countBullish ? isBullish : isBearish; if(!sameDirection) break; if(UseVolumeInSignals) { long currVol = iTickVolume(symbol, tf, shift); long sumVol = 0; int counted = 0; int lookback = MathMin(VolumeSignalLookback, 50); for(int i = shift + 1; i <= shift + lookback; i++) { long v = iTickVolume(symbol, tf, i); if(v <= 0) continue; sumVol += v; counted++; } if(counted > 0) { double avgVol = (double)sumVol / (double)counted; double volThreshold = VolumeSignalFactor * avgVol; // For MTF streak counting, weak-volume bars are still counted. } } streak++; } return streak; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int GetConsecutiveBarsMultiTFForSymbol(string symbol, bool countBullish) { int bestStreak = 0; for(int i = 0; i < 5; i++) { ENUM_TIMEFRAMES tf = (ENUM_TIMEFRAMES)TIMEFRAMES[i]; int streak = GetConsecutiveBarsForSymbol(symbol, tf, countBullish); if(streak > bestStreak) bestStreak = streak; } return bestStreak; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CountOpenTradesOfTypeForSymbol(string symbol, int pos_type) { int cnt = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) != symbol) continue; if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; if(PositionGetInteger(POSITION_TYPE) == pos_type) cnt++; } return cnt; } struct SymbolEntryGuard { string symbol; int lastBuyBar; int lastSellBar; }; SymbolEntryGuard symbolEntryGuards[]; struct AutoCloseSymbolState { string symbol; bool triggered; datetime timestamp; bool inTrailing; double highestProfitInTrailing; }; AutoCloseSymbolState autoCloseSymbolStates[]; struct AutoCloseBasketLearningContext { string symbol; bool active; bool learnSent; datetime activatedAt; datetime expiresAt; double basketProfitAtTrigger; double drawdownPctAtTrigger; int tradeCountAtTrigger; bool recoveryModeAtTrigger; }; AutoCloseBasketLearningContext autoCloseLearningContexts[]; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int FindSymbolEntryGuardIndex(string symbol) { for(int i = 0; i < ArraySize(symbolEntryGuards); i++) { if(symbolEntryGuards[i].symbol == symbol) return i; } int idx = ArraySize(symbolEntryGuards); ArrayResize(symbolEntryGuards, idx + 1); symbolEntryGuards[idx].symbol = symbol; symbolEntryGuards[idx].lastBuyBar = -1; symbolEntryGuards[idx].lastSellBar = -1; return idx; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CanEnterSymbolDirectionOnBar(string symbol, int direction, int barId) { int idx = FindSymbolEntryGuardIndex(symbol); if(direction == 1) return symbolEntryGuards[idx].lastBuyBar != barId; return symbolEntryGuards[idx].lastSellBar != barId; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void MarkSymbolDirectionEnteredOnBar(string symbol, int direction, int barId) { int idx = FindSymbolEntryGuardIndex(symbol); if(direction == 1) symbolEntryGuards[idx].lastBuyBar = barId; else symbolEntryGuards[idx].lastSellBar = barId; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int FindAutoCloseStateIndex(string symbol, bool createIfMissing = false) { for(int i = 0; i < ArraySize(autoCloseSymbolStates); i++) { if(autoCloseSymbolStates[i].symbol == symbol) return i; } if(!createIfMissing) return -1; int idx = ArraySize(autoCloseSymbolStates); ArrayResize(autoCloseSymbolStates, idx + 1); autoCloseSymbolStates[idx].symbol = symbol; autoCloseSymbolStates[idx].triggered = false; autoCloseSymbolStates[idx].timestamp = 0; autoCloseSymbolStates[idx].inTrailing = false; autoCloseSymbolStates[idx].highestProfitInTrailing = 0.0; return idx; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int FindAutoCloseLearningContextIndex(string symbol, bool createIfMissing = false) { for(int i = 0; i < ArraySize(autoCloseLearningContexts); i++) { if(autoCloseLearningContexts[i].symbol == symbol) return i; } if(!createIfMissing) return -1; int idx = ArraySize(autoCloseLearningContexts); ArrayResize(autoCloseLearningContexts, idx + 1); autoCloseLearningContexts[idx].symbol = symbol; autoCloseLearningContexts[idx].active = false; autoCloseLearningContexts[idx].learnSent = false; autoCloseLearningContexts[idx].activatedAt = 0; autoCloseLearningContexts[idx].expiresAt = 0; autoCloseLearningContexts[idx].basketProfitAtTrigger = 0.0; autoCloseLearningContexts[idx].drawdownPctAtTrigger = 0.0; autoCloseLearningContexts[idx].tradeCountAtTrigger = 0; autoCloseLearningContexts[idx].recoveryModeAtTrigger = false; return idx; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void RegisterAutoCloseLearningContext(string symbol, double basketProfit, datetime nowTime, int tradeCount, bool recoveryMode) { int idx = FindAutoCloseLearningContextIndex(symbol, true); autoCloseLearningContexts[idx].basketProfitAtTrigger = basketProfit; autoCloseLearningContexts[idx].drawdownPctAtTrigger = GetAccountDrawdownPercentAuto(); autoCloseLearningContexts[idx].tradeCountAtTrigger = MathMax(1, tradeCount); autoCloseLearningContexts[idx].recoveryModeAtTrigger = recoveryMode; autoCloseLearningContexts[idx].activatedAt = nowTime; autoCloseLearningContexts[idx].expiresAt = nowTime + 300; // valid 5 min for close wave autoCloseLearningContexts[idx].active = (basketProfit > 0.0); autoCloseLearningContexts[idx].learnSent = false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsAutoCloseLearningPositiveContext(string symbol, datetime nowTime) { int idx = FindAutoCloseLearningContextIndex(symbol, false); if(idx < 0) return false; if(!autoCloseLearningContexts[idx].active) return false; if(nowTime > autoCloseLearningContexts[idx].expiresAt) { autoCloseLearningContexts[idx].active = false; return false; } return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ClearAutoCloseLearningContext(string symbol) { int idx = FindAutoCloseLearningContextIndex(symbol, false); if(idx < 0) return; autoCloseLearningContexts[idx].active = false; autoCloseLearningContexts[idx].learnSent = false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void SyncChartAutoCloseState() { int idx = FindAutoCloseStateIndex(Symbol(), false); if(idx < 0) { autoCloseTriggered = false; autoCloseTimestamp = 0; isInAutoCloseTrailing = false; highestProfitInTrailing = 0.0; return; } autoCloseTriggered = autoCloseSymbolStates[idx].triggered; autoCloseTimestamp = autoCloseSymbolStates[idx].timestamp; isInAutoCloseTrailing = autoCloseSymbolStates[idx].inTrailing; highestProfitInTrailing = autoCloseSymbolStates[idx].highestProfitInTrailing; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsPositionManagedByEA() { long posMagic = PositionGetInteger(POSITION_MAGIC); if(posMagic == MagicNumber) return true; // Compatibility fallback for legacy/edge executions where magic may differ. string comment = PositionGetString(POSITION_COMMENT); if(StringFind(comment, "King Gold Scalper") >= 0) return true; if(StringFind(comment, "Multi-TF Confluence") >= 0) return true; return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CountOpenTradesForSymbolMagic(string symbol) { int count = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) != symbol) continue; if(!IsPositionManagedByEA()) continue; count++; } return count; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double CalculateTotalProfitForSymbol(string symbol) { double totalProfit = 0.0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) != symbol) continue; if(!IsPositionManagedByEA()) continue; totalProfit += PositionGetDouble(POSITION_PROFIT); } return totalProfit; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CloseAllTradesForSymbol(string symbol) { int maxAttempts = 3; for(int attempt = 1; attempt <= maxAttempts; attempt++) { int remainingBefore = 0; int closedNow = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) != symbol) continue; if(!IsPositionManagedByEA()) continue; remainingBefore++; if(trade.PositionClose(ticket)) closedNow++; else Print("AUTOCLOSE FAILED [", symbol, "] attempt=", attempt, " ticket=", ticket, " | retcode=", trade.ResultRetcode(), " | ", trade.ResultRetcodeDescription()); } if(remainingBefore == 0) return; if(attempt < maxAttempts) { Sleep(120); } } int remainingAfter = CountOpenTradesForSymbolMagic(symbol); if(remainingAfter > 0) Print("AUTOCLOSE WARNING [", symbol, "] still open after retries: ", remainingAfter); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void GetAutoCloseActiveSymbols(string &symbols[]) { ArrayResize(symbols, 0); for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(!PositionSelectByTicket(ticket)) continue; if(!IsPositionManagedByEA()) continue; string symbol = PositionGetString(POSITION_SYMBOL); bool exists = false; for(int j = 0; j < ArraySize(symbols); j++) { if(symbols[j] == symbol) { exists = true; break; } } if(!exists) { int idx = ArraySize(symbols); ArrayResize(symbols, idx + 1); symbols[idx] = symbol; } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsSymbolAutoCloseBlocked(string symbol, datetime gateTime) { if(AutoCloseResumeTrading) return false; int idx = FindAutoCloseStateIndex(symbol, false); if(idx < 0 || !autoCloseSymbolStates[idx].triggered) return false; if(autoCloseSymbolStates[idx].timestamp == 0) autoCloseSymbolStates[idx].timestamp = gateTime; MqlDateTime nowDt; TimeToStruct(gateTime, nowDt); MqlDateTime closedDt; TimeToStruct(autoCloseSymbolStates[idx].timestamp, closedDt); bool newDayReached = (nowDt.year > closedDt.year) || (nowDt.mon > closedDt.mon) || (nowDt.day > closedDt.day); bool timeWindowOk = IsHourInWindow(nowDt.hour, StartHour, EndHour); if(newDayReached && timeWindowOk) { Print(" NEW DAY REACHED [", symbol, "] - Re-entry enabled at ", nowDt.hour, ":00"); autoCloseSymbolStates[idx].triggered = false; autoCloseSymbolStates[idx].timestamp = 0; autoCloseSymbolStates[idx].inTrailing = false; autoCloseSymbolStates[idx].highestProfitInTrailing = 0.0; SyncChartAutoCloseState(); return false; } return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void MarkSymbolAutoCloseTriggered(string symbol, datetime triggerTime) { int idx = FindAutoCloseStateIndex(symbol, true); autoCloseSymbolStates[idx].timestamp = triggerTime; autoCloseSymbolStates[idx].triggered = !AutoCloseResumeTrading; autoCloseSymbolStates[idx].inTrailing = false; autoCloseSymbolStates[idx].highestProfitInTrailing = 0.0; if(autoCloseSymbolStates[idx].triggered) Print(" AUTO CLOSE TRIGGERED [", symbol, "] - Stopping new trades until next session (Time: ", TimeToString(autoCloseSymbolStates[idx].timestamp, TIME_DATE|TIME_SECONDS), ")"); else Print(" AUTO CLOSE TRIGGERED [", symbol, "] - Trading remains enabled this session (Time: ", TimeToString(autoCloseSymbolStates[idx].timestamp, TIME_DATE|TIME_SECONDS), ")"); SyncChartAutoCloseState(); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckAllFiltersForTradeForSymbol(string symbol, int positionType, double bid, double ask, int consecutiveBars) { string direction = (positionType == POSITION_TYPE_BUY) ? "BUY" : "SELL"; bool allFiltersPassed = true; // Spread check long spreadPoints = 0; SymbolInfoInteger(symbol, SYMBOL_SPREAD, spreadPoints); long maxSpreadInPoints = (long)(MaxSpread * pip_value); if(spreadPoints > maxSpreadInPoints) allFiltersPassed = false; // Volume check if(UseVolumeFilter) { long currentVolume = UseTickVolume ? iTickVolume(symbol, Period(), 0) : iVolume(symbol, Period(), 0); long sumVolume = 0; for(int i = 1; i <= 200; i++) { sumVolume += UseTickVolume ? iTickVolume(symbol, Period(), i) : iVolume(symbol, Period(), i); } long avgVolume = sumVolume / 200; double volumeRatio = (avgVolume > 0) ? ((double)currentVolume / avgVolume) : 1.3; double requiredVolumeRatio = MinVolumeRatio; if(safeModeActive) requiredVolumeRatio *= SafeVolumeEntryMultiplier; if(volumeRatio < requiredVolumeRatio) allFiltersPassed = false; } // DOM check: strict only for chart symbol where DOM context exists if(UseDOMFilter && !ForceDisableDOM && domFilterActive && domInitialized) { if(symbol == Symbol()) { if(!DOMAsSignal && !CheckDOMFilter(positionType, bid, ask)) allFiltersPassed = false; } else { if(!DOMAsSignal) allFiltersPassed = false; } } // Anti-extreme check: avoid buying near recent highs / selling near recent lows if(UseAntiExtremeFilter) { int lookbackBars = MathMax(10, AntiExtremeLookbackBars); double minDistancePrice = ConvertInputDistanceToPriceForSymbol(symbol, AntiExtremeMinDistance); if(minDistancePrice < 0.0) minDistancePrice = 0.0; int highShift = iHighest(symbol, (ENUM_TIMEFRAMES)Period(), MODE_HIGH, lookbackBars, 1); int lowShift = iLowest(symbol, (ENUM_TIMEFRAMES)Period(), MODE_LOW, lookbackBars, 1); double recentHigh = (highShift >= 0) ? iHigh(symbol, (ENUM_TIMEFRAMES)Period(), highShift) : 0.0; double recentLow = (lowShift >= 0) ? iLow(symbol, (ENUM_TIMEFRAMES)Period(), lowShift) : 0.0; if(positionType == POSITION_TYPE_BUY && recentHigh > 0.0 && ask >= (recentHigh - minDistancePrice)) allFiltersPassed = false; if(positionType == POSITION_TYPE_SELL && recentLow > 0.0 && bid <= (recentLow + minDistancePrice)) allFiltersPassed = false; } if(ShouldEmitFilterStatusLog(symbol, direction, allFiltersPassed, "symbol_filters")) { if(allFiltersPassed) Print(" ALL FILTERS PASSED [", direction, "][", symbol, "] | Consecutive=", consecutiveBars); else Print(" FILTERS BLOCKED [", direction, "][", symbol, "] | Consecutive=", consecutiveBars); } return allFiltersPassed; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void UpdateMarketStressDetection() { static bool wasStress = false; // Check stress conditions once and emit logs only on state transitions. bool highVolStress = (marketState.currentVolatility > marketState.avgVolatility * 2.0); bool lossStress = (marketState.consecutiveLosses >= 5); UpdateAccountCache(); double drawdown = (accountCache.balance > 0.0) ? (((accountCache.balance - accountCache.equity) / accountCache.balance) * 100.0) : 0.0; bool drawdownStress = (drawdown > 15.0); marketState.marketStress = (highVolStress || lossStress || drawdownStress); if(marketState.marketStress && !wasStress) { if(highVolStress) Print("STRESS: High volatility detected"); if(lossStress) Print("STRESS: Consecutive losses detected"); if(drawdownStress) Print("STRESS: High drawdown detected: ", DoubleToString(drawdown, 1), "%"); } if(!marketState.marketStress && wasStress) Print("STRESS: Conditions normalized"); wasStress = marketState.marketStress; } //+------------------------------------------------------------------+ // GLOBAL VARIABLES //+------------------------------------------------------------------+ double point_value; double pip_value; // Value of 1 pip in points (for spread conversion) int MaxSpreadRuntime; // Runtime-adjustable spread limit (tester-safe) int lastLossBar = -999; // Bar number when last loss occurred datetime lastLossTime = 0; // Time when last loss occurred (for cooldown) int consecutiveBullishBars = 0; // Counter for consecutive bullish (green) bars int consecutiveBearishBars = 0; // Counter for consecutive bearish (red) bars // Learning telemetry counters long memoryDecisionTotal = 0; long memoryDecisionAllow = 0; long memoryDecisionBlock = 0; long memoryDecisionExplore = 0; long setupRewardUpdates = 0; int lastBullishSignalBar = -1; // Track last bar we triggered bullish signal on int lastBearishSignalBar = -1; // Track last bar we triggered bearish signal on int lastMemoryBlockSellBar = -1; // Track last bar SELL was blocked by memory gate int lastMemoryBlockBuyBar = -1; // Track last bar BUY was blocked by memory gate datetime lastProcessedBarTime = 0; // Track which closed bar we already processed to avoid recounting bool autoCloseTriggered = false; // Track if AutoCloseProfit was just triggered datetime autoCloseTimestamp = 0; // Time when auto-close was triggered (full timestamp for rollover safety) bool isInAutoCloseTrailing = false; // Track if we're in trailing close mode double highestProfitInTrailing = 0; // Track highest profit during trailing mode double lastBalance = 0; // Track balance for sentiment int winCount = 0; // Count winning trades int lossCount = 0; // Count losing trades double lastDealProfit = 0; // Last trade profit/loss ulong lastProcessedDealTicket = 0; // Track last processed deal to avoid double counting double currentRiskPercent = 1.0; // Adaptive risk percent (changes based on performance) double memoryAdaptiveRiskMultiplier = 1.0; // Memory-driven multiplier applied in lot sizing string performanceMode = "NORMAL"; // Current performance mode: AGGRESSIVE, NORMAL, CONSERVATIVE string chartComment = ""; // Store text to display on chart string greedLevel = "MODERATE"; // Signal-based greed: GREEDY, MODERATE, CONSERVATIVE int trendDirection = 0; // 1 = up, -1 = down, 0 = neutral (for display) bool safeModeActive = false; // Safe mode if balance below threshold int currentMaxOpenTrades = 0; // Dynamic max trades (normal or safe) double queenAccountHealthScore = 0.50; // 0..1 account health (display only) double queenAccountDrawdownPct = 0.0; // Equity drawdown vs balance in % double queenMarginLevel = 0.0; // Current margin level % double queenExposureLots = 0.0; // Total open + pending lots (all symbols) // Tick-bar construction state int tickBarIndex = 0; // Synthetic tick-bar index used for gating signals int tickBarCount = 0; // How many ticks are in the current synthetic bar double tickBarOpen = 0; // Open price of the current synthetic bar double tickBarHigh = 0; // High price of the current synthetic bar double tickBarLow = 0; // Low price of the current synthetic bar double tickBarClose = 0; // Close price of the current synthetic bar datetime lastHistoryUpdate = 0; // Last time we refreshed deal history datetime lastDynamicAdjust = 0; // Last time we adjusted SL/TP dynamically int dynamicSLTPThrottleSeconds = 5; // Minimum seconds between dynamic SL/TP adjustments ulong processedTickets[]; // Track processed deal tickets to avoid double counting CTrade trade; datetime lastLimitRenewal = 0; // Throttle limit order renewal checks datetime lastHeartbeatLog = 0; // Throttle heartbeat logs datetime lastBestSessionReport = 0; // Throttle best-session reports string heartbeatStatus = "Heartbeat: INIT"; bool nextTradeUseExploreLot = false; // Lot reduction flag for controlled explore entries int exploreSignalCounter = 0; // Counter used by 1-in-N controlled explore gate bool recoveryModeActive = false; // DD-based recovery mode flag bool recoveryHoldConfiguredTarget = false; // In recovery: true = keep normal AutoClose targets uint lastFastSpikeSignalMs = 0; // Last tick-count when a spike-reaction order was attempted uint fastSpikeReferenceMs = 0; // Tick-count baseline for spike measurement double fastSpikeReferencePrice = 0.0; // Baseline price for spike measurement datetime fastSpikeAnchorBarTime = 0; // Current anchor bar time (stable during the bar) double fastSpikeAnchorOpen = 0.0; // Current anchor bar open price bool fastSpikePending = false; // Pending spike waiting for break+hold confirmation int fastSpikePendingDirection = 0; // 1=up/buy, -1=down/sell uint fastSpikePendingStartMs = 0; // Pending start tick-count double fastSpikePendingAnchorPrice = 0.0; // Anchor price captured at spike detect int fastSpikePendingThresholdPoints = 0; // Threshold used for pending confirmation level double fastSpikePendingMovePoints = 0.0; // Initial move detected at pending start int fastSpikePendingHoldTicks = 0; // Consecutive hold ticks beyond break level datetime lastStateSaveTime = 0; // Autosave throttle for persistence // Voice command queue (SQLite) long g_cmdDb = -1; string g_cmdDbPath = "C:\\sqlite_cmdq\\cmd_queue.db"; string g_cmdInstance = "KOG-VOICE"; datetime g_lastCmdPoll = 0; int g_cmdPollIntervalSeconds = 1; bool tradingPaused = false; // MicCmd.dll (SAPI) voice input string g_voiceGrammarPath = ""; string g_voiceGrammarContent = "\n" "\n" " \n" " \n" " pause\n" " pause trading\n" " stop trading\n" " hold on\n" " resume\n" " resume trading\n" " continue trading\n" " close all\n" " close everything\n" " close positions\n" " panic close\n" " risk one percent\n" " risk two percent\n" " risk three percent\n" " risk four percent\n" " risk five percent\n" " set risk to one percent\n" " set risk to two percent\n" " set risk to three percent\n" " set risk to four percent\n" " set risk to five percent\n" " hello\n" " hi\n" " good morning\n" " good afternoon\n" " good evening\n" " how are you\n" " status\n" " status report\n" " \n" " \n" "\n"; string g_voiceLogPath = ""; bool g_micStarted = false; // Telegram runtime-safe copies (trimmed) string g_telegramBotToken = ""; string g_telegramChatId = ""; bool g_decisionDllReady = false; datetime g_lastDecisionSnapshotLog = 0; string g_lastDllDecisionAction = "N/A"; string g_lastDllDecisionSymbol = ""; string g_lastDllDecisionRegime = "UNKNOWN"; int g_lastDllDecisionScore = 5; double g_lastDllDecisionAllowProb = 0.0; double g_lastDllDecisionRiskMul = 1.0; double g_lastDllDecisionLatencyUs = 0.0; string g_lastDllSnapshot = ""; long g_lastDllSnapScoreCalls = -1; long g_lastDllSnapLearnCalls = -1; long g_lastDllSnapBasketLearnCalls = -1; long g_lastDllSnapCacheHits = -1; double g_lastDllSnapBaselineVol = -1.0; double g_lastDllSnapBaselineSpread = -1.0; // Alert throttling state datetime lastBuySoundTime = 0; datetime lastSellSoundTime = 0; datetime lastBlockedSoundTime = 0; datetime lastRiskSoundTime = 0; datetime lastProfitSoundTime = 0; datetime lastLossSoundTime = 0; datetime lastSpikeSoundTime = 0; datetime lastNewsSoundTime = 0; datetime lastFutureSoundTime = 0; datetime lastAutoCloseSoundTime = 0; datetime lastNewsScanTime = 0; int lastFutureStartAlertDateKey = 0; int lastFutureEndAlertDateKey = 0; bool newsCalendarWarnedUnavailable = false; datetime lastTelegramEntryTime = 0; datetime lastTelegramBlockedTime = 0; datetime lastTelegramRiskTime = 0; datetime lastTelegramCloseTime = 0; datetime lastTelegramNewsTime = 0; datetime lastTelegramFutureTime = 0; datetime lastTelegramSpikeTime = 0; // DOM (Order Book) Variables MqlBookInfo bookArray[]; // Array to store Order Book data bool domInitialized = false; // Track if DOM is initialized bool domAvailable = false; // Track if DOM is available (ECN broker detected) bool domFilterActive = false; // Track if DOM filter is actually active (after ECN check) datetime lastBookUpdate = 0; // Last time Order Book was updated // Cache instances - Global access for performance AccountCache accountCache; MarketDataCache marketCache; //+------------------------------------------------------------------+ // CACHE MANAGEMENT FUNCTIONS - Reduce API calls and improve performance //+------------------------------------------------------------------+ void UpdateAccountCache() { if(accountCache.needsUpdate()) { accountCache.update(); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void UpdateRecoveryMode() { if(!UseRecoveryAutoClose) { recoveryModeActive = false; return; } UpdateAccountCache(); double balance = accountCache.balance; double equity = accountCache.equity; if(balance <= 0.0) return; // Recovery mode only makes sense if we already have open trades to recover. if(CountOpenTradesAllSymbols() <= 0) { recoveryModeActive = false; return; } double ddPct = ((balance - equity) / balance) * 100.0; if(ddPct < 0.0) ddPct = 0.0; double ddStart = MathMax(0.1, RecoveryDDStart); double ddEnd = MathMax(0.0, MathMin(RecoveryDDEnd, ddStart - 0.1)); bool previous = recoveryModeActive; if(!recoveryModeActive && ddPct >= ddStart) recoveryModeActive = true; else if(recoveryModeActive && ddPct <= ddEnd) recoveryModeActive = false; if(previous != recoveryModeActive) { if(recoveryModeActive) Print("RECOVERY MODE ON | DD: ", DoubleToString(ddPct, 2), "% | AutoClose at >= $", DoubleToString(MathMax(0.0, RecoveryMinProfitUsd), 2)); else Print("RECOVERY MODE OFF | DD recovered to ", DoubleToString(ddPct, 2), "%"); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int CountOpenTradesOfType(int pos_type) { int cnt = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) != Symbol()) continue; if(PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; if(PositionGetInteger(POSITION_TYPE) == pos_type) cnt++; } return cnt; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool ShouldHoldConfiguredAutoCloseInRecoveryForSymbol(string symbol) { // Determine dominant open basket direction for this EA/symbol int buyCount = CountOpenTradesOfTypeForSymbol(symbol, (int)POSITION_TYPE_BUY); int sellCount = CountOpenTradesOfTypeForSymbol(symbol, (int)POSITION_TYPE_SELL); int direction = 0; if(buyCount > sellCount) direction = 1; else if(sellCount > buyCount) direction = -1; if(direction == 0) return false; double bid = SymbolInfoDouble(symbol, SYMBOL_BID); double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); double volumeRatio = GetCurrentVolumeRatioForSymbol(symbol, (ENUM_TIMEFRAMES)Period()); int consecutiveBars = (direction == 1) ? GetConsecutiveBarsMultiTFForSymbol(symbol, false) : GetConsecutiveBarsMultiTFForSymbol(symbol, true); string details = ""; int decisionScore = GetDecisionScore10ForSymbol(symbol, direction, consecutiveBars, volumeRatio, bid, ask, details); bool strongMomentum = (volumeRatio >= StrongMomentumThreshold); bool acceptableRegime = (marketState.currentRegime != "VOLATILE"); bool hold = (decisionScore >= LongTermScoreThreshold) || (decisionScore >= DecisionScoreThresholdNormal && strongMomentum && acceptableRegime); return hold; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool ShouldHoldConfiguredAutoCloseInRecovery() { return ShouldHoldConfiguredAutoCloseInRecoveryForSymbol(Symbol()); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void LogRecoveryTargetModeForSymbol(string symbol, bool holdConfiguredTarget) { static string loggedSymbols[]; static int loggedModes[]; int idx = -1; for(int i = 0; i < ArraySize(loggedSymbols); i++) { if(loggedSymbols[i] == symbol) { idx = i; break; } } if(idx < 0) { idx = ArraySize(loggedSymbols); ArrayResize(loggedSymbols, idx + 1); ArrayResize(loggedModes, idx + 1); loggedSymbols[idx] = symbol; loggedModes[idx] = -1; } int mode = holdConfiguredTarget ? 1 : 0; if(loggedModes[idx] == mode) return; if(mode == 1) Print("RECOVERY TARGET MODE [", symbol, "]: HOLD_CONFIGURED | Favorable analysis detected - using normal AutoClose target"); else Print("RECOVERY TARGET MODE [", symbol, "]: QUICK_CLOSE | Analysis not favorable - using fast positive close"); loggedModes[idx] = mode; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EmitHeartbeat() { if(!UseHeartbeat) return; datetime now = TimeCurrent(); int interval = HeartbeatIntervalSeconds; if(interval < 5) interval = 5; if(lastHeartbeatLog != 0 && (now - lastHeartbeatLog) < interval) return; lastHeartbeatLog = now; int openTrades = CountOpenTrades(); long spreadPoints = 0; SymbolInfoInteger(_Symbol, SYMBOL_SPREAD, spreadPoints); double spreadDisplay = (pip_value > 0.0) ? ((double)spreadPoints / pip_value) : (double)spreadPoints; heartbeatStatus = "Heartbeat: " + TimeToString(now, TIME_SECONDS) + " | Open: " + IntegerToString(openTrades) + " | Regime: " + marketState.currentRegime + " | MemRisk: " + DoubleToString(memoryAdaptiveRiskMultiplier, 2) + "x" + " | Spread: " + DoubleToString(spreadDisplay, 1); if(HeartbeatToJournal) Print(" HEARTBEAT | Time: ", TimeToString(now, TIME_DATE | TIME_SECONDS), " | OpenTrades: ", openTrades, " | Mode: ", performanceMode, " | Greed: ", greedLevel, " | Regime: ", marketState.currentRegime, " | MemRisk: ", DoubleToString(memoryAdaptiveRiskMultiplier, 2), "x", " | Spread: ", DoubleToString(spreadDisplay, 1)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string ExtractSnapshotValueByKey(const string json, const string key) { string token = "\"" + key + "\":"; int p = StringFind(json, token); if(p < 0) return ""; int n = StringLen(json); int s = p + StringLen(token); while(s < n) { int ch = StringGetCharacter(json, s); if(ch != 32 && ch != 9 && ch != 10 && ch != 13) break; s++; } int e = s; while(e < n) { int ch = StringGetCharacter(json, e); if(ch == 44 || ch == 125) // ',' or '}' break; e++; } if(e <= s) return ""; return StringSubstr(json, s, e - s); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void RefreshDecisionSnapshotParsedMetrics() { string scoreCallsText = ExtractSnapshotValueByKey(g_lastDllSnapshot, "score_calls"); string learnCallsText = ExtractSnapshotValueByKey(g_lastDllSnapshot, "learn_calls"); string basketLearnCallsText = ExtractSnapshotValueByKey(g_lastDllSnapshot, "basket_learn_calls"); string cacheHitsText = ExtractSnapshotValueByKey(g_lastDllSnapshot, "cache_hits"); string baselineVolText = ExtractSnapshotValueByKey(g_lastDllSnapshot, "baseline_vol"); string baselineSpreadText = ExtractSnapshotValueByKey(g_lastDllSnapshot, "baseline_spread"); g_lastDllSnapScoreCalls = (scoreCallsText == "") ? -1 : (long)StringToInteger(scoreCallsText); g_lastDllSnapLearnCalls = (learnCallsText == "") ? -1 : (long)StringToInteger(learnCallsText); g_lastDllSnapBasketLearnCalls = (basketLearnCallsText == "") ? -1 : (long)StringToInteger(basketLearnCallsText); g_lastDllSnapCacheHits = (cacheHitsText == "") ? -1 : (long)StringToInteger(cacheHitsText); g_lastDllSnapBaselineVol = (baselineVolText == "") ? -1.0 : StringToDouble(baselineVolText); g_lastDllSnapBaselineSpread = (baselineSpreadText == "") ? -1.0 : StringToDouble(baselineSpreadText); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EmitDecisionDllSnapshot() { if(!UseDecisionCoreDll || !g_decisionDllReady) return; int interval = MathMax(10, DecisionDllSnapshotIntervalSeconds); datetime now = TimeCurrent(); if(g_lastDecisionSnapshotLog != 0 && (now - g_lastDecisionSnapshotLog) < interval) return; g_lastDecisionSnapshotLog = now; uchar snapBuf[]; ArrayResize(snapBuf, 1024); int snapLen = dc_snapshot(snapBuf, 1024); if(snapLen > 0) { g_lastDllSnapshot = CharArrayToString(snapBuf, 0, snapLen); RefreshDecisionSnapshotParsedMetrics(); Print("DECISION DLL SNAPSHOT | ", g_lastDllSnapshot); } else { g_lastDllSnapScoreCalls = -1; g_lastDllSnapLearnCalls = -1; g_lastDllSnapBasketLearnCalls = -1; g_lastDllSnapCacheHits = -1; g_lastDllSnapBaselineVol = -1.0; g_lastDllSnapBaselineSpread = -1.0; Print("DECISION DLL SNAPSHOT | failed rc=", snapLen); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ string UrlEncodeUtf8(const string value) { uchar bytes[]; int len = StringToCharArray(value, bytes, 0, WHOLE_ARRAY, CP_UTF8); if(len <= 0) return ""; string encoded = ""; for(int i = 0; i < len - 1; i++) // skip string terminator { uchar c = bytes[i]; bool isAlphaNum = ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9')); bool isUnreserved = (c == '-' || c == '_' || c == '.' || c == '~'); if(isAlphaNum || isUnreserved) encoded += CharToString(c); else if(c == ' ') encoded += "%20"; else encoded += "%" + StringFormat("%02X", (int)c); } return encoded; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool SendTelegramMessage(const string message, datetime &lastSendTime, int cooldownSeconds) { if(!EnableTelegramAlerts) return false; // Use normalized runtime copies to avoid accidental spaces NormalizeTelegramInputs(); if(StringLen(g_telegramBotToken) <= 0 || StringLen(g_telegramChatId) <= 0) return false; datetime now = TimeCurrent(); int cooldown = MathMax(0, cooldownSeconds); if(lastSendTime != 0 && (now - lastSendTime) < cooldown) return false; string url = "https://api.telegram.org/bot" + g_telegramBotToken + "/sendMessage"; string payload = "chat_id=" + UrlEncodeUtf8(g_telegramChatId) + "&text=" + UrlEncodeUtf8(message) + "&disable_web_page_preview=true"; char post[]; StringToCharArray(payload, post, 0, WHOLE_ARRAY, CP_UTF8); char response[]; string responseHeaders = ""; string requestHeaders = "Content-Type: application/x-www-form-urlencoded\r\n"; ResetLastError(); int httpCode = WebRequest("POST", url, requestHeaders, 7000, post, response, responseHeaders); if(httpCode == -1) { Print("TELEGRAM ALERT FAILED | WebRequest error=", GetLastError(), " | Add https://api.telegram.org to allowed URLs in MT5."); return false; } if(httpCode < 200 || httpCode >= 300) { string body = CharArrayToString(response, 0, WHOLE_ARRAY, CP_UTF8); Print("TELEGRAM ALERT FAILED | HTTP=", httpCode, " | Response=", body); return false; } lastSendTime = now; return true; } //+------------------------------------------------------------------+ // STATE PERSISTENCE (FILE_COMMON) //+------------------------------------------------------------------+ bool SaveStateToFile() { if(!UseFilePersistence) return false; string file = StateFileName(); int handle = FileOpen(file, FILE_WRITE | FILE_COMMON | FILE_TXT | FILE_ANSI | FILE_SHARE_READ); if(handle == INVALID_HANDLE) { Print("STATE SAVE FAILED | FileOpen error=", GetLastError(), " | file=", file); return false; } // State file version header (allows safe schema changes) FileWriteString(handle, "VER;6\n"); // Trades (capped) FileWriteString(handle, "[TRADE]\n"); int tradeCount = ArraySize(tradeHistory); int startTrade = MathMax(0, tradeCount - MaxSavedTrades); for(int i = startTrade; i < tradeCount; i++) { TradeMemory t = tradeHistory[i]; string line = StringFormat("T;%I64d;%d;%.5f;%.5f;%.2f;%d;%.4f;%.5f;%d;%d;%d;%.2f;%.2f;%d;%s\n", (long)t.entryTime, t.direction, t.entryPrice, t.exitPrice, t.profit, t.consecutiveBars, t.volumeRatio, t.spreadAtEntry, t.hourOfDay, t.dayOfWeek, (int)t.wasWinner, t.maxProfit, t.maxDrawdown, t.holdDuration, SanitizeField(t.exitReason)); FileWriteString(handle, line); } // Patterns FileWriteString(handle, "[PATTERN]\n"); int pattCount = ArraySize(patternHistory); for(int i = 0; i < pattCount; i++) { PatternMemory p = patternHistory[i]; string line = StringFormat("P;%d;%d;%.4f;%.2f;%.2f;%d;%d;%.2f;%I64d;%.2f;%s\n", p.patternType, p.consecutiveBars, p.volumeRatio, p.avgProfit, p.totalProfit, p.totalTrades, p.winningTrades, p.winRate, (long)p.lastUpdated, p.confidence, SanitizeField(p.symbol)); FileWriteString(handle, line); } // Time memory 24x7 FileWriteString(handle, "[TIME]\n"); for(int h = 0; h < 24; h++) for(int d = 0; d < 7; d++) { TimeBasedMemory tm = timeMemory[h][d]; string line = StringFormat("TM;%d;%d;%d;%d;%.2f;%.2f;%.2f;%.2f;%.2f;%d\n", h, d, tm.totalTrades, tm.winningTrades, tm.totalProfit, tm.avgProfit, tm.winRate, tm.avgSpread, tm.avgVolume, (int)tm.isOptimalHour); FileWriteString(handle, line); } // Session memory FileWriteString(handle, "[SESSION]\n"); for(int s = 0; s < ArraySize(sessionMemory); s++) { SessionBasedMemory sm = sessionMemory[s]; string line = StringFormat("S;%s;%d;%d;%.2f;%.2f;%.2f;%d\n", SanitizeField(sm.name), sm.totalTrades, sm.winningTrades, sm.totalProfit, sm.avgProfit, sm.winRate, (int)sm.isOptimal); FileWriteString(handle, line); } // Regime history (capped) FileWriteString(handle, "[REGIME]\n"); int regCount = ArraySize(regimeHistory); int startReg = MathMax(0, regCount - MaxSavedRegimes); for(int i = startReg; i < regCount; i++) { MarketRegimeMemory r = regimeHistory[i]; string line = StringFormat("R;%s;%I64d;%.2f;%d;%.4f;%.4f;%d;%d;%.2f\n", SanitizeField(r.regime), (long)r.detected, r.confidence, r.duration, r.avgVolatility, r.avgSpread, r.successfulTrades, r.totalTrades, r.performance); FileWriteString(handle, line); } // Setup buckets FileWriteString(handle, "[SETUP]\n"); int setupCount = ArraySize(setupMemory); for(int i = 0; i < setupCount; i++) { SetupBucketMemory sb = setupMemory[i]; string line = StringFormat("SB;%d;%d;%d;%d;%s;%d;%d;%.2f;%.2f;%.2f\n", sb.direction, sb.barsBucket, sb.volumeBucket, sb.entryType, SanitizeField(sb.regime), sb.totalTrades, sb.winningTrades, sb.totalProfit, sb.avgProfit, sb.rewardScore); FileWriteString(handle, line); } // Dynamic patterns FileWriteString(handle, "[DYNAMIC_PATTERN]\n"); for(int i = 0; i < dynamicPatternCount; i++) { DynamicPattern dp = dynamicPatterns[i]; string line = StringFormat("DP;%s;%s;%d;%d;%d;%d;%.2f;%.5f;%.5f;%I64d;%I64d;%d\n", SanitizeField(dp.patternHash), SanitizeField(dp.description), dp.patternType, dp.occurrenceCount, dp.winCount, dp.lossCount, dp.reliability, dp.avgProfit, dp.totalProfit, (long)dp.firstSeen, (long)dp.lastSeen, dp.timeframe); FileWriteString(handle, line); } // Symbol timeframe memory (per-symbol, per-timeframe) FileWriteString(handle, "[SYMBOL_TF]\n"); for(int s = 0; s < ArraySize(enhancedSymbolMemory); s++) { string sym = enhancedSymbolMemory[s].symbol; if(sym == "") continue; for(int t = 0; t < 5; t++) { string line = StringFormat("STF;%s;%d;%.5f;%d\n", SanitizeField(sym), TIMEFRAMES[t], enhancedSymbolMemory[s].tfPerformance[t], enhancedSymbolMemory[s].tfTrades[t]); FileWriteString(handle, line); } } // State snapshot FileWriteString(handle, "[STATE]\n"); string stateLine = StringFormat("ST;%s;%.2f;%I64d;%d;%d;%.2f;%d;%.2f;%.2f;%.2f;%.2f;%.2f;%s;%s;%.2f;%.2f\n", SanitizeField(marketState.currentRegime), marketState.regimeConfidence, (long)marketState.regimeStart, marketState.consecutiveWins, marketState.consecutiveLosses, marketState.recentPerformance, (int)marketState.marketStress, queenAccountDrawdownPct, queenMarginLevel, queenAccountHealthScore, currentRiskPercent, memoryAdaptiveRiskMultiplier, SanitizeField(performanceMode), SanitizeField(greedLevel), lastBalance, lastDealProfit); FileWriteString(handle, stateLine); FileClose(handle); FileFlush(handle); lastStateSaveTime = TimeCurrent(); return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool LoadStateFromFile() { if(!UseFilePersistence) return false; string file = StateFileName(); if(ResetStateFile) { FileDelete(file, FILE_COMMON); return false; } if(!FileIsExist(file, FILE_COMMON)) return false; int handle = FileOpen(file, FILE_READ | FILE_COMMON | FILE_TXT | FILE_ANSI); if(handle == INVALID_HANDLE) { Print("STATE LOAD FAILED | FileOpen error=", GetLastError(), " | file=", file); return false; } // reset containers ArrayResize(tradeHistory, 0); ArrayResize(patternHistory, 0); ArrayResize(regimeHistory, 0); ArrayResize(setupMemory, 0); ArrayResize(dynamicPatterns, 0); dynamicPatternCount = 0; for(int h = 0; h < 24; h++) for(int d = 0; d < 7; d++) { timeMemory[h][d].hourOfDay = h; timeMemory[h][d].dayOfWeek = d; timeMemory[h][d].totalTrades = 0; timeMemory[h][d].winningTrades = 0; timeMemory[h][d].totalProfit = 0.0; timeMemory[h][d].avgProfit = 0.0; timeMemory[h][d].winRate = 0.0; timeMemory[h][d].avgSpread = 0.0; timeMemory[h][d].avgVolume = 0.0; timeMemory[h][d].isOptimalHour = false; } for(int s = 0; s < ArraySize(sessionMemory); s++) { sessionMemory[s].name = ""; sessionMemory[s].totalTrades = 0; sessionMemory[s].winningTrades = 0; sessionMemory[s].totalProfit = 0.0; sessionMemory[s].avgProfit = 0.0; sessionMemory[s].winRate = 0.0; sessionMemory[s].isOptimal = false; } string line = ""; while(!FileIsEnding(handle)) { line = FileReadString(handle); if(StringLen(line) <= 0) continue; if(StringLen(line) > 2048) { Print("STATE LOAD WARN | Line too long, skipped: ", StringSubstr(line, 0, 80), "..."); continue; } if(line == "[TRADE]" || line == "[PATTERN]" || line == "[TIME]" || line == "[SESSION]" || line == "[REGIME]" || line == "[SETUP]" || line == "[DYNAMIC_PATTERN]" || line == "[SYMBOL_TF]" || line == "[STATE]") continue; string parts[]; int count = StringSplit(line, ';', parts); if(count <= 1) continue; string tag = parts[0]; if(tag == "VER" && count >= 2) { int version = (int)StringToInteger(parts[1]); if(version < 3) Print("STATE LOAD WARN | Unsupported version ", version, " expected >=3 | file=", file); continue; } if(tag == "T" && count >= 16) { TradeMemory t; t.entryTime = (datetime)StringToInteger(parts[1]); t.direction = (int)StringToInteger(parts[2]); t.entryPrice = SafeNumber(parts[3], -DBL_MAX, DBL_MAX, 0.0); t.exitPrice = SafeNumber(parts[4], -DBL_MAX, DBL_MAX, 0.0); t.profit = SafeNumber(parts[5], -1000000.0, 1000000.0, 0.0); t.consecutiveBars = (int)StringToInteger(parts[6]); t.volumeRatio = SafeNumber(parts[7], 0.0, 1000.0, 0.0); t.spreadAtEntry = SafeNumber(parts[8], 0.0, 100000.0, 0.0); t.hourOfDay = (int)StringToInteger(parts[9]); t.dayOfWeek = (int)StringToInteger(parts[10]); t.wasWinner = (StringToInteger(parts[11]) != 0); t.maxProfit = SafeNumber(parts[12], -1000000.0, 1000000.0, 0.0); t.maxDrawdown = SafeNumber(parts[13], -1000000.0, 1000000.0, 0.0); t.holdDuration = (int)StringToInteger(parts[14]); t.exitReason = parts[15]; int size = ArraySize(tradeHistory); ArrayResize(tradeHistory, size + 1); tradeHistory[size] = t; } else if(tag == "P" && count >= 11) { PatternMemory p; p.patternType = (int)StringToInteger(parts[1]); p.consecutiveBars = (int)StringToInteger(parts[2]); p.volumeRatio = StringToDouble(parts[3]); p.avgProfit = StringToDouble(parts[4]); p.totalProfit = StringToDouble(parts[5]); p.totalTrades = (int)StringToInteger(parts[6]); p.winningTrades = (int)StringToInteger(parts[7]); p.winRate = StringToDouble(parts[8]); p.lastUpdated = (datetime)StringToInteger(parts[9]); p.confidence = StringToDouble(parts[10]); p.symbol = (count >= 12) ? parts[11] : _Symbol; if(StringLen(p.symbol) <= 0) p.symbol = _Symbol; int size = ArraySize(patternHistory); ArrayResize(patternHistory, size + 1); patternHistory[size] = p; } else if(tag == "TM" && count >= 11) { int hIdx = (int)StringToInteger(parts[1]); int dIdx = (int)StringToInteger(parts[2]); if(hIdx >= 0 && hIdx < 24 && dIdx >= 0 && dIdx < 7) { timeMemory[hIdx][dIdx].hourOfDay = hIdx; timeMemory[hIdx][dIdx].dayOfWeek = dIdx; timeMemory[hIdx][dIdx].totalTrades = (int)StringToInteger(parts[3]); timeMemory[hIdx][dIdx].winningTrades = (int)StringToInteger(parts[4]); timeMemory[hIdx][dIdx].totalProfit = StringToDouble(parts[5]); timeMemory[hIdx][dIdx].avgProfit = StringToDouble(parts[6]); timeMemory[hIdx][dIdx].winRate = StringToDouble(parts[7]); timeMemory[hIdx][dIdx].avgSpread = StringToDouble(parts[8]); timeMemory[hIdx][dIdx].avgVolume = StringToDouble(parts[9]); timeMemory[hIdx][dIdx].isOptimalHour = (StringToInteger(parts[10]) != 0); } } else if(tag == "S" && count >= 8) { string name = parts[1]; int slot = 0; if(name == "LONDON") slot = 1; else if(name == "NEW_YORK") slot = 2; else if(name == "OVERLAP") slot = 3; else slot = 0; sessionMemory[slot].name = name; sessionMemory[slot].totalTrades = (int)StringToInteger(parts[2]); sessionMemory[slot].winningTrades = (int)StringToInteger(parts[3]); sessionMemory[slot].totalProfit = StringToDouble(parts[4]); sessionMemory[slot].avgProfit = StringToDouble(parts[5]); sessionMemory[slot].winRate = StringToDouble(parts[6]); sessionMemory[slot].isOptimal = (StringToInteger(parts[7]) != 0); } else if(tag == "R" && count >= 10) { MarketRegimeMemory r; r.regime = parts[1]; r.detected = (datetime)StringToInteger(parts[2]); r.confidence = SafeNumber(parts[3], 0.0, 1.0, 0.0); r.duration = (int)SafeNumber(parts[4], 0.0, 5256000.0, 0.0); r.avgVolatility = SafeNumber(parts[5], 0.0, 100.0, 0.0); r.avgSpread = SafeNumber(parts[6], 0.0, 100000.0, 0.0); r.successfulTrades = (int)SafeNumber(parts[7], 0.0, 1000000.0, 0.0); r.totalTrades = (int)SafeNumber(parts[8], 0.0, 1000000.0, 0.0); r.performance = SafeNumber(parts[9], 0.0, 100.0, 0.0); if(r.totalTrades < r.successfulTrades) r.successfulTrades = r.totalTrades; if(r.regime != "TRENDING" && r.regime != "RANGING" && r.regime != "VOLATILE" && r.regime != "QUIET" && r.regime != "UNKNOWN") r.regime = "UNKNOWN"; int size = ArraySize(regimeHistory); ArrayResize(regimeHistory, size + 1); regimeHistory[size] = r; } else if(tag == "SB" && count >= 10) { SetupBucketMemory sb; sb.direction = (int)SafeNumber(parts[1], -1.0, 1.0, 1.0); if(sb.direction == 0) sb.direction = 1; sb.barsBucket = (int)SafeNumber(parts[2], 0.0, 10.0, 0.0); sb.volumeBucket = (int)SafeNumber(parts[3], 0.0, 4.0, 0.0); int idxShift = 0; sb.entryType = 0; if(count >= 11) { sb.entryType = ((int)SafeNumber(parts[4], 0.0, 1.0, 0.0) != 0) ? 1 : 0; idxShift = 1; } sb.regime = parts[4 + idxShift]; if(sb.regime != "TRENDING" && sb.regime != "RANGING" && sb.regime != "VOLATILE" && sb.regime != "QUIET" && sb.regime != "UNKNOWN") sb.regime = "UNKNOWN"; sb.totalTrades = (int)SafeNumber(parts[5 + idxShift], 0.0, 1000000.0, 0.0); sb.winningTrades = (int)SafeNumber(parts[6 + idxShift], 0.0, 1000000.0, 0.0); if(sb.winningTrades > sb.totalTrades) sb.winningTrades = sb.totalTrades; sb.totalProfit = SafeNumber(parts[7 + idxShift], -1000000.0, 1000000.0, 0.0); sb.avgProfit = SafeNumber(parts[8 + idxShift], -1000000.0, 1000000.0, 0.0); sb.rewardScore = SafeNumber(parts[9 + idxShift], SetupRewardClampMin, SetupRewardClampMax, 0.0); int size = ArraySize(setupMemory); ArrayResize(setupMemory, size + 1); setupMemory[size] = sb; } else if(tag == "DP" && count >= 13) { DynamicPattern dp; dp.patternHash = parts[1]; dp.description = parts[2]; dp.patternType = (int)SafeNumber(parts[3], 0.0, 3.0, 0.0); dp.occurrenceCount = (int)SafeNumber(parts[4], 0.0, 1000000.0, 0.0); dp.winCount = (int)SafeNumber(parts[5], 0.0, 1000000.0, 0.0); dp.lossCount = (int)SafeNumber(parts[6], 0.0, 1000000.0, 0.0); dp.reliability = SafeNumber(parts[7], 0.0, 100.0, 50.0); dp.avgProfit = SafeNumber(parts[8], -1000000.0, 1000000.0, 0.0); dp.totalProfit = SafeNumber(parts[9], -10000000.0, 10000000.0, 0.0); dp.firstSeen = (datetime)StringToInteger(parts[10]); dp.lastSeen = (datetime)StringToInteger(parts[11]); dp.timeframe = (int)SafeNumber(parts[12], 1.0, 100000.0, (double)Period()); if(dp.patternHash != "") { int size = ArraySize(dynamicPatterns); ArrayResize(dynamicPatterns, size + 1); dynamicPatterns[size] = dp; } } else if(tag == "STF" && count >= 5) { string sym = parts[1]; int tf = (int)StringToInteger(parts[2]); double perf = SafeNumber(parts[3], -1000000.0, 1000000.0, 0.0); int trades = (int)SafeNumber(parts[4], 0.0, 1000000.0, 0.0); int symIdx = getSymbolIndex(sym); int tfIdx = getTimeframeIndex(tf); if(symIdx >= 0 && tfIdx >= 0) { enhancedSymbolMemory[symIdx].tfPerformance[tfIdx] = perf; enhancedSymbolMemory[symIdx].tfTrades[tfIdx] = trades; } } else if(tag == "ST" && count >= 17) { marketState.currentRegime = parts[1]; marketState.regimeConfidence = SafeNumber(parts[2], 0.0, 1.0, 0.0); marketState.regimeStart = (datetime)StringToInteger(parts[3]); marketState.consecutiveWins = (int)StringToInteger(parts[4]); marketState.consecutiveLosses = (int)StringToInteger(parts[5]); marketState.recentPerformance = SafeNumber(parts[6], -100.0, 100.0, 0.0); marketState.marketStress = (StringToInteger(parts[7]) != 0); queenAccountDrawdownPct = SafeNumber(parts[8], 0.0, 100.0, 0.0); queenMarginLevel = SafeNumber(parts[9], 0.0, 2000.0, 0.0); queenAccountHealthScore = SafeNumber(parts[10], 0.0, 1.0, 0.0); currentRiskPercent = SafeNumber(parts[11], 0.0, 20.0, 0.0); memoryAdaptiveRiskMultiplier = SafeNumber(parts[12], 0.0, 5.0, 1.0); performanceMode = parts[13]; if(performanceMode == "AUTO_PROTECT") performanceMode = "NORMAL"; greedLevel = parts[14]; lastBalance = SafeNumber(parts[15], 0.0, 10000000.0, 0.0); lastDealProfit = SafeNumber(parts[16], -1000000.0, 1000000.0, 0.0); } } FileClose(handle); if(ArraySize(tradeHistory) > MaxSavedTrades) ArrayResize(tradeHistory, MaxSavedTrades); if(ArraySize(regimeHistory) > MaxSavedRegimes) ArrayResize(regimeHistory, MaxSavedRegimes); dynamicPatternCount = ArraySize(dynamicPatterns); Print("STATE LOAD OK | Loaded trades=", ArraySize(tradeHistory), " patterns=", ArraySize(patternHistory), " regimes=", ArraySize(regimeHistory), " setups=", ArraySize(setupMemory), " dynamic=", dynamicPatternCount); return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void AutosaveStateIfDue() { if(!UseFilePersistence || StateAutosaveSeconds <= 0) return; datetime now = TimeCurrent(); if(lastStateSaveTime != 0 && (now - lastStateSaveTime) < StateAutosaveSeconds) return; SaveStateToFile(); } // Simple Zira TTS helper (no wav needed) void SpeakZira(const string text) { if(!EnableVoicePrompts) return; static datetime lastSpeak = 0; datetime now = TimeCurrent(); if(now - lastSpeak < 2) // throttle return; lastSpeak = now; string t = text; StringReplace(t, "'", "''"); // escape for PS single quotes string ps = "powershell -NoProfile -ExecutionPolicy Bypass -Command \"" "Add-Type -AssemblyName System.Speech;" "$s=New-Object System.Speech.Synthesis.SpeechSynthesizer;" "$s.SelectVoice('Microsoft Zira Desktop');" "$s.Rate=-1;" "$s.Volume=100;" "$s.Speak('" + t + "');\""; ShellExecuteW(0, "open", "cmd.exe", "/c " + ps, "", 0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool VoiceCooldown(datetime &lastTime, int cooldownSeconds) { datetime now = TimeCurrent(); int cd = MathMax(0, cooldownSeconds); if(lastTime != 0 && (now - lastTime) < cd) return false; lastTime = now; return true; } // Simple trim helper (MQL5 has no StringTrim) string Trim(const string src) { string t = src; StringTrimLeft(t); StringTrimRight(t); return t; } // ---------- SQLite voice command helpers ---------- string BufToString(uchar &buf[]) { int n = 0; while(n < ArraySize(buf) && buf[n] != 0) n++; return CharArrayToString(buf, 0, n, CP_UTF8); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool PopNextVoiceCommand(int &id, string &cmd, string &payload, string &symbol, long &magic) { if(g_cmdDb <= 0) return false; uchar bCmd[256]; ArrayInitialize(bCmd, 0); uchar bPay[512]; ArrayInitialize(bPay, 0); uchar bSym[64]; ArrayInitialize(bSym, 0); long lid = 0, lmg = 0; // Accept any symbol/magic (script already filters) int ok = sqlite_pop_next(g_cmdDb, g_cmdInstance, "", 0, lid, bCmd, 256, bPay, 512, bSym, 64, lmg); if(ok != 1 || lid <= 0) return false; id = (int)lid; magic = (long)lmg; cmd = BufToString(bCmd); payload = BufToString(bPay); symbol = BufToString(bSym); return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool ExecuteVoiceCommand(const string cmd, const string payload, string &result, string &error) { string c = NormalizeVoiceText(cmd); if(c == "") { error = "empty command"; return false; } // Pause / Resume if(c == "pause" || c == "pause trading" || c == "stop trading" || c == "hold on") { tradingPaused = true; result = "trading paused"; SpeakZira("Trading paused. Awaiting your next command."); return true; } if(c == "resume" || c == "resume trading" || c == "continue trading") { tradingPaused = false; result = "trading resumed"; SpeakZira("Yes master, I will resume trading."); return true; } // Close all positions if(c == "close_all" || c == "close all" || c == "close everything" || c == "close positions" || c == "panic close") { CloseAllTrades(); result = "close all sent"; SpeakZira("Closing all positions now."); return true; } // Risk commands: accept formats like "risk=2", "risk two percent", "set risk to 1.5 percent" if(StringFind(c, "risk=") == 0 || StringFind(c, "risk ") == 0 || StringFind(c, "set risk") == 0) { double r = 0.0; if(StringFind(c, "risk=") == 0) r = StringToDouble(StringSubstr(c, 5)); else { if(!ExtractFirstNumber(c, r)) { // try word numbers string words[]; int wc = StringSplit(c, ' ', words); for(int i = 0; i < wc; i++) { if(WordToNumber(words[i], r)) break; } } } if(r <= 0.0 || r > 10.0) { error = "invalid risk"; return false; } currentRiskPercent = r; result = "risk set to " + DoubleToString(r, 2); SpeakZira("Risk set to " + DoubleToString(r, 1) + " percent."); return true; } // Greetings if(c == "hello" || c == "hi" || c == "good morning" || c == "good afternoon" || c == "good evening" || c == "how are you") { result = "greeting"; SpeakZira("Hello. I am monitoring the markets for you."); return true; } // Status shorthand if(c == "status" || c == "status report") { double eq = AccountInfoDouble(ACCOUNT_EQUITY); double bal = AccountInfoDouble(ACCOUNT_BALANCE); int closedTrades = winCount + lossCount; double winRatePct = (closedTrades > 0) ? (((double)winCount * 100.0) / (double)closedTrades) : 0.0; string paused = tradingPaused ? "paused" : "active"; result = "status"; SpeakZira("Status check. Trading is " + paused + ". Balance " + DoubleToString(bal, 2) + ". Equity " + DoubleToString(eq, 2) + ". Risk " + DoubleToString(currentRiskPercent, 1) + " percent. Win rate " + DoubleToString(winRatePct, 1) + " percent."); return true; } // Unknown command fallback error = "unknown command: " + c; SpeakZira("I did not understand. Please repeat."); return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void PollVoiceCommandQueue() { if(g_cmdDb <= 0) return; datetime now = TimeCurrent(); if(g_lastCmdPoll != 0 && (now - g_lastCmdPoll) < g_cmdPollIntervalSeconds) return; g_lastCmdPoll = now; for(int i = 0; i < 3; i++) // process max 3 per poll { int id = 0; long mg = 0; string cmd = "", pay = "", sym = ""; if(!PopNextVoiceCommand(id, cmd, pay, sym, mg)) break; string res = "", err = ""; string cmdNorm = NormalizeVoiceText(cmd); bool ok = ExecuteVoiceCommand(cmd, pay, res, err); if(ok) { sqlite_mark_done(g_cmdDb, id, res); Print("VOICE CMD DONE: ", res, " | raw=", cmd, " | norm=", cmdNorm, " | payload=", pay); } else { sqlite_mark_failed(g_cmdDb, id, err); Print("VOICE CMD FAILED: ", err, " | raw=", cmd, " | norm=", cmdNorm, " | payload=", pay); } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EmitOrderExecutedSound(int direction) { if(!EnableVoicePrompts) return; if(direction == 1) { if(VoiceCooldown(lastBuySoundTime, VoiceCooldownSeconds)) SpeakZira("sir tudor.Buy order executed on " + _Symbol + "."); } else if(direction == -1) { if(VoiceCooldown(lastSellSoundTime, VoiceCooldownSeconds)) SpeakZira("sir tudor.Sell order executed on " + _Symbol + "."); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EmitSpikeDetectedAlert(int direction, double movePoints, double anchorPrice, int thresholdPoints) { if(direction != 1 && direction != -1) return; string side = (direction == 1) ? "UP" : "DOWN"; if(TelegramSendSpikes) { string msg = "KING GOLD SPIKE | " + _Symbol + " | " + side + " | move=" + DoubleToString(movePoints, 1) + " | threshold=" + IntegerToString(thresholdPoints) + " | anchor=" + DoubleToString(anchorPrice, _Digits) + " | time=" + TimeToString(TimeCurrent(), TIME_SECONDS); SendTelegramMessage(msg, lastTelegramSpikeTime, TelegramCooldownSeconds); } if(EnableVoicePrompts && VoiceSendSpikes) { if(VoiceCooldown(lastSpikeSoundTime, VoiceCooldownSeconds)) { string sideVoice = side; StringToLower(sideVoice); SpeakZira("Fast spike detected.big moves " + sideVoice + " on " + _Symbol + "."); } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EmitOrderBlockedSound() { if(TelegramSendBlocked) SendTelegramMessage("KING GOLD ALERT | Order blocked | " + _Symbol + " | Time=" + TimeToString(TimeCurrent(), TIME_SECONDS), lastTelegramBlockedTime, TelegramCooldownSeconds); // Zira voice if(EnableVoicePrompts) { if(VoiceCooldown(lastBlockedSoundTime, VoiceCooldownSeconds)) SpeakZira("Order blocked on " + _Symbol + "."); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EmitRiskWarningSound() { if(EnableVoicePrompts) { if(VoiceCooldown(lastRiskSoundTime, VoiceCooldownSeconds)) SpeakZira("sir.Bandits detects big moves coming on " + _Symbol + ". I will look for entry."); } if(TelegramSendRisk) SendTelegramMessage("KING GOLD RISK | Stress detected | " + _Symbol + " | Regime=" + marketState.currentRegime, lastTelegramRiskTime, TelegramCooldownSeconds); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void EmitCloseResultSound(double profit) { if(EnableVoicePrompts && MathAbs(profit) > 0.0) { if(profit > 0.0) { if(VoiceCooldown(lastProfitSoundTime, VoiceCooldownSeconds)) SpeakZira("Position closed in profit " + DoubleToString(profit, 2)); } else { if(VoiceCooldown(lastLossSoundTime, VoiceCooldownSeconds)) SpeakZira("Position closed in loss " + DoubleToString(profit, 2)); } } if(TelegramSendCloses && MathAbs(profit) > 0.0) { string outcome = (profit > 0.0) ? "✅ Profit" : "❌ Loss"; string msg = "📌 POSITION CLOSED\n" + outcome + " | " + _Symbol + "\nPnL: $" + DoubleToString(profit, 2) + "\n⏰ " + TimeToString(TimeCurrent(), TIME_SECONDS); SendTelegramMessage(msg, lastTelegramCloseTime, TelegramCooldownSeconds); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ int GetDateKey(datetime when) { MqlDateTime dt; TimeToStruct(when, dt); return (dt.year * 10000) + (dt.mon * 100) + dt.day; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CheckFutureSessionEventAlerts() { if(!EnableFutureSessionAlerts) return; datetime now = TimeCurrent(); MqlDateTime dt; TimeToStruct(now, dt); int leadMinutes = MathMax(1, FutureEventLeadMinutes); // Alert before session start (today) MqlDateTime startDt = dt; startDt.hour = MathMax(0, MathMin(23, StartHour)); startDt.min = 0; startDt.sec = 0; datetime todayStart = StructToTime(startDt); int todayKey = GetDateKey(now); int minutesToStart = (int)((todayStart - now) / 60); if(minutesToStart >= 0 && minutesToStart <= leadMinutes && lastFutureStartAlertDateKey != todayKey) { Print("🔔 FUTURE EVENT ALERT | Session start in ", minutesToStart, " min at ", TimeToString(todayStart, TIME_MINUTES), " ⏳"); if(EnableVoicePrompts && VoiceCooldown(lastFutureSoundTime, VoiceCooldownSeconds)) SpeakZira("Session starts in " + IntegerToString(minutesToStart) + " minutes."); if(TelegramSendFutureEvents) { string msg = "🔔 KING GOLD FUTURE EVENT | Session start in " + IntegerToString(minutesToStart) + " min | " + _Symbol + " | At " + TimeToString(todayStart, TIME_MINUTES) + " ⏳"; SendTelegramMessage(msg, lastTelegramFutureTime, TelegramCooldownSeconds); } lastFutureStartAlertDateKey = todayKey; } // Alert before session end (today) MqlDateTime endDt = dt; endDt.hour = MathMax(0, MathMin(23, EndHour)); endDt.min = 0; endDt.sec = 0; datetime todayEnd = StructToTime(endDt); int minutesToEnd = (int)((todayEnd - now) / 60); if(minutesToEnd >= 0 && minutesToEnd <= leadMinutes && lastFutureEndAlertDateKey != todayKey) { Print("🔔 FUTURE EVENT ALERT | Session end in ", minutesToEnd, " min at ", TimeToString(todayEnd, TIME_MINUTES), " ⏳"); if(EnableVoicePrompts && VoiceCooldown(lastFutureSoundTime, VoiceCooldownSeconds)) SpeakZira("Session ends in " + IntegerToString(minutesToEnd) + " minutes."); if(TelegramSendFutureEvents) { string msg = "🔔 KING GOLD FUTURE EVENT | Session end in " + IntegerToString(minutesToEnd) + " min | " + _Symbol + " | At " + TimeToString(todayEnd, TIME_MINUTES) + " ⏳"; SendTelegramMessage(msg, lastTelegramFutureTime, TelegramCooldownSeconds); } lastFutureEndAlertDateKey = todayKey; } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CheckUpcomingEconomicNewsAlerts() { if(!EnableNewsEventAlerts) return; datetime now = TimeCurrent(); int scanEvery = MathMax(10, NewsScanIntervalSeconds); if(lastNewsScanTime != 0 && (now - lastNewsScanTime) < scanEvery) return; lastNewsScanTime = now; int lookaheadMinutes = MathMax(1, NewsLookaheadMinutes); datetime horizon = now + (lookaheadMinutes * 60); MqlCalendarValue values[]; int total = CalendarValueHistory(values, now, horizon); if(total < 0) { if(!newsCalendarWarnedUnavailable) { Print("NEWS ALERT: Economic calendar unavailable in this environment."); newsCalendarWarnedUnavailable = true; } return; } if(total <= 0) return; datetime nearest = 0; int relevant = 0; for(int i = 0; i < total; i++) { datetime evt = values[i].time; if(evt < now || evt > horizon) continue; relevant++; if(nearest == 0 || evt < nearest) nearest = evt; } if(relevant <= 0) return; int newsCooldown = MathMax(10, NewsAlertCooldownSeconds); if(lastNewsSoundTime != 0 && (now - lastNewsSoundTime) < newsCooldown) return; int mins = (nearest > now) ? (int)((nearest - now) / 60) : 0; string scope = NewsAlertAllCurrencies ? "all-currencies" : "symbol-related"; Print("NEWS ALERT | Upcoming events: ", relevant, " within ", lookaheadMinutes, " min | Nearest in ", mins, " min | Scope=", scope); if(EnableVoicePrompts && VoiceCooldown(lastNewsSoundTime, VoiceCooldownSeconds)) SpeakZira("Sir tudor.High impact news in " + IntegerToString(mins) + " minutes."); if(TelegramSendNews) { string msg = "KING GOLD NEWS ALERT | Events=" + IntegerToString(relevant) + " | nearest in " + IntegerToString(mins) + " min | scope=" + scope + " | " + _Symbol; SendTelegramMessage(msg, lastTelegramNewsTime, newsCooldown); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void UpdateMarketCache() { if(marketCache.needsUpdate()) { marketCache.update(); } } // Utility functions using cached data double GetCachedBalance() { UpdateAccountCache(); return accountCache.balance; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetCachedEquity() { UpdateAccountCache(); return accountCache.equity; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetCachedMargin() { UpdateAccountCache(); return accountCache.margin; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetCachedMarginLevel() { UpdateAccountCache(); return accountCache.marginLevel; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetCachedBid() { UpdateMarketCache(); return marketCache.bid; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetCachedAsk() { UpdateMarketCache(); return marketCache.ask; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsDrawdownLimitExceeded() { UpdateAccountCache(); if(accountCache.balance <= 0.0) return false; double drawdownPct = ((accountCache.balance - accountCache.equity) / accountCache.balance) * 100.0; return drawdownPct > MAX_DRAWDOWN_PERCENT; } //+------------------------------------------------------------------+ // VALIDATION FUNCTIONS - Improve error handling and robustness //+------------------------------------------------------------------+ bool ValidateSymbolInfo() { UpdateMarketCache(); // Check critical symbol parameters if(marketCache.minLot <= 0.0) { marketCache.minLot = 0.01; Print("WARNING: Invalid SYMBOL_VOLUME_MIN. Using fallback minLot=", marketCache.minLot); } if(marketCache.maxLot <= 0.0 || marketCache.maxLot <= marketCache.minLot) { double fallbackMax = MathMax(marketCache.minLot * 100.0, marketCache.minLot + marketCache.lotStep); Print("WARNING: Invalid SYMBOL_VOLUME_MAX: ", marketCache.maxLot, " | Applying fallback maxLot=", fallbackMax); marketCache.maxLot = fallbackMax; } if(marketCache.lotStep <= 0.0) { marketCache.lotStep = marketCache.minLot; Print("WARNING: Invalid SYMBOL_VOLUME_STEP. Using fallback lotStep=", marketCache.lotStep); } if(marketCache.tickSize <= 0.0) { Print("WARNING: Invalid SYMBOL_TRADE_TICK_SIZE: ", marketCache.tickSize, " - using Point() as fallback"); marketCache.tickSize = Point(); } if(marketCache.tickValue <= 0.0) { marketCache.tickValue = Point(); Print("WARNING: Invalid SYMBOL_TRADE_TICK_VALUE. Using fallback tickValue=Point(): ", marketCache.tickValue); } return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool ValidateInputs() { // Validate risk parameters if(RiskPercent < 0.0 || RiskPercent > 10.0) { Print("ERROR: Invalid RiskPercent: ", RiskPercent, ". Must be between 0.0 and 10.0"); return false; } if(DefaultRiskPercent <= 0.0 || DefaultRiskPercent > 10.0) { Print("ERROR: Invalid DefaultRiskPercent: ", DefaultRiskPercent, ". Must be between 0.01 and 10.0"); return false; } if(TakeProfit <= 0.0 || StopLoss <= 0.0) { Print("ERROR: Invalid TP/SL values. TP: ", TakeProfit, ", SL: ", StopLoss); return false; } if(StartHour < 0 || StartHour > 23 || EndHour < 0 || EndHour > 23) { Print("ERROR: Invalid trading hours. Start: ", StartHour, ", End: ", EndHour); return false; } if(MaxOpenTrades <= 0 || MaxOpenTrades > 50) { Print("ERROR: Invalid MaxOpenTrades: ", MaxOpenTrades, ". Must be between 1 and 50"); return false; } if(BestSessionMinWinRate < 0.0 || BestSessionMinWinRate > 100.0) { Print("ERROR: Invalid BestSessionMinWinRate: ", BestSessionMinWinRate, ". Must be between 0 and 100"); return false; } if(BestSessionMinTrades < 1 || BestSessionMinTrades > 200) { Print("ERROR: Invalid BestSessionMinTrades: ", BestSessionMinTrades, ". Must be between 1 and 200"); return false; } if(LondonSessionStartHour < 0 || LondonSessionStartHour > 23 || LondonSessionEndHour < 0 || LondonSessionEndHour > 23) { Print("ERROR: Invalid London session hours. Start: ", LondonSessionStartHour, ", End: ", LondonSessionEndHour); return false; } if(NewYorkSessionStartHour < 0 || NewYorkSessionStartHour > 23 || NewYorkSessionEndHour < 0 || NewYorkSessionEndHour > 23) { Print("ERROR: Invalid New York session hours. Start: ", NewYorkSessionStartHour, ", End: ", NewYorkSessionEndHour); return false; } return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsValidPrice(double price) { return (price > 0.0 && price != INVALID_PRICE && price < 999999.0); } // Utility: get current time with optional local offset when broker time is not used datetime GetCurrentTimeWithOffset() { return UseBrokerTime ? TimeCurrent() : (TimeLocal() + (TimezoneOffset * 3600)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool WarmupFromHistory() { int requestedBars = WarmupBars; int totalBars = Bars(Symbol(), Period()); int closedBars = totalBars - 1; if(closedBars < 20) { Print("WARMUP SKIPPED | Not enough closed bars: ", closedBars); return false; } int barsToUse = (int)MathMin((double)requestedBars, (double)closedBars); barsToUse = (int)MathMin((double)barsToUse, (double)WarmupHardCap); barsToUse = (int)MathMin((double)barsToUse, (double)PatternMemoryBars); // Rebuild current streak from latest closed candles (shift 1 and older) consecutiveBullishBars = 0; consecutiveBearishBars = 0; int streakDirection = 0; // 1 bullish, -1 bearish for(int shift = 1; shift <= barsToUse; shift++) { double o = iOpen(Symbol(), Period(), shift); double c = iClose(Symbol(), Period(), shift); int dir = 0; if(c > o) dir = 1; else if(c < o) dir = -1; if(dir == 0) continue; // doji is neutral; do not break the current streak if(streakDirection == 0) streakDirection = dir; if(dir != streakDirection) break; if(streakDirection == 1) consecutiveBullishBars++; else consecutiveBearishBars++; } // Volume baseline and trend int volumeLookback = MathMin(MathMax(VolumeSignalLookback, 200), barsToUse); int recentVolumeLookback = MathMin(200, volumeLookback); double sumVolume = 0.0; double sumRecentVolume = 0.0; for(int i = 1; i <= volumeLookback; i++) { long v = UseTickVolume ? iTickVolume(Symbol(), Period(), i) : iVolume(Symbol(), Period(), i); sumVolume += (double)v; if(i <= recentVolumeLookback) sumRecentVolume += (double)v; } double avgVolumeAll = (volumeLookback > 0) ? (sumVolume / volumeLookback) : 0.0; double avgVolumeRecent = (recentVolumeLookback > 0) ? (sumRecentVolume / recentVolumeLookback) : 0.0; marketState.volumeTrend = (avgVolumeAll > 0.0) ? (avgVolumeRecent / avgVolumeAll) : 1.0; // Volatility baseline + current volatility from close prices int currentVolWindow = MathMin(VolWindowShort, barsToUse); int baselineVolWindow = MathMin(VolWindowLong, barsToUse); double sumCurrent = 0.0; for(int i = 1; i <= currentVolWindow; i++) sumCurrent += iClose(Symbol(), Period(), i); double avgCurrent = sumCurrent / currentVolWindow; double varCurrent = 0.0; for(int i = 1; i <= currentVolWindow; i++) { double px = iClose(Symbol(), Period(), i); varCurrent += MathPow(px - avgCurrent, 2); } marketState.currentVolatility = MathSqrt(varCurrent / currentVolWindow); double sumBaseline = 0.0; for(int i = 1; i <= baselineVolWindow; i++) sumBaseline += iClose(Symbol(), Period(), i); double avgBaseline = sumBaseline / baselineVolWindow; double varBaseline = 0.0; for(int i = 1; i <= baselineVolWindow; i++) { double px = iClose(Symbol(), Period(), i); varBaseline += MathPow(px - avgBaseline, 2); } marketState.avgVolatility = MathSqrt(varBaseline / baselineVolWindow); // Regime preload double baselineVol = MathMax(marketState.avgVolatility, SymbolInfoDouble(_Symbol, SYMBOL_POINT)); if(marketState.currentVolatility > baselineVol * 1.5) { marketState.currentRegime = "VOLATILE"; marketState.regimeConfidence = 0.8; } else if(marketState.currentVolatility < baselineVol * 0.5) { marketState.currentRegime = "QUIET"; marketState.regimeConfidence = 0.8; } else { marketState.currentRegime = "RANGING"; marketState.regimeConfidence = 0.6; } marketState.marketStress = (marketState.currentVolatility > baselineVol * 2.0); // Lightweight price trend preload (normalized points per bar) int trendWindow = MathMin(TrendWindowBars, barsToUse); double latestClose = iClose(Symbol(), Period(), 1); double oldestClose = iClose(Symbol(), Period(), trendWindow); marketState.priceTrend = (Point() > 0.0 && trendWindow > 0) ? ((latestClose - oldestClose) / (Point() * trendWindow)) : 0.0; Print("WARMUP DONE | Bars: ", barsToUse, " | BullishStreak: ", consecutiveBullishBars, " | BearishStreak: ", consecutiveBearishBars, " | VolRatio: ", DoubleToString(marketState.volumeTrend, 2), " | Volatility: ", DoubleToString(marketState.currentVolatility, 5), " | Regime: ", marketState.currentRegime, " | Stress: ", (marketState.marketStress ? "YES" : "NO")); return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool WarmupAllSymbolsFromMarketWatch() { if(totalTrackedSymbols <= 0) { Print("WARMUP ALL SYMBOLS SKIPPED | No tracked Market Watch symbols."); return false; } int warmed = 0; int skipped = 0; datetime now = TimeCurrent(); for(int i = 0; i < totalTrackedSymbols; i++) { string symbol = trackedSymbols[i]; if(symbol == "") { skipped++; continue; } int validTf = 0; double sumVolatility = 0.0; double sumTrend = 0.0; double sumVolumeRatio = 0.0; for(int t = 0; t < 5; t++) { ENUM_TIMEFRAMES tf = (ENUM_TIMEFRAMES)TIMEFRAMES[t]; int bars = Bars(symbol, tf); if(bars < 50) continue; double volatility = calculateVolatility(symbol, TIMEFRAMES[t]); double trend = calculateTrendStrength(symbol, TIMEFRAMES[t]); double avgVolume = calculateAverageVolume(symbol, TIMEFRAMES[t], 20); long currentVolume = UseTickVolume ? iTickVolume(symbol, tf, 1) : iVolume(symbol, tf, 1); if(currentVolume <= 0) currentVolume = iTickVolume(symbol, tf, 1); double volumeRatio = (avgVolume > 0.0) ? ((double)currentVolume / avgVolume) : 1.0; sumVolatility += volatility; sumTrend += trend; sumVolumeRatio += volumeRatio; validTf++; } if(validTf <= 0) { if(i < ArraySize(enhancedSymbolMemory)) enhancedSymbolMemory[i].isTradable = false; skipped++; continue; } double symbolPoint = SymbolInfoDouble(symbol, SYMBOL_POINT); if(symbolPoint <= 0.0) symbolPoint = Point(); double spreadPoints = (symbolPoint > 0.0) ? ((SymbolInfoDouble(symbol, SYMBOL_ASK) - SymbolInfoDouble(symbol, SYMBOL_BID)) / symbolPoint) : 0.0; if(i < ArraySize(enhancedSymbolMemory)) { enhancedSymbolMemory[i].avgVolatility = sumVolatility / (double)validTf; enhancedSymbolMemory[i].trendScore = (int)MathRound(sumTrend / (double)validTf); enhancedSymbolMemory[i].liquidityScore = sumVolumeRatio / (double)validTf; enhancedSymbolMemory[i].avgSpread = spreadPoints; enhancedSymbolMemory[i].lastAnalysis = now; enhancedSymbolMemory[i].isTradable = true; } warmed++; } if(warmed > 0) scanner.scanAllSymbols(); Print("WARMUP ALL SYMBOLS DONE | Warmed: ", warmed, " | Skipped: ", skipped, " | Tracked: ", totalTrackedSymbols); return (warmed > 0); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetInstantVolumeBurst(string symbol, ENUM_TIMEFRAMES tf, int lookbackBars) { int lookback = MathMax(5, MathMin(lookbackBars, 200)); long currentVolume = UseTickVolume ? iTickVolume(symbol, tf, 0) : iVolume(symbol, tf, 0); if(currentVolume <= 0) currentVolume = iTickVolume(symbol, tf, 0); long sumVolume = 0; int counted = 0; for(int i = 1; i <= lookback; i++) { long v = UseTickVolume ? iTickVolume(symbol, tf, i) : iVolume(symbol, tf, i); if(v <= 0) v = iTickVolume(symbol, tf, i); if(v <= 0) continue; sumVolume += v; counted++; } if(counted <= 0) return 1.0; double avgVolume = (double)sumVolume / (double)counted; if(avgVolume <= 0.0) return 1.0; return (double)currentVolume / avgVolume; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void GetAutoFastSpikeParameters(int &thresholdPoints, uint &windowMs, uint &cooldownMs, bool &preferMomentum) { long spreadPointsRaw = 0; SymbolInfoInteger(_Symbol, SYMBOL_SPREAD, spreadPointsRaw); int spreadPoints = (int)MathMax(1, spreadPointsRaw); double volPoints = (point_value > 0.0) ? (marketState.currentVolatility / point_value) : 0.0; if(volPoints < 0.0) volPoints = 0.0; double threshold = MathMax((double)(spreadPoints * 12), volPoints * 4.0); if(marketState.currentRegime == "VOLATILE") threshold *= 1.10; else if(marketState.currentRegime == "QUIET") threshold *= 0.85; if(marketState.marketStress) threshold *= 1.10; thresholdPoints = (int)ClampValue(MathRound(threshold), 20.0, 5000.0); uint baseWindowMs = 500; if(marketState.currentRegime == "VOLATILE") baseWindowMs = 280; else if(marketState.currentRegime == "QUIET") baseWindowMs = 800; if(marketState.marketStress) baseWindowMs = (uint)MathMax(180, (int)(baseWindowMs * 0.8)); windowMs = (uint)MathMax(150, MathMin(1500, (int)baseWindowMs)); uint baseCooldownMs = marketState.marketStress ? 1800 : 1200; cooldownMs = (uint)MathMax(500, MathMin(8000, (int)baseCooldownMs)); // Momentum bias in high-energy regimes (especially useful around news spikes). preferMomentum = (marketState.currentRegime == "VOLATILE" || marketState.marketStress); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ResetFastSpikePendingState() { fastSpikePending = false; fastSpikePendingDirection = 0; fastSpikePendingStartMs = 0; fastSpikePendingAnchorPrice = 0.0; fastSpikePendingThresholdPoints = 0; fastSpikePendingMovePoints = 0.0; fastSpikePendingHoldTicks = 0; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool DetectFastSpike(int &spikeDirection, double &movePoints, double &anchorPrice, uint &elapsedMs, int &thresholdPointsOut) { spikeDirection = 0; movePoints = 0.0; anchorPrice = 0.0; elapsedMs = 0; thresholdPointsOut = 0; if(!UseFastSpikeReaction || point_value <= 0.0) return false; uint nowMs = GetTickCount(); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); double mid = (bid + ask) / 2.0; if(mid <= 0.0) return false; double spreadPointsNow = MathAbs(ask - bid) / point_value; int maxSpikeSpreadPoints = (int)MathMax(1, MaxNewsSpreadPoints); if(spreadPointsNow > (double)maxSpikeSpreadPoints) return false; int thresholdPoints = 0; uint windowMs = 0; uint cooldownMs = 0; bool preferMomentum = false; GetAutoFastSpikeParameters(thresholdPoints, windowMs, cooldownMs, preferMomentum); if(fastSpikeAnchorOpen <= 0.0) fastSpikeAnchorOpen = mid; if(fastSpikeReferenceMs == 0) fastSpikeReferenceMs = nowMs; elapsedMs = nowMs - fastSpikeReferenceMs; int accelMs = FastSpikeAccelerationMs; if(accelMs > 0 && elapsedMs > (uint)MathMax(50, MathMin(5000, accelMs))) return false; movePoints = MathAbs(mid - fastSpikeAnchorOpen) / point_value; if(movePoints < (double)MathMax(1, thresholdPoints)) return false; double volumeBurst = GetInstantVolumeBurst(_Symbol, (ENUM_TIMEFRAMES)Period(), 40); double minVolumeBurst = 1.20; if(marketState.currentRegime == "VOLATILE") minVolumeBurst = 1.05; else if(marketState.currentRegime == "QUIET") minVolumeBurst = 1.30; // If the move is extremely strong, allow it even with moderate volume. bool exceptionalMove = (movePoints >= ((double)thresholdPoints * 1.8)); if(!exceptionalMove && volumeBurst < minVolumeBurst) return false; anchorPrice = fastSpikeAnchorOpen; thresholdPointsOut = MathMax(1, thresholdPoints); spikeDirection = (mid > fastSpikeAnchorOpen) ? 1 : -1; // 1=up spike, -1=down spike return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void UpdateAccountHealthDisplayState() { UpdateAccountCache(); double balance = accountCache.balance; double equity = accountCache.equity; double margin = accountCache.margin; double freeMargin = accountCache.freeMargin; queenMarginLevel = accountCache.marginLevel; if(balance <= 0.0) balance = 1.0; if(equity <= 0.0) equity = 1.0; queenAccountDrawdownPct = ((balance - equity) / balance) * 100.0; if(queenAccountDrawdownPct < 0.0) queenAccountDrawdownPct = 0.0; if(queenAccountDrawdownPct > 100.0) queenAccountDrawdownPct = 100.0; double exposureLots = 0.0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong posTicket = PositionGetTicket(i); if(posTicket <= 0) continue; exposureLots += PositionGetDouble(POSITION_VOLUME); } for(int j = OrdersTotal() - 1; j >= 0; j--) { ulong ordTicket = OrderGetTicket(j); if(ordTicket <= 0) continue; exposureLots += OrderGetDouble(ORDER_VOLUME_INITIAL); } queenExposureLots = exposureLots; double equityScore = 1.0 - MathMin(queenAccountDrawdownPct / 25.0, 1.0); double marginLevelScore = MathMin(MathMax((queenMarginLevel - 80.0) / 220.0, 0.0), 1.0); double freeMarginPct = (equity > 0.0) ? (freeMargin / equity) * 100.0 : 0.0; double freeMarginScore = MathMin(MathMax(freeMarginPct / 80.0, 0.0), 1.0); double marginUsedPct = (equity > 0.0) ? (margin / equity) * 100.0 : 1000.0; double loadScore = 1.0 - MathMin(MathMax(marginUsedPct / 120.0, 0.0), 1.0); queenAccountHealthScore = (equityScore * 0.40) + (marginLevelScore * 0.25) + (freeMarginScore * 0.20) + (loadScore * 0.15); queenAccountHealthScore = MathMin(MathMax(queenAccountHealthScore, 0.0), 1.0); } //+------------------------------------------------------------------+ // INITIALIZATION //+------------------------------------------------------------------+ int OnInit() { // Start MicCmd (voice via SAPI) early; rest of init follows if(!g_micStarted && EnableVoicePrompts) { bool grammarOk = EnsureVoiceGrammarFile(); string micErr = ""; if(grammarOk && MicStart(g_voiceGrammarPath)) { g_micStarted = true; Print("MicCmd started with grammar: ", g_voiceGrammarPath); } else { MicGetLastError(micErr); if(!grammarOk) Print("VOICE CMD | Grammar unavailable, trying dictation mode (no grammar file)."); else Print("MicCmd grammar start failed: ", micErr, " | retrying dictation mode."); // Fallback: start MicCmd without grammar (dictation catches any phrase) if(MicStart("")) { g_micStarted = true; Print("MicCmd started in dictation mode (fallback)."); } else { MicGetLastError(micErr); Print("MicCmd start failed even in dictation mode: ", micErr); } } } // Validate inputs first if(!ValidateInputs()) { Print("CRITICAL ERROR: Input validation failed. Expert Advisor will not start."); return(INIT_FAILED); } // Initialize trade object trade.SetExpertMagicNumber(MagicNumber); // Initialize external decision DLL (best effort with native fallback). g_decisionDllReady = false; g_lastDecisionSnapshotLog = 0; g_lastDllDecisionAction = "N/A"; g_lastDllDecisionSymbol = ""; g_lastDllDecisionRegime = "UNKNOWN"; g_lastDllDecisionScore = 5; g_lastDllDecisionAllowProb = 0.0; g_lastDllDecisionRiskMul = 1.0; g_lastDllDecisionLatencyUs = 0.0; g_lastDllSnapshot = ""; g_lastDllSnapScoreCalls = -1; g_lastDllSnapLearnCalls = -1; g_lastDllSnapBasketLearnCalls = -1; g_lastDllSnapCacheHits = -1; g_lastDllSnapBaselineVol = -1.0; g_lastDllSnapBaselineSpread = -1.0; if(UseDecisionCoreDll) { int dcInitRc = dc_init(DecisionCoreDbPath, DecisionCoreSeed); if(dcInitRc > 0) { g_decisionDllReady = true; Print("DECISION DLL | Initialized | path=", DecisionCoreDbPath, " | seed=", DecisionCoreSeed); } else { Print("DECISION DLL | Init failed rc=", dcInitRc, " | fallback to native memory engine"); } } // Open voice command DB (best-effort) g_cmdDb = sqlite_openW(g_cmdDbPath); if(g_cmdDb > 0) { sqlite_reset_stuck(g_cmdDb, 60); Print("VOICE CMD | DB opened: ", g_cmdDbPath); } else { Print("VOICE CMD | Failed to open DB: ", g_cmdDbPath); } // Friendly intro voice on attach (Zira TTS - no .wav needed) if(EnableVoicePrompts) SpeakZira("good day Sir Tudor .Systems is online. I will trade for you."); // Determine conversion for distance inputs (TP/SL/trailing/breakeven/spread) // If InputsArePips=true we convert pips to broker points, otherwise we use raw broker points double pipToPointMultiplier = 1.0; if(Digits() == 5 || Digits() == 3) { pipToPointMultiplier = 10.0; // 5/3-digit symbols: 1 pip = 10 points } else if(Digits() == 4 || Digits() == 2) { pipToPointMultiplier = 1.0; // 4/2-digit symbols: 1 pip = 1 point } else { pipToPointMultiplier = 10.0; // Safe default Print("?s??,? Unknown digits format (", Digits(), ") - using default: 1 pip = 10 points"); } // Inputs can be interpreted either as pips (then we scale by pipToPointMultiplier) or raw broker points if(InputsArePips) { point_value = Point() * pipToPointMultiplier; // price distance per input pip pip_value = pipToPointMultiplier; // points per pip for spread conversion } else { point_value = Point(); // price distance per broker point pip_value = 1.0; // spread inputs are already in points } long slippagePoints = (long)(InputsArePips ? (Slippage * pip_value) : Slippage); // convert pips to broker points when needed trade.SetDeviationInPoints(slippagePoints); // Apply slippage setting to trade execution string unitLabel = InputsArePips ? "pip" : "point"; Print("Symbol: ", Symbol(), " | Digits: ", Digits(), " | Price per ", unitLabel, ": ", point_value, " | Points per ", unitLabel, ": ", pip_value); Print("TakeProfit: ", TakeProfit, " ", unitLabel, "s = ", (TakeProfit * point_value), " price units"); Print("StopLoss: ", StopLoss, " ", unitLabel, "s = ", (StopLoss * point_value), " price units"); // Load persisted state (optional) if(UseFilePersistence) LoadStateFromFile(); // Initialize Order Book (DOM) if enabled if(UseDOMFilter && !ForceDisableDOM) { // Check account type (Demo/Live) ENUM_ACCOUNT_TRADE_MODE accountMode = (ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE); bool isDemoAccount = (accountMode == ACCOUNT_TRADE_MODE_DEMO); bool isLiveAccount = (accountMode == ACCOUNT_TRADE_MODE_REAL); string accountType = isDemoAccount ? "DEMO" : (isLiveAccount ? "LIVE" : "UNKNOWN"); // Check if running in Strategy Tester - DOM is not available in tester bool isTester = MQLInfoInteger(MQL_TESTER); if(isTester) { Print(" Strategy Tester detected - DOM (Order Book) is NOT available in tester"); string domModeRequested = DOMAsSignal ? "SIGNAL MODE" : "FILTER MODE"; Print(" DOM (", domModeRequested, ") will be disabled. Trading will use only Consecutive Bars signals."); Print(" Note: DOM requires ECN broker and live/demo account - not available in Strategy Tester"); domFilterActive = false; domAvailable = false; } // Outside tester: enforce ECN requirement for DOM (auto-disable if non-ECN) else { domAvailable = IsECNBroker(); if(!domAvailable) { Print(" ECN Broker NOT detected - DOM auto-disabled"); Print(" Account Type: ", accountType, " | Broker: ", AccountInfoString(ACCOUNT_COMPANY)); Print(" Trading continues normally without DOM."); if(isDemoAccount) Print(" Note: DEMO works with DOM only on ECN brokers."); domFilterActive = false; } else { Print(" ECN Broker detected - DOM available"); Print(" Account Type: ", accountType, " (DOM works on both DEMO and LIVE ECN accounts)"); domFilterActive = true; } } // Try to initialize DOM if available if(domFilterActive && domAvailable) { if(MarketBookAdd(Symbol())) { domInitialized = true; domFilterActive = true; Print(" Order Book (DOM) initialized for ", Symbol()); string domMode = DOMAsSignal ? "SIGNAL MODE (provides signal, doesn't block)" : "FILTER MODE (blocks trades)"; ENUM_ACCOUNT_TRADE_MODE accountMode = (ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE); string accountType = (accountMode == ACCOUNT_TRADE_MODE_DEMO) ? "DEMO" : ((accountMode == ACCOUNT_TRADE_MODE_REAL) ? "LIVE" : "UNKNOWN"); Print(" DOM: ENABLED - ", domMode, " (Imbalance: ", MinImbalanceRatio, "x | Min Liquidity: ", MinLiquidity, " lots)"); Print(" Account Type: ", accountType, " | Broker: ", AccountInfoString(ACCOUNT_COMPANY)); } else { int error = GetLastError(); Print(" Failed to initialize Order Book (DOM) - Error: ", error); if(error == 4014) // ERR_MARKET_NOT_SELECTED or similar { Print(" Market Book not available for this symbol or broker"); } Print(" DOM filter disabled - trading will continue without DOM analysis"); domFilterActive = false; domAvailable = false; } } else if(UseDOMFilter && !domFilterActive) { bool isTester = MQLInfoInteger(MQL_TESTER); if(isTester) { Print(" DOM requested but not available (Strategy Tester) - trading without DOM"); } else { string domModeRequested = DOMAsSignal ? "SIGNAL MODE" : "FILTER MODE"; ENUM_ACCOUNT_TRADE_MODE accountMode = (ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE); string accountType = (accountMode == ACCOUNT_TRADE_MODE_DEMO) ? "DEMO" : ((accountMode == ACCOUNT_TRADE_MODE_REAL) ? "LIVE" : "UNKNOWN"); Print(" DOM (", domModeRequested, ") requested but not available - trading without DOM"); Print(" Account Type: ", accountType, " | Broker: ", AccountInfoString(ACCOUNT_COMPANY)); Print(" Reason: Non-ECN broker detected. DOM requires ECN broker (IC Markets, Pepperstone, FXTM, etc.)"); if(accountMode == ACCOUNT_TRADE_MODE_DEMO) { Print(" Note: Even on DEMO accounts, DOM only works with ECN brokers"); } } } } else { domFilterActive = false; if(UseDOMFilter && ForceDisableDOM) Print("DOM hard bypass active - DOM is ignored by configuration."); } // Validate symbol information if(!ValidateSymbolInfo()) { Print("CRITICAL ERROR: Symbol validation failed. Expert Advisor will not start."); return(INIT_FAILED); } Print("=== King Gold Scalper v9.0 Initialized ==="); Print("Symbol: ", Symbol()); ENUM_ACCOUNT_TRADE_MODE accountMode = (ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE); string accountType = (accountMode == ACCOUNT_TRADE_MODE_DEMO) ? "DEMO" : ((accountMode == ACCOUNT_TRADE_MODE_REAL) ? "LIVE" : "UNKNOWN"); Print("Account Type: ", accountType, " | Broker: ", AccountInfoString(ACCOUNT_COMPANY)); string timeSource = (UseBrokerTime) ? "Broker Server Time" : ("Local PC Time (Offset: " + IntegerToString(TimezoneOffset) + "h)"); Print("Time Source: ", timeSource); Print("Trading Hours: ", StartHour, ":00 - ", EndHour, ":00 (", timeSource, ")"); double configuredBaseRisk = GetConfiguredBaseRiskPercent(); Print("Risk: ", configuredBaseRisk, "% (RiskPercent=", RiskPercent, ", Default=", DefaultRiskPercent, ") | TP: ", TakeProfit, " points | SL: ", StopLoss, " points"); if(RiskPercent <= 0.0) Print("RiskPercent is 0. Using DefaultRiskPercent for lot sizing and adaptive risk."); string consecutiveSignalMode = UseMomentumExhaustionSignals ? "Momentum Exhaustion (reversal)" : "Trend Following"; Print("Signals: Consecutive Bars (", consecutiveSignalMode, ") + Multi-TF scanner/memory per symbol"); // Initialize cache systems accountCache.update(); marketCache.update(); Print("Cache systems initialized - Account data updates every ", ACCOUNT_UPDATE_INTERVAL, " seconds"); Print("Drawdown protection enabled - Max allowed: ", MAX_DRAWDOWN_PERCENT, "%"); NormalizeTelegramInputs(); if(EnableTelegramAlerts) { Print("TELEGRAM ENABLED | Ensure MT5 WebRequest allow-list contains: https://api.telegram.org"); if(StringLen(g_telegramBotToken) <= 0 || StringLen(g_telegramChatId) <= 0) Print("TELEGRAM WARNING | TelegramBotToken/TelegramChatId are empty - notifications disabled until configured."); else { datetime telegramTestTime = 0; bool telegramTestOk = SendTelegramMessage("Test OK - King of Gold", telegramTestTime, 0); if(telegramTestOk) Print("TELEGRAM TEST | Message sent successfully."); else Print("TELEGRAM TEST | Failed to send test message. Check token/chat_id and WebRequest allow-list."); } } // Initialize advanced memory system InitializeMemorySystem(); if(UseWarmupOnInit) { WarmupFromHistory(); if(WarmupAllMarketWatchSymbols) WarmupAllSymbolsFromMarketWatch(); } Print("Advanced memory system initialized - Learning capabilities activated!"); if(domFilterActive) { string domMode = DOMAsSignal ? "SIGNAL MODE (provides signal, doesn't block)" : "FILTER MODE (blocks trades)"; Print("DOM: ACTIVE - ", domMode, " (Imbalance: ", MinImbalanceRatio, "x | Min Liquidity: ", MinLiquidity, " lots)"); Print(" DOM works on both DEMO and LIVE ECN accounts"); } else if(UseDOMFilter) { bool isTester = MQLInfoInteger(MQL_TESTER); if(isTester) { string domModeRequested = DOMAsSignal ? "SIGNAL MODE" : "FILTER MODE"; Print("DOM: REQUESTED (", domModeRequested, ") but not available - Strategy Tester detected (DOM requires live/demo ECN account)"); } else { string domModeRequested = DOMAsSignal ? "SIGNAL MODE" : "FILTER MODE"; ENUM_ACCOUNT_TRADE_MODE accountModeFinal = (ENUM_ACCOUNT_TRADE_MODE)AccountInfoInteger(ACCOUNT_TRADE_MODE); string accountTypeFinal = (accountModeFinal == ACCOUNT_TRADE_MODE_DEMO) ? "DEMO" : ((accountModeFinal == ACCOUNT_TRADE_MODE_REAL) ? "LIVE" : "UNKNOWN"); Print("DOM: REQUESTED (", domModeRequested, ") but not available"); Print(" Account Type: ", accountTypeFinal, " | Broker: ", AccountInfoString(ACCOUNT_COMPANY)); Print(" Reason: Non-ECN broker or initialization failed"); if(accountModeFinal == ACCOUNT_TRADE_MODE_DEMO) { Print(" Note: Even on DEMO accounts, DOM only works with ECN brokers (IC Markets, Pepperstone, FXTM, etc.)"); } else { Print(" Note: DOM works on both DEMO and LIVE accounts if broker is ECN (IC Markets, Pepperstone, etc.)"); } } } Print("Magic Number: ", MagicNumber); // Sentiment/adaptive init CalculateTotalClosedTrades(); lastBalance = AccountInfoDouble(ACCOUNT_BALANCE); currentRiskPercent = GetConfiguredBaseRiskPercent(); Print("Historical Trades: ", winCount, "W / ", lossCount, "L"); // Reset tick-bar state on init tickBarIndex = 0; tickBarCount = 0; tickBarOpen = 0; tickBarHigh = 0; tickBarLow = 0; tickBarClose = 0; fastSpikeReferenceMs = GetTickCount(); fastSpikeReferencePrice = (SymbolInfoDouble(_Symbol, SYMBOL_BID) + SymbolInfoDouble(_Symbol, SYMBOL_ASK)) / 2.0; fastSpikeAnchorBarTime = iTime(_Symbol, (ENUM_TIMEFRAMES)Period(), 0); fastSpikeAnchorOpen = iOpen(_Symbol, (ENUM_TIMEFRAMES)Period(), 0); if(fastSpikeAnchorOpen <= 0.0) fastSpikeAnchorOpen = fastSpikeReferencePrice; lastFastSpikeSignalMs = 0; ResetFastSpikePendingState(); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ // MAIN LOGIC - CALLED ON EVERY TICK //+------------------------------------------------------------------+ void OnTick() { PollMicCmdQueue(); EmitHeartbeat(); EmitDecisionDllSnapshot(); AutosaveStateIfDue(); CheckUpcomingEconomicNewsAlerts(); CheckFutureSessionEventAlerts(); PollVoiceCommandQueue(); // ========== PRIORITY 1: CLOSE ALL TRADES AT END OF TRADING SESSION ========== // Only close trades at EndHour if AutoCloseProfit target was hit datetime currentTime = GetCurrentTimeWithOffset(); UpdateRecoveryMode(); bool useAutoCloseEffective = UseAutoClose || (UseRecoveryAutoClose && recoveryModeActive); MqlDateTime dt; TimeToStruct(currentTime, dt); int currentHour = dt.hour; string activeSymbols[]; GetAutoCloseActiveSymbols(activeSymbols); if(useAutoCloseEffective) { for(int s = 0; s < ArraySize(activeSymbols); s++) { string acSymbol = activeSymbols[s]; int openTrades = CountOpenTradesForSymbolMagic(acSymbol); if(openTrades <= 0) continue; int stateIdx = FindAutoCloseStateIndex(acSymbol, true); double totalProfit = CalculateTotalProfitForSymbol(acSymbol); bool symbolUseAutoCloseEffective = UseAutoClose || (UseRecoveryAutoClose && recoveryModeActive); double symbolAutoCloseProfitEffective = AutoCloseProfit; bool symbolUseAutoCloseTrailingEffective = UseAutoCloseTrailing; double symbolAutoCloseTrailingStartEffective = AutoCloseTrailingStart; double symbolAutoCloseTrailingStopEffective = AutoCloseTrailingStop; bool recoveryCloseContext = false; if(UseRecoveryAutoClose && recoveryModeActive) { bool symbolHoldConfiguredTarget = ShouldHoldConfiguredAutoCloseInRecoveryForSymbol(acSymbol); LogRecoveryTargetModeForSymbol(acSymbol, symbolHoldConfiguredTarget); if(!symbolHoldConfiguredTarget) { double recoveryScale = MathMax(0.1, RecoveryRiskMultiplier); symbolAutoCloseProfitEffective = MathMax(0.0, RecoveryMinProfitUsd / recoveryScale); symbolUseAutoCloseTrailingEffective = RecoveryUseAutoCloseTrailing; symbolAutoCloseTrailingStartEffective = RecoveryAutoCloseTrailingStart / recoveryScale; symbolAutoCloseTrailingStopEffective = RecoveryAutoCloseTrailingStop / recoveryScale; recoveryCloseContext = true; } } if(!symbolUseAutoCloseEffective) continue; double trailingStart = MathMax(symbolAutoCloseProfitEffective, symbolAutoCloseTrailingStartEffective); // Session-end close per symbol, only after target was reached. if(currentHour >= EndHour && totalProfit >= symbolAutoCloseProfitEffective) { Print(" TRADING SESSION ENDED [", acSymbol, "] at hour ", currentHour, " (EndHour=", EndHour, ") - CLOSING ", openTrades, " TRADES (Profit: $", totalProfit, " >= Target: $", symbolAutoCloseProfitEffective, ")"); if(EnableVoicePrompts && VoiceCooldown(lastAutoCloseSoundTime, VoiceCooldownSeconds)) SpeakZira("Auto close triggered on " + acSymbol + ". Closing all trades."); if(TelegramSendCloses) { string msg = "📌 KING GOLD AUTO-CLOSE\n" "🚪 Session end trigger\n" "📊 Symbol: " + acSymbol + "\n" "💰 Profit: $" + DoubleToString(totalProfit, 2) + " / Target: $" + DoubleToString(symbolAutoCloseProfitEffective, 2) + "\n" "✅ Action: closing all trades\n" "⏰ Time: " + TimeToString(currentTime, TIME_DATE | TIME_SECONDS); SendTelegramMessage(msg, lastTelegramCloseTime, TelegramCooldownSeconds); } RegisterAutoCloseLearningContext(acSymbol, totalProfit, currentTime, openTrades, recoveryModeActive); CloseAllTradesForSymbol(acSymbol); if(recoveryCloseContext) Print(" CLOSED BY RECOVERY AUTOCLOSE [", acSymbol, "] | Profit: $", DoubleToString(totalProfit, 2), " | Target: $", DoubleToString(symbolAutoCloseProfitEffective, 2)); MarkSymbolAutoCloseTriggered(acSymbol, currentTime); continue; } // ========== TRAILING CLOSE MODE (PER SYMBOL) ========== if(symbolUseAutoCloseTrailingEffective && autoCloseSymbolStates[stateIdx].inTrailing) { if(totalProfit > autoCloseSymbolStates[stateIdx].highestProfitInTrailing) { autoCloseSymbolStates[stateIdx].highestProfitInTrailing = totalProfit; Print(" NEW PROFIT PEAK [", acSymbol, "] in Trailing Mode: $", autoCloseSymbolStates[stateIdx].highestProfitInTrailing); } double trailingCloseTarget = autoCloseSymbolStates[stateIdx].highestProfitInTrailing - symbolAutoCloseTrailingStopEffective; if(totalProfit <= trailingCloseTarget) { Print(" Trailing Stop Triggered [", acSymbol, "]: Profit $", totalProfit, " <= Target $", trailingCloseTarget, " - CLOSING TRADES"); if(EnableVoicePrompts && VoiceCooldown(lastAutoCloseSoundTime, VoiceCooldownSeconds)) SpeakZira("Auto close trailing stop triggered on " + acSymbol + ". Closing all trades."); if(TelegramSendCloses) { string msg = "📌 KING GOLD AUTO-CLOSE\n" "🧷 Trailing stop triggered\n" "📊 Symbol: " + acSymbol + "\n" "💰 Profit: $" + DoubleToString(totalProfit, 2) + "\n" "🎯 Trail target: $" + DoubleToString(trailingCloseTarget, 2) + "\n" "✅ Action: closing all trades\n" "⏰ Time: " + TimeToString(currentTime, TIME_DATE | TIME_SECONDS); SendTelegramMessage(msg, lastTelegramCloseTime, TelegramCooldownSeconds); } RegisterAutoCloseLearningContext(acSymbol, totalProfit, currentTime, openTrades, recoveryModeActive); CloseAllTradesForSymbol(acSymbol); if(recoveryCloseContext) Print(" CLOSED BY RECOVERY AUTOCLOSE (TRAILING) [", acSymbol, "] | Profit: $", DoubleToString(totalProfit, 2), " | TrailTarget: $", DoubleToString(trailingCloseTarget, 2)); MarkSymbolAutoCloseTriggered(acSymbol, currentTime); continue; } } else if(symbolUseAutoCloseTrailingEffective && totalProfit >= trailingStart) { autoCloseSymbolStates[stateIdx].inTrailing = true; autoCloseSymbolStates[stateIdx].highestProfitInTrailing = totalProfit; Print(" Target Profit Reached [", acSymbol, "]: $", totalProfit, " - ENTERING TRAILING CLOSE MODE (Start: $", trailingStart, ", Trail: $", symbolAutoCloseTrailingStopEffective, ")"); if(EnableVoicePrompts && VoiceCooldown(lastAutoCloseSoundTime, VoiceCooldownSeconds)) SpeakZira("Auto close trailing mode activated on " + acSymbol + "."); if(TelegramSendCloses) { string msg = "📌 KING GOLD AUTO-CLOSE\n" "📈 Trailing mode activated\n" "📊 Symbol: " + acSymbol + "\n" "💰 Current profit: $" + DoubleToString(totalProfit, 2) + "\n" "🎯 Start: $" + DoubleToString(trailingStart, 2) + " | Trail: $" + DoubleToString(symbolAutoCloseTrailingStopEffective, 2) + "\n" "⏰ Time: " + TimeToString(currentTime, TIME_DATE | TIME_SECONDS); SendTelegramMessage(msg, lastTelegramCloseTime, TelegramCooldownSeconds); } SyncChartAutoCloseState(); continue; } else if(!symbolUseAutoCloseTrailingEffective && totalProfit >= symbolAutoCloseProfitEffective) { Print(" Target Profit Reached [", acSymbol, "]: ", totalProfit, " >= ", symbolAutoCloseProfitEffective, " - CLOSING TRADES"); if(EnableVoicePrompts && VoiceCooldown(lastAutoCloseSoundTime, VoiceCooldownSeconds)) SpeakZira("Sir.Auto close target reached on " + acSymbol + ". Closing all trades.profits book in."); if(TelegramSendCloses) { string msg = "📌 KING GOLD AUTO-CLOSE\n" "🎯 Target reached\n" "📊 Symbol: " + acSymbol + "\n" "💰 Profit: $" + DoubleToString(totalProfit, 2) + " / Target: $" + DoubleToString(symbolAutoCloseProfitEffective, 2) + "\n" "✅ Action: closing all trades\n" "⏰ Time: " + TimeToString(currentTime, TIME_DATE | TIME_SECONDS); SendTelegramMessage(msg, lastTelegramCloseTime, TelegramCooldownSeconds); } RegisterAutoCloseLearningContext(acSymbol, totalProfit, currentTime, openTrades, recoveryModeActive); CloseAllTradesForSymbol(acSymbol); if(recoveryCloseContext) Print(" CLOSED BY RECOVERY AUTOCLOSE [", acSymbol, "] | Profit: $", DoubleToString(totalProfit, 2), " | Target: $", DoubleToString(symbolAutoCloseProfitEffective, 2)); MarkSymbolAutoCloseTriggered(acSymbol, currentTime); continue; } } } // ========== PER-SYMBOL RE-ENTRY GATE AFTER AUTO-CLOSE ========== if(!AutoCloseResumeTrading) { for(int i = 0; i < ArraySize(autoCloseSymbolStates); i++) { if(!autoCloseSymbolStates[i].triggered) continue; IsSymbolAutoCloseBlocked(autoCloseSymbolStates[i].symbol, currentTime); } } // ========== PRIORITY 3: Check if we can trade (normal trading hour restrictions) ========== if(!CanTrade()) return; // Safe mode detection safeModeActive = (UseSafeBalanceMode && AccountInfoDouble(ACCOUNT_BALANCE) < SafeBalanceThreshold); currentMaxOpenTrades = safeModeActive ? SafeMaxOpenTrades : MaxOpenTrades; // Update consecutive bar counters UpdateConsecutiveSignals(); // ========== PRIORITY 4: ENHANCED SYSTEM UPDATES ========== UpdateMarketState(); UpdateMarketStressDetection(); if(marketState.marketStress) EmitRiskWarningSound(); memoryAdaptiveRiskMultiplier = GetAdaptiveRiskMultiplier(); // Update enhanced memory system static datetime lastMemoryUpdate = 0; if(TimeCurrent() - lastMemoryUpdate > 60) // Every minute { UpdateEnhancedMemorySystem(); lastMemoryUpdate = TimeCurrent(); } // Scan for opportunities static datetime lastScan = 0; if(TimeCurrent() - lastScan > 30) // Every 30 seconds { scanner.scanAllSymbols(); lastScan = TimeCurrent(); // Print opportunity report Print("=== MULTI-SYMBOL SCANNER ==="); Print(scanner.getTopOpportunities()); } // Enhanced opportunity gate (multi-symbol, full rules) int maxTopSymbols = GetEffectiveMaxScannedOpportunities(); for(int i = 0; i < maxTopSymbols; i++) { if(scanner.topSymbols[i] == "") continue; string symbol = scanner.topSymbols[i]; if(!IsSymbolAllowedByScanCategories(symbol)) continue; double symbolBid = SymbolInfoDouble(symbol, SYMBOL_BID); double symbolAsk = SymbolInfoDouble(symbol, SYMBOL_ASK); if(symbolBid <= 0.0 || symbolAsk <= 0.0) continue; int symbolCurrentBar = (int)iTime(symbol, Period(), 1); // closed bar id (not live tick bar) if(symbolCurrentBar <= 0) continue; int bearishConsecutiveForBuy = GetConsecutiveBarsMultiTFForSymbol(symbol, false); int bullishConsecutiveForSell = GetConsecutiveBarsMultiTFForSymbol(symbol, true); bool longConsecutiveOk = true; bool shortConsecutiveOk = true; if(UseConsecutiveBars) { if(UseMomentumExhaustionSignals) { // Reversal mode: BUY after bearish streak, SELL after bullish streak longConsecutiveOk = (bearishConsecutiveForBuy >= BearishBarsForBuy); shortConsecutiveOk = (bullishConsecutiveForSell >= BullishBarsForSell); } else { // Trend mode: BUY after bullish streak, SELL after bearish streak longConsecutiveOk = (bullishConsecutiveForSell >= BullishBarsForSell); shortConsecutiveOk = (bearishConsecutiveForBuy >= BearishBarsForBuy); } } enhancedTFAnalyzer.analyzeSymbolWithSignals(symbol); if(enhancedTFAnalyzer.shouldEnterLong(symbol) && longConsecutiveOk) { if(CountOpenTradesAllSymbols() < currentMaxOpenTrades && CountOpenTradesOfTypeForSymbol(symbol, POSITION_TYPE_BUY) < MaxTradesPerSymbolDirection && CanEnterSymbolDirectionOnBar(symbol, 1, symbolCurrentBar)) { double currentVolumeRatio = GetCurrentVolumeRatioForSymbol(symbol, (ENUM_TIMEFRAMES)Period()); bool useExploreLot = false; int decisionScore = 0; bool hardFiltersPassed = CheckAllFiltersForTradeForSymbol(symbol, POSITION_TYPE_BUY, symbolBid, symbolAsk, bearishConsecutiveForBuy); if(!hardFiltersPassed) continue; bool blockedByMemory = ShouldSkipTradeBasedOnMemoryForSymbol(symbol, 1, bearishConsecutiveForBuy, currentVolumeRatio, symbolBid, symbolAsk, useExploreLot, decisionScore); if(blockedByMemory) { MarkSymbolDirectionEnteredOnBar(symbol, 1, symbolCurrentBar); continue; } if(hardFiltersPassed) { nextTradeUseExploreLot = useExploreLot; ExecuteBullishEntry(symbol); nextTradeUseExploreLot = false; MarkSymbolDirectionEnteredOnBar(symbol, 1, symbolCurrentBar); ManageOpenPositions(); continue; } } } else if(enhancedTFAnalyzer.shouldEnterShort(symbol) && shortConsecutiveOk) { if(CountOpenTradesAllSymbols() < currentMaxOpenTrades && CountOpenTradesOfTypeForSymbol(symbol, POSITION_TYPE_SELL) < MaxTradesPerSymbolDirection && CanEnterSymbolDirectionOnBar(symbol, -1, symbolCurrentBar)) { double currentVolumeRatio = GetCurrentVolumeRatioForSymbol(symbol, (ENUM_TIMEFRAMES)Period()); bool useExploreLot = false; int decisionScore = 0; bool hardFiltersPassed = CheckAllFiltersForTradeForSymbol(symbol, POSITION_TYPE_SELL, symbolBid, symbolAsk, bullishConsecutiveForSell); if(!hardFiltersPassed) continue; bool blockedByMemory = ShouldSkipTradeBasedOnMemoryForSymbol(symbol, -1, bullishConsecutiveForSell, currentVolumeRatio, symbolBid, symbolAsk, useExploreLot, decisionScore); if(blockedByMemory) { MarkSymbolDirectionEnteredOnBar(symbol, -1, symbolCurrentBar); continue; } if(hardFiltersPassed) { nextTradeUseExploreLot = useExploreLot; ExecuteBearishEntry(symbol); nextTradeUseExploreLot = false; MarkSymbolDirectionEnteredOnBar(symbol, -1, symbolCurrentBar); ManageOpenPositions(); continue; } } } } // Print signal reports static datetime lastSignalReport = 0; int signalReportInterval = MathMax(10, SignalReportIntervalSeconds); if(TimeCurrent() - lastSignalReport > signalReportInterval) { for(int i = 0; i < MathMin(3, scanner.opportunityCount); i++) { Print("=== ", scanner.opportunities[i].symbol, " SIGNAL ANALYSIS ==="); Print(enhancedTFAnalyzer.generateSignalReport(scanner.opportunities[i].symbol)); } lastSignalReport = TimeCurrent(); } // Sentiment is updated in OnTradeTransaction to avoid duplicate counting if(UseSignalStrengthGreed) UpdateSignalStrengthGreed(); if(UseAdaptiveTrading) UpdateAdaptiveTrading(); // Learning telemetry (memory + reward) - periodic visibility static datetime lastLearningReport = 0; int learningInterval = MathMax(30, LearningReportIntervalSeconds); if(UseLearningReport && (TimeCurrent() - lastLearningReport > learningInterval)) { PrintLearningTelemetry(); lastLearningReport = TimeCurrent(); } int bestSessionInterval = MathMax(60, BestSessionReportIntervalSeconds); if(UseBestSessionsEngine && (lastBestSessionReport == 0 || (TimeCurrent() - lastBestSessionReport) > bestSessionInterval)) { PrintBestSessionReport(); lastBestSessionReport = TimeCurrent(); } // Renew pending limit orders if volume momentum weakens RenewLimitOrdersByVolume(); // Update KogDecisionCore equity tracking UpdateEquityMetrics(); // Check cooldown after losing trade (revenge trading prevention) if(UseCooldown && IsInCooldown()) return; // Check current price double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID); double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); // ========== FAST SPIKE REACTION ========== uint nowMs = GetTickCount(); datetime anchorBarTime = iTime(_Symbol, (ENUM_TIMEFRAMES)Period(), 0); if(anchorBarTime != 0 && anchorBarTime != fastSpikeAnchorBarTime) { fastSpikeAnchorBarTime = anchorBarTime; fastSpikeAnchorOpen = iOpen(_Symbol, (ENUM_TIMEFRAMES)Period(), 0); if(fastSpikeAnchorOpen <= 0.0) fastSpikeAnchorOpen = (bid + ask) * 0.5; fastSpikeReferenceMs = nowMs; fastSpikeReferencePrice = fastSpikeAnchorOpen; ResetFastSpikePendingState(); } else if(fastSpikeAnchorOpen <= 0.0) { fastSpikeAnchorOpen = iOpen(_Symbol, (ENUM_TIMEFRAMES)Period(), 0); if(fastSpikeAnchorOpen <= 0.0) fastSpikeAnchorOpen = (bid + ask) * 0.5; fastSpikeReferenceMs = nowMs; fastSpikeReferencePrice = fastSpikeAnchorOpen; } int autoThresholdPoints = 0; uint autoWindowMs = 0; uint cooldownMs = 0; bool preferMomentum = false; GetAutoFastSpikeParameters(autoThresholdPoints, autoWindowMs, cooldownMs, preferMomentum); double spreadPointsNow = (point_value > 0.0) ? (MathAbs(ask - bid) / point_value) : 0.0; int maxSpikeSpreadPoints = (int)MathMax(1, MaxNewsSpreadPoints); if(fastSpikePending) { int newsConfirmMs = (NewsConfirmMs > 0) ? NewsConfirmMs : (int)autoWindowMs; uint confirmWindowMs = (uint)MathMax(120, MathMin(3000, newsConfirmMs)); uint confirmMinMs = (uint)MathMax(0, MathMin((int)confirmWindowMs, FastSpikeConfirmMinMs)); int holdTicksRequired = (int)MathMax(1, MathMin(5, FastSpikeHoldTicks)); double confirmFactor = ClampValue(FastSpikeConfirmThresholdFactor, 0.6, 1.5); uint pendingElapsedMs = nowMs - fastSpikePendingStartMs; if(pendingElapsedMs > confirmWindowMs || fastSpikePendingAnchorPrice <= 0.0) { ResetFastSpikePendingState(); } else { if(spreadPointsNow > (double)maxSpikeSpreadPoints) { if(pendingElapsedMs > (confirmWindowMs / 2)) { Print("FAST SPIKE REACTION | pending canceled (high spread ", DoubleToString(spreadPointsNow, 1), " > ", maxSpikeSpreadPoints, " points)"); ResetFastSpikePendingState(); } } else { double breakDistance = (double)MathMax(1, fastSpikePendingThresholdPoints) * confirmFactor * point_value; double upLevel = fastSpikePendingAnchorPrice + breakDistance; double dnLevel = fastSpikePendingAnchorPrice - breakDistance; bool reverted = false; bool holdingBreak = false; if(fastSpikePendingDirection == 1) { reverted = (bid < fastSpikePendingAnchorPrice); holdingBreak = (bid > upLevel); } else if(fastSpikePendingDirection == -1) { reverted = (ask > fastSpikePendingAnchorPrice); holdingBreak = (ask < dnLevel); } else reverted = true; if(reverted) { ResetFastSpikePendingState(); } else { fastSpikePendingHoldTicks = holdingBreak ? (fastSpikePendingHoldTicks + 1) : 0; bool confirmReady = (pendingElapsedMs >= confirmMinMs && fastSpikePendingHoldTicks >= holdTicksRequired); if(confirmReady) { bool spikeCooldownReady = (lastFastSpikeSignalMs == 0 || (nowMs - lastFastSpikeSignalMs) >= cooldownMs); if(spikeCooldownReady && CountOpenTradesAllSymbols() < currentMaxOpenTrades) { int reactionDirection = fastSpikePendingDirection; int positionType = (reactionDirection == 1) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL; bool filtersOk = CheckAllFiltersForTrade(positionType, bid, ask, 1); double spikeVolumeRatio = GetCurrentVolumeRatio(); bool useExploreLot = false; int decisionScore = 0; bool memoryOk = !ShouldSkipTradeBasedOnMemory(reactionDirection, 1, spikeVolumeRatio, bid, ask, useExploreLot, decisionScore); nextTradeUseExploreLot = useExploreLot; if(filtersOk && memoryOk) { string reactionSide = (reactionDirection == 1) ? "BUY" : "SELL"; string spikeSide = (reactionDirection == 1) ? "UP" : "DOWN"; string regimeBias = preferMomentum ? "VOLATILE_BIAS" : "CALM_BIAS"; double spikeVolumeBurstNow = GetInstantVolumeBurst(_Symbol, (ENUM_TIMEFRAMES)Period(), 40); Print("FAST SPIKE REACTION [BREAK_HOLD] | Spike ", spikeSide, " ", DoubleToString(fastSpikePendingMovePoints, 1), " points (confirm ", pendingElapsedMs, "ms, holdTicks ", fastSpikePendingHoldTicks, ", threshold ", fastSpikePendingThresholdPoints, ", autoThreshold ", autoThresholdPoints, ", autoWindow ", autoWindowMs, "ms, cooldown ", cooldownMs, "ms, volBurst ", DoubleToString(spikeVolumeBurstNow, 2), "x, ", regimeBias, ") -> opening ", reactionSide, " order"); if(reactionDirection == 1) OpenBuyTrade(ask); else OpenSellTrade(bid); nextTradeUseExploreLot = false; lastFastSpikeSignalMs = nowMs; ResetFastSpikePendingState(); ManageOpenPositions(); return; // avoid multiple entries in same tick } else { double spikeVolumeBurstNow = GetInstantVolumeBurst(_Symbol, (ENUM_TIMEFRAMES)Period(), 40); Print("FAST SPIKE REACTION [BREAK_HOLD] blocked | move=", DoubleToString(fastSpikePendingMovePoints, 1), " | threshold=", fastSpikePendingThresholdPoints, " | spread=", DoubleToString(spreadPointsNow, 1), " | volBurst=", DoubleToString(spikeVolumeBurstNow, 2), "x"); nextTradeUseExploreLot = false; ResetFastSpikePendingState(); } } } } } } } if(!fastSpikePending) { int spikeDirection = 0; // 1=up spike, -1=down spike double spikeMovePoints = 0.0; double spikeAnchorPrice = 0.0; uint spikeElapsedMs = 0; int spikeThresholdPoints = 0; bool spikeCooldownReady = (lastFastSpikeSignalMs == 0 || (nowMs - lastFastSpikeSignalMs) >= cooldownMs); if(DetectFastSpike(spikeDirection, spikeMovePoints, spikeAnchorPrice, spikeElapsedMs, spikeThresholdPoints) && spikeCooldownReady && CountOpenTradesAllSymbols() < currentMaxOpenTrades) { fastSpikePending = true; fastSpikePendingDirection = spikeDirection; fastSpikePendingStartMs = nowMs; fastSpikePendingAnchorPrice = spikeAnchorPrice; fastSpikePendingThresholdPoints = spikeThresholdPoints; fastSpikePendingMovePoints = spikeMovePoints; fastSpikePendingHoldTicks = 0; string spikeSide = (spikeDirection == 1) ? "UP" : "DOWN"; Print("FAST SPIKE DETECTED | ", spikeSide, " move=", DoubleToString(spikeMovePoints, 1), " points | elapsed=", spikeElapsedMs, "ms | threshold=", spikeThresholdPoints, " | autoThreshold=", autoThresholdPoints, " | spread=", DoubleToString(spreadPointsNow, 1), " -> pending break+hold confirm"); EmitSpikeDetectedAlert(spikeDirection, spikeMovePoints, spikeAnchorPrice, spikeThresholdPoints); } } // Note: Individual filter checks are now done in CheckAllFiltersForTrade() // This allows real-time decision making at every tick, not just at bar close // Get consecutive bar signals bool consecutiveBullishSignal = (consecutiveBullishBars >= BullishBarsForSell); bool consecutiveBearishSignal = (consecutiveBearishBars >= BearishBarsForBuy); bool sellSignalActive = UseMomentumExhaustionSignals ? consecutiveBullishSignal : consecutiveBearishSignal; int sellSignalBars = UseMomentumExhaustionSignals ? consecutiveBullishBars : consecutiveBearishBars; int sellSignalThreshold = UseMomentumExhaustionSignals ? BullishBarsForSell : BearishBarsForBuy; string sellSignalLabel = UseMomentumExhaustionSignals ? "Bullish" : "Bearish"; string sellSignalLabelVoice = UseMomentumExhaustionSignals ? "bullish" : "bearish"; bool buySignalActive = UseMomentumExhaustionSignals ? consecutiveBearishSignal : consecutiveBullishSignal; int buySignalBars = UseMomentumExhaustionSignals ? consecutiveBearishBars : consecutiveBullishBars; int buySignalThreshold = UseMomentumExhaustionSignals ? BearishBarsForBuy : BullishBarsForSell; string buySignalLabel = UseMomentumExhaustionSignals ? "Bearish" : "Bullish"; string buySignalLabelVoice = UseMomentumExhaustionSignals ? "bearish" : "bullish"; // Total open positions limit if(CountOpenTradesAllSymbols() >= currentMaxOpenTrades) { ManageOpenPositions(); return; } // Get current bar number int currentBar = UseTickBarSignals ? tickBarIndex : (int)iTime(Symbol(), Period(), 0); // ========== COMBINED SIGNAL ANALYSIS - Real-time decision making ========== // Combines consecutive bars + all filters for real-time trading decisions // Checks at EVERY TICK (milliseconds) - not just at bar close // Allows entry when ALL conditions are met in real-time // Check SELL signal with all filters combined (real-time) if(UseConsecutiveBars && AllowConsecutiveSell && sellSignalActive && sellSignalBars >= sellSignalThreshold) { if(CountOpenTradesAllSymbols() < currentMaxOpenTrades) { if(lastMemoryBlockSellBar == currentBar) return; // Memory-based intelligence check double currentVolumeRatio = GetCurrentVolumeRatio(); bool useExploreLot = false; int decisionScore = 0; bool hardFiltersPassed = CheckAllFiltersForTrade(POSITION_TYPE_SELL, bid, ask, sellSignalBars); if(!hardFiltersPassed) return; if(ShouldSkipTradeBasedOnMemory(-1, sellSignalBars, currentVolumeRatio, bid, ask, useExploreLot, decisionScore)) { lastMemoryBlockSellBar = currentBar; Print(" MEMORY: SELL signal blocked by intelligence system"); return; } // Real-time combined filter check for SELL - checks at every tick // Only enter once per bar to avoid multiple entries if(lastBullishSignalBar != currentBar && hardFiltersPassed) { double memoryConfidence = GetMemoryBasedConfidence(-1, sellSignalBars, currentVolumeRatio); double adaptiveRisk = memoryAdaptiveRiskMultiplier; Print(" SELL SIGNAL CONFIRMED - Consecutive ", sellSignalLabel, ": ", sellSignalBars, " | Memory Confidence: ", DoubleToString(memoryConfidence * 100, 1), "%", " | Decision Score: ", decisionScore, "/10", " | Adaptive Risk: ", DoubleToString(adaptiveRisk, 2), "x", " | All filters PASSED - TAKING SELL"); if(EnableVoicePrompts) SpeakZira("Sir.I find consecutive " + sellSignalLabelVoice + " bars on symbol " + Symbol() + ". Preparing sell."); nextTradeUseExploreLot = useExploreLot; OpenSellTrade(bid); nextTradeUseExploreLot = false; ManageOpenPositions(); lastBullishSignalBar = currentBar; } // If filters fail, signal is blocked (already logged in CheckAllFiltersForTrade) // Will retry on next tick when filters might pass } } // Check BUY signal with all filters combined (real-time) if(UseConsecutiveBars && AllowConsecutiveBuy && buySignalActive && buySignalBars >= buySignalThreshold) { if(CountOpenTradesAllSymbols() < currentMaxOpenTrades) { if(lastMemoryBlockBuyBar == currentBar) return; // Memory-based intelligence check double currentVolumeRatio = GetCurrentVolumeRatio(); bool useExploreLot = false; int decisionScore = 0; bool hardFiltersPassed = CheckAllFiltersForTrade(POSITION_TYPE_BUY, bid, ask, buySignalBars); if(!hardFiltersPassed) return; if(ShouldSkipTradeBasedOnMemory(1, buySignalBars, currentVolumeRatio, bid, ask, useExploreLot, decisionScore)) { lastMemoryBlockBuyBar = currentBar; Print(" MEMORY: BUY signal blocked by intelligence system"); return; } // Real-time combined filter check for BUY - checks at every tick // Only enter once per bar to avoid multiple entries if(lastBearishSignalBar != currentBar && hardFiltersPassed) { double memoryConfidence = GetMemoryBasedConfidence(1, buySignalBars, currentVolumeRatio); double adaptiveRisk = memoryAdaptiveRiskMultiplier; Print(" BUY SIGNAL CONFIRMED - Consecutive ", buySignalLabel, ": ", buySignalBars, " | Memory Confidence: ", DoubleToString(memoryConfidence * 100, 1), "%", " | Decision Score: ", decisionScore, "/10", " | Adaptive Risk: ", DoubleToString(adaptiveRisk, 2), "x", " | All filters PASSED - TAKING BUY"); if(EnableVoicePrompts) SpeakZira("Sir.I find consecutive " + buySignalLabelVoice + " bars on symbol " + Symbol() + ". Preparing buy."); nextTradeUseExploreLot = useExploreLot; OpenBuyTrade(ask); nextTradeUseExploreLot = false; ManageOpenPositions(); lastBearishSignalBar = currentBar; } // If filters fail, signal is blocked (already logged in CheckAllFiltersForTrade) // Will retry on next tick when filters might pass } } // Update trailing stops, breakeven stops, and dynamic SL/TP adjustments if(UseTrailingStop || UseBreakevenStop || UseDynamicSLTP) ManageOpenPositions(); // Update sentiment (wins/losses, win rate) and chart display TrackSentiment(); UpdateChartDisplay(); } //+------------------------------------------------------------------+ // FORWARD DECLARATIONS //+------------------------------------------------------------------+ void AdjustSLTPBasedOnAnalysis(ulong ticket, int positionType, double entryPrice, double currentSL, double currentTP, double bid, double ask, bool allowSLTightening); void CalculateVolumeProfile(double &liquidityZones[], int positionType, double currentPrice); double FindNearestLiquidityZone(double originalPrice, int positionType, double &distancePoints); bool ShouldRelaxStopManagementForSymbol(string symbol); bool GetPositionEntryFromHistory(ulong positionId, string symbol, long magic, datetime &tIn, double &pIn, int &dir); //+------------------------------------------------------------------+ // MANAGE OPEN POSITIONS (Trailing Stop Loss) - Safer position access //+------------------------------------------------------------------+ void ApplyAdaptiveStopLossByPerformance(ulong ticket, int posType, double entryPrice, double currentSL, double currentTP, double bid, double ask) { if(!UseAdaptiveStopLoss) return; if(ticket <= 0 || point_value <= 0.0) return; double priceNow = (posType == POSITION_TYPE_BUY) ? ask : bid; double profitPoints = ((posType == POSITION_TYPE_BUY) ? (priceNow - entryPrice) : (entryPrice - priceNow)) / point_value; if(profitPoints <= 0.0) return; double startPoints = 40.0; double lockFactor = 0.30; if(performanceMode == "CONSERVATIVE") { startPoints = 25.0; // protect earlier in conservative mode lockFactor = 0.45; } else if(performanceMode == "AGGRESSIVE") { startPoints = 55.0; // allow more breathing room in aggressive mode lockFactor = 0.20; } if(profitPoints < startPoints) return; double lockPoints = MathMax(2.0, profitPoints * lockFactor); double newSL = currentSL; bool shouldModify = false; if(posType == (int)POSITION_TYPE_BUY) { double candidateSL = entryPrice + (lockPoints * point_value); if(candidateSL > currentSL || currentSL <= 0.0) { newSL = candidateSL; shouldModify = true; } } else if(posType == (int)POSITION_TYPE_SELL) { double candidateSL = entryPrice - (lockPoints * point_value); if(candidateSL < currentSL || currentSL <= 0.0) { newSL = candidateSL; shouldModify = true; } } if(shouldModify) ModifyPosition(ticket, newSL, currentTP); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool ShouldRelaxStopManagementForSymbol(string symbol) { // In recovery we prioritize basket-based exits over tight per-position SL. if(UseRecoveryAutoClose && recoveryModeActive) return true; // If symbol is already in auto-close trailing mode, avoid tightening SL further. int idx = FindAutoCloseStateIndex(symbol, false); if(idx >= 0 && autoCloseSymbolStates[idx].inTrailing) return true; return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void RelaxStopToBreakevenInRecovery(ulong ticket, int posType, double entryPrice, double currentSL, double currentTP) { if(!(UseRecoveryAutoClose && recoveryModeActive)) return; if(ticket <= 0 || point_value <= 0.0 || currentSL <= 0.0) return; double beSL = NormalizeToTick(entryPrice); double newSL = currentSL; bool shouldModify = false; double relaxBand = 2.0 * point_value; if(posType == (int)POSITION_TYPE_BUY) { if(currentSL > beSL + relaxBand) { newSL = beSL; shouldModify = true; } } else if(posType == (int)POSITION_TYPE_SELL) { if(currentSL < beSL - relaxBand) { newSL = beSL; shouldModify = true; } } if(shouldModify) ModifyPosition(ticket, newSL, currentTP); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ManageOpenPositions() { double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID); double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); bool relaxSLManagement = ShouldRelaxStopManagementForSymbol(Symbol()); for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0) { // Safely select position before accessing its data if(!PositionSelectByTicket(ticket)) continue; // Verify this is our position and correct symbol if(PositionGetString(POSITION_SYMBOL) != Symbol() || PositionGetInteger(POSITION_MAGIC) != MagicNumber) continue; int posType = (int)PositionGetInteger(POSITION_TYPE); double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN); double currentSL = PositionGetDouble(POSITION_SL); double currentTP = PositionGetDouble(POSITION_TP); if(posType == (int)POSITION_TYPE_BUY) { // Trailing Stop: Only activate after reaching TrailingStartPips profit if(UseTrailingStop && !relaxSLManagement) { double profit = ask - entryPrice; if(profit >= (TrailingStartPips * point_value)) { double newSL = ask - (TrailingStopPips * point_value); if(newSL > currentSL) ModifyPosition(ticket, newSL, currentTP); } } // Breakeven Stop: Move SL to entry after reaching BreakevenTrigger if(UseBreakevenStop && !relaxSLManagement && currentSL < entryPrice) { double profit = ask - entryPrice; if(profit >= (BreakevenTrigger * point_value)) ModifyPosition(ticket, entryPrice, currentTP); } // Dynamic SL/TP Adjustment: Adjust based on market analysis if(UseDynamicSLTP) { AdjustSLTPBasedOnAnalysis(ticket, posType, entryPrice, currentSL, currentTP, bid, ask, !relaxSLManagement); } if(relaxSLManagement) RelaxStopToBreakevenInRecovery(ticket, posType, entryPrice, currentSL, currentTP); // Adaptive stop-loss by performance mode if(!relaxSLManagement) { currentSL = PositionGetDouble(POSITION_SL); currentTP = PositionGetDouble(POSITION_TP); ApplyAdaptiveStopLossByPerformance(ticket, posType, entryPrice, currentSL, currentTP, bid, ask); } } else if(posType == (int)POSITION_TYPE_SELL) { // Trailing Stop: Only activate after reaching TrailingStartPips profit if(UseTrailingStop && !relaxSLManagement) { double profit = entryPrice - bid; if(profit >= (TrailingStartPips * point_value)) { double newSL = bid + (TrailingStopPips * point_value); if(newSL < currentSL || currentSL == 0) ModifyPosition(ticket, newSL, currentTP); } } // Breakeven Stop: Move SL to entry after reaching BreakevenTrigger if(UseBreakevenStop && !relaxSLManagement && (currentSL == 0 || currentSL > entryPrice)) { double profit = entryPrice - bid; if(profit >= (BreakevenTrigger * point_value)) ModifyPosition(ticket, entryPrice, currentTP); } // Dynamic SL/TP Adjustment: Adjust based on market analysis if(UseDynamicSLTP) { AdjustSLTPBasedOnAnalysis(ticket, posType, entryPrice, currentSL, currentTP, bid, ask, !relaxSLManagement); } if(relaxSLManagement) RelaxStopToBreakevenInRecovery(ticket, posType, entryPrice, currentSL, currentTP); // Adaptive stop-loss by performance mode if(!relaxSLManagement) { currentSL = PositionGetDouble(POSITION_SL); currentTP = PositionGetDouble(POSITION_TP); ApplyAdaptiveStopLossByPerformance(ticket, posType, entryPrice, currentSL, currentTP, bid, ask); } } } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void ModifyPosition(ulong ticket, double newSL, double newTP) { // Validate inputs if(ticket <= 0) { Print(" ModifyPosition: Invalid parameters - Ticket: ", ticket, " SL: ", newSL, " TP: ", newTP); return; } if(newSL < 0 || newTP < 0) // allow zero to remove SL/TP; only block negative values { Print(" ModifyPosition: Negative SL/TP not allowed - SL: ", newSL, " TP: ", newTP); return; } if(!PositionSelectByTicket(ticket)) return; double currentSL = PositionGetDouble(POSITION_SL); double currentTP = PositionGetDouble(POSITION_TP); int posType = (int)PositionGetInteger(POSITION_TYPE); bool isBuy = (posType == POSITION_TYPE_BUY); double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK); if(newSL > 0) newSL = NormalizeToTick(newSL); if(newTP > 0) newTP = NormalizeToTick(newTP); double normCurrentSL = (currentSL > 0) ? NormalizeToTick(currentSL) : currentSL; double normCurrentTP = (currentTP > 0) ? NormalizeToTick(currentTP) : currentTP; if(ArePricesEqual(newSL, normCurrentSL) && ArePricesEqual(newTP, normCurrentTP)) return; double currentPriceForSL = isBuy ? bid : ask; double currentPriceForTP = isBuy ? ask : bid; if(!IsStopValidForModify(newSL, currentPriceForSL, isBuy, true) || !IsStopValidForModify(newTP, currentPriceForTP, isBuy, false)) { Print(" ModifyPosition skipped - stops too close to market (min distance: ", DoubleToString(MathMax(GetMinStopDistance(), GetFreezeDistance()), Digits()), ")"); return; } if(!trade.PositionModify(ticket, newSL, newTP)) { Print(" ModifyPosition FAILED - Ticket: ", ticket, " | Error: ", trade.ResultRetcodeDescription(), " (Code: ", trade.ResultRetcode(), ")"); } } //+------------------------------------------------------------------+ // CHECK IF WE CAN TRADE (TIME & DAY RESTRICTIONS with Timezone Support) //+------------------------------------------------------------------+ bool CanTrade() { // Voice/queue pause gate static datetime lastPauseLog = 0; if(tradingPaused) { if(TimeCurrent() - lastPauseLog > 10) { Print("TRADING PAUSED (voice command)"); lastPauseLog = TimeCurrent(); } return false; } // Get current time - use broker server time or local time with offset datetime currentTime = UseBrokerTime ? TimeCurrent() : (TimeLocal() + (TimezoneOffset * 3600)); // Check current hour and day using MqlDateTime struct MqlDateTime dt; TimeToStruct(currentTime, dt); int hour = dt.hour; if(hour < StartHour || hour >= EndHour) return false; // Check day of week (0=Sunday, 1=Monday, ..., 6=Saturday) int dayOfWeek = dt.day_of_week; if((dayOfWeek == 0 && !TradeSunday) || (dayOfWeek == 1 && !TradeMonday) || (dayOfWeek == 2 && !TradeTuesday) || (dayOfWeek == 3 && !TradeWednesday) || (dayOfWeek == 4 && !TradeThursday) || (dayOfWeek == 5 && !TradeFriday) || (dayOfWeek == 6 && !TradeSaturday)) return false; if(!IsCurrentSessionTradable()) return false; return true; } //+------------------------------------------------------------------+ // CHECK IF IN COOLDOWN PERIOD (After Losing Trade) // Uses real time-based cooldown instead of bar counting //+------------------------------------------------------------------+ bool IsInCooldown() { if(!UseCooldown) return false; if(lastLossTime == 0) return false; // No loss recorded yet // Calculate time elapsed since last loss datetime currentTime = UseBrokerTime ? TimeCurrent() : (TimeLocal() + (TimezoneOffset * 3600)); int timeElapsed = (int)(currentTime - lastLossTime); // Convert CooldownBars to seconds (approximate: 1 bar = period in seconds) int periodSeconds = PeriodSeconds(Period()); int cooldownSeconds = CooldownBars * periodSeconds; if(timeElapsed < cooldownSeconds) { int barsLeft = (cooldownSeconds - timeElapsed) / periodSeconds; if(barsLeft > 0) { Print(" In Cooldown: ", barsLeft, " bars left (", (cooldownSeconds - timeElapsed), " seconds)"); return true; } } return false; } //+------------------------------------------------------------------+ // CHECK IF POSITION EXISTS //+------------------------------------------------------------------+ // Count open positions for this symbol and direction (POSITION_TYPE_BUY / POSITION_TYPE_SELL) // ONLY counts trades with OUR magic number // (definition moved earlier) //+------------------------------------------------------------------+ // PRICE HELPERS - normalize to tick size and enforce stop distances //+------------------------------------------------------------------+ double NormalizeToTick(double price) { return NormalizeToTickForSymbol(_Symbol, price); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double NormalizeToTickForSymbol(string symbol, double price) { double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); if(tickSize <= 0) tickSize = SymbolInfoDouble(symbol, SYMBOL_POINT); if(tickSize <= 0) tickSize = Point(); double normalized = MathRound(price / tickSize) * tickSize; return NormalizeDouble(normalized, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetMinStopDistance() { return GetMinStopDistanceForSymbol(_Symbol); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetMinStopDistanceForSymbol(string symbol) { int stopsLevel = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL); if(stopsLevel <= 0) return 0.0; double pt = SymbolInfoDouble(symbol, SYMBOL_POINT); if(pt <= 0) pt = Point(); return stopsLevel * pt; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetFreezeDistance() { return GetFreezeDistanceForSymbol(_Symbol); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetFreezeDistanceForSymbol(string symbol) { int freezeLevel = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_FREEZE_LEVEL); if(freezeLevel <= 0) return 0.0; double pt = SymbolInfoDouble(symbol, SYMBOL_POINT); if(pt <= 0) pt = Point(); return freezeLevel * pt; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsPendingPriceValid(double price, double current, bool isBuy) { double minDistance = GetMinStopDistance(); if(minDistance <= 0) return true; return isBuy ? ((current - price) >= minDistance) : ((price - current) >= minDistance); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsStopValidForModify(double stopPrice, double currentPrice, bool isBuy, bool isSL) { if(stopPrice <= 0) return true; double minDistance = MathMax(GetMinStopDistance(), GetFreezeDistance()); if(minDistance <= 0) return true; if(isBuy) return isSL ? (stopPrice <= currentPrice - minDistance) : (stopPrice >= currentPrice + minDistance); return isSL ? (stopPrice >= currentPrice + minDistance) : (stopPrice <= currentPrice - minDistance); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool ArePricesEqual(double a, double b) { double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE); if(tickSize <= 0) tickSize = Point(); return MathAbs(a - b) <= (tickSize / 2.0); } //+------------------------------------------------------------------+ // VOLUME AND LIMIT CHECKS //+------------------------------------------------------------------+ bool CheckVolumeValueForSymbol(string symbol, double volume, string &description) { double min_volume = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); if(volume < min_volume) { description = StringFormat("Volume is less than SYMBOL_VOLUME_MIN=%.2f", min_volume); return false; } double max_volume = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); if(volume > max_volume) { description = StringFormat("Volume is greater than SYMBOL_VOLUME_MAX=%.2f", max_volume); return false; } double volume_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); if(volume_step <= 0) { description = "Invalid SYMBOL_VOLUME_STEP"; return false; } int ratio = (int)MathRound(volume / volume_step); if(MathAbs(ratio * volume_step - volume) > 0.0000001) { description = StringFormat("Volume not multiple of SYMBOL_VOLUME_STEP=%.2f (closest %.2f)", volume_step, ratio * volume_step); return false; } description = "Correct volume value"; return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckVolumeValue(double volume, string &description) { return CheckVolumeValueForSymbol(Symbol(), volume, description); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double PositionVolumeOnSymbol(string symbol) { double volume_on_symbol = 0.0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == symbol) volume_on_symbol += PositionGetDouble(POSITION_VOLUME); } return volume_on_symbol; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double PendingsVolumeOnSymbol(string symbol) { double volume_on_symbol = 0.0; int all_orders = OrdersTotal(); for(int i = 0; i < all_orders; i++) { ulong ticket = OrderGetTicket(i); if(ticket > 0 && OrderGetString(ORDER_SYMBOL) == symbol) volume_on_symbol += OrderGetDouble(ORDER_VOLUME_INITIAL); } return volume_on_symbol; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double NewOrderAllowedVolume(string symbol) { double symbol_max_volume = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double max_volume = SymbolInfoDouble(symbol, SYMBOL_VOLUME_LIMIT); if(max_volume <= 0) return symbol_max_volume; double opened_volume = PositionVolumeOnSymbol(symbol); if(max_volume - opened_volume <= 0) return 0.0; double orders_volume_on_symbol = PendingsVolumeOnSymbol(symbol); double allowed_volume = max_volume - opened_volume - orders_volume_on_symbol; if(allowed_volume > symbol_max_volume) allowed_volume = symbol_max_volume; return allowed_volume; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsNewOrderAllowed() { int max_allowed_orders = (int)AccountInfoInteger(ACCOUNT_LIMIT_ORDERS); if(max_allowed_orders == 0) return true; int orders = OrdersTotal(); return (orders < max_allowed_orders); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckStopLossTakeProfit(ENUM_ORDER_TYPE type, double sl, double tp) { return CheckStopLossTakeProfitForSymbol(_Symbol, type, sl, tp); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool CheckStopLossTakeProfitForSymbol(string symbol, ENUM_ORDER_TYPE type, double sl, double tp) { int stops_level = (int)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL); if(stops_level <= 0) return true; double bid = SymbolInfoDouble(symbol, SYMBOL_BID); double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); double pt = SymbolInfoDouble(symbol, SYMBOL_POINT); if(pt <= 0) pt = _Point; double min_dist = stops_level * pt; if(type == ORDER_TYPE_BUY) { bool sl_ok = (sl <= 0) ? true : ((bid - sl) > min_dist); bool tp_ok = (tp <= 0) ? true : ((tp - bid) > min_dist); if(!sl_ok) Print(" StopLoss too close for BUY [", symbol, "]: SL=", sl, " Bid=", bid, " min=", min_dist); if(!tp_ok) Print(" TakeProfit too close for BUY [", symbol, "]: TP=", tp, " Bid=", bid, " min=", min_dist); return (sl_ok && tp_ok); } if(type == ORDER_TYPE_SELL) { bool sl_ok = (sl <= 0) ? true : ((sl - ask) > min_dist); bool tp_ok = (tp <= 0) ? true : ((ask - tp) > min_dist); if(!sl_ok) Print(" StopLoss too close for SELL [", symbol, "]: SL=", sl, " Ask=", ask, " min=", min_dist); if(!tp_ok) Print(" TakeProfit too close for SELL [", symbol, "]: TP=", tp, " Ask=", ask, " min=", min_dist); return (sl_ok && tp_ok); } return true; } //+------------------------------------------------------------------+ // FUNDS CHECK - ensure enough margin before sending orders //+------------------------------------------------------------------+ bool CheckMoneyForTrade(string symb, double lots, ENUM_ORDER_TYPE type) { //--- Getting the opening price MqlTick mqltick; if(!SymbolInfoTick(symb, mqltick)) { Print(" CheckMoneyForTrade: SymbolInfoTick failed for ", symb); return false; } double price = mqltick.ask; if(type == ORDER_TYPE_SELL) price = mqltick.bid; //--- values of the required and free margin double margin, free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); //--- call of the checking function if(!OrderCalcMargin(type, symb, lots, price, margin)) { Print(" CheckMoneyForTrade: OrderCalcMargin failed, code=", GetLastError()); return false; } //--- if there are insufficient funds to perform the operation if(margin > free_margin) { Print(" Not enough money for ", EnumToString(type), " ", lots, " ", symb, " (margin=", margin, " free=", free_margin, ")"); return false; } //--- checking successful return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double EnforceMinStopDistance(double entry, double target, bool isBuy, bool isSL) { return EnforceMinStopDistanceForSymbol(_Symbol, entry, target, isBuy, isSL); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double EnforceMinStopDistanceForSymbol(string symbol, double entry, double target, bool isBuy, bool isSL) { double symbolPoint = SymbolInfoDouble(symbol, SYMBOL_POINT); if(symbolPoint <= 0.0) symbolPoint = Point(); double minDistance = MathMax(GetMinStopDistanceForSymbol(symbol), GetFreezeDistanceForSymbol(symbol)) + symbolPoint; if(minDistance <= 0) return NormalizeToTickForSymbol(symbol, target); double dist = MathAbs(target - entry); if(dist >= minDistance) return NormalizeToTickForSymbol(symbol, target); if(isSL) return NormalizeToTickForSymbol(symbol, isBuy ? (entry - minDistance) : (entry + minDistance)); return NormalizeToTickForSymbol(symbol, isBuy ? (entry + minDistance) : (entry - minDistance)); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double ConvertInputDistanceToPriceForSymbol(string symbol, double inputDistance) { double symbolPoint = SymbolInfoDouble(symbol, SYMBOL_POINT); if(symbolPoint <= 0.0) symbolPoint = Point(); if(!InputsArePips) return inputDistance * symbolPoint; long digits = SymbolInfoInteger(symbol, SYMBOL_DIGITS); double pipToPointMultiplier = ((digits == 5 || digits == 3) ? 10.0 : 1.0); return inputDistance * symbolPoint * pipToPointMultiplier; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void AdjustEntryDistancesSmart(string symbol, int direction, int decisionScore, double bid, double ask, double baseTPDistance, double baseSLDistance, double &tpDistance, double &slDistance) { double symbolPoint = SymbolInfoDouble(symbol, SYMBOL_POINT); if(symbolPoint <= 0.0) symbolPoint = Point(); double spread = MathAbs(ask - bid); double stopLevelDistance = ((double)SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL)) * symbolPoint; double freezeLevelDistance = ((double)SymbolInfoInteger(symbol, SYMBOL_TRADE_FREEZE_LEVEL)) * symbolPoint; double brokerMinDistance = MathMax(stopLevelDistance, freezeLevelDistance); tpDistance = MathMax(baseTPDistance, 0.0); slDistance = MathMax(baseSLDistance, 0.0); double spreadGuardTP = spread * 2.5; double spreadGuardSL = spread * 2.0; tpDistance = MathMax(tpDistance, MathMax(spreadGuardTP, brokerMinDistance)); slDistance = MathMax(slDistance, MathMax(spreadGuardSL, brokerMinDistance)); double regimeTpFactor = 1.0; double regimeSlFactor = 1.0; if(marketState.currentRegime == "VOLATILE") { regimeTpFactor = 1.25; regimeSlFactor = 1.30; } else if(marketState.currentRegime == "QUIET") { regimeTpFactor = 0.95; regimeSlFactor = 0.95; } if(marketState.marketStress) { regimeTpFactor = MathMin(regimeTpFactor, 1.0); regimeSlFactor = MathMax(regimeSlFactor, 1.10); } tpDistance *= regimeTpFactor; slDistance *= regimeSlFactor; double targetRR = 1.2; if(decisionScore >= LongTermScoreThreshold) targetRR = 1.5; else if(decisionScore <= ShortTermScoreThreshold) targetRR = 1.0; if(marketState.marketStress) targetRR = MathMin(targetRR, 1.0); if(slDistance > 0.0) tpDistance = MathMax(tpDistance, slDistance * targetRR); double maxRR = 2.4; if(slDistance > 0.0 && tpDistance > slDistance * maxRR) tpDistance = slDistance * maxRR; tpDistance = MathMax(tpDistance, MathMax(spreadGuardTP, brokerMinDistance)); slDistance = MathMax(slDistance, MathMax(spreadGuardSL, brokerMinDistance)); } //+------------------------------------------------------------------+ // OPEN BUY TRADE //+------------------------------------------------------------------+ void OpenBuyTrade(double ask) { if(IsSymbolAutoCloseBlocked(Symbol(), GetCurrentTimeWithOffset())) { Print("AUTO CLOSE GATE [", Symbol(), "] - BUY blocked until next session window."); return; } if(CountOpenTradesAllSymbols() >= currentMaxOpenTrades) return; double lots = CalculateLotSize(); if(!CheckMoneyForTrade(Symbol(), lots, ORDER_TYPE_BUY)) { EmitOrderBlockedSound(); return; } string volumeDesc = ""; if(!CheckVolumeValue(lots, volumeDesc)) { Print(" Volume check failed: ", volumeDesc); EmitOrderBlockedSound(); return; } double allowedVolume = NewOrderAllowedVolume(Symbol()); if(allowedVolume <= 0 || lots > allowedVolume) { Print(" Volume limit exceeded: lots=", lots, " allowed=", allowedVolume); EmitOrderBlockedSound(); return; } double entryPrice = ask; double bidNow = SymbolInfoDouble(Symbol(), SYMBOL_BID); double tpMultiplier = 1.0; double slMultiplier = 1.0; string entryProfile = "NORMAL"; int entryScore = 5; SelectEntryProfile(1, bidNow, ask, tpMultiplier, slMultiplier, entryProfile, entryScore); // Apply Smart Entry - adjust entry price towards liquidity zones double liqDistPts = 0.0; bool liqUsed = false; if(UseSmartEntry) { entryPrice = FindNearestLiquidityZone(ask, POSITION_TYPE_BUY, liqDistPts); liqUsed = (entryPrice != ask); } entryPrice = NormalizeToTick(entryPrice); double baseTPDistance = ConvertInputDistanceToPriceForSymbol(Symbol(), TakeProfit) * tpMultiplier; double baseSLDistance = ConvertInputDistanceToPriceForSymbol(Symbol(), StopLoss) * slMultiplier; double tpDistance = baseTPDistance; double slDistance = baseSLDistance; if(UseEntryProfileTP_SL) AdjustEntryDistancesSmart(Symbol(), 1, entryScore, bidNow, ask, baseTPDistance, baseSLDistance, tpDistance, slDistance); double tp = EnforceMinStopDistance(entryPrice, entryPrice + tpDistance, true, false); double sl = EnforceMinStopDistance(entryPrice, entryPrice - slDistance, true, true); if(!CheckStopLossTakeProfit(ORDER_TYPE_BUY, sl, tp)) return; Print(" ENTRY PROFILE [BUY] - ", entryProfile, " | Score: ", entryScore, "/10", " | Regime: ", marketState.currentRegime, " | TPx: ", DoubleToString(tpMultiplier, 2), " | SLx: ", DoubleToString(slMultiplier, 2)); // Execute BUY order with slippage control bool orderPlaced = false; if(UseSmartEntry && UseLimitOrders && entryPrice < ask) { // Use LIMIT order when smart entry found a better price below current ask if(!IsNewOrderAllowed()) { Print(" BUY LIMIT skipped - pending order limit reached"); } else if(!IsPendingPriceValid(entryPrice, ask, true)) { Print(" BUY LIMIT skipped - price too close to market (min distance: ", DoubleToString(GetMinStopDistance(), Digits()), ")"); } else { if(EnableVoicePrompts && liqUsed) SpeakZira("I find a liquidity zone on symbol " + Symbol() + ". I will place a buy limit order."); orderPlaced = trade.BuyLimit(lots, entryPrice, Symbol(), sl, tp, ORDER_TIME_GTC, 0, "King Gold Scalper BUY (Limit)"); } if(orderPlaced) Print(" BUY LIMIT Order PLACED - Ticket: ", trade.ResultOrder(), " | Lots: ", lots, " | Limit Price: ", entryPrice, " | Current Ask: ", ask, " | TP: ", tp, " | SL: ", sl); } else { // Use MARKET order (original behavior or when limit not applicable) orderPlaced = trade.Buy(lots, Symbol(), entryPrice, sl, tp, "King Gold Scalper BUY"); if(orderPlaced) Print(" BUY Order EXECUTED - Ticket: ", trade.ResultOrder(), " | Lots: ", lots, " | Entry: ", entryPrice, " | TP: ", tp, " | SL: ", sl); } // Verify order success if(!orderPlaced) { Print(" BUY Order FAILED - Error: ", trade.ResultRetcodeDescription(), " (Code: ", trade.ResultRetcode(), ")"); EmitOrderBlockedSound(); } else { Print(" BUY order accepted - trade context will be captured on DEAL_ENTRY_IN"); EmitOrderExecutedSound(1); if(TelegramSendEntries) { string msg = BuildTelegramEntryMessage(Symbol(), "BUY", entryPrice, sl, tp, entryScore, marketState.currentRegime); SendTelegramMessage(msg, lastTelegramEntryTime, TelegramCooldownSeconds); } } } //+------------------------------------------------------------------+ // OPEN SELL TRADE //+------------------------------------------------------------------+ void OpenSellTrade(double bid) { if(IsSymbolAutoCloseBlocked(Symbol(), GetCurrentTimeWithOffset())) { Print("AUTO CLOSE GATE [", Symbol(), "] - SELL blocked until next session window."); return; } if(CountOpenTradesAllSymbols() >= currentMaxOpenTrades) return; double lots = CalculateLotSize(); if(!CheckMoneyForTrade(Symbol(), lots, ORDER_TYPE_SELL)) { EmitOrderBlockedSound(); return; } string volumeDesc = ""; if(!CheckVolumeValue(lots, volumeDesc)) { Print(" Volume check failed: ", volumeDesc); EmitOrderBlockedSound(); return; } double allowedVolume = NewOrderAllowedVolume(Symbol()); if(allowedVolume <= 0 || lots > allowedVolume) { Print(" Volume limit exceeded: lots=", lots, " allowed=", allowedVolume); EmitOrderBlockedSound(); return; } double entryPrice = bid; double askNow = SymbolInfoDouble(Symbol(), SYMBOL_ASK); double tpMultiplier = 1.0; double slMultiplier = 1.0; string entryProfile = "NORMAL"; int entryScore = 5; SelectEntryProfile(-1, bid, askNow, tpMultiplier, slMultiplier, entryProfile, entryScore); // Apply Smart Entry - adjust entry price towards liquidity zones double liqDistPts = 0.0; bool liqUsed = false; if(UseSmartEntry) { entryPrice = FindNearestLiquidityZone(bid, POSITION_TYPE_SELL, liqDistPts); liqUsed = (entryPrice != bid); } entryPrice = NormalizeToTick(entryPrice); double baseTPDistance = ConvertInputDistanceToPriceForSymbol(Symbol(), TakeProfit) * tpMultiplier; double baseSLDistance = ConvertInputDistanceToPriceForSymbol(Symbol(), StopLoss) * slMultiplier; double tpDistance = baseTPDistance; double slDistance = baseSLDistance; if(UseEntryProfileTP_SL) AdjustEntryDistancesSmart(Symbol(), -1, entryScore, bid, askNow, baseTPDistance, baseSLDistance, tpDistance, slDistance); double tp = EnforceMinStopDistance(entryPrice, entryPrice - tpDistance, false, false); double sl = EnforceMinStopDistance(entryPrice, entryPrice + slDistance, false, true); if(!CheckStopLossTakeProfit(ORDER_TYPE_SELL, sl, tp)) return; Print(" ENTRY PROFILE [SELL] - ", entryProfile, " | Score: ", entryScore, "/10", " | Regime: ", marketState.currentRegime, " | TPx: ", DoubleToString(tpMultiplier, 2), " | SLx: ", DoubleToString(slMultiplier, 2)); // Execute SELL order with slippage control bool orderPlaced = false; if(UseSmartEntry && UseLimitOrders && entryPrice > bid) { // Use LIMIT order when smart entry found a better price above current bid if(!IsNewOrderAllowed()) { Print(" SELL LIMIT skipped - pending order limit reached"); } else if(!IsPendingPriceValid(entryPrice, bid, false)) { Print(" SELL LIMIT skipped - price too close to market (min distance: ", DoubleToString(GetMinStopDistance(), Digits()), ")"); } else { if(EnableVoicePrompts && liqUsed) SpeakZira("I find a liquidity zone on symbol " + Symbol() + ". I will place a sell limit order."); orderPlaced = trade.SellLimit(lots, entryPrice, Symbol(), sl, tp, ORDER_TIME_GTC, 0, "King Gold Scalper SELL (Limit)"); } if(orderPlaced) Print(" SELL LIMIT Order PLACED - Ticket: ", trade.ResultOrder(), " | Lots: ", lots, " | Limit Price: ", entryPrice, " | Current Bid: ", bid, " | TP: ", tp, " | SL: ", sl); } else { // Use MARKET order (original behavior or when limit not applicable) orderPlaced = trade.Sell(lots, Symbol(), entryPrice, sl, tp, "King Gold Scalper SELL"); if(orderPlaced) Print(" SELL Order EXECUTED - Ticket: ", trade.ResultOrder(), " | Lots: ", lots, " | Entry: ", entryPrice, " | TP: ", tp, " | SL: ", sl); } // Verify order success if(!orderPlaced) { Print(" SELL Order FAILED - Error: ", trade.ResultRetcodeDescription(), " (Code: ", trade.ResultRetcode(), ")"); EmitOrderBlockedSound(); } else { Print(" SELL order accepted - trade context will be captured on DEAL_ENTRY_IN"); EmitOrderExecutedSound(-1); if(TelegramSendEntries) { string msg = BuildTelegramEntryMessage(Symbol(), "SELL", entryPrice, sl, tp, entryScore, marketState.currentRegime); SendTelegramMessage(msg, lastTelegramEntryTime, TelegramCooldownSeconds); } } } //+------------------------------------------------------------------+ // CALCULATE LOT SIZE BASED ON RISK PERCENTAGE OF ACCOUNT BALANCE //+------------------------------------------------------------------+ double CalculateLotSizeForSymbol(string symbol) { if(LotSize > 0) return LotSize; // Fixed lot size mode double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); // Determine which risk % to use (adaptive + default fallback) double baseRisk = GetConfiguredBaseRiskPercent(); double riskToUse = UseAdaptiveTrading ? currentRiskPercent : baseRisk; if(UseAdaptiveTrading) riskToUse *= memoryAdaptiveRiskMultiplier; // Safe mode risk cut if(safeModeActive) riskToUse *= SafeRiskFactor; // Recovery mode risk cut if(recoveryModeActive) riskToUse *= ClampValue(RecoveryRiskMultiplier, 0.1, 5.0); // Controlled explore: allow reduced-risk entries for learning if(nextTradeUseExploreLot) { static bool warnedExploreLot = false; double exploreMultiplier = ClampValue(ExploreLotMultiplier, 0.1, 1.5); if(ExploreLotMultiplier > 1.0 && !warnedExploreLot) { Print("RISK WARNING | ExploreLotMultiplier=", DoubleToString(ExploreLotMultiplier, 2), " > 1.00; forcing 1.2 max for EXPLORE_SMALL consistency"); warnedExploreLot = true; } riskToUse *= exploreMultiplier; } // Apply greed multiplier if(UseSignalStrengthGreed) { if(greedLevel == "GREEDY") riskToUse *= 1.5; else if(greedLevel == "CONSERVATIVE") riskToUse *= 0.75; } riskToUse = ClampValue(riskToUse, 0.01, 10.0); double riskMoney = accountBalance * (riskToUse / 100.0); if(accountBalance <= 0 || riskMoney <= 0) return SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); // Get symbol properties double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); if(tickSize <= 0.0) tickSize = SymbolInfoDouble(symbol, SYMBOL_POINT); int symbolDigits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); double symbolPoint = SymbolInfoDouble(symbol, SYMBOL_POINT); double symbolPipMultiplier = ((symbolDigits == 5 || symbolDigits == 3) ? 10.0 : 1.0); double pointDistance = InputsArePips ? (symbolPoint * symbolPipMultiplier) : symbolPoint; // Convert StopLoss to price distance in current symbol units double slPriceDistance = StopLoss * pointDistance; if(slPriceDistance <= 0.0 || tickValue <= 0.0) return SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); // Risk per 1 lot = (SL distance / tick size) * tick value double riskPerLot = (slPriceDistance / tickSize) * tickValue; if(riskPerLot <= 0) return SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); // Calculate lot size: risk money / risk per lot double lots = riskMoney / riskPerLot; // Get symbol volume constraints double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); // Safety check for invalid lotStep if(lotStep <= 0 || lotStep > 1.0) { double originalLotStep = lotStep; lotStep = 0.01; // Default fallback Print(" Invalid lotStep detected (", originalLotStep, ") - using default 0.01"); } double logStep = MathLog10(lotStep); if(MathAbs(logStep - MathRound(logStep)) > 0.0001) Print(" lotStep is not a power of 10 (", lotStep, ") - may cause normalization issues"); // Normalize volume to meet symbol requirements lots = MathMax(lots, minLot); double effectiveMaxLot = MathMax(minLot, maxLot); lots = MathMin(lots, effectiveMaxLot); lots = NormalizeDouble(lots, (int)MathLog10(1.0 / lotStep)); if(lots > effectiveMaxLot) lots = effectiveMaxLot; lots = MathMax(lots, minLot); Print("LOT CALCULATION [", symbol, "] - Balance: $", accountBalance, " | Risk: ", riskToUse, "% = $", riskMoney, " | Lot Size: ", lots, " | MaxLot: ", DoubleToString(effectiveMaxLot, 2), " | Greed: ", greedLevel, " | Mode: ", performanceMode, " | MemoryRisk: ", DoubleToString(memoryAdaptiveRiskMultiplier, 2), "x"); return lots; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double CalculateLotSize() { return CalculateLotSizeForSymbol(Symbol()); } //+------------------------------------------------------------------+ // GET AVERAGE ENTRY PRICE FOR A DIRECTION //+------------------------------------------------------------------+ double GetAverageEntryPrice(int pos_type) { double totalPrice = 0; int count = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0) { if(PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_TYPE) == pos_type && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { totalPrice += PositionGetDouble(POSITION_PRICE_OPEN); count++; } } } if(count <= 0) return 0; return totalPrice / count; } //+------------------------------------------------------------------+ // COUNT OPEN TRADES - Only count trades with OUR magic number //+------------------------------------------------------------------+ int CountOpenTrades() { int count = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { if(PositionGetTicket(i) > 0) { // Only count if symbol matches AND magic number matches if(PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { count++; } } } return count; } //+------------------------------------------------------------------+ // COUNT OPEN TRADES (ALL SYMBOLS) - ONLY OUR MAGIC NUMBER //+------------------------------------------------------------------+ int CountOpenTradesAllSymbols() { int count = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(!PositionSelectByTicket(ticket)) continue; if(PositionGetInteger(POSITION_MAGIC) == MagicNumber) count++; } return count; } //+------------------------------------------------------------------+ // CALCULATE TOTAL PROFIT/LOSS (ALL POSITIONS) - ONLY OUR MAGIC NUMBER //+------------------------------------------------------------------+ double CalculateTotalProfit() { double totalProfit = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { totalProfit += PositionGetDouble(POSITION_PROFIT); } } return totalProfit; } //+------------------------------------------------------------------+ // CALCULATE PROFIT/LOSS FOR SPECIFIC DIRECTION (BUY or SELL) - ONLY OUR MAGIC NUMBER //+------------------------------------------------------------------+ double CalculateDirectionProfit(int posType) { double profit = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0 && PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { if(PositionGetInteger(POSITION_TYPE) == posType) { profit += PositionGetDouble(POSITION_PROFIT); } } } return profit; } //+------------------------------------------------------------------+ // CLOSE ALL TRADES - Safer position access with ticket verification //+------------------------------------------------------------------+ void CloseAllTrades() { for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket > 0) { // Safely select and verify position before closing if(!PositionSelectByTicket(ticket)) continue; // Only close if it's our symbol and magic number if(PositionGetString(POSITION_SYMBOL) == Symbol() && PositionGetInteger(POSITION_MAGIC) == MagicNumber) { trade.PositionClose(ticket); } } } } //+------------------------------------------------------------------+ // DEINITIALIZATION //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ // TRADE TRANSACTION HANDLER - Track winning/losing trades for cooldown //+------------------------------------------------------------------+ void OnTradeTransaction(const MqlTradeTransaction& trans, const MqlTradeRequest& request, const MqlTradeResult& result) { // Only process deal transactions if(trans.type != TRADE_TRANSACTION_DEAL_ADD) return; // Get deal ticket ulong dealTicket = trans.deal; if(dealTicket == 0) return; // Check if this is our deal if(!HistoryDealSelect(dealTicket)) return; // Verify it's our magic number (multi-symbol aware) string dealSymbol = HistoryDealGetString(dealTicket, DEAL_SYMBOL); if(HistoryDealGetInteger(dealTicket, DEAL_MAGIC) != MagicNumber) return; // Deal entry type ENUM_DEAL_ENTRY dealEntry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(dealTicket, DEAL_ENTRY); // Handle position entry (capture trade context for later real outcome learning) ulong positionId = (ulong)HistoryDealGetInteger(dealTicket, DEAL_POSITION_ID); if(dealEntry == DEAL_ENTRY_IN) { ENUM_DEAL_TYPE dealTypeIn = (ENUM_DEAL_TYPE)HistoryDealGetInteger(dealTicket, DEAL_TYPE); int direction = 0; if(dealTypeIn == DEAL_TYPE_BUY) direction = 1; else if(dealTypeIn == DEAL_TYPE_SELL) direction = -1; if(direction != 0) { datetime entryTime = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME); double entryPrice = HistoryDealGetDouble(dealTicket, DEAL_PRICE); double volumeRatio = GetCurrentVolumeRatioForSymbol(dealSymbol, (ENUM_TIMEFRAMES)Period()); double symbolPoint = SymbolInfoDouble(dealSymbol, SYMBOL_POINT); int symbolDigits = (int)SymbolInfoInteger(dealSymbol, SYMBOL_DIGITS); double spreadPointValue = InputsArePips ? ((((symbolDigits == 5 || symbolDigits == 3) ? 10.0 : 1.0) * symbolPoint)) : symbolPoint; double spread = (spreadPointValue > 0.0) ? ((SymbolInfoDouble(dealSymbol, SYMBOL_ASK) - SymbolInfoDouble(dealSymbol, SYMBOL_BID)) / spreadPointValue) : 0.0; int barsAtEntry = (direction == 1) ? GetConsecutiveBarsMultiTFForSymbol(dealSymbol, false) : GetConsecutiveBarsMultiTFForSymbol(dealSymbol, true); double bidNow = SymbolInfoDouble(dealSymbol, SYMBOL_BID); double askNow = SymbolInfoDouble(dealSymbol, SYMBOL_ASK); string scoreDetails = ""; int decisionScore = GetDecisionScore10ForSymbol(dealSymbol, direction, barsAtEntry, volumeRatio, bidNow, askNow, scoreDetails); int barsBucket = GetBarsBucket(barsAtEntry); int volumeBucket = GetVolumeBucket(volumeRatio); int entryTf = DetectEntryTimeframeForSymbol(dealSymbol, direction); string regimeAtEntry = marketState.currentRegime; // Generate sequence-based dynamic pattern for learning (last 3 closed candles) string seqHash = GeneratePatternSequenceHash(dealSymbol, entryTf, 1, 3); int seqType = InferSequencePatternType(dealSymbol, entryTf, 1, 3); string seqDesc = BuildSequenceDescription(dealSymbol, entryTf, 1, 3); int patternIdx = FindOrCreateDynamicPatternMeta(seqHash, seqType, entryTf, seqDesc); int decisionSetupIdAtEntry = 0; int decisionActionAtEntry = ConsumePendingDecisionActionForSymbol(dealSymbol, decisionSetupIdAtEntry); int entryTypeAtEntry = 0; ulong sourceOrderTicket = (ulong)HistoryDealGetInteger(dealTicket, DEAL_ORDER); int sourceOrderTypeAtEntry = -1; bool sourceOrderDetectedFromActivePool = false; if(sourceOrderTicket > 0 && HistoryOrderSelect(sourceOrderTicket)) { sourceOrderTypeAtEntry = (int)HistoryOrderGetInteger(sourceOrderTicket, ORDER_TYPE); if(IsSmartPendingOrderType(sourceOrderTypeAtEntry)) entryTypeAtEntry = 1; } else if(sourceOrderTicket > 0 && OrderSelect(sourceOrderTicket)) { sourceOrderDetectedFromActivePool = true; sourceOrderTypeAtEntry = (int)OrderGetInteger(ORDER_TYPE); if(IsSmartPendingOrderType(sourceOrderTypeAtEntry)) entryTypeAtEntry = 1; } if(LogDecisionLearningDetails && sourceOrderDetectedFromActivePool) Print("LEARN LINK [ENTRY_SOURCE] | history_missing=1 | used_orders_pool=1 | orderTicket=", (long)sourceOrderTicket, " | orderType=", sourceOrderTypeAtEntry, " | entryTypeDetected=", entryTypeAtEntry); double liqDistPts = 0.0; double liqLevel = FindNearestLiquidityZone(entryPrice, (direction == 1) ? POSITION_TYPE_BUY : POSITION_TYPE_SELL, liqDistPts); bool liqUsed = (UseSmartEntry && MathAbs(liqLevel - entryPrice) > SymbolInfoDouble(dealSymbol, SYMBOL_POINT) * 0.1); SavePositionEntryContext(positionId, direction, entryPrice, barsAtEntry, volumeRatio, spread, entryTime, decisionScore, decisionActionAtEntry, decisionSetupIdAtEntry, entryTypeAtEntry, sourceOrderTicket, sourceOrderTypeAtEntry, barsBucket, volumeBucket, entryTf, regimeAtEntry, seqHash, liqLevel, liqDistPts, liqUsed); } return; } // Check if it's a position close (not open) if(dealEntry != DEAL_ENTRY_OUT) return; // Only process position closes // Get deal profit double dealProfit = HistoryDealGetDouble(dealTicket, DEAL_PROFIT); double dealSwap = HistoryDealGetDouble(dealTicket, DEAL_SWAP); double dealCommission = HistoryDealGetDouble(dealTicket, DEAL_COMMISSION); double totalProfit = dealProfit + dealSwap + dealCommission; int entryIndex = FindPositionEntryContextIndex(positionId); bool hasEntryContext = (entryIndex != -1); if(hasEntryContext) { positionEntryMemory[entryIndex].accumulatedProfit += totalProfit; positionEntryMemory[entryIndex].closeDealCount++; } bool positionStillOpen = (positionId > 0 && IsPositionIdentifierStillOpen(positionId)); // Learn once per position at final close to avoid duplicate memory entries on partial closes. if(!positionStillOpen) { PositionEntryContext entryContext; int direction = 0; datetime entryTime = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME); double entryPrice = HistoryDealGetDouble(dealTicket, DEAL_PRICE); int barsAtEntry = 0; double volumeRatioAtEntry = 1.0; double symbolPoint = SymbolInfoDouble(dealSymbol, SYMBOL_POINT); double spreadAtEntry = (symbolPoint > 0.0) ? ((SymbolInfoDouble(dealSymbol, SYMBOL_ASK) - SymbolInfoDouble(dealSymbol, SYMBOL_BID)) / symbolPoint) : 0.0; double finalizedProfit = totalProfit; if(hasEntryContext) { entryContext = positionEntryMemory[entryIndex]; direction = entryContext.direction; entryTime = entryContext.entryTime; entryPrice = entryContext.entryPrice; barsAtEntry = entryContext.consecutiveBars; volumeRatioAtEntry = entryContext.volumeRatio; spreadAtEntry = entryContext.spreadAtEntry; finalizedProfit = entryContext.accumulatedProfit; } else { int tmpDir = 0; if(GetPositionEntryFromHistory(positionId, dealSymbol, MagicNumber, entryTime, entryPrice, tmpDir)) direction = tmpDir; ENUM_DEAL_TYPE closeType = (ENUM_DEAL_TYPE)HistoryDealGetInteger(dealTicket, DEAL_TYPE); if(direction == 0) { if(closeType == DEAL_TYPE_SELL) direction = 1; // closing a BUY else if(closeType == DEAL_TYPE_BUY) direction = -1; // closing a SELL } } if(direction != 0) { datetime exitTime = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME); double exitPrice = HistoryDealGetDouble(dealTicket, DEAL_PRICE); string exitReason = DealReasonToText(HistoryDealGetInteger(dealTicket, DEAL_REASON)); bool autoClosePositiveContext = IsAutoCloseLearningPositiveContext(dealSymbol, exitTime); double learningProfit = finalizedProfit; if(autoClosePositiveContext && learningProfit < 0.0) { learningProfit = 0.0; Print("AUTO-CLOSE LEARNING ADJUST [", dealSymbol, "] | position loss ignored in positive basket close | raw=", DoubleToString(finalizedProfit, 2)); } RecordTradeToMemory(direction, entryPrice, exitPrice, learningProfit, barsAtEntry, volumeRatioAtEntry, spreadAtEntry, entryTime, exitTime, exitReason, dealSymbol); EmitCloseResultSound(finalizedProfit); // Update per-symbol enhanced memory from real outcomes int symIndex = getSymbolIndex(dealSymbol); if(symIndex >= 0) { enhancedSymbolMemory[symIndex].totalTrades++; if(learningProfit > 0.0) enhancedSymbolMemory[symIndex].winningTrades++; enhancedSymbolMemory[symIndex].totalProfit += learningProfit; enhancedSymbolMemory[symIndex].winRate = (enhancedSymbolMemory[symIndex].totalTrades > 0) ? ((double)enhancedSymbolMemory[symIndex].winningTrades / (double)enhancedSymbolMemory[symIndex].totalTrades) : 0.0; int tfUsed = hasEntryContext ? entryContext.entryTimeframe : (int)Period(); if(getTimeframeIndex(tfUsed) < 0) tfUsed = (int)Period(); int tfIdx = getTimeframeIndex(tfUsed); if(tfIdx >= 0) { int prevTrades = enhancedSymbolMemory[symIndex].tfTrades[tfIdx]; double prevAvg = enhancedSymbolMemory[symIndex].tfPerformance[tfIdx]; int nextTrades = prevTrades + 1; enhancedSymbolMemory[symIndex].tfTrades[tfIdx] = nextTrades; enhancedSymbolMemory[symIndex].tfPerformance[tfIdx] = ((prevAvg * (double)prevTrades) + learningProfit) / (double)nextTrades; } } // Update dynamic pattern learning with outcome if(hasEntryContext && entryContext.patternHash != "") { int tfUsed = hasEntryContext ? entryContext.entryTimeframe : (int)Period(); if(getTimeframeIndex(tfUsed) < 0) tfUsed = (int)Period(); UpdateDynamicPattern(entryContext.patternHash, learningProfit, tfUsed); } int barsBucket = hasEntryContext ? entryContext.barsBucket : GetBarsBucket(barsAtEntry); int volumeBucket = hasEntryContext ? entryContext.volumeBucket : GetVolumeBucket(volumeRatioAtEntry); string regimeAtEntry = hasEntryContext ? entryContext.regimeAtEntry : marketState.currentRegime; int entryTypeAtEntry = hasEntryContext ? entryContext.entryTypeAtEntry : 0; double smartLimitOutcomeBonus = 0.0; if(UseSmartLimitRewardBoost && entryTypeAtEntry == 1) { if(learningProfit > 0.0) smartLimitOutcomeBonus = MathAbs(SmartLimitRewardWinBonus); else if(learningProfit < 0.0) smartLimitOutcomeBonus = -MathAbs(SmartLimitRewardLossPenalty); } UpdateSetupBucketReward(direction, barsBucket, volumeBucket, regimeAtEntry, learningProfit, entryTypeAtEntry); ApplyRewardPenaltyToPattern(dealSymbol, direction, barsAtEntry, learningProfit); if(UseDecisionCoreDll && g_decisionDllReady) { DC_TradeOutcome outcome; int learnTf = hasEntryContext ? entryContext.entryTimeframe : (int)Period(); if(getTimeframeIndex(learnTf) < 0) learnTf = (int)Period(); int setupIdForLearn = hasEntryContext ? entryContext.decisionSetupIdAtEntry : 0; int actionForLearn = hasEntryContext ? entryContext.decisionActionAtEntry : 1; int wasWinForLearn = (learningProfit > 0.0) ? 1 : 0; double drawdownAfterForLearn = GetAccountDrawdownPercentAuto(); int entryTypeFromContext = hasEntryContext ? entryContext.entryTypeAtEntry : -1; if(LogDecisionLearningDetails) { Print("LEARN LINK [ENTRY_TYPE] | posTicket=", (long)positionId, " | dealTicket=", (long)dealTicket, " | orderTicket=", (long)(hasEntryContext ? entryContext.sourceOrderTicketAtEntry : 0), " | orderType=", (hasEntryContext ? entryContext.sourceOrderTypeAtEntry : -1), " | entryTypeFromContext=", entryTypeFromContext, " | entryTypeUsed=", entryTypeAtEntry); Print("LEARN LINK [OUTCOME] | setup_id=", setupIdForLearn, " | action_taken=", actionForLearn, " | profit=", DoubleToString(learningProfit, 2), " | was_win=", wasWinForLearn, " | drawdown_after=", DoubleToString(drawdownAfterForLearn, 2), " | entry_type=", entryTypeAtEntry); if(setupIdForLearn <= 0) Print("LEARN LINK WARNING | setup_id is 0 (fallback matching may be weaker) | symbol=", dealSymbol); } DC_FillTradeOutcomeSafe(outcome, dealSymbol, learnTf, direction, actionForLearn, learningProfit, drawdownAfterForLearn, GetSetupRewardScore(direction, hasEntryContext ? entryContext.barsBucket : GetBarsBucket(barsAtEntry), hasEntryContext ? entryContext.volumeBucket : GetVolumeBucket(volumeRatioAtEntry), hasEntryContext ? entryContext.regimeAtEntry : marketState.currentRegime, entryTypeAtEntry) + smartLimitOutcomeBonus, wasWinForLearn, setupIdForLearn, entryTypeAtEntry); int learnRc = dc_learn(outcome); if(learnRc <= 0) Print("DECISION DLL | learn failed rc=", learnRc, " | symbol=", dealSymbol); else if(LogDecisionLearningDetails) Print("DECISION DLL | learn rc=", learnRc, " | symbol=", dealSymbol, " | setup_id=", setupIdForLearn, " | action=", actionForLearn, " | entry_type=", entryTypeAtEntry); } // Update memory streak state only once per fully closed position if(learningProfit > 0.0) { marketState.consecutiveWins++; marketState.consecutiveLosses = 0; marketState.recentPerformance = (marketState.recentPerformance * 0.90) + 0.10; } else if(learningProfit < 0.0) { marketState.consecutiveLosses++; marketState.consecutiveWins = 0; marketState.recentPerformance = (marketState.recentPerformance * 0.90) - 0.10; } else { marketState.consecutiveWins = 0; marketState.consecutiveLosses = 0; marketState.recentPerformance *= 0.90; } } if(positionId > 0 && hasEntryContext) RemovePositionEntryContext(positionId); } // Check if it's a loss datetime closeDealTime = (datetime)HistoryDealGetInteger(dealTicket, DEAL_TIME); bool ignoreDealLossForCooldown = IsAutoCloseLearningPositiveContext(dealSymbol, closeDealTime); if(totalProfit < 0 && !ignoreDealLossForCooldown) { // Record loss time for cooldown datetime dealTime = closeDealTime; lastLossTime = dealTime; // Also update bar-based tracking for compatibility int currentBar = (int)iTime(Symbol(), Period(), 0); lastLossBar = currentBar; Print(" LOSS DETECTED - Profit: $", DoubleToString(totalProfit, 2), " | Cooldown activated for ", CooldownBars, " bars"); } else if(totalProfit > 0) { Print(" WIN DETECTED - Profit: $", DoubleToString(totalProfit, 2)); } else if(totalProfit < 0 && ignoreDealLossForCooldown) Print(" AUTO-CLOSE BASKET CONTEXT | Negative deal ignored for cooldown/loss streak: ", DoubleToString(totalProfit, 2)); if(!positionStillOpen && CountOpenTradesForSymbolMagic(dealSymbol) <= 0) { int acCtxIdx = FindAutoCloseLearningContextIndex(dealSymbol, false); if(acCtxIdx >= 0 && autoCloseLearningContexts[acCtxIdx].active && !autoCloseLearningContexts[acCtxIdx].learnSent) { if(UseDecisionCoreDll && g_decisionDllReady) { DC_BasketOutcome basketOutcome; DC_FillBasketOutcomeSafe(basketOutcome, dealSymbol, (int)Period(), 0, 2, // auto-close buckets are treated as controlled/recovery actions autoCloseLearningContexts[acCtxIdx].basketProfitAtTrigger, autoCloseLearningContexts[acCtxIdx].drawdownPctAtTrigger, GetAccountDrawdownPercentAuto(), autoCloseLearningContexts[acCtxIdx].tradeCountAtTrigger, autoCloseLearningContexts[acCtxIdx].recoveryModeAtTrigger ? 1 : 0, (autoCloseLearningContexts[acCtxIdx].basketProfitAtTrigger > 0.0) ? 1 : 0); int basketLearnRc = dc_learn_basket(basketOutcome); if(basketLearnRc <= 0) Print("DECISION DLL | basket learn failed rc=", basketLearnRc, " | symbol=", dealSymbol); else Print("DECISION DLL | basket learn ok | symbol=", dealSymbol, " | profit=", DoubleToString(basketOutcome.basket_profit, 2), " | dd_before=", DoubleToString(basketOutcome.basket_drawdown_before, 2), " | dd_after=", DoubleToString(basketOutcome.basket_drawdown_after, 2), " | trades=", basketOutcome.basket_trade_count, " | recovery=", basketOutcome.recovery_mode); } autoCloseLearningContexts[acCtxIdx].learnSent = true; } ClearAutoCloseLearningContext(dealSymbol); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsSmartPendingOrderType(long orderType) { return (orderType == ORDER_TYPE_BUY_LIMIT || orderType == ORDER_TYPE_SELL_LIMIT || orderType == ORDER_TYPE_BUY_STOP_LIMIT || orderType == ORDER_TYPE_SELL_STOP_LIMIT); } //+------------------------------------------------------------------+ // DEINITIALIZATION //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(g_micStarted) { MicStop(); g_micStarted = false; } // Persist state on unload if(UseFilePersistence) SaveStateToFile(); // Release Order Book if it was initialized if(domInitialized && domAvailable) { if(!MarketBookRelease(Symbol())) { Print(" Failed to release Order Book for ", Symbol()); } else { Print(" Order Book (DOM) released"); } } // Clear arrays and reset state ArrayResize(bookArray, 0); string deinitReason = "Unknown"; switch(reason) { case REASON_ACCOUNT: deinitReason = "Account changed"; break; case REASON_CHARTCHANGE: deinitReason = "Chart symbol changed"; break; case REASON_CHARTCLOSE: deinitReason = "Chart closed"; break; case REASON_PARAMETERS: deinitReason = "Parameters changed"; break; case REASON_RECOMPILE: deinitReason = "Recompiled"; break; case REASON_REMOVE: deinitReason = "EA removed"; break; case REASON_INITFAILED: deinitReason = "Init failed"; break; } Print("=== King Gold Scalper Stopped === (Reason: ", deinitReason, ")"); // Close voice DB if(g_cmdDb > 0) { sqlite_close(g_cmdDb); g_cmdDb = -1; } if(g_decisionDllReady) { dc_close(); g_decisionDllReady = false; } } //+------------------------------------------------------------------+ // UPDATE CONSECUTIVE BARS COUNTERS // Tracks consecutive bullish (green) and bearish (red) bars //+------------------------------------------------------------------+ void UpdateConsecutiveSignals() { if(!UseConsecutiveBars) return; if(UseTickBarSignals) { // In tick-bar mode, respect synthetic bar construction for streaks. UpdateConsecutiveBarsFromTicks(); return; } // In timeframe mode, use closed bars from chart timeframe for local streak gating. UpdateConsecutiveBarsFromTimeframe(); } //+------------------------------------------------------------------+ // AVERAGE TICK VOLUME HELPER //+------------------------------------------------------------------+ double GetAverageTickVolumeForSymbol(string symbol, ENUM_TIMEFRAMES tf, int lookback) { if(lookback <= 0) return 0.0; int bars = Bars(symbol, tf); if(bars <= lookback + 1) return 0.0; long sumVol = 0; int counted = 0; for(int i = 1; i <= lookback; i++) { long v = iTickVolume(symbol, tf, i); if(v <= 0) continue; sumVol += v; counted++; } if(counted == 0) return 0.0; return (double)sumVol / (double)counted; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ double GetAverageTickVolume(ENUM_TIMEFRAMES tf, int lookback) { return GetAverageTickVolumeForSymbol(Symbol(), tf, lookback); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void UpdateConsecutiveBarsFromTimeframe() { datetime currentBarTime = iTime(Symbol(), Period(), 1); // use closed bar (index 1) consistently if(currentBarTime == lastProcessedBarTime) return; double currentOpen = iOpen(Symbol(), Period(), 1); double currentClose = iClose(Symbol(), Period(), 1); bool isBullish = (currentClose > currentOpen); bool isBearish = (currentClose < currentOpen); // Optional: validate bar by tick volume if(UseVolumeInSignals) { long currVol = iTickVolume(Symbol(), Period(), 1); double avgVol = GetAverageTickVolume(Period(), VolumeSignalLookback); double volThreshold = VolumeSignalFactor * avgVol; if(avgVol > 0.0) { bool strongVol = (double)currVol >= volThreshold; if(!strongVol) { // Weak volume: still count bullish/bearish into streaks, then mark bar as processed if(isBullish) { consecutiveBullishBars++; consecutiveBearishBars = 0; } else if(isBearish) { consecutiveBearishBars++; consecutiveBullishBars = 0; } lastProcessedBarTime = currentBarTime; string barTypeWeak = (currentClose > currentOpen) ? "BULLISH" : ((currentClose < currentOpen) ? "BEARISH" : "DOJI"); Print(barTypeWeak, " (WEAK VOLUME) [O:", currentOpen, " C:", currentClose, "] Vol: ", currVol, " | Avg: ", avgVol, " | Threshold: ", volThreshold, " - Counted | Bullish: ", consecutiveBullishBars, "/", BullishBarsForSell, " | Bearish: ", consecutiveBearishBars, "/", BearishBarsForBuy); return; } } } if(isBullish) { consecutiveBullishBars++; consecutiveBearishBars = 0; } else if(isBearish) { consecutiveBearishBars++; consecutiveBullishBars = 0; } else // DOJI - neutral: keep current streaks unchanged { } lastProcessedBarTime = currentBarTime; string barType = (currentClose > currentOpen) ? "BULLISH" : ((currentClose < currentOpen) ? "BEARISH" : "DOJI"); if(UseVolumeInSignals) { long currVol = iTickVolume(Symbol(), Period(), 1); double avgVol = GetAverageTickVolume(Period(), VolumeSignalLookback); double volThreshold = VolumeSignalFactor * avgVol; Print(barType, " [O:", currentOpen, " C:", currentClose, "] Vol: ", currVol, " | Avg: ", avgVol, " | Threshold: ", volThreshold, " - Bullish: ", consecutiveBullishBars, "/", BullishBarsForSell, " | Bearish: ", consecutiveBearishBars, "/", BearishBarsForBuy); } else { Print(barType, " [O:", currentOpen, " C:", currentClose, "] - Bullish: ", consecutiveBullishBars, "/", BullishBarsForSell, " | Bearish: ", consecutiveBearishBars, "/", BearishBarsForBuy); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void UpdateConsecutiveBarsFromTicks() { if(TickBarSize <= 0) return; MqlTick tick; if(!SymbolInfoTick(Symbol(), tick)) return; double price = (tick.last > 0) ? tick.last : ((tick.bid > 0) ? tick.bid : tick.ask); if(price <= 0) return; if(tickBarCount == 0) { tickBarOpen = price; tickBarHigh = price; tickBarLow = price; } tickBarHigh = MathMax(tickBarHigh, price); tickBarLow = MathMin(tickBarLow, price); tickBarClose = price; tickBarCount++; if(tickBarCount < TickBarSize) return; // Completed a tick-bar -> optionally filter by volume if(UseVolumeInSignals) { long currVol = iTickVolume(Symbol(), Period(), 1); double avgVol = GetAverageTickVolume(Period(), VolumeSignalLookback); double volThreshold = VolumeSignalFactor * avgVol; if(avgVol > 0.0) { bool strongVol = (double)currVol >= volThreshold; if(!strongVol) { // Weak volume: still count bullish/bearish into streaks and continue if(tickBarClose > tickBarOpen) { consecutiveBullishBars++; consecutiveBearishBars = 0; } else if(tickBarClose < tickBarOpen) { consecutiveBearishBars++; consecutiveBullishBars = 0; } string barTypeWeak = (tickBarClose > tickBarOpen) ? "BULLISH" : ((tickBarClose < tickBarOpen) ? "BEARISH" : "DOJI"); Print(barTypeWeak, " [TickBar WEAK VOLUME O:", tickBarOpen, " C:", tickBarClose, " H:", tickBarHigh, " L:", tickBarLow, "] Vol: ", currVol, " | Avg: ", avgVol, " | Threshold: ", volThreshold, " - Counted | Bullish: ", consecutiveBullishBars, "/", BullishBarsForSell, " | Bearish: ", consecutiveBearishBars, "/", BearishBarsForBuy, " | Size: ", TickBarSize, " ticks"); tickBarIndex++; tickBarCount = 0; double nextOpenWeak = (tickBarClose > 0) ? tickBarClose : tickBarOpen; tickBarOpen = nextOpenWeak; tickBarHigh = nextOpenWeak; tickBarLow = nextOpenWeak; return; } } } if(tickBarClose > tickBarOpen) { consecutiveBullishBars++; consecutiveBearishBars = 0; } else if(tickBarClose < tickBarOpen) { consecutiveBearishBars++; consecutiveBullishBars = 0; } else // DOJI - neutral: keep current streaks unchanged { } string barType = (tickBarClose > tickBarOpen) ? "BULLISH" : ((tickBarClose < tickBarOpen) ? "BEARISH" : "DOJI"); if(UseVolumeInSignals) { long currVol = iTickVolume(Symbol(), Period(), 1); double avgVol = GetAverageTickVolume(Period(), VolumeSignalLookback); double volThreshold = VolumeSignalFactor * avgVol; Print(barType, " [TickBar O:", tickBarOpen, " C:", tickBarClose, " H:", tickBarHigh, " L:", tickBarLow, "] Vol: ", currVol, " | Avg: ", avgVol, " | Threshold: ", volThreshold, " - Bullish: ", consecutiveBullishBars, "/", BullishBarsForSell, " | Bearish: ", consecutiveBearishBars, "/", BearishBarsForBuy, " | Size: ", TickBarSize, " ticks"); } else { Print(barType, " [TickBar O:", tickBarOpen, " C:", tickBarClose, " H:", tickBarHigh, " L:", tickBarLow, "] - Bullish: ", consecutiveBullishBars, "/", BullishBarsForSell, " | Bearish: ", consecutiveBearishBars, "/", BearishBarsForBuy, " | Size: ", TickBarSize, " ticks"); } tickBarIndex++; tickBarCount = 0; double nextOpen = (tickBarClose > 0) ? tickBarClose : tickBarOpen; // avoid seeding with invalid/old close tickBarOpen = nextOpen; tickBarHigh = nextOpen; tickBarLow = nextOpen; } //+------------------------------------------------------------------+ // VOLUME FILTER - Blocks trades if volume is too low // Uses tick volume for compatibility with Strategy Tester // Returns true if volume is sufficient, false if too low //+------------------------------------------------------------------+ bool CheckVolumeFilter() { long currentVolume; long sumVolume = 0; // Use tick volume (works in tester) or real volume based on setting if(UseTickVolume) { // Get tick volume - works perfectly in Strategy Tester currentVolume = iTickVolume(Symbol(), Period(), 0); // Calculate average tick volume for last 200 bars for(int i = 1; i <= 200; i++) { sumVolume += iTickVolume(Symbol(), Period(), i); } } else { // Use real volume (if available from broker) currentVolume = iVolume(Symbol(), Period(), 0); if(currentVolume <= 0) Print("Real volume unavailable (0) - broker did not provide volume for current bar."); // Calculate average real volume for last 200 bars for(int i = 1; i <= 200; i++) { sumVolume += iVolume(Symbol(), Period(), i); } } long avgVolume = sumVolume / 200; // Calculate volume ratio double volumeRatio = 0.0; if(avgVolume > 0) volumeRatio = (double)currentVolume / avgVolume; else { // All sampled bars have zero volume; treat as insufficient data and block trade Print(" Volume check: all sampled bars have zero volume - blocking trade"); return false; } string volumeType = (UseTickVolume) ? "Tick" : "Real"; double requiredRatio = MinVolumeRatio; if(safeModeActive) requiredRatio *= SafeVolumeEntryMultiplier; // Check if volume meets minimum requirement if(volumeRatio >= requiredRatio) { Print(" VOLUME FILTER [", volumeType, "] - Current: ", currentVolume, " | Avg: ", avgVolume, " | Ratio: ", DoubleToString(volumeRatio, 2), "x (>= ", requiredRatio, ") - OK"); return true; // Volume is sufficient } else { Print(" VOLUME FILTER [", volumeType, "] - Current: ", currentVolume, " | Avg: ", avgVolume, " | Ratio: ", DoubleToString(volumeRatio, 2), "x (< ", requiredRatio, ") - BLOCKED"); return false; // Volume too low - block trade } } //+------------------------------------------------------------------+ // VOLUME MOMENTUM RATIO - used for limit order renewal // Returns volume ratio (current/avg). Returns -1 if unavailable. //+------------------------------------------------------------------+ double GetVolumeMomentumRatio() { long currentVolume = 0; long sumVolume = 0; if(UseTickVolume) { currentVolume = iTickVolume(Symbol(), Period(), 0); for(int i = 1; i <= 200; i++) sumVolume += iTickVolume(Symbol(), Period(), i); } else { currentVolume = iVolume(Symbol(), Period(), 0); if(currentVolume <= 0) Print("Real volume unavailable (0) - broker did not provide volume for current bar."); for(int i = 1; i <= 200; i++) sumVolume += iVolume(Symbol(), Period(), i); } long avgVolume = sumVolume / 200; if(avgVolume <= 0) return -1.0; return (double)currentVolume / avgVolume; } //+------------------------------------------------------------------+ // LIMIT ORDER RENEWAL - remove old pending limits when momentum weak //+------------------------------------------------------------------+ void RenewLimitOrdersByVolume() { if(!UseLimitOrderRenewal || !UseLimitOrders) return; datetime now = GetCurrentTimeWithOffset(); if(lastLimitRenewal != 0 && (now - lastLimitRenewal) < LimitOrderRenewalCooldownSeconds) return; double volumeRatio = GetVolumeMomentumRatio(); if(volumeRatio < 0) return; // Use same trigger as ANALYSIS 2: weak momentum => renew limits if(volumeRatio >= StrongMomentumThreshold) { lastLimitRenewal = now; return; } int deleted = 0; int totalOrders = OrdersTotal(); for(int i = totalOrders - 1; i >= 0; i--) { ulong ticket = OrderGetTicket(i); if(ticket == 0) continue; if(!OrderSelect(ticket)) continue; string symbol = OrderGetString(ORDER_SYMBOL); long magic = OrderGetInteger(ORDER_MAGIC); long type = OrderGetInteger(ORDER_TYPE); datetime setupTime = (datetime)OrderGetInteger(ORDER_TIME_SETUP); if(symbol != Symbol() || magic != MagicNumber) continue; if(type != ORDER_TYPE_BUY_LIMIT && type != ORDER_TYPE_SELL_LIMIT) continue; if(setupTime <= 0) continue; int ageMinutes = (int)((now - setupTime) / 60); if(ageMinutes < LimitOrderMaxAgeMinutes) continue; if(trade.OrderDelete(ticket)) deleted++; else Print(" LIMIT RENEWAL FAILED - Ticket: ", ticket, " | ", trade.ResultRetcodeDescription(), " (Code: ", trade.ResultRetcode(), ")"); } if(deleted > 0) Print(" LIMIT RENEWAL: Weak volume momentum (", DoubleToString(volumeRatio, 2), "x) - Deleted ", deleted, " old limit orders (>", LimitOrderMaxAgeMinutes, " min)"); lastLimitRenewal = now; } //+------------------------------------------------------------------+ // CHECK IF BROKER IS ECN (Supports Market Book / DOM) // Returns true if ECN broker detected (IC Markets, Pepperstone, etc.) //+------------------------------------------------------------------+ bool IsECNBroker() { // Get broker name string brokerName = AccountInfoString(ACCOUNT_COMPANY); StringToUpper(brokerName); // Convert to uppercase in place // List of known ECN brokers that support Market Book string ecnBrokers[] = { "IC MARKETS", "ICMARKETS", "INTERACTIVE BROKERS", "INTERACTIVEBROKERS", "IB", "PEPPERSTONE", "FXTM", "XM", "FXPRO", "FXCM", "OANDA", "DUKASCOPY", "SAXOBANK", "SAXO", "LMAX", "CURRENEX", "GAIN CAPITAL", "GAINCAPITAL", "FOREX.COM", "FOREXCOM", "ADMIRAL MARKETS", "ADMIRALMARKETS", "ADMIRAL", "EXNESS", "INSTAFOREX", "INSTA", "ROBOFOREX", "ROBO", "TICKMILL" }; // Check if broker name contains any ECN broker name for(int i = 0; i < ArraySize(ecnBrokers); i++) { if(StringFind(brokerName, ecnBrokers[i]) >= 0) { Print(" ECN Broker detected: ", AccountInfoString(ACCOUNT_COMPANY)); return true; } } // Alternative: Try to test if Market Book is available // This is a more reliable method - test if we can add Market Book if(MarketBookAdd(Symbol())) { // Successfully added - this is likely an ECN broker MarketBookRelease(Symbol()); // Release it immediately Print(" Market Book test successful - ECN broker detected: ", AccountInfoString(ACCOUNT_COMPANY)); return true; } else { // Failed to add - not an ECN broker or Market Book not available int error = GetLastError(); ResetLastError(); // Clear error for next operations return false; } } //+------------------------------------------------------------------+ // ORDER BOOK (DOM) EVENT HANDLER // Called when Order Book is updated //+------------------------------------------------------------------+ void OnBookEvent(const string& symbol) { if(!UseDOMFilter || ForceDisableDOM || !domFilterActive || !domInitialized || symbol != Symbol()) return; // Update Order Book data if(MarketBookGet(Symbol(), bookArray)) { lastBookUpdate = TimeCurrent(); // Order Book data is now in bookArray - ready for analysis } } //+------------------------------------------------------------------+ // CHECK DOM FILTER - Main function to validate trade using Order Book // Returns true if trade should proceed, false if blocked // If DOMAsSignal=true, always returns true (doesn't block), but still analyzes DOM //+------------------------------------------------------------------+ bool CheckDOMFilter(int positionType, double bid, double ask) { if(!UseDOMFilter || ForceDisableDOM || !domFilterActive || !domInitialized) return true; // If DOM not enabled, allow trade // Ensure Order Book data is fresh (max age 5 seconds), refresh if stale datetime now = TimeCurrent(); bool bookStale = (lastBookUpdate == 0 || (now - lastBookUpdate) > 5); if(bookStale) { Print("DOM: Order Book data stale (age: ", (lastBookUpdate == 0 ? -1 : (now - lastBookUpdate)), "s) - refreshing"); if(MarketBookGet(Symbol(), bookArray)) lastBookUpdate = now; else { Print(" DOM: Failed to refresh stale Order Book data - allowing trade"); return true; // fail-safe } } else { // Refresh to use latest snapshot even when not stale if(MarketBookGet(Symbol(), bookArray)) lastBookUpdate = now; else { Print(" DOM: Failed to get Order Book data - allowing trade"); return true; // If can't get data, allow trade (fail-safe) } } // Check minimum liquidity requirement double totalLiquidity = GetTotalLiquidity(); if(totalLiquidity < MinLiquidity) { if(DOMAsSignal) { Print(" DOM: Insufficient liquidity (", totalLiquidity, " < ", MinLiquidity, " lots) - Signal mode: allowing trade but weak signal"); LogDOMAnalysis(positionType, totalLiquidity, 0.0); return true; // In signal mode, don't block } else { Print(" DOM: Insufficient liquidity (", totalLiquidity, " < ", MinLiquidity, " lots) - BLOCKING trade"); return false; // In filter mode, block } } // Check imbalance for direction confirmation double imbalance = CalculateImbalance(); bool directionConfirmed = false; if(positionType == POSITION_TYPE_BUY) { // For BUY: We want BID volume > ASK volume (negative imbalance) // Imbalance < 0 means more BID volume (buyers), which is good for BUY if(imbalance < 0 && MathAbs(imbalance) >= MinImbalanceRatio) { directionConfirmed = true; Print("DOM: BUY confirmed - BID volume dominance (Imbalance: ", MathAbs(imbalance), "x)"); } else { if(DOMAsSignal) Print("DOM: BUY signal weak - ASK volume too high (Imbalance: ", MathAbs(imbalance), "x < ", MinImbalanceRatio, "x) - Signal mode: allowing trade"); else Print("DOM: BUY NOT confirmed - ASK volume too high (Imbalance: ", MathAbs(imbalance), "x < ", MinImbalanceRatio, "x)"); } } else if(positionType == POSITION_TYPE_SELL) { // For SELL: We want ASK volume > BID volume (positive imbalance) // Imbalance > 0 means more ASK volume (sellers), which is good for SELL if(imbalance > 0 && MathAbs(imbalance) >= MinImbalanceRatio) { directionConfirmed = true; Print("DOM: SELL confirmed - ASK volume dominance (Imbalance: ", imbalance, "x)"); } else { if(DOMAsSignal) Print("DOM: SELL signal weak - BID volume too high (Imbalance: ", imbalance, "x < ", MinImbalanceRatio, "x) - Signal mode: allowing trade"); else Print("DOM: SELL NOT confirmed - BID volume too high (Imbalance: ", imbalance, "x < ", MinImbalanceRatio, "x)"); } } // Log DOM analysis details LogDOMAnalysis(positionType, totalLiquidity, imbalance); // In signal mode, always allow trade (DOM provides signal, doesn't block) if(DOMAsSignal) return true; // In filter mode, block if direction not confirmed return directionConfirmed; } //+------------------------------------------------------------------+ // GET DOM SIGNAL STRENGTH - Returns signal strength (0.0 to 1.0) for position type // 1.0 = strong confirmation, 0.0 = no confirmation or weak signal //+------------------------------------------------------------------+ double GetDOMSignalStrength(int positionType) { if(!UseDOMFilter || ForceDisableDOM || !domFilterActive || !domInitialized) return 0.0; // No signal if DOM not available // Ensure Order Book data is fresh datetime now = TimeCurrent(); bool bookStale = (lastBookUpdate == 0 || (now - lastBookUpdate) > 5); if(bookStale) { if(MarketBookGet(Symbol(), bookArray)) lastBookUpdate = now; else return 0.0; // Can't get data } else { if(!MarketBookGet(Symbol(), bookArray)) return 0.0; // Can't get data } // Check liquidity double totalLiquidity = GetTotalLiquidity(); if(totalLiquidity < MinLiquidity) return 0.0; // Weak signal if liquidity too low // Calculate imbalance double imbalance = CalculateImbalance(); double signalStrength = 0.0; if(positionType == POSITION_TYPE_BUY) { // For BUY: negative imbalance (BID > ASK) is good if(imbalance < 0) { double absImbalance = MathAbs(imbalance); if(absImbalance >= MinImbalanceRatio) { // Strong signal: normalize to 0.0-1.0 range // If imbalance is exactly MinImbalanceRatio, signal = 0.5 // If imbalance is 2x MinImbalanceRatio or more, signal = 1.0 signalStrength = MathMin(1.0, 0.5 + (absImbalance - MinImbalanceRatio) / (MinImbalanceRatio * 2.0)); } else { // Weak signal: partial confirmation signalStrength = absImbalance / MinImbalanceRatio * 0.5; } } } else if(positionType == POSITION_TYPE_SELL) { // For SELL: positive imbalance (ASK > BID) is good if(imbalance > 0) { if(imbalance >= MinImbalanceRatio) { // Strong signal: normalize to 0.0-1.0 range signalStrength = MathMin(1.0, 0.5 + (imbalance - MinImbalanceRatio) / (MinImbalanceRatio * 2.0)); } else { // Weak signal: partial confirmation signalStrength = imbalance / MinImbalanceRatio * 0.5; } } } return signalStrength; } //+------------------------------------------------------------------+ // CALCULATE IMBALANCE - Ratio of BID/ASK volumes // Returns positive value if ASK > BID, negative if BID > ASK //+------------------------------------------------------------------+ double CalculateImbalance() { if(ArraySize(bookArray) == 0) return 0.0; double totalBidVolume = 0.0; double totalAskVolume = 0.0; int bidLevels = 0; int askLevels = 0; // Analyze top DOMLevels on each side for(int i = 0; i < ArraySize(bookArray) && (bidLevels < DOMLevels || askLevels < DOMLevels); i++) { if(bookArray[i].type == BOOK_TYPE_BUY && bidLevels < DOMLevels) { totalBidVolume += (double)bookArray[i].volume; bidLevels++; } else if(bookArray[i].type == BOOK_TYPE_SELL && askLevels < DOMLevels) { totalAskVolume += (double)bookArray[i].volume; askLevels++; } } if(totalBidVolume == 0 && totalAskVolume == 0) return 0.0; if(totalBidVolume == 0) return 999.0; // Only ASK volume (extreme imbalance) if(totalAskVolume == 0) return -999.0; // Only BID volume (extreme imbalance) // Return ratio: positive = ASK dominance, negative = BID dominance // Formula: (ASK - BID) / BID, or ASK/BID ratio double ratio = totalAskVolume / totalBidVolume; // Convert to signed imbalance: if ratio > 1, ASK > BID (positive), else negative if(ratio >= 1.0) return ratio; // ASK dominance else if(ratio > 0.001) // Safety check: avoid division by near-zero return -1.0 / ratio; // BID dominance (negative) else return -999.0; // Extreme imbalance protection } //+------------------------------------------------------------------+ // GET TOTAL LIQUIDITY - Sum of all volumes in Order Book //+------------------------------------------------------------------+ double GetTotalLiquidity() { if(ArraySize(bookArray) == 0) return 0.0; double totalLiquidity = 0.0; int bidLevels = 0; int askLevels = 0; // Sum volumes from top DOMLevels on each side for(int i = 0; i < ArraySize(bookArray) && (bidLevels < DOMLevels || askLevels < DOMLevels); i++) { if(bookArray[i].type == BOOK_TYPE_BUY && bidLevels < DOMLevels) { totalLiquidity += (double)bookArray[i].volume; bidLevels++; } else if(bookArray[i].type == BOOK_TYPE_SELL && askLevels < DOMLevels) { totalLiquidity += (double)bookArray[i].volume; askLevels++; } } return totalLiquidity; } //+------------------------------------------------------------------+ // FIND SUPPORT/RESISTANCE LEVELS - Identify zones with large volume accumulation // Returns array of price levels with significant volume //+------------------------------------------------------------------+ void FindSupportResistance(double &supportLevels[], double &resistanceLevels[], double currentPrice) { ArrayResize(supportLevels, 0); ArrayResize(resistanceLevels, 0); if(ArraySize(bookArray) == 0) return; // Calculate average volume to identify significant levels double avgVolume = 0.0; int count = 0; for(int i = 0; i < ArraySize(bookArray); i++) { avgVolume += (double)bookArray[i].volume; count++; } if(count > 0) avgVolume /= count; double thresholdVolume = avgVolume * 1.5; // Levels with 1.5x average volume // Find support levels (BID side, below current price) for(int i = 0; i < ArraySize(bookArray); i++) { if(bookArray[i].type == BOOK_TYPE_BUY && bookArray[i].price < currentPrice && (double)bookArray[i].volume >= thresholdVolume) { int size = ArraySize(supportLevels); ArrayResize(supportLevels, size + 1); supportLevels[size] = bookArray[i].price; } } // Find resistance levels (ASK side, above current price) for(int i = 0; i < ArraySize(bookArray); i++) { if(bookArray[i].type == BOOK_TYPE_SELL && bookArray[i].price > currentPrice && (double)bookArray[i].volume >= thresholdVolume) { int size = ArraySize(resistanceLevels); ArrayResize(resistanceLevels, size + 1); resistanceLevels[size] = bookArray[i].price; } } } //+------------------------------------------------------------------+ // VOLUME PROFILE - Identify price zones with high volume from historical bars // Returns array of price levels with significant volume accumulation //+------------------------------------------------------------------+ void CalculateVolumeProfile(double &liquidityZones[], int positionType, double currentPrice) { ArrayResize(liquidityZones, 0); if(!UseVolumeProfile) return; // Create price buckets (zones) and accumulate volume int numBars = MathMin(VolumeProfileBars, Bars(Symbol(), Period())); if(numBars < 10) return; // Need at least 10 bars for meaningful analysis double priceRange = 0; double minPrice = DBL_MAX; double maxPrice = 0; // Find price range from historical bars for(int i = 1; i <= numBars; i++) { double high = iHigh(Symbol(), Period(), i); double low = iLow(Symbol(), Period(), i); if(high > maxPrice) maxPrice = high; if(low < minPrice) minPrice = low; } priceRange = maxPrice - minPrice; if(priceRange <= 0) return; // Create price buckets (50 zones) int numBuckets = 50; double bucketSize = priceRange / numBuckets; double volumeBuckets[]; double priceBuckets[]; ArrayResize(volumeBuckets, numBuckets); ArrayResize(priceBuckets, numBuckets); ArrayInitialize(volumeBuckets, 0.0); // Initialize price buckets for(int i = 0; i < numBuckets; i++) { priceBuckets[i] = minPrice + (i * bucketSize) + (bucketSize / 2.0); } // Accumulate volume in each price bucket for(int i = 1; i <= numBars; i++) { double high = iHigh(Symbol(), Period(), i); double low = iLow(Symbol(), Period(), i); long volume = UseTickVolume ? iTickVolume(Symbol(), Period(), i) : iVolume(Symbol(), Period(), i); if(volume <= 0) continue; // Distribute volume across buckets that this bar covers for(int j = 0; j < numBuckets; j++) { double bucketPrice = priceBuckets[j]; if(bucketPrice >= low && bucketPrice <= high) { volumeBuckets[j] += (double)volume; // Explicit conversion from long to double } } } // Calculate average volume double totalVolume = 0; for(int i = 0; i < numBuckets; i++) { totalVolume += volumeBuckets[i]; } double avgVolume = (numBuckets > 0) ? (totalVolume / numBuckets) : 0; if(avgVolume <= 0) return; // Find zones with volume >= 1.5x average (significant liquidity zones) double thresholdVolume = avgVolume * 1.5; for(int i = 0; i < numBuckets; i++) { if(volumeBuckets[i] >= thresholdVolume) { int size = ArraySize(liquidityZones); ArrayResize(liquidityZones, size + 1); liquidityZones[size] = priceBuckets[i]; } } // Sort zones by distance from current price for(int i = 0; i < ArraySize(liquidityZones) - 1; i++) { for(int j = i + 1; j < ArraySize(liquidityZones); j++) { double distI = MathAbs(liquidityZones[i] - currentPrice); double distJ = MathAbs(liquidityZones[j] - currentPrice); if(distJ < distI) { double temp = liquidityZones[i]; liquidityZones[i] = liquidityZones[j]; liquidityZones[j] = temp; } } } } //+------------------------------------------------------------------+ // FIND NEAREST LIQUIDITY ZONE - Get best entry price based on liquidity // Returns adjusted entry price, or original price if no zone found //+------------------------------------------------------------------+ double FindNearestLiquidityZone(double originalPrice, int positionType, double &distancePoints) { if(!UseSmartEntry) return originalPrice; if(point_value <= 0.0) return originalPrice; double maxDistancePoints = GetLiquidityZoneDistancePointsForSymbol(Symbol()); if(maxDistancePoints <= 0.0) return originalPrice; double adjustedPrice = originalPrice; double bestZone = 0; double minDistance = DBL_MAX; bool zoneFound = false; distancePoints = 0.0; // Method 1: Use DOM (Order Book) if available (NOT available in Strategy Tester) bool isTester = MQLInfoInteger(MQL_TESTER); if(UseDOMFilter && !isTester && domFilterActive && domInitialized) { double supportLevels[]; double resistanceLevels[]; double currentPrice = (positionType == POSITION_TYPE_BUY) ? SymbolInfoDouble(Symbol(), SYMBOL_ASK) : SymbolInfoDouble(Symbol(), SYMBOL_BID); FindSupportResistance(supportLevels, resistanceLevels, currentPrice); if(positionType == POSITION_TYPE_BUY && ArraySize(supportLevels) > 0) { // For BUY: find highest support level below current price for(int i = 0; i < ArraySize(supportLevels); i++) { if(supportLevels[i] < originalPrice) { double distance = originalPrice - supportLevels[i]; double distancePts = distance / point_value; if(distancePts <= maxDistancePoints && distance < minDistance) { minDistance = distance; bestZone = supportLevels[i]; zoneFound = true; } } } } else if(positionType == POSITION_TYPE_SELL && ArraySize(resistanceLevels) > 0) { // For SELL: find lowest resistance level above current price for(int i = 0; i < ArraySize(resistanceLevels); i++) { if(resistanceLevels[i] > originalPrice) { double distance = resistanceLevels[i] - originalPrice; double distPts = distance / point_value; if(distPts <= maxDistancePoints && distance < minDistance) { minDistance = distance; bestZone = resistanceLevels[i]; zoneFound = true; } } } } } // Method 2: Use Volume Profile if DOM not available or no zone found if(!zoneFound && UseVolumeProfile) { double liquidityZones[]; double currentPrice = (positionType == POSITION_TYPE_BUY) ? SymbolInfoDouble(Symbol(), SYMBOL_ASK) : SymbolInfoDouble(Symbol(), SYMBOL_BID); CalculateVolumeProfile(liquidityZones, positionType, currentPrice); if(ArraySize(liquidityZones) > 0) { for(int i = 0; i < ArraySize(liquidityZones); i++) { double distance = MathAbs(liquidityZones[i] - originalPrice); double distancePts = distance / point_value; if(distancePts <= maxDistancePoints) { if(positionType == POSITION_TYPE_BUY && liquidityZones[i] < originalPrice) { // For BUY: prefer zones below current price if(distance < minDistance) { minDistance = distance; bestZone = liquidityZones[i]; zoneFound = true; } } else if(positionType == POSITION_TYPE_SELL && liquidityZones[i] > originalPrice) { // For SELL: prefer zones above current price if(distance < minDistance) { minDistance = distance; bestZone = liquidityZones[i]; zoneFound = true; } } } } } } if(zoneFound) { adjustedPrice = bestZone; string direction = (positionType == POSITION_TYPE_BUY) ? "BUY" : "SELL"; distancePoints = MathAbs(adjustedPrice - originalPrice) / point_value; Print(" SMART ENTRY [", direction, "] - Original: ", originalPrice, " Adjusted: ", adjustedPrice, " (", DoubleToString(distancePoints, 1), " points)"); } return adjustedPrice; } //+------------------------------------------------------------------+ // LOG DOM ANALYSIS - Print detailed Order Book information //+------------------------------------------------------------------+ void LogDOMAnalysis(int positionType, double totalLiquidity, double imbalance) { if(ArraySize(bookArray) == 0) return; double totalBidVolume = 0.0; double totalAskVolume = 0.0; int bidLevels = 0; int askLevels = 0; // Calculate volumes for(int i = 0; i < ArraySize(bookArray) && (bidLevels < DOMLevels || askLevels < DOMLevels); i++) { if(bookArray[i].type == BOOK_TYPE_BUY && bidLevels < DOMLevels) { totalBidVolume += (double)bookArray[i].volume; bidLevels++; } else if(bookArray[i].type == BOOK_TYPE_SELL && askLevels < DOMLevels) { totalAskVolume += (double)bookArray[i].volume; askLevels++; } } string direction = (positionType == POSITION_TYPE_BUY) ? "BUY" : "SELL"; string imbalanceStatus = (imbalance > 0) ? "ASK dominance" : "BID dominance"; Print(" DOM ANALYSIS [", direction, "] - BID: ", totalBidVolume, " lots (", bidLevels, " levels) | ASK: ", totalAskVolume, " lots (", askLevels, " levels) | Total: ", totalLiquidity, " lots | Imbalance: ", MathAbs(imbalance), "x (", imbalanceStatus, ")"); } //+------------------------------------------------------------------+ // CHECK ALL FILTERS FOR TRADE - Combined real-time filter analysis // Combines consecutive bars signal with all active filters // Returns true if ALL filters pass, false if any filter blocks the trade // This function is called on EVERY tick for real-time decision making //+------------------------------------------------------------------+ bool CheckAllFiltersForTrade(int positionType, double bid, double ask, int consecutiveBars) { string direction = (positionType == POSITION_TYPE_BUY) ? "BUY" : "SELL"; string filterStatus = ""; bool allFiltersPassed = true; // ========== FILTER 1: SPREAD CHECK ========== long spreadPoints; SymbolInfoInteger(_Symbol, SYMBOL_SPREAD, spreadPoints); long maxSpreadInPoints = (long)(MaxSpread * pip_value); // Convert points to broker points if(spreadPoints > maxSpreadInPoints) { double spreadInPoints = (double)spreadPoints / pip_value; filterStatus += " Spread (" + DoubleToString(spreadInPoints, 1) + " points > " + IntegerToString(MaxSpread) + " points) | "; allFiltersPassed = false; } else { double spreadInPoints = (double)spreadPoints / pip_value; filterStatus += " Spread (" + DoubleToString(spreadInPoints, 1) + " points <= " + IntegerToString(MaxSpread) + " points) | "; } // ========== FILTER 2: VOLUME CHECK ========== if(UseVolumeFilter) { // Get current volume for real-time check long currentVolume = UseTickVolume ? iTickVolume(Symbol(), Period(), 0) : iVolume(Symbol(), Period(), 0); if(!UseTickVolume && currentVolume <= 0) Print("Real volume unavailable (0) - broker did not provide volume for current bar."); long sumVolume = 0; // Calculate average volume for last 200 bars for(int i = 1; i <= 200; i++) { sumVolume += UseTickVolume ? iTickVolume(Symbol(), Period(), i) : iVolume(Symbol(), Period(), i); } long avgVolume = sumVolume / 200; double volumeRatio = (avgVolume > 0) ? ((double)currentVolume / avgVolume) : 1.3; double requiredVolumeRatio = MinVolumeRatio; if(safeModeActive) requiredVolumeRatio *= SafeVolumeEntryMultiplier; if(volumeRatio < requiredVolumeRatio) { filterStatus += " Volume (" + DoubleToString(volumeRatio, 2) + "x < " + DoubleToString(requiredVolumeRatio, 2) + "x) | "; allFiltersPassed = false; } else { filterStatus += " Volume (" + DoubleToString(volumeRatio, 2) + "x >= " + DoubleToString(requiredVolumeRatio, 2) + "x) | "; } } else { filterStatus += " Volume (disabled) | "; } // ========== FILTER 3: DOM (ORDER BOOK) CHECK ========== if(UseDOMFilter && !ForceDisableDOM && domFilterActive && domInitialized) { if(DOMAsSignal) { // DOM as signal mode: doesn't block, but provides signal strength double domSignalStrength = GetDOMSignalStrength(positionType); if(domSignalStrength >= 0.7) { filterStatus += " DOM (STRONG signal: " + DoubleToString(domSignalStrength * 100, 0) + "%) | "; } else if(domSignalStrength >= 0.3) { filterStatus += " DOM (MODERATE signal: " + DoubleToString(domSignalStrength * 100, 0) + "%) | "; } else { filterStatus += " DOM (WEAK signal: " + DoubleToString(domSignalStrength * 100, 0) + "%) | "; } // In signal mode, DOM doesn't block trades CheckDOMFilter(positionType, bid, ask); // Still analyze DOM for logging } else { // DOM as filter mode: blocks trades that don't meet criteria if(!CheckDOMFilter(positionType, bid, ask)) { filterStatus += " DOM (imbalance/liquidity) | "; allFiltersPassed = false; } else { filterStatus += " DOM (confirmed) | "; } } } else { if(UseDOMFilter && ForceDisableDOM) filterStatus += " DOM (force bypass) | "; else if(UseDOMFilter) filterStatus += " DOM (not available) | "; else filterStatus += " DOM (disabled) | "; } // ========== FILTER 4: CONSECUTIVE BARS SIGNAL ========== if(UseAntiExtremeFilter) { int lookbackBars = MathMax(10, AntiExtremeLookbackBars); double minDistancePrice = ConvertInputDistanceToPriceForSymbol(Symbol(), AntiExtremeMinDistance); if(minDistancePrice < 0.0) minDistancePrice = 0.0; double symbolPoint = SymbolInfoDouble(Symbol(), SYMBOL_POINT); if(symbolPoint <= 0.0) symbolPoint = Point(); int highShift = iHighest(Symbol(), (ENUM_TIMEFRAMES)Period(), MODE_HIGH, lookbackBars, 1); int lowShift = iLowest(Symbol(), (ENUM_TIMEFRAMES)Period(), MODE_LOW, lookbackBars, 1); double recentHigh = (highShift >= 0) ? iHigh(Symbol(), (ENUM_TIMEFRAMES)Period(), highShift) : 0.0; double recentLow = (lowShift >= 0) ? iLow(Symbol(), (ENUM_TIMEFRAMES)Period(), lowShift) : 0.0; if(positionType == POSITION_TYPE_BUY && recentHigh > 0.0) { double distanceToHigh = recentHigh - ask; if(ask >= (recentHigh - minDistancePrice)) { filterStatus += " AntiExtreme(BUY near HIGH: " + DoubleToString(distanceToHigh / symbolPoint, 1) + " pts) | "; allFiltersPassed = false; } else { filterStatus += " AntiExtreme(BUY ok: " + DoubleToString(distanceToHigh / symbolPoint, 1) + " pts) | "; } } else if(positionType == POSITION_TYPE_SELL && recentLow > 0.0) { double distanceToLow = bid - recentLow; if(bid <= (recentLow + minDistancePrice)) { filterStatus += " AntiExtreme(SELL near LOW: " + DoubleToString(distanceToLow / symbolPoint, 1) + " pts) | "; allFiltersPassed = false; } else { filterStatus += " AntiExtreme(SELL ok: " + DoubleToString(distanceToLow / symbolPoint, 1) + " pts) | "; } } } else { filterStatus += " AntiExtreme(disabled) | "; } filterStatus += " Signal (" + IntegerToString(consecutiveBars) + " consecutive bars)"; // Log combined filter status if(ShouldEmitFilterStatusLog(Symbol(), direction, allFiltersPassed, "chart_filters")) { if(allFiltersPassed) { Print(" ALL FILTERS PASSED [", direction, "] - ", filterStatus); } else { Print(" FILTERS BLOCKED [", direction, "] - ", filterStatus); } } return allFiltersPassed; } //+------------------------------------------------------------------+ // ADJUST SL/TP BASED ON MARKET ANALYSIS // Analyzes DOM, volume, momentum and adjusts SL/TP to maximize profit // Only adjusts when market conditions are favorable //+------------------------------------------------------------------+ void AdjustSLTPBasedOnAnalysis(ulong ticket, int positionType, double entryPrice, double currentSL, double currentTP, double bid, double ask, bool allowSLTightening) { // Calculate current profit double currentProfit = (positionType == POSITION_TYPE_BUY) ? (ask - entryPrice) : (entryPrice - bid); double profitInPoints = currentProfit / point_value; // Only adjust if we have some profit (at least 10 points) if(profitInPoints < 20) return; // Throttle dynamic adjustments to avoid excessive order modifications datetime now = TimeCurrent(); if(lastDynamicAdjust != 0 && (now - lastDynamicAdjust) < dynamicSLTPThrottleSeconds) return; // Calculate original TP distance - use initial TP from settings if currentTP is invalid double originalTPDistance = 0; if(currentTP > 0 && currentTP != 9999526456.42 && currentTP < entryPrice * 20) // Sanity check for absurd values { originalTPDistance = (positionType == POSITION_TYPE_BUY) ? (currentTP - entryPrice) : (entryPrice - currentTP); } // If currentTP is invalid, use initial TP from settings if(originalTPDistance <= 0 || originalTPDistance > (TakeProfit * point_value * 20)) { // Use initial TP from settings originalTPDistance = TakeProfit * point_value; } // Final validation - ensure TP distance is reasonable (max 2x initial TP) double maxTPDistance = TakeProfit * point_value * 2; if(originalTPDistance > maxTPDistance) originalTPDistance = TakeProfit * point_value; double tpMultiplier = 1.0; // Default: no change bool veryFavorableConditions = false; // For aggressive SL movement bool favorableConditions = false; string analysisReason = ""; double strengthScore = 0.0; // Overall strength score (0-1) // ========== ANALYSIS 1: DOM (Order Book) Analysis ========== if(UseDOMFilter && domFilterActive && domInitialized) { if(MarketBookGet(Symbol(), bookArray)) { double imbalance = CalculateImbalance(); double totalLiquidity = GetTotalLiquidity(); if(positionType == POSITION_TYPE_BUY) { // For BUY: Strong BID dominance (negative imbalance) is favorable if(imbalance < 0 && MathAbs(imbalance) >= StrongImbalanceThreshold && totalLiquidity >= MinLiquidity) { double imbalanceStrength = MathMin(MathAbs(imbalance) / StrongImbalanceThreshold, 3.0) / 3.0; // 0-1 strengthScore += imbalanceStrength * 0.4; // 40% weight tpMultiplier = MathMin(1.0 + imbalanceStrength * 0.5, MaxTPMultiplier); favorableConditions = true; if(MathAbs(imbalance) >= StrongImbalanceThreshold * 1.3) veryFavorableConditions = true; analysisReason += "DOM: Strong BID (" + DoubleToString(MathAbs(imbalance), 2) + "x) | "; } } else if(positionType == POSITION_TYPE_SELL) { // For SELL: Strong ASK dominance (positive imbalance) is favorable if(imbalance > 0 && MathAbs(imbalance) >= StrongImbalanceThreshold && totalLiquidity >= MinLiquidity) { double imbalanceStrength = MathMin(MathAbs(imbalance) / StrongImbalanceThreshold, 3.0) / 3.0; // 0-1 strengthScore += imbalanceStrength * 0.4; // 40% weight tpMultiplier = MathMin(1.0 + imbalanceStrength * 0.5, MaxTPMultiplier); favorableConditions = true; if(MathAbs(imbalance) >= StrongImbalanceThreshold * 1.3) veryFavorableConditions = true; analysisReason += "DOM: Strong ASK (" + DoubleToString(MathAbs(imbalance), 2) + "x) | "; } } } } // ========== ANALYSIS 2: Volume Momentum Analysis ========== long currentVolume = UseTickVolume ? iTickVolume(Symbol(), Period(), 0) : iVolume(Symbol(), Period(), 0); if(!UseTickVolume && currentVolume <= 0) Print("Real volume unavailable (0) - broker did not provide volume for current bar."); long sumVolume = 0; for(int i = 1; i <= 200; i++) { sumVolume += UseTickVolume ? iTickVolume(Symbol(), Period(), i) : iVolume(Symbol(), Period(), i); } long avgVolume = sumVolume / 200; double volumeRatio = (avgVolume > 0) ? ((double)currentVolume / avgVolume) : 1.3; if(volumeRatio >= StrongMomentumThreshold) { double volumeStrength = MathMin((volumeRatio - StrongMomentumThreshold) / StrongMomentumThreshold, 2.0) / 2.0; // 0-1 strengthScore += volumeStrength * 0.3; // 30% weight double volumeBoost = volumeStrength * 0.3; // Up to 30% boost tpMultiplier = MathMin(tpMultiplier + volumeBoost, MaxTPMultiplier); favorableConditions = true; if(volumeRatio >= StrongMomentumThreshold * 1.3) veryFavorableConditions = true; analysisReason += "Volume: Strong (" + DoubleToString(volumeRatio, 2) + "x) | "; } // ========== ANALYSIS 3: Price Momentum (Current Profit) ========== if(profitInPoints > (TakeProfit * 0.3)) { double profitStrength = MathMin(profitInPoints / TakeProfit, 1.0); // 0-1 strengthScore += profitStrength * 0.3; // 30% weight double profitBoost = profitStrength * 0.2; // Up to 20% boost tpMultiplier = MathMin(tpMultiplier + profitBoost, MaxTPMultiplier); favorableConditions = true; if(profitInPoints > (TakeProfit * 0.6)) veryFavorableConditions = true; analysisReason += "Profit: " + DoubleToString(profitInPoints, 1) + " points | "; } // ========== CALCULATE NEW TP ========== double newTPDistance = originalTPDistance * tpMultiplier; // Limit TP distance to reasonable values (max 5x initial TP) double maxAllowedTPDistance = TakeProfit * point_value * 5; if(newTPDistance > maxAllowedTPDistance) newTPDistance = maxAllowedTPDistance; double newTP = (positionType == POSITION_TYPE_BUY) ? (entryPrice + newTPDistance) : (entryPrice - newTPDistance); // Final sanity check - ensure TP is reasonable if(newTP <= 0 || newTP > entryPrice * 10 || newTP < entryPrice * 0.1) { Print(" Invalid TP calculated: ", newTP, " - Skipping adjustment"); return; } // ========== CALCULATE NEW SL - MOVE INTO PROFIT ========== double newSL = currentSL; // Initialize with current SL bool shouldModifySL = false; if(favorableConditions && allowSLTightening) { // Calculate minimum SL in profit (60 pips above/below entry) double minSLInProfit = (positionType == POSITION_TYPE_BUY) ? (entryPrice + (MinSLProfitPips * point_value)) : (entryPrice - (MinSLProfitPips * point_value)); // Current price double currentPrice = (positionType == POSITION_TYPE_BUY) ? ask : bid; if(veryFavorableConditions && strengthScore > 0.7) { // VERY FAVORABLE: Trail SL close to current price (aggressive) double trailDistance = SLTrailDistance * point_value; newSL = (positionType == POSITION_TYPE_BUY) ? (currentPrice - trailDistance) : (currentPrice + trailDistance); // Ensure SL is at least in minimum profit if(positionType == POSITION_TYPE_BUY) { if(newSL < minSLInProfit) newSL = minSLInProfit; // Can "jump over" current SL if very favorable if(newSL > currentSL || currentSL < entryPrice) shouldModifySL = true; } else { if(newSL > minSLInProfit) newSL = minSLInProfit; // Can "jump over" current SL if very favorable if(newSL < currentSL || currentSL > entryPrice) shouldModifySL = true; } analysisReason += "VERY FAVORABLE: Trailing SL | "; } else if(profitInPoints >= MinSLProfitPips) { // FAVORABLE: Move SL to minimum profit level or better if(positionType == POSITION_TYPE_BUY) { newSL = MathMax(minSLInProfit, currentSL < entryPrice ? minSLInProfit : MathMax(currentSL, minSLInProfit)); if(newSL > currentSL || currentSL < entryPrice) shouldModifySL = true; } else { newSL = MathMin(minSLInProfit, currentSL > entryPrice ? minSLInProfit : MathMin(currentSL, minSLInProfit)); if(newSL < currentSL || currentSL > entryPrice) shouldModifySL = true; } analysisReason += "Moving SL to profit | "; } } // ========== APPLY ADJUSTMENTS ========== bool shouldModifyTP = (tpMultiplier > 1.01 && newTP != currentTP); // Final validation before modifying if(newSL <= 0 || newTP <= 0) { Print(" Invalid SL/TP values - SL: ", newSL, " TP: ", newTP, " - Skipping adjustment"); return; } // Ensure SL is reasonable (within 10x entry price range) if(shouldModifySL && (newSL > entryPrice * 10 || newSL < entryPrice * 0.1)) { Print(" Invalid SL value: ", newSL, " - Skipping SL adjustment"); shouldModifySL = false; } // Ensure TP is reasonable (within 10x entry price range) if(shouldModifyTP && (newTP > entryPrice * 10 || newTP < entryPrice * 0.1)) { Print(" Invalid TP value: ", newTP, " - Skipping TP adjustment"); shouldModifyTP = false; } if(shouldModifySL || shouldModifyTP) { // Use current SL/TP if we're not modifying them if(!shouldModifySL) newSL = currentSL; if(!shouldModifyTP) newTP = currentTP; // Final check before modification if(newSL > 0 && newTP > 0 && newSL != newTP) { lastDynamicAdjust = now; ModifyPosition(ticket, newSL, newTP); string direction = (positionType == POSITION_TYPE_BUY) ? "BUY" : "SELL"; double newSLPoints = (positionType == POSITION_TYPE_BUY) ? ((newSL - entryPrice) / point_value) : ((entryPrice - newSL) / point_value); Print(" DYNAMIC ADJUSTMENT [", direction, "] - TP: ", DoubleToString(tpMultiplier, 2), "x | SL: ", DoubleToString(newSLPoints, 1), " points in profit | Strength: ", DoubleToString(strengthScore, 2), " | ", analysisReason); } } } //+------------------------------------------------------------------+ // SENTIMENT TRACKING - Show emotions on wins/losses //+------------------------------------------------------------------+ bool RefreshHistoryIfStale(int maxAgeSeconds) { datetime now = TimeCurrent(); if(lastHistoryUpdate != 0 && (now - lastHistoryUpdate) <= maxAgeSeconds) return true; // recent enough if(!HistorySelect(0, now)) { Print(" HistorySelect failed - cannot refresh deal history"); return false; } lastHistoryUpdate = now; return true; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool IsTicketProcessed(ulong ticket) { for(int i = 0; i < ArraySize(processedTickets); i++) { if(processedTickets[i] == ticket) return true; } return false; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void AddProcessedTicket(ulong ticket) { int size = ArraySize(processedTickets); ArrayResize(processedTickets, size + 1); processedTickets[size] = ticket; int cap = 2000; int total = ArraySize(processedTickets); if(total > cap) { for(int i = 1; i < total; i++) processedTickets[i - 1] = processedTickets[i]; ArrayResize(processedTickets, cap); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ bool GetPositionEntryFromHistory(ulong positionId, string symbol, long magic, datetime &tIn, double &pIn, int &dir) { if(!HistorySelect(0, TimeCurrent())) return false; int total = HistoryDealsTotal(); datetime earliestTime = 0; bool found = false; for(int i = 0; i < total; i++) { ulong tk = HistoryDealGetTicket(i); if(tk == 0 || !HistoryDealSelect(tk)) continue; if((ulong)HistoryDealGetInteger(tk, DEAL_POSITION_ID) != positionId) continue; if(HistoryDealGetString(tk, DEAL_SYMBOL) != symbol) continue; if(HistoryDealGetInteger(tk, DEAL_MAGIC) != magic) continue; if((ENUM_DEAL_ENTRY)HistoryDealGetInteger(tk, DEAL_ENTRY) != DEAL_ENTRY_IN) continue; datetime ti = (datetime)HistoryDealGetInteger(tk, DEAL_TIME); if(!found || ti < earliestTime) { earliestTime = ti; pIn = HistoryDealGetDouble(tk, DEAL_PRICE); ENUM_DEAL_TYPE dt = (ENUM_DEAL_TYPE)HistoryDealGetInteger(tk, DEAL_TYPE); dir = (dt == DEAL_TYPE_BUY) ? 1 : ((dt == DEAL_TYPE_SELL) ? -1 : 0); tIn = ti; found = true; } } return found; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void CalculateTotalClosedTrades() { if(!RefreshHistoryIfStale(5)) // Load ALL deals from beginning to now; throttle refresh return; winCount = 0; lossCount = 0; lastProcessedDealTicket = 0; ArrayResize(processedTickets, 0); // reset processed ticket cache int totalDeals = HistoryDealsTotal(); Print(" CalculateTotalClosedTrades: Scanning ", totalDeals, " total deals in history..."); Print(" Looking for symbol=", Symbol(), " | MagicNumber: ", MagicNumber); int matchingDeals = 0; for(int i = 0; i < totalDeals; i++) { ulong ticket = HistoryDealGetTicket(i); if(ticket > 0) { string symbol = HistoryDealGetString(ticket, DEAL_SYMBOL); double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT); long dealEntry = HistoryDealGetInteger(ticket, DEAL_ENTRY); long magicNumber = HistoryDealGetInteger(ticket, DEAL_MAGIC); if(symbol == Symbol() && magicNumber == MagicNumber && dealEntry == DEAL_ENTRY_OUT) { matchingDeals++; if(profit > 0) { winCount++; Print(" WIN #", winCount, ": Ticket #", ticket, " | Profit: $", profit); } else if(profit < 0) { lossCount++; Print(" LOSS #", lossCount, ": Ticket #", ticket, " | Loss: $", profit); } lastProcessedDealTicket = ticket; AddProcessedTicket(ticket); } } } Print(" Found ", matchingDeals, " matching deals | Result: ", winCount, "W / ", lossCount, "L"); } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void TrackSentiment() { if(!RefreshHistoryIfStale(5)) // Load ALL deals from beginning to now; throttle refresh return; int totalDeals = HistoryDealsTotal(); ulong maxNewTicket = lastProcessedDealTicket; int newDealsFound = 0; for(int i = 0; i < totalDeals; i++) { ulong ticket = HistoryDealGetTicket(i); if(ticket > 0 && !IsTicketProcessed(ticket)) { string symbol = HistoryDealGetString(ticket, DEAL_SYMBOL); double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT); long dealEntry = HistoryDealGetInteger(ticket, DEAL_ENTRY); long magicNumber = HistoryDealGetInteger(ticket, DEAL_MAGIC); if(ticket > maxNewTicket) maxNewTicket = ticket; bool isOurClosedDeal = (symbol == Symbol() && magicNumber == MagicNumber && dealEntry == DEAL_ENTRY_OUT); if(!isOurClosedDeal) continue; if(newDealsFound == 0) Print(" TrackSentiment: Found new deals - Checking Symbol=", symbol, "(need:", Symbol(), ") Magic=", magicNumber, "(need:", MagicNumber, ") Entry=", dealEntry); newDealsFound++; if(profit > 0) { winCount++; if(UsesentimentTracking) DisplayWinSentiment(profit); Print(" WIN TRACKED: Ticket #", ticket, " | Profit: $", DoubleToString(profit, 2), " | Total: ", winCount, "W / ", lossCount, "L"); } else if(profit < 0) { lossCount++; if(UsesentimentTracking) DisplayLossSentiment(profit); Print(" LOSS TRACKED: Ticket #", ticket, " | Loss: $", DoubleToString(profit, 2), " | Total: ", winCount, "W / ", lossCount, "L"); } else { Print(" BREAKEVEN: Ticket #", ticket, " | Profit: $0"); } AddProcessedTicket(ticket); } } lastProcessedDealTicket = maxNewTicket; if(ShowBalanceChanges) { double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE); if(currentBalance != lastBalance) { double balanceChange = currentBalance - lastBalance; string changeType = (balanceChange > 0) ? " BALANCE UP" : " BALANCE DOWN"; Print(changeType, " | Previous: $", lastBalance, " Current: $", currentBalance, " | Change: $", NormalizeDouble(balanceChange, 2), " | Win Rate: ", (winCount + lossCount > 0 ? NormalizeDouble((winCount * 100.0) / (winCount + lossCount), 1) : 0), "% (", winCount, "W / ", lossCount, "L)"); lastBalance = currentBalance; } } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void DisplayWinSentiment(double profit) { // Happy emojis and messages on winning trades string sentiments[] = { "THE KING HAS SPOKEN.", "WE WIN THE BATTLE.", "VICTORY IS OURS.", "ROYAL ENTRY. ROYAL PROFIT.", "THE CROWN STAYS ON TOP.", "ANOTHER WIN FOR THE THRONE." }; int sentimentCount = ArraySize(sentiments); int randomSentiment = (sentimentCount > 0) ? (rand() % sentimentCount) : 0; // ASCII/emoji happy faces (GREEN) string happyFaces[] = { " \n ^ ^\n \n ", " \n \n WW", " \n ^ ^ \n \n ", " \\___/\n ( ^ )\n \\___/", " :D :D\n _/__\\_\n /////" }; int faceCount = ArraySize(happyFaces); int randomFace = (faceCount > 0) ? (rand() % faceCount) : 0; string msg = sentiments[randomSentiment] + " WINNING TRADE! Profit: $" + DoubleToString(NormalizeDouble(profit, 2), 2) + "\n\n"; msg += happyFaces[randomFace] + "\n\n"; msg += "King of Gold Scalper is HAPPY! Account growing nicely,scanning for next entryto increase account balance,i m hungry!"; Print(msg); chartComment = msg; } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void DisplayLossSentiment(double loss) { // Sad emojis and messages on losing trades string sentiments[] = { "THE KING HAS SPOKEN: REGROUP.", "WE LOST THIS BATTLE, NOT THE WAR.", "HOLD THE LINE. NEXT ENTRY WILL STRIKE.", "THE THRONE STAYS CALM UNDER PRESSURE.", "DISCIPLINE FIRST. REVENGE NEVER." }; int sentimentCount = ArraySize(sentiments); int randomSentiment = (sentimentCount > 0) ? (rand() % sentimentCount) : 0; // ASCII/emoji sad faces (RED) string sadFaces[] = { " \n T T\n \n ", " o_o\n \n \\/\\/", "\n ; ; \n \n ", " \\_/\n ( T )\n /---\\", " :( :(\n .__.__.\n /////" }; int faceCount = ArraySize(sadFaces); int randomFace = (faceCount > 0) ? (rand() % faceCount) : 0; string msg = sentiments[randomSentiment] + " LOSING TRADE... Loss: $" + DoubleToString(NormalizeDouble(loss, 2), 2) + "\n\n"; msg += sadFaces[randomFace] + "\n\n"; msg += "No problem Master the King we will recover the loss,next entry will be more acurate!"; Print(msg); chartComment = msg; } //+------------------------------------------------------------------+ // ADAPTIVE TRADING - Self-adjust based on performance //+------------------------------------------------------------------+ void UpdateSignalStrengthGreed() { if(!UseSignalStrengthGreed) { greedLevel = "MODERATE"; return; } bool smallAccountProtect = IsSmallAccountForAutoProtection(); if(safeModeActive || (ForceConservativeGreedOnSmallAccount && smallAccountProtect)) { greedLevel = "CONSERVATIVE"; trendDirection = 0; if(!safeModeActive && ForceConservativeGreedOnSmallAccount) Print("AUTO PROTECT | GREED forced to CONSERVATIVE for small account."); return; } int signalStrength = 0; // Consecutive bars strength if(consecutiveBullishBars >= 3 || consecutiveBearishBars >= 3) signalStrength += 3; else if(consecutiveBullishBars >= 1 || consecutiveBearishBars >= 1) signalStrength += 2; // Infer simple trend direction from consecutive bars for display if(consecutiveBullishBars > consecutiveBearishBars) trendDirection = 1; else if(consecutiveBearishBars > consecutiveBullishBars) trendDirection = -1; else trendDirection = 0; if(trendDirection != 0) signalStrength += 2; // Tick volume boost (if enabled) if(UseVolumeFilter) { long currentVolume = UseTickVolume ? iTickVolume(Symbol(), Period(), 0) : iVolume(Symbol(), Period(), 0); if(!UseTickVolume && currentVolume <= 0) Print("Real volume unavailable (0) - broker did not provide volume for current bar."); long sumVolume = 0; for(int i = 1; i <= 200; i++) sumVolume += UseTickVolume ? iTickVolume(Symbol(), Period(), i) : iVolume(Symbol(), Period(), i); long avgVolume = (sumVolume > 0) ? (sumVolume / 200) : 0; double volumeRatio = (avgVolume > 0) ? ((double)currentVolume / avgVolume) : 1.5; if(volumeRatio >= (MinVolumeRatio * 1.5)) signalStrength += 3; else if(volumeRatio >= MinVolumeRatio) signalStrength += 2; } string previousGreed = greedLevel; bool largeStableAccount = IsLargeAccountForAutoExplore(); if(signalStrength >= 3 && largeStableAccount) { greedLevel = "GREEDY"; if(previousGreed != "GREEDY") Print(" GREEDY MODE ACTIVATED! Strong signal + stable large account - controlled larger positions."); } else if(signalStrength >= 1) { greedLevel = "MODERATE"; if(previousGreed != "MODERATE") Print(" MODERATE MODE - Normal signal strength - Trading normally!"); } else { greedLevel = "CONSERVATIVE"; if(previousGreed != "CONSERVATIVE") Print(" CONSERVATIVE MODE - Weak signals detected - Playing it safe!"); } } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ void UpdateAdaptiveTrading() { if(winCount + lossCount < 3) return; double winRate = (winCount * 100.0) / (winCount + lossCount); double baseRisk = GetConfiguredBaseRiskPercent(); double previousRisk = currentRiskPercent; double balance = AccountInfoDouble(ACCOUNT_BALANCE); double dd = GetAccountDrawdownPercentAuto(); bool largeStableAccount = IsLargeAccountForAutoExplore(); if(winRate >= MinWinRatePercent) { currentRiskPercent = baseRisk * AggressiveMultiplier; if(performanceMode != "AGGRESSIVE") { performanceMode = "AGGRESSIVE"; Print(" AGGRESSIVE MODE ACTIVATED! Win Rate: ", NormalizeDouble(winRate, 1), "% | Risk increased from ", NormalizeDouble(previousRisk, 2), "% to ", NormalizeDouble(currentRiskPercent, 2), "% | Taking bigger positions!"); } } else if(winRate < (MinWinRatePercent - 15)) { currentRiskPercent = baseRisk * ConservativeMultiplier; if(performanceMode != "CONSERVATIVE") { performanceMode = "CONSERVATIVE"; Print(" CONSERVATIVE MODE ACTIVATED! Win Rate: ", NormalizeDouble(winRate, 1), "% | Risk reduced from ", NormalizeDouble(previousRisk, 2), "% to ", NormalizeDouble(currentRiskPercent, 2), "% | Protecting capital!"); } } else { currentRiskPercent = baseRisk; if(performanceMode != "NORMAL") { performanceMode = "NORMAL"; Print(" NORMAL MODE ACTIVATED! Win Rate: ", NormalizeDouble(winRate, 1), "% | Trading normally with baseline risk!"); } } // Automatic guardrails for large funded balances and stressed conditions. if(dd >= 8.0) currentRiskPercent *= 0.80; if(balance >= 10000.0) { double maxFundedRisk = largeStableAccount ? 0.60 : 0.50; currentRiskPercent = MathMin(currentRiskPercent, maxFundedRisk); } currentRiskPercent = ClampValue(currentRiskPercent, 0.05, 10.0); } //+------------------------------------------------------------------+ // UPDATE CHART DISPLAY - Show sentiment and EA status on chart //+------------------------------------------------------------------+ void UpdateChartDisplay() { string display = ""; UpdateAccountHealthDisplayState(); double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID); double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); int chartDirection = 0; if(consecutiveBearishBars > consecutiveBullishBars) chartDirection = 1; else if(consecutiveBullishBars > consecutiveBearishBars) chartDirection = -1; int chartBars = (chartDirection == 1) ? consecutiveBearishBars : consecutiveBullishBars; double chartVolRatio = GetCurrentVolumeRatio(); string scoreDetails = ""; int decisionScore = (chartDirection == 0) ? 5 : GetDecisionScore10(chartDirection, chartBars, chartVolRatio, bid, ask, scoreDetails); string chartSignal = "NEUTRAL"; if(chartDirection == 1) chartSignal = "BULLISH"; else if(chartDirection == -1) chartSignal = "BEARISH"; int threshold = marketState.marketStress ? DecisionScoreThresholdStress : DecisionScoreThresholdNormal; double allowRate = (memoryDecisionTotal > 0) ? ((double)memoryDecisionAllow * 100.0 / (double)memoryDecisionTotal) : 0.0; double blockRate = (memoryDecisionTotal > 0) ? ((double)memoryDecisionBlock * 100.0 / (double)memoryDecisionTotal) : 0.0; double exploreRate = (memoryDecisionTotal > 0) ? ((double)memoryDecisionExplore * 100.0 / (double)memoryDecisionTotal) : 0.0; int closedTrades = winCount + lossCount; double winRatePct = (closedTrades > 0) ? (((double)winCount * 100.0) / (double)closedTrades) : 0.0; int buyCount = CountOpenTradesOfType(POSITION_TYPE_BUY); int sellCount = CountOpenTradesOfType(POSITION_TYPE_SELL); int openAll = CountOpenTradesAllSymbols(); double totalProfit = CalculateTotalProfit(); string modeTag = recoveryModeActive ? "RECOVERY" : (safeModeActive ? "SAFE" : "NORMAL"); string canTradeTag = CanTrade() ? "YES" : "NO"; int nextNewsScanSec = (lastNewsScanTime == 0) ? 0 : (MathMax(0, NewsScanIntervalSeconds - (int)(TimeCurrent() - lastNewsScanTime))); display += "==============================\n"; display += " King of Gold v9.0 Status\n"; display += "==============================\n"; if(chartComment != "") { display += chartComment + "\n"; display += "------------------------------\n"; } display += "Core | CanTrade=" + canTradeTag + " | Mode=" + modeTag + " | Stress=" + (marketState.marketStress ? "YES" : "NO") + "\n"; display += "Signal | " + chartSignal + " | Regime=" + marketState.currentRegime + " | Score " + IntegerToString(decisionScore) + "/10 >= " + IntegerToString(threshold) + "\n"; display += "Multi | Top1=" + scanner.topSymbols[0] + " (" + DoubleToString(scanner.topScores[0], 1) + ")" + " Top2=" + scanner.topSymbols[1] + " (" + DoubleToString(scanner.topScores[1], 1) + ")" + " Top3=" + scanner.topSymbols[2] + " (" + DoubleToString(scanner.topScores[2], 1) + ")\n"; display += "Memory | Allow " + DoubleToString(allowRate, 1) + "% | Block " + DoubleToString(blockRate, 1) + "% | Explore " + DoubleToString(exploreRate, 1) + "%\n"; if(UseDecisionCoreDll) { string dllStatus = g_decisionDllReady ? "READY" : "FALLBACK"; display += "DLL | " + dllStatus + " | " + g_lastDllDecisionAction + " " + g_lastDllDecisionSymbol + " | pAllow=" + DoubleToString(g_lastDllDecisionAllowProb, 3) + " | risk=" + DoubleToString(g_lastDllDecisionRiskMul, 2) + "x | lat=" + DoubleToString(g_lastDllDecisionLatencyUs, 0) + "us\n"; if(g_lastDllSnapScoreCalls >= 0) { display += "DLL Learn | score=" + IntegerToString((int)g_lastDllSnapScoreCalls) + " learn=" + IntegerToString((int)g_lastDllSnapLearnCalls) + " basket=" + IntegerToString((int)g_lastDllSnapBasketLearnCalls) + " cache=" + IntegerToString((int)g_lastDllSnapCacheHits) + "\n"; } if(g_lastDllSnapBaselineVol >= 0.0 && g_lastDllSnapBaselineSpread >= 0.0) { display += "DLL Baseline | vol=" + DoubleToString(g_lastDllSnapBaselineVol, 3) + " spread=" + DoubleToString(g_lastDllSnapBaselineSpread, 2) + "\n"; } } display += "Streak | Wins=" + IntegerToString(marketState.consecutiveWins) + " Losses=" + IntegerToString(marketState.consecutiveLosses) + " | MemRisk=" + DoubleToString(memoryAdaptiveRiskMultiplier, 2) + "x\n"; display += "Perf | WinRate=" + DoubleToString(winRatePct, 1) + "% | Closed " + IntegerToString(closedTrades) + " (" + IntegerToString(winCount) + "W/" + IntegerToString(lossCount) + "L)\n"; display += "Learning | DynamicPatterns=" + IntegerToString(dynamicPatternCount) + " | RewardUpdates=" + IntegerToString((int)setupRewardUpdates) + "\n"; display += "Risk | DD=" + DoubleToString(queenAccountDrawdownPct, 2) + "% | Margin=" + DoubleToString(queenMarginLevel, 0) + "% | Health=" + DoubleToString(queenAccountHealthScore, 2) + "\n"; display += "Account | Bal=$" + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + " Eq=$" + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + " | PL=$" + DoubleToString(NormalizeDouble(totalProfit, 2), 2) + "\n"; display += "Trades | Chart B/S=" + IntegerToString(buyCount) + "/" + IntegerToString(sellCount) + " | All=" + IntegerToString(openAll) + "/" + IntegerToString(currentMaxOpenTrades) + "\n"; display += "Alerts | Voice=" + (EnableVoicePrompts ? "ON" : "OFF") + " News=" + (EnableNewsEventAlerts ? "ON" : "OFF") + " Future=" + (EnableFutureSessionAlerts ? "ON" : "OFF") + " | NewsScanIn=" + IntegerToString(nextNewsScanSec) + "s\n"; display += "Updated | " + TimeToString(TimeCurrent(), TIME_DATE | TIME_MINUTES); Comment(display); } //+------------------------------------------------------------------+// //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //+------------------------------------------------------------------+