Files
TR_Agent/MQL5/Experts/MultiAgentTest/MultiAgentTest.mq5
T

472 lines
19 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#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 <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>
#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<len; i++) {
h = (h * 31 + (int)StringGetCharacter(s, i)) % 999999;
}
return h;
}
int OnInit() {
int nAgents = 6; // Hurst, ADX, MA, Momentum, Consensus, Hunter
// Buffer bars auto: HurstMaxPeriod * 10
int hurstMaxP = (Inp_HurstPeriod > 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;
}