1174 lines
40 KiB
MQL5
1174 lines
40 KiB
MQL5
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| RiskManager_Impl.mqh |
|
||
|
|
//| Copyright 2025, EscapeEA |
|
||
|
|
//| https://www.escapeea.com |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
#property copyright "Copyright 2025, EscapeEA"
|
||
|
|
#property link "https://www.escapeea.com"
|
||
|
|
#property version "2.10"
|
||
|
|
#property strict
|
||
|
|
|
||
|
|
// Include necessary headers
|
||
|
|
#include <Trade\Trade.mqh>
|
||
|
|
#include <Trade\PositionInfo.mqh>
|
||
|
|
#include <Trade\DealInfo.mqh>
|
||
|
|
#include <Trade\OrderInfo.mqh>
|
||
|
|
#include <Trade\HistoryOrderInfo.mqh>
|
||
|
|
#include <Arrays\ArrayObj.mqh>
|
||
|
|
#include <Arrays\ArrayString.mqh>
|
||
|
|
#include <Arrays\List.mqh>
|
||
|
|
#include <Files\FileBin.mqh>
|
||
|
|
#include <Files\FileTxt.mqh>
|
||
|
|
#include <Indicators\Oscilators.mqh>
|
||
|
|
#include <Math\Stat\Math.mqh>
|
||
|
|
|
||
|
|
// Logging levels
|
||
|
|
enum ENUM_LOG_LEVEL
|
||
|
|
{
|
||
|
|
LOG_LEVEL_NONE = 0, // No logging
|
||
|
|
LOG_LEVEL_ERROR, // Only errors
|
||
|
|
LOG_LEVEL_WARNING, // Errors and warnings
|
||
|
|
LOG_LEVEL_INFO, // Basic information
|
||
|
|
LOG_LEVEL_DEBUG, // Detailed debug information
|
||
|
|
LOG_LEVEL_TRACE // Very detailed tracing
|
||
|
|
};
|
||
|
|
|
||
|
|
// Trade metrics structure
|
||
|
|
struct STradeMetrics
|
||
|
|
{
|
||
|
|
datetime timestamp; // Time of the trade
|
||
|
|
string symbol; // Symbol
|
||
|
|
double volume; // Trade volume
|
||
|
|
double entryPrice; // Entry price
|
||
|
|
double exitPrice; // Exit price
|
||
|
|
double pips; // Pips gained/lost
|
||
|
|
double profit; // Profit/loss in account currency
|
||
|
|
double drawdown; // Drawdown at time of trade
|
||
|
|
double volatility; // Market volatility (ATR)
|
||
|
|
double spread; // Spread at time of trade
|
||
|
|
int duration; // Trade duration in seconds
|
||
|
|
string comment; // Additional comments
|
||
|
|
|
||
|
|
// Default constructor
|
||
|
|
STradeMetrics() : timestamp(0), symbol(""), volume(0), entryPrice(0), exitPrice(0),
|
||
|
|
pips(0), profit(0), drawdown(0), volatility(0), spread(0), duration(0), comment("") {}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Performance metrics structure
|
||
|
|
struct SPerformanceMetrics
|
||
|
|
{
|
||
|
|
int totalTrades; // Total number of trades
|
||
|
|
int winningTrades; // Number of winning trades
|
||
|
|
double totalProfit; // Total profit
|
||
|
|
double totalLoss; // Total loss
|
||
|
|
double largestWin; // Largest winning trade
|
||
|
|
double largestLoss; // Largest losing trade
|
||
|
|
double maxDrawdown; // Maximum drawdown
|
||
|
|
double profitFactor; // Profit factor (gross profit / gross loss)
|
||
|
|
double recoveryFactor; // Net profit / max drawdown
|
||
|
|
double sharpeRatio; // Risk-adjusted return
|
||
|
|
double sortinoRatio; // Downside risk-adjusted return
|
||
|
|
double averageTrade; // Average trade result
|
||
|
|
double standardDeviation; // Standard deviation of returns
|
||
|
|
double winRate; // Win rate percentage
|
||
|
|
|
||
|
|
// Default constructor
|
||
|
|
SPerformanceMetrics() : totalTrades(0), winningTrades(0), totalProfit(0), totalLoss(0),
|
||
|
|
largestWin(0), largestLoss(0), maxDrawdown(0), profitFactor(0),
|
||
|
|
recoveryFactor(0), sharpeRatio(0), sortinoRatio(0), averageTrade(0),
|
||
|
|
standardDeviation(0), winRate(0) {}
|
||
|
|
};
|
||
|
|
|
||
|
|
// Forward declarations
|
||
|
|
class CSecurityManager;
|
||
|
|
class CTradeRiskRecord;
|
||
|
|
|
||
|
|
// Include the header file to get class declaration
|
||
|
|
#include "RiskManager.mqh"
|
||
|
|
|
||
|
|
// Circuit breaker thresholds
|
||
|
|
#define CIRCUIT_BREAKER_THRESHOLD 5 // Number of consecutive failures before tripping
|
||
|
|
#define CIRCUIT_BREAKER_TIMEOUT 300 // Timeout in seconds before reset attempt
|
||
|
|
#define MAX_RETRY_ATTEMPTS 3 // Maximum number of retry attempts
|
||
|
|
#define INITIAL_RETRY_DELAY 1000 // Initial retry delay in milliseconds
|
||
|
|
#define MAX_RETRY_DELAY 60000 // Maximum retry delay in milliseconds
|
||
|
|
|
||
|
|
// Metrics file constants
|
||
|
|
#define METRICS_FILE_PREFIX "RiskMetrics_"
|
||
|
|
#define MAX_METRICS_ENTRIES 10000 // Maximum number of metrics entries to keep in memory
|
||
|
|
|
||
|
|
// Log file constants
|
||
|
|
#define LOG_FILE_PREFIX "RiskManager_"
|
||
|
|
#define MAX_LOG_FILE_SIZE 10485760 // 10 MB max log file size
|
||
|
|
#define MAX_LOG_FILES 5 // Maximum number of log files to keep
|
||
|
|
|
||
|
|
// Define missing ENUM_ACCOUNT_INFO_DOUBLE values if not already defined
|
||
|
|
#ifndef ACCOUNT_COMMISSION
|
||
|
|
#define ACCOUNT_COMMISSION 16
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#ifndef ACCOUNT_SWAP
|
||
|
|
#define ACCOUNT_SWAP 32
|
||
|
|
#endif
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Constructor |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
CRiskManager::CRiskManager() :
|
||
|
|
m_security(NULL),
|
||
|
|
m_riskPerTrade(1.0),
|
||
|
|
m_dailyLossLimit(2.0),
|
||
|
|
m_maxOpenTrades(5),
|
||
|
|
m_maxOrdersPerDay(20),
|
||
|
|
m_useVolatilityAdjustment(true),
|
||
|
|
m_atrPeriod(14),
|
||
|
|
m_maxSpread(50.0),
|
||
|
|
m_circuitBreakerActive(false),
|
||
|
|
m_consecutiveFailures(0),
|
||
|
|
m_circuitBreakerTime(0),
|
||
|
|
m_logLevel(LOG_LEVEL_INFO),
|
||
|
|
m_enableMetrics(true),
|
||
|
|
m_enableDetailedLogging(false),
|
||
|
|
m_lastMetricsUpdate(0),
|
||
|
|
m_metricsUpdateInterval(60), // Update metrics every 60 seconds
|
||
|
|
m_retryAttempts(0),
|
||
|
|
m_nextRetryTime(0),
|
||
|
|
m_anomalyDetection(true),
|
||
|
|
m_maxLeverage(30),
|
||
|
|
m_dailyPnL(0.0),
|
||
|
|
m_todayOrders(0),
|
||
|
|
m_lastTradeDay(0),
|
||
|
|
m_equityHigh(0.0),
|
||
|
|
m_maxDrawdown(0.0),
|
||
|
|
m_openTrades(NULL),
|
||
|
|
m_watchlist(NULL),
|
||
|
|
m_lastError(""),
|
||
|
|
m_lastErrorSeverity(RISK_ERROR_NONE),
|
||
|
|
m_atrHandle(INVALID_HANDLE),
|
||
|
|
m_rateLimitHistory(NULL),
|
||
|
|
m_enableRateLimiting(true),
|
||
|
|
m_maxRequestsPerMinute(60),
|
||
|
|
m_ordersToday(0)
|
||
|
|
{
|
||
|
|
m_openTrades = new CArrayObj();
|
||
|
|
m_watchlist = new CArrayString();
|
||
|
|
m_security = new CSecurityManager();
|
||
|
|
m_rateLimitHistory = new CArrayObj();
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Destructor |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
CRiskManager::~CRiskManager()
|
||
|
|
{
|
||
|
|
if(CheckPointer(m_openTrades) == POINTER_DYNAMIC)
|
||
|
|
delete m_openTrades;
|
||
|
|
|
||
|
|
if(CheckPointer(m_watchlist) == POINTER_DYNAMIC)
|
||
|
|
delete m_watchlist;
|
||
|
|
|
||
|
|
if(CheckPointer(m_security) == POINTER_DYNAMIC)
|
||
|
|
delete m_security;
|
||
|
|
|
||
|
|
if(CheckPointer(m_rateLimitHistory) == POINTER_DYNAMIC)
|
||
|
|
delete m_rateLimitHistory;
|
||
|
|
|
||
|
|
if(m_atrHandle != INVALID_HANDLE)
|
||
|
|
IndicatorRelease(m_atrHandle);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Initialize the risk manager |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool CRiskManager::Initialize()
|
||
|
|
{
|
||
|
|
if(m_initialized)
|
||
|
|
{
|
||
|
|
m_lastError = "Risk manager already initialized";
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Initialize ATR indicator for volatility calculation
|
||
|
|
m_atrHandle = iATR(_Symbol, PERIOD_CURRENT, m_atrPeriod);
|
||
|
|
if(m_atrHandle == INVALID_HANDLE)
|
||
|
|
{
|
||
|
|
m_lastError = "Failed to create ATR indicator";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Initialize security manager
|
||
|
|
if(m_security == NULL)
|
||
|
|
{
|
||
|
|
m_security = new CSecurityManager();
|
||
|
|
}
|
||
|
|
|
||
|
|
if(!m_security.Initialize())
|
||
|
|
{
|
||
|
|
m_lastError = "Failed to initialize security manager: " + m_security.GetLastError();
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Initialize state
|
||
|
|
m_equityHigh = AccountInfoDouble(ACCOUNT_EQUITY);
|
||
|
|
m_lastTradeDay = TimeCurrent();
|
||
|
|
m_initialized = true;
|
||
|
|
|
||
|
|
// Load configuration
|
||
|
|
if(!LoadConfiguration())
|
||
|
|
{
|
||
|
|
m_lastError = "Warning: Using default configuration - " + m_lastError;
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_WARNING;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Deinitialize the risk manager |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void CRiskManager::Deinitialize()
|
||
|
|
{
|
||
|
|
if(m_atrHandle != INVALID_HANDLE)
|
||
|
|
{
|
||
|
|
IndicatorRelease(m_atrHandle);
|
||
|
|
m_atrHandle = INVALID_HANDLE;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(CheckPointer(m_security) == POINTER_DYNAMIC)
|
||
|
|
{
|
||
|
|
m_security.Deinitialize();
|
||
|
|
}
|
||
|
|
|
||
|
|
m_initialized = false;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate a trade request |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool CRiskManager::ValidateTrade(const string symbol, double volume, double price, double sl, double tp)
|
||
|
|
{
|
||
|
|
if(!m_initialized)
|
||
|
|
{
|
||
|
|
m_lastError = "Risk manager not initialized";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if trading is allowed
|
||
|
|
if(!IsTradingAllowed())
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// Check if symbol is in watchlist
|
||
|
|
if(m_watchlist.Total() > 0 && m_watchlist.SearchLinear(symbol) < 0)
|
||
|
|
{
|
||
|
|
m_lastError = "Symbol " + symbol + " not in watchlist";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_WARNING;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate lot size
|
||
|
|
if(!ValidateLotSize(symbol, volume))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// Validate spread
|
||
|
|
if(!ValidateSpread(symbol))
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// Check daily loss limit
|
||
|
|
if(!ValidateDailyLossLimit())
|
||
|
|
return false;
|
||
|
|
|
||
|
|
// Check circuit breaker
|
||
|
|
if(m_circuitBreakerActive)
|
||
|
|
{
|
||
|
|
m_lastError = "Trading suspended - circuit breaker active";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate lot size |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool CRiskManager::ValidateLotSize(const string symbol, double volume)
|
||
|
|
{
|
||
|
|
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||
|
|
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||
|
|
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||
|
|
|
||
|
|
if(volume < minLot || volume > maxLot)
|
||
|
|
{
|
||
|
|
m_lastError = StringFormat("Invalid lot size %.2f for %s (min: %.2f, max: %.2f)",
|
||
|
|
volume, symbol, minLot, maxLot);
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_WARNING;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if volume is a multiple of lot step
|
||
|
|
if(MathMod(volume, lotStep) > 0.0001) // Account for floating point precision
|
||
|
|
{
|
||
|
|
m_lastError = StringFormat("Lot size %.2f for %s must be a multiple of %.2f",
|
||
|
|
volume, symbol, lotStep);
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_WARNING;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate spread |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool CRiskManager::ValidateSpread(const string symbol)
|
||
|
|
{
|
||
|
|
double spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD);
|
||
|
|
if(spread > m_maxSpread)
|
||
|
|
{
|
||
|
|
m_lastError = StringFormat("Spread %.1f for %s exceeds maximum allowed (%.1f)",
|
||
|
|
spread, symbol, m_maxSpread);
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_WARNING;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Validate daily loss limit |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool CRiskManager::ValidateDailyLossLimit()
|
||
|
|
{
|
||
|
|
if(m_dailyPnL < 0 && MathAbs(m_dailyPnL) >= (AccountInfoDouble(ACCOUNT_BALANCE) * m_dailyLossLimit / 100.0))
|
||
|
|
{
|
||
|
|
m_lastError = StringFormat("Daily loss limit reached (%.2f%% of balance)", m_dailyLossLimit);
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Calculate position size based on risk parameters |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
double CRiskManager::CalculatePositionSize(const string symbol, const double entryPrice, const double stopLoss, const double riskAmount)
|
||
|
|
{
|
||
|
|
if(entryPrice <= 0 || stopLoss <= 0 || riskAmount <= 0)
|
||
|
|
{
|
||
|
|
m_lastError = "Invalid parameters for position sizing";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_WARNING;
|
||
|
|
return 0.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
double positionSize = 0.0;
|
||
|
|
|
||
|
|
// Select position sizing method
|
||
|
|
switch(m_positionSizingMethod)
|
||
|
|
{
|
||
|
|
case POSITION_SIZING_FIXED:
|
||
|
|
positionSize = m_fixedLotSize;
|
||
|
|
break;
|
||
|
|
|
||
|
|
case POSITION_SIZING_VOLATILITY:
|
||
|
|
positionSize = CalculateVolatilityBasedSize(symbol, entryPrice, stopLoss, riskAmount);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case POSITION_SIZING_KELLY:
|
||
|
|
{
|
||
|
|
double winRate = CalculateWinRate(symbol);
|
||
|
|
double winLossRatio = CalculateWinLossRatio(symbol);
|
||
|
|
positionSize = CalculateKellySize(symbol, entryPrice, stopLoss, winRate, winLossRatio);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case POSITION_SIZING_OPTIMAL_F:
|
||
|
|
{
|
||
|
|
double winRate = CalculateWinRate(symbol);
|
||
|
|
double winLossRatio = CalculateWinLossRatio(symbol);
|
||
|
|
positionSize = CalculateOptimalFSize(symbol, entryPrice, stopLoss, winRate, winLossRatio);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case POSITION_SIZING_HYBRID:
|
||
|
|
{
|
||
|
|
double winRate = CalculateWinRate(symbol);
|
||
|
|
double winLossRatio = CalculateWinLossRatio(symbol);
|
||
|
|
positionSize = CalculateHybridSize(symbol, entryPrice, stopLoss, riskAmount, winRate, winLossRatio);
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
|
||
|
|
case POSITION_SIZING_RISK_BASED:
|
||
|
|
default:
|
||
|
|
positionSize = CalculateRiskBasedSize(symbol, entryPrice, stopLoss, riskAmount);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Apply position scaling based on account growth if enabled
|
||
|
|
if(m_positionScaling != SCALING_NONE)
|
||
|
|
{
|
||
|
|
double initialBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||
|
|
double currentBalance = AccountInfoDouble(ACCOUNT_EQUITY);
|
||
|
|
double growthFactor = (currentBalance - initialBalance) / initialBalance;
|
||
|
|
positionSize = ScalePositionSize(positionSize, growthFactor);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Apply position size limits
|
||
|
|
positionSize = MathMax(m_minPositionSize, MathMin(positionSize, m_maxPositionSize));
|
||
|
|
|
||
|
|
// Round to nearest step
|
||
|
|
if(m_positionSizeStep > 0)
|
||
|
|
{
|
||
|
|
positionSize = MathRound(positionSize / m_positionSizeStep) * m_positionSizeStep;
|
||
|
|
}
|
||
|
|
|
||
|
|
return NormalizeLotSize(positionSize);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Calculate position size based on volatility (ATR) |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
double CRiskManager::CalculateVolatilityBasedSize(const string symbol, const double entryPrice, const double stopLoss, const double riskAmount)
|
||
|
|
{
|
||
|
|
double atrValue = GetATRValue(symbol, PERIOD_CURRENT, (int)m_volatilityPeriod);
|
||
|
|
|
||
|
|
if(atrValue <= 0)
|
||
|
|
{
|
||
|
|
// Fall back to standard risk-based if ATR is not available
|
||
|
|
return CalculateRiskBasedSize(symbol, entryPrice, stopLoss, riskAmount);
|
||
|
|
}
|
||
|
|
|
||
|
|
double stopDistance = MathAbs(entryPrice - stopLoss);
|
||
|
|
double atrMultiple = stopDistance / atrValue;
|
||
|
|
|
||
|
|
// Adjust risk amount based on volatility
|
||
|
|
double adjustedRisk = riskAmount * (1.0 + (1.0 - 1.0 / (1.0 + atrMultiple)) * m_volatilityMultiplier);
|
||
|
|
|
||
|
|
return CalculateRiskBasedSize(symbol, entryPrice, stopLoss, adjustedRisk);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Calculate position size using Kelly Criterion |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
double CRiskManager::CalculateKellySize(const string symbol, const double entryPrice, const double stopLoss,
|
||
|
|
const double winRate, const double winLossRatio)
|
||
|
|
{
|
||
|
|
if(winRate <= 0 || winRate >= 1.0 || winLossRatio <= 0)
|
||
|
|
{
|
||
|
|
// Fall back to standard risk-based if invalid parameters
|
||
|
|
return CalculateRiskBasedSize(symbol, entryPrice, stopLoss, m_riskPerTrade);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Kelly formula: f* = (bp - q) / b
|
||
|
|
// where: f* = fraction of current bankroll to wager
|
||
|
|
// b = net odds received on wager (win amount / loss amount)
|
||
|
|
// p = probability of winning
|
||
|
|
// q = probability of losing (1 - p)
|
||
|
|
double kellyFraction = (winRate * winLossRatio - (1.0 - winRate)) / winLossRatio;
|
||
|
|
|
||
|
|
// Apply user-defined fraction of Kelly (0.0 to 1.0)
|
||
|
|
kellyFraction = MathMax(0.0, MathMin(kellyFraction * m_kellyFraction, 1.0));
|
||
|
|
|
||
|
|
// Calculate position size based on Kelly fraction
|
||
|
|
double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||
|
|
double riskAmount = accountBalance * kellyFraction;
|
||
|
|
|
||
|
|
return CalculateRiskBasedSize(symbol, entryPrice, stopLoss, riskAmount);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Calculate position size using Optimal F |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
double CRiskManager::CalculateOptimalFSize(const string symbol, const double entryPrice, const double stopLoss,
|
||
|
|
const double winRate, const double winLossRatio)
|
||
|
|
{
|
||
|
|
if(winRate <= 0 || winRate >= 1.0 || winLossRatio <= 0)
|
||
|
|
{
|
||
|
|
// Fall back to standard risk-based if invalid parameters
|
||
|
|
return CalculateRiskBasedSize(symbol, entryPrice, stopLoss, m_riskPerTrade);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Simplified Optimal F calculation
|
||
|
|
// Optimal f = ( (1 + winLossRatio) * winRate - 1 ) / winLossRatio
|
||
|
|
double optimalF = ((1.0 + winLossRatio) * winRate - 1.0) / winLossRatio;
|
||
|
|
|
||
|
|
// Apply user-defined fraction of Optimal F (0.0 to 1.0)
|
||
|
|
optimalF = MathMax(0.0, MathMin(optimalF * m_optimalFFraction, 1.0));
|
||
|
|
|
||
|
|
// Calculate position size based on Optimal F
|
||
|
|
double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||
|
|
double riskAmount = accountBalance * optimalF;
|
||
|
|
|
||
|
|
return CalculateRiskBasedSize(symbol, entryPrice, stopLoss, riskAmount);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Calculate position size using hybrid approach |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
double CRiskManager::CalculateHybridSize(const string symbol, const double entryPrice, const double stopLoss,
|
||
|
|
const double riskAmount, const double winRate, const double winLossRatio)
|
||
|
|
{
|
||
|
|
// Calculate position sizes using different methods
|
||
|
|
double riskBased = CalculateRiskBasedSize(symbol, entryPrice, stopLoss, riskAmount);
|
||
|
|
double volatilityBased = CalculateVolatilityBasedSize(symbol, entryPrice, stopLoss, riskAmount);
|
||
|
|
|
||
|
|
// If we have valid win rate and win/loss ratio, include Kelly and Optimal F
|
||
|
|
if(winRate > 0 && winRate < 1.0 && winLossRatio > 0)
|
||
|
|
{
|
||
|
|
double kellySize = CalculateKellySize(symbol, entryPrice, stopLoss, winRate, winLossRatio);
|
||
|
|
double optimalFSize = CalculateOptimalFSize(symbol, entryPrice, stopLoss, winRate, winLossRatio);
|
||
|
|
|
||
|
|
// Use weighted average of all methods
|
||
|
|
return (riskBased * 0.2 + volatilityBased * 0.3 + kellySize * 0.25 + optimalFSize * 0.25);
|
||
|
|
}
|
||
|
|
|
||
|
|
// Fall back to average of risk-based and volatility-based
|
||
|
|
return (riskBased + volatilityBased) / 2.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Scale position size based on account growth |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
double CRiskManager::ScalePositionSize(const double size, const double accountGrowth)
|
||
|
|
{
|
||
|
|
if(accountGrowth <= 0 || m_positionScaling == SCALING_NONE)
|
||
|
|
{
|
||
|
|
return size;
|
||
|
|
}
|
||
|
|
|
||
|
|
double scaleFactor = 1.0;
|
||
|
|
|
||
|
|
switch(m_positionScaling)
|
||
|
|
{
|
||
|
|
case SCALING_LINEAR:
|
||
|
|
scaleFactor = 1.0 + accountGrowth;
|
||
|
|
break;
|
||
|
|
|
||
|
|
case SCALING_SQRT:
|
||
|
|
scaleFactor = MathSqrt(1.0 + accountGrowth);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case SCALING_LOG:
|
||
|
|
scaleFactor = 1.0 + MathLog(1.0 + accountGrowth);
|
||
|
|
break;
|
||
|
|
|
||
|
|
case SCALING_NONE:
|
||
|
|
default:
|
||
|
|
return size;
|
||
|
|
}
|
||
|
|
|
||
|
|
return size * scaleFactor;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Calculate position size based on risk (base method) |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
double CRiskManager::CalculateRiskBasedSize(const string symbol, const double entryPrice,
|
||
|
|
const double stopLoss, const double riskAmount)
|
||
|
|
{
|
||
|
|
if(entryPrice <= 0 || stopLoss <= 0 || riskAmount <= 0)
|
||
|
|
{
|
||
|
|
m_lastError = "Invalid parameters for risk-based position sizing";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_WARNING;
|
||
|
|
return 0.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
||
|
|
double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
||
|
|
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
||
|
|
|
||
|
|
if(tickSize <= 0 || tickValue <= 0 || lotStep <= 0)
|
||
|
|
{
|
||
|
|
m_lastError = "Invalid symbol parameters for position sizing";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_WARNING;
|
||
|
|
return 0.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
double stopDistance = MathAbs(entryPrice - stopLoss) / tickSize;
|
||
|
|
double riskPerLot = stopDistance * tickValue;
|
||
|
|
|
||
|
|
if(riskPerLot <= 0)
|
||
|
|
{
|
||
|
|
m_lastError = "Invalid risk per lot calculation";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_WARNING;
|
||
|
|
return 0.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
double lots = riskAmount / riskPerLot;
|
||
|
|
|
||
|
|
// Normalize to lot step
|
||
|
|
lots = MathFloor(lots / lotStep) * lotStep;
|
||
|
|
|
||
|
|
// Check against symbol limits
|
||
|
|
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
||
|
|
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
||
|
|
|
||
|
|
return MathMax(minLot, MathMin(lots, maxLot));
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Calculate risk amount based on account balance and risk percent |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
double CRiskManager::CalculateRiskAmount(double accountBalance, double riskPercent)
|
||
|
|
{
|
||
|
|
if(accountBalance <= 0 || riskPercent <= 0)
|
||
|
|
return 0.0;
|
||
|
|
|
||
|
|
return accountBalance * riskPercent / 100.0;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Calculate risk for a trade |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
double CRiskManager::CalculateTradeRisk(const string symbol, double volume, double entryPrice, double stopLoss)
|
||
|
|
{
|
||
|
|
if(entryPrice <= 0 || stopLoss <= 0 || volume <= 0)
|
||
|
|
return 0.0;
|
||
|
|
|
||
|
|
double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
||
|
|
double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
||
|
|
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
||
|
|
|
||
|
|
if(tickSize == 0 || tickValue == 0 || point == 0)
|
||
|
|
return 0.0;
|
||
|
|
|
||
|
|
double riskPoints = MathAbs(entryPrice - stopLoss) / point;
|
||
|
|
return riskPoints * tickValue * volume / (tickSize / point);
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Get current risk exposure |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
double CRiskManager::GetCurrentRiskExposure()
|
||
|
|
{
|
||
|
|
if(!m_initialized)
|
||
|
|
return 0.0;
|
||
|
|
|
||
|
|
double totalRisk = 0.0;
|
||
|
|
|
||
|
|
// Calculate risk from open positions
|
||
|
|
for(int i = 0; i < m_openTrades.Total(); i++)
|
||
|
|
{
|
||
|
|
CTradeRiskRecord* trade = m_openTrades.At(i);
|
||
|
|
if(trade != NULL)
|
||
|
|
{
|
||
|
|
totalRisk += CalculateTradeRisk(trade.symbol, trade.volume, trade.entry, trade.sl);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return totalRisk;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Check circuit breaker status |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool CRiskManager::CheckCircuitBreaker()
|
||
|
|
{
|
||
|
|
if(!m_initialized)
|
||
|
|
{
|
||
|
|
m_lastError = "Risk manager not initialized";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
||
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
||
|
|
double drawdown = 1.0 - (equity / m_equityHigh);
|
||
|
|
|
||
|
|
if(drawdown >= m_circuitBreakerLevel)
|
||
|
|
{
|
||
|
|
m_circuitBreakerActive = true;
|
||
|
|
m_circuitBreakerLevel = drawdown;
|
||
|
|
m_lastError = StringFormat("Circuit breaker triggered at %.2f%% drawdown", drawdown * 100.0);
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Reset circuit breaker |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void CRiskManager::ResetCircuitBreaker()
|
||
|
|
{
|
||
|
|
m_circuitBreakerActive = false;
|
||
|
|
m_equityHigh = AccountInfoDouble(ACCOUNT_EQUITY);
|
||
|
|
m_lastError = "Circuit breaker reset";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_NONE;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Load configuration from file |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool CRiskManager::LoadConfiguration()
|
||
|
|
{
|
||
|
|
string filename = "RiskManager.cfg";
|
||
|
|
int handle = FileOpen(filename, FILE_READ|FILE_BIN|FILE_COMMON);
|
||
|
|
|
||
|
|
if(handle == INVALID_HANDLE)
|
||
|
|
{
|
||
|
|
m_lastError = "No configuration file found, using defaults";
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Read configuration values
|
||
|
|
m_riskPerTrade = FileReadDouble(handle);
|
||
|
|
m_dailyLossLimit = FileReadDouble(handle);
|
||
|
|
m_maxOpenTrades = (int)FileReadInteger(handle);
|
||
|
|
m_maxOrdersPerDay = (int)FileReadInteger(handle);
|
||
|
|
m_useVolatilityAdjustment = FileReadInteger(handle) != 0;
|
||
|
|
m_atrPeriod = (int)FileReadInteger(handle);
|
||
|
|
m_maxSpread = FileReadDouble(handle);
|
||
|
|
m_anomalyDetection = FileReadInteger(handle) != 0;
|
||
|
|
m_maxLeverage = (int)FileReadInteger(handle);
|
||
|
|
m_enableRateLimiting = FileReadInteger(handle) != 0;
|
||
|
|
m_maxRequestsPerMinute = (int)FileReadInteger(handle);
|
||
|
|
|
||
|
|
FileClose(handle);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Save configuration to file |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool CRiskManager::SaveConfiguration()
|
||
|
|
{
|
||
|
|
if(!m_initialized)
|
||
|
|
{
|
||
|
|
m_lastError = "Risk manager not initialized";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
string filename = "RiskManager.cfg";
|
||
|
|
int handle = FileOpen(filename, FILE_WRITE|FILE_BIN|FILE_COMMON);
|
||
|
|
|
||
|
|
if(handle == INVALID_HANDLE)
|
||
|
|
{
|
||
|
|
m_lastError = "Failed to open configuration file for writing";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_WARNING;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Write configuration values
|
||
|
|
FileWriteDouble(handle, m_riskPerTrade);
|
||
|
|
FileWriteDouble(handle, m_dailyLossLimit);
|
||
|
|
FileWriteInteger(handle, m_maxOpenTrades);
|
||
|
|
FileWriteInteger(handle, m_maxOrdersPerDay);
|
||
|
|
FileWriteInteger(handle, m_useVolatilityAdjustment ? 1 : 0);
|
||
|
|
FileWriteInteger(handle, m_atrPeriod);
|
||
|
|
FileWriteDouble(handle, m_maxSpread);
|
||
|
|
FileWriteInteger(handle, m_anomalyDetection ? 1 : 0);
|
||
|
|
FileWriteInteger(handle, m_maxLeverage);
|
||
|
|
FileWriteInteger(handle, m_enableRateLimiting ? 1 : 0);
|
||
|
|
FileWriteInteger(handle, m_maxRequestsPerMinute);
|
||
|
|
|
||
|
|
FileClose(handle);
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Event handler for tick events |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void CRiskManager::OnTick()
|
||
|
|
{
|
||
|
|
if(!m_initialized)
|
||
|
|
return;
|
||
|
|
|
||
|
|
// Update equity high water mark
|
||
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
||
|
|
if(equity > m_equityHigh)
|
||
|
|
m_equityHigh = equity;
|
||
|
|
|
||
|
|
// Check for new trading day
|
||
|
|
MqlDateTime dt;
|
||
|
|
TimeCurrent(dt);
|
||
|
|
MqlDateTime lastDt;
|
||
|
|
TimeToStruct(m_lastTradeDay, lastDt);
|
||
|
|
|
||
|
|
if(dt.day != lastDt.day || dt.mon != lastDt.mon || dt.year != lastDt.year)
|
||
|
|
{
|
||
|
|
// New trading day - reset daily counters
|
||
|
|
m_todayOrders = 0;
|
||
|
|
m_dailyPnL = 0;
|
||
|
|
m_lastTradeDay = TimeCurrent();
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check circuit breaker
|
||
|
|
CheckCircuitBreaker();
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Event handler for trade events |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void CRiskManager::OnTrade()
|
||
|
|
{
|
||
|
|
if(!m_initialized)
|
||
|
|
return;
|
||
|
|
|
||
|
|
// Update open trades list
|
||
|
|
UpdateOpenTrades();
|
||
|
|
|
||
|
|
// Update daily P&L
|
||
|
|
UpdateDailyPnL();
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Update list of open trades |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void CRiskManager::UpdateOpenTrades()
|
||
|
|
{
|
||
|
|
if(!m_initialized)
|
||
|
|
return;
|
||
|
|
|
||
|
|
m_openTrades.Clear();
|
||
|
|
|
||
|
|
// Get all open positions
|
||
|
|
for(int i = 0; i < PositionsTotal(); i++)
|
||
|
|
{
|
||
|
|
ulong ticket = PositionGetTicket(i);
|
||
|
|
if(ticket > 0)
|
||
|
|
{
|
||
|
|
CTradeRiskRecord* trade = new CTradeRiskRecord();
|
||
|
|
trade.ticket = ticket;
|
||
|
|
trade.symbol = PositionGetString(POSITION_SYMBOL);
|
||
|
|
trade.volume = PositionGetDouble(POSITION_VOLUME);
|
||
|
|
trade.entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
||
|
|
trade.sl = PositionGetDouble(POSITION_SL);
|
||
|
|
trade.tp = PositionGetDouble(POSITION_TP);
|
||
|
|
trade.risk = CalculateTradeRisk(trade.symbol, trade.volume, trade.entry, trade.sl);
|
||
|
|
trade.openTime = (datetime)PositionGetInteger(POSITION_TIME);
|
||
|
|
|
||
|
|
m_openTrades.Add(trade);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Update daily P&L |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void CRiskManager::UpdateDailyPnL()
|
||
|
|
{
|
||
|
|
if(!m_initialized)
|
||
|
|
return;
|
||
|
|
|
||
|
|
m_dailyPnL = 0.0;
|
||
|
|
|
||
|
|
// Calculate P&L for today's closed trades
|
||
|
|
HistorySelect(TimeCurrent() - 86400, TimeCurrent());
|
||
|
|
int totalDeals = HistoryDealsTotal();
|
||
|
|
|
||
|
|
for(int i = 0; i < totalDeals; i++)
|
||
|
|
{
|
||
|
|
ulong ticket = HistoryDealGetTicket(i);
|
||
|
|
if(ticket > 0)
|
||
|
|
{
|
||
|
|
datetime dealTime = (datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
|
||
|
|
if(TimeDay(dealTime) == TimeDay(TimeCurrent()))
|
||
|
|
{
|
||
|
|
double profit = HistoryDealGetDouble(ticket, DEAL_PROFIT) +
|
||
|
|
HistoryDealGetDouble(ticket, DEAL_COMMISSION) +
|
||
|
|
HistoryDealGetDouble(ticket, DEAL_SWAP);
|
||
|
|
m_dailyPnL += profit;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Event handler for trade transactions |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void CRiskManager::OnTradeTransaction(const MqlTradeTransaction& trans,
|
||
|
|
const MqlTradeRequest& request,
|
||
|
|
const MqlTradeResult& result)
|
||
|
|
{
|
||
|
|
if(!m_initialized)
|
||
|
|
return;
|
||
|
|
|
||
|
|
// Increment order counter for the day
|
||
|
|
if(trans.type == TRADE_TRANSACTION_ORDER_ADD ||
|
||
|
|
trans.type == TRADE_TRANSACTION_ORDER_UPDATE ||
|
||
|
|
trans.type == TRADE_TRANSACTION_ORDER_DELETE)
|
||
|
|
{
|
||
|
|
m_todayOrders++;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Update open trades if position was opened/closed
|
||
|
|
if(trans.type == TRADE_TRANSACTION_DEAL_ADD)
|
||
|
|
{
|
||
|
|
UpdateOpenTrades();
|
||
|
|
UpdateDailyPnL();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Check if trading is allowed |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool CRiskManager::IsTradingAllowed()
|
||
|
|
{
|
||
|
|
if(!m_initialized)
|
||
|
|
{
|
||
|
|
m_lastError = "Risk manager not initialized";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check circuit breaker
|
||
|
|
if(m_circuitBreakerActive)
|
||
|
|
{
|
||
|
|
m_lastError = "Circuit breaker active";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check security manager
|
||
|
|
if(m_security != NULL && !m_security.IsTradingAllowed())
|
||
|
|
{
|
||
|
|
m_lastError = "Security restrictions: " + m_security.GetLastError();
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check daily order limit
|
||
|
|
if(m_todayOrders >= m_maxOrdersPerDay)
|
||
|
|
{
|
||
|
|
m_lastError = "Daily order limit reached";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check open trades limit
|
||
|
|
if(m_openTrades.Total() >= m_maxOpenTrades)
|
||
|
|
{
|
||
|
|
m_lastError = "Maximum number of open trades reached";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_WARNING;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Save risk manager state |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool CRiskManager::Save(const int handle)
|
||
|
|
{
|
||
|
|
if(handle == INVALID_HANDLE)
|
||
|
|
{
|
||
|
|
m_lastError = "Invalid file handle";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_WARNING;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Save state variables
|
||
|
|
FileWriteLong(handle, m_lastTradeDay);
|
||
|
|
FileWriteDouble(handle, m_equityHigh);
|
||
|
|
FileWriteDouble(handle, m_dailyPnL);
|
||
|
|
FileWriteInteger(handle, m_todayOrders);
|
||
|
|
FileWriteInteger(handle, m_circuitBreakerActive ? 1 : 0);
|
||
|
|
FileWriteDouble(handle, m_circuitBreakerLevel);
|
||
|
|
FileWriteInteger(handle, m_ordersToday);
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Load risk manager state |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool CRiskManager::Load(const int handle)
|
||
|
|
{
|
||
|
|
if(handle == INVALID_HANDLE)
|
||
|
|
{
|
||
|
|
m_lastError = "Invalid file handle";
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_WARNING;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Load state variables
|
||
|
|
m_lastTradeDay = (datetime)FileReadLong(handle);
|
||
|
|
m_equityHigh = FileReadDouble(handle);
|
||
|
|
m_dailyPnL = FileReadDouble(handle);
|
||
|
|
m_todayOrders = (int)FileReadInteger(handle);
|
||
|
|
m_circuitBreakerActive = FileReadInteger(handle) != 0;
|
||
|
|
m_circuitBreakerLevel = FileReadDouble(handle);
|
||
|
|
m_ordersToday = (int)FileReadInteger(handle);
|
||
|
|
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Update symbol cache with latest market data |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void CRiskManager::UpdateSymbolCache(const string symbol)
|
||
|
|
{
|
||
|
|
// Implementation for updating symbol cache
|
||
|
|
// This is a placeholder - actual implementation would depend on requirements
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Check if rate limit is exceeded |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
bool CRiskManager::CheckRateLimit()
|
||
|
|
{
|
||
|
|
// Thread-safe section
|
||
|
|
CCriticalLock lock(m_cs);
|
||
|
|
|
||
|
|
// Check if rate limiting is enabled
|
||
|
|
if(!m_enableRateLimiting)
|
||
|
|
{
|
||
|
|
m_lastErrorCode = RISK_ERROR_NONE;
|
||
|
|
return true; // Rate limiting is disabled
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get current time once to ensure consistency
|
||
|
|
datetime currentTime = TimeCurrent();
|
||
|
|
MqlDateTime timeStruct;
|
||
|
|
if(!TimeToStruct(currentTime, timeStruct))
|
||
|
|
{
|
||
|
|
m_lastError = FormatError("Failed to get time structure", RISK_ERROR_INVALID_STATE);
|
||
|
|
m_lastErrorCode = RISK_ERROR_INVALID_STATE;
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate configuration
|
||
|
|
if(!ValidateConfig())
|
||
|
|
{
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Validate member variables
|
||
|
|
if(CheckPointer(m_rateLimitHistory) == POINTER_INVALID)
|
||
|
|
{
|
||
|
|
m_lastError = FormatError("Rate limit history not initialized", RISK_ERROR_INVALID_STATE);
|
||
|
|
m_lastErrorCode = RISK_ERROR_INVALID_STATE;
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Clean up old entries from the rate limit history
|
||
|
|
for(int i = m_rateLimitHistory.Total() - 1; i >= 0; i--)
|
||
|
|
{
|
||
|
|
CTradeRiskRecord* record = m_rateLimitHistory.At(i);
|
||
|
|
if(CheckPointer(record) == POINTER_INVALID)
|
||
|
|
{
|
||
|
|
m_rateLimitHistory.Delete(i);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Remove entries older than our rate limit window (1 minute)
|
||
|
|
if(currentTime - record.Time() > 60) // 60 seconds window
|
||
|
|
{
|
||
|
|
CTradeRiskRecord* oldRecord = m_rateLimitHistory.Detach(i);
|
||
|
|
if(CheckPointer(oldRecord) == POINTER_DYNAMIC)
|
||
|
|
delete oldRecord;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if we've exceeded the maximum allowed requests per minute
|
||
|
|
if(m_rateLimitHistory.Total() >= m_maxRequestsPerMinute)
|
||
|
|
{
|
||
|
|
m_lastError = FormatError("Rate limit exceeded: " +
|
||
|
|
IntegerToString(m_rateLimitHistory.Total()) +
|
||
|
|
" requests in the last minute",
|
||
|
|
RISK_ERROR_RATE_LIMIT);
|
||
|
|
m_lastErrorCode = RISK_ERROR_RATE_LIMIT;
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_WARNING;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if we've exceeded the maximum concurrent open trades
|
||
|
|
int totalPositions = PositionsTotal();
|
||
|
|
if(totalPositions < 0) // Handle error case
|
||
|
|
{
|
||
|
|
m_lastError = FormatError("Failed to get open positions count", RISK_ERROR_INVALID_STATE);
|
||
|
|
m_lastErrorCode = RISK_ERROR_INVALID_STATE;
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
if(totalPositions >= m_maxOpenTrades)
|
||
|
|
{
|
||
|
|
m_lastError = FormatError("Maximum open trades limit reached: " +
|
||
|
|
IntegerToString(m_maxOpenTrades),
|
||
|
|
RISK_ERROR_TRADE_LIMIT);
|
||
|
|
m_lastErrorCode = RISK_ERROR_TRADE_LIMIT;
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if we've exceeded daily order limit with boundary check
|
||
|
|
if(m_ordersToday < 0)
|
||
|
|
m_ordersToday = 0; // Fix corrupted counter
|
||
|
|
|
||
|
|
if(m_ordersToday >= m_maxOrdersPerDay)
|
||
|
|
{
|
||
|
|
m_lastError = FormatError("Daily order limit reached: " +
|
||
|
|
IntegerToString(m_ordersToday) + " of " +
|
||
|
|
IntegerToString(m_maxOrdersPerDay),
|
||
|
|
RISK_ERROR_DAILY_LIMIT);
|
||
|
|
m_lastErrorCode = RISK_ERROR_DAILY_LIMIT;
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add current request to rate limit history with memory safety
|
||
|
|
CTradeRiskRecord* newRecord = new CTradeRiskRecord();
|
||
|
|
if(CheckPointer(newRecord) == POINTER_INVALID)
|
||
|
|
{
|
||
|
|
m_lastError = FormatError("Failed to allocate memory for rate limit tracking",
|
||
|
|
RISK_ERROR_MEMORY);
|
||
|
|
m_lastErrorCode = RISK_ERROR_MEMORY;
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
newRecord.SetTime(currentTime);
|
||
|
|
if(m_rateLimitHistory.Add(newRecord) < 0)
|
||
|
|
{
|
||
|
|
delete newRecord;
|
||
|
|
m_lastError = FormatError("Failed to track rate limit", RISK_ERROR_INVALID_STATE);
|
||
|
|
m_lastErrorCode = RISK_ERROR_INVALID_STATE;
|
||
|
|
m_lastErrorSeverity = RISK_ERROR_CRITICAL;
|
||
|
|
return false;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Reset daily counters at the start of a new trading day
|
||
|
|
if(timeStruct.day != m_lastTradeDay)
|
||
|
|
{
|
||
|
|
m_lastTradeDay = timeStruct.day;
|
||
|
|
m_ordersToday = 0;
|
||
|
|
m_todayOrders = 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
m_lastErrorCode = RISK_ERROR_NONE;
|
||
|
|
return true;
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Adversarial testing: Simulate slippage |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
#ifdef ADVERSARIAL_TESTING
|
||
|
|
void CRiskManager::SimulateSlippage()
|
||
|
|
{
|
||
|
|
// Implementation for slippage simulation in testing
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Adversarial testing: Simulate network latency |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void CRiskManager::SimulateNetworkLatency()
|
||
|
|
{
|
||
|
|
// Implementation for network latency simulation in testing
|
||
|
|
}
|
||
|
|
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
//| Adversarial testing: Inject fault |
|
||
|
|
//+------------------------------------------------------------------+
|
||
|
|
void CRiskManager::InjectFault(const string component, const string type)
|
||
|
|
{
|
||
|
|
// Implementation for fault injection in testing
|
||
|
|
}
|
||
|
|
#endif
|