//+------------------------------------------------------------------+ //| LiveEA.mq5 | //| DualEA Live Trading Expert - Reconstructed | //+------------------------------------------------------------------+ #property copyright "DualEA" #property version "2.0" #property strict //+------------------------------------------------------------------+ //| INCLUDES | //+------------------------------------------------------------------+ // New Architecture: Auto-learning gate system (unified with PaperEA) #include "..\Include\GateSystemAutoLearning.mqh" #include "..\Include\IStrategy.mqh" #include "..\Include\CPositionManager.mqh" #include "..\Include\TradeManager.mqh" #include "..\Include\Telemetry.mqh" #include "..\Include\TelemetryStandard.mqh" #include "..\Include\SessionManager.mqh" #include "..\Include\CorrelationManager.mqh" #include "..\Include\VolatilitySizer.mqh" #include "..\Include\InsightsLoader.mqh" #include "..\Include\IncrementalInsightEngine.mqh" // Real-time O(1) statistics engine #include "LiveEA_StrategyBridge.mqh" // Undefine logging macros from included headers to avoid clashes with LogLevel enum #ifdef LOG_DEBUG #undef LOG_DEBUG #endif #ifdef LOG_INFO #undef LOG_INFO #endif #ifdef LOG_WARNING #undef LOG_WARNING #endif #ifdef LOG_ERROR #undef LOG_ERROR #endif //+------------------------------------------------------------------+ //| ENUMS AND CONSTANTS | //+------------------------------------------------------------------+ enum LogLevel { LOG_ERROR=0, LOG_WARN=1, LOG_INFO=2, LOG_DEBUG=3 }; //+------------------------------------------------------------------+ //| INPUT PARAMETERS - CORE SETTINGS | //+------------------------------------------------------------------+ input long MagicNumber = 123456; input int Verbosity = 1; // 0=errors,1=warn,2=info,3=debug input bool TelemetryEnabled = false; // enable telemetry emission input bool NoConstraintsMode = false; // shadow-gate only, do not block //+------------------------------------------------------------------+ //| INPUT PARAMETERS - TELEMETRY | //+------------------------------------------------------------------+ input string TelemetryExperiment = "live"; input int TelemetryBufferMax = 1000; //+------------------------------------------------------------------+ //| INPUT PARAMETERS - RISK MANAGEMENT | //+------------------------------------------------------------------+ input double SpreadMaxPoints = 0.0; // 0=disabled input int SessionStartHour = 0; // 0..23 input int SessionMaxTrades = 0; // 0=disabled input double MaxDailyLossPct = 0.0; // 0=disabled input double MaxDrawdownPct = 0.0; // 0=disabled input double MinMarginLevel = 0.0; // 0=disabled, e.g. 100.0 input int ConsecutiveLossLimit = 0; // 0=disabled input bool P5_PersistLossCounters = false; // persist counters (minimal in-memory here) //+------------------------------------------------------------------+ //| INPUT PARAMETERS - INSIGHTS GATING | //+------------------------------------------------------------------+ input bool UseInsightsGating = true; input int GateMinTrades = 0; // loosened for bootstrap input double GateMinWinRate = 0.00; // loosened for bootstrap input double GateMinExpectancyR = -10.0; // loosened for bootstrap input double GateMaxDrawdownR = 1000000.0;// loosened for bootstrap input double GateMinProfitFactor = 0.00; // loosened for bootstrap //+------------------------------------------------------------------+ //| INPUT PARAMETERS - EXPLORATION MODE | //+------------------------------------------------------------------+ input bool ExploreOnNoSlice = true; // allow limited trades when slice has no data input int ExploreMaxPerSlice = 100; // loosened for bootstrap input int ExploreMaxPerSlicePerDay = 100; // loosened for bootstrap //+------------------------------------------------------------------+ //| INPUT PARAMETERS - POLICY GATING | //+------------------------------------------------------------------+ input bool UsePolicyGating = true; input bool DefaultPolicyFallback = true; // allow neutral trading when policy slice is missing input bool FallbackDemoOnly = true; // restrict fallback to demo accounts input bool FallbackWhenNoPolicy = true; // allow fallback when policy file is not loaded //+------------------------------------------------------------------+ //| INPUT PARAMETERS - CORRELATION MANAGEMENT | //+------------------------------------------------------------------+ input double P5_CorrMax = 1.0; // 1.0=no pruning by default input int P5_CorrLookbackDays = 5; // used if PositionManager is not available input bool UsePositionManager = false; // optional PositionManager usage // News / Promotion / Regime / Cooldown inputs input bool UseNewsFilter = false; // block around defined news windows input int NewsBufferBeforeMin = 30; // minutes before an event input int NewsBufferAfterMin = 30; // minutes after an event input int NewsImpactMin = 2; // 1=low, 2=medium, 3=high input bool NewsUseFile = true; // read blackouts from CSV in Common files input string NewsFileRelPath = "DualEA\\news_blackouts.csv"; // --- Insights live auto-reload controls input bool InsightsAutoReload = true; // if stale/missing, request rebuild and auto-reload when ready input int InsightsLiveFreshMinutes = 10; // consider insights fresh if modified within N minutes input int InsightsReadyPollSec = 5; // poll frequency for ready signal input bool UsePromotionGate = false; // allow only during configured windows input bool PromoLiveOnly = false; // apply only on live accounts input int PromoStartHour = 0; // inclusive, server time input int PromoEndHour = 24; // exclusive, supports wrap if less than start input bool UseRegimeGate = false; // filter by volatility regimes input int RegimeATRPeriod = 14; input double RegimeMinATRPct = 0.0; // 0=disabled input double RegimeMaxATRPct = 1000.0; // 1000=disabled input int CircuitCooldownSec = 0; // 0=disabled // --- FR-02 Session Manager inputs input bool UseSessionManager = true; input int SessionEndHour = 20; input int MaxTradesPerSession = 10; // --- FR-05 Correlation Manager inputs input bool UseCorrelationManager = true; input double MaxCorrelationLimit = 0.7; input int CorrLookbackDays = 30; // --- FR-04 Position Manager inputs input int PMMaxOpenPositions = 10; // 0=disabled; cap total open positions before PM gate blocks // --- FR-06 Volatility Sizer inputs input bool UseVolatilitySizer = false; input int VolSizerATRPeriod = 14; input double VolSizerBaseATRPct = 1.0; input double VolSizerMinMult = 0.1; input double VolSizerMaxMult = 3.0; input double VolSizerTargetRisk = 1.0; input double BaseLotSize = 0.01; // base lot size used when sizing via VolatilitySizer //+------------------------------------------------------------------+ //| GLOBAL VARIABLES | //+------------------------------------------------------------------+ CEfficientGateManagerEnhanced* g_gate_manager = NULL; // New unified gate system CTelemetry *g_telemetry = NULL; CTelemetryStandard* g_tel_standard = NULL; CSessionManager* g_session_manager = NULL; CCorrelationManager* g_correlation_manager = NULL; CVolatilitySizer* g_volatility_sizer = NULL; // g_position_manager and g_insight_engine are defined in their respective modules // CPositionManager* g_position_manager; // CIncrementalInsightEngine* g_insight_engine; bool g_eval_busy = false; datetime g_last_trade_placed= 0; // --- Insights gating cache (loaded from insights.json via InsightsLoader) string g_gate_strat[]; string g_gate_sym[]; int g_gate_tf[]; int g_gate_cnt[]; double g_gate_wr[]; double g_gate_avgR[]; double g_gate_pf[]; double g_gate_dd[]; // --- Policy gating cache (loaded from policy.json) bool g_policy_loaded = false; double g_policy_min_conf= 0.0; string g_pol_strat[]; string g_pol_sym[]; int g_pol_tf[]; double g_pol_p[]; double g_pol_sl[]; double g_pol_tp[]; double g_pol_trail[]; // Live insights reload state bool g_waiting_insights = false; // set after we request reload; reset when ready is consumed // --- Exploration Mode tracking (weekly + daily persistent) string g_exp_keys[]; // slice key: strategy|symbol|timeframe int g_exp_weeks[]; // week bucket id (Monday yyyymmdd) int g_exp_counts[]; // count within week string g_explore_pending_key = ""; // set by Insights_Allow when allowing explore // Daily exploration tracking (persistent) string g_exp_day_keys[]; int g_exp_day_days[]; int g_exp_day_counts[]; // --- News blackout cache (from CSV) string g_news_key[]; datetime g_news_from[]; datetime g_news_to[]; int g_news_impact[]; //+------------------------------------------------------------------+ //| UTILITY FUNCTIONS | //+------------------------------------------------------------------+ bool ShouldLog(const int level) { return (Verbosity >= level); } void LogMessage(const int level, const string message) { if (!ShouldLog(level)) return; string prefix = ""; switch(level) { case LOG_ERROR: prefix = "[ERROR] "; break; case LOG_WARN: prefix = "[WARN] "; break; case LOG_INFO: prefix = "[INFO] "; break; case LOG_DEBUG: prefix = "[DEBUG] "; break; } Print(prefix + message); } //+------------------------------------------------------------------+ //| SERVICE INITIALIZATION FUNCTIONS | //+------------------------------------------------------------------+ void EnsureTelemetry() { if(!TelemetryEnabled) return; if(CheckPointer(g_telemetry)==POINTER_INVALID) { g_telemetry = new CTelemetry(_Symbol, TelemetryExperiment, TelemetryBufferMax, (int)_Period); if(CheckPointer(g_telemetry)==POINTER_INVALID) { Print("Failed to create telemetry instance"); return; } g_tel_standard = new CTelemetryStandard(g_telemetry); } } void EnsurePM() { if(!UsePositionManager) return; if(CheckPointer(g_position_manager)==POINTER_INVALID) { g_position_manager = new CPositionManager(); if(CheckPointer(g_position_manager)!=POINTER_INVALID) (*g_position_manager).SetMagicNumber((int)MagicNumber); } } void EnsureSessionManager() { if(!UseSessionManager) return; if(CheckPointer(g_session_manager)==POINTER_INVALID) { g_session_manager = new CSessionManager(_Symbol, _Period); g_session_manager.SetEnabled(true); g_session_manager.SetSessionHours(SessionStartHour, SessionEndHour); g_session_manager.SetMaxTradesPerSession(MaxTradesPerSession); g_session_manager.SetMaxDailyLossPct(MaxDailyLossPct); } } void EnsureCorrelationManager() { if(!UseCorrelationManager) return; if(CheckPointer(g_correlation_manager)==POINTER_INVALID) { g_correlation_manager = new CCorrelationManager(_Symbol, _Period); g_correlation_manager.SetEnabled(true); g_correlation_manager.SetMaxCorrelation(MaxCorrelationLimit); g_correlation_manager.SetLookbackDays(CorrLookbackDays); } } void EnsureVolatilitySizer() { if(!UseVolatilitySizer) return; if(CheckPointer(g_volatility_sizer)==POINTER_INVALID) { g_volatility_sizer = new CVolatilitySizer(_Symbol, _Period); g_volatility_sizer.SetEnabled(true); g_volatility_sizer.SetATRPeriod(VolSizerATRPeriod); g_volatility_sizer.SetBaseATRPercent(VolSizerBaseATRPct); g_volatility_sizer.SetMultiplierRange(VolSizerMinMult, VolSizerMaxMult); g_volatility_sizer.SetTargetRiskPercent(VolSizerTargetRisk); } } // --- Spread/Session/Risk gating helpers and session tracking datetime g_session_start = 0; int g_session_day = 0; double g_session_equity_start = 0.0; double g_equity_highwater = 0.0; double CurrentSpreadPoints() { double bid=0.0, ask=0.0; SymbolInfoDouble(_Symbol, SYMBOL_BID, bid); SymbolInfoDouble(_Symbol, SYMBOL_ASK, ask); if(ask>0.0 && bid>0.0 && _Point>0.0) return (ask - bid)/_Point; return 0.0; } datetime ComputeSessionBoundary(datetime now) { MqlDateTime dt; TimeToStruct(now, dt); dt.hour = SessionStartHour; dt.min = 0; dt.sec = 0; datetime today_boundary = StructToTime(dt); if(now < today_boundary) return today_boundary - 86400; return today_boundary; } void EnsureSessionRollover() { datetime b = ComputeSessionBoundary(TimeCurrent()); MqlDateTime dts; TimeToStruct(b, dts); int day = dts.year*10000 + dts.mon*100 + dts.day; if(g_session_start==0 || g_session_day!=day) { g_session_start = b; g_session_day = day; g_session_equity_start = AccountInfoDouble(ACCOUNT_EQUITY); g_equity_highwater = g_session_equity_start; if(ShouldLog(LOG_INFO)) PrintFormat("Session rollover: start=%s equity=%.2f", TimeToString(g_session_start, TIME_DATE|TIME_MINUTES), g_session_equity_start); } } int CountNewTradesSince(const datetime t0) { int count=0; if(!HistorySelect(t0, TimeCurrent())) return 0; int total = (int)HistoryDealsTotal(); for(int i=0;i=0; --i) { ulong ticket = HistoryDealGetTicket(i); if((int)HistoryDealGetInteger(ticket, DEAL_ENTRY)!=DEAL_ENTRY_OUT) continue; long mag = (long)HistoryDealGetInteger(ticket, DEAL_MAGIC); if(mag != MagicNumber) continue; double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT); if(profit < 0.0) consec++; else break; } return consec; } // --- Minimal persistent consecutive loss counters (in-memory) string g_loss_sym[]; long g_loss_mag[]; int g_loss_cnt[]; int GetConsecLossPersist(const string sym, const long mag) { for(int i=0;ig_equity_highwater) g_equity_highwater = eq; if(MaxDailyLossPct>0.0 && g_session_equity_start>0.0) { double dd = 100.0*(g_session_equity_start - eq)/g_session_equity_start; if(dd >= MaxDailyLossPct) { reason="max_daily_loss"; return false; } } if(MaxDrawdownPct>0.0 && g_equity_highwater>0.0) { double ddh = 100.0*(g_equity_highwater - eq)/g_equity_highwater; if(ddh >= MaxDrawdownPct) { reason="max_drawdown"; return false; } } if(MinMarginLevel>0.0) { double ml = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL); if(ml>0.0 && ml < MinMarginLevel) { reason="margin_level"; return false; } } if(ConsecutiveLossLimit>0) { int cl = (P5_PersistLossCounters ? GetConsecLossPersist(_Symbol, (long)MagicNumber) : CountConsecutiveLosses()); if(cl >= ConsecutiveLossLimit) { reason="consec_losses"; return false; } } return true; } // Helper: append unique key to a dynamic string array void AddUniqueKey(string &arr[], const string k) { for(int i=0;i=0) AddUniqueKey(keys, ccy[i]); // Index heuristics -> base currency if(StringFind(sU, "US30")>=0 || StringFind(sU, "US500")>=0 || StringFind(sU, "NAS")>=0 || StringFind(sU, "DJ")>=0 || StringFind(sU, "SPX")>=0) AddUniqueKey(keys, "USD"); if(StringFind(sU, "GER")>=0 || StringFind(sU, "DAX")>=0 || StringFind(sU, "FRA40")>=0 || StringFind(sU, "CAC")>=0) AddUniqueKey(keys, "EUR"); if(StringFind(sU, "UK100")>=0 || StringFind(sU, "FTSE")>=0) AddUniqueKey(keys, "GBP"); if(StringFind(sU, "JPN")>=0 || StringFind(sU, "JP")>=0 || StringFind(sU, "NIK")>=0) AddUniqueKey(keys, "JPY"); if(StringFind(sU, "HK50")>=0 || StringFind(sU, "HSI")>=0) AddUniqueKey(keys, "HKD"); if(StringFind(sU, "AUS")>=0) AddUniqueKey(keys, "AUD"); datetime now = TimeCurrent(); // Scan loaded blackout windows for(int i=0;i=from && now<=to) { reason = "news_window"; return false; } } return true; } bool PromotionAllowed(string &reason) { reason = "ok"; if(!UsePromotionGate) return true; // Apply only on live accounts if configured if(PromoLiveOnly && AccountInfoInteger(ACCOUNT_TRADE_MODE)!=ACCOUNT_TRADE_MODE_REAL) return true; MqlDateTime dt; TimeToStruct(TimeCurrent(), dt); int h = dt.hour; // handle windows possibly spanning midnight bool in_window = false; if(PromoStartHour<=PromoEndHour) in_window = (h>=PromoStartHour && h=PromoStartHour || h allow double atr_pct = 100.0 * atr / px; if(RegimeMinATRPct>0.0 && atr_pct < RegimeMinATRPct) { reason = "regime_low_atr"; return false; } if(RegimeMaxATRPct>0.0 && atr_pct > RegimeMaxATRPct) { reason = "regime_high_atr"; return false; } return true; } bool CircuitCooldownAllowed(string &reason) { reason = "ok"; if(CircuitCooldownSec<=0) return true; if(g_last_trade_placed<=0) return true; datetime now = TimeCurrent(); if((now - g_last_trade_placed) < CircuitCooldownSec) { reason = "circuit_cooldown"; return false; } return true; } bool P5_CorrPruneAllow(const TradeOrder &order, string &reason) { reason = "ok"; // No open positions -> nothing to prune int npos = PositionsTotal(); if(npos<=0) return true; // Prefer PositionManager's portfolio correlation if available double avg_abs_corr = 0.0; bool corr_from_pm = (CheckPointer(g_position_manager)!=POINTER_INVALID); if(corr_from_pm) { avg_abs_corr = (*g_position_manager).GetPortfolioCorrelation(_Symbol); } else { // Manual weighted average absolute correlation to each open symbol int ps = PeriodSeconds(_Period); if(ps<=0) ps = 60; int approx_bars = (int)MathMax(50, MathMin(2000, (P5_CorrLookbackDays*86400)/ps)); double sum=0.0, wsum=0.0; for(int i=0;i1.0) c=1.0; if(c<-1.0) c=-1.0; sum += MathAbs(c) * vol; wsum += vol; } avg_abs_corr = (wsum>0.0? sum/wsum : 0.0); } if(avg_abs_corr > P5_CorrMax) { reason = StringFormat("corr=%.2f>max=%.2f", avg_abs_corr, P5_CorrMax); return false; } return true; } // --- Insights/Policy helpers and loaders // Trim helper string TrimCopy(string s){ StringTrimLeft(s); StringTrimRight(s); return s; } // Normalize broker-specific suffixes to base symbol (e.g., "AUDNZD_otc" -> "AUDNZD") string NormalizeSymbol(string s) { string lowers = s; StringToLower(lowers); // Common suffixes/prefixes to strip string suf[] = { "_otc", "_pro", "_ecn", "_mini", "_micro", ".r", ".i", ".pro", ".ecn", ".m" }; for(int i=0;i=0 && p==StringLen(lowers)-StringLen(suf[i])) { // Remove suffix of same length from original string s = StringSubstr(s, 0, StringLen(s)-StringLen(suf[i])); break; } } return s; } // Load policy.json from Common Files and cache min_confidence and slice_probs bool Policy_Load() { g_policy_loaded = false; g_policy_min_conf = 0.0; ArrayResize(g_pol_strat,0); ArrayResize(g_pol_sym,0); ArrayResize(g_pol_tf,0); ArrayResize(g_pol_p,0); ArrayResize(g_pol_sl,0); ArrayResize(g_pol_tp,0); ArrayResize(g_pol_trail,0); string path = "DualEA\\policy.json"; int h = FileOpen(path, FILE_READ|FILE_TXT|FILE_COMMON|FILE_ANSI); if(h==INVALID_HANDLE) { if(ShouldLog(LOG_WARN)) PrintFormat("Policy gating: cannot open %s (Common). Err=%d", path, GetLastError()); return false; } string cur_s="", cur_y=""; int cur_tf=-1; double cur_p=-1.0; double cur_sl=1.0, cur_tp=1.0, cur_tr=1.0; // optional scales default to 1.0 while(!FileIsEnding(h)) { string line = FileReadString(h); if(line=="" && FileIsEnding(h)) break; int p; // min_confidence p = StringFind(line, "\"min_confidence\"", 0); if(p>=0) { int c = StringFind(line, ":", p); if(c>=0){ string num = TrimCopy(StringSubstr(line, c+1)); double v = StringToDouble(num); if(v>0) g_policy_min_conf = v; } } // strategy p = StringFind(line, "\"strategy\"", 0); if(p>=0) { int q = StringFind(line, ",", p+1); string seg = (q>p? StringSubstr(line, p, q-p) : StringSubstr(line, p)); int c3=StringFind(seg, "\"", 0); c3 = StringFind(seg, "\"", c3+1); int c4=StringFind(seg, "\"", c3+1); int c5=StringFind(seg, "\"", c4+1); if(c4>0 && c5>c4) cur_s = StringSubstr(seg, c4+1, c5-c4-1); } // symbol p = StringFind(line, "\"symbol\"", 0); if(p>=0) { int q = StringFind(line, ",", p+1); string seg = (q>p? StringSubstr(line, p, q-p) : StringSubstr(line, p)); int c3=StringFind(seg, "\"", 0); c3 = StringFind(seg, "\"", c3+1); int c4=StringFind(seg, "\"", c3+1); int c5=StringFind(seg, "\"", c4+1); if(c4>0 && c5>c4) cur_y = StringSubstr(seg, c4+1, c5-c4-1); } // timeframe p = StringFind(line, "\"timeframe\"", 0); if(p>=0) { int c = StringFind(line, ":", p); if(c>=0){ string num = TrimCopy(StringSubstr(line, c+1)); cur_tf = (int)StringToInteger(num); } } // p_win p = StringFind(line, "\"p_win\"", 0); if(p>=0) { int c = StringFind(line, ":", p); if(c>=0){ string num = TrimCopy(StringSubstr(line, c+1)); cur_p = StringToDouble(num); } } // optional: sl_scale p = StringFind(line, "\"sl_scale\"", 0); if(p>=0) { int c = StringFind(line, ":", p); if(c>=0){ string num = TrimCopy(StringSubstr(line, c+1)); cur_sl = StringToDouble(num); } } // optional: tp_scale p = StringFind(line, "\"tp_scale\"", 0); if(p>=0) { int c = StringFind(line, ":", p); if(c>=0){ string num = TrimCopy(StringSubstr(line, c+1)); cur_tp = StringToDouble(num); } } // optional: trail_scale p = StringFind(line, "\"trail_scale\"", 0); if(p>=0) { int c = StringFind(line, ":", p); if(c>=0){ string num = TrimCopy(StringSubstr(line, c+1)); cur_tr = StringToDouble(num); } } // commit when we have strat+symbol (timeframe may be -1) if(cur_s!="" && cur_y!="" && cur_tf!=-2 && cur_p>=0.0) { int n = ArraySize(g_pol_strat); ArrayResize(g_pol_strat,n+1); ArrayResize(g_pol_sym,n+1); ArrayResize(g_pol_tf,n+1); ArrayResize(g_pol_p,n+1); ArrayResize(g_pol_sl,n+1); ArrayResize(g_pol_tp,n+1); ArrayResize(g_pol_trail,n+1); g_pol_strat[n]=cur_s; g_pol_sym[n]=cur_y; g_pol_tf[n]=cur_tf; g_pol_p[n]=cur_p; g_pol_sl[n]=cur_sl; g_pol_tp[n]=cur_tp; g_pol_trail[n]=cur_tr; // reset slice accumulators for next object cur_s=""; cur_y=""; cur_tf=-1; cur_p=-1.0; cur_sl=1.0; cur_tp=1.0; cur_tr=1.0; } } FileClose(h); g_policy_loaded = (ArraySize(g_pol_strat)>0); if(ShouldLog(LOG_INFO)) PrintFormat("Policy gating: min_conf=%.3f slices=%d", g_policy_min_conf, ArraySize(g_pol_strat)); return g_policy_loaded; } double GetPolicyProb(const string strategy, const string symbol, const int timeframe) { string symN = NormalizeSymbol(symbol); // 1) Exact slice: strat+symbol+timeframe for(int i=0;i0? g_pol_sl[i] : 1.0); for(int i=0;i0? g_pol_sl[i] : 1.0); for(int i=0;i0? g_pol_sl[i] : 1.0); return 1.0; } double GetPolicyScaleTP(const string strategy, const string symbol, const int timeframe) { string symN = NormalizeSymbol(symbol); for(int i=0;i0? g_pol_tp[i] : 1.0); for(int i=0;i0? g_pol_tp[i] : 1.0); for(int i=0;i0? g_pol_tp[i] : 1.0); return 1.0; } double GetPolicyScaleTrail(const string strategy, const string symbol, const int timeframe) { string symN = NormalizeSymbol(symbol); for(int i=0;i0? g_pol_trail[i] : 1.0); for(int i=0;i0? g_pol_trail[i] : 1.0); for(int i=0;i0? g_pol_trail[i] : 1.0); return 1.0; } // Apply policy-driven scaling to SL/TP/trailing of an order. Entry price approximated. void ApplyPolicyScaling(TradeOrder &order, const string symbol, const int timeframe, const double ppol) { if(!UsePolicyGating || !g_policy_loaded) return; // fetch scales double sls = GetPolicyScaleSL(order.strategy_name, symbol, timeframe); double tps = GetPolicyScaleTP(order.strategy_name, symbol, timeframe); double trs = GetPolicyScaleTrail(order.strategy_name, symbol, timeframe); // determine entry reference double entry = 0.0; if(order.price>0.0) entry = order.price; // pending else entry = (order.action==ACTION_BUY? SymbolInfoDouble(symbol, SYMBOL_ASK) : SymbolInfoDouble(symbol, SYMBOL_BID)); // Adjust SL if(order.stop_loss>0.0 && entry>0.0 && sls>0.0 && sls!=1.0) { double d = MathAbs(entry - order.stop_loss); double nd = d * sls; if(order.action==ACTION_BUY) order.stop_loss = entry - nd; else if(order.action==ACTION_SELL) order.stop_loss = entry + nd; } // Adjust TP if(order.take_profit>0.0 && entry>0.0 && tps>0.0 && tps!=1.0) { double d = MathAbs(order.take_profit - entry); double nd = d * tps; if(order.action==ACTION_BUY) order.take_profit = entry + nd; else if(order.action==ACTION_SELL) order.take_profit = entry - nd; } // Adjust trailing if(order.trailing_enabled && trs>0.0 && trs!=1.0) { if(order.trailing_type==TRAIL_ATR) order.atr_multiplier *= trs; else if(order.trailing_type==TRAIL_FIXED_POINTS) { order.trail_distance_points *= trs; order.trail_step_points *= trs; } } if(ShouldLog(LOG_DEBUG)) PrintFormat("[POLICY] scales applied %s/%s tf=%d p=%.3f sl=%.2f tp=%.2f tr=%.2f", order.strategy_name, symbol, timeframe, ppol, sls, tps, trs); } // --- Exploration counters (weekly/daily) persistence int WeekMondayId(datetime t) { MqlDateTime dt; TimeToStruct(t, dt); // MT5: day_of_week 0=Sunday, 1=Monday, ... 6=Saturday int dow = dt.day_of_week; int delta_days = (dow==0 ? 6 : (dow-1)); datetime monday = t - (delta_days * 86400); MqlDateTime md; TimeToStruct(monday, md); return (md.year*10000 + md.mon*100 + md.day); } // P0-3: Checksum calculation for exploration counters uint CalculateExploreChecksum(string key, int week, int count) { string data = key + "|" + IntegerToString(week) + "|" + IntegerToString(count); uint checksum = 0; for(int i = 0; i < StringLen(data); i++) { checksum = ((checksum << 5) + checksum) + (uchar)StringGetCharacter(data, i); } return checksum; } string ExploreCountsPath() { return "DualEA\\explore_counts.csv"; // FILE_COMMON } void SaveExploreCounts() { string path = ExploreCountsPath(); int h = FileOpen(path, FILE_WRITE|FILE_CSV|FILE_ANSI|FILE_COMMON, ','); if(h==INVALID_HANDLE){ PrintFormat("Explore persist: cannot open %s for write. Err=%d", path, GetLastError()); return; } // P0-3: Add checksum column to header FileWrite(h, "key", "week_monday_yyyymmdd", "count", "checksum"); for(int i=0;i 0) { uint expected = CalculateExploreChecksum(k, (int)StringToInteger(wk_s), (int)StringToInteger(cnt_s)); uint actual = (uint)StringToInteger(checksum_s); if(expected != actual) { PrintFormat("[P0-3] Checksum mismatch at row %d: key=%s", row, k); valid = false; } } row++; } FileClose(h); return valid; } bool LoadExploreCounts() { ArrayResize(g_exp_keys,0); ArrayResize(g_exp_weeks,0); ArrayResize(g_exp_counts,0); string path = ExploreCountsPath(); int h = FileOpen(path, FILE_READ|FILE_CSV|FILE_ANSI|FILE_COMMON, ','); if(h==INVALID_HANDLE) { if(ShouldLog(LOG_INFO)) PrintFormat("Explore persist: no prior %s (ok)", path); return true; } bool first=true; int corrupted_rows = 0; int total_rows = 0; while(!FileIsEnding(h)) { string k = FileReadString(h); if(k=="" && FileIsEnding(h)) break; string wk_s = FileReadString(h); string cnt_s = FileReadString(h); string checksum_s = FileReadString(h); // P0-3: Read checksum column // skip header if present if(first && (StringFind(k, "key", 0)==0)) { first=false; continue; } first=false; total_rows++; // P0-3: Validate checksum before loading bool valid = true; if(StringLen(checksum_s) > 0) { uint expected = CalculateExploreChecksum(k, (int)StringToInteger(wk_s), (int)StringToInteger(cnt_s)); uint actual = (uint)StringToInteger(checksum_s); if(expected != actual) { PrintFormat("[P0-3] Corrupted row detected and skipped: key=%s (checksum mismatch)", k); corrupted_rows++; valid = false; } } if(valid) { int n = ArraySize(g_exp_keys); ArrayResize(g_exp_keys,n+1); ArrayResize(g_exp_weeks,n+1); ArrayResize(g_exp_counts,n+1); g_exp_keys[n]=k; g_exp_weeks[n]=(int)StringToInteger(wk_s); g_exp_counts[n]=(int)StringToInteger(cnt_s); } } FileClose(h); // P0-3: Log corruption statistics if(corrupted_rows > 0) { PrintFormat("[P0-3] LoadExploreCounts: Recovered from %d corrupted rows out of %d total", corrupted_rows, total_rows); } return true; } int DayId(datetime t) { MqlDateTime dt; TimeToStruct(t, dt); return (dt.year*10000 + dt.mon*100 + dt.day); } string ExploreDayCountsPath() { return "DualEA\\explore_counts_day.csv"; } void SaveExploreCountsDay() { string path = ExploreDayCountsPath(); int h = FileOpen(path, FILE_WRITE|FILE_CSV|FILE_ANSI|FILE_COMMON, ','); if(h==INVALID_HANDLE){ PrintFormat("Explore persist(day): cannot open %s for write. Err=%d", path, GetLastError()); return; } FileWrite(h, "key,day_yyyymmdd,count"); for(int i=0;i0) return true; return false; } bool Insights_Load() { int loaded = Insights_Load_Default( g_gate_strat, g_gate_sym, g_gate_tf, g_gate_cnt, g_gate_wr, g_gate_avgR, g_gate_pf, g_gate_dd ); if(loaded<0) return false; if(ShouldLog(LOG_INFO)) PrintFormat("Insights gating: loaded %d slices", ArraySize(g_gate_strat)); return ArraySize(g_gate_strat)>0; } bool Insights_Allow(const string strategy, const string symbol, const int timeframe, string &reason, const bool shadow=false, const bool no_side_effects=false) { reason = ""; // Global bypass for data collection if(NoConstraintsMode && !shadow) { reason = "no_constraints"; return true; } // Default policy fallback when no policy is loaded if(DefaultPolicyFallback && UsePolicyGating && FallbackWhenNoPolicy && !g_policy_loaded) { bool demo_ok0 = (!FallbackDemoOnly) || (AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_DEMO); if(demo_ok0) { reason = "fallback_no_policy"; return true; } } // Default policy fallback: allow neutral trading when policy slice is missing if(DefaultPolicyFallback && UsePolicyGating && g_policy_loaded) { double pchk = GetPolicyProb(strategy, symbol, timeframe); if(pchk < 0.0) { bool demo_ok = (!FallbackDemoOnly) || (AccountInfoInteger(ACCOUNT_TRADE_MODE)==ACCOUNT_TRADE_MODE_DEMO); if(demo_ok) { reason = "fallback_policy_miss"; return true; } } } // Find matching slice for(int i=0;i GateMaxDrawdownR) { reason = "drawdown"; return false; } if(g_gate_pf[i] < GateMinProfitFactor) { reason = "profit_factor"; return false; } // Policy check for exact slice if(UsePolicyGating && g_policy_loaded) { double ppol = GetPolicyProb(strategy, symbol, timeframe); if(ppol>=0.0 && ppol < g_policy_min_conf) { reason = "policy_min_conf"; return false; } } return true; } // If no slices loaded at all, allow cold-start to bootstrap data if(ArraySize(g_gate_strat)==0) { reason = "cold_start"; return true; } // Fallback 1: aggregate by strategy+symbol across all timeframes int total=0; double wins=0.0; double sumR=0.0; double pf_sum=0.0; int pf_cnt=0; double worst_dd=0.0; for(int j=0;jworst_dd) worst_dd=g_gate_dd[j]; } if(total>0) { double wr = (total>0? wins/total : 0.0); double avgR = (total>0? sumR/total : 0.0); double pf = (pf_cnt>0? pf_sum/pf_cnt : 0.0); double dd = worst_dd; if(total < GateMinTrades) { reason = "min_trades_agg_sym"; return false; } if(wr < GateMinWinRate) { reason = "win_rate_agg_sym"; return false; } if(avgR < GateMinExpectancyR) { reason = "expectancy_agg_sym"; return false; } if(dd > GateMaxDrawdownR) { reason = "drawdown_agg_sym"; return false; } if(pf < GateMinProfitFactor) { reason = "profit_factor_agg_sym"; return false; } // Policy check for aggregated slice (if available) if(UsePolicyGating && g_policy_loaded) { double ppol = GetPolicyProb(strategy, symbol, timeframe); if(ppol>=0.0 && ppol < g_policy_min_conf) { reason = "policy_min_conf_agg_sym"; return false; } } reason = "agg_sym_ok"; return true; } // Fallback 2: aggregate by strategy across all symbols/timeframes total=0; wins=0.0; sumR=0.0; pf_sum=0.0; pf_cnt=0; worst_dd=0.0; for(int j=0;jworst_dd) worst_dd=g_gate_dd[j]; } if(total>0) { double wr = (total>0? wins/total : 0.0); double avgR = (total>0? sumR/total : 0.0); double pf = (pf_cnt>0? pf_sum/pf_cnt : 0.0); double dd = worst_dd; if(total < GateMinTrades) { reason = "min_trades_agg_strat"; return false; } if(wr < GateMinWinRate) { reason = "win_rate_agg_strat"; return false; } if(avgR < GateMinExpectancyR) { reason = "expectancy_agg_strat"; return false; } if(dd > GateMaxDrawdownR) { reason = "drawdown_agg_strat"; return false; } if(pf < GateMinProfitFactor) { reason = "profit_factor_agg_strat"; return false; } // Policy check for aggregated strategy (if available) if(UsePolicyGating && g_policy_loaded) { double ppol = GetPolicyProb(strategy, symbol, timeframe); if(ppol>=0.0 && ppol < g_policy_min_conf) { reason = "policy_min_conf_agg_strat"; return false; } } reason = "agg_strat_ok"; return true; } // If slice not found and gating disabled, allow if(!UseInsightsGating) return true; // Else, no exact slice exists. Exploration Mode: allow only when no slice, with day+week caps if(ExploreOnNoSlice) { // Strict policy: allow exploration ONLY when no slice exists bool has_slice = HasSlice(strategy, symbol, timeframe); if(has_slice) { reason = "slice_exists"; return false; } string key = SliceKey(strategy, symbol, timeframe); int used_w = GetExploreCount(key); int used_d = GetExploreCountDay(key); if(ExploreMaxPerSlicePerDay > 0 && used_d >= ExploreMaxPerSlicePerDay) { reason = "explore_cap_day"; return false; } if(ExploreMaxPerSlice > 0 && used_w >= ExploreMaxPerSlice) { reason = "explore_cap_week"; return false; } if(!no_side_effects) g_explore_pending_key = key; reason = "explore_allow"; return true; } reason = "no_slice"; return false; } // --- Evaluation and execution orchestrator void EvaluateAndMaybeExecute(const TradeOrder &order) { if(g_eval_busy) return; // prevent reentry g_eval_busy = true; ulong t0 = GetTickCount(); EnsureSessionRollover(); EnsureTelemetry(); EnsurePM(); EnsureSessionManager(); EnsureCorrelationManager(); EnsureVolatilitySizer(); // Shadow gates (diagnostic only) if(NoConstraintsMode && TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) { int lat=0; string r; r=""; bool s_ok = SpreadAllowed(r); lat=(int)(GetTickCount()-t0); (*g_telemetry).LogGatingShadow(order.strategy_name, _Symbol, (int)_Period, "spread", s_ok, (s_ok?"ok":r)+";p6_latency="+IntegerToString(lat)+"ms", true); r=""; bool ses_ok = SessionAllowed(r); lat=(int)(GetTickCount()-t0); (*g_telemetry).LogGatingShadow(order.strategy_name, _Symbol, (int)_Period, "session", ses_ok, (ses_ok?"ok":r)+";p6_latency="+IntegerToString(lat)+"ms", true); r=""; bool r_ok = RiskAllowed(r); lat=(int)(GetTickCount()-t0); (*g_telemetry).LogGatingShadow(order.strategy_name, _Symbol, (int)_Period, "risk", r_ok, (r_ok?"ok":r)+";p6_latency="+IntegerToString(lat)+"ms", true); r=""; bool news_ok = NewsAllowed(r); lat=(int)(GetTickCount()-t0); (*g_telemetry).LogGatingShadow(order.strategy_name, _Symbol, (int)_Period, "news", news_ok, (news_ok?"ok":r)+";p6_latency="+IntegerToString(lat)+"ms", true); r=""; bool promo_ok = PromotionAllowed(r); lat=(int)(GetTickCount()-t0); (*g_telemetry).LogGatingShadow(order.strategy_name, _Symbol, (int)_Period, "promotion", promo_ok, (promo_ok?"ok":r)+";p6_latency="+IntegerToString(lat)+"ms", true); r=""; bool regime_ok = RegimeAllowed(r); lat=(int)(GetTickCount()-t0); (*g_telemetry).LogGatingShadow(order.strategy_name, _Symbol, (int)_Period, "regime", regime_ok, (regime_ok?"ok":r)+";p6_latency="+IntegerToString(lat)+"ms", true); r=""; bool cool_ok = CircuitCooldownAllowed(r); lat=(int)(GetTickCount()-t0); (*g_telemetry).LogGatingShadow(order.strategy_name, _Symbol, (int)_Period, "circuit_cooldown", cool_ok, (cool_ok?"ok":r)+";p6_latency="+IntegerToString(lat)+"ms", true); // Shadow insights gating (no side effects, ignore NoConstraints bypass) if(UseInsightsGating) { string rshadow=""; bool allow_shadow = Insights_Allow(order.strategy_name, _Symbol, (int)_Period, rshadow, true, true); (*g_telemetry).LogGatingShadow(order.strategy_name, _Symbol, (int)_Period, "insights", allow_shadow, rshadow, true); } } // Early gates (fail-closed unless NoConstraintsMode) if(!NoConstraintsMode) { // Spread gate string r_spread_early=""; if(!SpreadAllowed(r_spread_early)) { if(ShouldLog(LOG_INFO)) PrintFormat("GATE: blocked %s on %s/%s reason=%s (spread)", order.strategy_name, _Symbol, EnumToString(_Period), r_spread_early); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogGating(_Symbol, (int)_Period, order.strategy_name, "spread", false, r_spread_early+";p6_latency="+IntegerToString((int)(GetTickCount()-t0))+"ms"); g_eval_busy = false; return; } // Session gate string r_sess_early=""; if(!SessionAllowed(r_sess_early)) { if(ShouldLog(LOG_INFO)) PrintFormat("GATE: blocked %s on %s/%s reason=%s (session)", order.strategy_name, _Symbol, EnumToString(_Period), r_sess_early); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogGating(_Symbol, (int)_Period, order.strategy_name, "session", false, r_sess_early+";p6_latency="+IntegerToString((int)(GetTickCount()-t0))+"ms"); g_eval_busy = false; return; } string rr=""; if(!RiskAllowed(rr)) { if(ShouldLog(LOG_INFO)) PrintFormat("GATE: blocked %s on %s/%s reason=%s (eq=%.2f base=%.2f high=%.2f ml=%.2f)", order.strategy_name, _Symbol, EnumToString(_Period), rr, AccountInfoDouble(ACCOUNT_EQUITY), g_session_equity_start, g_equity_highwater, AccountInfoDouble(ACCOUNT_MARGIN_LEVEL)); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogGating(_Symbol, (int)_Period, order.strategy_name, "risk", false, rr+";p6_latency="+IntegerToString((int)(GetTickCount()-t0))+"ms"); g_eval_busy = false; return; } string r_news_early=""; if(!NewsAllowed(r_news_early)) { if(ShouldLog(LOG_INFO)) PrintFormat("GATE: blocked %s on %s/%s reason=%s (news)", order.strategy_name, _Symbol, EnumToString(_Period), r_news_early); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogGating(_Symbol, (int)_Period, order.strategy_name, "news", false, r_news_early+";p6_latency="+IntegerToString((int)(GetTickCount()-t0))+"ms"); g_eval_busy = false; return; } string r_promo_early=""; if(!PromotionAllowed(r_promo_early)) { if(ShouldLog(LOG_INFO)) PrintFormat("GATE: blocked %s on %s/%s reason=%s (promotion)", order.strategy_name, _Symbol, EnumToString(_Period), r_promo_early); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogGating(_Symbol, (int)_Period, order.strategy_name, "promotion", false, r_promo_early+";p6_latency="+IntegerToString((int)(GetTickCount()-t0))+"ms"); g_eval_busy = false; return; } string r_regime_early=""; if(!RegimeAllowed(r_regime_early)) { if(ShouldLog(LOG_INFO)) PrintFormat("GATE: blocked %s on %s/%s reason=%s (regime)", order.strategy_name, _Symbol, EnumToString(_Period), r_regime_early); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogGating(_Symbol, (int)_Period, order.strategy_name, "regime", false, r_regime_early+";p6_latency="+IntegerToString((int)(GetTickCount()-t0))+"ms"); g_eval_busy = false; return; } string r_cool_early=""; if(!CircuitCooldownAllowed(r_cool_early)) { if(ShouldLog(LOG_INFO)) PrintFormat("GATE: blocked %s on %s/%s reason=%s (circuit_cooldown)", order.strategy_name, _Symbol, EnumToString(_Period), r_cool_early); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogGating(_Symbol, (int)_Period, order.strategy_name, "circuit_cooldown", false, r_cool_early+";p6_latency="+IntegerToString((int)(GetTickCount()-t0))+"ms"); g_eval_busy = false; return; } } // Insights/Policy gate (fail-closed unless NoConstraintsMode) if(!NoConstraintsMode) { // Check IncrementalInsightEngine for auto-promoted strategies if(CheckPointer(g_insight_engine) != POINTER_INVALID) { if(!g_insight_engine.IsStrategyApproved(order.strategy_name, _Symbol, (int)_Period)) { // Strategy not yet promoted - block trade if(ShouldLog(LOG_INFO)) PrintFormat("[INSIGHTS] blocked %s on %s/%s reason=not_approved", order.strategy_name, _Symbol, EnumToString(_Period)); g_eval_busy=false; return; } } string gate_reason=""; bool ins_ok = Insights_Allow(order.strategy_name, _Symbol, (int)_Period, gate_reason); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogGating(_Symbol, (int)_Period, order.strategy_name, "insights", ins_ok, (ins_ok?"ok":gate_reason)+";p6_latency="+IntegerToString((int)(GetTickCount()-t0))+"ms"); if(!ins_ok) { if(ShouldLog(LOG_INFO)) PrintFormat("[INSIGHTS] blocked %s on %s/%s reason=%s", order.strategy_name, _Symbol, EnumToString(_Period), gate_reason); g_eval_busy=false; return; } } // Correlation gate string r_corr=""; bool corr_ok = P5_CorrPruneAllow(order, r_corr); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogGating(_Symbol, (int)_Period, order.strategy_name, "corr", corr_ok, (corr_ok?"ok":r_corr)+";p6_latency="+IntegerToString((int)(GetTickCount()-t0))+"ms"); if(!corr_ok) { if(ShouldLog(LOG_INFO)) PrintFormat("[CORR] blocked %s on %s/%s reason=%s", order.strategy_name, _Symbol, EnumToString(_Period), r_corr); g_eval_busy=false; return; } // Final RISK4 gates just before execution string r_rk_exec=""; if(!RiskAllowed(r_rk_exec)) { if(ShouldLog(LOG_INFO)) PrintFormat("[RISK4] blocked %s on %s/%s reason=%s", order.strategy_name, _Symbol, EnumToString(_Period), r_rk_exec); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogGating(_Symbol, (int)_Period, order.strategy_name, "risk4", false, r_rk_exec+";p6_latency="+IntegerToString((int)(GetTickCount()-t0))+"ms"); g_eval_busy = false; return; } string r_spread_exec=""; if(!SpreadAllowed(r_spread_exec)) { if(ShouldLog(LOG_INFO)) PrintFormat("[RISK4] blocked %s on %s/%s reason=%s (spread)", order.strategy_name, _Symbol, EnumToString(_Period), r_spread_exec); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogGating(_Symbol, (int)_Period, order.strategy_name, "risk4_spread", false, r_spread_exec+";p6_latency="+IntegerToString((int)(GetTickCount()-t0))+"ms"); g_eval_busy = false; return; } string r_sess_exec=""; if(!SessionAllowed(r_sess_exec)) { if(ShouldLog(LOG_INFO)) PrintFormat("[RISK4] blocked %s on %s/%s reason=%s (session)", order.strategy_name, _Symbol, EnumToString(_Period), r_sess_exec); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogGating(_Symbol, (int)_Period, order.strategy_name, "risk4_session", false, r_sess_exec+";p6_latency="+IntegerToString((int)(GetTickCount()-t0))+"ms"); g_eval_busy = false; return; } string r_news_exec=""; if(!NewsAllowed(r_news_exec)) { if(ShouldLog(LOG_INFO)) PrintFormat("[RISK4] blocked %s on %s/%s reason=%s (news)", order.strategy_name, _Symbol, EnumToString(_Period), r_news_exec); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogGating(_Symbol, (int)_Period, order.strategy_name, "risk4_news", false, r_news_exec+";p6_latency="+IntegerToString((int)(GetTickCount()-t0))+"ms"); g_eval_busy = false; return; } string r_promo_exec=""; if(!PromotionAllowed(r_promo_exec)) { if(ShouldLog(LOG_INFO)) PrintFormat("[RISK4] blocked %s on %s/%s reason=%s (promotion)", order.strategy_name, _Symbol, EnumToString(_Period), r_promo_exec); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogGating(_Symbol, (int)_Period, order.strategy_name, "risk4_promotion", false, r_promo_exec+";p6_latency="+IntegerToString((int)(GetTickCount()-t0))+"ms"); g_eval_busy = false; return; } string r_regime_exec=""; if(!RegimeAllowed(r_regime_exec)) { if(ShouldLog(LOG_INFO)) PrintFormat("[RISK4] blocked %s on %s/%s reason=%s (regime)", order.strategy_name, _Symbol, EnumToString(_Period), r_regime_exec); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogGating(_Symbol, (int)_Period, order.strategy_name, "risk4_regime", false, r_regime_exec+";p6_latency="+IntegerToString((int)(GetTickCount()-t0))+"ms"); g_eval_busy = false; return; } string r_cool_exec=""; if(!CircuitCooldownAllowed(r_cool_exec)) { if(ShouldLog(LOG_INFO)) PrintFormat("[RISK4] blocked %s on %s/%s reason=%s (circuit_cooldown)", order.strategy_name, _Symbol, EnumToString(_Period), r_cool_exec); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogGating(_Symbol, (int)_Period, order.strategy_name, "risk4_circuit_cooldown", false, r_cool_exec+";p6_latency="+IntegerToString((int)(GetTickCount()-t0))+"ms"); g_eval_busy = false; return; } if(ShouldLog(LOG_INFO)) PrintFormat("[READY] %s on %s/%s passed gates", order.strategy_name, _Symbol, EnumToString(_Period)); // Apply policy-driven scaling (if enabled and available) using a local copy of order TradeOrder req = order; double ppol_now = (UsePolicyGating && g_policy_loaded ? GetPolicyProb(req.strategy_name, _Symbol, (int)_Period) : -1.0); ApplyPolicyScaling(req, _Symbol, (int)_Period, ppol_now); // --- FR-06 Volatility Position Sizing (set req.lots) --- if(UseVolatilitySizer && CheckPointer(g_volatility_sizer)!=POINTER_INVALID) { double stop_pts = 0.0; double bid=0.0, ask=0.0; SymbolInfoDouble(_Symbol, SYMBOL_BID, bid); SymbolInfoDouble(_Symbol, SYMBOL_ASK, ask); if(req.stop_loss>0.0) { if(req.order_type==ORDER_TYPE_BUY || req.order_type==ORDER_TYPE_BUY_LIMIT || req.order_type==ORDER_TYPE_BUY_STOP) { double ref = (req.price>0.0? req.price : ask); stop_pts = MathMax(0.0, (ref - req.stop_loss)/_Point); } else { double ref = (req.price>0.0? req.price : bid); stop_pts = MathMax(0.0, (req.stop_loss - ref)/_Point); } } double atr_pct=0.0; string vs_r1=""; double mult_hint = (*g_volatility_sizer).CalculateSizeMultiplier(atr_pct, vs_r1); double mult_final=1.0; string vs_r2=""; double sized = (*g_volatility_sizer).CalculatePositionSize(BaseLotSize, stop_pts, mult_final, vs_r2); if(sized>0.0) req.lots = sized; if(TelemetryEnabled && CheckPointer(g_tel_standard)!=POINTER_INVALID) (*g_tel_standard).LogVolatilitySizingEvent(_Symbol, (int)_Period, BaseLotSize, mult_final, (sized>0.0?sized:BaseLotSize), atr_pct); } // Execute the trade using TradeManager CTradeManager tm(_Symbol, 0.0 /* default to symbol min lot if order.lots==0 */, (int)MagicNumber); bool exec_ok = tm.ExecuteOrder(req); uint retcode = tm.ResultRetcode(); ulong deal_id = tm.ResultDeal(); ulong order_id = tm.ResultOrder(); if(exec_ok) { g_last_trade_placed = TimeCurrent(); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogTradeExecuted(order.strategy_name, _Symbol, (int)_Period, (int)retcode, deal_id, order_id); // Exploration accounting: count only on successful execution if(g_explore_pending_key!="") { IncExploreCount(g_explore_pending_key); if(ShouldLog(LOG_INFO)) PrintFormat("Exploration used for %s -> %d/%d", g_explore_pending_key, GetExploreCount(g_explore_pending_key), ExploreMaxPerSlice); g_explore_pending_key = ""; } } else { if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) (*g_telemetry).LogTradeFailed(order.strategy_name, _Symbol, (int)_Period, (int)retcode); // Ensure exploration pending key does not leak across failed executions if(g_explore_pending_key!="") { if(ShouldLog(LOG_INFO)) PrintFormat("Exploration not used due to failed execution for %s", g_explore_pending_key); g_explore_pending_key = ""; } } g_eval_busy = false; } // --- Terminal event handlers int OnInit() { EnsureSessionRollover(); EnsureTelemetry(); EnsurePM(); EnsureSessionManager(); EnsureCorrelationManager(); EnsureVolatilitySizer(); // Initialize Incremental Insight Engine for real-time strategy approval g_insight_engine = new CIncrementalInsightEngine(); if(CheckPointer(g_insight_engine) != POINTER_INVALID) { if(ShouldLog(LOG_INFO)) Print("[LiveEA] Incremental Insight Engine initialized"); } // Load exploration counters and gating caches LoadExploreCounts(); LoadExploreCountsDay(); bool gate_loaded = Insights_Load(); if(ShouldLog(LOG_INFO)) PrintFormat("Insights gating cache load: %s", (gate_loaded?"ok":"fail")); if(UsePolicyGating) { bool pol_ok = Policy_Load(); if(ShouldLog(LOG_INFO)) PrintFormat("Policy cache load: %s", (pol_ok?"ok":"fail")); } // Load news blackout windows if(UseNewsFilter && NewsUseFile) { // Reset and load ArrayResize(g_news_key,0); ArrayResize(g_news_from,0); ArrayResize(g_news_to,0); ArrayResize(g_news_impact,0); int h = FileOpen(NewsFileRelPath, FILE_READ|FILE_CSV|FILE_ANSI|FILE_COMMON, ','); if(h!=INVALID_HANDLE) { bool first=true; while(!FileIsEnding(h)) { string k = FileReadString(h); if(k=="" && FileIsEnding(h)) break; string sfrom = FileReadString(h); string sto = FileReadString(h); string simp = FileReadString(h); if(first && (StringFind(k, "key", 0)==0)) { first=false; continue; } first=false; datetime tfrom = StringToTime(sfrom); datetime tto = StringToTime(sto); int impact = (int)StringToInteger(simp); int n = ArraySize(g_news_key); ArrayResize(g_news_key,n+1); ArrayResize(g_news_from,n+1); ArrayResize(g_news_to,n+1); ArrayResize(g_news_impact,n+1); StringToUpper(k); g_news_key[n]=k; g_news_from[n]=tfrom; g_news_to[n]=tto; g_news_impact[n]=impact; } FileClose(h); if(ShouldLog(LOG_INFO)) PrintFormat("News blackout windows loaded: %d", ArraySize(g_news_key)); } else { if(ShouldLog(LOG_WARN)) PrintFormat("News filter: cannot open %s (Common). Err=%d", NewsFileRelPath, GetLastError()); } } // GATE summary string gsum = StringFormat("insights=%s policy=%s(min=%.3f) explore=%s caps(slice=%d/day=%d)", (UseInsightsGating?"on":"off"), (UsePolicyGating?"on":"off"), g_policy_min_conf, (ExploreOnNoSlice?"on":"off"), ExploreMaxPerSlice, ExploreMaxPerSlicePerDay); if(ShouldLog(LOG_INFO)) PrintFormat("[GATE] %s", gsum); if(ShouldLog(LOG_INFO)) Print("LiveEA initialized"); // Start watcher timer for insights ready signal if(InsightsAutoReload && InsightsReadyPollSec>0) EventSetTimer(InsightsReadyPollSec); // Bootstrap: ensure insights are fresh or request a rebuild immediately MaybeEnsureInsights(); return(INIT_SUCCEEDED); } void OnDeinit(const int reason) { if(CheckPointer(g_position_manager)!=POINTER_INVALID) { delete g_position_manager; g_position_manager=NULL; } if(CheckPointer(g_telemetry)!=POINTER_INVALID) { delete g_telemetry; g_telemetry=NULL; } if(CheckPointer(g_tel_standard)!=POINTER_INVALID) { delete g_tel_standard; g_tel_standard=NULL; } if(CheckPointer(g_session_manager)!=POINTER_INVALID) { delete g_session_manager; g_session_manager=NULL; } if(CheckPointer(g_correlation_manager)!=POINTER_INVALID) { delete g_correlation_manager; g_correlation_manager=NULL; } if(CheckPointer(g_volatility_sizer)!=POINTER_INVALID) { delete g_volatility_sizer; g_volatility_sizer=NULL; } if(CheckPointer(g_insight_engine)!=POINTER_INVALID) { delete g_insight_engine; g_insight_engine=NULL; } if(InsightsAutoReload && InsightsReadyPollSec>0) EventKillTimer(); } void OnTick() { // Strategy orchestration is external; gates are available via EvaluateAndMaybeExecute(order) // Early gating before any selection/insights logic if(!EvaluateAndMaybeExecute_PreSelector()) { if(ShouldLog(LOG_INFO)) Print("[EARLY] gates blocked tick"); return; } // If an external orchestrator performs selection and prepares orders, it should // call EvaluateAndMaybeExecute_PreExecute() just before execution. We call it here // as a placeholder to ensure telemetry is emitted even when no orders are placed. EvaluateAndMaybeExecute_PreExecute(); } // --- Phase 6: Gate orchestration with shadow telemetry ulong NowMs(){ return (ulong)GetTickCount(); } void LogGate(const string tag, const bool allowed, const string phase, const ulong t0) { int latency = (int)(NowMs() - t0); if(ShouldLog(LOG_INFO)) PrintFormat("[%s] %s latency_ms=%d", tag, (allowed?"allow":"block"), latency); if(TelemetryEnabled && CheckPointer(g_telemetry)!=POINTER_INVALID) { string det = StringFormat("phase=%s p6_latency_ms=%d", phase, latency); (*g_telemetry).LogEvent(_Symbol, (int)_Period, "gate", StringFormat("%s_%s", tag, (allowed?"allow":"block")), det); } // Standardized telemetry if(TelemetryEnabled && CheckPointer(g_tel_standard)!=POINTER_INVALID) { (*g_tel_standard).LogGateEvent(_Symbol, (int)_Period, tag, allowed, phase, latency, "n/a"); } } // Early gates before selector/insights bool EarlyGatesAllow() { string reason; ulong t0; // NEWS t0=NowMs(); bool news_ok = NewsAllowed(reason); LogGate("NEWS", news_ok, "early", t0); if(!news_ok && !NoConstraintsMode) return false; // PROMO t0=NowMs(); bool promo_ok = PromotionAllowed(reason); LogGate("PROMO", promo_ok, "early", t0); if(!promo_ok && !NoConstraintsMode) return false; // REGIME t0=NowMs(); bool reg_ok = RegimeAllowed(reason); LogGate("REGIME", reg_ok, "early", t0); if(!reg_ok && !NoConstraintsMode) return false; // CIRCUIT cooldown t0=NowMs(); bool circ_ok = CircuitCooldownAllowed(reason); LogGate("CIRCUIT", circ_ok, "early", t0); // SESSION (diagnostic in early; block only if NoConstraintsMode is false) EnsureSessionManager(); if(UseSessionManager && CheckPointer(g_session_manager)!=POINTER_INVALID) { t0=NowMs(); bool sess_ok = g_session_manager.IsSessionAllowed(reason); LogGate("SESSION", sess_ok, "early", t0); if(!sess_ok && !NoConstraintsMode) return false; } // CORRELATION (diagnostic/block depending on NoConstraintsMode) EnsureCorrelationManager(); if(UseCorrelationManager && CheckPointer(g_correlation_manager)!=POINTER_INVALID) { double max_corr; t0=NowMs(); bool corr_ok = g_correlation_manager.CheckCorrelationLimits(reason, max_corr); LogGate("CORR", corr_ok, "early", t0); if(!corr_ok && !NoConstraintsMode) return false; } return true; } // Final risk4 gates just before execution bool Risk4GatesAllow() { string reason; ulong t0; t0=NowMs(); bool news_ok = NewsAllowed(reason); LogGate("NEWS", news_ok, "risk4", t0); if(!news_ok) return false; t0=NowMs(); bool promo_ok= PromotionAllowed(reason); LogGate("PROMO", promo_ok,"risk4", t0); if(!promo_ok) return false; t0=NowMs(); bool reg_ok = RegimeAllowed(reason); LogGate("REGIME", reg_ok, "risk4", t0); if(!reg_ok) return false; t0=NowMs(); bool circ_ok = CircuitCooldownAllowed(reason); LogGate("CIRCUIT", circ_ok,"risk4", t0); if(!circ_ok) return false; // FR-02 Session gate EnsureSessionManager(); if(UseSessionManager && CheckPointer(g_session_manager)!=POINTER_INVALID) { t0=NowMs(); bool sess_ok = g_session_manager.IsSessionAllowed(reason); LogGate("SESSION", sess_ok, "risk4", t0); if(!sess_ok) return false; } // FR-05 Correlation gate EnsureCorrelationManager(); if(UseCorrelationManager && CheckPointer(g_correlation_manager)!=POINTER_INVALID) { double max_corr; t0=NowMs(); bool corr_ok = g_correlation_manager.CheckCorrelationLimits(reason, max_corr); LogGate("CORR", corr_ok, "risk4", t0); if(!corr_ok) return false; } // FR-04 Position Manager gate EnsurePM(); if(UsePositionManager && CheckPointer(g_position_manager)!=POINTER_INVALID) { int open_positions = PositionsTotal(); bool pm_ok = (PMMaxOpenPositions<=0 ? true : (open_positions < PMMaxOpenPositions)); t0=NowMs(); LogGate("PM", pm_ok, "risk4", t0); if(!pm_ok) return false; } return true; } // Public function to be called by orchestrator before/after selector/insights bool EvaluateAndMaybeExecute_PreSelector() { return EarlyGatesAllow(); } bool EvaluateAndMaybeExecute_PreExecute() { return Risk4GatesAllow(); } // --- Insights ready/reload logic --- bool IsInsightsFresh(const int fresh_minutes) { string ip = "DualEA\\insights.json"; long ex_i = FileGetInteger(ip, FILE_EXISTS, true); if(ex_i==0) return false; datetime ti = (datetime)FileGetInteger(ip, FILE_MODIFY_DATE, true); if(fresh_minutes<=0) return true; // any existing is fine return ((TimeCurrent() - ti) <= (fresh_minutes*60)); } void RequestInsightsReload() { string path = "DualEA\\insights.reload"; if(FileIsExist(path, FILE_COMMON)) FileDelete(path, FILE_COMMON); int h = FileOpen(path, FILE_WRITE|FILE_TXT|FILE_ANSI|FILE_COMMON); if(h!=INVALID_HANDLE) { FileWrite(h, IntegerToString((int)TimeCurrent())); FileClose(h); if(ShouldLog(LOG_INFO)) Print("Insights reload requested"); } else { if(ShouldLog(LOG_WARN)) PrintFormat("Insights reload: cannot create %s (err=%d)", path, GetLastError()); } } void CheckInsightsReady() { string rdy = "DualEA\\insights.ready"; int h = FileOpen(rdy, FILE_READ|FILE_TXT|FILE_ANSI|FILE_COMMON); if(h==INVALID_HANDLE) return; // Ready present -> consume and reload FileClose(h); if(!FileDelete(rdy, FILE_COMMON)) { // still proceed, but log if(ShouldLog(LOG_WARN)) PrintFormat("Insights ready: cannot delete %s (err=%d)", rdy, GetLastError()); } bool ok = Insights_Load(); if(ShouldLog(LOG_INFO)) PrintFormat("Insights ready detected: reload %s", (ok?"ok":"fail")); g_waiting_insights = false; } void MaybeEnsureInsights() { if(!InsightsAutoReload) return; if(IsInsightsFresh(InsightsLiveFreshMinutes)) return; if(!g_waiting_insights) { RequestInsightsReload(); g_waiting_insights = true; } } void OnTimer() { if(!InsightsAutoReload) return; MaybeEnsureInsights(); CheckInsightsReady(); }