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

473 lines
18 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 = "";
input ENUM_TIMEFRAMES Inp_TF = PERIOD_CURRENT;
input double Inp_MinZ = 0.0;
input double Inp_WMin = 0.0;
input int Inp_BufferBars = 0;
input double Inp_SLRiskATR = 0.0;
input double Inp_TPRiskATR = 0.0;
input int Inp_HurstPeriod = 0;
input int Inp_ADXPeriod = 0;
input bool Inp_UseNeural = false;
input string Inp_NNModelFile = "";
input bool Inp_TrainMode = false;
input int Inp_TrainEpochs = 100;
input double Inp_TrainLR = 0.001;
input int Inp_NNHidden = 6;
input double Inp_RiskPerTrade = 0.01; // % capitale da rischiare per trade (es. 0.01 = 1%)
input double Inp_RiskTotal = 0.05; // % capitale massima totale in rischio
input bool Inp_UseReversalClose = true;
Orchestrator *orchestrator;
CTrade *trade;
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);
}
}
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);
orchestrator.LogBarHistory(data.time[0]);
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;
}