mql5/Experts/Advisors/DualEA/Include/LearningBridge.mqh

615 lines
20 KiB
MQL5
Raw Permalink Normal View History

2025-09-24 15:10:34 -04:00
//+------------------------------------------------------------------+
//| 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
2025-09-24 22:41:55 -04:00
// 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
2025-09-24 15:10:34 -04:00
// 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
2026-02-05 23:31:20 -05:00
double final_confidence; // Final confidence after gating
bool is_adjusted; // Was signal adjusted by gates?
string block_reason; // Reason for blocking signal
2025-09-24 15:10:34 -04:00
double outcome; // Trade result (profit/loss)
datetime close_time; // When trade closed
2025-09-24 22:41:55 -04:00
datetime execution_time; // Execution time
double execution_price; // Actual filled price
2025-09-24 15:10:34 -04:00
// 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;
2025-09-24 22:41:55 -04:00
strategy = "";
order_type = 0;
magic_number = 0;
confidence = 0.0;
2025-09-24 15:10:34 -04:00
executed = false;
final_price = 0;
final_sl = 0;
final_tp = 0;
final_volume = 0;
2026-02-05 23:31:20 -05:00
final_confidence = 0.0;
is_adjusted = false;
block_reason = "";
2025-09-24 15:10:34 -04:00
outcome = 0;
close_time = 0;
2025-09-24 22:41:55 -04:00
execution_time = 0;
execution_price = 0.0;
2025-09-24 15:10:34 -04:00
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;
2025-09-24 22:41:55 -04:00
string m_metadata_keys[];
string m_metadata_values[];
2025-10-03 01:38:36 -04:00
bool m_directory_initialized; // Flag to prevent repeated directory checks
2025-09-24 17:58:57 -04:00
string m_current_regime;
double m_regime_confidence;
datetime m_last_regime_update;
2025-09-24 15:10:34 -04:00
public:
CLearningBridge(string data_path, int max_records=10000)
{
m_data_path = data_path;
m_max_records = max_records;
2025-10-03 01:38:36 -04:00
m_directory_initialized = false; // Directory not yet verified
2025-09-24 17:58:57 -04:00
// Initialize regime tracking
m_current_regime = "unknown";
m_regime_confidence = 0.0;
m_last_regime_update = 0;
2025-09-24 22:41:55 -04:00
ArrayResize(m_metadata_keys, 0);
ArrayResize(m_metadata_values, 0);
2025-09-24 15:10:34 -04:00
LoadLearningData();
}
2025-09-24 22:41:55 -04:00
// Metadata helpers --------------------------------------------------
void MetadataSet(const string key, const string value)
2025-09-24 15:10:34 -04:00
{
2025-09-24 22:41:55 -04:00
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;
2025-09-24 15:10:34 -04:00
}
2025-09-24 22:41:55 -04:00
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);
}
2025-09-24 15:10:34 -04:00
// 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()
{
2025-09-24 17:58:57 -04:00
// 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;
}
2025-09-24 15:10:34 -04:00
}
// 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)
{
2025-10-03 01:38:36 -04:00
// 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";
2025-09-24 15:10:34 -04:00
CFileTxt file;
2025-10-03 01:38:36 -04:00
if(file.Open(file_path, FILE_WRITE|FILE_TXT|FILE_COMMON))
2025-09-24 15:10:34 -04:00
{
2025-10-03 01:38:36 -04:00
int transferred_count = 0;
2025-09-24 15:10:34 -04:00
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)
{
2025-10-03 01:38:36 -04:00
string line = StringFormat("%s,%s,%.5f,%.5f,%.5f,%.2f\n",
2025-09-24 15:10:34 -04:00
dec.signal_id, dec.symbol, dec.final_price,
dec.final_sl, dec.final_tp, dec.final_volume);
file.WriteString(line);
2025-10-03 01:38:36 -04:00
transferred_count++;
2025-09-24 15:10:34 -04:00
}
}
file.Close();
2025-10-03 01:38:36 -04:00
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());
2025-09-24 15:10:34 -04:00
}
}
private:
void LoadLearningData()
{
// Load historical decisions from file
2025-10-03 01:38:36 -04:00
// 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
2025-09-24 15:10:34 -04:00
CFileTxt file;
2025-10-03 01:38:36 -04:00
if(file.Open(file_path, FILE_READ|FILE_TXT|FILE_COMMON))
2025-09-24 15:10:34 -04:00
{
2025-10-03 01:38:36 -04:00
int loaded_count = 0;
while(!file.IsEnding() && loaded_count < m_max_records)
2025-09-24 15:10:34 -04:00
{
string line = file.ReadString();
2025-10-03 01:38:36 -04:00
// Skip comments and empty lines
if(line == "" || StringGetCharacter(line, 0) == '#')
continue;
CSignalDecision *decision = ParseDecisionFromString(line);
if(decision != NULL)
2025-09-24 15:10:34 -04:00
{
2025-10-03 01:38:36 -04:00
m_decisions.Add(decision);
loaded_count++;
2025-09-24 15:10:34 -04:00
}
}
file.Close();
2025-10-03 01:38:36 -04:00
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;
2025-09-24 15:10:34 -04:00
}
2025-10-03 01:38:36 -04:00
// 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;
2025-09-24 15:10:34 -04:00
}
void SaveLearningData()
{
2025-10-03 01:38:36 -04:00
// 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";
2025-09-24 15:10:34 -04:00
CFileTxt file;
2025-10-03 01:38:36 -04:00
if(file.Open(file_path, FILE_WRITE|FILE_TXT|FILE_COMMON))
2025-09-24 15:10:34 -04:00
{
2025-10-03 01:38:36 -04:00
// Write header
file.WriteString("# DualEA Learning Data - Auto-updated\n");
file.WriteString("# Format: signal_id,symbol,price,sl,tp,volume,executed\n");
// Write decisions
2025-09-24 15:10:34 -04:00
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();
}
2025-10-03 01:38:36 -04:00
else
{
PrintFormat("LearningBridge: Failed to save learning data to %s (Error: %d)",
file_path, GetLastError());
}
2025-09-24 15:10:34 -04:00
}
void SaveSignalToFile(CSignalDecision &decision)
{
// Save individual signal to file
2025-10-03 01:38:36 -04:00
// Ensure directory exists before saving
if(!EnsureDirectoryExists(m_data_path))
{
return;
}
string file_path = m_data_path + "\\signals.txt";
2025-09-24 15:10:34 -04:00
CFileTxt file;
2025-10-03 01:38:36 -04:00
if(file.Open(file_path, FILE_WRITE|FILE_TXT|FILE_COMMON))
2025-09-24 15:10:34 -04:00
{
string line = FormatDecisionToString(&decision);
file.WriteString(line);
file.Close();
}
2025-10-03 01:38:36 -04:00
else
{
PrintFormat("LearningBridge: Failed to save signal to %s (Error: %d)",
file_path, GetLastError());
}
2025-09-24 15:10:34 -04:00
}
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