#property copyright "MultiAgent Test v7" #property version "7.00" #property description "6 agenti: Hurst, ADX, Consensus, MA, Momentum, Hunter" #property description "Neural orchestrator (CNeuralNet) + Pattern hunter cross-agente" #include #include #include "Core\Orchestrator.mqh" #include "Agents\RegimeDetector.mqh" #include "Agents\RegimeADX.mqh" #include "Agents\RegimeConsensus.mqh" #include "Agents\PatternHunter.mqh" #include "Agents\MAAgent.mqh" #include "Agents\MomentumAgent.mqh" input string Inp_Symbol = ""; // Simbolo da testare (vuoto = simbolo corrente) input ENUM_TIMEFRAMES Inp_TF = PERIOD_CURRENT; // Timeframe del test input double Inp_MinZ = 0.0; // Min |z| per aprire trade (0 = soglia adattiva automatica) input double Inp_WMin = 0.0; // Peso minimo per agente (0 = 1/(N*0.1) automatico) input int Inp_BufferBars = 0; // Barre buffer storico (0 = max(HurstPeriod*10, 300)) input double Inp_SLRiskATR = 0.0; // SL in ATR (0 = SL adattivo basato su MAE storico) input double Inp_TPRiskATR = 0.0; // TP in ATR (0 = stesso fattore dello SL) input int Inp_HurstPeriod = 0; // Periodo Hurst (0 = 200) input int Inp_ADXPeriod = 0; // Periodo ADX (0 = 14) input bool Inp_UseNeural = false; // Usa Neural Network invece di softmax pesato input string Inp_NNModelFile = ""; // File modello NN da caricare (vuoto = auto) input bool Inp_TrainMode = false; // Addestra NN durante il test (colleziona campioni) input int Inp_TrainEpochs = 100; // Epoche di training NN per sessione input double Inp_TrainLR = 0.001; // Learning rate NN input int Inp_NNHidden = 6; // Neuroni hidden layer NN input double Inp_RiskPerTrade = 0.01; // % capitale da rischiare per trade (0.01 = 1%) input double Inp_RiskTotal = 0.05; // % capitale massima in rischio su TUTTE le posizioni input bool Inp_UseReversalClose = true; // Chiudi posizione se segnale opposto (false = long+short coesistono) Orchestrator *orchestrator; CTrade *trade; CPositionInfo posInfo; string sym; int dig; datetime lastBarTime = 0; int lastBarTotal = 0; int bufferBars = 0; int magicNumber; // Hash semplice per magic number int StringHash(string s) { int h = 0; int len = StringLen(s); for(int i=0; i 0) ? Inp_HurstPeriod : 200; bufferBars = (Inp_BufferBars > 0) ? Inp_BufferBars : MathMax(300, hurstMaxP * 10); // Peso minimo auto: 1/agentCount * 0.1 double wMinAuto = 1.0 / nAgents * 0.1; double wMin = (Inp_WMin > 0.0) ? Inp_WMin : wMinAuto; sym = (Inp_Symbol == "") ? _Symbol : Inp_Symbol; magicNumber = StringHash(sym + (string)Inp_TF + "MAT") % 999999; if(magicNumber < 10000) magicNumber += 10000; // weightAlpha: 1/(N+10) per N agenti orchestrator = new Orchestrator(Inp_MinZ, wMin, 1.0 / (nAgents + 10), Inp_UseNeural, Inp_NNModelFile, Inp_TrainMode, Inp_TrainEpochs, Inp_TrainLR, Inp_NNHidden, Inp_RiskPerTrade, Inp_RiskTotal); trade = new CTrade(); dig = (int)SymbolInfoInteger(sym, SYMBOL_DIGITS); trade.SetExpertMagicNumber(magicNumber); orchestrator.AddAgent(new RegimeDetector("Hurst", 1.0, Inp_HurstPeriod)); orchestrator.AddAgent(new RegimeADX("ADX", 1.0, Inp_ADXPeriod)); orchestrator.AddAgent(new RegimeConsensus("Consensus", 1.0)); orchestrator.AddAgent(new MAAgent("MA", 1.0, 8, 40)); orchestrator.AddAgent(new MomentumAgent("Momentum", 1.0, 6, 40)); orchestrator.AddAgent(new PatternHunter("Hunter", 1.0)); orchestrator.InitAgents(sym, Inp_TF); orchestrator.LoadState(sym, Inp_TF); // Carica o inizializza modello NN if(Inp_UseNeural || Inp_TrainMode) { if(!orchestrator.LoadNNModel()) { if(Inp_UseNeural) { Print(" NN model not found, initializing fresh network..."); } orchestrator.InitNeuralNet(8, Inp_NNHidden, 3, Inp_UseNeural); } } string modeStr = Inp_UseNeural ? "NEURAL" : "SOFTMAX"; Print("MultiAgentTest v7 avviato: ", sym, " ", EnumToString(Inp_TF), " [", modeStr, "]"); Print("Agenti: ", orchestrator.TotalAgents(), " (Hurst, ADX, MA, Momentum, Consensus, Hunter)"); Print("Min |z|: ", Inp_MinZ, " | Buffer: ", bufferBars, " barre | Neural: ", modeStr); // Warm-up statistiche: passa i dati storici per inizializzare le EWMA MarketData warmup(sym, Inp_TF, bufferBars); if(warmup.Fetch()) { Print("Warm-up statistiche... (", warmup.count, " barre)"); // Processa ogni barra storica una volta, dalla più vecchia alla più recente for(int bar = warmup.count - 1; bar >= 1; bar--) { MarketData single(sym, Inp_TF, 1); single.open[0] = warmup.open[bar]; single.high[0] = warmup.high[bar]; single.low[0] = warmup.low[bar]; single.close[0] = warmup.close[bar]; single.volume[0] = warmup.volume[bar]; single.time[0] = warmup.time[bar]; single.count = 1; orchestrator.Analyze(single); } // Ultima barra (current) processata una volta orchestrator.Analyze(warmup); Print("Hurst: H=", StringFormat("%.3f", SHARED_regimeH), " ADX: ", StringFormat("%.1f", SHARED_adxRaw), " Consensus: ", StringFormat("%+.2f", SHARED_regimeConsensus), " pattern: ", SHARED_patternName); } if(Inp_UseNeural) { if(orchestrator.IsNeuralReady()) { Print(" Neural: READY — ", orchestrator.NeuralInfo()); } else { Print(" Neural: INITIALIZED (untrained — will fallback to softmax)"); } } if(Inp_TrainMode) Print(" TrainMode: ON (collecting samples)"); Print(orchestrator.TotalAgents(), " agenti pronti."); return INIT_SUCCEEDED; } void OnDeinit(const int reason) { CloseAllPositions(); Print("=== MultiAgentTest: Final Report ==="); Print(" Reason: ", reasonToStr(reason)); Print(" Symbol: ", sym, " | TF: ", EnumToString(Inp_TF)); Print(" Mode: ", Inp_UseNeural ? "NEURAL" : "SOFTMAX", " | TrainMode: ", Inp_TrainMode ? "ON" : "OFF"); Print(" Trades: ", orchestrator.TradeCount(), " | Win rate: ", orchestrator.TradeCount() > 0 ? StringFormat("%.1f%%", 100.0 * orchestrator.WinCount() / orchestrator.TradeCount()) : "N/A"); PrintAgentStats(); // Neural: training e salvataggio bool nnTrained = false; if(Inp_TrainMode && orchestrator.TradeCount() >= 3) { Print("=== Neural Network Training (fine backtest) ==="); Print(" Trade count: ", orchestrator.TradeCount()); double loss = orchestrator.TrainNN(); if(loss >= 0) { nnTrained = true; Print(" Training complete: loss=", StringFormat("%.6f", loss)); PrintNeuralStats(); } else { Print(" Training skipped: insufficient samples (need >=3)"); } } // Agent learning summary orchestrator.PrintAgentLearningSummary(); orchestrator.SaveAgentLearningCsv(); // Per-bar agent z-scores, combinedZ, SHARED variables orchestrator.SaveBarHistoryCSV(sym, Inp_TF); // Per-bar agent weights, biases, correlations, raw signals orchestrator.SaveAgentInteractionCSV(sym, Inp_TF); // Per-trade details with PnL, exitReason, full features orchestrator.SaveTradeHistoryCSV(sym, Inp_TF); // Entry/exit decision log orchestrator.SaveDecisionLogCSV(sym, Inp_TF); // Analisi completa: tutti i parametri derivati, soglie, periodi, stats orchestrator.SaveAnalysisCSV(sym, Inp_TF); // Save state file (include NN weights inline se addestrata) orchestrator.SaveState(sym, Inp_TF); // Salva anche NN standalone (utile per debug/backup) if(nnTrained || orchestrator.IsNeuralReady()) orchestrator.SaveNNModel(); orchestrator.ReleaseAgents(); delete orchestrator; delete trade; Print("=== MultiAgentTest terminato ==="); } string reasonToStr(int r) { switch(r) { case REASON_PROGRAM: return "Program"; case REASON_REMOVE: return "Remove"; case REASON_CHARTCLOSE: return "Chart Close"; case REASON_PARAMETERS: return "Parameters Changed"; case REASON_RECOMPILE: return "Recompiled"; case REASON_ACCOUNT: return "Account Changed"; case REASON_TEMPLATE: return "Template"; case REASON_INITFAILED: return "Init Failed"; case REASON_CLOSE: return "Terminal Close"; default: return "Unknown (" + (string)r + ")"; } } void PrintAgentStats() { Print(" Hurst H: ", StringFormat("%.3f", SHARED_regimeH), " | ADX: ", StringFormat("%.1f", SHARED_adxRaw), " | Consensus: ", StringFormat("%+.2f", SHARED_regimeConsensus), " | Agreement: ", StringFormat("%.2f", SHARED_regimeAgreement), " | Pattern: ", SHARED_patternName); } void PrintNeuralStats() { Print(" " + orchestrator.NeuralInfo()); // Feature importanza approssimata: media |W1| per input if(orchestrator.IsNeuralReady()) { // Non possiamo accedere direttamente ai pesi Print(" (vedi CSV per loss history e training samples)"); } } int CountPositions() { int count = 0; for(int i=PositionsTotal()-1; i>=0; i--) { if(posInfo.SelectByIndex(i)) { if(posInfo.Symbol() == sym && posInfo.Magic() == magicNumber) count++; } } return count; } void CloseAllPositions() { for(int i=PositionsTotal()-1; i>=0; i--) { if(posInfo.SelectByIndex(i)) { if(posInfo.Symbol() == sym && posInfo.Magic() == magicNumber) trade.PositionClose(posInfo.Ticket()); } } } void OnTick() { if(!IsNewBar()) return; MarketData data(sym, Inp_TF, bufferBars); if(!data.Fetch()) return; double z = orchestrator.Analyze(data); FinalSignal fs = orchestrator.GetFinalSignal(Inp_MinZ); double minZ = (Inp_MinZ > 0) ? Inp_MinZ : orchestrator.AdaptiveMinZ(); if(fs.IsActionable(minZ)) { ManagePositions(data, fs); } LogSignal(data, fs); if(CountPositions() == 0 && orchestrator.HasOpenTrade()) { orchestrator.ResetTradeState(); } } bool IsNewBar() { int barsTotal = Bars(sym, Inp_TF); datetime timeArr[]; CopyTime(sym, Inp_TF, 0, 1, timeArr); if(ArraySize(timeArr) < 1) return false; datetime barTime = timeArr[0]; if(lastBarTime == 0) { lastBarTime = barTime; lastBarTotal = barsTotal; return false; } if(barTime != lastBarTime || barsTotal != lastBarTotal) { lastBarTime = barTime; lastBarTotal = barsTotal; return true; } return false; } void ManagePositions(const MarketData &data, const FinalSignal &fs) { double minZ = (Inp_MinZ > 0) ? Inp_MinZ : orchestrator.AdaptiveMinZ(); // 1. Update MAE/MFE per tutti gli aperti (traccia high/low intra-barra) orchestrator.UpdateOpenTrades(data.High(0), data.Low(0)); orchestrator.TrailStops(); // 2. Reversal close (opzionale): chiudi posizioni con segnale opposto // Se disabilitato, long e short coesistono — massimizza profitto multi-direzionale if(Inp_UseReversalClose) { int closeTickets[]; orchestrator.GetTradesToClose(closeTickets, fs.zScore, minZ); for(int c = 0; c < ArraySize(closeTickets); c++) { int tkt = closeTickets[c]; if(PositionSelectByTicket(tkt)) { bool isBuy = orchestrator.IsBuyTrade(tkt); double closePrice = isBuy ? SymbolInfoDouble(sym, SYMBOL_BID) : SymbolInfoDouble(sym, SYMBOL_ASK); orchestrator.OnTradeClose(tkt, closePrice, "REVERSAL"); trade.PositionClose(tkt); } } } // 3. Aggiorna trailing stop sul terminale (tester supporta PositionModify) for(int i=PositionsTotal()-1; i>=0; i--) { if(posInfo.SelectByIndex(i)) { if(posInfo.Symbol() != sym || posInfo.Magic() != magicNumber) continue; int tkt = (int)posInfo.Ticket(); double newSL = orchestrator.GetTradeSL(tkt); if(newSL <= 0) continue; double curSL = posInfo.StopLoss(); bool isBuy = posInfo.PositionType() == POSITION_TYPE_BUY; bool shouldUpdate = isBuy ? (newSL > curSL + _Point) : (newSL < curSL - _Point); if(shouldUpdate) { double curTP = posInfo.TakeProfit(); trade.PositionModify(tkt, newSL, curTP); } } } // 4. Apri nuova posizione OGNI BARRA con segnale actionable (aggressivo) if(MathAbs(fs.zScore) > minZ) { double ask = SymbolInfoDouble(sym, SYMBOL_ASK); double bid = SymbolInfoDouble(sym, SYMBOL_BID); double price = (fs.direction == 1) ? ask : bid; // ATR period: scala con N agenti e buffer disponibile int atrPeriod = MathMax(orchestrator.TotalAgents() + 1, MathMin(bufferBars / 15, 50)); double atr = data.ATR(atrPeriod); if(atr <= 0) return; // 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 slPoints = price - sl; if(fs.direction == -1) slPoints = sl - price; if(slPoints <= 0) { Print("SL troppo stretto — skip"); return; } // 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 double freeMargin = AccountInfoDouble(ACCOUNT_MARGIN_FREE); double marginReq = lot * SymbolInfoDouble(sym, SYMBOL_MARGIN_INITIAL); if(marginReq >= freeMargin && freeMargin > 0) { Print("Margine insufficiente: lot=", lot, " free=", freeMargin, " req=", marginReq); return; // Skip — solo il margine blocca i trade } int ticket = 0; // Calcolo TakeProfit opzionale (usiamo lo stesso fattoreATR per TP) double tp = 0; // TakeProfit: se impostato usa Inp_TPRiskATR, altrimenti usa lo stesso fattore di SL (dynamic) double tpWidth = (Inp_TPRiskATR > 0) ? Inp_TPRiskATR : ((Inp_SLRiskATR > 0) ? Inp_SLRiskATR : orchestrator.AdaptiveSLWidth()); if(tpWidth > 0) { tp = (fs.direction == 1) ? price + atr * tpWidth : price - atr * tpWidth; tp = NormalizeDouble(tp, dig); } if(fs.direction == 1) { // BUY – validate SL and TP if(sl <= 0 || sl >= ask) { Print("Invalid BUY SL (", sl, ") – order skipped"); } else if(tp > 0 && tp <= sl) { Print("Invalid BUY TP (", tp, ") – must be > SL – order skipped"); } else { ticket = trade.Buy(lot, sym, ask, sl, tp); // TP may be 0 (no TP) } } else { // SELL – validate SL and TP if(sl <= 0 || sl <= bid) { Print("Invalid SELL SL (", sl, ") – order skipped"); } else if(tp > 0 && tp >= sl) { Print("Invalid SELL TP (", tp, ") – must be < SL – order skipped"); } else { ticket = trade.Sell(lot, sym, bid, sl, tp); } } if(ticket > 0) { orchestrator.OnTradeOpen(ticket, price, atr, lot); } } } void LogSignal(const MarketData &data, const FinalSignal &fs) { string timeStr = TimeToString(TimeCurrent(), TIME_DATE|TIME_MINUTES); string dir = "NONE"; if(fs.direction == 1) dir = "BUY"; if(fs.direction == -1) dir = "SELL"; string mode = Inp_UseNeural && orchestrator.IsNeuralReady() ? "NN" : "SM"; string log = timeStr + " [" + mode + "] " + dir; log += " | z=" + StringFormat("%+.3f", fs.zScore); log += " | conf=" + StringFormat("%.0f%%", fs.confidence*100); log += " | agree=" + (string)fs.agreeingCount + "/" + (string)fs.totalAgents; static int printCounter = 0; if(printCounter % 5 == 0) { orchestrator.PrintAgentStatus(); if(Inp_UseNeural) Print(" ", orchestrator.NeuralInfo()); } printCounter++; // Log decision even for non-actionable signals double minZ = (Inp_MinZ > 0) ? Inp_MinZ : orchestrator.AdaptiveMinZ(); orchestrator.LogDecision("SIGNAL", fs.direction, minZ, data.Close(0)); if(MathAbs(fs.zScore) > Inp_MinZ) { Print(log); Print(" Agents: ", fs.contributingAgents); } } // Rileva chiusure (SL/TP) fatte dal broker — multi-trade void OnTrade() { static int onTradeCloses = 0; static int saveInterval = 0; if(saveInterval == 0) { // Minimum 5 trades, scaled with number of agents saveInterval = MathMax(5, orchestrator.TotalAgents() * 5); } if(!orchestrator.HasOpenTrade()) return; int maxSlots = orchestrator.MaxTradeSlots(); for(int t = 0; t < maxSlots; t++) { int tkt = orchestrator.GetTrackedTicket(t); if(tkt < 0) break; // no more active trades bool found = false; for(int p = PositionsTotal() - 1; p >= 0; p--) { if(posInfo.SelectByIndex(p)) { if((int)posInfo.Ticket() == tkt) { found = true; break; } } } if(!found) { // Trade closed by SL/TP (not by ManagePositions) bool isBuy = orchestrator.IsBuyTrade(tkt); double closePrice = isBuy ? SymbolInfoDouble(sym, SYMBOL_BID) : SymbolInfoDouble(sym, SYMBOL_ASK); orchestrator.OnTradeClose(tkt, closePrice, "SLTP"); onTradeCloses++; Print("SL/TP chiuso trade #", tkt, " (totale SL/TP: ", onTradeCloses, ")"); if(onTradeCloses % saveInterval == 0) orchestrator.SaveState(sym, Inp_TF); } } } double OnTester() { double sharpe = orchestrator.TradeSharpe(); int trades = orchestrator.TradeCount(); Print("OnTester: ", trades, " trade, Sharpe=", StringFormat("%.3f", sharpe)); return sharpe; }