Indice
- Architettura: handle-based vs vecchio stile
- Sintassi completa di iCustom
- Gestione degli handle: OnInit e OnDeinit
- CopyBuffer: parametri e casi d’uso
- Passaggio di parametri all’indicatore
- Indicatori multi-buffer
- I 10 errori più comuni
- Performance e ottimizzazione
- Caso reale: Oracle v78 legge UlcerZenith
- Debug e troubleshooting
1. Architettura Handle-Based: il Modello Corretto in MQL5
In MQL4 esisteva la funzione iCustom() che restituiva direttamente il valore del buffer dell’indicatore. In MQL5 il modello è diverso e più efficiente: prima ottieni un handle (un intero che rappresenta l’istanza dell’indicatore), poi usi CopyBuffer() per leggere i dati da quel handle.
Il vantaggio di questo modello è che l’indicatore viene calcolato una sola volta anche se viene referenziato da più EA o indicatori contemporaneamente — MetaTrader gestisce la cache dei risultati internamente. Il handle è un intero ≥ 0; il valore INVALID_HANDLE (-1) indica che la creazione dell’handle è fallita.
// SCHEMA GENERALE (da implementare sempre così) // 1. Dichiarazione globale dell'handle int handleMioIndicatore = INVALID_HANDLE; // 2. Creazione dell'handle in OnInit() int OnInit() { handleMioIndicatore = iCustom(_Symbol, PERIOD_H4, "MioIndicatore"); if(handleMioIndicatore == INVALID_HANDLE) { Print("ERRORE: iCustom fallito. Codice: ", GetLastError()); return(INIT_FAILED); } return(INIT_SUCCEEDED); } // 3. Lettura del buffer con CopyBuffer in OnTick() o OnCalculate() void OnTick() { double valori[]; ArraySetAsSeries(valori, true); if(CopyBuffer(handleMioIndicatore, 0, 0, 3, valori) <= 0) { Print("ERRORE CopyBuffer: ", GetLastError()); return; } double valoreBarra0 = valori[0]; // barra corrente double valoreBarra1 = valori[1]; // barra precedente double valoreBarra2 = valori[2]; // due barre fa } // 4. Rilascio dell'handle in OnDeinit() void OnDeinit(const int reason) { if(handleMioIndicatore != INVALID_HANDLE) IndicatorRelease(handleMioIndicatore); }
2. Sintassi Completa di iCustom
La firma di iCustom in MQL5 è più flessibile di quella di MQL4 grazie ai parametri variabili:
int iCustom( string symbol, // simbolo: usa _Symbol per il corrente ENUM_TIMEFRAMES timeframe, // timeframe: PERIOD_H4, PERIOD_D1, etc. string name, // nome del file .ex5 SENZA l'estensione ... // parametri dell'indicatore (opzionali, variabili) ); // ESEMPI DI CHIAMATA: // Indicatore senza parametri aggiuntivi int h1 = iCustom(_Symbol, PERIOD_H4, "MioIndicatore"); // Indicatore con 1 parametro intero (es. periodo) int h2 = iCustom(_Symbol, PERIOD_H4, "UlcerIndex", 14); // Indicatore con più parametri di tipi diversi int h3 = iCustom(_Symbol, PERIOD_H4, "ZenithScore", 14, // int: periodo UI 2.0, // double: moltiplicatore ATR true, // bool: abilita filtro sessione "XAUUSD" // string: simbolo correlazione ); // Indicatore su simbolo diverso da quello corrente int h4 = iCustom("EURUSD", PERIOD_H4, "MioIndicatore", 14); // Indicatore su timeframe diverso (lettura multi-TF) int h5 = iCustom(_Symbol, PERIOD_D1, "MioIndicatore", 14); // Legge l'indicatore sul Daily anche se il chart è H4
name è il percorso relativo al file .ex5 nella cartella MQL5/Indicators/. Se l’indicatore è in una sottocartella: "Subfolder/MioIndicatore". Il percorso usa slash (/) o backslash (\\). Non includere l’estensione .ex5.3. Gestione Corretta degli Handle
La gestione degli handle è il punto dove i programmatori MQL5 fanno più errori. Le regole sono semplici ma critiche:
// Pattern completo per gestione robusta degli handle // usato in Oracle v78 per i suoi 6 handle di indicatori // Dichiarazione globale — inizializza SEMPRE a INVALID_HANDLE int hUlcer = INVALID_HANDLE; int hATR = INVALID_HANDLE; int hSession = INVALID_HANDLE; int OnInit() { // Crea tutti gli handle in sequenza hUlcer = iCustom(_Symbol, PERIOD_H4, "UlcerZenith_v77", 14, true); hATR = iATR(_Symbol, PERIOD_H4, 14); hSession = iCustom(_Symbol, PERIOD_H4, "SessionFilter"); // Verifica TUTTI prima di procedere if(hUlcer == INVALID_HANDLE || hATR == INVALID_HANDLE || hSession == INVALID_HANDLE) { PrintFormat("OnInit FAILED: hUlcer=%d hATR=%d hSession=%d err=%d", hUlcer, hATR, hSession, GetLastError()); RilasciaHandles(); // rilascia quelli eventualmente validi return(INIT_FAILED); } // Attesa warm-up: aspetta che gli indicatori abbiano dati calcolati // Specialmente importante su simboli appena aperti o dati scarsi EventSetTimer(1); // controlla ogni secondo in OnTimer() return(INIT_SUCCEEDED); } void RilasciaHandles() { // Helper function — rilascia solo gli handle validi if(hUlcer != INVALID_HANDLE) { IndicatorRelease(hUlcer); hUlcer = INVALID_HANDLE; } if(hATR != INVALID_HANDLE) { IndicatorRelease(hATR); hATR = INVALID_HANDLE; } if(hSession != INVALID_HANDLE) { IndicatorRelease(hSession); hSession = INVALID_HANDLE; } } void OnDeinit(const int reason) { EventKillTimer(); RilasciaHandles(); } // Verifica che i dati siano disponibili prima di usarli bool DatiPronti() { return( BarsCalculated(hUlcer) >= 50 && BarsCalculated(hATR) >= 14 && BarsCalculated(hSession) >= 1 ); }
IndicatorRelease su un handle INVALID_HANDLE. Causa undefined behavior. Verifica sempre con if(handle != INVALID_HANDLE) prima di rilasciare.4. CopyBuffer: Parametri e Tutti i Casi d’Uso
CopyBuffer è la funzione che legge effettivamente i dati da un handle. Ha due forme principali: copia per conteggio (i più recenti N elementi) e copia per range temporale (tra due datetime). Entrambe restituiscono il numero di elementi copiati o -1 in caso di errore.
// VARIANTE 1: copia N elementi partendo dalla barra start_pos // (la più usata) int CopyBuffer( int indicator_handle, // handle dell'indicatore int buffer_num, // indice del buffer (0 = primo buffer) int start_pos, // barra da cui iniziare (0 = più recente) int count, // numero di elementi da copiare double& buffer[] // array di destinazione ); // VARIANTE 2: copia N elementi a partire da un datetime specifico int CopyBuffer( int indicator_handle, int buffer_num, datetime start_time, // datetime di inizio int count, double& buffer[] ); // VARIANTE 3: copia tra due datetime int CopyBuffer( int indicator_handle, int buffer_num, datetime start_time, datetime stop_time, double& buffer[] ); // ═══════════════════════════════════════════════════════════ // ESEMPI PRATICI // ═══════════════════════════════════════════════════════════ double buf[]; ArraySetAsSeries(buf, true); // indice 0 = barra più recente // Leggi solo la barra corrente (più efficiente quando serve solo 1 valore) double valoreSingolo[1]; CopyBuffer(hUlcer, 0, 0, 1, valoreSingolo); // valoreSingolo[0] = valore Ulcer Index sulla barra più recente // Leggi le ultime 3 barre (per confrontare valori recenti) CopyBuffer(hUlcer, 0, 0, 3, buf); // buf[0] = barra corrente, buf[1] = precedente, buf[2] = due fa // Leggi dal secondo buffer dell'indicatore (indice 1) double segnaleBuf[]; CopyBuffer(hUlcer, 1, 0, 3, segnaleBuf); // Leggi le ultime 200 barre per calcolo statistico ArrayResize(buf, 200); int copied = CopyBuffer(hUlcer, 0, 0, 200, buf); if(copied != 200) Print("Solo ", copied, " barre disponibili (richieste 200)"); // Leggi i dati dell'ultima settimana con range datetime datetime unaSettimanaFa = TimeCurrent() - 7 * 86400; CopyBuffer(hUlcer, 0, unaSettimanaFa, TimeCurrent(), buf);
Gestione Corretta degli Errori di CopyBuffer
// Funzione helper che gestisce tutti gli errori di CopyBuffer bool LeggiBuffer(int handle, int bufferIdx, int count, double &out[]) { if(handle == INVALID_HANDLE) return(false); ArraySetAsSeries(out, true); int copied = CopyBuffer(handle, bufferIdx, 0, count, out); if(copied <= 0) { int err = GetLastError(); switch(err) { case ERR_INDICATOR_DATA_NOT_FOUND: // I dati non sono ancora calcolati — normale all'avvio return(false); case ERR_HISTORY_NOT_FOUND: // Non ci sono abbastanza dati storici Print("Dati storici insufficienti per buffer ", bufferIdx); return(false); default: PrintFormat("CopyBuffer errore %d su handle %d buffer %d", err, handle, bufferIdx); return(false); } } if(copied < count) { // Meno dati del richiesto — non necessariamente un errore // (es. il grafico ha meno barre di quante richieste) Print("CopyBuffer: ricevute ", copied, " barre su ", count, " richieste"); } return(true); }
5. Passaggio di Parametri all’Indicatore
I parametri passati a iCustom dopo il nome del file devono corrispondere esattamente ai parametri input dell’indicatore, nello stesso ordine e dello stesso tipo. Un errore di tipo o ordine produce un INVALID_HANDLE o, peggio, un handle valido che legge dati sbagliati in silenzio.
// INDICATORE TARGET (UlcerZenith_v77.mq5) — parametri input: // input int PeriodoUI = 14; // periodi per il calcolo UI // input double MoltiplicatoreATR = 2.0; // ATR multiplier per lo stop // input bool FiltroSessione = true; // abilita filtro sessione // input ENUM_TIMEFRAMES TFConfermta = PERIOD_D1; // TF di conferma // CHIAMATA CORRETTA — tipi e ordine devono corrispondere esattamente int hZenith = iCustom(_Symbol, PERIOD_H4, "UlcerZenith_v77", (int) 14, // ✓ int → int (double) 2.0, // ✓ double → double (bool) true, // ✓ bool → bool (int) PERIOD_D1 // ✓ int (enum) → ENUM_TIMEFRAMES ); // ERRORI COMUNI: // ✗ Passare 14 (int) dove si aspetta double int hErrato1 = iCustom(_Symbol, PERIOD_H4, "UlcerZenith_v77", 14, // OK (int literal) 2, // ✗ int literal dove serve double → dati SBAGLIATI in silenzio! true, PERIOD_D1 ); // Fix: usa 2.0 (double literal) o (double)2 // ✗ Saltare un parametro int hErrato2 = iCustom(_Symbol, PERIOD_H4, "UlcerZenith_v77", 14, 2.0 // ✗ mancano filtroSessione e TFConferma // usa i default dell'indicatore — ma potrebbe produrre // INVALID_HANDLE se i tipi successivi non corrispondono ); // Fix: passa TUTTI i parametri o usa i default espliciti // ✓ Uso di variabili parametrizzate (pattern EA con input) input int InpPeriodoUI = 14; input double InpMoltiplicatoreATR = 2.0; input bool InpFiltroSessione = true; int hCorretto = iCustom(_Symbol, PERIOD_H4, "UlcerZenith_v77", InpPeriodoUI, InpMoltiplicatoreATR, InpFiltroSessione, PERIOD_D1 );
6. Leggere Indicatori Multi-Buffer
Un indicatore può esporre più buffer. Ad esempio, UlcerZenith espone 8 buffer: lo score 0-10, il segnale direzionale, il livello di ingresso proposto, le tre bande T-Student e due buffer ausiliari. Ogni buffer ha un indice numerico (0, 1, 2 ...) che corrisponde all’ordine in cui è stato registrato con SetIndexBuffer nell’indicatore.
// Struttura per raccogliere tutti i valori di UlcerZenith in una barra struct ZenithData { double score; // buffer 0: score 0-10 double direzione; // buffer 1: +1 long, -1 short, 0 neutro double entryLevel; // buffer 2: livello prezzo suggerito per entry double band68; // buffer 3: banda T-Student al 68% double band95; // buffer 4: banda T-Student al 95% double band99; // buffer 5: banda T-Student al 99% double ulcerRaw; // buffer 6: valore grezzo Ulcer Index double atrAdattivo; // buffer 7: ATR adattivo corrente }; bool LeggiZenith(ZenithData &dati, int barra = 0) { double tmp[1]; // Leggi ogni buffer separatamente (approccio più sicuro) if(CopyBuffer(hZenith, 0, barra, 1, tmp) <= 0) return(false); dati.score = tmp[0]; if(CopyBuffer(hZenith, 1, barra, 1, tmp) <= 0) return(false); dati.direzione = tmp[0]; if(CopyBuffer(hZenith, 2, barra, 1, tmp) <= 0) return(false); dati.entryLevel = tmp[0]; if(CopyBuffer(hZenith, 3, barra, 1, tmp) <= 0) return(false); dati.band68 = tmp[0]; if(CopyBuffer(hZenith, 4, barra, 1, tmp) <= 0) return(false); dati.band95 = tmp[0]; if(CopyBuffer(hZenith, 5, barra, 1, tmp) <= 0) return(false); dati.band99 = tmp[0]; if(CopyBuffer(hZenith, 6, barra, 1, tmp) <= 0) return(false); dati.ulcerRaw = tmp[0]; if(CopyBuffer(hZenith, 7, barra, 1, tmp) <= 0) return(false); dati.atrAdattivo = tmp[0]; return(true); } // Uso in OnTick ZenithData zenith; if(!LeggiZenith(zenith)) { Print("Dati Zenith non disponibili"); return; } PrintFormat("Score: %.2f | Dir: %+.0f | Entry: %.5f | Band95: %.5f", zenith.score, zenith.direzione, zenith.entryLevel, zenith.band95);
7. I 10 Errori Più Comuni con iCustom
| # | Errore | Causa | Fix |
|---|---|---|---|
| 1 | INVALID_HANDLE al primo avvio | Il file .ex5 non esiste o è in una sottocartella sbagliata | Controlla il percorso esatto in MQL5/Indicators/ |
| 2 | CopyBuffer restituisce 0 | L’indicatore non ha ancora calcolato abbastanza barre (warm-up) | Aggiungi BarsCalculated(handle) >= N come guard clause |
| 3 | Parametri integer passati come double | Literal 2 invece di 2.0 per parametro double | Usa sempre literals tipizzati o cast espliciti |
| 4 | Buffer 0 legge dati del buffer 1 | Ordine buffer diverso dal previsto | Conta i buffer con SetIndexBuffer nell’indicatore sorgente |
| 5 | Memory leak | IndicatorRelease non chiamato in OnDeinit | Implementa sempre RilasciaHandles() in OnDeinit |
| 6 | Race condition su nuova barra | Leggi buffer prima che la nuova barra sia calcolata | Usa buf[1] (barra chiusa) invece di buf[0] (barra aperta) |
| 7 | Simbolo errato su chart diverso | Usi _Symbol ma l’EA gira su chart EURUSD e vuoi XAUUSD | Passa il simbolo esplicito come stringa invece di _Symbol |
| 8 | Timeframe errato | iCustom con PERIOD_H4 su chart M15: legge H4, non M15 | Usa _Period per il timeframe corrente del chart |
| 9 | EMPTY_VALUE nei calcoli | Buffer restituisce EMPTY_VALUE (2147483647.0) per barre senza dati | Aggiungi if(buf[0] == EMPTY_VALUE) return; |
| 10 | Handle duplicati | OnInit chiamato più volte senza rilasciare il vecchio handle | Chiama RilasciaHandles() all’inizio di OnInit prima di ricreare |
8. Performance e Ottimizzazione
In un EA che gira su ogni tick, la lettura dei buffer degli indicatori deve essere efficiente. Alcune regole pratiche:
- Leggi solo su nuova barra, non su ogni tick: l’indicatore H4 cambia solo ogni 4 ore — non serve leggerlo su ogni tick. Usa il pattern
static datetime ultimaBarra = 0e aggiorna solo quandoiTime(_Symbol, _Period, 0) != ultimaBarra. - Copia il minimo di barre necessario: se ti serve solo il valore corrente, copia 1 barra. Se ti serve il confronto tra corrente e precedente, copia 2. Evita di copiare 200 barre se ne usi 3.
- Riusa gli array preallocati: dichiara gli array di destinazione come variabili globali o statiche — evita la riallocazione ad ogni tick.
// Pattern ottimizzato: leggi solo su nuova barra static datetime ultimaBarra = 0; static double zenithScore = 0.0; // cache del valore void OnTick() { datetime barraCorrT = iTime(_Symbol, PERIOD_H4, 0); if(barraCorrT == ultimaBarra) return; // stessa barra, esci subito ultimaBarra = barraCorrT; // Nuova barra H4: leggi i buffer double buf[2]; ArraySetAsSeries(buf, true); if(CopyBuffer(hZenith, 0, 1, 2, buf) < 2) return; // legge barre chiuse // Nota: start_pos=1 per leggere barra 1 e barra 2 (entrambe chiuse) // Evita la barra 0 che è ancora aperta e potrebbe avere dati parziali zenithScore = buf[0]; // barra 1 (chiusa più recente) // Processa il segnale... if(zenithScore >= 7.0) ConsideraSegnaleLong(); else if(zenithScore <= 3.0) ConsideraSegnaleShort(); }
9. Caso Reale: Come Oracle v78 Usa iCustom
Oracle v78 è composto da due file MQL5 distinti: l’indicatore UlcerZenith_v77.ex5 (che calcola lo score e i buffer statistici) e l’EA ZenithExecutor.ex5 (che legge i buffer dell’indicatore e gestisce le operazioni). Questa architettura permette di aggiornare i due componenti indipendentemente e di usare l’indicatore anche senza l’EA per il solo monitoraggio visivo.
// ZenithExecutor.mq5 — sezione rilevante per iCustom // (semplificata per illustrare il pattern) input string InpNomeIndicatore = "MTInvestments\\UlcerZenith_v77"; input int InpPeriodoUI = 14; input double InpScoreMinLong = 7.0; input double InpScoreMaxShort = 3.0; int hZenith = INVALID_HANDLE; int OnInit() { // Crea handle con parametri identici a quelli dell'indicatore sul chart hZenith = iCustom(_Symbol, PERIOD_H4, InpNomeIndicatore, InpPeriodoUI, // parametro 1: periodo 2.0, // parametro 2: moltiplicatore ATR true, // parametro 3: filtro sessione PERIOD_D1 // parametro 4: TF conferma ); if(hZenith == INVALID_HANDLE) { Alert("Errore creazione handle UlcerZenith. File presente in Indicators?"); return(INIT_FAILED); } // Attendi che l'indicatore abbia almeno 50 barre calcolate int tentativi = 0; while(BarsCalculated(hZenith) < 50 && tentativi < 100) { Sleep(100); // aspetta 100ms tentativi++; } if(BarsCalculated(hZenith) < 50) { Print("UlcerZenith non ha dati sufficienti dopo warm-up"); return(INIT_FAILED); } Print("ZenithExecutor inizializzato. Handle: ", hZenith, " Barre calcolate: ", BarsCalculated(hZenith)); return(INIT_SUCCEEDED); }
10. Debug e Troubleshooting
Quando iCustom non funziona come atteso, il debug sistematico risolve il 95% dei problemi:
// Script MQL5 per diagnosticare problemi con iCustom // Incolla in uno script e premi F5 per eseguire void OnStart() { string nomeIndicatore = "MTInvestments\\UlcerZenith_v77"; // Test 1: il file esiste? Print("=== Test iCustom Diagnostica ==="); int h = iCustom(_Symbol, PERIOD_H4, nomeIndicatore, 14, 2.0, true, PERIOD_D1); PrintFormat("Handle: %d (INVALID=%d)", h, INVALID_HANDLE); if(h == INVALID_HANDLE) { PrintFormat("FALLITO. Errore: %d. Verifica:", GetLastError()); Print("1. File esiste in MQL5/Indicators/", nomeIndicatore, ".ex5?"); Print("2. Il file è compilato (verde in Navigator)?"); Print("3. I parametri corrispondono in tipo e ordine?"); return; } // Test 2: quante barre calcolate? Sleep(2000); // aspetta 2 secondi per il calcolo int barre = BarsCalculated(h); PrintFormat("Barre calcolate: %d", barre); // Test 3: leggi i primi 3 valori di ogni buffer for(int buf = 0; buf < 8; buf++) { double vals[3]; int copied = CopyBuffer(h, buf, 0, 3, vals); if(copied <= 0) PrintFormat("Buffer %d: ERRORE (copied=%d err=%d)", buf, copied, GetLastError()); else PrintFormat("Buffer %d: [%.4f, %.4f, %.4f]", buf, vals[0], vals[1], vals[2]); } IndicatorRelease(h); Print("=== Fine diagnostica ==="); }