168 lines
5.0 KiB
Plaintext
168 lines
5.0 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| RiskManager.mqh - Risk and position sizing management |
|
|
//| Calculates lot sizes, validates parameters, checks trading hours |
|
|
//+------------------------------------------------------------------+
|
|
|
|
#ifndef __RISKMANAGER_MQH__
|
|
#define __RISKMANAGER_MQH__
|
|
|
|
#include "Config.mqh"
|
|
#include "Logger.mqh"
|
|
#include "MarketData.mqh"
|
|
#include "Utilities.mqh"
|
|
|
|
class CRiskManager
|
|
{
|
|
private:
|
|
CMarketData *mp_market_data;
|
|
CLogger *mp_logger;
|
|
|
|
public:
|
|
// Constructor
|
|
CRiskManager(CMarketData *market_data, CLogger *logger)
|
|
{
|
|
mp_market_data = market_data;
|
|
mp_logger = logger;
|
|
}
|
|
|
|
// Calculate lot size based on configuration
|
|
double CalculateLotSize(int stop_loss_points)
|
|
{
|
|
double lot = 0.0;
|
|
|
|
if(g_lot_mode == LOT_MODE_FIXED)
|
|
{
|
|
lot = g_fixed_lot;
|
|
}
|
|
else if(g_lot_mode == LOT_MODE_RISK)
|
|
{
|
|
lot = CalculateLotByRisk(stop_loss_points);
|
|
}
|
|
|
|
return ValidateLotSize(lot);
|
|
}
|
|
|
|
// Calculate lot size based on risk percent
|
|
double CalculateLotByRisk(int stop_loss_points)
|
|
{
|
|
double account_balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double stop_loss_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(), stop_loss_points);
|
|
double contract_size = CUtilities::GetContractSize(mp_market_data.GetSymbol());
|
|
|
|
if(stop_loss_distance == 0 || contract_size == 0)
|
|
return g_min_lot;
|
|
|
|
// Risk = Account Balance * Risk Percent / 100
|
|
double risk_amount = account_balance * (g_risk_percent / 100.0);
|
|
|
|
// Lot = Risk Amount / (SL Distance * Contract Size * Point)
|
|
double point = mp_market_data.GetPoint();
|
|
double lot = risk_amount / (stop_loss_distance * contract_size);
|
|
|
|
return lot;
|
|
}
|
|
|
|
// Validate and normalize lot size
|
|
double ValidateLotSize(double lot)
|
|
{
|
|
// Apply global limits first
|
|
if(lot < g_min_lot)
|
|
lot = g_min_lot;
|
|
if(lot > g_max_lot)
|
|
lot = g_max_lot;
|
|
|
|
// Normalize to broker's lot step
|
|
lot = CUtilities::NormalizeLot(mp_market_data.GetSymbol(), lot);
|
|
|
|
if(mp_logger && g_debug_mode)
|
|
mp_logger.Info(StringFormat("Lot size calculated: %.2f", lot));
|
|
|
|
return lot;
|
|
}
|
|
|
|
// Check if spread is acceptable
|
|
bool IsSpreadAcceptable()
|
|
{
|
|
int max_spread = g_max_spread_points;
|
|
string symbol = mp_market_data.GetSymbol();
|
|
|
|
// XAUUSD/gold normally trades with a much wider spread than forex
|
|
if(StringFind(symbol, "XAU") >= 0 || StringFind(symbol, "GOLD") >= 0)
|
|
{
|
|
max_spread = MathMax(max_spread, 200);
|
|
}
|
|
|
|
return mp_market_data.IsSpreadAcceptable(max_spread);
|
|
}
|
|
|
|
// Check if trading is allowed by time filter
|
|
bool IsTradingHourValid()
|
|
{
|
|
if(!g_use_trading_hours)
|
|
return true;
|
|
|
|
MqlDateTime time_struct;
|
|
TimeToStruct(TimeCurrent(), time_struct);
|
|
int current_hour = time_struct.hour;
|
|
|
|
if(g_trade_start_hour <= g_trade_end_hour)
|
|
{
|
|
// Normal case: e.g., 8:00 to 20:00
|
|
if(current_hour < g_trade_start_hour || current_hour >= g_trade_end_hour)
|
|
{
|
|
if(mp_logger && g_debug_mode)
|
|
mp_logger.Info(StringFormat("Outside trading hours: %d (allowed: %d-%d)",
|
|
current_hour, g_trade_start_hour, g_trade_end_hour));
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Overnight case: e.g., 20:00 to 8:00
|
|
if(current_hour < g_trade_start_hour && current_hour >= g_trade_end_hour)
|
|
{
|
|
if(mp_logger && g_debug_mode)
|
|
mp_logger.Info(StringFormat("Outside trading hours: %d (allowed: %d-%d)",
|
|
current_hour, g_trade_start_hour, g_trade_end_hour));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Calculate stop loss price in absolute terms
|
|
double CalculateStopLossPrice(bool buy)
|
|
{
|
|
double bid = mp_market_data.GetBid();
|
|
double ask = mp_market_data.GetAsk();
|
|
double entry_price = buy ? ask : bid;
|
|
double sl_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(), g_stop_loss_points);
|
|
|
|
double sl_price = buy ? (entry_price - sl_distance) : (entry_price + sl_distance);
|
|
|
|
return CUtilities::NormalizePrice(mp_market_data.GetSymbol(), sl_price);
|
|
}
|
|
|
|
// Calculate take profit price in absolute terms
|
|
double CalculateTakeProfitPrice(bool buy)
|
|
{
|
|
double bid = mp_market_data.GetBid();
|
|
double ask = mp_market_data.GetAsk();
|
|
double entry_price = buy ? ask : bid;
|
|
double tp_distance = CUtilities::PointsToPrice(mp_market_data.GetSymbol(), g_take_profit_points);
|
|
|
|
double tp_price = buy ? (entry_price + tp_distance) : (entry_price - tp_distance);
|
|
|
|
return CUtilities::NormalizePrice(mp_market_data.GetSymbol(), tp_price);
|
|
}
|
|
|
|
// Get market data reference
|
|
CMarketData* GetMarketData()
|
|
{
|
|
return mp_market_data;
|
|
}
|
|
};
|
|
|
|
#endif //__RISKMANAGER_MQH__
|