From bb670836e1d031ccb94eaf5ca04555ef2deb856e Mon Sep 17 00:00:00 2001 From: Pietro Giacobazzi Date: Sun, 14 Jun 2026 18:21:44 +0000 Subject: [PATCH] style: normalize indentation in Orchestrator.mqh via clang-format Whitespace-only: consistent 3-space indentation throughout (was a mix of 3/4/5/6.. spaces). Repo conventions preserved: for(/if( spacing, access labels, attached braces. Adds .clang-format so the style is reproducible (run: clang-format -i --style=file ). Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- MQL5/Experts/MultiAgentTest/.clang-format | 15 + .../MultiAgentTest/Core/Orchestrator.mqh | 2571 +++++++++-------- 2 files changed, 1327 insertions(+), 1259 deletions(-) create mode 100644 MQL5/Experts/MultiAgentTest/.clang-format diff --git a/MQL5/Experts/MultiAgentTest/.clang-format b/MQL5/Experts/MultiAgentTest/.clang-format new file mode 100644 index 0000000..60452d9 --- /dev/null +++ b/MQL5/Experts/MultiAgentTest/.clang-format @@ -0,0 +1,15 @@ +BasedOnStyle: LLVM +IndentWidth: 3 +TabWidth: 3 +UseTab: Never +ColumnLimit: 0 +BreakBeforeBraces: Attach +AllowShortFunctionsOnASingleLine: All +AllowShortBlocksOnASingleLine: Always +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +SortIncludes: false +SpaceBeforeParens: Never +IndentCaseLabels: true +AccessModifierOffset: -3 +PointerAlignment: Right diff --git a/MQL5/Experts/MultiAgentTest/Core/Orchestrator.mqh b/MQL5/Experts/MultiAgentTest/Core/Orchestrator.mqh index fc25085..ee13b50 100644 --- a/MQL5/Experts/MultiAgentTest/Core/Orchestrator.mqh +++ b/MQL5/Experts/MultiAgentTest/Core/Orchestrator.mqh @@ -14,102 +14,102 @@ struct BarSnapshot { datetime time; - double z[MAX_AGENTS]; - double combinedZ; + double z[MAX_AGENTS]; + double combinedZ; }; struct AgentSnapshot { - double lastZScore; - double weight; - double rho; - double bias; - double biasStd; - double rhoLearn; - double rawSignal; + double lastZScore; + double weight; + double rho; + double bias; + double biasStd; + double rhoLearn; + double rawSignal; }; struct TradeRecord { - int ticket; - bool isBuy; + int ticket; + bool isBuy; datetime entryTime; datetime closeTime; - double entryPrice; - double closePrice; - double entryATR; - double entryZ; - double slPrice; - double highestPrice; - double lowestPrice; - int barsHeld; - double maeATR; - double mfeATR; - double actualReturn; - double entryZScores[MAX_AGENTS]; - double entryFeatures[NN_FEATURES]; - string exitReason; + double entryPrice; + double closePrice; + double entryATR; + double entryZ; + double slPrice; + double highestPrice; + double lowestPrice; + int barsHeld; + double maeATR; + double mfeATR; + double actualReturn; + double entryZScores[MAX_AGENTS]; + double entryFeatures[NN_FEATURES]; + string exitReason; }; struct DecisionRecord { datetime time; - string action; - int direction; - double zScore; - double combinedZ; - double price; - int ticket; - int agreeingCount; - int totalAgents; - double confidence; - double minZ; + string action; + int direction; + double zScore; + double combinedZ; + double price; + int ticket; + int agreeingCount; + int totalAgents; + double confidence; + double minZ; }; // Posizione aperta tracciata con MAE/MFE struct TrackedTrade { - int ticket; + int ticket; datetime entryTime; - double entryPrice; - double entryATR; - double entryZ; - double entryZScores[MAX_AGENTS]; - double entryFeatures[NN_FEATURES]; - double slPrice; - double lot; - double riskAmount; - bool isBuy; - double highestPrice; - double lowestPrice; - double maeATR; - double mfeATR; - int barsHeld; - bool active; + double entryPrice; + double entryATR; + double entryZ; + double entryZScores[MAX_AGENTS]; + double entryFeatures[NN_FEATURES]; + double slPrice; + double lot; + double riskAmount; + bool isBuy; + double highestPrice; + double lowestPrice; + double maeATR; + double mfeATR; + int barsHeld; + bool active; }; class Orchestrator { private: IAgent *agents[MAX_AGENTS]; - int agentCount; - + int agentCount; + // Stato correlazione per ogni agente (flat arrays per compatibilità MQL5) double corrMeanX[MAX_AGENTS], corrMeanY[MAX_AGENTS]; double corrCov[MAX_AGENTS], corrVarX[MAX_AGENTS], corrVarY[MAX_AGENTS]; - int corrCount[MAX_AGENTS]; - int corrMinSamples; - + int corrCount[MAX_AGENTS]; + int corrMinSamples; + // Distribuzione dei ritorni dei trade RunningStats returnStats; - - // Distribuzione del combinedZ (per soglia adattiva) - RunningStats combinedZStats; - - // --- Multi-trade management --- - TrackedTrade openTrades[]; - int maxOpenTrades; - - // MAE/MFE distributions per adaptive SL/trailing - RunningStats maeStats; // MAE di tutti i trade - RunningStats mfeStats; // MFE di tutti i trade - RunningStats maeWinStats; // MAE dei soli trade vincenti (per SL width) - + + // Distribuzione del combinedZ (per soglia adattiva) + RunningStats combinedZStats; + + // --- Multi-trade management --- + TrackedTrade openTrades[]; + int maxOpenTrades; + + // MAE/MFE distributions per adaptive SL/trailing + RunningStats maeStats; // MAE di tutti i trade + RunningStats mfeStats; // MFE di tutti i trade + RunningStats maeWinStats; // MAE dei soli trade vincenti (per SL width) + // Config double weightMin; double minActionableZ; @@ -117,7 +117,7 @@ private: // Helper: parametri RunningStats da N agenti double RStatsAlpha() const { return 1.0 / MathMax(agentCount * 10.0, 10.0); } - int RStatsMinSamp() const { return MathMax(agentCount * 5, 10); } + int RStatsMinSamp() const { return MathMax(agentCount * 5, 10); } // Expected max |z| for N i.i.d. N(0,1) (Extreme Value Theory) // E[max|Z_i|] ≈ sqrt(2·log(2N)) — leading term of EVT asymptotic double EVT_MaxAbsZ() const { @@ -136,65 +136,65 @@ private: double denom = MathSqrt(MathMax(agentCount, 1)) + 1.0; return EVT_SL() / denom; } - - // --- Self-evaluation --- - // Rolling Sharpe (ultimi 20 trade) - double rollingReturns[ROLLING_TRADES]; - int rollingIdx; - int rollingCount; - double rollingSharpe; - - // Win/Loss tracker per TP ratio - double tradeReturns[ROLLING_TRADES]; - int tradeCount; - int winCount; - - // Max drawdown storico per position sizing - double peakEquity; - double maxDrawdown; - - // Weight convergence - double prevCorr[MAX_AGENTS]; - int convergeCount; - bool isConverged; - double maxDeltaRho; - - // --- Neural Network --- - CNeuralNet *m_neuralNet; - NNTrainBuffer *m_trainBuffer; - bool m_useNeural; - bool m_trainMode; - string m_modelFilename; - int m_nnEpochs; - double m_nnLR; - int m_nnHidden; - double m_riskPerTrade; - double m_riskTotal; - string m_symbol; - string m_runTimestamp; - // --- History cache --- - BarSnapshot history[MAX_HISTORY]; - int histIdx; - int histCount; + // --- Self-evaluation --- + // Rolling Sharpe (ultimi 20 trade) + double rollingReturns[ROLLING_TRADES]; + int rollingIdx; + int rollingCount; + double rollingSharpe; + + // Win/Loss tracker per TP ratio + double tradeReturns[ROLLING_TRADES]; + int tradeCount; + int winCount; + + // Max drawdown storico per position sizing + double peakEquity; + double maxDrawdown; + + // Weight convergence + double prevCorr[MAX_AGENTS]; + int convergeCount; + bool isConverged; + double maxDeltaRho; + + // --- Neural Network --- + CNeuralNet *m_neuralNet; + NNTrainBuffer *m_trainBuffer; + bool m_useNeural; + bool m_trainMode; + string m_modelFilename; + int m_nnEpochs; + double m_nnLR; + int m_nnHidden; + double m_riskPerTrade; + double m_riskTotal; + string m_symbol; + string m_runTimestamp; + + // --- History cache --- + BarSnapshot history[MAX_HISTORY]; + int histIdx; + int histCount; + + // --- Export buffers --- + AgentSnapshot agentHistory[MAX_HISTORY][MAX_AGENTS]; + TradeRecord completedTrades[]; + int completedTradeCount; + DecisionRecord decisionLog[]; + int decisionCount; + int decisionCapacity; - // --- Export buffers --- - AgentSnapshot agentHistory[MAX_HISTORY][MAX_AGENTS]; - TradeRecord completedTrades[]; - int completedTradeCount; - DecisionRecord decisionLog[]; - int decisionCount; - int decisionCapacity; - void InitCorrelation(int idx) { corrMeanX[idx] = 0; corrMeanY[idx] = 0; - corrCov[idx] = 0; - corrVarX[idx] = 0; - corrVarY[idx] = 0; + corrCov[idx] = 0; + corrVarX[idx] = 0; + corrVarY[idx] = 0; corrCount[idx] = 0; } - + void UpdateCorrelation(int idx, double x, double y) { if(corrCount[idx] == 0) { corrMeanX[idx] = x; @@ -202,579 +202,613 @@ private: corrCount[idx] = 1; return; } - + double dx = x - corrMeanX[idx]; double dy = y - corrMeanY[idx]; - + corrMeanX[idx] += weightAlpha * dx; corrMeanY[idx] += weightAlpha * dy; - + double dxNew = x - corrMeanX[idx]; double dyNew = y - corrMeanY[idx]; - - corrCov[idx] = (1.0 - weightAlpha) * corrCov[idx] + weightAlpha * dx * dyNew; + + corrCov[idx] = (1.0 - weightAlpha) * corrCov[idx] + weightAlpha * dx * dyNew; corrVarX[idx] = (1.0 - weightAlpha) * corrVarX[idx] + weightAlpha * dx * dxNew; corrVarY[idx] = (1.0 - weightAlpha) * corrVarY[idx] + weightAlpha * dy * dyNew; corrCount[idx]++; } - + double GetCorrelation(int idx) const { double denom = MathSqrt(corrVarX[idx] * corrVarY[idx]); double eps = DATA_EPS(MathSqrt(corrVarX[idx] + corrVarY[idx])); if(denom < eps || corrCount[idx] < corrMinSamples) return 0; double r = corrCov[idx] / denom; - if(r > 1.0) return 1.0; + if(r > 1.0) return 1.0; if(r < -1.0) return -1.0; return r; } - + public: double combinedZ; - int OpenTradeCount() const { + int OpenTradeCount() const { int c = 0; - for(int i=0; i 0; } - int OpenTicket() const { - for(int i=0; i 0; } + int OpenTicket() const { + for(int i = 0; i < maxOpenTrades; i++) + if(openTrades[i].active) return openTrades[i].ticket; return -1; } - double EntryPrice() const { - for(int i=0; i= MAX_AGENTS) { + Print("Max agents reached"); + return; + } + agents[agentCount] = agent; + InitCorrelation(agentCount); + agentCount++; + // Ri-inizializza RunningStats con alpha/min derivati dal vero agentCount + returnStats = RunningStats(RStatsAlpha(), RStatsMinSamp()); + combinedZStats = RunningStats(RStatsAlpha(), RStatsMinSamp()); + maeStats = RunningStats(RStatsAlpha(), RStatsMinSamp()); + mfeStats = RunningStats(RStatsAlpha(), RStatsMinSamp()); + maeWinStats = RunningStats(RStatsAlpha(), RStatsMinSamp()); + // Min campioni per correlazione: sqrt(2*N) arrotondato, almeno 2 + corrMinSamples = MathMax(2, (int)MathRound(MathSqrt(MathMax(agentCount, 1) * 2.0))); + // Peso minimo: 1/(N*sqrt(N)) — cala con più agenti per diversificazione + weightMin = 1.0 / (MathMax(agentCount, 1) * MathSqrt(MathMax(agentCount, 1))); + } - void ReleaseAgents() { - for(int i=0; i= MAX_AGENTS) { - Print("Max agents reached"); - return; - } - agents[agentCount] = agent; - InitCorrelation(agentCount); - agentCount++; - // Ri-inizializza RunningStats con alpha/min derivati dal vero agentCount - returnStats = RunningStats(RStatsAlpha(), RStatsMinSamp()); - combinedZStats = RunningStats(RStatsAlpha(), RStatsMinSamp()); - maeStats = RunningStats(RStatsAlpha(), RStatsMinSamp()); - mfeStats = RunningStats(RStatsAlpha(), RStatsMinSamp()); - maeWinStats = RunningStats(RStatsAlpha(), RStatsMinSamp()); - // Min campioni per correlazione: sqrt(2*N) arrotondato, almeno 2 - corrMinSamples = MathMax(2, (int)MathRound(MathSqrt(MathMax(agentCount, 1) * 2.0))); - // Peso minimo: 1/(N*sqrt(N)) — cala con più agenti per diversificazione - weightMin = 1.0 / (MathMax(agentCount, 1) * MathSqrt(MathMax(agentCount, 1))); - } - void InitAgents(string symbol, ENUM_TIMEFRAMES tf) { - for(int i=0; i 0; + for(int i = 0; i < agentCount; i++) { + if(agents[i].enabled) agents[i].Interact(agents, agentCount); + } + // Dopo Interact, aggiorna zs[] con lastZScore (alcuni agenti come PatternHunter + // generano il segnale SOLO in Interact, non in Analyze) + for(int i = 0; i < agentCount; i++) { + if(agents[i].enabled) zs[i] = agents[i].lastZScore; + } - if(neuralReady) { - // Estrai feature vector: [z_Hurst, z_ADX, z_MA, z_Mom, z_Consensus, z_Pattern, agreement, trendStrength] - vector nnInput(NN_FEATURES); - for(int f = 0; f < NN_FEATURES; f++) nnInput[f] = 0.0; - for(int i=0; i 0; - combinedZ = m_neuralNet.GetCombinedZ(nnInput); - combinedZ = MathTanh(combinedZ); - } else { - // Softmax gating coerente: il softmax pesa le competenze ws[] e la - // temperatura deriva dalla dispersione delle STESSE competenze (non - // dalla varianza dei segnali zs[]), così i due termini sono consistenti. - // Competenze simili → temp bassa; competenze molto diverse → temp alta - // (mixing più uniforme, evita di sovra-fidarsi di un singolo agente). - double wMean = 0; - int wCount = 0; - for(int i=0; i 0) wMean /= wCount; - double wVar = 0; - for(int i=0; i 0) wVar /= wCount; - double temp = (wCount > 0) ? (MathSqrt(wVar) + DATA_EPS(wVar)) / MathSqrt(wCount) : 1.0 / MathSqrt(MathMax(1, agentCount)); - double safeTemp = MathMax(temp, DATA_EPS(temp) * 10.0); // evita overflow di MathExp + if(neuralReady) { + // Estrai feature vector: [z_Hurst, z_ADX, z_MA, z_Mom, z_Consensus, z_Pattern, agreement, trendStrength] + vector nnInput(NN_FEATURES); + for(int f = 0; f < NN_FEATURES; f++) nnInput[f] = 0.0; + for(int i = 0; i < agentCount; i++) { + if(agents[i].name == "Hurst") + nnInput[0] = agents[i].lastZScore; + else if(agents[i].name == "ADX") + nnInput[1] = agents[i].lastZScore; + else if(agents[i].name == "MA") + nnInput[2] = agents[i].lastZScore; + else if(agents[i].name == "Momentum") + nnInput[3] = agents[i].lastZScore; + else if(agents[i].name == "Consensus") + nnInput[4] = agents[i].lastZScore; + else if(agents[i].name == "Hunter") + nnInput[5] = agents[i].lastZScore; + } + nnInput[6] = SHARED_regimeAgreement; + nnInput[7] = SHARED_trendStrength; - double sumExp = 0; - for(int i=0; i 0) wMean /= wCount; + double wVar = 0; + for(int i = 0; i < agentCount; i++) { + if(!agents[i].enabled) continue; + wVar += (ws[i] - wMean) * (ws[i] - wMean); + } + if(wCount > 0) wVar /= wCount; + double temp = (wCount > 0) ? (MathSqrt(wVar) + DATA_EPS(wVar)) / MathSqrt(wCount) : 1.0 / MathSqrt(MathMax(1, agentCount)); + double safeTemp = MathMax(temp, DATA_EPS(temp) * 10.0); // evita overflow di MathExp + + double sumExp = 0; + for(int i = 0; i < agentCount; i++) { + if(!agents[i].enabled) continue; + sumExp += MathExp(ws[i] / safeTemp); + } + + combinedZ = 0; + double epsExp = DATA_EPS((double)wCount); + for(int i = 0; i < agentCount; i++) { + if(!agents[i].enabled) continue; + double softmaxW = (sumExp > epsExp) ? MathExp(ws[i] / safeTemp) / sumExp : 1.0 / MathMax(1, wCount); + // Segnale sign-corretto: agenti anti-correlati contribuiscono invertiti. + combinedZ += softmaxW * corrSign[i] * zs[i]; + } + combinedZ = MathTanh(combinedZ); + } - combinedZ = 0; - double epsExp = DATA_EPS((double)wCount); - for(int i=0; i epsExp) ? MathExp(ws[i] / safeTemp) / sumExp : 1.0 / MathMax(1, wCount); - // Segnale sign-corretto: agenti anti-correlati contribuiscono invertiti. - combinedZ += softmaxW * corrSign[i] * zs[i]; - } - combinedZ = MathTanh(combinedZ); - } - // Traccia la distribuzione del combinedZ combinedZStats.Update(combinedZ); - - // Adatta weightAlpha alla volatilità dei ritorni - // Alta volatilità → alpha veloce (ci adattiamo in fretta) - // Bassa volatilità → alpha lento (pesiamo di più la storia) - if(combinedZStats.Ready() && returnStats.Count() > 0) { - double rv = returnStats.Std(); // volatilità ritorni in ATR - weightAlpha = rv / (1.0 + rv); - // Min: 10% del peso per agente così non congela mai del tutto - double minAlpha = 1.0 / (double)MathMax(1, agentCount) * 0.1; - if(weightAlpha < minAlpha) weightAlpha = minAlpha; - // Cap weightAlpha: massimo 1/sqrt(N) (non più veloce del rumore per agente) - double maxAlpha = 1.0 / MathSqrt(MathMax(1, agentCount)); - if(weightAlpha > maxAlpha) weightAlpha = maxAlpha; - } - + + // Adatta weightAlpha alla volatilità dei ritorni + // Alta volatilità → alpha veloce (ci adattiamo in fretta) + // Bassa volatilità → alpha lento (pesiamo di più la storia) + if(combinedZStats.Ready() && returnStats.Count() > 0) { + double rv = returnStats.Std(); // volatilità ritorni in ATR + weightAlpha = rv / (1.0 + rv); + // Min: 10% del peso per agente così non congela mai del tutto + double minAlpha = 1.0 / (double)MathMax(1, agentCount) * 0.1; + if(weightAlpha < minAlpha) weightAlpha = minAlpha; + // Cap weightAlpha: massimo 1/sqrt(N) (non più veloce del rumore per agente) + double maxAlpha = 1.0 / MathSqrt(MathMax(1, agentCount)); + if(weightAlpha > maxAlpha) weightAlpha = maxAlpha; + } + LogBarHistory(data.count > 0 ? data.time[0] : TimeCurrent()); - + return combinedZ; } - - // Cresce dinamicamente l'array openTrades se necessario - void EnsureTradeCapacity() { - int used = 0; - for(int i = 0; i < maxOpenTrades; i++) - if(openTrades[i].active) used++; - if(used >= maxOpenTrades) { - int oldSize = maxOpenTrades; - maxOpenTrades = MathMax(oldSize * 2, 100); - ArrayResize(openTrades, maxOpenTrades); - for(int i = oldSize; i < maxOpenTrades; i++) - openTrades[i].active = false; - Print("Trade capacity expanded: ", oldSize, " → ", maxOpenTrades); + + // Cresce dinamicamente l'array openTrades se necessario + void EnsureTradeCapacity() { + int used = 0; + for(int i = 0; i < maxOpenTrades; i++) + if(openTrades[i].active) used++; + if(used >= maxOpenTrades) { + int oldSize = maxOpenTrades; + maxOpenTrades = MathMax(oldSize * 2, 100); + ArrayResize(openTrades, maxOpenTrades); + for(int i = oldSize; i < maxOpenTrades; i++) + openTrades[i].active = false; + Print("Trade capacity expanded: ", oldSize, " → ", maxOpenTrades); + } + } + + 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; } } - - 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 0 && tickValue > 0 && slPoints > 0) - openTrades[idx].riskAmount = lot * (tickValue / tickSize) * slPoints; - else - openTrades[idx].riskAmount = 0; - - 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; + 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; - 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); + 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; } + openTrades[idx].entryFeatures[6] = SHARED_regimeAgreement; + openTrades[idx].entryFeatures[7] = SHARED_trendStrength; - void LogDecision(string action, int dir, double minZ, double price, int ticket = 0) { - if(decisionCount >= decisionCapacity) { - decisionCapacity *= 2; - ArrayResize(decisionLog, decisionCapacity); - } - DecisionRecord d; - d.time = TimeCurrent(); - d.action = action; - d.direction = dir; - d.zScore = MathAbs(combinedZ); - d.combinedZ = combinedZ; - d.price = price; - d.ticket = ticket; - d.agreeingCount = 0; - d.totalAgents = agentCount; - d.confidence = MathAbs(combinedZ); - d.minZ = minZ; - if(action == "SIGNAL" || action == "ENTRY") { - for(int i=0; i 0.1) { - if((agents[i].lastZScore > 0) == (dir > 0)) d.agreeingCount++; - } + // SL iniziale adattivo + double slWidth = AdaptiveSLWidth(); + openTrades[idx].slPrice = isBuy ? price - atr * slWidth : price + atr * slWidth; + + // 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; + + 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; + } + + 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); + } + + void LogDecision(string action, int dir, double minZ, double price, int ticket = 0) { + if(decisionCount >= decisionCapacity) { + decisionCapacity *= 2; + ArrayResize(decisionLog, decisionCapacity); + } + DecisionRecord d; + d.time = TimeCurrent(); + d.action = action; + d.direction = dir; + d.zScore = MathAbs(combinedZ); + d.combinedZ = combinedZ; + d.price = price; + d.ticket = ticket; + d.agreeingCount = 0; + d.totalAgents = agentCount; + d.confidence = MathAbs(combinedZ); + d.minZ = minZ; + if(action == "SIGNAL" || action == "ENTRY") { + for(int i = 0; i < agentCount; i++) { + if(agents[i].enabled && MathAbs(agents[i].lastZScore) > 0.1) { + if((agents[i].lastZScore > 0) == (dir > 0)) d.agreeingCount++; } } - decisionLog[decisionCount] = d; - decisionCount++; } - -void OnTradeClose(int ticket, double closePrice, string exitReason = "MANUAL") { - // Trova il trade nell'array - int idx = -1; - for(int i=0; i 0) maeWinStats.Update(openTrades[idx].maeATR); + // ROI in ATR (positivo = profitto nella direzione del trade) + double actualReturn = openTrades[idx].isBuy + ? (closePrice - openTrades[idx].entryPrice) / openTrades[idx].entryATR + : (openTrades[idx].entryPrice - closePrice) / openTrades[idx].entryATR; - tradeReturns[tradeCount % ROLLING_TRADES] = actualReturn; - tradeCount++; - if(actualReturn > 0) winCount++; + returnStats.Update(actualReturn); + maeStats.Update(openTrades[idx].maeATR); + mfeStats.Update(openTrades[idx].mfeATR); + if(actualReturn > 0) maeWinStats.Update(openTrades[idx].maeATR); - double eq = AccountInfoDouble(ACCOUNT_EQUITY); - if(eq > peakEquity) peakEquity = eq; - double dd = (peakEquity > 0) ? (peakEquity - eq) / peakEquity : 0; - if(dd > maxDrawdown) maxDrawdown = dd; - double actualReturnZ = returnStats.RawZScore(actualReturn); + tradeReturns[tradeCount % ROLLING_TRADES] = actualReturn; + tradeCount++; + if(actualReturn > 0) winCount++; - Print("=== Learning: Trade #", ticket, " ", openTrades[idx].isBuy ? "BUY" : "SELL", " chiuso ==="); - Print(" Return: ", StringFormat("%+.2f ATR", actualReturn), - " | MAE: ", StringFormat("%.2f", openTrades[idx].maeATR), - " | MFE: ", StringFormat("%.2f", openTrades[idx].mfeATR), - " | bars: ", openTrades[idx].barsHeld); - Print(" Entry z=", StringFormat("%+.3f", openTrades[idx].entryZ), - " | SL width: ", StringFormat("%.2f ATR", AdaptiveSLWidth())); + double eq = AccountInfoDouble(ACCOUNT_EQUITY); + if(eq > peakEquity) peakEquity = eq; + double dd = (peakEquity > 0) ? (peakEquity - eq) / peakEquity : 0; + if(dd > maxDrawdown) maxDrawdown = dd; + double actualReturnZ = returnStats.RawZScore(actualReturn); + + Print("=== Learning: Trade #", ticket, " ", openTrades[idx].isBuy ? "BUY" : "SELL", " chiuso ==="); + Print(" Return: ", StringFormat("%+.2f ATR", actualReturn), + " | MAE: ", StringFormat("%.2f", openTrades[idx].maeATR), + " | MFE: ", StringFormat("%.2f", openTrades[idx].mfeATR), + " | bars: ", openTrades[idx].barsHeld); + Print(" Entry z=", StringFormat("%+.3f", openTrades[idx].entryZ), + " | SL width: ", StringFormat("%.2f ATR", AdaptiveSLWidth())); double sigThr = combinedZStats.Ready() - ? combinedZStats.Std() / MathSqrt(MathMax(1, agentCount)) - : 1.0 / MathSqrt(MathMax(1, agentCount)); -for(int i=0; i 0) { - if(tradeDir == 1) target[0] = 1; - else target[2] = 1; - } else { - if(tradeDir == 1) target[2] = 1; - else target[0] = 1; - } + if(actualReturn > 0) { + if(tradeDir == 1) + target[0] = 1; + else + target[2] = 1; + } else { + if(tradeDir == 1) + target[2] = 1; + else + target[0] = 1; + } - double sampleWeight = MathAbs(actualReturn) + 1.0; // trade profittevoli/perdenti pesano di più - m_trainBuffer.Add(features, target, sampleWeight); + double sampleWeight = MathAbs(actualReturn) + 1.0; // trade profittevoli/perdenti pesano di più + m_trainBuffer.Add(features, target, sampleWeight); - if(m_trainBuffer.Count() < 20) - Print(" NN sample #", m_trainBuffer.Count(), " collected (w=", StringFormat("%.2f", sampleWeight), ")"); + if(m_trainBuffer.Count() < 20) + Print(" NN sample #", m_trainBuffer.Count(), " collected (w=", StringFormat("%.2f", sampleWeight), ")"); - if(m_trainBuffer.Count() % 50 == 0 && m_trainBuffer.Count() >= 20) { - if(m_useNeural) { - TrainNN(); - string nnFn = (m_modelFilename != "") ? m_modelFilename : "TR_Agent_NN_v1_" + m_runTimestamp + ".dat"; - m_neuralNet.Save(nnFn); - Print(" NN auto-saved to ", nnFn, " after ", m_trainBuffer.Count(), " samples"); - } - } - } + if(m_trainBuffer.Count() % 50 == 0 && m_trainBuffer.Count() >= 20) { + if(m_useNeural) { + TrainNN(); + string nnFn = (m_modelFilename != "") ? m_modelFilename : "TR_Agent_NN_v1_" + m_runTimestamp + ".dat"; + m_neuralNet.Save(nnFn); + Print(" NN auto-saved to ", nnFn, " after ", m_trainBuffer.Count(), " samples"); + } + } + } -UpdateHealth(actualReturn); - LogHealth(); + UpdateHealth(actualReturn); + LogHealth(); - // Save completed trade record - int ct = completedTradeCount; - ArrayResize(completedTrades, ct + 1); - completedTrades[ct].ticket = openTrades[idx].ticket; - completedTrades[ct].isBuy = openTrades[idx].isBuy; - completedTrades[ct].entryPrice = openTrades[idx].entryPrice; - completedTrades[ct].closePrice = closePrice; - completedTrades[ct].entryATR = openTrades[idx].entryATR; - completedTrades[ct].entryZ = openTrades[idx].entryZ; - completedTrades[ct].slPrice = openTrades[idx].slPrice; - completedTrades[ct].highestPrice = openTrades[idx].highestPrice; - completedTrades[ct].lowestPrice = openTrades[idx].lowestPrice; - completedTrades[ct].barsHeld = openTrades[idx].barsHeld; - completedTrades[ct].maeATR = openTrades[idx].maeATR; - completedTrades[ct].mfeATR = openTrades[idx].mfeATR; - completedTrades[ct].actualReturn = actualReturn; - completedTrades[ct].entryTime = openTrades[idx].entryTime; - completedTrades[ct].closeTime = TimeCurrent(); - completedTrades[ct].exitReason = exitReason; - for(int ei = 0; ei < agentCount; ei++) completedTrades[ct].entryZScores[ei] = openTrades[idx].entryZScores[ei]; - for(int ef = 0; ef < NN_FEATURES; ef++) completedTrades[ct].entryFeatures[ef] = openTrades[idx].entryFeatures[ef]; + // Save completed trade record + int ct = completedTradeCount; + ArrayResize(completedTrades, ct + 1); + completedTrades[ct].ticket = openTrades[idx].ticket; + completedTrades[ct].isBuy = openTrades[idx].isBuy; + completedTrades[ct].entryPrice = openTrades[idx].entryPrice; + completedTrades[ct].closePrice = closePrice; + completedTrades[ct].entryATR = openTrades[idx].entryATR; + completedTrades[ct].entryZ = openTrades[idx].entryZ; + completedTrades[ct].slPrice = openTrades[idx].slPrice; + completedTrades[ct].highestPrice = openTrades[idx].highestPrice; + completedTrades[ct].lowestPrice = openTrades[idx].lowestPrice; + completedTrades[ct].barsHeld = openTrades[idx].barsHeld; + completedTrades[ct].maeATR = openTrades[idx].maeATR; + completedTrades[ct].mfeATR = openTrades[idx].mfeATR; + completedTrades[ct].actualReturn = actualReturn; + completedTrades[ct].entryTime = openTrades[idx].entryTime; + completedTrades[ct].closeTime = TimeCurrent(); + completedTrades[ct].exitReason = exitReason; + for(int ei = 0; ei < agentCount; ei++) completedTrades[ct].entryZScores[ei] = openTrades[idx].entryZScores[ei]; + for(int ef = 0; ef < NN_FEATURES; ef++) completedTrades[ct].entryFeatures[ef] = openTrades[idx].entryFeatures[ef]; - LogDecision("EXIT", openTrades[idx].isBuy ? 1 : -1, 0, closePrice, ticket); + LogDecision("EXIT", openTrades[idx].isBuy ? 1 : -1, 0, closePrice, ticket); - openTrades[idx].active = false; - } + openTrades[idx].active = false; + } // Trova un trade per ticket int FindTrade(int ticket) const { - for(int i=0; i openTrades[i].highestPrice) openTrades[i].highestPrice = high; - if(low < openTrades[i].lowestPrice) openTrades[i].lowestPrice = low; - openTrades[i].barsHeld++; - } - } + // Aggiorna MAE/MFE per tutti i trade aperti (chiamato ogni barra) + void UpdateOpenTrades(double high, double low) { + for(int i = 0; i < maxOpenTrades; i++) { + if(!openTrades[i].active) continue; + if(high > openTrades[i].highestPrice) openTrades[i].highestPrice = high; + if(low < openTrades[i].lowestPrice) openTrades[i].lowestPrice = low; + openTrades[i].barsHeld++; + } + } - // Trailing stop: sposta SL dopo che il profitto supera la soglia - void TrailStops() { - for(int i=0; i trigger) { - double offset = AdaptiveTrailOffset(); - double newSL; - if(openTrades[i].isBuy) { - newSL = openTrades[i].highestPrice - offset * openTrades[i].entryATR; - if(newSL > openTrades[i].slPrice) openTrades[i].slPrice = newSL; - } else { - newSL = openTrades[i].lowestPrice + offset * openTrades[i].entryATR; - if(newSL < openTrades[i].slPrice) openTrades[i].slPrice = newSL; - } - } - } - } + double trigger = AdaptiveTrailTrigger(); + if(profitATR > trigger) { + double offset = AdaptiveTrailOffset(); + double newSL; + if(openTrades[i].isBuy) { + newSL = openTrades[i].highestPrice - offset * openTrades[i].entryATR; + if(newSL > openTrades[i].slPrice) openTrades[i].slPrice = newSL; + } else { + newSL = openTrades[i].lowestPrice + offset * openTrades[i].entryATR; + if(newSL < openTrades[i].slPrice) openTrades[i].slPrice = newSL; + } + } + } + } // Trade da chiudere per inversione di segnale (ritorna array di ticket) void GetTradesToClose(int &closeTickets[], double currentZ, double minZ) { ArrayResize(closeTickets, 0); - for(int i=0; i 0) ? overrideMinZ : AdaptiveMinZ(); int dir = 0; - if(combinedZ > thr) dir = 1; + if(combinedZ > thr) dir = 1; if(combinedZ < -thr) dir = -1; - + FinalSignal fs(dir, combinedZ); fs.totalAgents = agentCount; - + fs.agreeingCount = 0; fs.contributingAgents = ""; - double sigThr = combinedZStats.Ready() - ? combinedZStats.Std() / MathSqrt(MathMax(1, agentCount)) - : 1.0 / MathSqrt(MathMax(1, agentCount)); - for(int i=0; i sigThr) { int agentDir = (agents[i].lastZScore > 0) ? 1 : -1; @@ -870,29 +904,29 @@ UpdateHealth(actualReturn); } } } - + return fs; } - - void PrintAgentLearningSummary() const { - Print("=== Agent Learning Summary ==="); - for(int i=0; i= corrMinSamples) { double sum = 0, sumSq = 0; - for(int i=0; i epsVarRoll) ? mean / MathSqrt(var) : 0; + double mean = sum / rollingCount; + double var = sumSq / rollingCount - mean * mean; + double epsVarRoll = DATA_EPS(mean); + rollingSharpe = (var > epsVarRoll) ? mean / MathSqrt(var) : 0; } - + // Weight convergence: max delta di ρ tra tutti gli agenti // Soglia: 1/corrMinSamples = drift atteso di una correlazione con N campioni double thr = 1.0 / (double)MathMax(1, corrMinSamples); int minTrades = MathMax(corrMinSamples, agentCount); - + bool allHaveSamples = true; maxDeltaRho = 0; - for(int i=0; i maxDeltaRho) maxDeltaRho = delta; prevCorr[i] = rho; } - + if(allHaveSamples && maxDeltaRho < thr) { convergeCount++; if(convergeCount >= minTrades) isConverged = true; @@ -984,285 +1021,286 @@ UpdateHealth(actualReturn); convergeCount = 0; } } - + double RollingSharpe() const { return rollingSharpe; } - bool IsConverged() const { return isConverged; } - double MaxDeltaRho() const { return maxDeltaRho; } - int RollingTrades() const { return rollingCount; } - + bool IsConverged() const { return isConverged; } + double MaxDeltaRho() const { return maxDeltaRho; } + int RollingTrades() const { return rollingCount; } + void LogHealth() const { string status = "CONVERGED"; if(!isConverged) status = "learning"; - + Print("=== Health ==="); Print(" Rolling Sharpe (", rollingCount, " trade): ", StringFormat("%+.2f", rollingSharpe)); Print(" Win rate: ", (tradeCount > 0) ? StringFormat("%.1f%%", 100.0 * winCount / tradeCount) : "N/A"); Print(" Max DD: ", StringFormat("%.2f%%", maxDrawdown * 100.0)); Print(" Max Δρ: ", StringFormat("%.4f", maxDeltaRho), " | status: ", status); - if(rollingCount >= corrMinSamples) { - if(rollingSharpe < 0) - Print(" ⚠ WARNING: Sharpe negativo — possibile cambio regime!"); - else { - double sharpeZ = rollingSharpe * MathSqrt(rollingCount); - // z_0.95 ≈ invNormalCDF(0.95) tramite approssimazione di Abramowitz & Stegun 26.2.23 - double p05 = 0.95; - double t = MathSqrt(-2.0 * MathLog(1.0 - p05)); - double z095 = t - (2.515517 + 0.802853*t + 0.010328*t*t) - / (1.0 + 1.432788*t + 0.189269*t*t + 0.001308*t*t*t); - if(sharpeZ > z095) - Print(" ✓ Sharpe significativamente positivo (z=", StringFormat("%.2f", sharpeZ), ")"); - } - } + if(rollingCount >= corrMinSamples) { + if(rollingSharpe < 0) + Print(" ⚠ WARNING: Sharpe negativo — possibile cambio regime!"); + else { + double sharpeZ = rollingSharpe * MathSqrt(rollingCount); + // z_0.95 ≈ invNormalCDF(0.95) tramite approssimazione di Abramowitz & Stegun 26.2.23 + double p05 = 0.95; + double t = MathSqrt(-2.0 * MathLog(1.0 - p05)); + double z095 = t - (2.515517 + 0.802853 * t + 0.010328 * t * t) / (1.0 + 1.432788 * t + 0.189269 * t * t + 0.001308 * t * t * t); + if(sharpeZ > z095) + Print(" ✓ Sharpe significativamente positivo (z=", StringFormat("%.2f", sharpeZ), ")"); + } + } } - + void LogBarHistory(datetime time) { history[histIdx].time = time; history[histIdx].combinedZ = combinedZ; - for(int i=0; i 0) ? (double)winCount / tradeCount : 1.0 / agentCount; - double rr = (wr < 1.0) ? 1.0 / (1.0 - wr) : MathSqrt(MathMax(1, agentCount)); - return sl * rr; - } - - // Somma del rischio (in valuta conto) di tutte le posizioni aperte - double TotalRiskUsed() const { - double total = 0; - for(int i=0; i 0) ? (double)winCount / tradeCount : 1.0 / agentCount; + double rr = (wr < 1.0) ? 1.0 / (1.0 - wr) : MathSqrt(MathMax(1, agentCount)); + return sl * rr; + } + + // 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 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, skip"); + return 0; } - // 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 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); + double lot = riskAmount / riskPerLot; + lot = MathRound(lot / lotStep) * lotStep; + lot = MathMax(lotMin, MathMin(lotMax, lot)); - // Rischio desiderato per questo trade = bal × riskPerTrade × |z| (confidenza) - double riskAmount = bal * m_riskPerTrade * MathAbs(zScore); + 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; + } - // 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, skip"); - return 0; - } - - 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; - if(n < corrMinSamples) return 0; - int lag = 1; - double sum=0, sumSq=0, sumShift=0, sumSqShift=0, sumCov=0; - // Usa fino a n/2 per bilanciare stabilità e reattività, minimo corrMinSamples - int cnt = MathMin(n, MathMax(corrMinSamples, n / 2)); - for(int i=0; i epsDenom) ? cov/denom : 0; + return (denom > epsDenom) ? cov / denom : 0; } - - double TradeSharpe() const { - double m = returnStats.Mean(); - double s = returnStats.Std(); - double epsS = DATA_EPS(m); - return (s > epsS) ? m / s : 0; + + double TradeSharpe() const { + double m = returnStats.Mean(); + double s = returnStats.Std(); + double epsS = DATA_EPS(m); + return (s > epsS) ? m / s : 0; } - int TradeCount() const { return returnStats.Count(); } - int WinCount() const { return winCount; } - - // --- Neural Network Methods --- - void InitNeuralNet(int inputs, int hidden, int outputs, bool setUseNeural = false, double dropoutRate = 0.2) { - if(m_neuralNet == NULL) { - m_neuralNet = new CNeuralNet(); - m_trainBuffer = new NNTrainBuffer(); - } - m_neuralNet.Init(inputs, hidden, outputs, 1e-4, dropoutRate); - if(setUseNeural) m_useNeural = true; + int TradeCount() const { return returnStats.Count(); } + int WinCount() const { return winCount; } + + // --- Neural Network Methods --- + void InitNeuralNet(int inputs, int hidden, int outputs, bool setUseNeural = false, double dropoutRate = 0.2) { + if(m_neuralNet == NULL) { + m_neuralNet = new CNeuralNet(); + m_trainBuffer = new NNTrainBuffer(); + } + m_neuralNet.Init(inputs, hidden, outputs, 1e-4, dropoutRate); + if(setUseNeural) m_useNeural = true; + } + + double TrainNN() { + if(m_neuralNet == NULL || !m_neuralNet.IsInitialized()) { + Print("TrainNN: rete non inizializzata. Inizializzo..."); + InitNeuralNet(NN_FEATURES, m_nnHidden, NN_TARGETS, true); + } + if(m_trainBuffer == NULL || m_trainBuffer.Count() < 3) { + Print("TrainNN: campioni insufficienti (", + (m_trainBuffer ? m_trainBuffer.Count() : 0), ")"); + return -1; } - double TrainNN() { - if(m_neuralNet == NULL || !m_neuralNet.IsInitialized()) { - Print("TrainNN: rete non inizializzata. Inizializzo..."); - InitNeuralNet(NN_FEATURES, m_nnHidden, NN_TARGETS, true); - } - if(m_trainBuffer == NULL || m_trainBuffer.Count() < 3) { - Print("TrainNN: campioni insufficienti (", - (m_trainBuffer ? m_trainBuffer.Count() : 0), ")"); - return -1; - } + matrix features, targets; + m_trainBuffer.ToMatrices(features, targets); + vector weights = m_trainBuffer.GetWeights(); - matrix features, targets; - m_trainBuffer.ToMatrices(features, targets); - vector weights = m_trainBuffer.GetWeights(); + Print("=== Neural Network Training ==="); + Print(" Samples: ", m_trainBuffer.Count(), " | Arch: ", NN_FEATURES, "→", m_nnHidden, "→", NN_TARGETS); + Print(" Epochs: ", m_nnEpochs, " | LR: ", m_nnLR); + m_trainBuffer.PrintStats(); + m_trainBuffer.PrintFeatureStats(); - Print("=== Neural Network Training ==="); - Print(" Samples: ", m_trainBuffer.Count(), " | Arch: ", NN_FEATURES, "→", m_nnHidden, "→", NN_TARGETS); - Print(" Epochs: ", m_nnEpochs, " | LR: ", m_nnLR); - m_trainBuffer.PrintStats(); - m_trainBuffer.PrintFeatureStats(); + double loss = m_neuralNet.Train(features, targets, m_nnEpochs, m_nnLR, weights); - double loss = m_neuralNet.Train(features, targets, m_nnEpochs, m_nnLR, weights); + Print(" Final loss: ", StringFormat("%.6f", loss)); + Print(" Total epochs trained: ", m_neuralNet.EpochsTrained()); + Print(" ", m_neuralNet.Info()); + Print(" ", m_neuralNet.WeightsSummary()); + Print(" ", m_neuralNet.LossHistorySummary()); - Print(" Final loss: ", StringFormat("%.6f", loss)); - Print(" Total epochs trained: ", m_neuralNet.EpochsTrained()); - Print(" ", m_neuralNet.Info()); - Print(" ", m_neuralNet.WeightsSummary()); - Print(" ", m_neuralNet.LossHistorySummary()); + // CSV dump per debug + m_trainBuffer.SaveCsv("NN_TrainingSamples_" + Symbol() + "_" + EnumToString(Period()) + "_" + m_runTimestamp + ".csv"); + m_neuralNet.SaveLossCsv("NN_LossHistory_" + Symbol() + "_" + EnumToString(Period()) + "_" + m_runTimestamp + ".csv"); - // CSV dump per debug - m_trainBuffer.SaveCsv("NN_TrainingSamples_" + Symbol() + "_" + EnumToString(Period()) + "_" + m_runTimestamp + ".csv"); - m_neuralNet.SaveLossCsv("NN_LossHistory_" + Symbol() + "_" + EnumToString(Period()) + "_" + m_runTimestamp + ".csv"); + return loss; + } - return loss; - } + bool SaveNNModel() { + if(m_neuralNet == NULL || !m_neuralNet.IsInitialized()) return false; + string fn = (m_modelFilename != "") ? m_modelFilename + : "TR_Agent_NN_" + Symbol() + "_" + EnumToString(Period()) + "_" + m_runTimestamp + ".dat"; + bool ok = m_neuralNet.Save(fn); + if(ok) + Print("NN model saved: ", fn); + else + Print("NN model save FAILED: ", fn); + return ok; + } - bool SaveNNModel() { - if(m_neuralNet == NULL || !m_neuralNet.IsInitialized()) return false; - string fn = (m_modelFilename != "") ? m_modelFilename - : "TR_Agent_NN_" + Symbol() + "_" + EnumToString(Period()) + "_" + m_runTimestamp + ".dat"; - bool ok = m_neuralNet.Save(fn); - if(ok) Print("NN model saved: ", fn); - else Print("NN model save FAILED: ", fn); - return ok; - } + bool LoadNNModel() { + string fn = (m_modelFilename != "") ? m_modelFilename + : "TR_Agent_NN_" + Symbol() + "_" + EnumToString(Period()) + ".dat"; + if(m_neuralNet == NULL) m_neuralNet = new CNeuralNet(); - bool LoadNNModel() { - string fn = (m_modelFilename != "") ? m_modelFilename - : "TR_Agent_NN_" + Symbol() + "_" + EnumToString(Period()) + ".dat"; - if(m_neuralNet == NULL) m_neuralNet = new CNeuralNet(); + bool ok = m_neuralNet.Load(fn); + if(ok) { + Print("NN model loaded: ", fn, " — ", m_neuralNet.Info()); + m_useNeural = true; + } else { + Print("NN model not found: ", fn, " — using softmax fallback"); + } + return ok; + } - bool ok = m_neuralNet.Load(fn); - if(ok) { - Print("NN model loaded: ", fn, " — ", m_neuralNet.Info()); - m_useNeural = true; - } else { - Print("NN model not found: ", fn, " — using softmax fallback"); - } - return ok; - } + bool IsNeuralReady() const { + return m_useNeural && m_neuralNet != NULL && m_neuralNet.IsInitialized() && m_neuralNet.EpochsTrained() > 0; + } - bool IsNeuralReady() const { - return m_useNeural && m_neuralNet != NULL - && m_neuralNet.IsInitialized() - && m_neuralNet.EpochsTrained() > 0; - } + string NeuralInfo() const { + if(m_neuralNet == NULL) return "NN: not initialized"; + return m_neuralNet.Info(); + } - string NeuralInfo() const { - if(m_neuralNet == NULL) return "NN: not initialized"; - return m_neuralNet.Info(); - } + // Salva summary CSV con learning stats di ogni agente + void SaveAgentLearningCsv() const { + string fn = "AgentLearning_" + Symbol() + "_" + EnumToString(Period()) + "_" + m_runTimestamp + ".csv"; + int fh = FileOpen(fn, FILE_TXT | FILE_WRITE | FILE_COMMON); + if(fh == INVALID_HANDLE) return; + FileWriteString(fh, "agent,bias,bias_n,rho_learn,rho_learn_n,weight\n"); + for(int i = 0; i < agentCount; i++) { + double bias = agents[i].predictionError.Mean(); + int biasN = agents[i].predictionError.Count(); + double rho = agents[i].predCorr.Ready() ? agents[i].predCorr.Correlation() : 0; + int rhoN = agents[i].predCorr.Count(); + FileWriteString(fh, agents[i].name + "," + StringFormat("%+.6f", bias) + "," + (string)biasN + "," + StringFormat("%+.6f", rho) + "," + (string)rhoN + "," + StringFormat("%.6f", agents[i].weight) + "\r\n"); + } + FileClose(fh); + Print("Agent learning CSV saved: ", fn); + } - // Salva summary CSV con learning stats di ogni agente - void SaveAgentLearningCsv() const { - string fn = "AgentLearning_" + Symbol() + "_" + EnumToString(Period()) + "_" + m_runTimestamp + ".csv"; - int fh = FileOpen(fn, FILE_TXT|FILE_WRITE|FILE_COMMON); - if(fh == INVALID_HANDLE) return; - FileWriteString(fh, "agent,bias,bias_n,rho_learn,rho_learn_n,weight\n"); - for(int i=0; i= MAX_HISTORY) ? histIdx : 0; // dal più vecchio - for(int i=0; i= MAX_HISTORY) ? histIdx : 0; // dal più vecchio + for(int i = 0; i < nHist; i++) { int ii = (start + i) % MAX_HISTORY; FileWriteInteger(fh, (int)history[ii].time); FileWriteDouble(fh, history[ii].combinedZ); - for(int j=0; j= 6) - // I pesi NN sono salvati direttamente nello state file, non in un file separato. - // Questo garantisce portabilità tester → live: basta copiare UN file. - bool hasNN = m_neuralNet != NULL && m_neuralNet.IsInitialized() && m_neuralNet.EpochsTrained() > 0; - FileWriteInteger(fh, hasNN ? 1 : 0); - if(hasNN) { - m_neuralNet.Save(fh); - FileWriteInteger(fh, m_useNeural ? 1 : 0); - } - FileClose(fh); - string nnInfo = hasNN ? " NN inline" : ""; - Print("Modello salvato: ", fn, " (", histCount, " barre in cache", nnInfo, ")"); - if(MQLInfoInteger(MQL_TESTER)) { - Print(" [TESTER] Per usare in live, copia questo file in:"); - Print(" ", TerminalInfoString(TERMINAL_COMMONDATA_PATH)); - } - } - + // Neural network inline (versione >= 6) + // I pesi NN sono salvati direttamente nello state file, non in un file separato. + // Questo garantisce portabilità tester → live: basta copiare UN file. + bool hasNN = m_neuralNet != NULL && m_neuralNet.IsInitialized() && m_neuralNet.EpochsTrained() > 0; + FileWriteInteger(fh, hasNN ? 1 : 0); + if(hasNN) { + m_neuralNet.Save(fh); + FileWriteInteger(fh, m_useNeural ? 1 : 0); + } + + FileClose(fh); + string nnInfo = hasNN ? " NN inline" : ""; + Print("Modello salvato: ", fn, " (", histCount, " barre in cache", nnInfo, ")"); + if(MQLInfoInteger(MQL_TESTER)) { + Print(" [TESTER] Per usare in live, copia questo file in:"); + Print(" ", TerminalInfoString(TERMINAL_COMMONDATA_PATH)); + } + } + void LoadState(string symbol, ENUM_TIMEFRAMES tf) { string fn = ModelFilename(symbol, tf); if(!FileIsExist(fn, FILE_COMMON)) { Print("Nessun modello trovato per ", symbol, " ", EnumToString(tf)); return; } - - int fh = FileOpen(fn, FILE_READ|FILE_BIN|FILE_COMMON); + + int fh = FileOpen(fn, FILE_READ | FILE_BIN | FILE_COMMON); if(fh == INVALID_HANDLE) { Print("Load: impossibile aprire ", fn); return; } - + int version = FileReadInteger(fh); int savedCount = FileReadInteger(fh); int n = MathMin(savedCount, agentCount); - - for(int i=0; i= 2) { - int savedHist = FileReadInteger(fh); - int savedIdx = FileReadInteger(fh); - int nHist = MathMin(savedHist, MAX_HISTORY); - for(int i=0; i= MAX_HISTORY) ? (savedIdx + i) % MAX_HISTORY : i; - if(ii < 0 || ii >= MAX_HISTORY) { ii = 0; } - history[ii].time = (datetime)FileReadInteger(fh); - history[ii].combinedZ = FileReadDouble(fh); - for(int j=0; j= MAX_HISTORY) ? savedIdx : nHist % MAX_HISTORY; - } - - // Neural network inline (versione >= 6) - if(version >= 6 && FileIsEnding(fh) == false) { - int hasNN = FileReadInteger(fh); - if(hasNN == 1) { - if(m_neuralNet == NULL) m_neuralNet = new CNeuralNet(); - m_neuralNet.Load(fh); - int usedNeural = FileReadInteger(fh); - if(usedNeural == 1 && m_neuralNet.EpochsTrained() > 0) { - m_useNeural = true; - Print(" NN loaded inline: ", m_neuralNet.Info()); - } - } - } else if(version == 5 && FileIsEnding(fh) == false) { - // Backward compat: v5 usava filename separato - int hasNN = FileReadInteger(fh); - if(hasNN == 1) { - int sl = FileReadInteger(fh); - string nnFn = FileReadString(fh, sl); - int usedNeural = FileReadInteger(fh); - if(m_neuralNet == NULL) m_neuralNet = new CNeuralNet(); - m_neuralNet.Load(nnFn); - if(usedNeural == 1 && m_neuralNet.EpochsTrained() > 0) { - m_useNeural = true; - Print(" NN loaded (v5 compat): ", m_neuralNet.Info()); - } - } - } - FileClose(fh); - Print("Modello caricato: ", fn, " (", n, " agenti, ", histCount, " barre cache)"); + returnStats.Load(fh); + + // Cache storica + if(version >= 2) { + int savedHist = FileReadInteger(fh); + int savedIdx = FileReadInteger(fh); + int nHist = MathMin(savedHist, MAX_HISTORY); + for(int i = 0; i < nHist; i++) { + int ii = (savedHist >= MAX_HISTORY) ? (savedIdx + i) % MAX_HISTORY : i; + if(ii < 0 || ii >= MAX_HISTORY) { ii = 0; } + history[ii].time = (datetime)FileReadInteger(fh); + history[ii].combinedZ = FileReadDouble(fh); + for(int j = 0; j < agentCount && j < MAX_AGENTS; j++) + history[ii].z[j] = FileReadDouble(fh); + } + histCount = nHist; + histIdx = (savedHist >= MAX_HISTORY) ? savedIdx : nHist % MAX_HISTORY; } - // Salva CSV con TUTTI i parametri derivati per analisi periodica - void SaveAnalysisCSV(string symbol, ENUM_TIMEFRAMES tf) const { - string fn = "TR_Agent_Analysis_" + symbol + "_" + EnumToString(tf) + "_" + m_runTimestamp + ".csv"; - int fh = FileOpen(fn, FILE_TXT|FILE_WRITE|FILE_COMMON); - if(fh == INVALID_HANDLE) { Print("SaveAnalysis: errore apertura ", fn); return; } + // Neural network inline (versione >= 6) + if(version >= 6 && FileIsEnding(fh) == false) { + int hasNN = FileReadInteger(fh); + if(hasNN == 1) { + if(m_neuralNet == NULL) m_neuralNet = new CNeuralNet(); + m_neuralNet.Load(fh); + int usedNeural = FileReadInteger(fh); + if(usedNeural == 1 && m_neuralNet.EpochsTrained() > 0) { + m_useNeural = true; + Print(" NN loaded inline: ", m_neuralNet.Info()); + } + } + } else if(version == 5 && FileIsEnding(fh) == false) { + // Backward compat: v5 usava filename separato + int hasNN = FileReadInteger(fh); + if(hasNN == 1) { + int sl = FileReadInteger(fh); + string nnFn = FileReadString(fh, sl); + int usedNeural = FileReadInteger(fh); + if(m_neuralNet == NULL) m_neuralNet = new CNeuralNet(); + m_neuralNet.Load(nnFn); + if(usedNeural == 1 && m_neuralNet.EpochsTrained() > 0) { + m_useNeural = true; + Print(" NN loaded (v5 compat): ", m_neuralNet.Info()); + } + } + } - // ===================== INTESTAZIONE ===================== - FileWriteString(fh, "=== TR_Agent Analysis Report ===\r\n"); - FileWriteString(fh, "Symbol," + symbol + "\r\n"); - FileWriteString(fh, "Timeframe," + EnumToString(tf) + "\r\n"); - FileWriteString(fh, "Generated," + TimeToString(TimeCurrent()) + "\r\n"); - FileWriteString(fh, "AgentCount," + (string)agentCount + "\r\n"); - FileWriteString(fh, "NeuralMode," + (string)m_useNeural + "\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"); - FileWriteString(fh, "Equity," + StringFormat("%.2f", eq) + "\r\n"); - FileWriteString(fh, "\r\n"); + FileClose(fh); + Print("Modello caricato: ", fn, " (", n, " agenti, ", histCount, " barre cache)"); + } - // ===================== STATISTICHE TRADE ===================== - FileWriteString(fh, "=== Trade Statistics ===\r\n"); - FileWriteString(fh, "TotalTrades," + (string)tradeCount + "\r\n"); - FileWriteString(fh, "WinCount," + (string)winCount + "\r\n"); - double wr = tradeCount > 0 ? 100.0 * winCount / tradeCount : 0; - FileWriteString(fh, "WinRate," + StringFormat("%.2f", wr) + "\r\n"); - FileWriteString(fh, "MaxDrawdown," + StringFormat("%.4f", maxDrawdown) + "\r\n"); - FileWriteString(fh, "RollingSharpe," + StringFormat("%+.4f", rollingSharpe) + "\r\n"); - FileWriteString(fh, "RollingTrades," + (string)rollingCount + "\r\n"); - if(returnStats.Ready()) { - FileWriteString(fh, "ReturnMean," + StringFormat("%+.6f", returnStats.Mean()) + "\r\n"); - FileWriteString(fh, "ReturnStd," + StringFormat("%.6f", returnStats.Std()) + "\r\n"); - FileWriteString(fh, "ReturnCount," + (string)returnStats.Count() + "\r\n"); - } - if(combinedZStats.Ready()) { - FileWriteString(fh, "CombinedZMean," + StringFormat("%+.4f", combinedZStats.Mean()) + "\r\n"); - FileWriteString(fh, "CombinedZStd," + StringFormat("%.4f", combinedZStats.Std()) + "\r\n"); - FileWriteString(fh, "CombinedZCount," + (string)combinedZStats.Count() + "\r\n"); - } - FileWriteString(fh, "\r\n"); + // Salva CSV con TUTTI i parametri derivati per analisi periodica + void SaveAnalysisCSV(string symbol, ENUM_TIMEFRAMES tf) const { + string fn = "TR_Agent_Analysis_" + symbol + "_" + EnumToString(tf) + "_" + m_runTimestamp + ".csv"; + int fh = FileOpen(fn, FILE_TXT | FILE_WRITE | FILE_COMMON); + if(fh == INVALID_HANDLE) { + Print("SaveAnalysis: errore apertura ", fn); + return; + } - // ===================== MAE / MFE ===================== - FileWriteString(fh, "=== MAE/MFE Distributions ===\r\n"); - if(maeStats.Ready()) { - FileWriteString(fh, "MAE_Mean," + StringFormat("%.4f", maeStats.Mean()) + "\r\n"); - FileWriteString(fh, "MAE_Std," + StringFormat("%.4f", maeStats.Std()) + "\r\n"); - } - if(mfeStats.Ready()) { - FileWriteString(fh, "MFE_Mean," + StringFormat("%.4f", mfeStats.Mean()) + "\r\n"); - FileWriteString(fh, "MFE_Std," + StringFormat("%.4f", mfeStats.Std()) + "\r\n"); - } - if(maeWinStats.Ready()) { - FileWriteString(fh, "MAE_Win_Mean," + StringFormat("%.4f", maeWinStats.Mean()) + "\r\n"); - FileWriteString(fh, "MAE_Win_Std," + StringFormat("%.4f", maeWinStats.Std()) + "\r\n"); - } - FileWriteString(fh, "\r\n"); + // ===================== INTESTAZIONE ===================== + FileWriteString(fh, "=== TR_Agent Analysis Report ===\r\n"); + FileWriteString(fh, "Symbol," + symbol + "\r\n"); + FileWriteString(fh, "Timeframe," + EnumToString(tf) + "\r\n"); + FileWriteString(fh, "Generated," + TimeToString(TimeCurrent()) + "\r\n"); + FileWriteString(fh, "AgentCount," + (string)agentCount + "\r\n"); + FileWriteString(fh, "NeuralMode," + (string)m_useNeural + "\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"); + FileWriteString(fh, "Equity," + StringFormat("%.2f", eq) + "\r\n"); + FileWriteString(fh, "\r\n"); - // ===================== THRESHOLDS DERIVATI ===================== - FileWriteString(fh, "=== Derived Thresholds (Adaptive) ===\r\n"); - double derivMinZ = AdaptiveMinZ(); - FileWriteString(fh, "MinZ," + StringFormat("%.4f", derivMinZ) + "\r\n"); - double derivSL = AdaptiveSLWidth(); - FileWriteString(fh, "SLWidth," + StringFormat("%.4f", derivSL) + "\r\n"); - double derivTrailTrig = AdaptiveTrailTrigger(); - FileWriteString(fh, "TrailTrigger," + StringFormat("%.4f", derivTrailTrig) + "\r\n"); - double derivTrailOff = AdaptiveTrailOffset(); - FileWriteString(fh, "TrailOffset," + StringFormat("%.4f", derivTrailOff) + "\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"); - double evtMaxAbsZ = EVT_MaxAbsZ(); - FileWriteString(fh, "EVT_MaxAbsZ," + StringFormat("%.4f", evtMaxAbsZ) + "\r\n"); - FileWriteString(fh, "\r\n"); + // ===================== STATISTICHE TRADE ===================== + FileWriteString(fh, "=== Trade Statistics ===\r\n"); + FileWriteString(fh, "TotalTrades," + (string)tradeCount + "\r\n"); + FileWriteString(fh, "WinCount," + (string)winCount + "\r\n"); + double wr = tradeCount > 0 ? 100.0 * winCount / tradeCount : 0; + FileWriteString(fh, "WinRate," + StringFormat("%.2f", wr) + "\r\n"); + FileWriteString(fh, "MaxDrawdown," + StringFormat("%.4f", maxDrawdown) + "\r\n"); + FileWriteString(fh, "RollingSharpe," + StringFormat("%+.4f", rollingSharpe) + "\r\n"); + FileWriteString(fh, "RollingTrades," + (string)rollingCount + "\r\n"); + if(returnStats.Ready()) { + FileWriteString(fh, "ReturnMean," + StringFormat("%+.6f", returnStats.Mean()) + "\r\n"); + FileWriteString(fh, "ReturnStd," + StringFormat("%.6f", returnStats.Std()) + "\r\n"); + FileWriteString(fh, "ReturnCount," + (string)returnStats.Count() + "\r\n"); + } + if(combinedZStats.Ready()) { + FileWriteString(fh, "CombinedZMean," + StringFormat("%+.4f", combinedZStats.Mean()) + "\r\n"); + FileWriteString(fh, "CombinedZStd," + StringFormat("%.4f", combinedZStats.Std()) + "\r\n"); + FileWriteString(fh, "CombinedZCount," + (string)combinedZStats.Count() + "\r\n"); + } + FileWriteString(fh, "\r\n"); - // ===================== AGENTI ===================== - FileWriteString(fh, "=== Agent Details ===\r\n"); - FileWriteString(fh, "Name,Weight,Rho,LastZ,Bias,BiasN,RhoLearn,RhoLearnN\r\n"); - for(int i=0; i" + (string)m_nnHidden + "->3\r\n"); - FileWriteString(fh, "NN_LR," + StringFormat("%.5f", m_nnLR) + "\r\n"); - } else { - FileWriteString(fh, "NN_Status,not_initialized\r\n"); - } - FileWriteString(fh, "\r\n"); + // ===================== THRESHOLDS DERIVATI ===================== + FileWriteString(fh, "=== Derived Thresholds (Adaptive) ===\r\n"); + double derivMinZ = AdaptiveMinZ(); + FileWriteString(fh, "MinZ," + StringFormat("%.4f", derivMinZ) + "\r\n"); + double derivSL = AdaptiveSLWidth(); + FileWriteString(fh, "SLWidth," + StringFormat("%.4f", derivSL) + "\r\n"); + double derivTrailTrig = AdaptiveTrailTrigger(); + FileWriteString(fh, "TrailTrigger," + StringFormat("%.4f", derivTrailTrig) + "\r\n"); + double derivTrailOff = AdaptiveTrailOffset(); + FileWriteString(fh, "TrailOffset," + StringFormat("%.4f", derivTrailOff) + "\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"); + double evtMaxAbsZ = EVT_MaxAbsZ(); + FileWriteString(fh, "EVT_MaxAbsZ," + StringFormat("%.4f", evtMaxAbsZ) + "\r\n"); + FileWriteString(fh, "\r\n"); - // ===================== INPUT PARAMS (per replica) ===================== - FileWriteString(fh, "=== Input Parameters (replicabili) ===\r\n"); - FileWriteString(fh, "Inp_MinZ," + (string)minActionableZ + "\r\n"); - FileWriteString(fh, "Inp_UseNeural," + (string)m_useNeural + "\r\n"); - FileWriteString(fh, "Inp_NNHidden," + (string)m_nnHidden + "\r\n"); - 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_RiskPerTrade," + StringFormat("%.4f", m_riskPerTrade) + "\r\n"); - FileWriteString(fh, "Inp_RiskTotal," + StringFormat("%.4f", m_riskTotal) + "\r\n"); - FileWriteString(fh, "MaxOpenTrades," + (string)maxOpenTrades + "\r\n"); + // ===================== AGENTI ===================== + FileWriteString(fh, "=== Agent Details ===\r\n"); + FileWriteString(fh, "Name,Weight,Rho,LastZ,Bias,BiasN,RhoLearn,RhoLearnN\r\n"); + for(int i = 0; i < agentCount; i++) { + double rho = GetCorrelation(i); + double bias = agents[i].predictionError.Mean(); + double biasN = agents[i].predictionError.Count(); + double rhoL = agents[i].predCorr.Ready() ? agents[i].predCorr.Correlation() : 0; + double rhoLN = agents[i].predCorr.Count(); + FileWriteString(fh, + agents[i].name + "," + + StringFormat("%.4f", agents[i].weight) + "," + + StringFormat("%+.4f", rho) + "," + + StringFormat("%+.4f", agents[i].lastZScore) + "," + + StringFormat("%+.4f", bias) + "," + + (string)biasN + "," + + StringFormat("%+.4f", rhoL) + "," + + (string)rhoLN + "\r\n"); + } + FileWriteString(fh, "\r\n"); - FileClose(fh); - Print("Analisi salvata: ", fn); - } + // ===================== NEURAL NETWORK ===================== + FileWriteString(fh, "=== Neural Network ===\r\n"); + if(m_neuralNet != NULL && m_neuralNet.IsInitialized()) { + FileWriteString(fh, "NN_Epochs," + (string)m_neuralNet.EpochsTrained() + "\r\n"); + FileWriteString(fh, "NN_Loss," + StringFormat("%.6f", m_neuralNet.LastLoss()) + "\r\n"); + FileWriteString(fh, "NN_Arch,8->" + (string)m_nnHidden + "->3\r\n"); + FileWriteString(fh, "NN_LR," + StringFormat("%.5f", m_nnLR) + "\r\n"); + } else { + FileWriteString(fh, "NN_Status,not_initialized\r\n"); + } + FileWriteString(fh, "\r\n"); - // ===================== SAVE BAR HISTORY CSV ===================== - void SaveBarHistoryCSV(string symbol, ENUM_TIMEFRAMES tf) const { - string fn = "BarHistory_" + symbol + "_" + EnumToString(tf) + "_" + m_runTimestamp + ".csv"; - int fh = FileOpen(fn, FILE_TXT|FILE_WRITE|FILE_COMMON); - if(fh == INVALID_HANDLE) { Print("BarHistoryCSV: errore apertura ", fn); return; } + // ===================== INPUT PARAMS (per replica) ===================== + FileWriteString(fh, "=== Input Parameters (replicabili) ===\r\n"); + FileWriteString(fh, "Inp_MinZ," + (string)minActionableZ + "\r\n"); + FileWriteString(fh, "Inp_UseNeural," + (string)m_useNeural + "\r\n"); + FileWriteString(fh, "Inp_NNHidden," + (string)m_nnHidden + "\r\n"); + 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_RiskPerTrade," + StringFormat("%.4f", m_riskPerTrade) + "\r\n"); + FileWriteString(fh, "Inp_RiskTotal," + StringFormat("%.4f", m_riskTotal) + "\r\n"); + FileWriteString(fh, "MaxOpenTrades," + (string)maxOpenTrades + "\r\n"); - string header = "bar,time,combinedZ"; - string agentNames[MAX_AGENTS]; - for(int i=0; i= MAX_HISTORY) ? histIdx : 0; - for(int i=0; i= MAX_HISTORY) ? histIdx : 0; + for(int i = 0; i < n; i++) { + int ii = (start + i) % MAX_HISTORY; + string line = (string)i + "," + TimeToString(history[ii].time) + "," + StringFormat("%+.6f", history[ii].combinedZ); + for(int j = 0; j < agentCount; j++) + line += "," + StringFormat("%+.6f", history[ii].z[j]); + line += "," + StringFormat("%.4f", SHARED_regimeH); + line += "," + StringFormat("%.1f", SHARED_adxRaw); + line += "," + StringFormat("%+.4f", SHARED_regimeConsensus); + line += "," + StringFormat("%.4f", SHARED_regimeAgreement); + line += "," + StringFormat("%.4f", SHARED_trendStrength); + line += "," + SHARED_patternName; + FileWriteString(fh, line + "\r\n"); + } + FileClose(fh); + Print("Bar history salvata: ", fn, " (", n, " barre)"); + } - // Completed trades - for(int t=0; t= MAX_HISTORY) ? histIdx : 0; - for(int i=0; i= MAX_HISTORY) ? histIdx : 0; + for(int i = 0; i < n; i++) { + int ii = (start + i) % MAX_HISTORY; + string line = (string)i + "," + TimeToString(history[ii].time) + "," + StringFormat("%+.6f", history[ii].combinedZ); + for(int j = 0; j < agentCount; j++) { + line += "," + StringFormat("%+.4f", agentHistory[ii][j].lastZScore); + line += "," + StringFormat("%.4f", agentHistory[ii][j].weight); + line += "," + StringFormat("%+.4f", agentHistory[ii][j].rho); + line += "," + StringFormat("%+.4f", agentHistory[ii][j].bias); + line += "," + StringFormat("%.4f", agentHistory[ii][j].biasStd); + line += "," + StringFormat("%+.4f", agentHistory[ii][j].rhoLearn); + line += "," + StringFormat("%+.4f", agentHistory[ii][j].rawSignal); + } + FileWriteString(fh, line + "\r\n"); + } + FileClose(fh); + Print("Agent interaction salvata: ", fn, " (", n, " barre)"); + } + + // ===================== SAVE DECISION LOG CSV ===================== + void SaveDecisionLogCSV(string symbol, ENUM_TIMEFRAMES tf) const { + string fn = "DecisionLog_" + symbol + "_" + EnumToString(tf) + "_" + m_runTimestamp + ".csv"; + int fh = FileOpen(fn, FILE_TXT | FILE_WRITE | FILE_COMMON); + if(fh == INVALID_HANDLE) { + Print("DecisionLogCSV: errore apertura ", fn); + return; + } + + FileWriteString(fh, "time,action,direction,zScore,combinedZ,price,ticket,agreeingCount,totalAgents,confidence,minZ\r\n"); + for(int d = 0; d < decisionCount; d++) { + string line = TimeToString(decisionLog[d].time); + line += "," + decisionLog[d].action; + line += "," + (string)decisionLog[d].direction; + line += "," + StringFormat("%+.4f", decisionLog[d].zScore); + line += "," + StringFormat("%+.4f", decisionLog[d].combinedZ); + line += "," + StringFormat("%.5f", decisionLog[d].price); + line += "," + (string)decisionLog[d].ticket; + line += "," + (string)decisionLog[d].agreeingCount; + line += "," + (string)decisionLog[d].totalAgents; + line += "," + StringFormat("%.4f", decisionLog[d].confidence); + line += "," + StringFormat("%.4f", decisionLog[d].minZ); + FileWriteString(fh, line + "\r\n"); + } + FileClose(fh); + Print("Decision log salvato: ", fn, " (", decisionCount, " decisioni)"); + } +}; +#endif