| //+------------------------------------------------------------------+
|
| //| CInsightGateBridge.mqh - Real-time O(1) Gate-Insights Integration |
|
| //| P1-2: Bridge IncrementalInsightEngine with GateManager |
|
| //| Eliminates file I/O latency with in-memory O(1) lookups |
|
| //+------------------------------------------------------------------+
|
| #ifndef CINSIGHTGATEBRIDGE_MQH
|
| #define CINSIGHTGATEBRIDGE_MQH
|
|
|
| #include "CSQLiteKnowledgeBase.mqh"
|
|
|
| //+------------------------------------------------------------------+
|
| //| Real-time Strategy Metrics Structure |
|
| //+------------------------------------------------------------------+
|
| struct SStrategyMetrics
|
| {
|
| string strategy_name;
|
| string symbol;
|
| datetime last_updated;
|
|
|
| // Core metrics
|
| int total_trades;
|
| int win_count;
|
| int loss_count;
|
| double win_rate;
|
| double profit_factor;
|
| double expectancy;
|
| double avg_pnl;
|
| double max_drawdown;
|
| double r_multiple_avg;
|
|
|
| // Recent performance (last N trades)
|
| double recent_win_rate;
|
| int recent_trades_count;
|
|
|
| // Regime-specific performance
|
| string best_regime;
|
| string worst_regime;
|
| double regime_performance[5]; // For 5 different regimes
|
|
|
| void Reset()
|
| {
|
| strategy_name = "";
|
| symbol = "";
|
| last_updated = 0;
|
| total_trades = 0;
|
| win_count = 0;
|
| loss_count = 0;
|
| win_rate = 0.0;
|
| profit_factor = 0.0;
|
| expectancy = 0.0;
|
| avg_pnl = 0.0;
|
| max_drawdown = 0.0;
|
| r_multiple_avg = 0.0;
|
| recent_win_rate = 0.0;
|
| recent_trades_count = 0;
|
| best_regime = "";
|
| worst_regime = "";
|
| ArrayInitialize(regime_performance, 0.0);
|
| }
|
| };
|
|
|
| //+------------------------------------------------------------------+
|
| //| Insight-Gate Bridge Class |
|
| //+------------------------------------------------------------------+
|
| class CInsightGateBridge
|
| {
|
| private:
|
| // O(1) lookup tables
|
| SStrategyMetrics m_metrics_cache[];
|
| string m_cache_keys[]; // Composite key: "strategy|symbol"
|
| int m_cache_size;
|
| int m_max_cache_size;
|
|
|
| // Update tracking
|
| datetime m_last_db_sync;
|
| int m_sync_interval_seconds;
|
|
|
| // SQLite KB reference
|
| CSQLiteKnowledgeBase* m_sqlite_kb;
|
| bool m_use_sqlite;
|
|
|
| // Performance tracking
|
| ulong m_lookup_count;
|
| ulong m_cache_hit_count;
|
| double m_avg_lookup_time_us;
|
|
|
| public:
|
| // Constructor
|
| CInsightGateBridge(CSQLiteKnowledgeBase* kb = NULL)
|
| {
|
| m_cache_size = 0;
|
| m_max_cache_size = 100; // Max strategies to cache
|
| m_last_db_sync = 0;
|
| m_sync_interval_seconds = 60; // Sync every minute
|
| m_sqlite_kb = kb;
|
| m_use_sqlite = (kb != NULL);
|
| m_lookup_count = 0;
|
| m_cache_hit_count = 0;
|
| m_avg_lookup_time_us = 0.0;
|
|
|
| ArrayResize(m_metrics_cache, 0);
|
| ArrayResize(m_cache_keys, 0);
|
| }
|
|
|
| // Destructor
|
| ~CInsightGateBridge()
|
| {
|
| // Don't delete m_sqlite_kb - it's owned by caller
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Initialize bridge |
|
| //+------------------------------------------------------------------+
|
| bool Initialize(CSQLiteKnowledgeBase* kb = NULL)
|
| {
|
| if(kb != NULL) m_sqlite_kb = kb;
|
| m_use_sqlite = (m_sqlite_kb != NULL);
|
|
|
| // Pre-load hot strategies
|
| if(m_use_sqlite)
|
| {
|
| PreloadHotStrategies();
|
| }
|
|
|
| Print("[InsightGateBridge] Initialized - O(1) lookup ready");
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Build composite key |
|
| //+------------------------------------------------------------------+
|
| string BuildKey(string strategy, string symbol)
|
| {
|
| return strategy + "|" + symbol;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Find cache index (O(n) but n <= 100) |
|
| //+------------------------------------------------------------------+
|
| int FindCacheIndex(string key)
|
| {
|
| for(int i = 0; i < m_cache_size; i++)
|
| {
|
| if(m_cache_keys[i] == key) return i;
|
| }
|
| return -1;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| O(1) Get metrics with automatic cache refresh |
|
| //+------------------------------------------------------------------+
|
| bool GetStrategyMetrics(string strategy, string symbol, SStrategyMetrics &metrics)
|
| {
|
| ulong start_time = GetMicrosecondCount();
|
|
|
| string key = BuildKey(strategy, symbol);
|
| int idx = FindCacheIndex(key);
|
|
|
| bool cache_hit = (idx >= 0);
|
| bool needs_refresh = false;
|
|
|
| if(cache_hit)
|
| {
|
| // Check if cache entry is stale
|
| datetime age = TimeCurrent() - m_metrics_cache[idx].last_updated;
|
| if(age > m_sync_interval_seconds)
|
| {
|
| needs_refresh = true;
|
| }
|
| else
|
| {
|
| // Cache hit - return cached data
|
| metrics = m_metrics_cache[idx];
|
| m_cache_hit_count++;
|
| }
|
| }
|
|
|
| if(!cache_hit || needs_refresh)
|
| {
|
| // Cache miss or stale - load from database
|
| if(!LoadMetricsFromDB(strategy, symbol, metrics))
|
| {
|
| // Failed to load - return empty metrics
|
| metrics.Reset();
|
| return false;
|
| }
|
|
|
| // Update cache
|
| if(idx >= 0)
|
| {
|
| // Update existing entry
|
| m_metrics_cache[idx] = metrics;
|
| }
|
| else
|
| {
|
| // Add new entry (LRU eviction if needed)
|
| if(m_cache_size >= m_max_cache_size)
|
| {
|
| EvictLRUEntry();
|
| }
|
|
|
| idx = m_cache_size;
|
| ArrayResize(m_metrics_cache, m_cache_size + 1);
|
| ArrayResize(m_cache_keys, m_cache_size + 1);
|
| m_cache_keys[idx] = key;
|
| m_metrics_cache[idx] = metrics;
|
| m_cache_size++;
|
| }
|
| }
|
|
|
| // Update performance metrics
|
| m_lookup_count++;
|
| ulong lookup_time = GetMicrosecondCount() - start_time;
|
| m_avg_lookup_time_us = (m_avg_lookup_time_us * (m_lookup_count - 1) + lookup_time) / m_lookup_count;
|
|
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Load metrics from SQLite database |
|
| //+------------------------------------------------------------------+
|
| bool LoadMetricsFromDB(string strategy, string symbol, SStrategyMetrics &metrics)
|
| {
|
| if(!m_use_sqlite || m_sqlite_kb == NULL) return false;
|
|
|
| // Use SQLite KB to get stats
|
| double win_rate, profit_factor, total_pnl;
|
| int total_trades;
|
|
|
| if(!m_sqlite_kb.GetStrategyStats(strategy, symbol, win_rate, profit_factor, total_pnl, total_trades))
|
| {
|
| return false;
|
| }
|
|
|
| // Populate metrics
|
| metrics.strategy_name = strategy;
|
| metrics.symbol = symbol;
|
| metrics.win_rate = win_rate;
|
| metrics.profit_factor = profit_factor;
|
| metrics.avg_pnl = (total_trades > 0) ? total_pnl / total_trades : 0.0;
|
| metrics.total_trades = total_trades;
|
| metrics.last_updated = TimeCurrent();
|
|
|
| // Get recent trades for recent performance
|
| STradeRecord recent_trades[];
|
| int recent_count = m_sqlite_kb.GetRecentTrades(strategy, symbol, recent_trades, 20);
|
|
|
| if(recent_count > 0)
|
| {
|
| int recent_wins = 0;
|
| double recent_pnl = 0;
|
|
|
| for(int i = 0; i < recent_count; i++)
|
| {
|
| if(recent_trades[i].is_winner) recent_wins++;
|
| recent_pnl += recent_trades[i].pnl;
|
| }
|
|
|
| metrics.recent_win_rate = (double)recent_wins / recent_count;
|
| metrics.recent_trades_count = recent_count;
|
| }
|
|
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Preload historical strategy data into cache |
|
| //+------------------------------------------------------------------+
|
| void PreloadHistoricalData()
|
| {
|
| if(!m_use_sqlite || m_sqlite_kb == NULL)
|
| {
|
| Print("[InsightGateBridge] No SQLite KB available for historical preload");
|
| return;
|
| }
|
|
|
| Print("[InsightGateBridge] Preloading historical strategy data...");
|
|
|
| // Get all strategies from knowledge base
|
| string strategies[];
|
| int strategy_count = m_sqlite_kb.GetUniqueStrategies(strategies);
|
|
|
| if(strategy_count == 0)
|
| {
|
| Print("[InsightGateBridge] No historical strategies found in SQLite KB");
|
|
|
| // CRITICAL: Try to import from CSV if SQLite is empty
|
| Print("[InsightGateBridge] Attempting to import from CSV files...");
|
|
|
| // Try trades CSV files first (actual format used by EA)
|
| int imported = m_sqlite_kb.ImportFromTradesCSV();
|
|
|
| if(imported == 0)
|
| {
|
| // Fallback to legacy knowledge_base.csv
|
| string csv_path = TerminalInfoString(TERMINAL_COMMONDATA_PATH) + "\\Files\\DualEA\\knowledge_base.csv";
|
| imported = m_sqlite_kb.ImportFromCSV(csv_path);
|
| }
|
|
|
| if(imported > 0)
|
| {
|
| Print(StringFormat("[InsightGateBridge] Successfully imported %d trades from CSV", imported));
|
|
|
| // Try again to get strategies after import
|
| strategy_count = m_sqlite_kb.GetUniqueStrategies(strategies);
|
| if(strategy_count == 0)
|
| {
|
| Print("[InsightGateBridge] Still no strategies found after CSV import");
|
| return;
|
| }
|
| }
|
| else
|
| {
|
| Print("[InsightGateBridge] CSV import failed or no CSV data available");
|
| return;
|
| }
|
| }
|
|
|
| Print(StringFormat("[InsightGateBridge] Found %d strategies in KB", strategy_count));
|
|
|
| // Get all symbols from knowledge base
|
| string symbols[];
|
| int symbol_count = m_sqlite_kb.GetUniqueSymbols(symbols);
|
|
|
| if(symbol_count == 0)
|
| {
|
| Print("[InsightGateBridge] No symbols found in KB");
|
| return;
|
| }
|
|
|
| Print(StringFormat("[InsightGateBridge] Found %d symbols in KB", symbol_count));
|
|
|
| // Preload each strategy/symbol combination
|
| int loaded_count = 0;
|
| for(int s = 0; s < strategy_count; s++)
|
| {
|
| for(int sym = 0; sym < symbol_count; sym++)
|
| {
|
| SStrategyMetrics metrics;
|
| if(LoadMetricsFromDB(strategies[s], symbols[sym], metrics))
|
| {
|
| string key = BuildKey(strategies[s], symbols[sym]);
|
|
|
| // Check if already in cache
|
| int existing_idx = FindCacheIndex(key);
|
| if(existing_idx >= 0)
|
| {
|
| // Update existing
|
| m_metrics_cache[existing_idx] = metrics;
|
| }
|
| else
|
| {
|
| // Add new entry
|
| if(m_cache_size >= m_max_cache_size)
|
| {
|
| EvictLRUEntry();
|
| }
|
|
|
| int idx = m_cache_size;
|
| ArrayResize(m_metrics_cache, m_cache_size + 1);
|
| ArrayResize(m_cache_keys, m_cache_size + 1);
|
| m_cache_keys[idx] = key;
|
| m_metrics_cache[idx] = metrics;
|
| m_cache_size++;
|
| }
|
| loaded_count++;
|
| }
|
| }
|
| }
|
|
|
| Print(StringFormat("[InsightGateBridge] Preloaded %d strategy/symbol combinations", loaded_count));
|
| Print(StringFormat("[InsightGateBridge] Cache now contains %d entries", m_cache_size));
|
|
|
| // Update lookup count to reflect preloaded data
|
| m_lookup_count = loaded_count;
|
| m_cache_hit_count = loaded_count; // All preloaded data counts as cache hits
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Preload frequently used strategies |
|
| //+------------------------------------------------------------------+
|
| void PreloadHotStrategies()
|
| {
|
| // Enhanced preload: Load all historical data first
|
| PreloadHistoricalData();
|
|
|
| // Additional hot strategy logic can be added here if needed
|
| Print("[InsightGateBridge] Preloading hot strategies...");
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Evict least recently used entry |
|
| //+------------------------------------------------------------------+
|
| void EvictLRUEntry()
|
| {
|
| if(m_cache_size == 0) return;
|
|
|
| // Find oldest entry
|
| int oldest_idx = 0;
|
| datetime oldest_time = m_metrics_cache[0].last_updated;
|
|
|
| for(int i = 1; i < m_cache_size; i++)
|
| {
|
| if(m_metrics_cache[i].last_updated < oldest_time)
|
| {
|
| oldest_time = m_metrics_cache[i].last_updated;
|
| oldest_idx = i;
|
| }
|
| }
|
|
|
| // Remove oldest entry by swapping with last and resizing
|
| if(oldest_idx < m_cache_size - 1)
|
| {
|
| m_metrics_cache[oldest_idx] = m_metrics_cache[m_cache_size - 1];
|
| m_cache_keys[oldest_idx] = m_cache_keys[m_cache_size - 1];
|
| }
|
|
|
| m_cache_size--;
|
| ArrayResize(m_metrics_cache, m_cache_size);
|
| ArrayResize(m_cache_keys, m_cache_size);
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Fast gate decision using cached insights |
|
| //+------------------------------------------------------------------+
|
| bool FastGateDecision(string strategy, string symbol,
|
| double min_win_rate, double min_profit_factor,
|
| string &block_reason)
|
| {
|
| SStrategyMetrics metrics;
|
|
|
| // O(1) lookup
|
| if(!GetStrategyMetrics(strategy, symbol, metrics))
|
| {
|
| block_reason = "No metrics available";
|
| return false; // Conservative: block if no data
|
| }
|
|
|
| // Check win rate threshold
|
| if(metrics.win_rate < min_win_rate)
|
| {
|
| block_reason = StringFormat("Win rate %.1f%% < %.1f%%",
|
| metrics.win_rate * 100, min_win_rate * 100);
|
| return false;
|
| }
|
|
|
| // Check profit factor
|
| if(metrics.profit_factor < min_profit_factor)
|
| {
|
| block_reason = StringFormat("PF %.2f < %.2f",
|
| metrics.profit_factor, min_profit_factor);
|
| return false;
|
| }
|
|
|
| // Check recent performance degradation
|
| if(metrics.recent_trades_count >= 5 &&
|
| metrics.recent_win_rate < min_win_rate * 0.8)
|
| {
|
| block_reason = StringFormat("Recent WR %.1f%% degraded",
|
| metrics.recent_win_rate * 100);
|
| return false;
|
| }
|
|
|
| return true;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Get confidence score for signal |
|
| //+------------------------------------------------------------------+
|
| double GetSignalConfidence(string strategy, string symbol)
|
| {
|
| SStrategyMetrics metrics;
|
|
|
| if(!GetStrategyMetrics(strategy, symbol, metrics))
|
| {
|
| return 0.5; // Neutral if no data
|
| }
|
|
|
| // Calculate composite confidence score
|
| double confidence = 0.5;
|
|
|
| // Weight by historical win rate
|
| confidence += (metrics.win_rate - 0.5) * 0.3;
|
|
|
| // Weight by profit factor
|
| if(metrics.profit_factor > 1.5)
|
| confidence += 0.1;
|
| else if(metrics.profit_factor < 1.0)
|
| confidence -= 0.1;
|
|
|
| // Weight by recent performance
|
| if(metrics.recent_trades_count >= 5)
|
| {
|
| confidence += (metrics.recent_win_rate - 0.5) * 0.2;
|
| }
|
|
|
| // Normalize
|
| confidence = MathMax(0.0, MathMin(1.0, confidence));
|
|
|
| return confidence;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Update metrics after trade completion |
|
| //+------------------------------------------------------------------+
|
| void OnTradeCompleted(string strategy, string symbol, double pnl, double r_multiple)
|
| {
|
| string key = BuildKey(strategy, symbol);
|
| int idx = FindCacheIndex(key);
|
|
|
| if(idx >= 0)
|
| {
|
| // Update cached metrics incrementally
|
| m_metrics_cache[idx].total_trades++;
|
| if(pnl > 0)
|
| {
|
| m_metrics_cache[idx].win_count++;
|
| m_metrics_cache[idx].recent_win_rate = (m_metrics_cache[idx].recent_win_rate * m_metrics_cache[idx].recent_trades_count + 1.0) / (m_metrics_cache[idx].recent_trades_count + 1);
|
| }
|
| else
|
| {
|
| m_metrics_cache[idx].loss_count++;
|
| m_metrics_cache[idx].recent_win_rate = (m_metrics_cache[idx].recent_win_rate * m_metrics_cache[idx].recent_trades_count + 0.0) / (m_metrics_cache[idx].recent_trades_count + 1);
|
| }
|
|
|
| m_metrics_cache[idx].recent_trades_count = MathMin(20, m_metrics_cache[idx].recent_trades_count + 1);
|
| m_metrics_cache[idx].win_rate = (double)m_metrics_cache[idx].win_count / m_metrics_cache[idx].total_trades;
|
| m_metrics_cache[idx].last_updated = TimeCurrent();
|
|
|
| // Recalculate profit factor periodically
|
| if(m_metrics_cache[idx].total_trades % 10 == 0)
|
| {
|
| LoadMetricsFromDB(strategy, symbol, m_metrics_cache[idx]);
|
| }
|
| }
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Get bridge performance stats |
|
| //+------------------------------------------------------------------+
|
| void GetStats(double &hit_rate, double &avg_lookup_us, int &cache_size)
|
| {
|
| hit_rate = (m_lookup_count > 0) ? (double)m_cache_hit_count / m_lookup_count : 0.0;
|
| avg_lookup_us = m_avg_lookup_time_us;
|
| cache_size = m_cache_size;
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Force full cache refresh |
|
| //+------------------------------------------------------------------+
|
| void RefreshCache()
|
| {
|
| for(int i = 0; i < m_cache_size; i++)
|
| {
|
| string key = m_cache_keys[i];
|
| string parts[];
|
| if(StringSplit(key, '|', parts) == 2)
|
| {
|
| LoadMetricsFromDB(parts[0], parts[1], m_metrics_cache[i]);
|
| }
|
| }
|
| m_last_db_sync = TimeCurrent();
|
| Print("[InsightGateBridge] Cache refreshed");
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Sync with database if needed |
|
| //+------------------------------------------------------------------+
|
| void OnTickSync()
|
| {
|
| if(TimeCurrent() - m_last_db_sync > m_sync_interval_seconds)
|
| {
|
| RefreshCache();
|
| }
|
| }
|
| };
|
|
|
| // Global bridge instance
|
| CInsightGateBridge* g_insight_gate_bridge = NULL;
|
|
|
| //+------------------------------------------------------------------+
|
| //| Initialize Insight-Gate Bridge |
|
| //+------------------------------------------------------------------+
|
| bool InitializeInsightGateBridge(CSQLiteKnowledgeBase* kb = NULL)
|
| {
|
| if(g_insight_gate_bridge != NULL)
|
| {
|
| delete g_insight_gate_bridge;
|
| }
|
|
|
| g_insight_gate_bridge = new CInsightGateBridge(kb);
|
| return g_insight_gate_bridge.Initialize(kb);
|
| }
|
|
|
| //+------------------------------------------------------------------+
|
| //| Shutdown Insight-Gate Bridge |
|
| //+------------------------------------------------------------------+
|
| void ShutdownInsightGateBridge()
|
| {
|
| if(g_insight_gate_bridge != NULL)
|
| {
|
| delete g_insight_gate_bridge;
|
| g_insight_gate_bridge = NULL;
|
| }
|
| }
|
|
|
| #endif // CINSIGHTGATEBRIDGE_MQH
|