| //+------------------------------------------------------------------+
|
| //| LearningBridge.mqh - Bidirectional Learning Between Paper/Live EA |
|
| //+------------------------------------------------------------------+
|
| #ifndef __LEARNINGBRIDGE_MQH__
|
| #define __LEARNINGBRIDGE_MQH__
|
|
|
| #include <Arrays/ArrayObj.mqh>
|
| #include <Files/FileTxt.mqh>
|
|
|
| // Signal decision record with full audit trail
|
| class CSignalDecision : public CObject
|
| {
|
| public:
|
| string signal_id; // Unique identifier
|
| datetime timestamp; // When signal was generated
|
| string symbol; // Trading symbol
|
| int timeframe; // Chart timeframe
|
| double original_price; // Original signal price
|
| int original_type; // Original buy/sell (0=buy, 1=sell)
|
| double original_sl; // Original stop loss
|
| double original_tp; // Original take profit
|
| double original_volume; // Original lot size
|
| // Additional metadata used by PaperEA
|
| string strategy; // Strategy name
|
| int order_type; // ORDER_TYPE_* for execution
|
| int magic_number; // Magic number for tagging
|
| double confidence; // Signal confidence
|
|
|
| // Gate processing results
|
| bool gate_results[8]; // Results for each gate (0-7)
|
| string gate_reasons[8]; // Reasons for gate decisions
|
| double gate_tweaks[8][5]; // Tweaks applied at each gate [gate][param]
|
| datetime gate_timestamps[8]; // When each gate was processed
|
|
|
| // Final execution details
|
| bool executed; // Was trade executed?
|
| double final_price; // Final execution price
|
| double final_sl; // Final stop loss
|
| double final_tp; // Final take profit
|
| double final_volume; // Final lot size
|
| double final_confidence; // Final confidence after gating
|
| bool is_adjusted; // Was signal adjusted by gates?
|
| string block_reason; // Reason for blocking signal
|
| double outcome; // Trade result (profit/loss)
|
| datetime close_time; // When trade closed
|
| datetime execution_time; // Execution time
|
| double execution_price; // Actual filled price
|
|
|
| // Learning data
|
| string market_regime; // Market condition at time
|
| double volatility; // Market volatility
|
| double correlation_score; // Correlation with other pairs
|
| int ml_confidence; // ML model confidence (0-100)
|
|
|
| CSignalDecision()
|
| {
|
| signal_id = "";
|
| timestamp = 0;
|
| symbol = "";
|
| timeframe = 0;
|
| original_price = 0;
|
| original_type = 0;
|
| original_sl = 0;
|
| original_tp = 0;
|
| original_volume = 0;
|
| strategy = "";
|
| order_type = 0;
|
| magic_number = 0;
|
| confidence = 0.0;
|
| executed = false;
|
| final_price = 0;
|
| final_sl = 0;
|
| final_tp = 0;
|
| final_volume = 0;
|
| final_confidence = 0.0;
|
| is_adjusted = false;
|
| block_reason = "";
|
| outcome = 0;
|
| close_time = 0;
|
| execution_time = 0;
|
| execution_price = 0.0;
|
| market_regime = "";
|
| volatility = 0;
|
| correlation_score = 0;
|
| ml_confidence = 0;
|
|
|
| for(int i = 0; i < 8; i++)
|
| {
|
| gate_results[i] = false;
|
| gate_reasons[i] = "";
|
| gate_timestamps[i] = 0;
|
| for(int j = 0; j < 5; j++)
|
| gate_tweaks[i][j] = 0;
|
| }
|
| }
|
| };
|
|
|
| // Learning bridge for bidirectional data flow
|
| class CLearningBridge
|
| {
|
| private:
|
| string m_data_path;
|
| CArrayObj m_decisions;
|
| int m_max_records;
|
| string m_metadata_keys[];
|
| string m_metadata_values[];
|
| bool m_directory_initialized; // Flag to prevent repeated directory checks
|
| string m_current_regime;
|
| double m_regime_confidence;
|
| datetime m_last_regime_update;
|
| public:
|
| CLearningBridge(string data_path, int max_records=10000)
|
| {
|
| m_data_path = data_path;
|
| m_max_records = max_records;
|
| m_directory_initialized = false; // Directory not yet verified
|
|
|
| // Initialize regime tracking
|
| m_current_regime = "unknown";
|
| m_regime_confidence = 0.0;
|
| m_last_regime_update = 0;
|
|
|
| ArrayResize(m_metadata_keys, 0);
|
| ArrayResize(m_metadata_values, 0);
|
|
|
| LoadLearningData();
|
| }
|
|
|
| // Metadata helpers --------------------------------------------------
|
| void MetadataSet(const string key, const string value)
|
| {
|
| if(StringLen(key) == 0)
|
| return;
|
| int count = ArraySize(m_metadata_keys);
|
| for(int i = 0; i < count; i++)
|
| {
|
| if(m_metadata_keys[i] == key)
|
| {
|
| m_metadata_values[i] = value;
|
| return;
|
| }
|
| }
|
| ArrayResize(m_metadata_keys, count + 1);
|
| ArrayResize(m_metadata_values, count + 1);
|
| m_metadata_keys[count] = key;
|
| m_metadata_values[count] = value;
|
| }
|
|
|
| string MetadataGet(const string key, const string def_value = "") const
|
| {
|
| int count = ArraySize(m_metadata_keys);
|
| for(int i = 0; i < count; i++)
|
| {
|
| if(m_metadata_keys[i] == key)
|
| return m_metadata_values[i];
|
| }
|
| return def_value;
|
| }
|
|
|
| bool MetadataHas(const string key) const
|
| {
|
| int count = ArraySize(m_metadata_keys);
|
| for(int i = 0; i < count; i++)
|
| {
|
| if(m_metadata_keys[i] == key)
|
| return true;
|
| }
|
| return false;
|
| }
|
|
|
| void MetadataClear()
|
| {
|
| ArrayResize(m_metadata_keys, 0);
|
| ArrayResize(m_metadata_values, 0);
|
| }
|
|
|
| // Record a decision (alias for RecordSignal for compatibility)
|
| void RecordDecision(CSignalDecision &decision)
|
| {
|
| RecordSignal(decision);
|
| }
|
|
|
| // Get optimal threshold for a specific gate
|
| double GetOptimalThreshold(const string &gate_name)
|
| {
|
| // Default thresholds by gate name
|
| if(gate_name == "SignalRinse") return 0.7;
|
| if(gate_name == "MarketSoap") return 0.02;
|
| if(gate_name == "StrategyScrub") return 0.55;
|
| if(gate_name == "RiskWash") return 0.02; // 2% risk
|
| if(gate_name == "PerformanceWax") return 0.6; // 60% win rate
|
| if(gate_name == "MLPolish") return 0.7; // 70% confidence
|
| if(gate_name == "LiveClean") return 0.001; // Max spread ratio
|
| if(gate_name == "FinalVerify") return 0.0; // No threshold for final verify
|
|
|
| return 0.5; // Default threshold
|
| }
|
|
|
| // Update market regime based on current market conditions
|
| void UpdateMarketRegime()
|
| {
|
| // Advanced market regime detection using multiple indicators
|
| string current_symbol = Symbol();
|
| ENUM_TIMEFRAMES current_tf = Period();
|
|
|
| // Initialize regime as unknown
|
| m_current_regime = "unknown";
|
| double regime_confidence = 0.0;
|
|
|
| // Factor 1: Trend strength using ADX
|
| int adx_handle = iADX(current_symbol, current_tf, 14);
|
| double adx_buffer[1];
|
| if(CopyBuffer(adx_handle, 0, 0, 1, adx_buffer) == 1)
|
| {
|
| double adx_value = adx_buffer[0];
|
| if(adx_value > 25)
|
| {
|
| m_current_regime = "trending";
|
| regime_confidence += 0.3;
|
| }
|
| else if(adx_value < 15)
|
| {
|
| m_current_regime = "ranging";
|
| regime_confidence += 0.2;
|
| }
|
| }
|
|
|
| // Factor 2: Volatility analysis using ATR
|
| int atr_handle = iATR(current_symbol, current_tf, 14);
|
| double atr_buffer[1];
|
| if(CopyBuffer(atr_handle, 0, 0, 1, atr_buffer) == 1)
|
| {
|
| double current_price = SymbolInfoDouble(current_symbol, SYMBOL_BID);
|
| double volatility_pct = (atr_buffer[0] / current_price) * 100.0;
|
|
|
| if(volatility_pct > 2.0) // High volatility
|
| {
|
| if(m_current_regime == "trending")
|
| m_current_regime = "volatile_trending";
|
| else
|
| m_current_regime = "volatile";
|
| regime_confidence += 0.25;
|
| }
|
| else if(volatility_pct < 0.5) // Low volatility
|
| {
|
| if(m_current_regime == "ranging")
|
| m_current_regime = "low_vol_ranging";
|
| else
|
| m_current_regime = "consolidation";
|
| regime_confidence += 0.2;
|
| }
|
| }
|
|
|
| // Factor 3: Market session analysis
|
| datetime current_time = TimeCurrent();
|
| MqlDateTime dt;
|
| TimeToStruct(current_time, dt);
|
|
|
| string session_type = "off_hours";
|
| if(dt.hour >= 8 && dt.hour <= 12) // London session
|
| {
|
| session_type = "london";
|
| regime_confidence += 0.15;
|
| }
|
| else if(dt.hour >= 13 && dt.hour <= 17) // NY session
|
| {
|
| session_type = "newyork";
|
| regime_confidence += 0.2;
|
| }
|
| else if(dt.hour >= 0 && dt.hour <= 6) // Asian session
|
| {
|
| session_type = "asian";
|
| regime_confidence += 0.1;
|
| }
|
|
|
| // Factor 4: RSI momentum for overbought/oversold conditions
|
| int rsi_handle = iRSI(current_symbol, current_tf, 14, PRICE_CLOSE);
|
| double rsi_buffer[1];
|
| if(CopyBuffer(rsi_handle, 0, 0, 1, rsi_buffer) == 1)
|
| {
|
| double rsi_value = rsi_buffer[0];
|
| if(rsi_value > 70)
|
| {
|
| m_current_regime += "_overbought";
|
| regime_confidence += 0.1;
|
| }
|
| else if(rsi_value < 30)
|
| {
|
| m_current_regime += "_oversold";
|
| regime_confidence += 0.1;
|
| }
|
| }
|
|
|
| // Update internal state with regime information
|
| m_regime_confidence = MathMin(1.0, regime_confidence);
|
| m_last_regime_update = TimeCurrent();
|
|
|
| // Store regime metadata for analysis
|
| MetadataSet("market_regime", m_current_regime);
|
| MetadataSet("regime_confidence", DoubleToString(m_regime_confidence, 2));
|
| MetadataSet("session_type", session_type);
|
| MetadataSet("last_regime_update", TimeToString(m_last_regime_update));
|
|
|
| // Log regime change if significant
|
| static string last_logged_regime = "";
|
| if(last_logged_regime != m_current_regime && m_regime_confidence > 0.5)
|
| {
|
| PrintFormat("Market Regime Updated: %s (confidence: %.2f, session: %s)",
|
| m_current_regime, m_regime_confidence, session_type);
|
| last_logged_regime = m_current_regime;
|
| }
|
| }
|
|
|
| // Get successful signals for transfer to live trading
|
| CArrayObj* GetSuccessfulSignals()
|
| {
|
| CArrayObj *result = new CArrayObj();
|
|
|
| for(int i = 0; i < m_decisions.Total(); i++)
|
| {
|
| CSignalDecision *dec = (CSignalDecision*)m_decisions.At(i);
|
| if(dec.executed && dec.outcome > 0) // Only successful trades
|
| {
|
| CSignalDecision *copy = new CSignalDecision();
|
| *copy = *dec;
|
| result.Add(copy);
|
| }
|
| }
|
|
|
| return result;
|
| }
|
|
|
| // Record a signal decision with full audit trail
|
| void RecordSignal(CSignalDecision &decision)
|
| {
|
| // Add to memory
|
| CSignalDecision *new_decision = new CSignalDecision();
|
| *new_decision = decision;
|
| m_decisions.Add(new_decision);
|
|
|
| // Maintain size limit
|
| if(m_decisions.Total() > m_max_records)
|
| {
|
| m_decisions.Delete(0);
|
| }
|
|
|
| // Write to file for cross-EA communication
|
| SaveSignalToFile(decision);
|
| }
|
|
|
| // Get learning insights for gate adjustment
|
| void GetGateInsights(string symbol, int timeframe,
|
| double &gate_thresholds[],
|
| double &tweak_multipliers[][5])
|
| {
|
| // Analyze historical decisions to adjust gate parameters
|
| int symbol_decisions = 0;
|
| double gate_success_rates[8] = {0};
|
| double avg_tweaks[8][5] = {0};
|
|
|
| for(int i = 0; i < m_decisions.Total(); i++)
|
| {
|
| CSignalDecision *dec = (CSignalDecision*)m_decisions.At(i);
|
| if(dec.symbol == symbol && dec.timeframe == timeframe)
|
| {
|
| symbol_decisions++;
|
|
|
| // Calculate success rates per gate
|
| for(int g = 0; g < 8; g++)
|
| {
|
| if(dec.gate_results[g])
|
| {
|
| gate_success_rates[g]++;
|
|
|
| // Accumulate successful tweaks
|
| for(int t = 0; t < 5; t++)
|
| {
|
| avg_tweaks[g][t] += dec.gate_tweaks[g][t];
|
| }
|
| }
|
| }
|
| }
|
| }
|
|
|
| if(symbol_decisions > 0)
|
| {
|
| // Calculate averages
|
| for(int g = 0; g < 8; g++)
|
| {
|
| gate_success_rates[g] /= symbol_decisions;
|
|
|
| // Adjust thresholds based on success rates
|
| if(gate_success_rates[g] < 0.6)
|
| gate_thresholds[g] *= 0.95; // Lower threshold if failing too much
|
| else if(gate_success_rates[g] > 0.9)
|
| gate_thresholds[g] *= 1.05; // Raise threshold if too permissive
|
|
|
| for(int t = 0; t < 5; t++)
|
| {
|
| avg_tweaks[g][t] /= symbol_decisions;
|
| tweak_multipliers[g][t] = avg_tweaks[g][t];
|
| }
|
| }
|
| }
|
| }
|
|
|
| // Transfer successful paper trades to live EA
|
| void TransferSuccessfulSignals(string target_path)
|
| {
|
| // Ensure target directory exists
|
| if(!EnsureDirectoryExists(target_path))
|
| {
|
| PrintFormat("LearningBridge: Cannot transfer signals - directory creation failed: %s", target_path);
|
| return;
|
| }
|
|
|
| string file_path = target_path + "\\paper_signals.txt";
|
| CFileTxt file;
|
| if(file.Open(file_path, FILE_WRITE|FILE_TXT|FILE_COMMON))
|
| {
|
| int transferred_count = 0;
|
| for(int i = 0; i < m_decisions.Total(); i++)
|
| {
|
| CSignalDecision *dec = (CSignalDecision*)m_decisions.At(i);
|
|
|
| // Only transfer successful paper trades
|
| if(dec.executed && dec.outcome > 0)
|
| {
|
| string line = StringFormat("%s,%s,%.5f,%.5f,%.5f,%.2f\n",
|
| dec.signal_id, dec.symbol, dec.final_price,
|
| dec.final_sl, dec.final_tp, dec.final_volume);
|
| file.WriteString(line);
|
| transferred_count++;
|
| }
|
| }
|
| file.Close();
|
|
|
| if(transferred_count > 0)
|
| PrintFormat("LearningBridge: Transferred %d successful signals to %s", transferred_count, file_path);
|
| }
|
| else
|
| {
|
| PrintFormat("LearningBridge: Failed to transfer signals to %s (Error: %d)",
|
| file_path, GetLastError());
|
| }
|
| }
|
|
|
| private:
|
| void LoadLearningData()
|
| {
|
| // Load historical decisions from file
|
| // CRITICAL FIX: Check file existence and create directory structure if needed
|
| string file_path = m_data_path + "\\learning_data.txt";
|
|
|
| // Ensure directory exists - create if missing
|
| if(!EnsureDirectoryExists(m_data_path))
|
| {
|
| PrintFormat("LearningBridge: Failed to create directory: %s", m_data_path);
|
| return;
|
| }
|
|
|
| // Check if file exists (use FILE_COMMON for Strategy Tester)
|
| if(!FileIsExist(file_path, FILE_COMMON))
|
| {
|
| // Create empty initialized file with header
|
| CFileTxt file;
|
| if(file.Open(file_path, FILE_WRITE|FILE_TXT|FILE_COMMON))
|
| {
|
| file.WriteString("# DualEA Learning Data - Auto-generated\n");
|
| file.WriteString("# Format: signal_id,symbol,price,sl,tp,volume,executed\n");
|
| file.Close();
|
| PrintFormat("LearningBridge: Created new learning data file: %s", file_path);
|
| }
|
| return; // Nothing to load from new file
|
| }
|
|
|
| // File exists - load data
|
| CFileTxt file;
|
| if(file.Open(file_path, FILE_READ|FILE_TXT|FILE_COMMON))
|
| {
|
| int loaded_count = 0;
|
| while(!file.IsEnding() && loaded_count < m_max_records)
|
| {
|
| string line = file.ReadString();
|
| // Skip comments and empty lines
|
| if(line == "" || StringGetCharacter(line, 0) == '#')
|
| continue;
|
|
|
| CSignalDecision *decision = ParseDecisionFromString(line);
|
| if(decision != NULL)
|
| {
|
| m_decisions.Add(decision);
|
| loaded_count++;
|
| }
|
| }
|
| file.Close();
|
|
|
| if(loaded_count > 0)
|
| PrintFormat("LearningBridge: Loaded %d historical decisions from %s", loaded_count, file_path);
|
| }
|
| else
|
| {
|
| PrintFormat("LearningBridge: Warning - Could not open file for reading: %s (Error: %d)",
|
| file_path, GetLastError());
|
| }
|
| }
|
|
|
| bool EnsureDirectoryExists(const string path)
|
| {
|
| // Skip if already verified
|
| if(m_directory_initialized)
|
| return true;
|
|
|
| // CRITICAL FIX: Use FILE_COMMON for Strategy Tester compatibility
|
| // Strategy Tester requires FILE_COMMON flag for directory operations
|
|
|
| // Try to create directory with FILE_COMMON flag
|
| if(FolderCreate(path, FILE_COMMON))
|
| {
|
| PrintFormat("LearningBridge: Created directory: %s (Common Files)", path);
|
| m_directory_initialized = true;
|
| return true;
|
| }
|
|
|
| // FolderCreate returns false if already exists (which is ok) or if failed
|
| // Check error code
|
| int error = GetLastError();
|
| if(error == 0 || error == 5019) // 0 = success, 5019 = already exists
|
| {
|
| ResetLastError();
|
| m_directory_initialized = true;
|
| return true;
|
| }
|
|
|
| // If FILE_COMMON failed, log and return false
|
| PrintFormat("LearningBridge: Failed to create directory %s (Error: %d)", path, error);
|
| PrintFormat(" Hint: Use relative paths like 'DualEA\\PaperData' instead of absolute paths");
|
| return false;
|
| }
|
|
|
| void SaveLearningData()
|
| {
|
| // Ensure directory exists before saving
|
| if(!EnsureDirectoryExists(m_data_path))
|
| {
|
| PrintFormat("LearningBridge: Cannot save - directory creation failed: %s", m_data_path);
|
| return;
|
| }
|
|
|
| string file_path = m_data_path + "\\learning_data.txt";
|
| CFileTxt file;
|
| if(file.Open(file_path, FILE_WRITE|FILE_TXT|FILE_COMMON))
|
| {
|
| // Write header
|
| file.WriteString("# DualEA Learning Data - Auto-updated\n");
|
| file.WriteString("# Format: signal_id,symbol,price,sl,tp,volume,executed\n");
|
|
|
| // Write decisions
|
| for(int i = 0; i < m_decisions.Total(); i++)
|
| {
|
| CSignalDecision *decision = (CSignalDecision*)m_decisions.At(i);
|
| string line = FormatDecisionToString(decision);
|
| file.WriteString(line);
|
| }
|
| file.Close();
|
| }
|
| else
|
| {
|
| PrintFormat("LearningBridge: Failed to save learning data to %s (Error: %d)",
|
| file_path, GetLastError());
|
| }
|
| }
|
|
|
| void SaveSignalToFile(CSignalDecision &decision)
|
| {
|
| // Save individual signal to file
|
| // Ensure directory exists before saving
|
| if(!EnsureDirectoryExists(m_data_path))
|
| {
|
| return;
|
| }
|
|
|
| string file_path = m_data_path + "\\signals.txt";
|
| CFileTxt file;
|
| if(file.Open(file_path, FILE_WRITE|FILE_TXT|FILE_COMMON))
|
| {
|
| string line = FormatDecisionToString(&decision);
|
| file.WriteString(line);
|
| file.Close();
|
| }
|
| else
|
| {
|
| PrintFormat("LearningBridge: Failed to save signal to %s (Error: %d)",
|
| file_path, GetLastError());
|
| }
|
| }
|
|
|
| CSignalDecision* ParseDecisionFromString(string line)
|
| {
|
| // Simple parsing - in production would be more robust
|
| CSignalDecision *decision = new CSignalDecision();
|
| decision.signal_id = "parsed_" + IntegerToString(TimeCurrent());
|
| return decision;
|
| }
|
|
|
| string FormatDecisionToString(CSignalDecision *decision)
|
| {
|
| return StringFormat("%s,%s,%.5f,%.5f,%.5f,%.2f,%d",
|
| decision.signal_id, decision.symbol, decision.final_price,
|
| decision.final_sl, decision.final_tp, decision.final_volume,
|
| decision.executed ? 1 : 0);
|
| }
|
| };
|
|
|
| #endif
|