1. Array Statici e Dinamici: Le Differenze che Contano
In MQL5 esistono due tipi di array: statici (dimensione fissa definita al momento della dichiarazione) e dinamici (dimensione modificabile a runtime con ArrayResize()). La scelta tra i due non è arbitraria — ha implicazioni dirette sulle performance e sulla stabilità dell’EA.
Array Statici
Un array statico viene dichiarato con la dimensione specificata tra parentesi quadre. La memoria viene allocata al momento della compilazione e rimane fissa per tutta la vita dell’oggetto che lo contiene. Sono ideali quando la dimensione è nota e costante: 5 timeframe, 4 componenti di scoring, 12 mesi dell’anno.
// Array statici: dimensione fissa a compile-time
double prezziChiusura[100]; // 100 double, inizializzati a 0.0
int contatoriSignali[5]; // 5 interi per 5 componenti di scoring
bool filtriAttivi[8]; // 8 flag booleani
string nomiSymbol[6] = {"EURUSD","USDJPY","XAUUSD","AUDUSD","GBPUSD","NZDUSD"};
// Array statico multidimensionale
double matriceCorrelazione[6][6]; // Matrice 6×6 per correlazioni cross-asset
// Dimensione array: operatore ArraySize()
int n = ArraySize(prezziChiusura); // n = 100
int r = ArrayRange(matriceCorrelazione, 0); // righe = 6
int c = ArrayRange(matriceCorrelazione, 1); // colonne = 6
// Gli array statici NON possono essere ridimensionati
// ArrayResize(prezziChiusura, 200); // ERRORE di compilazione se dichiarati così
Array Dinamici
Un array dinamico viene dichiarato senza specificare la dimensione. È essenzialmente un puntatore a un’area di memoria che può essere riallocata con ArrayResize(). Sono indispensabili quando la dimensione dipende da dati runtime: il numero di barre disponibili, il numero di operazioni in un periodo variabile, la lunghezza di un dataset da calcolare.
// Dichiarazione array dinamico (senza dimensione) double bufferUlcerIndex[]; double bufferRSI[]; int indiciSegnali[]; // Allocazione memoria con ArrayResize() // Secondo parametro opzionale: reserve (pre-alloca memoria extra) // → evita riallocazioni costose durante cicli di push int barre = iBars(_Symbol, _Period); ArrayResize(bufferUlcerIndex, barre); // Alloca esattamente 'barre' elementi ArrayResize(bufferRSI, barre, 1000); // Alloca 'barre' + riserva 1000 elementi ArrayResize(indiciSegnali, 0); // Array vuoto, pronto per crescere // Aggiunta elemento (pattern "push_back"): int nuovoIndice = 1234; int vecchiaDim = ArraySize(indiciSegnali); ArrayResize(indiciSegnali, vecchiaDim + 1, 100); // +1 elemento, riserva 100 indiciSegnali[vecchiaDim] = nuovoIndice; // Liberare la memoria quando non più necessaria ArrayFree(bufferUlcerIndex); ArrayFree(bufferRSI); // Copiare un array dinamico in un altro double copia[]; ArrayCopy(copia, bufferRSI); // Copia completa ArrayCopy(copia, bufferRSI, 0, 0, 50); // Copia solo i primi 50 elementi // Inizializzare tutti gli elementi a un valore specifico ArrayInitialize(bufferUlcerIndex, 0.0); ArrayInitialize(filtriAttivi, false); // Su array bool
reserve di ArrayResize() è critico per le performance. Se aggiungi elementi uno alla volta in un ciclo senza reserve, MQL5 riallocherà la memoria a ogni chiamata — l’O(n²) ammazzerà la velocità dell'EA. Usa sempre una riserva generosa nelle operazioni di push_back.2. Accesso agli Elementi: Indicizzazione Diretta e ArraySeries
In MQL5 esiste una distinzione fondamentale che confonde quasi tutti i principianti e molti intermedi: la direzione dell’indicizzazione. Un array normale ha l’elemento 0 come primo elemento inserito (l’elemento più vecchio se parli di prezzi storici). Un array “series” ha l’elemento 0 come l’elemento più recente (l’ultima barra formata).
Questa distinzione è fondamentale perché le funzioni di copia dei prezzi storici (CopyClose(), CopyHigh(), ecc.) producono array series per default, ma le funzioni degli indicatori e i buffer degli indicatori si comportano diversamente. Confondere i due porta a bug subdoli che sono quasi impossibili da debuggare senza capire questo meccanismo.
double chiusure[];
double chiusureAsc[];
// CopyClose con array dinamico: di default è SERIES (indice 0 = barra più recente)
CopyClose(_Symbol, PERIOD_H4, 0, 100, chiusure);
// chiusure[0] = prezzo chiusura barra H4 più recente (barra 0)
// chiusure[1] = prezzo chiusura barra H4 precedente (barra 1)
// chiusure[99] = prezzo chiusura H4 di 99 barre fa
// Verifica e impostazione manuale della direzione
if(ArrayGetAsSeries(chiusure))
Print("L'array è in modalità series (0 = più recente)");
// Forza direzione crescente (0 = più vecchio, utile per calcoli statistici)
ArraySetAsSeries(chiusureAsc, false);
CopyClose(_Symbol, PERIOD_H4, 0, 100, chiusureAsc);
// chiusureAsc[0] = barra più vecchia (99 barre fa)
// chiusureAsc[99] = barra più recente
// ATTENZIONE: inversione su array esistente
double temp[];
ArrayCopy(temp, chiusure);
ArraySetAsSeries(temp, false); // Inverte LOGICAMENTE la direzione (non copia i dati)
// Ora temp[0] = vecchio, temp[99] = recente — ma è il PUNTATORE a cambiare, non i dati
// Pattern sicuro per confronto su serie temporali:
// Usa sempre ArrayGetAsSeries() prima di accedere agli elementi in funzioni generiche
void ProcessaArray(double &arr[])
{
bool isSeries = ArrayGetAsSeries(arr);
int n = ArraySize(arr);
for(int i = 0; i < n-1; i++)
{
// Se series: arr[0] è più recente → confronto con arr[1] è "oggi vs ieri"
// Se non series: arr[n-1] è più recente → confronto è invertito
double corrente = isSeries ? arr[i] : arr[n-1-i];
double precedente = isSeries ? arr[i+1] : arr[n-2-i];
double variazione = corrente - precedente;
// ... elaborazione
}
}
SERIES=true dopo la copia, indipendentemente da come li hai ricevuti. Aggiunge una riga ma elimina una categoria intera di bug.3. Array Multidimensionali in MQL5
MQL5 supporta array fino a 4 dimensioni. Gli array multidimensionali in MQL5 sono implementati come array a righe continue in memoria (row-major order), come in C. Questo ha implicazioni importanti per le performance: accedere agli elementi lungo la prima dimensione è più lento che accedere lungo l’ultima (cache miss).
// Matrice di correlazione 6×6 tra 6 coppie forex
double corrMatrix[6][6];
// Calcolo correlazione di Pearson tra due serie
double PearsonCorrelation(const double &x[], const double &y[], int n)
{
double sumX = 0, sumY = 0, sumXY = 0, sumX2 = 0, sumY2 = 0;
for(int i = 0; i < n; i++)
{
sumX += x[i];
sumY += y[i];
sumXY += x[i] * y[i];
sumX2 += x[i] * x[i];
sumY2 += y[i] * y[i];
}
double num = n * sumXY - sumX * sumY;
double den = MathSqrt((n * sumX2 - sumX*sumX) * (n * sumY2 - sumY*sumY));
if(den == 0.0) return(0.0);
return(num / den);
}
// Riempimento matrice di correlazione
string simboli[6] = {"EURUSD","USDJPY","XAUUSD","AUDUSD","GBPUSD","NZDUSD"};
int LOOKBACK = 200; // 200 barre H4 = ~33 giorni di trading
void AggiornaMtriceCorrelazione()
{
double rendimenti[6][]; // Array di array dinamici (jagged array)
for(int i = 0; i < 6; i++)
{
double prezzi[];
int copiati = CopyClose(simboli[i], PERIOD_H4, 0, LOOKBACK+1, prezzi);
ArrayResize(rendimenti[i], LOOKBACK);
for(int j = 0; j < LOOKBACK; j++)
rendimenti[i][j] = MathLog(prezzi[j] / prezzi[j+1]); // log-return
}
// Riempimento simmetrico della matrice
for(int i = 0; i < 6; i++)
{
corrMatrix[i][i] = 1.0; // Autocorrelazione = 1
for(int j = i+1; j < 6; j++)
{
double r = PearsonCorrelation(rendimenti[i], rendimenti[j], LOOKBACK);
corrMatrix[i][j] = r;
corrMatrix[j][i] = r; // Matrice simmetrica
}
}
}
// Accesso a un elemento specifico
double corrAUDUSD_NZDUSD = corrMatrix[3][5]; // Indici di AUDUSD e NZDUSD nell'array simboli
// ArrayRange per dimensioni
int righe = ArrayRange(corrMatrix, 0); // 6
int colonne = ArrayRange(corrMatrix, 1); // 6
// Dimensione totale
int totale = ArraySize(corrMatrix); // 36 (6×6)
double arr[6][] non compila. Per array irregolari usa array piatti con offset manuali oppure strutture dinamiche.4. Array come Buffer degli Indicatori Personalizzati
Questa è forse la funzionalità più importante degli array in MQL5 per chi sviluppa indicatori. I buffer degli indicatori sono array speciali collegati direttamente al motore di rendering del grafico di MT5. Ogni valore che scrivi nel buffer appare automaticamente sul grafico.
// File: UlcerIndex_Indicator.mq5
#property indicator_separate_window
#property indicator_buffers 3
#property indicator_plots 3
// Dichiarazione buffer come variabili globali dell'indicatore
double BufferUlcer[]; // Buffer 0: valore Ulcer Index
double BufferSogliaSup[]; // Buffer 1: soglia superiore (livello di allerta)
double BufferSogliaInf[]; // Buffer 2: soglia inferiore (zona di entrata)
//--- Parametri input
input int InpPeriodo = 14; // Periodo Ulcer Index
input double InpSogliaAlta = 7.5; // Soglia superiore
input double InpSogliaBassa = 2.0; // Soglia inferiore
int OnInit()
{
// Registrazione dei buffer — DEVE avvenire in OnInit()
SetIndexBuffer(0, BufferUlcer, INDICATOR_DATA); // Dati visualizzati
SetIndexBuffer(1, BufferSogliaSup, INDICATOR_DATA);
SetIndexBuffer(2, BufferSogliaInf, INDICATOR_DATA);
// Proprietà visive dei buffer
PlotIndexSetInteger(0, PLOT_DRAW_TYPE, DRAW_LINE);
PlotIndexSetInteger(0, PLOT_LINE_COLOR, 0xC8A84B); // Gold
PlotIndexSetInteger(0, PLOT_LINE_WIDTH, 2);
PlotIndexSetString(0, PLOT_LABEL, "Ulcer Index");
PlotIndexSetInteger(1, PLOT_DRAW_TYPE, DRAW_LINE);
PlotIndexSetInteger(1, PLOT_LINE_COLOR, 0xCC2200); // Red
PlotIndexSetInteger(1, PLOT_LINE_STYLE, STYLE_DOT);
PlotIndexSetString(1, PLOT_LABEL, "Soglia Alta");
PlotIndexSetInteger(2, PLOT_DRAW_TYPE, DRAW_LINE);
PlotIndexSetInteger(2, PLOT_LINE_COLOR, 0x00D084); // Green
PlotIndexSetInteger(2, PLOT_LINE_STYLE, STYLE_DOT);
PlotIndexSetString(2, PLOT_LABEL, "Soglia Bassa");
// Valore EMPTY per barre non calcolate
PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
// Numero minimo di barre necessarie prima del calcolo
IndicatorSetInteger(INDICATOR_DIGITS, 2);
return(INIT_SUCCEEDED);
}
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
// rates_total = numero totale di barre disponibili
// prev_calculated = quante barre erano già state calcolate nella chiamata precedente
// Ottimizzazione: ricalcola solo le nuove barre
int start = (prev_calculated > InpPeriodo) ? prev_calculated - 1 : InpPeriodo;
for(int i = start; i < rates_total; i++)
{
// Calcolo Ulcer Index su InpPeriodo barre precedenti
double massimoPeriodo = 0.0;
double sommaScartiQuadratici = 0.0;
for(int j = i - InpPeriodo + 1; j <= i; j++)
{
if(close[j] > massimoPeriodo) massimoPeriodo = close[j];
}
for(int j = i - InpPeriodo + 1; j <= i; j++)
{
double drawdown = 0.0;
if(massimoPeriodo > 0)
drawdown = (massimoPeriodo - close[j]) / massimoPeriodo * 100.0;
sommaScartiQuadratici += drawdown * drawdown;
}
BufferUlcer[i] = MathSqrt(sommaScartiQuadratici / InpPeriodo);
BufferSogliaSup[i] = InpSogliaAlta;
BufferSogliaInf[i] = InpSogliaBassa;
}
return(rates_total); // Indica quante barre sono state calcolate
}
OnCalculate() è il parametro che diventerà prev_calculated alla chiamata successiva. Restituire rates_total dice a MT5 che hai calcolato tutto, ottimizzando le chiamate successive.5. Ordinamento degli Array: ArraySort e Algoritmi Custom
MQL5 fornisce ArraySort() nativo per array monodimensionali. È un quicksort O(n log n) che ordina in senso crescente. Il problema è che ordina solo l’array passato — se hai un array parallelo di indici da mantenere sincronizzato, devi implementare un ordinamento custom.
// === CASO 1: ArraySort nativo — semplice ma limitato ===
double valori[] = {5.2, 1.8, 9.1, 3.4, 7.6};
ArraySort(valori); // {1.8, 3.4, 5.2, 7.6, 9.1} — crescente, in-place
// Inversione per ordinamento decrescente:
ArraySort(valori);
ArrayReverse(valori); // MQL5 >= build 2361, o implementa manualmente
// === CASO 2: Ordinamento con indici paralleli (ArgSort) ===
// Necessario per trovare "i 5 valori più alti e sapere da quali barre vengono"
void ArgSort(const double &arr[], int &indici[], bool decrescente = false)
{
int n = ArraySize(arr);
ArrayResize(indici, n);
for(int i = 0; i < n; i++) indici[i] = i; // indici[i] = i inizialmente
// Insertion sort (buono per n < 100, semplice da implementare)
for(int i = 1; i < n; i++)
{
int keyIdx = indici[i];
double keyVal = arr[keyIdx];
int j = i - 1;
while(j >= 0 && (decrescente ? arr[indici[j]] < keyVal : arr[indici[j]] > keyVal))
{
indici[j+1] = indici[j];
j--;
}
indici[j+1] = keyIdx;
}
}
// Utilizzo:
double scoreComponenti[] = {3.2, 7.8, 1.4, 9.1, 5.5, 4.3, 8.7, 2.1};
int ordine[];
ArgSort(scoreComponenti, ordine, true); // Ordine decrescente
// ordine[0] = 3 (indice del valore massimo: 9.1)
// ordine[1] = 6 (indice del secondo massimo: 8.7)
// ordine[2] = 1 (indice del terzo massimo: 7.8)
// ... ecc.
Print("Componente con score più alto: indice ", ordine[0],
" → score = ", scoreComponenti[ordine[0]]);
// === CASO 3: Percentile con array ordinato (usato in Oracle per soglie adattive) ===
double CalcolaPercentile(double &arr[], double percentile)
{
int n = ArraySize(arr);
if(n == 0) return(0.0);
double copia[];
ArrayCopy(copia, arr);
ArraySort(copia); // Ordina la copia, non l'originale
double posizione = percentile * (n - 1) / 100.0;
int idx_basso = (int)MathFloor(posizione);
int idx_alto = (int)MathCeil(posizione);
double frac = posizione - idx_basso;
if(idx_alto >= n) return(copia[n-1]);
return(copia[idx_basso] * (1.0 - frac) + copia[idx_alto] * frac);
}
// Esempio: soglia adattiva all'80° percentile dell'Ulcer Index ultimi 100 barre
double ulcerBuffer[];
CopyBuffer(handleUlcer, 0, 0, 100, ulcerBuffer);
double soglia80 = CalcolaPercentile(ulcerBuffer, 80.0);
// → Entra solo quando UI è nell'80° percentile degli ultimi 100 valori
6. Ricerca Binaria con ArrayBsearch
ArrayBsearch() è la funzione di ricerca binaria nativa di MQL5. Funziona su array già ordinati e ha complessità O(log n) — su 10.000 elementi fa 14 confronti invece di 10.000. È fondamentale quando devi cercare frequentemente in dataset grandi.
// ArrayBsearch — Ricerca binaria su array ordinato
// Prototipo:
// int ArrayBsearch(array[], valore, count=WHOLE_ARRAY, start=0, direction=MODE_ASCEND)
// Ritorna l'indice dell'elemento trovato, o l'indice dell'elemento più vicino
// === CASO 1: Ricerca di un prezzo in una serie di livelli ===
double livelliFibonacci[] = {3180.5, 3195.2, 3210.0, 3225.8, 3240.3, 3255.1, 3270.0};
// Array GIA' ordinato crescente (requisito per Bsearch)
double prezzoCorrente = 3228.4;
int idx = ArrayBsearch(livelliFibonacci, prezzoCorrente, WHOLE_ARRAY, 0, MODE_ASCEND);
// Ritorna l'indice dell'elemento <= prezzoCorrente più vicino
// In questo caso: idx = 3 (livelliFibonacci[3] = 3225.8, il più vicino sotto)
double livelloVicinoBasso = livelliFibonacci[idx];
double livelloVicinoAlto = (idx+1 < ArraySize(livelliFibonacci)) ?
livelliFibonacci[idx+1] : livelliFibonacci[idx];
Print("Prezzo ", prezzoCorrente, " è tra ", livelloVicinoBasso,
" e ", livelloVicinoAlto);
// === CASO 2: Ricerca in database orario eventi news (usato per filtro news in Oracle) ===
// Database di timestamp news ad alto impatto (ordinati)
datetime newsTimestamps[]; // Caricato da file o aggiornato periodicamente
bool IsNearHighImpactNews(datetime candleTime, int minutiFinestra = 30)
{
if(ArraySize(newsTimestamps) == 0) return(false);
int idx = ArrayBsearch(newsTimestamps, candleTime, WHOLE_ARRAY, 0, MODE_ASCEND);
// Controlla il news event più vicino (può essere sopra o sotto)
for(int d = -1; d <= 1; d++)
{
int i = idx + d;
if(i < 0 || i >= ArraySize(newsTimestamps)) continue;
long diffSeconds = MathAbs((long)(candleTime - newsTimestamps[i]));
if(diffSeconds <= minutiFinestra * 60)
return(true);
}
return(false);
}
// === CASO 3: Ricerca di una data in serie storica per backtest analysis ===
datetime tempiH4[];
CopyTime(_Symbol, PERIOD_H4, 0, 5000, tempiH4);
ArraySetAsSeries(tempiH4, false); // Ordine crescente per BSearch
// Trova l'indice della barra più vicina a una data specifica
datetime dataCercata = D'2024.01.15 10:00';
int barraPiuVicina = ArrayBsearch(tempiH4, (long)dataCercata,
WHOLE_ARRAY, 0, MODE_ASCEND);
Print("Barra più vicina al ", TimeToString(dataCercata),
" → barra indice ", barraPiuVicina,
" (", TimeToString(tempiH4[barraPiuVicina]), ")");
ArrayBsearch() ritorna l'indice dell'elemento ≤ al valore cercato se non trova la corrispondenza esatta. Verifica sempre che il valore all’indice ritornato sia effettivamente quello cercato se ti serve una corrispondenza esatta.7. Ottimizzazione della Memoria e Performance
In un Expert Advisor che gira 24/5 su VPS con tick frequenti su XAUUSD, la gestione della memoria degli array diventa rilevante. Ecco i pattern di ottimizzazione che Oracle usa internamente:
// === OTTIMIZZAZIONE 1: Passaggio per riferimento, mai per valore ===
// SBAGLIATO: copia l'intero array nello stack → O(n) memoria extra, lento
double CalcolaMediaLenta(double arr[], int n) // arr viene copiato!
{
double sum = 0;
for(int i = 0; i < n; i++) sum += arr[i];
return sum / n;
}
// CORRETTO: passa solo il riferimento → O(1) overhead
double CalcolaMedia(const double &arr[], int n) // solo il puntatore
{
double sum = 0;
for(int i = 0; i < n; i++) sum += arr[i];
return sum / n;
}
// === OTTIMIZZAZIONE 2: Pre-allocazione con reserve per array che crescono ===
// LENTO: riallocazione a ogni iterazione
double risultati_lento[];
for(int i = 0; i < 10000; i++)
{
ArrayResize(risultati_lento, i+1); // 10.000 riallocazioni!
risultati_lento[i] = MathSqrt(i);
}
// VELOCE: pre-alloca tutto in una volta
double risultati_veloce[];
ArrayResize(risultati_veloce, 10000); // Una sola allocazione
for(int i = 0; i < 10000; i++)
risultati_veloce[i] = MathSqrt(i);
// O: usa reserve se la dimensione finale non è nota
double risultati_reserve[];
ArrayResize(risultati_reserve, 0, 10000); // 0 elementi, 10000 di riserva
// Poi aggiungi con pattern push_back senza riallocazioni
// === OTTIMIZZAZIONE 3: Riciclare array invece di riallocarli ===
// Pattern buono per OnTick() che viene chiamato migliaia di volte:
// Dichiara gli array come variabili globali/statiche, non locali
static double s_bufferCalcolo[500]; // Allocato una volta, riusato ogni tick
static bool s_inizializzato = false;
void ProcessaTick()
{
if(!s_inizializzato)
{
ArrayInitialize(s_bufferCalcolo, 0.0);
s_inizializzato = true;
}
// Usa s_bufferCalcolo senza allocare/deallocare ogni tick
CopyClose(_Symbol, PERIOD_H4, 0, 200, s_bufferCalcolo);
// ...
}
// === OTTIMIZZAZIONE 4: ArrayCopy vs loop manuale ===
double src[], dst[];
ArrayResize(src, 1000);
ArrayResize(dst, 1000);
// LENTO: loop manuale
for(int i = 0; i < 1000; i++) dst[i] = src[i];
// VELOCE: ArrayCopy usa memcpy interno ottimizzato
ArrayCopy(dst, src); // ~10x più veloce per array grandi
8. Errori Comuni e Come Evitarli
Questi sono gli errori più frequenti che vedo nel codice MQL5 dei trader che si avvicinano alla programmazione. Ognuno è stato incontrato almeno una volta in 15 anni di sviluppo su MetaTrader.
| Errore | Causa | Soluzione | Gravità |
|---|---|---|---|
| Accesso fuori bounds | arr[ArraySize(arr)] — l'ultimo indice valido è ArraySize-1 | Usa sempre if(i >= 0 && i < ArraySize(arr)) | Crash EA |
| Series vs non-series mix | Copi con CopyClose (series) e usi come se fosse crescente | Chiama sempre ArraySetAsSeries(arr, true/false) esplicitamente | Bug silenzioso |
| ArrayResize senza reserve in loop | Riallocazione O(n²) in cicli push_back | Usa il terzo parametro reserve di ArrayResize | Performance |
| Passaggio per valore di array grandi | Array copiato sullo stack a ogni chiamata di funzione | Usa sempre const double &arr[] nei parametri | Performance |
| ArrayBsearch su array non ordinato | Risultati casuali/errati senza errore esplicito | Chiama ArraySort() prima di ArrayBsearch() | Bug silenzioso |
| Dimenticare di registrare i buffer in OnInit | SetIndexBuffer non chiamato → buffer non collegato al grafico | Ogni buffer dichiarato va registrato in OnInit | Indicatore vuoto |
| Modificare un array const per riferimento | Compilazione ok ma comportamento undefined | Usa const solo su array che non modifichi | Bug intermittente |
9. Come Oracle v78 UlcerZenith Usa gli Array Internamente
Oracle v78 è un sistema a 6 componenti di scoring. Ogni componente calcola un valore numerico che contribuisce allo score totale. La gestione di questi calcoli è interamente basata su array ottimizzati. Ecco la struttura semplificata della parte core:
// Struttura core di Oracle v78 — componenti di scoring
// (versione semplificata per scopo didattico)
#define N_COMPONENTI 6
#define LOOKBACK_MAX 500
// Array globali statici — allocati una volta, riusati ogni tick
static double s_ulcerBuffer[LOOKBACK_MAX]; // Ulcer Index H4
static double s_atrBuffer[LOOKBACK_MAX]; // ATR per normalizzazione
static double s_rsiBuffer[LOOKBACK_MAX]; // RSI filtro momentum
static double s_volumeBuffer[LOOKBACK_MAX]; // Volume relativo
static double s_scoreStorico[LOOKBACK_MAX]; // Score totale ultime N barre
static double s_scoreComponenti[N_COMPONENTI]; // Score attuale per componente
// Handle degli indicatori (calcolati in OnInit)
int g_handleUI, g_handleATR, g_handleRSI;
// Funzione principale di aggiornamento score (chiamata in OnTick sul H4 close)
bool AggiornaScore()
{
// 1. Copia buffer — array statici, nessuna allocazione
if(CopyBuffer(g_handleUI, 0, 0, LOOKBACK_MAX, s_ulcerBuffer) < 0) return false;
if(CopyBuffer(g_handleATR, 0, 0, LOOKBACK_MAX, s_atrBuffer) < 0) return false;
if(CopyBuffer(g_handleRSI, 0, 0, LOOKBACK_MAX, s_rsiBuffer) < 0) return false;
// Tutti i buffer sono SERIES: [0] = barra più recente
// 2. Calcolo percentile adattivo (Comp. 1: Ulcer Index Percentile)
double ulcerScore = CalcolaPercentile(s_ulcerBuffer, 80.0); // Vedi funzione sopra
s_scoreComponenti[0] = NormalizzaRange(s_ulcerBuffer[0], 0, ulcerScore, 0, 10);
// 3. Componente 2: ATR relativo (volatilità normalizzata)
double atrMedio = CalcolaMedia(s_atrBuffer, 100);
double atrRatio = (atrMedio > 0) ? s_atrBuffer[0] / atrMedio : 1.0;
s_scoreComponenti[1] = NormalizzaRange(atrRatio, 0.5, 2.0, 0, 10);
// 4. Componente 3: RSI momentum filter
s_scoreComponenti[2] = (s_rsiBuffer[0] < 35.0) ? 8.0 :
(s_rsiBuffer[0] < 45.0) ? 5.0 :
(s_rsiBuffer[0] > 65.0) ? 8.0 : 2.0;
// 5. Score totale = media pesata delle componenti
double pesi[N_COMPONENTI] = {0.30, 0.20, 0.20, 0.10, 0.10, 0.10};
double scoreTotale = 0.0;
for(int i = 0; i < N_COMPONENTI; i++)
scoreTotale += s_scoreComponenti[i] * pesi[i];
// 6. Push score storico (shift dell'array)
// NB: per array statico fisso, lo shift è manuale
for(int i = LOOKBACK_MAX - 1; i > 0; i--)
s_scoreStorico[i] = s_scoreStorico[i-1];
s_scoreStorico[0] = scoreTotale;
return true;
}
// Helper: normalizza un valore da range [minIn,maxIn] a [minOut,maxOut]
double NormalizzaRange(double val, double minIn, double maxIn,
double minOut, double maxOut)
{
if(maxIn == minIn) return (minOut + maxOut) / 2.0;
double norm = (val - minIn) / (maxIn - minIn);
norm = MathMax(0.0, MathMin(1.0, norm)); // Clamp a [0,1]
return minOut + norm * (maxOut - minOut);
}
✅ Pattern Riassuntivo: Le 5 Regole degli Array in MQL5 Professionale
1. Passa sempre per riferimento (const double &arr[]) nelle funzioni che non modificano l’array. 2. Imposta esplicitamente la direzione series con ArraySetAsSeries() dopo ogni CopyBuffer/CopyClose. 3. Pre-alloca array che crescono in loop usando il parametro reserve. 4. Usa ArraySort() prima di ArrayBsearch() e verifica sempre il risultato. 5. Dichiara array riutilizzati come static o globali per evitare riallocazioni in OnTick.