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

456 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; // CURRENT = timeframe del chart
input double Inp_Lot = 0.0; // 0 = adaptive (AdaptiveBaseLot * |z|), >0 = fixed lot
input double Inp_MinZ = 0.0; // 0=adattivo, >0=fisso
input double Inp_WMin = 0.0; // 0 = auto (1/agentCount * 0.1)
input int Inp_BufferBars = 0; // 0 = auto (HurstMaxPeriod * 10)
input double Inp_SLRiskATR = 0.0; // 0=adattivo, >0=fisso
input double Inp_TPRiskATR = 0.0; // 0=adattivo, >0=fisso
input int Inp_HurstPeriod = 0; // 0=auto, >0=fisso
input int Inp_ADXPeriod = 0; // 0=auto, >0=fisso
input bool Inp_UseNeural = false; // true = neural orchestrator, false = softmax
input string Inp_NNModelFile = ""; // modello NN (vuoto = auto Nome_Symbol_TF.dat)
input bool Inp_TrainMode = false; // colleziona campioni e allena NN in backtest
input int Inp_TrainEpochs = 100; // epoche di training
input double Inp_TrainLR = 0.001; // learning rate Adam
input int Inp_NNHidden = 6; // neuroni hidden layer
input double Inp_RiskFraction = 0.20; // frazione di capitale da rischiare per trade
input bool Inp_UseReversalClose = true; // true=chiudi posizioni su segnale opposto, false=long+short coesistono
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_RiskFraction);
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();
// 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);
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 lot = (Inp_Lot > 0) ? Inp_Lot : orchestrator.AdaptiveBaseLot() * MathAbs(fs.zScore);
double lotStep = SymbolInfoDouble(sym, SYMBOL_VOLUME_STEP);
double lotMin = SymbolInfoDouble(sym, SYMBOL_VOLUME_MIN);
double lotMax = SymbolInfoDouble(sym, SYMBOL_VOLUME_MAX);
lot = MathRound(lot / lotStep) * lotStep;
lot = MathMax(lotMin, MathMin(lotMax, lot));
// 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);
}
}
}
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++;
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);
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;
}