Files

472 lines
19 KiB
Plaintext
Raw Permalink Normal View History

#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;
}