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:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user