1845 lines
67 KiB
MQL5
1845 lines
67 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| RiskManager.mqh |
|
|
//| Copyright 2025, EscapeEA |
|
|
//| https://www.escapeea.com |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2025, EscapeEA"
|
|
#property link "https://www.escapeea.com"
|
|
#property version "2.00"
|
|
#property strict
|
|
|
|
#include <Trade\Trade.mqh>
|
|
#include <Trade\PositionInfo.mqh>
|
|
#include <Trade\DealInfo.mqh>
|
|
#include <Arrays\ArrayObj.mqh>
|
|
#include <Arrays\ArrayString.mqh>
|
|
// Forward declarations to avoid circular dependencies
|
|
class CSecurityEnhancements;
|
|
class CTradeRecord;
|
|
|
|
// Risk limits and constraints
|
|
#define MAX_RISK_PER_TRADE 5.0 // Maximum allowed risk per trade (%)
|
|
#define MAX_DAILY_RISK 10.0 // Maximum daily risk limit (%)
|
|
#define MAX_POSITIONS 20 // Maximum open positions
|
|
#define MAX_ORDERS_PER_DAY 100 // Maximum orders per day
|
|
#define VOLATILITY_LOOKBACK 14 // Period for volatility calculation
|
|
#define MIN_ACCOUNT_BALANCE 100.0 // Minimum account balance to trade
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Risk Profile Configuration |
|
|
//+------------------------------------------------------------------+
|
|
input group "=== Risk Management Settings ==="
|
|
input double InpRiskPerTrade = 1.0; // Risk per trade (% of balance, 0.1-5.0)
|
|
input double InpDailyLossLimit = 5.0; // Max daily loss (% of balance, 1.0-10.0)
|
|
input int InpMaxOpenTrades = 5; // Max open trades (1-20)
|
|
input int InpMaxOrdersPerDay = 20; // Max orders per day (1-100)
|
|
input bool InpUseVolatilityAdjustment = true; // Adjust position size by volatility
|
|
input int InpAtrPeriod = 14; // ATR period for volatility (5-50)
|
|
// Max spread is now defined in SecurityEnhancements.mqh
|
|
input bool InpEnableCircuitBreaker = true; // Enable circuit breaker for extreme conditions
|
|
input double InpCircuitBreakerThreshold = 3.0; // % drawdown to trigger circuit breaker
|
|
// Anomaly detection is now handled by SecurityEnhancements
|
|
input int InpMaxLeverage = 30; // Maximum allowed leverage (1:1 to 1:500)
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Trade Risk Record |
|
|
//+------------------------------------------------------------------+
|
|
class CTradeRiskRecord : public CObject
|
|
{
|
|
public:
|
|
ulong ticket; // Trade ticket
|
|
string symbol; // Symbol
|
|
double volume; // Position volume
|
|
double entry; // Entry price
|
|
double sl; // Stop loss
|
|
double tp; // Take profit
|
|
double risk; // Risk amount
|
|
datetime openTime; // Open time
|
|
|
|
CTradeRiskRecord() {}
|
|
~CTradeRiskRecord() {}
|
|
|
|
void Set(ulong t, string s, double v, double e, double sll, double tpp, double r, datetime ot)
|
|
{
|
|
ticket = t;
|
|
symbol = s;
|
|
volume = v;
|
|
entry = e;
|
|
sl = sll;
|
|
tp = tpp;
|
|
risk = r;
|
|
openTime = ot;
|
|
}
|
|
|
|
bool AddTrade(ulong tradeTicket, const string tradeSymbol, double tradeVolume, double tradeEntry,
|
|
double tradeSL, double tradeTP, double tradeRisk, datetime tradeOpenTime) {
|
|
// Check if trade is valid
|
|
if(!IsMarketOpen(tradeSymbol) || tradeVolume <= 0 || tradeEntry <= 0 || tradeRisk < 0) {
|
|
return false;
|
|
}
|
|
|
|
// Create a new trade record
|
|
CTradeRiskRecord *newRecord = new CTradeRiskRecord();
|
|
newRecord.Set(tradeTicket, tradeSymbol, tradeVolume, tradeEntry, tradeSL, tradeTP, tradeRisk, tradeOpenTime);
|
|
|
|
// Add to open trades
|
|
if(m_openTrades == NULL) {
|
|
m_openTrades = new CArrayObj();
|
|
}
|
|
m_openTrades.Add(newRecord);
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Risk Manager Class |
|
|
//+------------------------------------------------------------------+
|
|
class CRiskManager : public CObject
|
|
{
|
|
private:
|
|
// Risk parameters
|
|
double m_riskPerTrade; // Risk per trade as percentage of balance
|
|
double m_dailyLossLimit; // Maximum daily loss percentage
|
|
int m_maxOpenTrades; // Maximum number of open trades
|
|
int m_maxOrdersPerDay; // Maximum orders per day
|
|
bool m_useVolatilityAdjustment; // Use volatility-based position sizing
|
|
double m_maxDailyLoss; // Maximum daily loss amount
|
|
bool m_circuitBreakerActive; // Circuit breaker state
|
|
int m_atrPeriod; // Period for ATR calculation
|
|
int m_maxSpread; // Maximum allowed spread in points
|
|
double m_circuitBreakerLevel; // Drawdown level for circuit breaker
|
|
bool m_anomalyDetection; // Enable anomaly detection
|
|
int m_maxLeverage; // Maximum allowed leverage
|
|
|
|
// State tracking
|
|
double m_dailyPnL; // Today's PnL
|
|
int m_todayOrders; // Number of orders placed today
|
|
int m_dailyOrderCount; // Daily order counter
|
|
double m_dailyPL; // Daily profit/loss
|
|
datetime m_lastTradeDay; // Last trading day
|
|
string m_lastError; // Last error message
|
|
double m_equityHigh; // Highest equity level
|
|
double m_maxDrawdown; // Maximum drawdown percentage
|
|
bool m_isTradingAllowed; // Flag to enable/disable trading
|
|
bool m_logToFile; // Enable logging to file
|
|
int m_consecutiveErrors; // Count of consecutive errors
|
|
bool m_useCircuitBreaker; // Enable circuit breaker
|
|
int m_maxConsecutiveErrors; // Max allowed consecutive errors
|
|
datetime m_circuitBreakerTime; // When circuit breaker was activated
|
|
int m_circuitBreakTime; // Duration of circuit break in seconds
|
|
CTrade* m_trade; // Trade execution object
|
|
|
|
// News filtering
|
|
bool m_useNewsFilter; // Enable news filtering
|
|
datetime m_newsStartTime; // Start time of news event
|
|
datetime m_newsEndTime; // End time of news event
|
|
CArrayString* m_affectedSymbols; // Symbols affected by news
|
|
|
|
// Collections
|
|
CArrayObj m_openTrades; // Currently open trades
|
|
CArrayObj m_closedTrades; // Recently closed trades
|
|
CArrayObj m_tradeHistory; // Historical record of all trades
|
|
|
|
// Volatility data
|
|
int m_atrHandle; // ATR indicator handle
|
|
double m_atrBuffer[]; // ATR buffer
|
|
|
|
// Security - Using pointer to avoid circular dependency
|
|
CSecurityEnhancements *m_security; // Security enhancements
|
|
|
|
// Private methods
|
|
// Update daily trading metrics
|
|
void UpdateDailyMetrics() {
|
|
MqlDateTime time;
|
|
TimeCurrent(time);
|
|
|
|
// Check if it's a new day
|
|
if(m_lastTradeDay != time.day) {
|
|
// Reset daily counters if it's a new day
|
|
if(m_lastTradeDay != 0) { // Don't reset on the first run
|
|
m_dailyPnL = 0.0;
|
|
m_todayOrders = 0;
|
|
m_dailyOrderCount = 0;
|
|
|
|
// Log end of day report
|
|
string report = "=== End of Day Report ===\n";
|
|
report += "Date: " + TimeToString(TimeCurrent(), TIME_DATE) + "\n";
|
|
report += "Daily P/L: " + DoubleToString(m_dailyPnL, 2) + " " + AccountInfoString(ACCOUNT_CURRENCY) + "\n";
|
|
report += "Orders Today: " + IntegerToString(m_todayOrders) + "\n";
|
|
report += "Open Positions: " + IntegerToString(PositionsTotal()) + "\n";
|
|
report += "Account Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + "\n";
|
|
report += "Account Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + "\n";
|
|
report += "======================\n";
|
|
|
|
Print(report);
|
|
|
|
// Save to file if needed
|
|
if(m_logToFile) {
|
|
int file_handle = FileOpen("DailyReport_" + TimeToString(TimeCurrent(), TIME_DATE) + ".txt",
|
|
FILE_WRITE|FILE_TXT);
|
|
if(file_handle != INVALID_HANDLE) {
|
|
FileWriteString(file_handle, report);
|
|
FileClose(file_handle);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update last trade day
|
|
m_lastTradeDay = time.day;
|
|
}
|
|
|
|
// Update current day's P/L
|
|
double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
double currentBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
m_dailyPnL = currentEquity - currentBalance;
|
|
}
|
|
|
|
// Log trade metrics for analysis
|
|
void LogTradeMetrics(const ulong ticket, const string symbol, const ENUM_ORDER_TYPE type,
|
|
const double volume, const double openPrice, const double sl,
|
|
const double tp, const string comment = "") {
|
|
// Create a trade record
|
|
CTradeRecord *record = new CTradeRecord();
|
|
record.Set(ticket, symbol, type, volume, openPrice, sl, tp, 0.0, comment);
|
|
|
|
// Add to trade history
|
|
m_tradeHistory.Add(record);
|
|
|
|
// Prepare log message
|
|
string logMsg = StringFormat("[%s] %s %s %.2f lots @ %.5f",
|
|
TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS),
|
|
EnumToString(type),
|
|
symbol,
|
|
volume,
|
|
openPrice);
|
|
|
|
if(sl > 0) logMsg += StringFormat(" SL:%.5f", sl);
|
|
if(tp > 0) logMsg += StringFormat(" TP:%.5f", tp);
|
|
if(comment != "") logMsg += " " + comment;
|
|
|
|
// Print to log
|
|
Print(logMsg);
|
|
|
|
// Log to file if enabled
|
|
if(m_logToFile) {
|
|
int file_handle = FileOpen("TradeLog_" + symbol + "_" + TimeToString(TimeCurrent(), TIME_DATE) + ".csv",
|
|
FILE_WRITE|FILE_CSV|FILE_READ, ",");
|
|
if(file_handle != INVALID_HANDLE) {
|
|
// Write header if file is empty
|
|
if(FileSize(file_handle) == 0) {
|
|
FileWrite(file_handle, "DateTime","Type","Symbol","Volume","Price","SL","TP","Comment");
|
|
}
|
|
|
|
// Write trade data
|
|
FileWrite(file_handle,
|
|
TimeToString(TimeCurrent(), TIME_DATE|TIME_SECONDS),
|
|
EnumToString(type),
|
|
symbol,
|
|
volume,
|
|
openPrice,
|
|
sl,
|
|
tp,
|
|
comment);
|
|
|
|
FileClose(file_handle);
|
|
}
|
|
}
|
|
|
|
// Update metrics
|
|
m_todayOrders++;
|
|
m_dailyOrderCount++;
|
|
UpdateDailyMetrics();
|
|
}
|
|
|
|
// Check if there's sufficient margin for the trade
|
|
bool CheckMargin(const string symbol, const double volume, const double price, const ENUM_ORDER_TYPE orderType) {
|
|
// Get order type direction
|
|
bool isBuy = (orderType == ORDER_TYPE_BUY || orderType == ORDER_TYPE_BUY_LIMIT || orderType == ORDER_TYPE_BUY_STOP);
|
|
|
|
// Calculate required margin
|
|
double margin = 0.0;
|
|
if(!OrderCalcMargin((ENUM_ORDER_TYPE)orderType, symbol, volume, price, margin)) {
|
|
m_lastError = "Failed to calculate margin for " + symbol;
|
|
return false;
|
|
}
|
|
|
|
// Get free margin and account balance
|
|
double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
|
|
// Check if we have enough margin
|
|
if(margin > freeMargin) {
|
|
m_lastError = StringFormat("Insufficient margin. Required: %.2f, Available: %.2f", margin, freeMargin);
|
|
return false;
|
|
}
|
|
|
|
// Check margin level (if position would be opened)
|
|
double marginLevel = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
|
|
if(marginLevel > 0 && marginLevel < 100.0) { // 100% margin level is critical
|
|
m_lastError = StringFormat("Margin level too low: %.2f%%", marginLevel);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Check if the current spread is acceptable for trading
|
|
bool CheckSpread(const string symbol) {
|
|
// Get current spread
|
|
long spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD);
|
|
|
|
// Get average spread for the current timeframe
|
|
double avgSpread = GetAverageSpread(symbol, PERIOD_CURRENT, 20);
|
|
|
|
// Check if spread is within acceptable limits (2x average or 20 points, whichever is higher)
|
|
double maxSpread = MathMax(avgSpread * 2.0, 20.0);
|
|
|
|
if(spread > maxSpread) {
|
|
m_lastError = StringFormat("Spread too high: %d points (max: %.1f)", spread, maxSpread);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Check if the market is open for trading
|
|
bool CheckMarketHours(const string symbol) {
|
|
// Check if the symbol is selected and visible in Market Watch
|
|
if(!SymbolSelect(symbol, true)) {
|
|
m_lastError = "Symbol " + symbol + " is not available";
|
|
return false;
|
|
}
|
|
|
|
// Check if the symbol is enabled for trading
|
|
if(!SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_DISABLED) {
|
|
m_lastError = "Trading is disabled for " + symbol;
|
|
return false;
|
|
}
|
|
|
|
// Check if we're outside of market hours (if defined)
|
|
MqlDateTime time;
|
|
TimeCurrent(time);
|
|
int dayOfWeek = time.day_of_week;
|
|
int hour = time.hour;
|
|
|
|
// Example: Don't trade on weekends
|
|
if(dayOfWeek == 0 || dayOfWeek == 6) { // Sunday or Saturday
|
|
m_lastError = "Market is closed on weekends";
|
|
return false;
|
|
}
|
|
|
|
// Example: Only trade during specific hours (e.g., London + NY session)
|
|
// Adjust these values based on your trading strategy
|
|
if(hour < 8 || hour >= 22) { // 8 AM to 10 PM server time
|
|
m_lastError = "Outside of trading hours (8:00-22:00 server time)";
|
|
return false;
|
|
}
|
|
|
|
// Check for market holidays (you would implement this based on your needs)
|
|
if(IsMarketHoliday(time)) {
|
|
m_lastError = "Market holiday";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Helper function to get average spread over a period
|
|
double GetAverageSpread(const string symbol, const ENUM_TIMEFRAMES timeframe, const int periods) {
|
|
double totalSpread = 0;
|
|
int count = 0;
|
|
|
|
for(int i = 0; i < periods; i++) {
|
|
long spread = (long)SymbolInfoInteger(symbol, SYMBOL_SPREAD);
|
|
if(spread > 0) {
|
|
totalSpread += spread;
|
|
count++;
|
|
}
|
|
// Move to previous bar (this is simplified, in real code you'd use CopySpread)
|
|
Sleep(10); // Small delay to avoid flooding
|
|
}
|
|
|
|
return (count > 0) ? (totalSpread / count) : 0;
|
|
}
|
|
|
|
// Check if today is a market holiday (example implementation)
|
|
bool IsMarketHoliday(const MqlDateTime &time) {
|
|
// This is a simplified example - you'd want to implement a proper holiday calendar
|
|
// based on the market you're trading
|
|
|
|
// Example: New Year's Day
|
|
if(time.mon == 1 && time.day == 1) return true;
|
|
|
|
// Example: Christmas
|
|
if(time.mon == 12 && time.day == 25) return true;
|
|
|
|
// Add other major holidays as needed
|
|
|
|
return false;
|
|
}
|
|
|
|
// Execute a trading order with risk management
|
|
bool ExecuteOrder(const string symbol,
|
|
const ENUM_ORDER_TYPE orderType,
|
|
const double volume,
|
|
const double price,
|
|
const double sl,
|
|
const double tp,
|
|
const string comment = "") {
|
|
// Validate order parameters
|
|
if(!ValidateOrder(symbol, orderType, volume, price, sl, tp)) {
|
|
return false;
|
|
}
|
|
|
|
// Check if trading is allowed
|
|
if(!IsTradingAllowed()) {
|
|
m_lastError = "Trading is not allowed: " + m_lastError;
|
|
return false;
|
|
}
|
|
|
|
// Execute the order
|
|
bool result = false;
|
|
m_trade.SetExpertMagicNumber(m_magicNumber);
|
|
|
|
switch(orderType) {
|
|
case ORDER_TYPE_BUY:
|
|
result = m_trade.Buy(volume, symbol, price, sl, tp, comment);
|
|
break;
|
|
|
|
case ORDER_TYPE_SELL:
|
|
result = m_trade.Sell(volume, symbol, price, sl, tp, comment);
|
|
break;
|
|
|
|
case ORDER_TYPE_BUY_LIMIT:
|
|
result = m_trade.BuyLimit(volume, price, symbol, sl, tp, 0, 0, comment);
|
|
break;
|
|
|
|
case ORDER_TYPE_SELL_LIMIT:
|
|
result = m_trade.SellLimit(volume, price, symbol, sl, tp, 0, 0, comment);
|
|
break;
|
|
|
|
case ORDER_TYPE_BUY_STOP:
|
|
result = m_trade.BuyStop(volume, price, symbol, sl, tp, ORDER_TIME_GTC, 0, comment);
|
|
break;
|
|
|
|
case ORDER_TYPE_SELL_STOP:
|
|
result = m_trade.SellStop(volume, price, symbol, sl, tp, ORDER_TIME_GTC, 0, comment);
|
|
break;
|
|
|
|
default:
|
|
m_lastError = "Unsupported order type: " + EnumToString(orderType);
|
|
return false;
|
|
}
|
|
|
|
// Handle the result
|
|
if(!result) {
|
|
HandleError(m_trade.ResultRetcode(), "Order execution failed");
|
|
return false;
|
|
}
|
|
|
|
// Update metrics and reset error counter on success
|
|
UpdateDailyMetrics();
|
|
ResetErrorCounter();
|
|
|
|
return true;
|
|
}
|
|
|
|
// Handle trading errors and update error counter
|
|
void HandleError(const int errorCode, const string context = "") {
|
|
// Log the error
|
|
string errorMsg = "Error " + IntegerToString(errorCode) + ": " + ErrorDescription(errorCode);
|
|
if(context != "") {
|
|
errorMsg = context + " - " + errorMsg;
|
|
}
|
|
Print(errorMsg);
|
|
|
|
// Update error counter and check circuit breaker
|
|
m_consecutiveErrors++;
|
|
if(m_useCircuitBreaker && m_consecutiveErrors >= m_maxConsecutiveErrors) {
|
|
m_circuitBreakerActive = true;
|
|
m_circuitBreakerTime = TimeCurrent();
|
|
Print("Circuit breaker activated! Trading suspended until ",
|
|
TimeToString(m_circuitBreakerTime + m_circuitBreakTime, TIME_MINUTES|TIME_SECONDS));
|
|
}
|
|
|
|
// Update last error message
|
|
m_lastError = errorMsg;
|
|
}
|
|
|
|
// Reset the error counter and circuit breaker if needed
|
|
void ResetErrorCounter() {
|
|
if(m_consecutiveErrors > 0) {
|
|
m_consecutiveErrors = 0;
|
|
if(m_circuitBreakerActive) {
|
|
m_circuitBreakerActive = false;
|
|
Print("Circuit breaker reset. Trading resumed.");
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get current market volatility using ATR
|
|
double GetCurrentVolatility(const string symbol, const ENUM_TIMEFRAMES timeframe = PERIOD_CURRENT) {
|
|
// If ATR handle is not valid, try to create it
|
|
if(m_atrHandle == INVALID_HANDLE) {
|
|
m_atrHandle = iATR(symbol, timeframe, m_atrPeriod);
|
|
if(m_atrHandle == INVALID_HANDLE) {
|
|
m_lastError = "Failed to create ATR indicator for " + symbol;
|
|
return 0.0;
|
|
}
|
|
}
|
|
|
|
// Copy ATR values
|
|
double atrValues[1];
|
|
if(CopyBuffer(m_atrHandle, 0, 0, 1, atrValues) <= 0) {
|
|
m_lastError = "Failed to get ATR values for " + symbol;
|
|
return 0.0;
|
|
}
|
|
|
|
// Get the current ATR value
|
|
double currentATR = atrValues[0];
|
|
|
|
// Normalize ATR to percentage of current price for consistent comparison
|
|
double currentPrice = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
|
if(currentPrice <= 0) {
|
|
currentPrice = SymbolInfoDouble(symbol, SYMBOL_BID);
|
|
}
|
|
|
|
if(currentPrice > 0) {
|
|
return (currentATR / currentPrice) * 100.0; // Return as percentage
|
|
}
|
|
|
|
return currentATR; // Fallback to raw ATR value if price is 0
|
|
}
|
|
|
|
// Calculate position size based on risk parameters
|
|
double CalculatePositionSize(const string symbol, const double entryPrice, const double stopLoss, const double riskPercent) {
|
|
if(riskPercent <= 0 || entryPrice <= 0 || stopLoss <= 0) {
|
|
m_lastError = "Invalid parameters for position sizing";
|
|
return 0.0;
|
|
}
|
|
|
|
// Get account currency and symbol information
|
|
string accountCurrency = AccountInfoString(ACCOUNT_CURRENCY);
|
|
double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
|
double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
|
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
|
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
|
|
|
// Calculate risk amount in account currency
|
|
double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double riskAmount = accountBalance * (riskPercent / 100.0);
|
|
|
|
// Calculate position size based on stop loss
|
|
double stopLossDistance = MathAbs(entryPrice - stopLoss) / point;
|
|
double tickValueAdjusted = tickValue * (point / tickSize);
|
|
double positionSize = riskAmount / (stopLossDistance * tickValueAdjusted);
|
|
|
|
// Adjust position size to lot step
|
|
positionSize = MathFloor(positionSize / lotStep) * lotStep;
|
|
|
|
// Ensure position size is within symbol limits
|
|
positionSize = MathMin(MathMax(positionSize, minLot), maxLot);
|
|
|
|
return positionSize;
|
|
}
|
|
|
|
bool CheckDailyLimits() {
|
|
// Update daily metrics first
|
|
UpdateDailyMetrics();
|
|
|
|
// Check if we've exceeded daily order limit
|
|
if(m_todayOrders >= m_maxOrdersPerDay) {
|
|
m_lastError = StringFormat("Daily order limit reached (%d/%d)", m_todayOrders, m_maxOrdersPerDay);
|
|
return false;
|
|
}
|
|
|
|
// Check if we've exceeded maximum open trades
|
|
if(GetOpenPositions() >= m_maxOpenTrades) {
|
|
m_lastError = "Maximum open trades limit reached";
|
|
return false;
|
|
}
|
|
|
|
// Check daily loss limit
|
|
if(m_dailyPnL < -m_dailyLossLimit) {
|
|
m_lastError = "Daily loss limit reached";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CheckPositionSize(const string symbol, const double volume, const double price) {
|
|
if(volume <= 0 || price <= 0) {
|
|
m_lastError = "Invalid position parameters";
|
|
return false;
|
|
}
|
|
|
|
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
|
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
|
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
|
|
|
// Check against symbol's lot size constraints
|
|
if(volume < minLot || volume > maxLot) {
|
|
m_lastError = "Volume out of allowed range";
|
|
return false;
|
|
}
|
|
|
|
// Check if volume is a multiple of lot step
|
|
if(MathMod(volume, lotStep) != 0) {
|
|
m_lastError = "Volume must be a multiple of " + DoubleToString(lotStep, 2);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CheckMarketSpread(const string symbol) {
|
|
if(!SymbolSelect(symbol, true)) {
|
|
m_lastError = "Symbol " + symbol + " is not selected";
|
|
return false;
|
|
}
|
|
|
|
long spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD);
|
|
if(spread > (long)m_maxSpread) {
|
|
m_lastError = "Spread too wide: " + IntegerToString((int)spread) + " points";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CheckVolatility(const string symbol) {
|
|
if(!m_useVolatilityAdjustment) {
|
|
return true; // Volatility check disabled
|
|
}
|
|
|
|
double volatility = GetCurrentVolatility(symbol, PERIOD_CURRENT, m_atrPeriod);
|
|
double maxVolatility = GetMaxVolatility(symbol);
|
|
|
|
if(volatility > maxVolatility) {
|
|
m_lastError = symbol + " volatility too high: " + DoubleToString(volatility, 2) + " (max: " + DoubleToString(maxVolatility, 2) + ")";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
double CalculateVolatilityFactor(const string symbol) {
|
|
if(!m_useVolatilityAdjustment) {
|
|
return 1.0; // No adjustment
|
|
}
|
|
|
|
double volatility = GetCurrentVolatility(symbol, PERIOD_CURRENT, m_atrPeriod);
|
|
if(volatility <= 0) {
|
|
return 1.0; // Fallback to no adjustment
|
|
}
|
|
|
|
// Normalize ATR to a factor between 0.5 and 1.5
|
|
double avgAtr = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE) * 10; // Example average ATR
|
|
double factor = avgAtr / volatility;
|
|
return MathMin(1.5, MathMax(0.5, factor));
|
|
}
|
|
|
|
void UpdateTradeHistory(const CTradeRiskRecord *record) {
|
|
if(record == NULL) return;
|
|
|
|
// Add to open trades
|
|
CTradeRiskRecord *newRecord = new CTradeRiskRecord();
|
|
*newRecord = *record;
|
|
m_openTrades.Add(newRecord);
|
|
|
|
// Update daily stats
|
|
m_todayOrders++;
|
|
|
|
// Update equity high water mark
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
if(equity > m_equityHigh) {
|
|
m_equityHigh = equity;
|
|
}
|
|
|
|
// Update drawdown
|
|
UpdateDrawdown();
|
|
}
|
|
|
|
double GetRecentProfitFactor(const string symbol, const int periods) {
|
|
// Implementation depends on your trading history tracking
|
|
// This is a simplified version
|
|
double profit = 0;
|
|
double loss = 0;
|
|
|
|
// In a real implementation, you would iterate through recent trades
|
|
// and calculate profit factor based on wins/losses
|
|
|
|
if(loss == 0) return 10.0; // Avoid division by zero
|
|
return profit / loss;
|
|
}
|
|
|
|
bool CheckMarginRequirements(const string symbol, const double volume) {
|
|
double margin = 0;
|
|
double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
|
|
|
|
if(!OrderCalcMargin(ORDER_TYPE_BUY, symbol, volume, 0, margin)) {
|
|
m_lastError = "Failed to calculate margin requirements";
|
|
return false;
|
|
}
|
|
|
|
// Check if we have enough margin
|
|
if(margin > freeMargin) {
|
|
m_lastError = "Insufficient margin";
|
|
return false;
|
|
}
|
|
|
|
// Check leverage
|
|
double leverage = AccountInfoInteger(ACCOUNT_LEVERAGE);
|
|
if(leverage > m_maxLeverage) {
|
|
m_lastError = "Account leverage too high";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool CheckCorrelatedPositions(const string symbol, const ENUM_ORDER_TYPE type) {
|
|
// In a real implementation, you would check for correlated positions
|
|
// This is a simplified version that just checks for opposite positions on the same symbol
|
|
|
|
for(int i = 0; i < m_openTrades.Total(); i++) {
|
|
CTradeRiskRecord *record = m_openTrades.At(i);
|
|
if(record != NULL && record.symbol == symbol) {
|
|
// Check for opposite position type
|
|
if((type == ORDER_TYPE_BUY && record.type == ORDER_TYPE_SELL) ||
|
|
(type == ORDER_TYPE_SELL && record.type == ORDER_TYPE_BUY)) {
|
|
m_lastError = "Opposite position exists";
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void UpdateDrawdown() {
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
|
|
if(balance > 0) {
|
|
double drawdown = (balance - equity) / balance * 100;
|
|
if(drawdown > m_maxDrawdown) {
|
|
m_maxDrawdown = drawdown;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckCircuitBreaker() {
|
|
if(!InpEnableCircuitBreaker) return;
|
|
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
|
|
if(balance > 0) {
|
|
double drawdown = (balance - equity) / balance * 100;
|
|
if(drawdown >= m_circuitBreakerLevel) {
|
|
m_circuitBreakerActive = true;
|
|
LogAnomaly("Circuit breaker triggered: Drawdown " + DoubleToString(drawdown, 2) + "%");
|
|
}
|
|
}
|
|
}
|
|
|
|
void LogAnomaly(const string message) {
|
|
// In a real implementation, you would log to a file or send an alert
|
|
Print("[ANOMALY] ", message);
|
|
}
|
|
|
|
public:
|
|
// Constructor/Destructor
|
|
CRiskManager();
|
|
~CRiskManager();
|
|
|
|
// Initialization
|
|
bool Initialize();
|
|
void Deinitialize();
|
|
|
|
// Configuration with validation
|
|
void SetRiskPerTrade(double risk) { m_riskPerTrade = MathMin(MathMax(risk, 0.1), MAX_RISK_PER_TRADE); }
|
|
void SetDailyLossLimit(double limit) { m_dailyLossLimit = MathMin(MathMax(limit, 0.5), MAX_DAILY_RISK); }
|
|
void SetMaxOpenTrades(int maxTrades) { m_maxOpenTrades = MathMax(1, MathMin(maxTrades, MAX_POSITIONS)); }
|
|
void SetMaxOrdersPerDay(int maxOrders) { m_maxOrdersPerDay = MathMax(1, MathMin(maxOrders, MAX_ORDERS_PER_DAY)); }
|
|
void UseVolatilityAdjustment(bool enable) { m_useVolatilityAdjustment = enable; }
|
|
void SetAtrPeriod(int period) { m_atrPeriod = MathMax(5, MathMin(period, 50)); }
|
|
void SetMaxSpread(int maxSpread) { m_maxSpread = MathMax(1, MathMin(maxSpread, 100)); }
|
|
void SetMaxLeverage(int leverage) { m_maxLeverage = MathMax(1, MathMin(leverage, 500)); }
|
|
|
|
// Risk validation with enhanced security
|
|
bool ValidateTrade(const string symbol, const ENUM_ORDER_TYPE type,
|
|
const double volume, const double price,
|
|
const double sl, const double tp) {
|
|
// Reset last error
|
|
m_lastError = "";
|
|
|
|
// Basic parameter validation
|
|
if(volume <= 0 || price <= 0) {
|
|
m_lastError = "Invalid trade parameters";
|
|
return false;
|
|
}
|
|
|
|
// Check circuit breaker first
|
|
if(m_circuitBreakerActive) {
|
|
m_lastError = "Trading suspended by circuit breaker";
|
|
return false;
|
|
}
|
|
|
|
// Check daily limits
|
|
if(!CheckDailyLimits()) {
|
|
return false;
|
|
}
|
|
|
|
// Check position size
|
|
if(!CheckPositionSize(symbol, volume, price)) {
|
|
return false;
|
|
}
|
|
|
|
// Check market spread
|
|
if(!CheckMarketSpread(symbol)) {
|
|
return false;
|
|
}
|
|
|
|
// Check volatility
|
|
if(!CheckVolatility(symbol)) {
|
|
return false;
|
|
}
|
|
|
|
// Check margin requirements
|
|
if(!CheckMarginRequirements(symbol, volume)) {
|
|
return false;
|
|
}
|
|
|
|
// Check for correlated positions
|
|
if(!CheckCorrelatedPositions(symbol, type)) {
|
|
return false;
|
|
}
|
|
|
|
// Additional security checks if security manager is available
|
|
if(m_security != NULL && !m_security.ValidateTrade(symbol, type, volume, price, sl, tp)) {
|
|
m_lastError = m_security.GetLastError();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Position sizing with risk controls
|
|
double CalculatePositionSize(const string symbol, const double price, const int slPoints) {
|
|
if(price <= 0 || slPoints <= 0) {
|
|
m_lastError = "Invalid parameters for position sizing";
|
|
return 0.0;
|
|
}
|
|
|
|
// Get account balance and calculate risk amount
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double riskAmount = balance * (m_riskPerTrade / 100.0);
|
|
|
|
// Calculate tick value and tick size
|
|
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) {
|
|
m_lastError = "Failed to get symbol information";
|
|
return 0.0;
|
|
}
|
|
|
|
// Calculate position size
|
|
double riskMoney = riskAmount;
|
|
double lossInQuoteCurrency = slPoints * point * tickValue / tickSize;
|
|
|
|
if(lossInQuoteCurrency <= 0) {
|
|
m_lastError = "Invalid stop loss for position sizing";
|
|
return 0.0;
|
|
}
|
|
|
|
double volume = riskMoney / lossInQuoteCurrency;
|
|
|
|
// Apply volatility adjustment if enabled
|
|
if(m_useVolatilityAdjustment) {
|
|
double volatilityFactor = CalculateVolatilityFactor(symbol);
|
|
volume *= volatilityFactor;
|
|
}
|
|
|
|
// Get symbol's volume constraints
|
|
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
|
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
|
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
|
|
|
// Ensure volume is within allowed range and step
|
|
volume = MathMax(minLot, MathMin(volume, maxLot));
|
|
volume = MathFloor(volume / lotStep) * lotStep;
|
|
|
|
return volume;
|
|
}
|
|
|
|
// Check if market is open for a symbol
|
|
bool IsMarketOpen(const string symbol) {
|
|
// Check if the symbol is selected
|
|
if(!SymbolSelect(symbol, true)) {
|
|
m_lastError = "Failed to select symbol " + symbol;
|
|
return false;
|
|
}
|
|
|
|
// Check if the market is open now
|
|
datetime current = TimeCurrent();
|
|
if(!SymbolInfoInteger(symbol, SYMBOL_TRADE_MODE) == SYMBOL_TRADE_MODE_DISABLED) {
|
|
// Check trading session times
|
|
MqlDateTime time;
|
|
TimeToStruct(current, time);
|
|
|
|
// Skip weekend check if symbol trades on weekends
|
|
if(!SymbolInfoInteger(symbol, SYMBOL_TRADE_CALENDAR)) {
|
|
if(time.day_of_week == 0 || time.day_of_week == 6) { // Sunday or Saturday
|
|
m_lastError = "Market is closed (weekend)";
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Check if current time is within trading hours
|
|
// This is a simplified check - adjust according to your broker's trading hours
|
|
int dayOfWeek = time.day_of_week;
|
|
if(dayOfWeek >= 1 && dayOfWeek <= 5) { // Monday to Friday
|
|
int hour = time.hour;
|
|
// Example: Check if current time is within 0:00-23:59 (24/5 market)
|
|
if(hour >= 0 && hour < 24) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_lastError = "Market is currently closed";
|
|
return false;
|
|
}
|
|
|
|
// Market conditions with anomaly detection
|
|
bool CheckMarketConditions(const string symbol) {
|
|
// Basic symbol check
|
|
if(!SymbolSelect(symbol, true)) {
|
|
m_lastError = "Symbol " + symbol + " is not available";
|
|
return false;
|
|
}
|
|
|
|
// Check if market is open
|
|
if(!IsMarketOpen(symbol)) {
|
|
m_lastError = symbol + " market is closed";
|
|
return false;
|
|
}
|
|
|
|
// Check spread
|
|
if(!CheckMarketSpread(symbol)) {
|
|
return false;
|
|
}
|
|
|
|
// Check volatility if enabled
|
|
if(m_useVolatilityAdjustment && !CheckVolatility(symbol)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Getters with validation
|
|
string GetLastError() const { return m_lastError; }
|
|
double GetDailyPnL() const { return m_dailyPnL; }
|
|
int GetTodayOrders() const { return m_todayOrders; }
|
|
double GetMaxDrawdown() const { return m_maxDrawdown; }
|
|
bool IsCircuitBreakerActive() const { return m_circuitBreakerActive; }
|
|
|
|
// State management
|
|
void ResetDailyStats();
|
|
|
|
// Advanced risk metrics
|
|
double CalculateDrawdown(const string symbol = NULL);
|
|
double CalculateRiskRewardRatio(const double entry, const double sl, const double tp);
|
|
double GetVolatility(const string symbol);
|
|
double GetRiskOfRuin() const;
|
|
|
|
// Position management
|
|
int GetOpenPositions(const string symbol = "") {
|
|
int count = 0;
|
|
|
|
if(symbol == "") {
|
|
// Count all open positions
|
|
for(int i = 0; i < m_openTrades.Total(); i++) {
|
|
CTradeRiskRecord *record = m_openTrades.At(i);
|
|
if(record != NULL && !record.isClosed) {
|
|
count++;
|
|
}
|
|
}
|
|
} else {
|
|
// Count positions for specific symbol
|
|
for(int i = 0; i < m_openTrades.Total(); i++) {
|
|
CTradeRiskRecord *record = m_openTrades.At(i);
|
|
if(record != NULL && record.symbol == symbol && !record.isClosed) {
|
|
count++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
double GetTotalRisk() {
|
|
double totalRisk = 0;
|
|
|
|
for(int i = 0; i < m_openTrades.Total(); i++) {
|
|
CTradeRiskRecord *record = m_openTrades.At(i);
|
|
if(record != NULL && !record.isClosed) {
|
|
totalRisk += record.risk;
|
|
}
|
|
}
|
|
|
|
return totalRisk;
|
|
}
|
|
|
|
double GetPortfolioRisk() const {
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
if(balance <= 0) return 0;
|
|
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
return ((balance - equity) / balance) * 100.0;
|
|
}
|
|
|
|
// Risk controls
|
|
bool IsTradingAllowed() {
|
|
// Check circuit breaker first
|
|
if(m_circuitBreakerActive) {
|
|
m_lastError = "Trading suspended by circuit breaker";
|
|
return false;
|
|
}
|
|
|
|
// Check if market is open for trading
|
|
if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) {
|
|
m_lastError = "Trading is not allowed by the terminal";
|
|
return false;
|
|
}
|
|
|
|
// Check if expert is allowed to trade
|
|
if(!MQLInfoInteger(MQL_TRADE_ALLOWED)) {
|
|
m_lastError = "Trading is not allowed for this expert";
|
|
return false;
|
|
}
|
|
|
|
// Check if account allows trading
|
|
if(!AccountInfoInteger(ACCOUNT_TRADE_ALLOWED)) {
|
|
m_lastError = "Trading is not allowed for this account";
|
|
return false;
|
|
}
|
|
|
|
// Check if account is in profit/loss limits
|
|
double profit = AccountInfoDouble(ACCOUNT_PROFIT);
|
|
if(profit < -m_dailyLossLimit) {
|
|
m_lastError = "Daily loss limit reached";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void SuspendTrading(const string reason) {
|
|
m_circuitBreakerActive = true;
|
|
LogAnomaly("Trading suspended: " + reason);
|
|
}
|
|
|
|
void ResumeTrading() {
|
|
if(m_circuitBreakerActive) {
|
|
m_circuitBreakerActive = false;
|
|
LogAnomaly("Trading resumed");
|
|
}
|
|
}
|
|
|
|
// Monitoring
|
|
void MonitorPositions() {
|
|
// Update all open positions
|
|
for(int i = m_openTrades.Total() - 1; i >= 0; i--) {
|
|
CTradeRiskRecord *record = m_openTrades.At(i);
|
|
if(record == NULL) continue;
|
|
|
|
// Check if position is still open
|
|
if(!PositionSelectByTicket(record.ticket)) {
|
|
// Position was closed, move to closed trades
|
|
record.isClosed = true;
|
|
m_closedTrades.Add(m_openTrades.Detach(i));
|
|
|
|
// Update daily PnL
|
|
double profit = record.volume * (record.price - record.sl); // Simplified PnL calculation
|
|
m_dailyPnL += profit;
|
|
|
|
// Update drawdown
|
|
UpdateDrawdown();
|
|
}
|
|
}
|
|
|
|
// Check circuit breaker
|
|
CheckCircuitBreaker();
|
|
}
|
|
|
|
void CheckMarginLevels() {
|
|
double margin = AccountInfoDouble(ACCOUNT_MARGIN);
|
|
double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
|
|
double marginLevel = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
|
|
|
|
// Log warning if margin level is low
|
|
if(marginLevel > 0 && marginLevel < 100.0) { // 100% margin level is critical
|
|
LogAnomaly("Low margin level: " + DoubleToString(marginLevel, 2) + "%");
|
|
|
|
// Trigger circuit breaker if margin level is critically low
|
|
if(marginLevel < 50.0) { // 50% margin level is emergency
|
|
SuspendTrading("Critically low margin level: " + DoubleToString(marginLevel, 2) + "%");
|
|
}
|
|
}
|
|
}
|
|
|
|
void CheckNewsImpact() {
|
|
// This would integrate with a news feed or economic calendar
|
|
// For now, just a placeholder
|
|
if(m_security != NULL) {
|
|
string symbol = Symbol();
|
|
if(m_security.IsNewsEvent(symbol)) {
|
|
LogAnomaly("High impact news detected for " + symbol);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Recovery
|
|
void RecoverFromDrawdown() {
|
|
// Simple recovery strategy: reduce position sizes after drawdown
|
|
double drawdown = GetPortfolioRisk();
|
|
|
|
if(drawdown > 5.0) { // If drawdown > 5%
|
|
// Reduce risk per trade by 50%
|
|
m_riskPerTrade *= 0.5;
|
|
LogAnomaly("Reducing risk per trade to " + DoubleToString(m_riskPerTrade, 2) + "% due to drawdown");
|
|
}
|
|
}
|
|
|
|
// Logging and reporting
|
|
void LogRiskMetrics() {
|
|
string logEntry = "";
|
|
logEntry += "=== Risk Metrics ===\n";
|
|
logEntry += "Account Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + "\n";
|
|
logEntry += "Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + "\n";
|
|
logEntry += "Margin: " + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN), 2) + "\n";
|
|
logEntry += "Free Margin: " + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_FREE), 2) + "\n";
|
|
logEntry += "Margin Level: " + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL), 2) + "%\n";
|
|
logEntry += "Open Trades: " + IntegerToString(GetOpenPositions()) + "\n";
|
|
logEntry += "Daily PnL: " + DoubleToString(m_dailyPnL, 2) + "\n";
|
|
logEntry += "Max Drawdown: " + DoubleToString(m_maxDrawdown, 2) + "%\n";
|
|
logEntry += "Circuit Breaker: " + string(m_circuitBreakerActive ? "ACTIVE" : "Inactive") + "\n";
|
|
|
|
Print(logEntry);
|
|
}
|
|
|
|
string GenerateRiskReport() const {
|
|
string report = "";
|
|
report += "=== Risk Management Report ===\n\n";
|
|
report += "Account Information:\n";
|
|
report += "- Balance: " + DoubleToString(AccountInfoDouble(ACCOUNT_BALANCE), 2) + " " + AccountInfoString(ACCOUNT_CURRENCY) + "\n";
|
|
report += "- Equity: " + DoubleToString(AccountInfoDouble(ACCOUNT_EQUITY), 2) + "\n";
|
|
report += "- Margin: " + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN), 2) + "\n";
|
|
report += "- Free Margin: " + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_FREE), 2) + "\n";
|
|
report += "- Margin Level: " + DoubleToString(AccountInfoDouble(ACCOUNT_MARGIN_LEVEL), 2) + "%\n\n";
|
|
|
|
report += "Risk Parameters:\n";
|
|
report += "- Risk per Trade: " + DoubleToString(m_riskPerTrade, 2) + "%\n";
|
|
report += "- Daily Loss Limit: " + DoubleToString(m_dailyLossLimit, 2) + "%\n";
|
|
report += "- Max Open Trades: " + IntegerToString(m_maxOpenTrades) + "\n";
|
|
report += "- Max Orders per Day: " + IntegerToString(m_maxOrdersPerDay) + "\n";
|
|
report += "- Current Daily Orders: " + IntegerToString(m_todayOrders) + "\n\n";
|
|
|
|
report += "Portfolio Metrics:\n";
|
|
report += "- Open Positions: " + IntegerToString(GetOpenPositions()) + "\n";
|
|
report += "- Daily PnL: " + DoubleToString(m_dailyPnL, 2) + "\n";
|
|
report += "- Max Drawdown: " + DoubleToString(m_maxDrawdown, 2) + "%\n";
|
|
report += "- Circuit Breaker: " + string(m_circuitBreakerActive ? "ACTIVE" : "Inactive") + "\n";
|
|
return report;
|
|
}
|
|
|
|
// Event handlers
|
|
void OnTick() {
|
|
// Update market data
|
|
if(m_atrHandle != INVALID_HANDLE) {
|
|
if(CopyBuffer(m_atrHandle, 0, 0, 1, m_atrBuffer) <= 0) {
|
|
m_lastError = "Failed to update ATR data";
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Monitor open positions
|
|
MonitorPositions();
|
|
|
|
// Check margin levels periodically
|
|
static datetime lastCheck = 0;
|
|
if(TimeCurrent() - lastCheck > 60) { // Check every minute
|
|
CheckMarginLevels();
|
|
CheckNewsImpact();
|
|
lastCheck = TimeCurrent();
|
|
}
|
|
|
|
// Log metrics every 15 minutes
|
|
static datetime lastLog = 0;
|
|
if(TimeCurrent() - lastLog > 900) { // 15 minutes = 900 seconds
|
|
LogRiskMetrics();
|
|
lastLog = TimeCurrent();
|
|
}
|
|
}
|
|
|
|
void OnTrade() {
|
|
// Update trade history and statistics
|
|
UpdateDrawdown();
|
|
|
|
// Check if we need to recover from drawdown
|
|
if(GetPortfolioRisk() > 5.0) { // If drawdown > 5%
|
|
RecoverFromDrawdown();
|
|
}
|
|
|
|
// Log the trade
|
|
LogRiskMetrics();
|
|
}
|
|
|
|
void OnTradeTransaction(const MqlTradeTransaction& trans,
|
|
const MqlTradeRequest& request,
|
|
const MqlTradeResult& result) {
|
|
// Handle different transaction types
|
|
switch(trans.type) {
|
|
case TRADE_TRANSACTION_DEAL_ADD: {
|
|
// A new deal was added
|
|
if(HistoryDealSelect(trans.deal)) {
|
|
ulong ticket = HistoryDealGetInteger(trans.deal, DEAL_TICKET);
|
|
string symbol = HistoryDealGetString(trans.deal, DEAL_SYMBOL);
|
|
double volume = HistoryDealGetDouble(trans.deal, DEAL_VOLUME);
|
|
double price = HistoryDealGetDouble(trans.deal, DEAL_PRICE);
|
|
double profit = HistoryDealGetDouble(trans.deal, DEAL_PROFIT);
|
|
|
|
// Update daily PnL
|
|
m_dailyPnL += profit;
|
|
|
|
// Log the deal
|
|
PrintFormat("Deal #%d: %s %s %.2f lots @ %s (Profit: %.2f)",
|
|
ticket, symbol, EnumToString((ENUM_DEAL_TYPE)HistoryDealGetInteger(trans.deal, DEAL_TYPE)),
|
|
volume, DoubleToString(price, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)), profit);
|
|
|
|
// Update drawdown
|
|
UpdateDrawdown();
|
|
}
|
|
break;
|
|
}
|
|
case TRADE_TRANSACTION_ORDER_ADD: {
|
|
// A new order was added
|
|
if(HistoryOrderSelect(trans.order)) {
|
|
ulong ticket = HistoryOrderGetInteger(trans.order, ORDER_TICKET);
|
|
string symbol = HistoryOrderGetString(trans.order, ORDER_SYMBOL);
|
|
double volume = HistoryOrderGetDouble(trans.order, ORDER_VOLUME_CURRENT);
|
|
double price = HistoryOrderGetDouble(trans.order, ORDER_PRICE_OPEN);
|
|
|
|
// Log the order
|
|
PrintFormat("Order #%d: %s %s %.2f lots @ %s",
|
|
ticket, symbol, EnumToString((ENUM_ORDER_TYPE)HistoryOrderGetInteger(trans.order, ORDER_TYPE)),
|
|
volume, DoubleToString(price, (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS)));
|
|
}
|
|
break;
|
|
}
|
|
case TRADE_TRANSACTION_HISTORY_ADD: {
|
|
// New history record added
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Stress testing
|
|
void RunStressTest(int iterations = 1000) {
|
|
Print("Starting stress test...");
|
|
|
|
// Simulate various market conditions
|
|
SimulateMarketCrash();
|
|
SimulateSlippage();
|
|
SimulateNetworkLatency();
|
|
|
|
Print("Stress test completed");
|
|
}
|
|
|
|
void SimulateMarketCrash() {
|
|
Print("Simulating market crash...");
|
|
|
|
// Save current state
|
|
bool wasActive = m_circuitBreakerActive;
|
|
|
|
// Simulate a 10% drawdown
|
|
double currentEquity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
double newEquity = currentEquity * 0.9; // 10% drawdown
|
|
|
|
// Force update of equity (simulation only)
|
|
// In a real test, you would mock the account info
|
|
PrintFormat("Simulating drawdown: Equity from %.2f to %.2f (%.2f%%)",
|
|
currentEquity, newEquity,
|
|
((currentEquity - newEquity) / currentEquity) * 100.0);
|
|
|
|
// Check if circuit breaker would trigger
|
|
double drawdown = ((AccountInfoDouble(ACCOUNT_BALANCE) - newEquity) /
|
|
AccountInfoDouble(ACCOUNT_BALANCE)) * 100.0;
|
|
|
|
if(drawdown >= m_circuitBreakerLevel) {
|
|
Print("Circuit breaker would trigger at ", DoubleToString(drawdown, 2), "% drawdown");
|
|
} else {
|
|
Print("Circuit breaker would NOT trigger at ", DoubleToString(drawdown, 2), "% drawdown");
|
|
}
|
|
|
|
// Restore state
|
|
m_circuitBreakerActive = wasActive;
|
|
}
|
|
|
|
void SimulateSlippage() {
|
|
Print("Simulating slippage...");
|
|
|
|
// Simulate a trade with slippage
|
|
string symbol = Symbol();
|
|
double ask = SymbolInfoDouble(symbol, SYMBOL_ASK);
|
|
double bid = SymbolInfoDouble(symbol, SYMBOL_BID);
|
|
double spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
|
|
// Simulate 2x spread slippage
|
|
double slippage = spread * 2;
|
|
|
|
PrintFormat("Simulated slippage: %.5f pips (%.5f %s)",
|
|
slippage / SymbolInfoDouble(symbol, SYMBOL_POINT),
|
|
slippage, SymbolInfoString(symbol, SYMBOL_CURRENCY_PROFIT));
|
|
}
|
|
|
|
void SimulateNetworkLatency() {
|
|
Print("Simulating network latency...");
|
|
|
|
// Simulate network latency by measuring order execution time
|
|
ulong startTime = GetTickCount64();
|
|
|
|
// In a real test, you would measure actual order execution time
|
|
// This is just a simulation
|
|
Sleep(100); // Simulate 100ms network latency
|
|
|
|
ulong endTime = GetTickCount64();
|
|
ulong latency = endTime - startTime;
|
|
|
|
Print("Simulated network latency: ", latency, "ms");
|
|
|
|
if(latency > 500) { // 500ms threshold
|
|
LogAnomaly("High network latency detected: " + IntegerToString(latency) + "ms");
|
|
}
|
|
}
|
|
|
|
// Serialization
|
|
virtual bool Save(const int handle) {
|
|
// Serialize risk manager state
|
|
if(!FileWrite(handle, m_riskPerTrade)) return false;
|
|
if(!FileWrite(handle, m_dailyLossLimit)) return false;
|
|
if(!FileWrite(handle, m_maxOpenTrades)) return false;
|
|
if(!FileWrite(handle, m_maxOrdersPerDay)) return false;
|
|
if(!FileWrite(handle, m_useVolatilityAdjustment)) return false;
|
|
if(!FileWrite(handle, m_atrPeriod)) return false;
|
|
if(!FileWrite(handle, m_maxSpread)) return false;
|
|
if(!FileWrite(handle, m_circuitBreakerActive)) return false;
|
|
if(!FileWrite(handle, m_circuitBreakerLevel)) return false;
|
|
if(!FileWrite(handle, m_anomalyDetection)) return false;
|
|
if(!FileWrite(handle, m_maxLeverage)) return false;
|
|
if(!FileWrite(handle, m_dailyPnL)) return false;
|
|
if(!FileWrite(handle, m_todayOrders)) return false;
|
|
if(!FileWrite(handle, m_lastTradeDay)) return false;
|
|
if(!FileWrite(handle, m_equityHigh)) return false;
|
|
if(!FileWrite(handle, m_maxDrawdown)) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual bool Load(const int handle) {
|
|
// Deserialize risk manager state
|
|
if(!FileRead(handle, m_riskPerTrade)) return false;
|
|
if(!FileRead(handle, m_dailyLossLimit)) return false;
|
|
if(!FileRead(handle, m_maxOpenTrades)) return false;
|
|
if(!FileRead(handle, m_maxOrdersPerDay)) return false;
|
|
if(!FileRead(handle, m_useVolatilityAdjustment)) return false;
|
|
if(!FileRead(handle, m_atrPeriod)) return false;
|
|
if(!FileRead(handle, m_maxSpread)) return false;
|
|
if(!FileRead(handle, m_circuitBreakerActive)) return false;
|
|
if(!FileRead(handle, m_circuitBreakerLevel)) return false;
|
|
if(!FileRead(handle, m_anomalyDetection)) return false;
|
|
if(!FileRead(handle, m_maxLeverage)) return false;
|
|
if(!FileRead(handle, m_dailyPnL)) return false;
|
|
if(!FileRead(handle, m_todayOrders)) return false;
|
|
if(!FileRead(handle, m_lastTradeDay)) return false;
|
|
if(!FileRead(handle, m_equityHigh)) return false;
|
|
if(!FileRead(handle, m_maxDrawdown)) return false;
|
|
|
|
return true;
|
|
}
|
|
|
|
// Adversarial testing hooks
|
|
#ifdef ADVERSARIAL_TESTING
|
|
void InjectFault(const string component, const string type) {
|
|
// Inject faults into the risk manager for adversarial testing
|
|
}
|
|
#endif
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CRiskManager::CRiskManager(void) : m_riskPerTrade(1.0),
|
|
m_dailyLossLimit(5.0),
|
|
m_maxOpenTrades(5),
|
|
m_useVolatilityAdjustment(true),
|
|
m_maxDailyLoss(0.0),
|
|
m_circuitBreakerActive(false),
|
|
m_atrPeriod(14),
|
|
m_maxSpread(20),
|
|
m_circuitBreakerLevel(10.0),
|
|
m_anomalyDetection(true),
|
|
m_maxLeverage(30.0),
|
|
m_dailyPnL(0.0),
|
|
m_todayOrders(0),
|
|
m_dailyOrderCount(0),
|
|
m_dailyPL(0.0),
|
|
m_lastTradeDay(0),
|
|
m_lastError(""),
|
|
m_equityHigh(0.0),
|
|
m_maxDrawdown(0.0),
|
|
m_isTradingAllowed(true),
|
|
m_logToFile(false),
|
|
m_consecutiveErrors(0),
|
|
m_useCircuitBreaker(true),
|
|
m_maxConsecutiveErrors(5),
|
|
m_circuitBreakerTime(0),
|
|
m_circuitBreakTime(300),
|
|
m_trade(NULL),
|
|
m_useNewsFilter(false),
|
|
m_newsStartTime(0),
|
|
m_newsEndTime(0),
|
|
m_affectedSymbols(NULL),
|
|
m_atrHandle(INVALID_HANDLE),
|
|
m_security(NULL)
|
|
{
|
|
ArraySetAsSeries(m_atrBuffer, true);
|
|
m_security = new CSecurityEnhancements();
|
|
m_security->Initialize();
|
|
m_trade = new CTrade();
|
|
m_trade->SetExpertMagicNumber(12345);
|
|
m_trade->SetDeviationInPoints(10);
|
|
m_trade->SetTypeFilling(ORDER_FILLING_FOK);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
CRiskManager::~CRiskManager(void)
|
|
{
|
|
// Clean up indicator handles
|
|
if(m_atrHandle != INVALID_HANDLE) {
|
|
IndicatorRelease(m_atrHandle);
|
|
}
|
|
|
|
// Clean up collections
|
|
m_openTrades.Clear();
|
|
m_closedTrades.Clear();
|
|
|
|
// Clean up security manager
|
|
if(CheckPointer(m_security) == POINTER_DYNAMIC) {
|
|
delete m_security;
|
|
m_security = NULL;
|
|
}
|
|
|
|
// Clean up trade object
|
|
if(CheckPointer(m_trade) == POINTER_DYNAMIC) {
|
|
delete m_trade;
|
|
m_trade = NULL;
|
|
}
|
|
|
|
// Clean up affected symbols array
|
|
if(CheckPointer(m_affectedSymbols) == POINTER_DYNAMIC) {
|
|
delete m_affectedSymbols;
|
|
m_affectedSymbols = NULL;
|
|
}
|
|
|
|
Deinitialize();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Initialize the risk manager |
|
|
//+------------------------------------------------------------------+
|
|
bool CRiskManager::Initialize()
|
|
{
|
|
// Initialize ATR indicator for volatility measurement
|
|
m_atrHandle = iATR(Symbol(), PERIOD_CURRENT, m_atrPeriod);
|
|
if(m_atrHandle == INVALID_HANDLE) {
|
|
m_lastError = "Failed to create ATR indicator";
|
|
Print("Failed to create ATR handle");
|
|
return false;
|
|
}
|
|
|
|
// Initialize ATR buffer
|
|
ArraySetAsSeries(m_atrBuffer, true);
|
|
|
|
// Load historical data if needed
|
|
if(CopyBuffer(m_atrHandle, 0, 0, 1, m_atrBuffer) <= 0) {
|
|
m_lastError = "Failed to copy ATR buffer";
|
|
Print("Failed to copy ATR buffer: ", GetLastError());
|
|
return false;
|
|
}
|
|
|
|
// Update daily metrics
|
|
UpdateDailyMetrics();
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Deinitialize risk manager |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManager::Deinitialize(void)
|
|
{
|
|
if(m_atrHandle != INVALID_HANDLE)
|
|
{
|
|
IndicatorRelease(m_atrHandle);
|
|
m_atrHandle = INVALID_HANDLE;
|
|
}
|
|
ArrayFree(m_atrBuffer);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Validate order parameters |
|
|
//+------------------------------------------------------------------+
|
|
bool CRiskManager::ValidateOrder(string symbol, ENUM_ORDER_TYPE type, double volume, double price, double sl, double tp)
|
|
{
|
|
// Basic parameter validation
|
|
if(volume <= 0 || price <= 0)
|
|
{
|
|
m_lastError = "Invalid order parameters";
|
|
return false;
|
|
}
|
|
|
|
// Check if trading is allowed
|
|
if(!m_isTradingAllowed)
|
|
{
|
|
m_lastError = "Trading is currently suspended";
|
|
return false;
|
|
}
|
|
|
|
// Check daily limits
|
|
if(!CheckDailyLimits())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check market conditions
|
|
if(!CheckMarketConditions(symbol))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check margin requirements
|
|
if(!CheckMargin(symbol, volume, price))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check spread
|
|
if(!CheckSpread(symbol))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check market hours
|
|
if(!CheckMarketHours())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check news impact
|
|
if(!CheckNewsImpact(symbol))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Check position size limits
|
|
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("Volume %.2f is outside allowed range [%.2f, %.2f]", volume, minLot, maxLot);
|
|
return false;
|
|
}
|
|
|
|
// Round volume to nearest lot step
|
|
volume = MathFloor(volume / lotStep) * lotStep;
|
|
|
|
// Check stop loss and take profit levels
|
|
if(sl > 0 && tp > 0)
|
|
{
|
|
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
double minStopLevel = SymbolInfoInteger(symbol, SYMBOL_TRADE_STOPS_LEVEL) * point;
|
|
|
|
if(type == ORDER_TYPE_BUY)
|
|
{
|
|
if((price - sl) < minStopLevel || (tp - price) < minStopLevel)
|
|
{
|
|
m_lastError = StringFormat("Stop levels too close to price. Min distance: %.1f pips", minStopLevel/point);
|
|
return false;
|
|
}
|
|
}
|
|
else if(type == ORDER_TYPE_SELL)
|
|
{
|
|
if((sl - price) < minStopLevel || (price - tp) < minStopLevel)
|
|
{
|
|
m_lastError = StringFormat("Stop levels too close to price. Min distance: %.1f pips", minStopLevel/point);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check daily trading limits (consolidated implementation) |
|
|
//+------------------------------------------------------------------+
|
|
bool CRiskManager::CheckDailyLimits()
|
|
{
|
|
m_lastError = StringFormat("Daily loss limit reached (%.2f%%)", m_dailyPL);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate position size based on risk |
|
|
//+------------------------------------------------------------------+
|
|
double CRiskManager::CalculatePositionSize(string symbol, double stopLoss, double riskPercent)
|
|
{
|
|
if(stopLoss <= 0 || riskPercent <= 0)
|
|
return 0.0;
|
|
|
|
double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double tickSize = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE);
|
|
double tickValue = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
double lotStep = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP);
|
|
double minLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN);
|
|
double maxLot = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX);
|
|
|
|
// Calculate position size based on risk
|
|
double riskAmount = accountBalance * (riskPercent / 100.0);
|
|
double stopLossPoints = stopLoss / tickSize;
|
|
double lotSize = riskAmount / (stopLossPoints * tickValue);
|
|
|
|
// Round to nearest lot step
|
|
lotSize = MathFloor(lotSize / lotStep) * lotStep;
|
|
|
|
// Apply position size limits
|
|
lotSize = MathMin(MathMax(lotSize, minLot), maxLot);
|
|
|
|
return lotSize;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get current market volatility |
|
|
//+------------------------------------------------------------------+
|
|
double CRiskManager::GetCurrentVolatility(string symbol, ENUM_TIMEFRAMES timeframe, int period)
|
|
{
|
|
if(m_atrHandle == INVALID_HANDLE)
|
|
return 0.0;
|
|
|
|
if(CopyBuffer(m_atrHandle, 0, 0, 1, m_atrBuffer) <= 0)
|
|
return 0.0;
|
|
|
|
return m_atrBuffer[0];
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Handle trading errors |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManager::HandleError(int error, string context)
|
|
{
|
|
string errorDesc = "";
|
|
bool isCritical = false;
|
|
|
|
switch(error)
|
|
{
|
|
case 4756: errorDesc = "Invalid stops"; break;
|
|
case 130: errorDesc = "Invalid stops"; isCritical = true; break;
|
|
case 131: errorDesc = "Invalid volume"; isCritical = true; break;
|
|
case 134: errorDesc = "Not enough money"; isCritical = true; break;
|
|
case 10013: errorDesc = "Invalid request"; isCritical = true; break;
|
|
default: errorDesc = "Error " + IntegerToString(error);
|
|
}
|
|
|
|
m_lastError = context + ": " + errorDesc;
|
|
Print(m_lastError);
|
|
|
|
// Update error tracking
|
|
m_consecutiveErrors++;
|
|
m_lastErrorTime = TimeCurrent();
|
|
|
|
// If too many errors, temporarily disable trading
|
|
if(m_consecutiveErrors >= 5)
|
|
{
|
|
m_isTradingAllowed = false;
|
|
Print("Trading temporarily disabled due to multiple errors");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Reset error counter |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManager::ResetErrorCounter(void)
|
|
{
|
|
m_consecutiveErrors = 0;
|
|
m_lastError = "";
|
|
m_isTradingAllowed = true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Execute order with risk management |
|
|
//+------------------------------------------------------------------+
|
|
bool CRiskManager::ExecuteOrder(string symbol, ENUM_ORDER_TYPE type, double volume, double price, double sl, double tp, string comment)
|
|
{
|
|
// Validate order parameters
|
|
if(!ValidateOrder(symbol, type, volume, price, sl, tp))
|
|
{
|
|
Print("Order validation failed: ", m_lastError);
|
|
return false;
|
|
}
|
|
|
|
// Execute the order
|
|
bool result = false;
|
|
|
|
if(type == ORDER_TYPE_BUY)
|
|
result = m_trade.Buy(volume, symbol, price, sl, tp, comment);
|
|
else if(type == ORDER_TYPE_SELL)
|
|
result = m_trade.Sell(volume, symbol, price, sl, tp, comment);
|
|
|
|
// Check result
|
|
if(!result)
|
|
{
|
|
HandleError(GetLastError(), "Order execution failed");
|
|
return false;
|
|
}
|
|
|
|
// Update metrics
|
|
m_dailyOrderCount++;
|
|
LogTradeMetrics(symbol, type, volume, price, sl, tp);
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check margin requirements |
|
|
//+------------------------------------------------------------------+
|
|
bool CRiskManager::CheckMargin(string symbol, double volume, double price)
|
|
{
|
|
double margin = 0.0;
|
|
|
|
if(!OrderCalcMargin(ORDER_TYPE_BUY, symbol, volume, price, margin))
|
|
{
|
|
m_lastError = "Failed to calculate margin";
|
|
return false;
|
|
}
|
|
|
|
double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
|
|
|
|
if(margin > freeMargin)
|
|
{
|
|
m_lastError = StringFormat("Insufficient margin. Required: %.2f, Available: %.2f", margin, freeMargin);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check current spread |
|
|
//+------------------------------------------------------------------+
|
|
bool CRiskManager::CheckSpread(string symbol)
|
|
{
|
|
double spread = SymbolInfoInteger(symbol, SYMBOL_SPREAD) * SymbolInfoDouble(symbol, SYMBOL_POINT);
|
|
double maxSpread = SymbolInfoDouble(symbol, SYMBOL_ASK) * 0.001; // 0.1% of ask price
|
|
|
|
if(spread > maxSpread)
|
|
{
|
|
m_lastError = StringFormat("Spread too high: %.1f pips (max: %.1f)", spread, maxSpread);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check if current time is within trading hours |
|
|
//+------------------------------------------------------------------+
|
|
bool CRiskManager::CheckMarketHours(void)
|
|
{
|
|
// Implement market hours check based on your trading strategy
|
|
// This is a placeholder - adjust according to your needs
|
|
MqlDateTime time;
|
|
TimeToStruct(TimeCurrent(), time);
|
|
|
|
// Example: Don't trade during major news events (first Friday of the month)
|
|
if(time.day_of_week == 5 && time.day <= 7)
|
|
{
|
|
m_lastError = "Avoiding trading during major news events";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Check for high-impact news events |
|
|
//+------------------------------------------------------------------+
|
|
bool CRiskManager::CheckNewsImpact(string symbol)
|
|
{
|
|
// Implement news impact check based on your strategy
|
|
// This is a placeholder - integrate with your news API or calendar
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Update daily metrics |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManager::UpdateDailyMetrics(void)
|
|
{
|
|
MqlDateTime time;
|
|
TimeToStruct(TimeCurrent(), time);
|
|
|
|
// Reset daily metrics if it's a new day
|
|
if(time.day != m_lastTradeDay)
|
|
{
|
|
m_dailyPL = 0.0;
|
|
m_dailyOrderCount = 0;
|
|
m_lastTradeDay = time.day;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Log trade metrics |
|
|
//+------------------------------------------------------------------+
|
|
void CRiskManager::LogTradeMetrics(string symbol, ENUM_ORDER_TYPE type, double volume, double price, double sl, double tp)
|
|
{
|
|
// Log trade details for analysis
|
|
string logEntry = StringFormat("%s %s %.2f lots @ %.5f, SL: %.5f, TP: %.5f",
|
|
type == ORDER_TYPE_BUY ? "BUY" : "SELL",
|
|
symbol, volume, price, sl, tp);
|
|
|
|
Print(logEntry);
|
|
|
|
// Here you could also write to a file or database for long-term analysis
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|