436 lines
16 KiB
Plaintext
436 lines
16 KiB
Plaintext
#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.01;
|
|
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()) {
|
|
string lines[8];
|
|
// 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_REQUIRED);
|
|
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;
|
|
if(fs.direction == 1)
|
|
ticket = trade.Buy(lot, sym, ask, sl, 0); // TP=0: trailing stop
|
|
else
|
|
ticket = trade.Sell(lot, sym, bid, sl, 0);
|
|
|
|
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) saveInterval = MathMax(orchestrator.TotalAgents(), 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; // nessun altro trade attivo
|
|
|
|
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 chiuso da SL/TP (non da noi in 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;
|
|
}
|