mql5/Experts/_Architecture/Trade/RiskManager.mqh
younglubah-gmail e8a2e2d6d1
2026-08-14 16:19:56 +02:00

296 lines
10 KiB
MQL5

//+------------------------------------------------------------------+
//| RiskManager.mqh |
//| Copyright 2025, MetaQuotes Ltd. |
//| https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2025, MetaQuotes Ltd."
#property link "https://www.mql5.com"
#property version "1.00"
#define MAX_PERCENT 10
#include "../Configs/EAConfig.mqh"
#include "../Market/DataManager.mqh"
#include "../Events/Events/SignalEvent.mqh"
#include "../Events/Events/TradingEvents.mqh"
#include <Mql5Book\TradeHedge.mqh>
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
class RiskManager final: public IEventListener
{
private:
EventBus* m_bus;
EAConfig m_eaConfig;
CPositions m_pos;
int m_inFlight;
double MoneyManagement(string pSymbol, double pFixedVol, double pPercent, int pStopPoints);
double VerifyVolume(string pSymbol, double pVolume);
bool FindSymbolConfig(string symbol, SymbolConfig &config)
{
for(int i = 0; i < m_eaConfig.tfConfigs.Size(); i++)
{
SymbolConfig cfg = m_eaConfig.tfConfigs.At(i);
if(cfg.symbol == symbol)
{
config = cfg;
return true;
}
}
return false;
}
bool IsDirectionAllowed(int direction, const SymbolConfig &config)
{
if(direction == 1)
return config.allowBuy;
if(direction == -1)
return config.allowSell;
return false;
}
bool CheckPositionLimit(string symbol)
{
int actualPositions = m_pos.TotalPositions(m_eaConfig.magicNumber, symbol);
int totalExposure = actualPositions + m_inFlight;
/* Print("Risk: ", symbol, " actual=", actualPositions,
" inFlight=", m_inFlight, " total=", totalExposure,
"/", m_eaConfig.maxOpenPositions);
*/
if(totalExposure >= m_eaConfig.maxOpenPositions)
{
m_bus.Push(new RiskRejectedEvent(symbol, "Max positions reached"));
return false;
}
return true;
}
bool CheckPendingLimit(string symbol)
{
int actualPositions = m_pos.TotalPending(m_eaConfig.magicNumber, symbol);
int totalExposure = actualPositions + m_inFlight;
/* Print("Risk: ", symbol, " actual=", actualPositions,
" inFlight=", m_inFlight, " total=", totalExposure,
"/", m_eaConfig.maxOpenPositions);*/
if(totalExposure >= m_eaConfig.maxOpenPositions)
{
m_bus.Push(new RiskRejectedEvent(symbol, "Too many pending orders"));
return false;
}
return true;
}
double CalculateSL(const SignalEvent* signal, const SymbolConfig &config)
{
if(config.rmConfigs.StopLoss <= 0)
return 0.0;
double point = SymbolInfoDouble(signal.GetSymbolName(), SYMBOL_POINT);
if(point <= 0)
return 0.0;
if(signal.GetDirection() == 1)
return signal.GetPrice() - config.rmConfigs.StopLoss * point;
if(signal.GetDirection() == -1)
return signal.GetPrice() + config.rmConfigs.StopLoss * point;
return 0.0;
}
double CalculateTP(const SignalEvent* signal, const SymbolConfig &config)
{
if(config.rmConfigs.TakeProfit <= 0)
return 0.0;
double point = SymbolInfoDouble(signal.GetSymbolName(), SYMBOL_POINT);
if(point <= 0)
return 0.0;
if(signal.GetDirection() == 1)
return signal.GetPrice() + config.rmConfigs.TakeProfit * point;
if(signal.GetDirection() == -1)
return signal.GetPrice() - config.rmConfigs.TakeProfit * point;
return 0.0;
}
bool ValidatePrices(const SignalEvent* signal, double sl, double tp)
{
string symbol = signal.GetSymbolName();
double point = SymbolInfoDouble(symbol, SYMBOL_POINT);
if(point <= 0)
{
m_bus.Push(new RiskRejectedEvent(symbol, "Invalid symbol point"));
return false;
}
if(signal.GetPrice() <= 0)
{
m_bus.Push(new RiskRejectedEvent(symbol, "Invalid entry price"));
return false;
}
return true;
}
bool m_isLaunching;
public:
RiskManager() : m_bus(NULL), m_isLaunching(true), m_inFlight(0) {}
~RiskManager() {}
void SetLaunching(bool launching) { m_isLaunching = launching; }
void Initialize(EventBus* bus, const EAConfig &config)
{
m_bus = bus;
m_eaConfig = config;
}
virtual void OnEvent(AbstractEvent* event) override
{
if(m_isLaunching)
return;
if(event.GetType() == POSITION_OPENED)
{
if(m_inFlight > 0)
m_inFlight--;
return; // nothing else to do
}
// --- SIGNAL processing ---
if(event.GetType() == SIGNAL)
{
// Stale check – you may want to reduce this window or remove it temporarily
if(event.GetTimestamp() < TimeCurrent() - 5000)
{
Print("RiskManager: Rejecting stale SIGNAL");
return;
}
SignalEvent* signal = (SignalEvent*)event;
string symbol = signal.GetSymbolName();
int direction = signal.GetDirection();
ENUM_ORDER_TYPE orderType = signal.GetOrderType();
double entryPrice = signal.GetPrice();
SymbolConfig symCfg;
if(!FindSymbolConfig(symbol, symCfg))
{
m_bus.Push(new RiskRejectedEvent(symbol, "Symbol configuration not found"));
return;
}
if(signal.GetTimeFrame() != symCfg.tradeTimeframe)
{
m_bus.Push(new RiskRejectedEvent(symbol, "Trading TimeFrame not allowed"));
return;
}
if(!IsDirectionAllowed(direction, symCfg))
{
m_bus.Push(new RiskRejectedEvent(symbol, "Trading direction not allowed"));
return;
}
bool isMarketOrder = (orderType == ORDER_TYPE_BUY || orderType == ORDER_TYPE_SELL);
bool isPendingOrder = (orderType == ORDER_TYPE_BUY_LIMIT || orderType == ORDER_TYPE_BUY_STOP ||
orderType == ORDER_TYPE_BUY_STOP_LIMIT || orderType == ORDER_TYPE_SELL_LIMIT ||
orderType == ORDER_TYPE_SELL_STOP || orderType == ORDER_TYPE_SELL_STOP_LIMIT);
if(!isMarketOrder && !isPendingOrder)
{
m_bus.Push(new RiskRejectedEvent(symbol, "Unsupported order type"));
return;
}
if(isPendingOrder && entryPrice <= 0)
{
m_bus.Push(new RiskRejectedEvent(symbol, "Invalid pending order price"));
return;
}
// *** Check limits (now using actual + pending)
if(!CheckPositionLimit(symbol))
return;
if(isPendingOrder && !CheckPendingLimit(symbol))
return;
double sl = CalculateSL(signal, symCfg);
double tp = CalculateTP(signal, symCfg);
if(!ValidatePrices(signal, sl, tp))
return;
double lotSize = MoneyManagement(symbol, symCfg.rmConfigs.FixedVolume, m_eaConfig.RiskPercent, symCfg.rmConfigs.StopLoss);
lotSize = VerifyVolume(symbol, lotSize);
if(lotSize <= 0)
{
m_bus.Push(new RiskRejectedEvent(symbol, "Invalid trade volume"));
return;
}
if(isMarketOrder)
{
m_bus.Push(new ExecuteMarketOrderEvent(symbol, entryPrice, direction, lotSize, sl, tp, m_eaConfig.comment));
m_inFlight++;
return;
}
if(isPendingOrder)
{
m_bus.Push(new ExecutePendingOrderEvent(symbol, orderType, lotSize, entryPrice, 0, sl, tp, 0, m_eaConfig.comment + "_Pending"));
m_inFlight++;
}
}
}
};
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double RiskManager::MoneyManagement(string pSymbol, double pFixedVol, double pPercent, int pStopPoints)
{
double tradeSize;
if(pPercent > 0 && pStopPoints > 0)
{
if(pPercent > MAX_PERCENT)
{
pPercent = MAX_PERCENT;
Print("Warning: Risk Percent exceeds the limit: ", MAX_PERCENT);
}
double margin = AccountInfoDouble(ACCOUNT_BALANCE) * (pPercent / 100);
double tickSize = SymbolInfoDouble(pSymbol, SYMBOL_TRADE_TICK_VALUE);
tradeSize = (margin / pStopPoints) / tickSize;
}
else
tradeSize = pFixedVol;
return VerifyVolume(pSymbol, tradeSize);
}
//+------------------------------------------------------------------+
//| |
//+------------------------------------------------------------------+
double RiskManager::VerifyVolume(string pSymbol, double pVolume)
{
double minVolume = SymbolInfoDouble(pSymbol, SYMBOL_VOLUME_MIN);
double maxVolume = SymbolInfoDouble(pSymbol, SYMBOL_VOLUME_MAX);
double stepVolume = SymbolInfoDouble(pSymbol, SYMBOL_VOLUME_STEP);
double tradeSize;
if(pVolume < minVolume)
tradeSize = minVolume;
else
if(pVolume > maxVolume)
tradeSize = maxVolume;
else
tradeSize = MathRound(pVolume / stepVolume) * stepVolume;
if(stepVolume >= 0.1)
tradeSize = NormalizeDouble(tradeSize, 1);
else
tradeSize = NormalizeDouble(tradeSize, 2);
return tradeSize;
}
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+