90 lines
3.7 KiB
Plaintext
90 lines
3.7 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| Risk/PositionSizer.mqh |
|
|
//| ATR-based Position Sizing with Dynamic Risk Multiplier |
|
|
//| Reduces size after losses, increases after wins |
|
|
//+------------------------------------------------------------------+
|
|
#ifndef __POSITION_SIZER_MQH__
|
|
#define __POSITION_SIZER_MQH__
|
|
|
|
#include "../Core/Config.mqh"
|
|
#include "../Core/State.mqh"
|
|
#include "../Risk/Protection.mqh"
|
|
|
|
extern CProtection g_protection;
|
|
|
|
class CPositionSizer
|
|
{
|
|
private:
|
|
AssetProfile m_profile;
|
|
double m_maxRiskPercent;
|
|
|
|
public:
|
|
bool Init(const AssetProfile &profile, double maxRisk)
|
|
{
|
|
m_profile = profile;
|
|
m_maxRiskPercent = maxRisk;
|
|
Print("[PositionSizer] Max risk per trade: ", maxRisk, "% (with dynamic multiplier)");
|
|
return true;
|
|
}
|
|
|
|
void Calculate(TradeParams ¶ms, const SignalData &signal, const EAState &state)
|
|
{
|
|
params.isValid = false;
|
|
params.rejectReason = "";
|
|
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
if(equity <= 0) { params.rejectReason = "Invalid account equity"; return; }
|
|
|
|
// Apply dynamic risk multiplier based on recent performance
|
|
double riskMultiplier = g_protection.GetRiskMultiplier();
|
|
double adjustedRiskPercent = m_maxRiskPercent * riskMultiplier;
|
|
|
|
double riskAmount = equity * (adjustedRiskPercent / 100.0);
|
|
double slDistance = MathAbs(signal.entryPrice - signal.slPrice);
|
|
if(slDistance <= 0) { params.rejectReason = "Invalid SL distance"; return; }
|
|
|
|
double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
|
|
double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
|
|
if(tickValue <= 0 || tickSize <= 0) { params.rejectReason = "Invalid tick value/size"; return; }
|
|
|
|
double slTicks = slDistance / tickSize;
|
|
double lotSize = riskAmount / (slTicks * tickValue);
|
|
double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
|
|
double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
|
|
double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
|
|
if(lotStep > 0) lotSize = MathFloor(lotSize / lotStep) * lotStep;
|
|
lotSize = MathMax(minLot, MathMin(maxLot, lotSize));
|
|
|
|
double marginRequired = 0;
|
|
double price = signal.entryPrice;
|
|
bool marginCalc = OrderCalcMargin(ORDER_TYPE_BUY, _Symbol, lotSize, price, marginRequired);
|
|
|
|
double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
|
|
if(marginCalc && marginRequired > 0 && freeMargin < marginRequired * 1.2)
|
|
{
|
|
double maxLotByMargin = (freeMargin / 1.2) / (marginRequired / lotSize);
|
|
if(lotStep > 0) lotSize = MathFloor(maxLotByMargin / lotStep) * lotStep;
|
|
lotSize = MathMax(minLot, lotSize);
|
|
if(lotSize <= minLot) { params.rejectReason = "Insufficient margin"; return; }
|
|
marginCalc = OrderCalcMargin(ORDER_TYPE_BUY, _Symbol, lotSize, price, marginRequired);
|
|
}
|
|
|
|
double finalSlTicks = slDistance / tickSize;
|
|
double finalRisk = lotSize * finalSlTicks * tickValue;
|
|
double finalRiskPercent = (finalRisk / equity) * 100.0;
|
|
if(finalRiskPercent > adjustedRiskPercent * 1.1)
|
|
{ params.rejectReason = "Risk exceeds adjusted max"; return; }
|
|
|
|
params.lotSize = lotSize;
|
|
params.riskAmount = finalRisk;
|
|
params.riskPercent = finalRiskPercent;
|
|
params.slDistance = slDistance;
|
|
params.tp1Distance = MathAbs(signal.tp1Price - signal.entryPrice);
|
|
params.tp2Distance = MathAbs(signal.tp2Price - signal.entryPrice);
|
|
params.marginRequired = marginRequired;
|
|
params.isValid = true;
|
|
}
|
|
};
|
|
|
|
#endif // __POSITION_SIZER_MQH__
|