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

This commit is contained in:
pietro_giacobazzi
2026-06-14 09:55:51 +02:00
parent e2ca6336af
commit e7d38ec937
2 changed files with 155 additions and 97 deletions
+126 -68
View File
@@ -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<maxOpenTrades; i++) openTrades[i].active = false;
}
Orchestrator(double minZ=0.5, double wMin=0.05, double wAlpha=0.05,
bool useNeural=false, string modelFile="",
bool trainMode=false, int nnEpochs=100,
double nnLR=0.001, int nnHidden=6,
double riskFraction=0.20) {
Orchestrator(double minZ=0.5, double wMin=0.05, double wAlpha=0.05,
bool useNeural=false, string modelFile="",
bool trainMode=false, int nnEpochs=100,
double nnLR=0.001, int nnHidden=6,
double riskPerTrade=0.01, double riskTotal=0.05) {
agentCount = 0;
combinedZ = 0;
minActionableZ = minZ;
@@ -293,8 +296,9 @@ public:
m_modelFilename = modelFile;
m_nnEpochs = nnEpochs;
m_nnLR = nnLR;
m_nnHidden = nnHidden;
m_riskFraction = riskFraction;
m_nnHidden = nnHidden;
m_riskPerTrade = riskPerTrade;
m_riskTotal = riskTotal;
m_symbol = _Symbol;
m_neuralNet = NULL;
m_trainBuffer = NULL;
@@ -475,60 +479,72 @@ public:
}
}
int AddTrade(int ticket, double price, double atr, double z, bool isBuy) {
// Solo il margine libero limita i trade — nessun cap artificiale
EnsureTradeCapacity();
// Trova slot libero
int idx = -1;
for(int i=0; i<maxOpenTrades; i++) {
if(!openTrades[i].active) { idx = i; break; }
int AddTrade(int ticket, double price, double atr, double z, bool isBuy, double lot) {
// Solo il margine libero limita i trade — nessun cap artificiale
EnsureTradeCapacity();
// Trova slot libero
int idx = -1;
for(int i=0; i<maxOpenTrades; i++) {
if(!openTrades[i].active) { idx = i; break; }
}
if(idx < 0) { // non dovrebbe mai succedere dopo EnsureTradeCapacity
Print("ERROR: slot non disponibile nonostante capacity expansion");
return -1;
}
openTrades[idx].ticket = ticket;
openTrades[idx].entryPrice = price;
openTrades[idx].entryATR = MathMax(atr, 1e-10);
openTrades[idx].entryZ = z;
openTrades[idx].isBuy = isBuy;
openTrades[idx].lot = lot;
openTrades[idx].entryTime = TimeCurrent();
openTrades[idx].highestPrice = price;
openTrades[idx].lowestPrice = price;
openTrades[idx].barsHeld = 0;
openTrades[idx].maeATR = 0;
openTrades[idx].mfeATR = 0;
openTrades[idx].active = true;
for(int i=0; i<agentCount; i++)
openTrades[idx].entryZScores[i] = agents[i].lastZScore;
// Feature vector per NN
for(int f = 0; f < NN_FEATURES; f++) openTrades[idx].entryFeatures[f] = 0.0;
for(int i=0; i<agentCount; i++) {
if(agents[i].name == "Hurst") openTrades[idx].entryFeatures[0] = agents[i].lastZScore;
else if(agents[i].name == "ADX") openTrades[idx].entryFeatures[1] = agents[i].lastZScore;
else if(agents[i].name == "MA") openTrades[idx].entryFeatures[2] = agents[i].lastZScore;
else if(agents[i].name == "Momentum") openTrades[idx].entryFeatures[3] = agents[i].lastZScore;
else if(agents[i].name == "Consensus") openTrades[idx].entryFeatures[4] = agents[i].lastZScore;
else if(agents[i].name == "Hunter") openTrades[idx].entryFeatures[5] = agents[i].lastZScore;
}
if(idx < 0) { // non dovrebbe mai succedere dopo EnsureTradeCapacity
Print("ERROR: slot non disponibile nonostante capacity expansion");
return -1;
}
openTrades[idx].ticket = ticket;
openTrades[idx].entryPrice = price;
openTrades[idx].entryATR = MathMax(atr, 1e-10);
openTrades[idx].entryZ = z;
openTrades[idx].isBuy = isBuy;
openTrades[idx].entryTime = TimeCurrent();
openTrades[idx].highestPrice = price;
openTrades[idx].lowestPrice = price;
openTrades[idx].barsHeld = 0;
openTrades[idx].maeATR = 0;
openTrades[idx].mfeATR = 0;
openTrades[idx].active = true;
openTrades[idx].entryFeatures[6] = SHARED_regimeAgreement;
openTrades[idx].entryFeatures[7] = SHARED_trendStrength;
for(int i=0; i<agentCount; i++)
openTrades[idx].entryZScores[i] = agents[i].lastZScore;
// SL iniziale adattivo
double slWidth = AdaptiveSLWidth();
openTrades[idx].slPrice = isBuy ? price - atr * slWidth : price + atr * slWidth;
// Feature vector per NN
for(int f = 0; f < NN_FEATURES; f++) openTrades[idx].entryFeatures[f] = 0.0;
for(int i=0; i<agentCount; i++) {
if(agents[i].name == "Hurst") openTrades[idx].entryFeatures[0] = agents[i].lastZScore;
else if(agents[i].name == "ADX") openTrades[idx].entryFeatures[1] = agents[i].lastZScore;
else if(agents[i].name == "MA") openTrades[idx].entryFeatures[2] = agents[i].lastZScore;
else if(agents[i].name == "Momentum") openTrades[idx].entryFeatures[3] = agents[i].lastZScore;
else if(agents[i].name == "Consensus") openTrades[idx].entryFeatures[4] = agents[i].lastZScore;
else if(agents[i].name == "Hunter") openTrades[idx].entryFeatures[5] = agents[i].lastZScore;
}
openTrades[idx].entryFeatures[6] = SHARED_regimeAgreement;
openTrades[idx].entryFeatures[7] = SHARED_trendStrength;
// Rischio effettivo in valuta conto per questo trade
double tickValue = SymbolInfoDouble(m_symbol, SYMBOL_TRADE_TICK_VALUE);
double tickSize = SymbolInfoDouble(m_symbol, SYMBOL_TRADE_TICK_SIZE);
double slPoints = MathAbs(price - openTrades[idx].slPrice);
if(tickSize > 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<maxOpenTrades; i++)
if(openTrades[i].active) total += openTrades[i].riskAmount;
return total;
}
// Posizione sizing risk-based: calcola lotto da capitale da rischiare ÷ distanza SL
// slPoints = distanza in prezzo dall'entry allo StopLoss (es. 0.00150 per 15 pips EURUSD)
double CalcRiskLot(double zScore, double slPoints) const {
double bal = AccountInfoDouble(ACCOUNT_BALANCE);
double minLot = SymbolInfoDouble(m_symbol, SYMBOL_VOLUME_MIN);
// Lotto base = riskFraction del capitale. Il chiamante scala per |z|.
// Esempio: 20% di 10000$ / 100000 = 0.20 lot, × |z|=1.0 → 0.20 lot.
double lot = bal * m_riskFraction / 100000.0;
if(minLot > 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);