From e7d38ec9379f7cdc15644fdd4f20c6ae01e060ab Mon Sep 17 00:00:00 2001 From: pietro_giacobazzi Date: Sun, 14 Jun 2026 09:55:51 +0200 Subject: [PATCH] Risk-based position sizing: replace Inp_Lot+Inp_RiskFraction with Inp_RiskPerTrade+Inp_RiskTotal, proper CalcRiskLot() using tickValue/tickSize, total risk cap, track lot/riskAmount per trade --- .../MultiAgentTest/Core/Orchestrator.mqh | 194 ++++++++++++------ .../Experts/MultiAgentTest/MultiAgentTest.mq5 | 58 +++--- 2 files changed, 155 insertions(+), 97 deletions(-) diff --git a/MQL5/Experts/MultiAgentTest/Core/Orchestrator.mqh b/MQL5/Experts/MultiAgentTest/Core/Orchestrator.mqh index 3a6e2a7..58466fc 100644 --- a/MQL5/Experts/MultiAgentTest/Core/Orchestrator.mqh +++ b/MQL5/Experts/MultiAgentTest/Core/Orchestrator.mqh @@ -73,6 +73,8 @@ struct TrackedTrade { double entryZScores[MAX_AGENTS]; double entryFeatures[NN_FEATURES]; double slPrice; + double lot; + double riskAmount; bool isBuy; double highestPrice; double lowestPrice; @@ -166,7 +168,8 @@ private: int m_nnEpochs; double m_nnLR; int m_nnHidden; - double m_riskFraction; + double m_riskPerTrade; + double m_riskTotal; string m_symbol; // --- History cache --- @@ -250,11 +253,11 @@ public: for(int i=0; i 0 && tickValue > 0 && slPoints > 0) + openTrades[idx].riskAmount = lot * (tickValue / tickSize) * slPoints; + else + openTrades[idx].riskAmount = 0; - // SL iniziale adattivo - double slWidth = AdaptiveSLWidth(); - openTrades[idx].slPrice = isBuy ? price - atr * slWidth : price + atr * slWidth; + Print("Trade #", ticket, " ", isBuy ? "BUY" : "SELL", + " entry=", price, " z=", StringFormat("%+.3f", z), + " SL=", StringFormat("%.5f", openTrades[idx].slPrice), + " (", StringFormat("%.1f", slWidth), " ATR)", + " lot=", StringFormat("%.3f", lot), + " risk=", StringFormat("%.2f", openTrades[idx].riskAmount)); + return idx; + } - Print("Trade #", ticket, " ", isBuy ? "BUY" : "SELL", - " entry=", price, " z=", StringFormat("%+.3f", z), - " SL=", StringFormat("%.5f", openTrades[idx].slPrice), - " (", StringFormat("%.1f", slWidth), " ATR)"); - return idx; - } - - void OnTradeOpen(int ticket, double price, double atr) { - AddTrade(ticket, price, atr, combinedZ, combinedZ > 0); + void OnTradeOpen(int ticket, double price, double atr, double lot) { + AddTrade(ticket, price, atr, combinedZ, combinedZ > 0, lot); LogDecision("ENTRY", combinedZ > 0 ? 1 : -1, 0, price, ticket); } @@ -1017,15 +1033,55 @@ UpdateHealth(actualReturn); return sl * rr; } - double AdaptiveBaseLot() const { + // Somma del rischio (in valuta conto) di tutte le posizioni aperte + double TotalRiskUsed() const { + double total = 0; + for(int i=0; i 0 && lot < minLot) lot = minLot; + double tickValue = SymbolInfoDouble(m_symbol, SYMBOL_TRADE_TICK_VALUE); + double tickSize = SymbolInfoDouble(m_symbol, SYMBOL_TRADE_TICK_SIZE); + double lotStep = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_STEP); + double lotMin = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MIN); + double lotMax = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MAX); + + // Rischio desiderato per questo trade = bal × riskPerTrade × |z| (confidenza) + double riskAmount = bal * m_riskPerTrade * MathAbs(zScore); + + // Cap sul rischio totale (tutte le posizioni aperte insieme) + double maxTotalRisk = bal * m_riskTotal; + double usedRisk = TotalRiskUsed(); + double remainingRisk = maxTotalRisk - usedRisk; + if(remainingRisk <= 0) { Print(" Risk budget esaurito (", StringFormat("%.2f", usedRisk), "/", StringFormat("%.2f", maxTotalRisk), ")"); return 0; } + riskAmount = MathMin(riskAmount, remainingRisk); + + // Costo in valuta conto per 1 lotto a questa distanza SL + double riskPerLot = 0; + if(tickSize > 0 && tickValue > 0) + riskPerLot = (tickValue / tickSize) * MathAbs(slPoints); + + if(riskPerLot <= 0) { + Print(" CalcRiskLot: tickValue=", tickValue, " tickSize=", tickSize, " — impossibile calcolare lotto"); + return lotMin; + } + + double lot = riskAmount / riskPerLot; + lot = MathRound(lot / lotStep) * lotStep; + lot = MathMax(lotMin, MathMin(lotMax, lot)); + + Print(" Risk: bal=", StringFormat("%.0f", bal), + " risk=", StringFormat("%.2f", riskAmount), + " SL=", StringFormat("%.5f", slPoints), + " risk/Lot=", StringFormat("%.2f", riskPerLot), + " → lot=", StringFormat("%.4f", lot)); return lot; - } + } double AutocorrelationQuick() const { int n = histCount; @@ -1323,7 +1379,8 @@ UpdateHealth(actualReturn); FileWriteString(fh, "Generated," + TimeToString(TimeCurrent()) + "\r\n"); FileWriteString(fh, "AgentCount," + (string)agentCount + "\r\n"); FileWriteString(fh, "NeuralMode," + (string)m_useNeural + "\r\n"); - FileWriteString(fh, "RiskFraction," + StringFormat("%.4f", m_riskFraction) + "\r\n"); + FileWriteString(fh, "RiskPerTrade," + StringFormat("%.4f", m_riskPerTrade) + "\r\n"); + FileWriteString(fh, "RiskTotal," + StringFormat("%.4f", m_riskTotal) + "\r\n"); double bal = AccountInfoDouble(ACCOUNT_BALANCE); double eq = AccountInfoDouble(ACCOUNT_EQUITY); FileWriteString(fh, "Balance," + StringFormat("%.2f", bal) + "\r\n"); @@ -1377,8 +1434,8 @@ UpdateHealth(actualReturn); FileWriteString(fh, "TrailTrigger," + StringFormat("%.4f", derivTrailTrig) + "\r\n"); double derivTrailOff = AdaptiveTrailOffset(); FileWriteString(fh, "TrailOffset," + StringFormat("%.4f", derivTrailOff) + "\r\n"); - double derivBaseLot = AdaptiveBaseLot(); - FileWriteString(fh, "BaseLot," + StringFormat("%.4f", derivBaseLot) + "\r\n"); + FileWriteString(fh, "RiskPerTrade," + StringFormat("%.4f", m_riskPerTrade) + "\r\n"); + FileWriteString(fh, "RiskTotal," + StringFormat("%.4f", m_riskTotal) + "\r\n"); FileWriteString(fh, "CorrMinSamples," + (string)corrMinSamples + "\r\n"); FileWriteString(fh, "WeightMin," + StringFormat("%.6f", weightMin) + "\r\n"); FileWriteString(fh, "WeightAlpha," + StringFormat("%.6f", weightAlpha) + "\r\n"); @@ -1427,7 +1484,8 @@ UpdateHealth(actualReturn); FileWriteString(fh, "Inp_TrainMode," + (string)m_trainMode + "\r\n"); FileWriteString(fh, "Inp_TrainEpochs," + (string)m_nnEpochs + "\r\n"); FileWriteString(fh, "Inp_TrainLR," + StringFormat("%.5f", m_nnLR) + "\r\n"); - FileWriteString(fh, "Inp_RiskFraction," + StringFormat("%.4f", m_riskFraction) + "\r\n"); + FileWriteString(fh, "Inp_RiskPerTrade," + StringFormat("%.4f", m_riskPerTrade) + "\r\n"); + FileWriteString(fh, "Inp_RiskTotal," + StringFormat("%.4f", m_riskTotal) + "\r\n"); FileWriteString(fh, "MaxOpenTrades," + (string)maxOpenTrades + "\r\n"); FileClose(fh); diff --git a/MQL5/Experts/MultiAgentTest/MultiAgentTest.mq5 b/MQL5/Experts/MultiAgentTest/MultiAgentTest.mq5 index 57df31d..602e65a 100644 --- a/MQL5/Experts/MultiAgentTest/MultiAgentTest.mq5 +++ b/MQL5/Experts/MultiAgentTest/MultiAgentTest.mq5 @@ -14,23 +14,23 @@ #include "Agents\MomentumAgent.mqh" input string Inp_Symbol = ""; -input ENUM_TIMEFRAMES Inp_TF = PERIOD_CURRENT; // CURRENT = timeframe del chart -input double Inp_Lot = 0.0; // 0 = adaptive (AdaptiveBaseLot * |z|), >0 = fixed lot -input double Inp_MinZ = 0.0; // 0=adattivo, >0=fisso -input double Inp_WMin = 0.0; // 0 = auto (1/agentCount * 0.1) -input int Inp_BufferBars = 0; // 0 = auto (HurstMaxPeriod * 10) -input double Inp_SLRiskATR = 0.0; // 0=adattivo, >0=fisso -input double Inp_TPRiskATR = 0.0; // 0=adattivo, >0=fisso -input int Inp_HurstPeriod = 0; // 0=auto, >0=fisso -input int Inp_ADXPeriod = 0; // 0=auto, >0=fisso -input bool Inp_UseNeural = false; // true = neural orchestrator, false = softmax -input string Inp_NNModelFile = ""; // modello NN (vuoto = auto Nome_Symbol_TF.dat) -input bool Inp_TrainMode = false; // colleziona campioni e allena NN in backtest -input int Inp_TrainEpochs = 100; // epoche di training -input double Inp_TrainLR = 0.001; // learning rate Adam -input int Inp_NNHidden = 6; // neuroni hidden layer -input double Inp_RiskFraction = 0.20; // frazione di capitale da rischiare per trade -input bool Inp_UseReversalClose = true; // true=chiudi posizioni su segnale opposto, false=long+short coesistono +input ENUM_TIMEFRAMES Inp_TF = PERIOD_CURRENT; +input double Inp_MinZ = 0.0; +input double Inp_WMin = 0.0; +input int Inp_BufferBars = 0; +input double Inp_SLRiskATR = 0.0; +input double Inp_TPRiskATR = 0.0; +input int Inp_HurstPeriod = 0; +input int Inp_ADXPeriod = 0; +input bool Inp_UseNeural = false; +input string Inp_NNModelFile = ""; +input bool Inp_TrainMode = false; +input int Inp_TrainEpochs = 100; +input double Inp_TrainLR = 0.001; +input int Inp_NNHidden = 6; +input double Inp_RiskPerTrade = 0.01; // % capitale da rischiare per trade (es. 0.01 = 1%) +input double Inp_RiskTotal = 0.05; // % capitale massima totale in rischio +input bool Inp_UseReversalClose = true; Orchestrator *orchestrator; CTrade *trade; @@ -74,7 +74,7 @@ int OnInit() { Inp_UseNeural, Inp_NNModelFile, Inp_TrainMode, Inp_TrainEpochs, Inp_TrainLR, Inp_NNHidden, - Inp_RiskFraction); + Inp_RiskPerTrade, Inp_RiskTotal); trade = new CTrade(); dig = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS); @@ -347,18 +347,18 @@ void ManagePositions(const MarketData &data, const FinalSignal &fs) { // SL adattivo via MAE (trailing stop gestisce l'uscita, no TP fisso) double slWidth = (Inp_SLRiskATR > 0) ? Inp_SLRiskATR : orchestrator.AdaptiveSLWidth(); - double sl = (fs.direction == 1) ? price - atr * slWidth - : price + atr * slWidth; - sl = NormalizeDouble(sl, dig); + double sl = (fs.direction == 1) ? price - atr * slWidth + : price + atr * slWidth; + sl = NormalizeDouble(sl, dig); + double slPoints = price - sl; + if(fs.direction == -1) slPoints = sl - price; + if(slPoints <= 0) { Print("SL troppo stretto — skip"); return; } - double lot = (Inp_Lot > 0) ? Inp_Lot : orchestrator.AdaptiveBaseLot() * MathAbs(fs.zScore); - double lotStep = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP); - double lotMin = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN); - double lotMax = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX); - lot = MathRound(lot / lotStep) * lotStep; - lot = MathMax(lotMin, MathMin(lotMax, lot)); + // Posizione sizing risk-based tramite Orchestrator + double lot = orchestrator.CalcRiskLot(fs.zScore, slPoints); + if(lot <= 0) { Print("Lot calcolato = 0 — skip"); return; } - // Solo il margine libero limita i trade + // Solo il margine libero limita i trade double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double marginReq = lot * SymbolInfoDouble(sym, SYMBOL_MARGIN_INITIAL); if(marginReq >= freeMargin && freeMargin > 0) { @@ -396,7 +396,7 @@ void ManagePositions(const MarketData &data, const FinalSignal &fs) { } if(ticket > 0) { - orchestrator.OnTradeOpen(ticket, price, atr); + orchestrator.OnTradeOpen(ticket, price, atr, lot); } } }