mql5/Experts/Advisors/Modules_optimised/Performance_Tracker (2).mqh

607 lines
20 KiB
MQL5
Raw Permalink Normal View History

2025-08-15 23:05:57 +01:00
//+------------------------------------------------------------------+
//| PerformanceTracker.mqh v1.0 |
//| Centralized Performance Tracking & Analytics |
//| Optimized for Real-time Updates |
//+------------------------------------------------------------------+
#ifndef PERFORMANCE_TRACKER_MQH
#define PERFORMANCE_TRACKER_MQH
#include "DataTypes.mqh"
#include <Math/Stat/Math.mqh>
//+------------------------------------------------------------------+
//| Trade history entry for performance calculation |
//+------------------------------------------------------------------+
struct TradeHistoryEntry
{
ulong ticket;
string symbol;
ENUM_POSITION_TYPE type;
datetime open_time;
datetime close_time;
double volume;
double open_price;
double close_price;
double profit;
double commission;
double swap;
double risk_amount;
double r_multiple;
int duration_bars;
string strategy;
};
//+------------------------------------------------------------------+
//| Performance Tracker Class |
//+------------------------------------------------------------------+
class CPerformanceTracker
{
private:
//--- Trade history
TradeHistoryEntry m_history[];
int m_history_size;
int m_max_history;
//--- Cached metrics
PerformanceMetrics m_metrics;
datetime m_last_update;
bool m_cache_valid;
//--- Daily tracking
double m_daily_starting_balance;
datetime m_day_start;
PerformanceMetrics m_daily_metrics;
//--- Return series for advanced calculations
double m_returns[];
double m_equity_curve[];
int m_curve_size;
//--- Configuration
int m_lookback_period;
double m_risk_free_rate;
//--- Helper methods
void UpdateBasicMetrics();
void UpdateAdvancedMetrics();
void UpdateDrawdownMetrics();
void UpdateReturnDistribution();
void CalculateRatios();
double CalculateSharpe();
double CalculateSortino();
double CalculateCalmar();
void UpdateEquityCurve(double current_balance);
public:
CPerformanceTracker();
~CPerformanceTracker();
//--- Initialization
bool Initialize(int max_history = 1000, int lookback = 252);
//--- Trade tracking
void AddTrade(const ManagedTrade &trade, double close_price);
void UpdateOpenTrade(const ManagedTrade &trade);
//--- Metric access
PerformanceMetrics GetMetrics(bool force_update = false);
PerformanceMetrics GetDailyMetrics();
//--- Specific metrics
double GetWinRate() { return m_metrics.win_rate; }
double GetProfitFactor() { return m_metrics.profit_factor; }
double GetExpectancy() { return m_metrics.expectancy; }
double GetSharpeRatio() { return m_metrics.sharpe_ratio; }
double GetMaxDrawdown() { return m_metrics.max_drawdown_percent; }
double GetRecoveryFactor() { return m_metrics.recovery_factor; }
//--- Advanced analytics
double GetValueAtRisk(double confidence = 0.95);
double GetExpectedShortfall(double confidence = 0.95);
double GetMaximumAdverseExcursion();
double GetMaximumFavorableExcursion();
double GetProfitability();
double GetConsistencyScore();
//--- Strategy analysis
void GetStrategyBreakdown(string &strategies[], double &profits[]);
void GetTimeAnalysis(int &hour_wins[], int &hour_losses[]);
void GetSymbolBreakdown(string &symbols[], double &profits[]);
//--- Period analysis
PerformanceMetrics GetPeriodMetrics(datetime from, datetime to);
PerformanceMetrics GetMonthlyMetrics(int year, int month);
PerformanceMetrics GetWeeklyMetrics();
//--- Reset functions
void ResetDaily();
void ResetAll();
//--- Export functions
bool ExportToCSV(string filename);
string GetSummaryReport();
//--- Statistical functions
double GetAverageTradeTime();
double GetWinLossRatio();
double GetPayoffRatio();
int GetTradingDays();
double GetTradesPerDay();
};
//+------------------------------------------------------------------+
//| Constructor |
//+------------------------------------------------------------------+
CPerformanceTracker::CPerformanceTracker()
{
m_history_size = 0;
m_max_history = 1000;
m_last_update = 0;
m_cache_valid = false;
m_curve_size = 0;
m_lookback_period = 252; // Trading days
m_risk_free_rate = 0.02; // 2% annual
m_daily_starting_balance = 0;
m_day_start = 0;
//--- Initialize metrics
ZeroMemory(m_metrics);
ZeroMemory(m_daily_metrics);
}
//+------------------------------------------------------------------+
//| Destructor |
//+------------------------------------------------------------------+
CPerformanceTracker::~CPerformanceTracker()
{
}
//+------------------------------------------------------------------+
//| Initialize tracker |
//+------------------------------------------------------------------+
bool CPerformanceTracker::Initialize(int max_history, int lookback)
{
m_max_history = max_history;
m_lookback_period = lookback;
//--- Allocate arrays
ArrayResize(m_history, m_max_history);
ArrayResize(m_returns, m_lookback_period);
ArrayResize(m_equity_curve, m_lookback_period * 2);
//--- Initialize daily tracking
m_daily_starting_balance = AccountInfoDouble(ACCOUNT_BALANCE);
m_day_start = TimeCurrent();
Print("PerformanceTracker initialized: MaxHistory=", m_max_history,
", Lookback=", m_lookback_period);
return true;
}
//+------------------------------------------------------------------+
//| Add closed trade to history |
//+------------------------------------------------------------------+
void CPerformanceTracker::AddTrade(const ManagedTrade &trade, double close_price)
{
//--- Check array capacity
if(m_history_size >= m_max_history)
{
//--- Shift array (remove oldest)
for(int i = 0; i < m_max_history - 1; i++)
m_history[i] = m_history[i + 1];
m_history_size = m_max_history - 1;
}
//--- Add new entry
TradeHistoryEntry entry;
entry.ticket = trade.ticket;
entry.symbol = trade.symbol;
entry.type = trade.type;
entry.open_time = trade.open_time;
entry.close_time = TimeCurrent();
entry.volume = trade.volume;
entry.open_price = trade.open_price;
entry.close_price = close_price;
entry.profit = trade.profit;
entry.commission = trade.commission;
entry.swap = trade.swap;
entry.risk_amount = trade.risk_amount;
entry.r_multiple = trade.r_multiple;
entry.duration_bars = trade.bars_in_trade;
entry.strategy = trade.entry_reason;
m_history[m_history_size] = entry;
m_history_size++;
//--- Invalidate cache
m_cache_valid = false;
//--- Update equity curve
UpdateEquityCurve(AccountInfoDouble(ACCOUNT_BALANCE));
//--- Update metrics if significant time passed
if(TimeCurrent() - m_last_update > 5)
{
UpdateBasicMetrics();
m_last_update = TimeCurrent();
}
}
//+------------------------------------------------------------------+
//| Get performance metrics with caching |
//+------------------------------------------------------------------+
PerformanceMetrics CPerformanceTracker::GetMetrics(bool force_update)
{
if(!m_cache_valid || force_update || TimeCurrent() - m_last_update > 30)
{
UpdateBasicMetrics();
UpdateAdvancedMetrics();
UpdateDrawdownMetrics();
CalculateRatios();
m_cache_valid = true;
m_last_update = TimeCurrent();
}
return m_metrics;
}
//+------------------------------------------------------------------+
//| Update basic performance metrics |
//+------------------------------------------------------------------+
void CPerformanceTracker::UpdateBasicMetrics()
{
//--- Reset counters
m_metrics.total_trades = m_history_size;
m_metrics.winning_trades = 0;
m_metrics.losing_trades = 0;
m_metrics.gross_profit = 0;
m_metrics.gross_loss = 0;
//--- Process all trades
for(int i = 0; i < m_history_size; i++)
{
double net_profit = m_history[i].profit + m_history[i].commission + m_history[i].swap;
if(net_profit > 0)
{
m_metrics.winning_trades++;
m_metrics.gross_profit += net_profit;
if(net_profit > m_metrics.largest_win)
m_metrics.largest_win = net_profit;
}
else
{
m_metrics.losing_trades++;
m_metrics.gross_loss += MathAbs(net_profit);
if(MathAbs(net_profit) > m_metrics.largest_loss)
m_metrics.largest_loss = MathAbs(net_profit);
}
}
//--- Calculate derived metrics
m_metrics.net_profit = m_metrics.gross_profit - m_metrics.gross_loss;
if(m_metrics.total_trades > 0)
{
m_metrics.win_rate = (double)m_metrics.winning_trades / m_metrics.total_trades * 100;
m_metrics.expectancy = m_metrics.net_profit / m_metrics.total_trades;
}
if(m_metrics.winning_trades > 0)
m_metrics.avg_win = m_metrics.gross_profit / m_metrics.winning_trades;
if(m_metrics.losing_trades > 0)
m_metrics.avg_loss = m_metrics.gross_loss / m_metrics.losing_trades;
if(m_metrics.gross_loss > 0)
m_metrics.profit_factor = m_metrics.gross_profit / m_metrics.gross_loss;
else
m_metrics.profit_factor = m_metrics.gross_profit > 0 ? 999.99 : 0;
if(m_metrics.avg_loss > 0)
m_metrics.risk_reward_ratio = m_metrics.avg_win / m_metrics.avg_loss;
}
//+------------------------------------------------------------------+
//| Update advanced metrics |
//+------------------------------------------------------------------+
void CPerformanceTracker::UpdateAdvancedMetrics()
{
if(m_history_size == 0) return;
//--- Consecutive tracking
int current_wins = 0;
int current_losses = 0;
m_metrics.max_consecutive_wins = 0;
m_metrics.max_consecutive_losses = 0;
//--- Trade duration
double total_duration = 0;
double win_duration = 0;
double loss_duration = 0;
int win_count = 0;
int loss_count = 0;
for(int i = 0; i < m_history_size; i++)
{
double net_profit = m_history[i].profit + m_history[i].commission + m_history[i].swap;
//--- Consecutive tracking
if(net_profit > 0)
{
current_wins++;
current_losses = 0;
if(current_wins > m_metrics.max_consecutive_wins)
m_metrics.max_consecutive_wins = current_wins;
win_duration += m_history[i].duration_bars;
win_count++;
}
else
{
current_losses++;
current_wins = 0;
if(current_losses > m_metrics.max_consecutive_losses)
m_metrics.max_consecutive_losses = current_losses;
loss_duration += m_history[i].duration_bars;
loss_count++;
}
total_duration += m_history[i].duration_bars;
}
//--- Average durations
if(m_history_size > 0)
m_metrics.avg_trade_duration = total_duration / m_history_size;
if(win_count > 0)
m_metrics.win_duration = win_duration / win_count;
if(loss_count > 0)
m_metrics.loss_duration = loss_duration / loss_count;
}
//+------------------------------------------------------------------+
//| Calculate Sharpe ratio |
//+------------------------------------------------------------------+
double CPerformanceTracker::CalculateSharpe()
{
if(m_curve_size < 30) return 0; // Need sufficient data
//--- Calculate returns
ArrayResize(m_returns, m_curve_size - 1);
for(int i = 1; i < m_curve_size; i++)
{
if(m_equity_curve[i-1] > 0)
m_returns[i-1] = (m_equity_curve[i] - m_equity_curve[i-1]) / m_equity_curve[i-1];
else
m_returns[i-1] = 0;
}
//--- Calculate mean and std dev
double mean = MathMean(m_returns);
double std_dev = MathStandardDeviation(m_returns);
if(std_dev == 0) return 0;
//--- Annualize (assuming daily returns)
double annual_return = mean * 252;
double annual_std = std_dev * MathSqrt(252);
return (annual_return - m_risk_free_rate) / annual_std;
}
//+------------------------------------------------------------------+
//| Update drawdown metrics |
//+------------------------------------------------------------------+
void CPerformanceTracker::UpdateDrawdownMetrics()
{
if(m_curve_size == 0) return;
double peak = m_equity_curve[0];
m_metrics.max_drawdown_amount = 0;
m_metrics.max_drawdown_percent = 0;
for(int i = 1; i < m_curve_size; i++)
{
if(m_equity_curve[i] > peak)
peak = m_equity_curve[i];
double drawdown = peak - m_equity_curve[i];
double drawdown_percent = (peak > 0) ? drawdown / peak * 100 : 0;
if(drawdown > m_metrics.max_drawdown_amount)
{
m_metrics.max_drawdown_amount = drawdown;
m_metrics.max_drawdown_percent = drawdown_percent;
}
}
//--- Current drawdown
double current_balance = AccountInfoDouble(ACCOUNT_BALANCE);
if(current_balance < peak)
{
m_metrics.current_drawdown = (peak - current_balance) / peak * 100;
}
else
{
m_metrics.current_drawdown = 0;
}
//--- Recovery factor
if(m_metrics.max_drawdown_amount > 0)
m_metrics.recovery_factor = m_metrics.net_profit / m_metrics.max_drawdown_amount;
}
//+------------------------------------------------------------------+
//| Update equity curve |
//+------------------------------------------------------------------+
void CPerformanceTracker::UpdateEquityCurve(double current_balance)
{
//--- Shift array if full
if(m_curve_size >= ArraySize(m_equity_curve))
{
for(int i = 0; i < m_curve_size - 1; i++)
m_equity_curve[i] = m_equity_curve[i + 1];
m_curve_size--;
}
//--- Add new value
m_equity_curve[m_curve_size] = current_balance;
m_curve_size++;
}
//+------------------------------------------------------------------+
//| Calculate all ratios |
//+------------------------------------------------------------------+
void CPerformanceTracker::CalculateRatios()
{
m_metrics.sharpe_ratio = CalculateSharpe();
m_metrics.sortino_ratio = CalculateSortino();
m_metrics.calmar_ratio = CalculateCalmar();
}
//+------------------------------------------------------------------+
//| Calculate Sortino ratio (downside deviation) |
//+------------------------------------------------------------------+
double CPerformanceTracker::CalculateSortino()
{
if(m_curve_size < 30) return 0;
//--- Calculate downside returns only
double negative_returns[];
int neg_count = 0;
for(int i = 0; i < ArraySize(m_returns); i++)
{
if(m_returns[i] < 0)
{
ArrayResize(negative_returns, neg_count + 1);
negative_returns[neg_count] = m_returns[i];
neg_count++;
}
}
if(neg_count == 0) return 999.99; // No downside
double mean_return = MathMean(m_returns);
double downside_dev = MathStandardDeviation(negative_returns);
if(downside_dev == 0) return 999.99;
//--- Annualize
double annual_return = mean_return * 252;
double annual_downside = downside_dev * MathSqrt(252);
return (annual_return - m_risk_free_rate) / annual_downside;
}
//+------------------------------------------------------------------+
//| Calculate Calmar ratio |
//+------------------------------------------------------------------+
double CPerformanceTracker::CalculateCalmar()
{
if(m_metrics.max_drawdown_percent == 0) return 0;
//--- Annualized return
int days = GetTradingDays();
if(days == 0) return 0;
double annual_return = (m_metrics.net_profit / m_daily_starting_balance) * (252.0 / days) * 100;
return annual_return / m_metrics.max_drawdown_percent;
}
//+------------------------------------------------------------------+
//| Get trading days |
//+------------------------------------------------------------------+
int CPerformanceTracker::GetTradingDays()
{
if(m_history_size == 0) return 0;
datetime first_trade = m_history[0].open_time;
datetime last_trade = m_history[m_history_size - 1].close_time;
int days = (int)((last_trade - first_trade) / 86400);
//--- Approximate trading days (exclude weekends)
return (int)(days * 5.0 / 7.0);
}
//+------------------------------------------------------------------+
//| Get strategy breakdown |
//+------------------------------------------------------------------+
void CPerformanceTracker::GetStrategyBreakdown(string &strategies[], double &profits[])
{
//--- Temporary storage
string temp_strategies[];
double temp_profits[];
int strategy_count = 0;
//--- Aggregate by strategy
for(int i = 0; i < m_history_size; i++)
{
string strategy = m_history[i].strategy;
double profit = m_history[i].profit + m_history[i].commission + m_history[i].swap;
//--- Find or add strategy
int index = -1;
for(int j = 0; j < strategy_count; j++)
{
if(temp_strategies[j] == strategy)
{
index = j;
break;
}
}
if(index >= 0)
{
temp_profits[index] += profit;
}
else
{
ArrayResize(temp_strategies, strategy_count + 1);
ArrayResize(temp_profits, strategy_count + 1);
temp_strategies[strategy_count] = strategy;
temp_profits[strategy_count] = profit;
strategy_count++;
}
}
//--- Copy to output arrays
ArrayResize(strategies, strategy_count);
ArrayResize(profits, strategy_count);
for(int i = 0; i < strategy_count; i++)
{
strategies[i] = temp_strategies[i];
profits[i] = temp_profits[i];
}
}
//+------------------------------------------------------------------+
//| Get summary report |
//+------------------------------------------------------------------+
string CPerformanceTracker::GetSummaryReport()
{
GetMetrics(); // Ensure metrics are current
string report = "=== PERFORMANCE SUMMARY ===\n";
report += StringFormat("Total Trades: %d (Win: %d, Loss: %d)\n",
m_metrics.total_trades, m_metrics.winning_trades, m_metrics.losing_trades);
report += StringFormat("Win Rate: %.2f%%\n", m_metrics.win_rate);
report += StringFormat("Net Profit: $%.2f\n", m_metrics.net_profit);
report += StringFormat("Profit Factor: %.2f\n", m_metrics.profit_factor);
report += StringFormat("Expectancy: $%.2f\n", m_metrics.expectancy);
report += StringFormat("Sharpe Ratio: %.2f\n", m_metrics.sharpe_ratio);
report += StringFormat("Max Drawdown: %.2f%%\n", m_metrics.max_drawdown_percent);
report += StringFormat("Recovery Factor: %.2f\n", m_metrics.recovery_factor);
return report;
}
#endif // PERFORMANCE_TRACKER_MQH