2025-10-03 01:38:36 -04:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| PolicyUpdater.mqh |
|
|
|
|
|
//| Purpose: Auto-updating ML policy system |
|
|
|
|
|
//| Features: Learn from trades, update policy.json automatically |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
#ifndef __POLICYUPDATER_MQH__
|
|
|
|
|
#define __POLICYUPDATER_MQH__
|
|
|
|
|
|
|
|
|
|
#include "LearningBridge.mqh"
|
|
|
|
|
#include <Files/File.mqh>
|
|
|
|
|
#include <Generic/HashMap.mqh>
|
|
|
|
|
|
|
|
|
|
// Policy entry for a strategy/symbol/timeframe combination
|
|
|
|
|
struct PolicyEntry
|
|
|
|
|
{
|
|
|
|
|
string strategy;
|
|
|
|
|
string symbol;
|
|
|
|
|
int timeframe;
|
|
|
|
|
double probability; // ML-predicted win rate
|
|
|
|
|
double sl_scale; // SL multiplier
|
|
|
|
|
double tp_scale; // TP multiplier
|
|
|
|
|
double trail_atr_mult; // Trailing stop ATR multiplier
|
|
|
|
|
double min_confidence; // Minimum confidence threshold
|
|
|
|
|
|
|
|
|
|
// Learning metrics
|
|
|
|
|
int total_trades;
|
|
|
|
|
int winning_trades;
|
|
|
|
|
double total_profit;
|
|
|
|
|
datetime last_update;
|
2026-02-24 12:47:37 -05:00
|
|
|
|
|
|
|
|
// NEW: Advanced metrics (P0 improvement)
|
|
|
|
|
double gross_profit; // Total winning trades amount
|
|
|
|
|
double gross_loss; // Total losing trades amount (absolute)
|
|
|
|
|
double expectancy; // (win_rate * avg_win) - (loss_rate * avg_loss)
|
|
|
|
|
double profit_factor; // gross_profit / gross_loss
|
|
|
|
|
double r_multiple_avg; // Average R-multiple per trade
|
|
|
|
|
double max_drawdown_pct; // Peak-to-trough drawdown
|
|
|
|
|
double sharpe_20; // 20-trade Sharpe ratio
|
|
|
|
|
double peak_equity; // For drawdown calculation
|
|
|
|
|
double current_equity; // Running equity curve
|
|
|
|
|
|
|
|
|
|
// NEW: Trade history buffer for advanced stats (last 20 trades)
|
|
|
|
|
double recent_pnls[20];
|
|
|
|
|
int recent_count;
|
|
|
|
|
|
|
|
|
|
// NEW: Regime-aware tracking
|
|
|
|
|
string market_regime; // "trending", "ranging", "volatile"
|
|
|
|
|
double regime_performance[3]; // Performance per regime index
|
2025-10-03 01:38:36 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Policy Updater - ML-driven policy learning |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
class CPolicyUpdater
|
|
|
|
|
{
|
|
|
|
|
private:
|
|
|
|
|
CLearningBridge* m_learning;
|
2025-10-16 18:03:12 -04:00
|
|
|
|
|
|
|
|
// Simple array-based policy storage (MQL5 HashMap doesn't support structs)
|
|
|
|
|
string m_policy_keys[];
|
|
|
|
|
PolicyEntry m_policy_values[];
|
2025-10-03 01:38:36 -04:00
|
|
|
|
|
|
|
|
string m_policy_file_path;
|
|
|
|
|
int m_update_interval_minutes;
|
|
|
|
|
datetime m_last_update;
|
|
|
|
|
bool m_auto_update_enabled;
|
|
|
|
|
|
|
|
|
|
// Statistics
|
|
|
|
|
int m_total_updates;
|
|
|
|
|
int m_successful_updates;
|
|
|
|
|
|
2026-02-24 12:47:37 -05:00
|
|
|
// NEW: Real-time update trigger
|
|
|
|
|
int m_trades_since_update;
|
|
|
|
|
int m_trades_update_threshold; // Write policy after N trades
|
|
|
|
|
|
2025-10-16 18:03:12 -04:00
|
|
|
// Helper methods for policy storage
|
|
|
|
|
int FindPolicyIndex(const string key)
|
|
|
|
|
{
|
|
|
|
|
for(int i = 0; i < ArraySize(m_policy_keys); i++)
|
|
|
|
|
{
|
|
|
|
|
if(m_policy_keys[i] == key) return i;
|
|
|
|
|
}
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GetPolicy(const string key, PolicyEntry &out_policy)
|
|
|
|
|
{
|
|
|
|
|
int idx = FindPolicyIndex(key);
|
|
|
|
|
if(idx >= 0)
|
|
|
|
|
{
|
|
|
|
|
out_policy = m_policy_values[idx];
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void SetPolicy(const string key, const PolicyEntry &policy)
|
|
|
|
|
{
|
|
|
|
|
int idx = FindPolicyIndex(key);
|
|
|
|
|
if(idx >= 0)
|
|
|
|
|
{
|
|
|
|
|
m_policy_values[idx] = policy;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
int size = ArraySize(m_policy_keys);
|
|
|
|
|
ArrayResize(m_policy_keys, size + 1);
|
|
|
|
|
ArrayResize(m_policy_values, size + 1);
|
|
|
|
|
m_policy_keys[size] = key;
|
|
|
|
|
m_policy_values[size] = policy;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Load policies from JSON file
|
2025-10-03 01:38:36 -04:00
|
|
|
string GetPolicyKey(const string strategy, const string symbol, const int timeframe)
|
|
|
|
|
{
|
|
|
|
|
return StringFormat("%s_%s_%d", strategy, symbol, timeframe);
|
|
|
|
|
}
|
2026-02-24 12:47:37 -05:00
|
|
|
// Learn optimal parameters from trade history with advanced metrics
|
2025-10-03 01:38:36 -04:00
|
|
|
void LearnFromTrades(PolicyEntry &policy)
|
|
|
|
|
{
|
|
|
|
|
if(m_learning == NULL) return;
|
|
|
|
|
|
|
|
|
|
// Calculate actual win rate from trades
|
|
|
|
|
if(policy.total_trades > 5)
|
|
|
|
|
{
|
|
|
|
|
double actual_win_rate = (double)policy.winning_trades / policy.total_trades;
|
|
|
|
|
double avg_profit = policy.total_profit / policy.total_trades;
|
|
|
|
|
|
|
|
|
|
// Update probability (exponential moving average)
|
|
|
|
|
policy.probability = policy.probability * 0.7 + actual_win_rate * 0.3;
|
|
|
|
|
|
2026-02-24 12:47:37 -05:00
|
|
|
// NEW: Calculate profit factor
|
|
|
|
|
if(policy.gross_loss > 0)
|
|
|
|
|
policy.profit_factor = policy.gross_profit / policy.gross_loss;
|
|
|
|
|
else if(policy.gross_profit > 0)
|
|
|
|
|
policy.profit_factor = 999.0; // No losses yet
|
|
|
|
|
|
|
|
|
|
// NEW: Calculate expectancy
|
|
|
|
|
double loss_rate = 1.0 - actual_win_rate;
|
|
|
|
|
double avg_win = policy.winning_trades > 0 ? policy.gross_profit / policy.winning_trades : 0;
|
|
|
|
|
double avg_loss = (policy.total_trades - policy.winning_trades) > 0 ?
|
|
|
|
|
policy.gross_loss / (policy.total_trades - policy.winning_trades) : 0;
|
|
|
|
|
policy.expectancy = (actual_win_rate * avg_win) - (loss_rate * avg_loss);
|
|
|
|
|
|
|
|
|
|
// NEW: Calculate 20-trade Sharpe ratio if we have enough data
|
|
|
|
|
if(policy.recent_count >= 5)
|
|
|
|
|
{
|
|
|
|
|
double mean_pnl = 0, variance = 0;
|
|
|
|
|
for(int i = 0; i < policy.recent_count; i++)
|
|
|
|
|
mean_pnl += policy.recent_pnls[i];
|
|
|
|
|
mean_pnl /= policy.recent_count;
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < policy.recent_count; i++)
|
|
|
|
|
variance += MathPow(policy.recent_pnls[i] - mean_pnl, 2);
|
|
|
|
|
variance /= policy.recent_count;
|
|
|
|
|
|
|
|
|
|
double std_dev = MathSqrt(variance);
|
|
|
|
|
if(std_dev > 0)
|
|
|
|
|
policy.sharpe_20 = mean_pnl / std_dev;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Adjust SL/TP based on profit factor (more robust than raw profit)
|
|
|
|
|
if(policy.profit_factor > 1.5 && policy.expectancy > 0)
|
2025-10-03 01:38:36 -04:00
|
|
|
{
|
2026-02-24 12:47:37 -05:00
|
|
|
// Profitable with good profit factor - can be slightly more aggressive
|
|
|
|
|
policy.sl_scale = MathMin(1.5, policy.sl_scale * 1.02);
|
|
|
|
|
policy.tp_scale = MathMax(1.0, policy.tp_scale * 1.02);
|
2025-10-03 01:38:36 -04:00
|
|
|
}
|
2026-02-24 12:47:37 -05:00
|
|
|
else if(policy.profit_factor < 1.0 || policy.expectancy < 0)
|
2025-10-03 01:38:36 -04:00
|
|
|
{
|
2026-02-24 12:47:37 -05:00
|
|
|
// Poor profit factor or negative expectancy - tighten up
|
|
|
|
|
policy.sl_scale = MathMax(0.7, policy.sl_scale * 0.98);
|
|
|
|
|
policy.tp_scale = MathMin(2.0, policy.tp_scale * 0.98);
|
2025-10-03 01:38:36 -04:00
|
|
|
}
|
|
|
|
|
|
2026-02-24 12:47:37 -05:00
|
|
|
// Adjust confidence threshold based on Sharpe and expectancy
|
|
|
|
|
if(policy.sharpe_20 > 0.5 && policy.expectancy > 0)
|
2025-10-03 01:38:36 -04:00
|
|
|
{
|
2026-02-24 12:47:37 -05:00
|
|
|
// Good risk-adjusted returns - can lower threshold
|
|
|
|
|
policy.min_confidence = MathMax(0.3, policy.min_confidence * 0.99);
|
2025-10-03 01:38:36 -04:00
|
|
|
}
|
2026-02-24 12:47:37 -05:00
|
|
|
else if(policy.sharpe_20 < 0.0 || policy.max_drawdown_pct > 10.0)
|
2025-10-03 01:38:36 -04:00
|
|
|
{
|
2026-02-24 12:47:37 -05:00
|
|
|
// Negative Sharpe or high drawdown - raise threshold
|
|
|
|
|
policy.min_confidence = MathMin(0.8, policy.min_confidence * 1.01);
|
2025-10-03 01:38:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
policy.last_update = TimeCurrent();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 12:47:37 -05:00
|
|
|
// Write policy to JSON file with atomic writes and versioning
|
2025-10-03 01:38:36 -04:00
|
|
|
bool WritePolicyFile()
|
|
|
|
|
{
|
2026-02-24 12:47:37 -05:00
|
|
|
// ATOMIC WRITE: Write to temp file first, then rename
|
|
|
|
|
string temp_path = m_policy_file_path + ".tmp";
|
|
|
|
|
string backup_path = m_policy_file_path + ".backup";
|
|
|
|
|
|
|
|
|
|
int handle = FileOpen(temp_path, FILE_WRITE|FILE_TXT|FILE_COMMON|FILE_ANSI);
|
2025-10-03 01:38:36 -04:00
|
|
|
if(handle == INVALID_HANDLE)
|
|
|
|
|
{
|
2026-02-24 12:47:37 -05:00
|
|
|
PrintFormat("❌ PolicyUpdater: Failed to open temp file for writing: %s (Error: %d)",
|
|
|
|
|
temp_path, GetLastError());
|
2025-10-03 01:38:36 -04:00
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-24 12:47:37 -05:00
|
|
|
// VERSIONING: Increment version on each write
|
|
|
|
|
static int policy_version = 1;
|
|
|
|
|
policy_version++;
|
|
|
|
|
|
|
|
|
|
// Write JSON header with version
|
2025-10-03 01:38:36 -04:00
|
|
|
FileWriteString(handle, "{\n");
|
2026-02-24 12:47:37 -05:00
|
|
|
FileWriteString(handle, StringFormat(" \"version\": \"1.0.%d\",\n", policy_version));
|
|
|
|
|
FileWriteString(handle, " \"schema_version\": \"2.0\",\n");
|
2025-10-03 01:38:36 -04:00
|
|
|
FileWriteString(handle, StringFormat(" \"last_updated\": \"%s\",\n", TimeToString(TimeCurrent())));
|
2025-10-16 18:03:12 -04:00
|
|
|
FileWriteString(handle, StringFormat(" \"total_policies\": %d,\n", ArraySize(m_policy_keys)));
|
2026-02-24 12:47:37 -05:00
|
|
|
FileWriteString(handle, " \"write_mode\": \"atomic\",\n");
|
2025-10-03 01:38:36 -04:00
|
|
|
FileWriteString(handle, " \"policies\": [\n");
|
|
|
|
|
|
|
|
|
|
// Write each policy entry
|
|
|
|
|
int count = 0;
|
|
|
|
|
|
2025-10-16 18:03:12 -04:00
|
|
|
for(int i = 0; i < ArraySize(m_policy_keys); i++)
|
2025-10-03 01:38:36 -04:00
|
|
|
{
|
2025-10-16 18:03:12 -04:00
|
|
|
PolicyEntry policy = m_policy_values[i];
|
2025-10-03 01:38:36 -04:00
|
|
|
{
|
|
|
|
|
if(count > 0)
|
|
|
|
|
FileWriteString(handle, ",\n");
|
|
|
|
|
|
|
|
|
|
string entry = StringFormat(
|
|
|
|
|
" {\n"
|
|
|
|
|
" \"strategy\": \"%s\",\n"
|
|
|
|
|
" \"symbol\": \"%s\",\n"
|
|
|
|
|
" \"timeframe\": %d,\n"
|
|
|
|
|
" \"probability\": %.4f,\n"
|
|
|
|
|
" \"sl_scale\": %.2f,\n"
|
|
|
|
|
" \"tp_scale\": %.2f,\n"
|
|
|
|
|
" \"trail_atr_mult\": %.1f,\n"
|
|
|
|
|
" \"min_confidence\": %.2f,\n"
|
|
|
|
|
" \"total_trades\": %d,\n"
|
|
|
|
|
" \"winning_trades\": %d,\n"
|
2026-02-24 12:47:37 -05:00
|
|
|
" \"win_rate\": %.2f,\n"
|
|
|
|
|
" \"profit_factor\": %.2f,\n"
|
|
|
|
|
" \"expectancy\": %.2f,\n"
|
|
|
|
|
" \"sharpe_20\": %.2f,\n"
|
|
|
|
|
" \"max_drawdown_pct\": %.2f\n"
|
2025-10-03 01:38:36 -04:00
|
|
|
" }",
|
|
|
|
|
policy.strategy, policy.symbol, policy.timeframe,
|
|
|
|
|
policy.probability, policy.sl_scale, policy.tp_scale,
|
|
|
|
|
policy.trail_atr_mult, policy.min_confidence,
|
|
|
|
|
policy.total_trades, policy.winning_trades,
|
2026-02-24 12:47:37 -05:00
|
|
|
policy.total_trades > 0 ? (double)policy.winning_trades/policy.total_trades : 0.0,
|
|
|
|
|
policy.profit_factor,
|
|
|
|
|
policy.expectancy,
|
|
|
|
|
policy.sharpe_20,
|
|
|
|
|
policy.max_drawdown_pct
|
2025-10-03 01:38:36 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
|
|
FileWriteString(handle, entry);
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FileWriteString(handle, "\n ]\n");
|
|
|
|
|
FileWriteString(handle, "}\n");
|
|
|
|
|
|
|
|
|
|
FileClose(handle);
|
|
|
|
|
|
2026-02-24 12:47:37 -05:00
|
|
|
// ATOMIC: Backup existing file if it exists
|
|
|
|
|
int existing_handle = FileOpen(m_policy_file_path, FILE_READ|FILE_TXT|FILE_COMMON);
|
|
|
|
|
if(existing_handle != INVALID_HANDLE)
|
|
|
|
|
{
|
|
|
|
|
FileClose(existing_handle);
|
|
|
|
|
// Copy existing to backup
|
|
|
|
|
int backup_handle = FileOpen(backup_path, FILE_WRITE|FILE_TXT|FILE_COMMON|FILE_ANSI);
|
|
|
|
|
if(backup_handle != INVALID_HANDLE)
|
|
|
|
|
{
|
|
|
|
|
int old_handle = FileOpen(m_policy_file_path, FILE_READ|FILE_TXT|FILE_COMMON|FILE_ANSI);
|
|
|
|
|
if(old_handle != INVALID_HANDLE)
|
|
|
|
|
{
|
|
|
|
|
while(!FileIsEnding(old_handle))
|
|
|
|
|
{
|
|
|
|
|
string line = FileReadString(old_handle);
|
|
|
|
|
FileWriteString(backup_handle, line + "\n");
|
|
|
|
|
}
|
|
|
|
|
FileClose(old_handle);
|
|
|
|
|
}
|
|
|
|
|
FileClose(backup_handle);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ATOMIC: Rename temp to final
|
|
|
|
|
FileDelete(m_policy_file_path, FILE_COMMON);
|
|
|
|
|
FileMove(temp_path, FILE_COMMON, m_policy_file_path, FILE_COMMON|FILE_REWRITE);
|
|
|
|
|
|
|
|
|
|
PrintFormat("✅ PolicyUpdater: Atomically updated policy file with %d entries (version %d)", count, policy_version);
|
2025-10-03 01:38:36 -04:00
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public:
|
|
|
|
|
CPolicyUpdater(CLearningBridge* learning, int update_interval_minutes = 60)
|
|
|
|
|
{
|
|
|
|
|
m_learning = learning;
|
|
|
|
|
m_policy_file_path = "DualEA\\policy.json";
|
|
|
|
|
m_update_interval_minutes = update_interval_minutes;
|
|
|
|
|
m_last_update = 0;
|
|
|
|
|
m_auto_update_enabled = true;
|
|
|
|
|
m_total_updates = 0;
|
|
|
|
|
m_successful_updates = 0;
|
|
|
|
|
|
2026-02-24 12:47:37 -05:00
|
|
|
// NEW: Real-time update configuration
|
|
|
|
|
m_trades_since_update = 0;
|
|
|
|
|
m_trades_update_threshold = 5; // Update policy file after every 5 trades
|
|
|
|
|
|
2025-10-03 01:38:36 -04:00
|
|
|
// Initialize default policies for common combinations
|
|
|
|
|
InitializeDefaultPolicies();
|
|
|
|
|
|
|
|
|
|
// Write initial policy file
|
|
|
|
|
if(WritePolicyFile())
|
|
|
|
|
m_successful_updates++;
|
|
|
|
|
|
2026-02-24 12:47:37 -05:00
|
|
|
PrintFormat("🎯 PolicyUpdater initialized: auto-update every %d minutes, real-time updates every %d trades",
|
|
|
|
|
m_update_interval_minutes, m_trades_update_threshold);
|
2025-10-03 01:38:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void InitializeDefaultPolicies()
|
|
|
|
|
{
|
|
|
|
|
string strategies[] = {
|
|
|
|
|
"MovingAverageStrategy", "ADXStrategy", "RSIStrategy", "BollingerBandsStrategy",
|
|
|
|
|
"MACDStrategy", "StochasticStrategy", "IchimokuStrategy", "MomentumStrategy"
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
string symbols[] = {"US500", "UK100", "GER40", "EURUSD", "GBPUSD", "USDJPY"};
|
|
|
|
|
int timeframes[] = {60, 240, 1440}; // H1, H4, D1
|
|
|
|
|
|
|
|
|
|
for(int s = 0; s < ArraySize(strategies); s++)
|
|
|
|
|
{
|
|
|
|
|
for(int sym = 0; sym < ArraySize(symbols); sym++)
|
|
|
|
|
{
|
|
|
|
|
for(int tf = 0; tf < ArraySize(timeframes); tf++)
|
|
|
|
|
{
|
|
|
|
|
PolicyEntry policy;
|
|
|
|
|
policy.strategy = strategies[s];
|
|
|
|
|
policy.symbol = symbols[sym];
|
|
|
|
|
policy.timeframe = timeframes[tf];
|
|
|
|
|
|
|
|
|
|
// Conservative defaults
|
|
|
|
|
policy.probability = 0.55;
|
|
|
|
|
policy.sl_scale = 1.0;
|
|
|
|
|
policy.tp_scale = 1.2;
|
|
|
|
|
policy.trail_atr_mult = 2.0;
|
|
|
|
|
policy.min_confidence = 0.45; // Lower than before to allow more signals
|
|
|
|
|
|
|
|
|
|
policy.total_trades = 0;
|
|
|
|
|
policy.winning_trades = 0;
|
|
|
|
|
policy.total_profit = 0.0;
|
|
|
|
|
policy.last_update = TimeCurrent();
|
|
|
|
|
|
|
|
|
|
string key = GetPolicyKey(policy.strategy, policy.symbol, policy.timeframe);
|
2025-10-16 18:03:12 -04:00
|
|
|
SetPolicy(key, policy);
|
2025-10-03 01:38:36 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-10-16 18:03:12 -04:00
|
|
|
PrintFormat("✅ Initialized %d default policy entries", ArraySize(m_policy_keys));
|
2025-10-03 01:38:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Record trade outcome for policy learning
|
|
|
|
|
void RecordTradeOutcome(const string strategy, const string symbol, const int timeframe,
|
|
|
|
|
bool won, double profit)
|
|
|
|
|
{
|
|
|
|
|
string key = GetPolicyKey(strategy, symbol, timeframe);
|
|
|
|
|
PolicyEntry policy;
|
|
|
|
|
|
2025-10-16 18:03:12 -04:00
|
|
|
if(!GetPolicy(key, policy))
|
2025-10-03 01:38:36 -04:00
|
|
|
{
|
|
|
|
|
// Create new policy entry
|
|
|
|
|
policy.strategy = strategy;
|
|
|
|
|
policy.symbol = symbol;
|
|
|
|
|
policy.timeframe = timeframe;
|
|
|
|
|
policy.probability = 0.5;
|
|
|
|
|
policy.sl_scale = 1.0;
|
|
|
|
|
policy.tp_scale = 1.2;
|
|
|
|
|
policy.trail_atr_mult = 2.0;
|
|
|
|
|
policy.min_confidence = 0.5;
|
|
|
|
|
policy.total_trades = 0;
|
|
|
|
|
policy.winning_trades = 0;
|
|
|
|
|
policy.total_profit = 0.0;
|
|
|
|
|
policy.last_update = TimeCurrent();
|
2026-02-24 12:47:37 -05:00
|
|
|
|
|
|
|
|
// NEW: Initialize advanced metrics
|
|
|
|
|
policy.gross_profit = 0.0;
|
|
|
|
|
policy.gross_loss = 0.0;
|
|
|
|
|
policy.expectancy = 0.0;
|
|
|
|
|
policy.profit_factor = 1.0;
|
|
|
|
|
policy.r_multiple_avg = 0.0;
|
|
|
|
|
policy.max_drawdown_pct = 0.0;
|
|
|
|
|
policy.sharpe_20 = 0.0;
|
|
|
|
|
policy.peak_equity = 0.0;
|
|
|
|
|
policy.current_equity = 0.0;
|
|
|
|
|
policy.recent_count = 0;
|
|
|
|
|
policy.market_regime = "";
|
|
|
|
|
ArrayInitialize(policy.recent_pnls, 0.0);
|
|
|
|
|
ArrayInitialize(policy.regime_performance, 0.0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NEW: Update gross profit/loss for profit factor calculation
|
|
|
|
|
if(profit > 0)
|
|
|
|
|
policy.gross_profit += profit;
|
|
|
|
|
else
|
|
|
|
|
policy.gross_loss += MathAbs(profit);
|
|
|
|
|
|
|
|
|
|
// NEW: Update equity curve for drawdown calculation
|
|
|
|
|
policy.current_equity += profit;
|
|
|
|
|
if(policy.current_equity > policy.peak_equity)
|
|
|
|
|
policy.peak_equity = policy.current_equity;
|
|
|
|
|
|
|
|
|
|
// NEW: Calculate running drawdown
|
|
|
|
|
if(policy.peak_equity > 0)
|
|
|
|
|
{
|
|
|
|
|
double current_dd = (policy.peak_equity - policy.current_equity) / policy.peak_equity * 100.0;
|
|
|
|
|
if(current_dd > policy.max_drawdown_pct)
|
|
|
|
|
policy.max_drawdown_pct = current_dd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NEW: Update recent trades buffer for Sharpe calculation
|
|
|
|
|
if(policy.recent_count < 20)
|
|
|
|
|
{
|
|
|
|
|
policy.recent_pnls[policy.recent_count] = profit;
|
|
|
|
|
policy.recent_count++;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
// Shift array (FIFO)
|
|
|
|
|
for(int i = 0; i < 19; i++)
|
|
|
|
|
policy.recent_pnls[i] = policy.recent_pnls[i+1];
|
|
|
|
|
policy.recent_pnls[19] = profit;
|
2025-10-03 01:38:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update trade statistics
|
|
|
|
|
policy.total_trades++;
|
|
|
|
|
if(won)
|
|
|
|
|
policy.winning_trades++;
|
|
|
|
|
policy.total_profit += profit;
|
|
|
|
|
|
|
|
|
|
// Learn from this data
|
|
|
|
|
LearnFromTrades(policy);
|
|
|
|
|
|
|
|
|
|
// Save updated policy
|
2025-10-16 18:03:12 -04:00
|
|
|
SetPolicy(key, policy);
|
2026-02-24 12:47:37 -05:00
|
|
|
|
|
|
|
|
// NEW: Real-time policy update trigger
|
|
|
|
|
m_trades_since_update++;
|
|
|
|
|
if(m_trades_since_update >= m_trades_update_threshold)
|
|
|
|
|
{
|
|
|
|
|
if(WritePolicyFile())
|
|
|
|
|
{
|
|
|
|
|
m_successful_updates++;
|
|
|
|
|
m_last_update = TimeCurrent();
|
|
|
|
|
PrintFormat("🔄 PolicyUpdater: Real-time update after %d trades", m_trades_since_update);
|
|
|
|
|
}
|
|
|
|
|
m_trades_since_update = 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Alias wrapper to avoid method signature confusion with other classes
|
|
|
|
|
void UpdatePolicyFromTrade(const string strategy, const string symbol, const int timeframe,
|
|
|
|
|
bool won, double profit)
|
|
|
|
|
{
|
|
|
|
|
RecordTradeOutcome(strategy, symbol, timeframe, won, profit);
|
2025-10-03 01:38:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Auto-update policy file if interval elapsed
|
|
|
|
|
void CheckAndUpdate()
|
|
|
|
|
{
|
|
|
|
|
if(!m_auto_update_enabled)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
datetime now = TimeCurrent();
|
|
|
|
|
if(now - m_last_update < m_update_interval_minutes * 60)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_total_updates++;
|
|
|
|
|
|
|
|
|
|
if(WritePolicyFile())
|
|
|
|
|
{
|
|
|
|
|
m_successful_updates++;
|
|
|
|
|
m_last_update = now;
|
|
|
|
|
|
|
|
|
|
PrintFormat("🔄 PolicyUpdater: Auto-updated policy file (%d/%d successful updates)",
|
|
|
|
|
m_successful_updates, m_total_updates);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Force immediate update
|
|
|
|
|
void ForceUpdate()
|
|
|
|
|
{
|
|
|
|
|
m_total_updates++;
|
|
|
|
|
|
|
|
|
|
if(WritePolicyFile())
|
|
|
|
|
m_successful_updates++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get policy for specific combination
|
|
|
|
|
bool GetPolicy(const string strategy, const string symbol, const int timeframe,
|
|
|
|
|
PolicyEntry &policy)
|
|
|
|
|
{
|
|
|
|
|
string key = GetPolicyKey(strategy, symbol, timeframe);
|
2025-10-16 18:03:12 -04:00
|
|
|
return GetPolicy(key, policy);
|
2025-10-03 01:38:36 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Enable/disable auto-updates
|
|
|
|
|
void SetAutoUpdate(bool enabled)
|
|
|
|
|
{
|
|
|
|
|
m_auto_update_enabled = enabled;
|
|
|
|
|
PrintFormat("PolicyUpdater: Auto-update %s", enabled ? "ENABLED" : "DISABLED");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get statistics
|
|
|
|
|
void GetStatistics(int &total_policies, int &total_updates, int &successful_updates)
|
|
|
|
|
{
|
2025-10-16 18:03:12 -04:00
|
|
|
total_policies = ArraySize(m_policy_keys);
|
2025-10-03 01:38:36 -04:00
|
|
|
total_updates = m_total_updates;
|
|
|
|
|
successful_updates = m_successful_updates;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PrintReport()
|
|
|
|
|
{
|
|
|
|
|
PrintFormat("\n=== 📈 Policy Learning Report ===");
|
2025-10-16 18:03:12 -04:00
|
|
|
PrintFormat("Total Policies: %d", ArraySize(m_policy_keys));
|
2025-10-03 01:38:36 -04:00
|
|
|
PrintFormat("Total Updates: %d", m_total_updates);
|
|
|
|
|
PrintFormat("Successful Updates: %d (%.1f%%)", m_successful_updates,
|
|
|
|
|
m_total_updates > 0 ? (double)m_successful_updates/m_total_updates*100 : 0);
|
|
|
|
|
PrintFormat("Auto-Update: %s (interval: %d minutes)",
|
|
|
|
|
m_auto_update_enabled ? "ON" : "OFF", m_update_interval_minutes);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#endif
|