multi_arm_bandit/Multi_Arm_Bandit_Scalper.mq5

13212 lines
503 KiB
MQL5

//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
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 <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#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 =
"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
"<grammar xmlns=\"http://www.w3.org/2001/06/grammar\" version=\"1.0\" xml:lang=\"en-US\" mode=\"voice\" root=\"cmd\">\n"
" <rule id=\"cmd\" scope=\"public\">\n"
" <one-of>\n"
" <item>pause</item>\n"
" <item>pause trading</item>\n"
" <item>stop trading</item>\n"
" <item>hold on</item>\n"
" <item>resume</item>\n"
" <item>resume trading</item>\n"
" <item>continue trading</item>\n"
" <item>close all</item>\n"
" <item>close everything</item>\n"
" <item>close positions</item>\n"
" <item>panic close</item>\n"
" <item>risk one percent</item>\n"
" <item>risk two percent</item>\n"
" <item>risk three percent</item>\n"
" <item>risk four percent</item>\n"
" <item>risk five percent</item>\n"
" <item>set risk to one percent</item>\n"
" <item>set risk to two percent</item>\n"
" <item>set risk to three percent</item>\n"
" <item>set risk to four percent</item>\n"
" <item>set risk to five percent</item>\n"
" <item>hello</item>\n"
" <item>hi</item>\n"
" <item>good morning</item>\n"
" <item>good afternoon</item>\n"
" <item>good evening</item>\n"
" <item>how are you</item>\n"
" <item>status</item>\n"
" <item>status report</item>\n"
" </one-of>\n"
" </rule>\n"
"</grammar>\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);
}
//+------------------------------------------------------------------+//
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+