English Русский Deutsch 日本語 한국어 Türkçe
preview
Applicazione del filtro di trend L1 in MetaTrader 5

Applicazione del filtro di trend L1 in MetaTrader 5

MetaTrader 5 — Esempi |
30 9
MetaQuotes
MetaQuotes
L'obiettivo principale del filtro di trend L1 è estrarre il trend sottostante da una serie temporale in modo tale che:
  • le dinamiche a lungo termine dei dati vengano preservate;
  • le fluttuazioni a breve termine e il rumore vengano soppressi;
  • i punti di rottura strutturali (cambiamenti nella pendenza del trend) vengono rilevati automaticamente.
A differenza dei metodi di livellamento classici, questo approccio non impone il livellamento del trend, ma produce un'approssimazione lineare a tratti, particolarmente importante per l'analisi delle serie temporali finanziarie.

Filtro di trend L1



Contenuto


Introduzione

Le serie storiche finanziarie sono caratterizzate da elevati livelli di rumore, frequenti valori anomali e regimi di mercato mutevoli. Nei sistemi di trading pratici, questo si manifesta in modo semplice e misurabile: i classici filtri "smussati” (medie mobili, HP) sono in ritardo, sfocano i momenti di cambio di pendenza e spesso interpretano le correzioni locali come inversioni di tendenza - di conseguenza, il numero di falsi ingressi/uscite aumenta, il profit factor diminuisce e il drawdown cresce. Inoltre, la selezione del parametro di regolarizzazione λ si riduce solitamente a una regolazione manuale e non si generalizza facilmente tra diversi strumenti, timeframe e lunghezze storiche.

Questo articolo propone una soluzione pratica a questi problemi basata sul filtro di trend L1: l'ottimizzazione con regolarizzazione L1 delle differenze seconde produce automaticamente un'approssimazione lineare a tratti con punti di rottura espliciti. I principali vantaggi sono una chiara interpretazione dei punti di rottura come cambiamenti di regime, la possibilità di impostare la scala di regolarizzazione tramite il calcolo di λmax e il passaggio a un parametro relativo λ = coef · λmax, nonché una complessità computazionale lineare adatta all'implementazione in MQL5.

Presentiamo non solo la teoria, ma anche una roadmap pratica completa: metodi per il calcolo di λmax e del trend L1, tre indicatori (trend, pendenza, segno della pendenza), sette indicatori di volatilità del trend L1, integrazione negli Expert Advisor e un protocollo di test riproducibile (quattro modalità di filtraggio, esportazione del saldo/equity e la visualizzazione).


1. Formulazione del problema del filtro di trend

Consideriamo una serie temporale scalare rappresentata come somma di due componenti:

, 

Dove è la componente di trend e è un rumore o una componente irregolare.

L'obiettivo è stimare il trend  dai dati osservati  ​.

Il problema può essere espresso come un compromesso tra la fedeltà ai dati originali e la regolarità del trend stimato.


1.1. Filtro Hodrick-Prescott

Il filtro di Hodrick-Prescott definisce il trend come la soluzione al problema di minimizzazione:

,

dove il parametro λ controlla il grado di smussatura.

Caratteristiche principali del filtro HP:

  • Linearità rispetto ai dati;
  • Complessità computazionale O(n);
  • Per valori piccoli di λ, il trend si avvicina ai dati originali;
  • Per valori elevati di λ, il trend si avvicina alla migliore approssimazione lineare.

Tuttavia, il filtro HP produce sempre un trend smussato e rileva con difficoltà i bruschi cambiamenti di pendenza.


1.2. Metodo del filtro di trend L1

L'idea principale del filtro di trend L1 è quella di trovare un trend che sia vicino ai dati originali ma che contenga il minor numero possibile di variazioni di pendenza. A differenza dei metodi di smussamento classici che minimizzano la curvatura al quadrato, l'approccio L1 minimizza la somma dei valori assoluti delle differenze seconde.

Ciò porta a un risultato fondamentalmente diverso:

  • la maggior parte delle differenze seconde diventano uguali a zero,
  • Il trend viene automaticamente suddiviso in segmenti lineari.

Pertanto, il filtro L1 non cerca di rendere uniforme il trend, ma individua il numero minimo di cambiamenti strutturali che spiegano i dati osservati. Ciò rende il metodo particolarmente adatto alle serie temporali finanziarie, dove le dinamiche spesso consistono in sequenze di fasi di crescita e declino quasi lineari.

Nel filtro di trend L1, la penalità quadratica sulle differenze seconde viene sostituita dalla norma L1 e il trend è definito come la soluzione di un problema di ottimizzazione convessa:


In forma matriciale:


dove:

  • y — serie temporale di input;
  • x — il trend stimato;
  • D — matrice delle differenze seconde;
  • λ>=0 — parametro di regolarizzazione.

L'utilizzo della norma L1 porta a un risultato fondamentalmente diverso: molte differenze seconde si annullano, il che significa che il trend è lineare a tratti.

La seconda differenza è definita come:

Se, quindi i punti giacciono su una linea retta.

Pertanto, una differenza seconda pari a zero corrisponde a un segmento lineare del trend, mentre una differenza seconda diversa da zero corrisponde a un punto di rottura. La norma L1 promuove la sparsità nel vettore Dx, il che significa che la maggior parte delle differenze seconde diventano zero. Ciò implica che, negli intervalli corrispondenti, il trend è lineare. I punti in cui le differenze seconde sono diverse da zero vengono interpretati come punti di rottura del trend.

Pertanto, il metodo del filtro del trend L1 costruisce automaticamente il trend come un insieme di segmenti lineari collegati nei punti di cambiamento strutturale.

Principali caratteristiche del filtro di trend L1:

    • Il trend è costituito da segmenti lineari;
    • I punti di rottura vengono interpretati come cambiamenti strutturali nelle serie temporali;
    • A λ = 0, il trend coincide con i dati originali;
    • Per valori di λ sufficientemente grandi, il trend diventa esattamente la migliore approssimazione lineare;
    • La complessità computazionale rimane lineare rispetto al numero di osservazioni.


    1.3. Ruolo del parametro di regolarizzazione λ

    Il parametro λ controlla il compromesso tra accuratezza dell'approssimazione e complessità del trend:

    Valore di λ Natura della soluzione
    λ=0
    x=y, nessuna smussatura
    λ piccolo
    Smussatura debole, molti punti di rottura
    λ medio
    Trend a tratti lineare
    λ grande
    Trend quasi lineare
    λ≥λmax​
    Trend strettamente lineare

    Tabella 1. Dipendenza del trend L1 dal parametro di regolarizzazione λ

    Pertanto, λ controlla il numero e la posizione dei punti di rottura del trend.


    1.4. Interpretazione geometrica del problema

    Il trend desiderato x può essere visto come un punto in uno spazio n-dimensionale. Il primo termine della funzione obiettivo, responsabile della precisione dell'approssimazione, definisce una sfera euclidea centrata sul punto di osservazione y: più x è vicino a y, minore è l'errore.

    Il termine di regolarizzazione con la norma L1 delle differenze seconde definisce un insieme poliedrico convesso (poliedro). A differenza degli ellissoidi smussati che emergono nella regolarizzazione L2, questo poliedro presenta vertici appuntiti. Questi vertici corrispondono a situazioni in cui alcune differenze seconde del trend sono pari a zero.

    È proprio la presenza di angoli acuti nella norma L1 che porta a soluzioni sparse: la soluzione ottimale tende a trovarsi in un vertice del poliedro, dove sono attivi solo alcuni vincoli. Ciò significa che la maggior parte delle differenze seconde diventano zero e l'andamento assume automaticamente una forma lineare a tratti.

    La soluzione ottimale corrisponde al primo punto di contatto tra la sfera euclidea e il poliedro L1. A questo punto, il trend è costituito da segmenti lineari collegati in un numero limitato di punti di rottura.

    Il parametro λmax corrisponde alla situazione in cui la sfera euclidea tocca il poliedro L1 non in un vertice, ma lungo il sottospazio delle funzioni lineari. In questo caso, tutte le differenze seconde sono pari a zero e il trend è strettamente lineare.

    Per λ ≥ λmax, nessuno dei vincoli L1 diventa attivo, quindi ulteriori incrementi di regolarizzazione non modificano la soluzione e il trend rimane lineare.

    1.5. Algoritmo per il calcolo di λmax

    Consideriamo il calcolo del parametro di regolarizzazione massimo λmax per un vettore di input y di lunghezza N.

    1. Costruisci la matrice delle differenze seconde D di dimensione (N−2)×N:


    2. Calcola il vettore di curvatura Dy.

    3. Risolvi il sistema di equazioni lineari:

    4. Prendi l'elemento massimo (in valore assoluto) del vettore v:

    Per le serie temporali finanziarie, il parametro λmax riveste un'importante rilevanza pratica:

    • Consente la normalizzazione del parametro di regolarizzazione;
    • Rende la scelta di λ indipendente dalla scala dei dati;
    • Semplifica il confronto tra diverse serie temporali;
    • Ciò consente di interpretare λ come una frazione della regolarizzazione massima.

    L'utilizzo di un parametro relativo della forma: λ=coef_lambda_max⋅λmax⁡, dove coef_lambda_max ∈ (0,1), semplifica notevolmente l'applicazione pratica.

    Nei seguenti esempi di indicatori ed Expert Advisor, λ verrà utilizzato in unità di λmax, mentre le impostazioni del parametro specificheranno il moltiplicatore coef_lambda_max.


    2. Metodi MQL5 per il calcolo del trend L1

    Per l'utilizzo pratico del filtro di trend L1, vengono implementati due metodi per vettori di tipo double e float.

    • L1TrendFilterLambdaMax calcola il parametro di regolarizzazione massimo;
    • L1TrendFilter calcola il trend L1 per un dato valore del parametro di regolarizzazione λ, che può essere specificato anche in unità di λmax.

    2.1. L1TrendFilterLambdaMax

    Metodo per il calcolo del parametro di regolarizzazione massimo λmax per un vettore di dati.

    Calcolo per vector<double>:

    bool  vector::L1TrendFilterLambdaMax(
       double          &lambda_max       // the maximum value of the regularization parameter lambda
       )
    Calcolo per vector<float>:
    bool  vectorf::L1TrendFilterLambdaMax(
       float           &lambda_max       // the maximum value of the regularization parameter lambda
       );

    Parametri

    lambda

    [out] Il valore massimo del parametro di regolarizzazione λmax, oppure -1 in caso di errore.

    Valore restituito

    Restituisce true in caso di successo.

    Nota

    Il consumo di memoria aumenta linearmente con la dimensione del vettore.


    2.2. L1TrendFilter

    Metodo per il calcolo del trend L1 per un vettore di dati.

    Calcolo per vector<double>:

    bool  vector::L1TrendFilter(
       double          lambda,         // regularization parameter
       bool            relative,       // flag indicating lambda is in λmax units
       vector&         result          // output vector with L1 filtering result
       );

    Calcolo per vector<float>:

    bool  vectorf::L1TrendFilter(
       float           lambda,         // regularization parameter
       bool            relative,       // flag indicating lambda is in λmax units
       vectorf&        result          // output vector with L1 filtering result
       );

    Parametri

    lambda

    [in] Valore del parametro di regolarizzazione lambda (se relative = true, lambda è definito nell'intervallo [0, 1] come frazione di λmax).

    relative

    [in] Flag che indica come viene specificato λ. Se true, λ viene espresso in unità di λmax; altrimenti, viene utilizzato il valore assoluto.

    result

    [out] Vettore contenente il risultato del filtro L1.

    Valore restituito

    Restituisce true in caso di successo.

    Nota

    Il consumo di memoria aumenta linearmente con la dimensione del vettore.


    Intervalli consigliati per λ (modo relative).

    Moltiplicatore λ Risultato
    0.005 – 0.015 quasi L2, rumoroso
    0.02 – 0.04 microsegmenti
    0.04 – 0.07 ottimale per i segnali
    0.07 – 0.12 trend a medio termine
    0.12 – 0.25 regimi di mercato
    > 0.3 pochi segmenti

    Tabella 2. Intervalli di lavoro di λ in unità di λmax⁡


    Per le applicazioni pratiche, si consiglia di utilizzare moltiplicatori compresi tra 0.04 e 0.25.



    3. Esempi di applicazione

    In questa sezione, consideriamo i calcoli del trend L1 su dati simulati di moto browniano, su dati di prezzo dell'indice S&P 500, nonché le proprietà di scaling di λmax sia per il moto browniano che per i dati del mercato FOREX.

    Presentiamo inoltre tre varianti dell'indicatore che aiutano a determinare i parametri di regolarizzazione ottimali (moltiplicatori di λmax) per ottenere la migliore decomposizione del trend L1 per simboli e timeframe specifici.

    Inoltre, vengono presentati i risultati del filtraggio dei segnali di trading (allineamento con il trend L1) per le strategie MovingAverage, MACD, ADX ed EMA.


    3.1. Calcolo del trend L1 su dati simulati (Random Walk)

    Ad esempio, considerate il calcolo del trend L1 con diversi valori del parametro di regolarizzazione λ su dati simulati di moto browniano.

    Codice dello script:

    //+------------------------------------------------------------------+
    //|                                                  TestL1Trend.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                              http://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property script_show_inputs
    #include <Graphics\Graphic.mqh>
    //+------------------------------------------------------------------+
    //| Generate Brown movement data                                     |
    //+------------------------------------------------------------------+
    void BMData(vector<double> &data,int &data_count)
      {
       data.Resize(data_count);
       data[0] = 0.0;
       for(int i=1; i<data_count; i++)
          data[i] = data[i-1] + (MathRand()/32767.0 - 0.5);
      }
    //+------------------------------------------------------------------+
    //| CopyValues                                                       |
    //+------------------------------------------------------------------+
    bool CopyValues(vector<double> &data_v,double &data[])
      {
       int data_count=(int)data.Size();
       if(data_count==0)
          return(false);
       ArrayResize(data,data.Size());
       for(int i=0; i<data_count; i++)
          data[i]=data_v[i];
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
      {
       MathSrand(1);
       int data_count=1000;
       vector<double> data_test;
       BMData(data_test,data_count);
    //--- prepare arrays for chart
       double x[],y[];
       ArrayResize(x,data_count);
       ArrayResize(y,data_count);
       for(int i=0; i<data_count; i++)
          x[i]=i;
    //---
       CGraphic graphic;
       long chart=0;
       string name="test";
       if(ObjectFind(chart,name)<0)
          graphic.Create(chart,name,0,0,0,1000,600);
       else
          graphic.Attach(chart,name);
       graphic.BackgroundMain("L1 Trend filtering (random walk) with different lambda");
       graphic.BackgroundMainSize(16);
       graphic.HistoryNameWidth(60);
       graphic.HistoryColor(ColorToARGB(clrGray,255));
       graphic.XAxis().AutoScale(false);
       graphic.XAxis().Min(0);
       graphic.XAxis().Max(data_count);
    //---
       CopyValues(data_test,y);
       graphic.CurveAdd(x,y,CURVE_LINES,"Data").LinesWidth(1);
    //--- L1TrendFilterLambdaMax
       double lambda_max=0.0;
       if(data_test.L1TrendFilterLambdaMax(lambda_max))
          PrintFormat("lambda_max=%f",lambda_max);
    //---
       vector<double> data_l1;
       const double lambda_factors[]= {1.0,0.9,0.8,0.5,0.25,0.1,0.01,0.05,0.001,0.0005};
       for(int i=0; i<ArraySize(lambda_factors); i++)
         {
          double lambda=lambda_max*lambda_factors[i];
          PrintFormat("%d. lambda=%f",i+1,lambda);
          bool res=data_test.L1TrendFilter(lambda_factors[i],true,data_l1);
          if(res)
            {
             CopyValues(data_l1,y);
             graphic.CurveAdd(x,y,CURVE_LINES,"lambda="+DoubleToString(lambda,0)).LinesWidth(3);
            }
         }
    //---
       graphic.CurvePlotAll();
       graphic.Update();
       DebugBreak();
      }
    //+------------------------------------------------------------------+
    
    Output:
    TestL1Trend (EURUSD,H1) lambda_max=51703.353749
    TestL1Trend (EURUSD,H1) 1. lambda=51703.353749
    TestL1Trend (EURUSD,H1) 2. lambda=46533.018374
    TestL1Trend (EURUSD,H1) 3. lambda=41362.682999
    TestL1Trend (EURUSD,H1) 4. lambda=25851.676874
    TestL1Trend (EURUSD,H1) 5. lambda=12925.838437
    TestL1Trend (EURUSD,H1) 6. lambda=5170.335375
    TestL1Trend (EURUSD,H1) 7. lambda=517.033537
    TestL1Trend (EURUSD,H1) 8. lambda=2585.167687
    TestL1Trend (EURUSD,H1) 9. lambda=51.703354
    TestL1Trend (EURUSD,H1) 10. lambda=25.851677
    

    In questo esempio, si può notare che la diminuzione del parametro di regolarizzazione λ consente una decomposizione più dettagliata in segmenti di trend (Fig.1).

    Se λ ≥ λmax, la soluzione diventa una linea retta corrispondente alla regressione lineare (il trend globale).


    Fig.1. Esempio di calcolo del filtro L1 con diversi valori di λ sui dati del moto browniano

    Fig.1. Esempio di calcolo del filtro L1 con diversi valori di λ su dati di moto browniano


    Le funzioni per il calcolo del trend L1 sono disponibili sia per vettori double che float.

    Di seguito viene presentato lo script di prova per confrontare i calcoli.

    //+------------------------------------------------------------------+
    //|                                       TestL1TrendFloatDouble.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                              http://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    
    #include <Graphics\Graphic.mqh>
    
    uint32_t ExtSeed=1;
    //+------------------------------------------------------------------+
    //| Generate Brown movement data                                     |
    //+------------------------------------------------------------------+
    template<typename T>
    void BMData(vector<T> &data,uint64_t data_count)
      {
       MathSrand(ExtSeed);
    
       data.Resize(data_count);
       data[0] = 0.0;
    
       for(uint64_t i=1; i<data_count; i++)
          data[i] = data[i-1] + T(MathRand()/32767.0 - 0.5);
      }
    //+------------------------------------------------------------------+
    //| CopyValues                                                       |
    //+------------------------------------------------------------------+
    template<typename T>
    bool CopyValues(double &data[],const vector<T> &data_v)
      {
       if(ArrayResize(data,data.Size())!=data.Size())
          return(false);
       for(uint64_t i=0; i<data.Size(); i++)
          data[i]=data_v[i];
    
       return(true);
      }
    //+------------------------------------------------------------------+
    //| L1TrendCalculate                                                 |
    //+------------------------------------------------------------------+
    template<typename T>
    bool L1TrendCalculate(double &result[],uint64_t data_count,double lambda,bool lambda_is_relative)
      {
       vector<T> data_test;
       BMData(data_test,data_count);
    
       vector<T> vres;
       if(!data_test.L1TrendFilter((T)lambda,lambda_is_relative,vres))
          return(false);
       if(ArrayResize(result,(uint32_t)vres.Size())!=vres.Size())
          return(false);
       for(uint64_t n=0; n<result.Size(); n++)
          result[n]=vres[n];
    
       return(true);
      }
    //+------------------------------------------------------------------+
    //| TestRun                                                          |
    //+------------------------------------------------------------------+
    bool TestRun(uint32_t data_count,uint32_t mode)
      {
    //--- create graph
       CGraphic graphic;
       long     chart=0;
       string   name="L1TrendTest";
    
       if(ObjectFind(chart,name)<0)
          graphic.Create(chart,name,0,0,0,1280,600);
       else
          graphic.Attach(chart,name);
    
       string mode_name="(";
       if((mode&1)==1)
          mode_name+="DOUBLE";
       if((mode&3)==3)
          mode_name+=" & ";
       if((mode&2)==2)
          mode_name+="FLOAT";
       mode_name+=")";
    
       graphic.BackgroundMain("L1Trend filtering (random walk) with different lambda "+mode_name);
       graphic.BackgroundMainSize(16);
       graphic.HistoryNameWidth(60);
       graphic.HistoryColor(ColorToARGB(clrGray,255));
       graphic.XAxis().AutoScale(false);
       graphic.XAxis().Min(0);
       graphic.XAxis().Max(data_count);
    //--- prepare arrays
       double x[];
       double y[];
    
       if(ArrayResize(x,data_count)!=data_count)
          return(false);
      
       for(uint32_t i=0; i<data_count; i++)
          x[i]=i;
    
       vector<double> v;
       BMData(v,data_count);
       v.Swap(y);
       graphic.CurveAdd(x,y,CURVE_LINES,"Data").LinesWidth(1);
    //--- calculate
       const double lambda_factors[]= {1.0,0.9,0.8,0.5,0.25,0.1,0.01,0.05,0.001,0.0005};
    //--- double
       if((mode&1)==1)
         {
          for(uint64_t i=0; i<lambda_factors.Size(); i++)
            {
             if(L1TrendCalculate<double>(y,data_count,lambda_factors[i],true))
                graphic.CurveAdd(x,y,CURVE_LINES,"DBL="+DoubleToString(lambda_factors[i],4)).LinesWidth(4);
            }
         }
    //--- float
       if((mode&2)==2)
         {
          for(uint64_t i=0; i<lambda_factors.Size(); i++)
            {
             if(L1TrendCalculate<float>(y,data_count,(float)lambda_factors[i],true))
                graphic.CurveAdd(x,y,CURVE_LINES,"FLT="+DoubleToString(lambda_factors[i],4)).LinesWidth(2);
            }
         }
    //--- update
       graphic.CurvePlotAll();
       graphic.Update();
       return(true);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
      {
       for(uint32_t n=0; !IsStopped(); n++,Sleep(1000))
         {
          TestRun(1000,1+n%3);
    
          if((n%3)==2)
             ExtSeed++;
         }
      }
    //+------------------------------------------------------------------+

    Output:


    3.2. Calcolo del trend L1 per la serie storica dei prezzi dell'indice S&P 500

    Consideriamo il calcolo di log(S&P 500) dal documento originale l_1 Trend Filtering, SJ Kim, K. Koh, S. Boyd e D. Gorinevsky, SIAM Review, sezione problemi e tecniche, 51(2):339–360, maggio 2009. 

    Per eseguire lo script, vengono utilizzati i dati contenuti nel file "snp500.txt". Deve essere posizionato nella cartella: terminal_data_folder\MQL5\Files

    //+------------------------------------------------------------------+
    //|                                       TestL1TrendFilterSP500.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                              http://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property script_show_inputs
    #include <Graphics\Graphic.mqh>
    //+------------------------------------------------------------------+
    //| LoadData                                                         |
    //+------------------------------------------------------------------+
    void LoadData(string filename,vector<double> &data,int &data_count)
      {
       data_count=0;
       ResetLastError();
       int file_handle=FileOpen(filename,FILE_READ|FILE_TXT|FILE_ANSI);
       if(file_handle!=INVALID_HANDLE)
         {
          while(!FileIsEnding(file_handle))
            {
             string str=FileReadString(file_handle);
             if(data.Size()<=(ulong)data_count)
                data.Resize(data_count+1);
             data[data_count]=StringToDouble(str);
             data_count++;
            }
          FileClose(file_handle);
         }
       else
          PrintFormat("Failed to open %s file, Error code = %d",filename,GetLastError());
    //---
       data.Resize(data_count);
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
      {
       long chart=0;
       string name="log SP500";
       int data_count=0;
       vector<double> data_sp500;
       LoadData("snp500.txt",data_sp500,data_count);
       vector<double> data_l1_sp500;
       data_l1_sp500.Resize(data_count);
    //--- L1TrendFilterLambdaMax
       double lambda_max=0.0;
       if(data_sp500.L1TrendFilterLambdaMax(lambda_max))
          PrintFormat("Lambda_max=%f",lambda_max);
       double lambda=50;
    //--- L1TrendFilter
       if(data_sp500.L1TrendFilter(lambda,false,data_l1_sp500))
         {
          //--- prepare arrays for chart
          double x[],y[],y2[];
          ArrayResize(x,data_count);
          ArrayResize(y,data_count);
          ArrayResize(y2,data_count);
          for(int i=0; i<data_count; i++)
            {
             x[i]=i;
             y[i]=data_sp500[i];
             y2[i]=data_l1_sp500[i];
            }
          //---
          CGraphic graphic;
          if(ObjectFind(chart,name)<0)
             graphic.Create(chart,name,0,0,0,1000,600);
          else
             graphic.Attach(chart,name);
          graphic.BackgroundMain("log SP500 L1 trend filtering");
          graphic.BackgroundMainSize(16);
          graphic.HistoryNameWidth(60);
          graphic.HistoryColor(ColorToARGB(clrGray,255));
          graphic.XAxis().AutoScale(false);
          graphic.XAxis().Min(0);
          graphic.XAxis().Max(data_count);
          graphic.XAxis().DefaultStep(100);
          graphic.CurveAdd(x,y,CURVE_LINES,"SP500").LinesWidth(1);
          graphic.CurveAdd(x,y2,CURVE_LINES,"L1 trend").LinesWidth(3);
          graphic.CurvePlotAll();
          graphic.Update();
          DebugBreak();
         }
      }
    //+------------------------------------------------------------------+
    

    Il risultato dell'esecuzione dello script è mostrato in Figura 2.

    Fig. 2. Esempio di stima del trend L1 per la serie logaritmica dei prezzi dell'indice S&P 500

    Fig.2. Esempio di stima del trend L1 per la serie logaritmica dei prezzi dell'indice S&P 500


    Nella scheda Esperti, verrà visualizzato il valore di λmax per la serie temporale specificata:

    TestL1TrendFilterSP500 (EURUSD,H1)      Lambda_max=37394.835512
    

    Questo script dimostra l'utilizzo dei metodi L1TrendFilterLambdaMax e L1TrendFilter con un valore fisso λ = 50, come nel documento originale degli autori del metodo.

    Negli esempi seguenti, al posto dei valori assoluti del parametro di regolarizzazione λ, verranno utilizzati valori relativi (in unità di λmax) con il flag relative = true.


    3.3. Proprietà di scaling di λmax⁡

    Il parametro λmax gioca un ruolo chiave nel filtro L1, poiché definisce il limite superiore della regolarizzazione al raggiungimento del quale la soluzione degenera in un'approssimazione lineare globale. Una proprietà interessante di questa grandezza è la sua dipendenza di scala dalla lunghezza della serie temporale.

    Gli esperimenti numerici dimostrano che λmax cresce secondo una legge di potenza rispetto al numero di osservazioni:

    dove: T — lunghezza della serie temporale, α — esponente di scala.

    Per una random walk (moto browniano), si può dimostrare che l'esponente dovrebbe essere vicino ad α ≈ 2,5. L'ampiezza del moto browniano cresce come, mentre l'operatore di seconda differenza scala come.  Nel calcolo di λmax, si valuta di fatto il massimo di una quantità correlata all'integrale della curvatura della serie.


    Di conseguenza, la scalatura combinata porta alla seguente relazione:

    che corrisponde ad un esponente α ≈ 2.5.

    Pertanto, all'aumentare della lunghezza della serie temporale, il valore di λmax cresce in modo significativamente più rapido che lineare.


    3.3.1. Esperimento numerico per il moto browniano

    Per verificare la legge di scala, è stato condotto un esperimento numerico.

    Per diverse lunghezze delle serie temporali T, sono state generate realizzazioni del moto browniano, dopodiché è stato calcolato il valore medio di λmax.

    È stata utilizzata un'approssimazione logaritmica:

    che consente di stimare l'esponente α utilizzando la regressione lineare.

    Il codice per l'esperimento è riportato di seguito.

    //+------------------------------------------------------------------+
    //|                            TestScalingLambdaMaxBrownMovement.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                              http://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #include <Graphics\Graphic.mqh>
    //+------------------------------------------------------------------+
    //| Generate Brownian motion                                         |
    //+------------------------------------------------------------------+
    void GenerateBrownian(int N,vector<double> &data)
      {
       data.Resize(N);
       data[0] = 0.0;
       for(int i=1; i<N; i++)
          data[i] = data[i-1] + (MathRand()/32767.0 - 0.5);
      }
    //+------------------------------------------------------------------+
    //| LinearRegression                                                 |
    //+------------------------------------------------------------------+
    void LinearRegression(const double &x[], const double &y[], int n, double &a, double &b)
      {
       double sx = 0.0, sy = 0.0, sxx = 0.0, sxy = 0.0;
       for(int i = 0; i < n; i++)
         {
          sx  += x[i];
          sy  += y[i];
          sxx += x[i] * x[i];
          sxy += x[i] * y[i];
         }
       double denom = n * sxx - sx * sx;
       a = (n * sxy - sx * sy) / denom;
       b = (sy - a * sx) / n;
      }
    //+------------------------------------------------------------------+
    //| TestScaling with statistics                                      |
    //+------------------------------------------------------------------+
    void TestScalingStatistics()
      {
       MathSrand(42);
       int RUNS = 10;     //
       int MC   = 10;   // Monte Carlo
       double alpha_values[];
       ArrayResize(alpha_values, RUNS);
    // --- geometric grid of T
       int nT = 8;
       int Tvals[];
       ArrayResize(Tvals, nT);
       int T0 = 64;
       for(int i = 0; i < nT; i++)
          Tvals[i] = T0 << i;
       Print("Scaling test with statistics");
    //---
       double logT[];
       double logLambda[];
       vector<double> bm;
       vector<double> l1_trend;
       for(int run = 0; run < RUNS; run++)
         {
          ArrayResize(logT, nT);
          ArrayResize(logLambda, nT);
          //---
          for(int i = 0; i < nT; i++)
            {
             int T = Tvals[i];
             double lambda_sum = 0.0;
             l1_trend.Resize(T);
             for(int k = 0; k < MC; k++)
               {
                GenerateBrownian(T, bm);
                double lambda_max=0.0;
                if (bm.L1TrendFilterLambdaMax(lambda_max))
                   lambda_sum += lambda_max;
                bm.L1TrendFilter(0.2,true,l1_trend);
               }
             double lambda_avg = lambda_sum / MC;
             logT[i]      = MathLog((double)T);
             logLambda[i] = MathLog(lambda_avg);
            }
          // --- regression
          double alpha, c;
          LinearRegression(logT, logLambda, nT, alpha, c);
          alpha_values[run] = alpha;
          PrintFormat("run %d -> alpha = %.6f", run+1, alpha);
         }
    //--- statistics
       double mean = 0.0;
       for(int i=0;i<RUNS;i++)
          mean += alpha_values[i];
       mean /= RUNS;
    // --- standard deviation
       double var = 0.0;
       for(int i=0;i<RUNS;i++)
          var += (alpha_values[i]-mean)*(alpha_values[i]-mean);
       var /= (RUNS - 1);
       double stddev = MathSqrt(var);
    // --- standard error of mean
       double sem = stddev / MathSqrt((double)RUNS);
    // --- theoretical comparison
       double alpha_theory=2.5;
       double percent_error=MathAbs(mean-alpha_theory)/alpha_theory*100.0;
    //--- results
       PrintFormat("mean alpha = %.6f", mean);
       PrintFormat("std deviation = %.6f", stddev);
       PrintFormat("standard error = %.6f", sem);
       PrintFormat("theory = %.4f", alpha_theory);
       PrintFormat("percent error from theory = %.4f %%", percent_error);
      }
    //+------------------------------------------------------------------+
    //| TestScaling                                                      |
    //+------------------------------------------------------------------+
    void TestScaling()
      {
       MathSrand(1);
    // --- geometric grid of T
       int nT = 8;
       int Tvals[];
       ArrayResize(Tvals,nT);
    //---
       int T0 = 64;
       for(int i=0; i<nT; i++)
          Tvals[i]=T0<<i;   // 64 * 2^i
    //---
       double logT[], logLambda[];
       ArrayResize(logT,nT);
       ArrayResize(logLambda,nT);
    //---
       Print("scaling test for lambda_max");
       for(int i=0; i<nT; i++)
         {
          int T = Tvals[i];
          //--- Monte-Carlo simulations
          int MC=1000;
          double lambda_sum = 0.0;
          for(int k=0; k<MC; k++)
            {
             vector<double> bm;
             GenerateBrownian(T, bm);
             double lambda_max=0.0;
             if(bm.L1TrendFilterLambdaMax(lambda_max))
                lambda_sum += lambda_max;
            }
          double lambda_avg=lambda_sum/MC;
          logT[i]= MathLog((double)T);
          logLambda[i]=MathLog(lambda_avg);
          PrintFormat("T=%5d   <lambda_max>=%.6f",T,lambda_avg);
         }
    // --- linear regression in log-log
       double alpha, c;
       LinearRegression(logT,logLambda,nT,alpha,c);
    //---
       PrintFormat("estimated scaling exponent alpha = %.4f",alpha);
       double alpha_theory=2.5;
       PrintFormat("theoretical value = %.4f",alpha_theory);
    //--- plot scaling law
       CGraphic g;
       g.Create(0, "ScalingLaw",0,0,0,1000,600);
       g.BackgroundMain("Scaling law of lambda_max (Brownian motion)");
       g.BackgroundMainSize(16);
       g.CurveAdd(logT, logLambda, CURVE_POINTS, "Simulation");
    //---
       double xfit[2], yfit[2];
       xfit[0] = logT[0];
       xfit[1] = logT[nT-1];
    //---
       yfit[0] = alpha*xfit[0] + c;
       yfit[1] = alpha*xfit[1] + c;
    //---least squares fit
       g.CurveAdd(xfit, yfit, CURVE_LINES, "LS fit");
       g.CurvePlotAll();
       g.Update();
       DebugBreak();
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
      {
    //--- calculate scaling with statistics
       TestScalingStatistics();
    //--- show sample results
       TestScaling();
      }
    //+------------------------------------------------------------------+

    Output:

    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   Scaling test with statistics    
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 1 -> alpha = 2.480774       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 2 -> alpha = 2.530977       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 3 -> alpha = 2.435511       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 4 -> alpha = 2.461984       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 5 -> alpha = 2.467093       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 6 -> alpha = 2.487965       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 7 -> alpha = 2.532371       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 8 -> alpha = 2.455831       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 9 -> alpha = 2.483485       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   run 10 -> alpha = 2.420283      
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   mean alpha = 2.475627   
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   std deviation = 0.036281        
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   standard error = 0.011473       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   theory = 2.5000 
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   percent error from theory = 0.9749 %    
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   scaling test for lambda_max     
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   T=   64   <lambda_max>=97.302362        
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   T=  128   <lambda_max>=566.626861       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   T=  256   <lambda_max>=3162.076116      
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   T=  512   <lambda_max>=18271.204936     
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   T= 1024   <lambda_max>=100057.796790    
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   T= 2048   <lambda_max>=578620.887399    
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   T= 4096   <lambda_max>=3192555.936035   
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   T= 8192   <lambda_max>=17895314.647170  
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   estimated scaling exponent alpha = 2.4967       
    TestScalingLambdaMaxBrownMovement (EURUSD,H1)   theoretical value = 2.5000      
    

    Il grafico in doppia scala logaritmica mostra la presenza di una dipendenza di tipo legge di potenza della funzione λmax dal numero di punti dati per il moto browniano.
    .

    Figura 3. Dipendenza di legge di potenza di LambdaMax per il moto browniano

    Fig.3. Dipendenza di legge di potenza di LambdaMax per il moto browniano


    I risultati della simulazione mostrano:

    mean alpha = 2.4756
    std deviation = 0.036
    theory = 2.5
    percent error ≈ 1%

    Pertanto, l'esperimento conferma la relazione teorica:



    Il grafico a doppia scala logaritmica dimostra una relazione lineare tra log(λmax) e log(T).


    3.3.2. Scaling per serie temporali finanziarie

    Un esperimento simile è stato condotto per le serie storiche dei prezzi del mercato FOREX. Per diverse coppie di valute e timeframe, è stato stimato l'esponente α.

    I risultati mostrano che per i dati finanziari reali il valore di α si trova anch'esso nell'intervallo α ≈ 2.45–2.60, che è molto vicino al valore teorico per il moto browniano. Ciò significa che il comportamento di scaling di λmax è pressoché universale e si mantiene valido in diversi mercati e timeframes.

    Lo script TestScalingLambdaMaxSymbol.mq5 calcola l'esponente di λmax per un dato simbolo su timeframe standard M1–H1.

    //+------------------------------------------------------------------+
    //|                                   TestScalingLambdaMaxSymbol.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                              http://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property script_show_inputs
    //--- input parameters
    input string WorkSymbol   = "EURUSD";     // Symbol
    input int    YearStart    = 2024;
    input int    YearEnd      = 2025;
    #include <Graphics\Graphic.mqh>
    //+------------------------------------------------------------------+
    //| GetHistoricalData                                                |
    //+------------------------------------------------------------------+
    bool GetHistoricalData(double &data[], string symbol, ENUM_TIMEFRAMES tf, int year_start, int year_end)
      {
       datetime from = StringToTime(IntegerToString(year_start) + ".01.01 00:00");
       datetime to   = StringToTime(IntegerToString(year_end)   + ".12.31 23:59");
       int copied = CopyClose(symbol, tf, from, to, data);
       if(copied <= 0)
         {
          Print("Error in CopyClose: ", GetLastError());
          ArrayResize(data, 0);
          return false;
         }
    //PrintFormat("Loaded bars: %d (%s %s)", ArraySize(data), symbol, EnumToString(tf));
       return true;
      }
    //+------------------------------------------------------------------+
    //| LinearRegression                                                 |
    //+------------------------------------------------------------------+
    void LinearRegression(const double &x[], const double &y[], int n, double &a, double &b)
      {
       double sx = 0, sy = 0, sxx = 0, sxy = 0;
       for(int i = 0; i < n; i++)
         {
          sx  += x[i];
          sy  += y[i];
          sxx += x[i] * x[i];
          sxy += x[i] * y[i];
         }
       double denom = n*sxx - sx*sx;
       if(denom!=0)
         {
          a = (n*sxy-sx*sy)/denom;
          b = (sy-a*sx)/n;
         }
      }
    //+------------------------------------------------------------------+
    //| Scaling test for one timeframe                                   |
    //+------------------------------------------------------------------+
    bool TestScalingLambaMaxTF(string symbol, ENUM_TIMEFRAMES tf, double &logT_out[], double &logLambda_out[], double &alpha_out)
      {
       MathSrand(42);
       double prices[];
       if(!GetHistoricalData(prices, symbol, tf, YearStart, YearEnd))
          return false;
       int Tvals[];
       int nT=8;
       int T0=64;
       ArrayResize(Tvals, nT);
       for(int i = 0; i < nT; i++)
          Tvals[i] = T0 << i;
       ArrayResize(logT_out, nT);
       ArrayResize(logLambda_out, nT);
       int data_size = ArraySize(prices);
       vector<double> data_prices;
       for(int i = 0; i < nT; i++)
         {
          int T = Tvals[i];
          int MC = 1000;
          double lambda_sum = 0.0;
          for(int k = 0; k < MC; k++)
            {
             if(data_size < T)
                break;
             int start = MathRand() % (data_size - T);
             data_prices.Resize(T);
             for(int j=0; j<T;  j++)
                data_prices[j]=prices[start+j];
             double lambda_max=0.0;
             if(data_prices.L1TrendFilterLambdaMax(lambda_max))
                lambda_sum += lambda_max;
            }
          double lambda_avg = lambda_sum / MC;
          logT_out[i]=MathLog((double)T);
          logLambda_out[i]=MathLog(lambda_avg);
          //PrintFormat("TF=%s T=%5d   <lambda_max>=%.6f", EnumToString(tf), T, lambda_avg);
         }
       double c;
       LinearRegression(logT_out, logLambda_out, nT, alpha_out, c);
       PrintFormat("%s (%s) estimated scaling exponent = %.4f", symbol,EnumToString(tf), alpha_out);
       return true;
      }
    //+------------------------------------------------------------------+
    //| TestScalingLambdaMaxSymbol                                       |
    //+------------------------------------------------------------------+
    void TestScalingLambdaMaxSymbol(string symbol)
      {
       ENUM_TIMEFRAMES timeframes[] = {PERIOD_M1, PERIOD_M2, PERIOD_M3, PERIOD_M4, PERIOD_M5, PERIOD_M6,
                                       PERIOD_M10, PERIOD_M12, PERIOD_M15, PERIOD_M20, PERIOD_M30, PERIOD_H1
                                      };
       uint colors[] = {clrRed,clrBlue,clrGreen,clrOrange,clrPurple,clrDarkGreen,clrCyan,
                        clrNavy,clrOrangeRed,clrDodgerBlue,clrCrimson,clrDarkRed
                       };
    //---
       CGraphic g;
       g.Create(0,"ScalingLawTest",0,0,0,1000,600);
       g.BackgroundMain("Scaling law of lambda_max ("+symbol+")");
       g.BackgroundMainSize(16);
       PrintFormat("%s scaling test for standard timeframes",symbol);
       for(int i = 0; i < ArraySize(timeframes); i++)
         {
          double logT[], logLambda[], alpha;
          // Print("processing timeframe: ", EnumToString(timeframes[i]), " -----");
          if(TestScalingLambaMaxTF(symbol,timeframes[i],logT,logLambda,alpha))
            {
             g.CurveAdd(logT,logLambda,ColorToARGB(colors[i % ArraySize(colors)],255),CURVE_POINTS_AND_LINES,EnumToString(timeframes[i]));
            }
         }
       g.CurvePlotAll();
       g.Update();
    //---
       DebugBreak();
      }
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
      {
    //--- estimate lambda_max scale exponent for price data
       TestScalingLambdaMaxSymbol(WorkSymbol);
      }
    //+------------------------------------------------------------------+
    

    Risultati per EURUSD:

    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD scaling test for standard timeframes     
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M1) estimated scaling exponent = 2.5038  
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M2) estimated scaling exponent = 2.5350  
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M3) estimated scaling exponent = 2.5034  
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M4) estimated scaling exponent = 2.5422  
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M5) estimated scaling exponent = 2.5341  
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M6) estimated scaling exponent = 2.5132  
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M10) estimated scaling exponent = 2.5188 
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M12) estimated scaling exponent = 2.5126 
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M15) estimated scaling exponent = 2.5208 
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M20) estimated scaling exponent = 2.4887 
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_M30) estimated scaling exponent = 2.5695 
    TestScalingLambdMaxSymbol (EURUSD,H1)   EURUSD (PERIOD_H1) estimated scaling exponent = 2.6118  

    I risultati per EURUSD (timeframe standard M1–H1) sono mostrati nella Figura 4.

    Figura 4. Dipendenza di legge di potenza di λmax per i diversi timeframe EURUSD

    Fig.4. Dipendenza di legge di potenza di λmax per i diversi timeframe di EURUSD

    Analogamente, è possibile analizzare altre coppie di valute.

    Per USDJPY:

    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY scaling test for standard timeframes     
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M1) estimated scaling exponent = 2.5851  
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M2) estimated scaling exponent = 2.5825  
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M3) estimated scaling exponent = 2.4889  
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M4) estimated scaling exponent = 2.5099  
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M5) estimated scaling exponent = 2.5059  
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M6) estimated scaling exponent = 2.4939  
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M10) estimated scaling exponent = 2.5548 
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M12) estimated scaling exponent = 2.5641 
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M15) estimated scaling exponent = 2.5525 
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M20) estimated scaling exponent = 2.5390 
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_M30) estimated scaling exponent = 2.5805 
    TestScalingLambdMaxSymbol (EURUSD,H1)   USDJPY (PERIOD_H1) estimated scaling exponent = 2.4645  
    

    Anche i risultati per USDJPY sono ben approssimati da una relazione di legge di potenza.

    Fig.5. Dipendenza di legge di potenza di λmax per i diversi timeframe USDJPY

    Fig.5. Dipendenza di legge di potenza di λmax per i diversi timeframe USDJPY

    Per GBPUSD:

    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD scaling test for standard timeframes     
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M1) estimated scaling exponent = 2.5235  
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M2) estimated scaling exponent = 2.5449  
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M3) estimated scaling exponent = 2.5439  
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M4) estimated scaling exponent = 2.5427  
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M5) estimated scaling exponent = 2.5248  
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M6) estimated scaling exponent = 2.5308  
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M10) estimated scaling exponent = 2.5293 
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M12) estimated scaling exponent = 2.5235 
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M15) estimated scaling exponent = 2.5069 
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M20) estimated scaling exponent = 2.4977 
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_M30) estimated scaling exponent = 2.5659 
    TestScalingLambdMaxSymbol (EURUSD,H1)   GBPUSD (PERIOD_H1) estimated scaling exponent = 2.5524  

    Una situazione simile si osserva per la serie dei prezzi GBPUSD (Fig. 6).

    Figura 6. Dipendenza di legge di potenza di LambdaMax per i diversi timeframe GBPUSD

    Fig.6. Dipendenza di legge di potenza di λmax per i diversi timeframe GBPUSD.


    Per le serie EURUSD, USDJPY e GBPUSD considerate, i valori stimati dell'esponente sono anch'essi prossimi a 2.5.

    Le relazioni lineari in scala logaritmica per la funzione λmax su più timeframe e coppie di valute indicano una dipendenza di legge di potenza di λmax dal numero di osservazioni.


    3.3.3. Implicazioni pratiche della scalatura

    L'esistenza di una dipendenza di legge di potenza per λmax ha un'importante implicazione pratica.

    Poiché λmax ∝ T^2.5, il valore assoluto di λ dipende fortemente da:

    1. lunghezza della finestra dati,
    2. timeframe,
    3. la scala della serie temporale.

    Pertanto, in pratica, l'utilizzo di un valore assoluto di λ risulta scomodo.

    Un approccio molto più robusto consiste nell'utilizzare un parametro relativo λ=c⋅λmax, dove 0<c<1.

    Un approccio di questo tipo:

    • rende il parametro di regolarizzazione invariante di scala,
    • semplifica il trasferimento dei parametri tra diversi strumenti,
    • consente di utilizzare le stesse impostazioni in timeframe diversi.

    Per questo motivo, in tutti gli esempi successivi il parametro λ sarà specificato in unità di λmax.


    3.4. Indicatori di trend L1

    In questa sezione vengono presi in considerazione tre tipi di indicatori:

    • Calcolo del trend L1 basato sui prezzi di chiusura;
    • Calcolo dei coefficienti di crescita lineare (pendenza) del trend L1;
    • Calcolo del segno della pendenza del trend L1;
    Questi indicatori possono essere utilizzati per l'analisi visiva della decomposizione del trend e possono aiutare a identificare valori appropriati del parametro di regolarizzazione λ da utilizzare nelle strategie di trading.

    3.4.1. L1TrendFilter.mq5 - Indicatore di trend L1

    In questo esempio, il filtro L1 viene calcolato utilizzando i prezzi di chiusura per un numero specificato di barre (nell'esempio, BarsToShow = 1000) con il coefficiente lambda specificato in unità di λmax.

    Il calcolo utilizza la chiamata al metodo L1TrendFilter(relative = true), dove il parametro λ è definito in unità di λmax. I valori dell'indicatore vengono visualizzati direttamente nella finestra del grafico.

    Il codice dell'indicatore L1TrendFilter.mq5 è riportato di seguito.

    //+------------------------------------------------------------------+
    //|                                                L1TrendFilter.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property indicator_chart_window
    #property indicator_buffers 1
    #property indicator_plots   1
    //---
    #property indicator_label1  "L1TrendFilter"
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  clrDodgerBlue
    #property indicator_width1  2
    //---
    input int    BarsToShow = 1000;  // Number of bars to calculate L1
    input double CoefLambda = 0.015; // Lambda in lambda_max units
    //---
    double Trend[];
    //+------------------------------------------------------------------+
    //| Indicator initialization function                                |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       SetIndexBuffer(0,Trend,INDICATOR_DATA);
       ArrayInitialize(Trend,EMPTY_VALUE);
    //---
       PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
       IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
    //---
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| Indicator iteration function                                     |
    //+------------------------------------------------------------------+
    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[])
      {
    //--- check bars
       static bool warned=false;
       if(rates_total < BarsToShow)
         {
          if(!warned)
            {
             Print("Waiting for enough bars: ",BarsToShow);
             warned=true;
            }
          ArrayInitialize(Trend,EMPTY_VALUE);
          return(0);
         }
    //--- check new bar
       static datetime last_bar_time=0;
       bool new_bar=(time[0]!=last_bar_time);
       bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated);
       if(!need_recalc)
          return(prev_calculated);
       last_bar_time=time[0];
    //--- range
       int start=rates_total-BarsToShow;
    //--- hide old bars
       for(int i=0; i<start; i++)
          Trend[i]=EMPTY_VALUE;
    //---
       int data_count=BarsToShow;
    //--- copy Close
       vector<double> DataClose;
       DataClose.Resize(data_count);
       for(int i=0; i<data_count; i++)
          DataClose[i]=close[start+i];
    //--- lambda max
       double lambda_max=0.0;
       bool res=DataClose.L1TrendFilterLambdaMax(lambda_max);
       if(res)
         {
          PrintFormat("lambda_max=%f (%s,%s) Coef=%f lambda=%f",
                      lambda_max,Symbol(),EnumToString(Period()),CoefLambda,lambda_max*CoefLambda);
         }
    //--- L1 trend filtering
       vector<double> filtered_data;
       filtered_data.Resize(data_count);
       if(DataClose.L1TrendFilter(CoefLambda,true,filtered_data))
         {
          for(int i=0; i<data_count; i++)
             Trend[start+i]=filtered_data[i];
         }
    //---
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    

    Nella Figura 7 viene mostrato un esempio di calcolo dell'indicatore L1TrendFilter.mq5 con CoefLambda = 0.015.


    Fig.7. Esempio di calcolo dell'indicatore L1TrendFilter.mq5 con CoefLambda = 0.015

    Fig.7. Esempio di calcolo dell'indicatore L1TrendFilter.mq5 con CoefLambda = 0.015


    Per confronto, è possibile calcolare diverse varianti con parametri di regolarizzazione differenti.

    La Figura 8 mostra i calcoli con i parametri CoefLambda = 0.015, CoefLambda = 0.025 e CoefLambda = 0.055.


    Fig.8. Esempi di calcolo dell'indicatore L1TrendFilter.mq5 con i diversi valori di CoefLambda

    Fig.8. Esempi di calcolo dell'indicatore L1TrendFilter.mq5 con diversi valori di CoefLambda



    3.4.2. L1TrendFilterSlope.mq5 - indicatore della dinamica del trend L1

    Per visualizzare la pendenza del trend, è possibile utilizzare l'incremento dei valori dell'indicatore L1TrendFilter.

    Ad esempio, considerate l'indicatore L1TrendFilterSlope, che visualizza i valori in una finestra separata.

    Il codice dell'indicatore è riportato di seguito.

    //+------------------------------------------------------------------+
    //|                                           L1TrendFilterSlope.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property indicator_separate_window
    #property indicator_buffers 1
    #property indicator_plots   1
    //---
    #property indicator_label1  "L1TrendFilterSlope"
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  clrDodgerBlue
    #property indicator_width1  2
    //---
    input int    BarsToShow = 1000;  // Number of bars to calculate L1
    input double CoefLambda = 0.015; // Lambda in lambda_max units
    //---
    double Trend[];
    //+------------------------------------------------------------------+
    //| Indicator initialization function                                |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       SetIndexBuffer(0,Trend,INDICATOR_DATA);
       ArrayInitialize(Trend,EMPTY_VALUE);
    //---
       PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
       IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
    //---
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| Indicator iteration function                                     |
    //+------------------------------------------------------------------+
    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[])
      {
    //--- check bars
       static bool warned=false;
       if(rates_total < BarsToShow)
         {
          if(!warned)
            {
             Print("Waiting for enough bars: ",BarsToShow);
             warned=true;
            }
          ArrayInitialize(Trend,EMPTY_VALUE);
          return(0);
         }
    //--- check  new bar
       static datetime last_bar_time=0;
       bool new_bar=(time[0]!=last_bar_time);
       bool need_recalc= (prev_calculated==0) || new_bar || (rates_total!=prev_calculated);
       if(!need_recalc)
          return(prev_calculated);
       last_bar_time=time[0];
    //---
       int start=rates_total-BarsToShow;
       int data_count=BarsToShow;
    //--- hide old bars
       for(int i=0;i<start;i++)
          Trend[i]=EMPTY_VALUE;
    //--- copy Close
       vector<double> DataClose;
       DataClose.Resize(data_count);
       for(int i=0;i<data_count;i++)
          DataClose[i]=close[start+i];
    //--- lambda max
       double lambda_max=0.0;
       if(DataClose.L1TrendFilterLambdaMax(lambda_max))
         {
          PrintFormat("lambda_max=%f (%s,%s) Coef=%f lambda=%f",
                      lambda_max,Symbol(),EnumToString(Period()),CoefLambda,lambda_max*CoefLambda);
         }
    //--- L1 filtering
       vector<double> filtered_data;
       filtered_data.Resize(data_count);
       bool res=DataClose.L1TrendFilter(CoefLambda,true,filtered_data);
       if(res)
         {
          //--- slope (first difference)
          for(int i=1; i<data_count; i++)
            {
             double delta=filtered_data[i]-filtered_data[i-1];
             Trend[start+i]=delta;
            }
          //--- copy first element
          Trend[start]=Trend[start+1];
         }
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    

    Il risultato degli indicatori congiunti L1TrendFilter.mq5 e L1TrendFilterSlope.mq5 è mostrato in Figura 9.

    Fig.9. Esempio di calcolo degli indicatori L1TrendFilter.mq5 e L1TrendFilterSlope.mq5 con CoefLambda = 0.015

    Fig.9. Esempio di calcolo degli indicatori L1TrendFilter.mq5 e L1TrendFilterSlope.mq5 con CoefLambda = 0.015


    3.4.3. L1TrendFilterSlopeSign.mq5 - indicatore della direzione del trend L1

    Analogamente, è possibile calcolare un indicatore che visualizzi il segno dell'incremento dell'indicatore L1TrendFilterSlope.mq5.

    Codice dell'indicatore L1TrendFilterSlopeSign.mq5:

    //+------------------------------------------------------------------+
    //|                                       L1TrendFilterSlopeSign.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property indicator_separate_window
    #property indicator_buffers 1
    #property indicator_plots   1
    //---
    #property indicator_label1  "L1TrendFilterSlope"
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  clrDodgerBlue
    #property indicator_width1  2
    //---
    input int    BarsToShow = 1000;  // Number of bars to calculate L1
    input double CoefLambda = 0.015; // Lambda in lambda_max units
    //---
    double Trend[];
    //+------------------------------------------------------------------+
    //| Signum                                                           |
    //+------------------------------------------------------------------+
    double Signum(const double value)
      {
       return((value>0)-(value<0));
      }
    //+------------------------------------------------------------------+
    //| Indicator initialization function                                |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       SetIndexBuffer(0,Trend,INDICATOR_DATA);
       ArrayInitialize(Trend,EMPTY_VALUE);
    //---
       PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
       IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
    //---
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| Indicator iteration function                                     |
    //+------------------------------------------------------------------+
    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[])
      {
    //--- check bars
       static bool warned=false;
       if(rates_total < BarsToShow)
         {
          if(!warned)
            {
             Print("Waiting for enough bars: ",BarsToShow);
             warned=true;
            }
          ArrayInitialize(Trend,EMPTY_VALUE);
          return(0);
         }
    //--- check new bar
       static datetime last_bar_time=0;
       bool new_bar=(time[0]!=last_bar_time);
       bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated);
       if(!need_recalc)
          return(prev_calculated);
       last_bar_time=time[0];
    //---
       int start=rates_total-BarsToShow;
       int data_count=BarsToShow;
    //--- hide old bars
       for(int i=0; i<start; i++)
          Trend[i]=EMPTY_VALUE;
    //--- copy Close
       vector<double> DataClose;
       DataClose.Resize(data_count);
       for(int i=0; i<data_count; i++)
          DataClose[i]=close[start+i];
    //--- lambda max
       double lambda_max=0.0;
       bool res=DataClose.L1TrendFilterLambdaMax(lambda_max);
       if(res)
         {
          PrintFormat("lambda_max=%f (%s,%s) Coef=%f lambda=%f",
                      lambda_max,Symbol(),EnumToString(Period()),CoefLambda,lambda_max*CoefLambda);
         }
    //--- L1 filtering
       vector<double> filtered_data;
       filtered_data.Resize(data_count);
       res=DataClose.L1TrendFilter(CoefLambda,true,filtered_data);
       if(res)
         {
          Trend[start]=0;
          for(int i=1; i<data_count; i++)
            {
             double delta=filtered_data[i]-filtered_data[i-1];
             Trend[start+i]=Signum(delta);
            }
         }
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    

    Un esempio della combinazione di tutti e tre gli indicatori è mostrato in Figura 10 (è stato utilizzato lo stesso valore del coefficiente CoefLambda = 0.015).

    Fig.10. Esempio di calcolo degli indicatori L1TrendFilter.mq5, L1TrendFilterSlope.mq5 e L1TrendFilterSlopeSign.mq5 con CoefLambda = 0.015

    Fig.10. Esempio di calcolo degli indicatori L1TrendFilter.mq5, L1TrendFilterSlope.mq5 e L1TrendFilterSlopeSign.mq5 con CoefLambda = 0.015




    3.4.4. Indicatori di volatilità basati sul trend L1

    Questa sezione presenta indicatori progettati per valutare la volatilità di uno strumento finanziario sulla base del trend L1.

    Questi strumenti permettono di identificare periodi di instabilità e stabilità del mercato, analizzare le dinamiche di mercato attuali e prendere decisioni di trading più consapevoli.

    Gli indicatori considerati in questa sezione sono:

    • L1Volatility.mq5 — volatilità residua relativa al trend L1;
    • L1VolatilitySmoothed.mq5 — volatilità residua smussata;
    • L1VolatilityAbsolute.mq5 — volatilità assoluta;
    • L1VolatilityNormalized.mq5 — volatilità normalizzata;
    • L1VolatilityNormalizedSmoothed.mq5 — volatilità normalizzata smussata;
    • L1VolatilityRegime.mq5 — rilevamento del regime di mercato basato sulla volatilità.

    Tutti gli indicatori sono costruiti su un quadro di riferimento unificato del trend L1, che garantisce la coerenza analitica e semplifica l'interpretazione dei risultati ottenuti.

    L'utilizzo di questi indicatori consente l'identificazione visiva dei periodi di elevata e bassa volatilità, nonché la determinazione dell'attuale regime di mercato: fase laterale, trend, espansione o panico.

    Di conseguenza, un trader può adattare le strategie di trading per le condizioni di mercato prevalenti, ad esempio applicando approcci più conservativi durante i periodi di bassa volatilità o strategie più attive durante i periodi di forte movimento del mercato.


    3.4.4.1. L1Volatility.mq5 — Indicatore di volatilità L1

    L'indicatore calcola la volatilità residua come la differenza tra i prezzi di chiusura e il valore corrispondente del trend L1.

    Questo approccio consente di identificare i periodi di instabilità del mercato e i momenti precisi di ingresso o uscita.

    Visivamente, l'indicatore viene visualizzato in una finestra del grafico separata come una linea arancione.

    L'indicatore aiuta a:

    • Valutare le deviazioni del prezzo dal trend L1 e misurare la forza del movimento di mercato;
    • Individuare i picchi di volatilità locali per una gestione del rischio più accurata;
    • Confrontare le dinamiche di diversi strumenti nello stesso timeframe.

    Questo indicatore è particolarmente utile nei sistemi in cui è necessario monitorare le variazioni di volatilità a breve termine senza perdere di vista il contesto del trend generale.

    Il codice dell'indicatore L1Volatility.mq5 è riportato di seguito.

    //+------------------------------------------------------------------+
    //|                                                 L1Volatility.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property indicator_separate_window
    #property indicator_buffers 1
    #property indicator_plots   1
    //---
    #property indicator_label1  "L1Volatility"
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  clrOrangeRed
    #property indicator_width1  2
    //---
    input int    BarsToShow = 1000;  // Number of bars to calculate L1
    input double CoefLambda = 0.015; // Lambda in lambda_max units
    //---
    double Volatility[];
    //---
    //+------------------------------------------------------------------+
    //| Indicator initialization function                                |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       SetIndexBuffer(0,Volatility,INDICATOR_DATA);
       ArrayInitialize(Volatility,EMPTY_VALUE);
    //---
       PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
       IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
    //---
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| Indicator iteration function                                     |
    //+------------------------------------------------------------------+
    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[])
      {
    //--- check bars
       static bool warned=false;
       if(rates_total<BarsToShow)
         {
          if(!warned)
            {
             Print("Waiting for enough bars: ",BarsToShow);
             warned=true;
            }
          ArrayInitialize(Volatility,EMPTY_VALUE);
          return(0);
         }
    //--- check new bar
       static datetime last_bar_time=0;
       bool new_bar=(time[0]!=last_bar_time);
       bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated);
       if(!need_recalc)
          return(prev_calculated);
       last_bar_time=time[0];
    //---
       int start=rates_total-BarsToShow;
       int data_count=BarsToShow;
    //--- hide old bars
       for(int i=0;i<start;i++)
          Volatility[i]=EMPTY_VALUE;
    //--- copy Close
       vector<double> DataClose;
       DataClose.Resize(data_count);
       for(int i=0; i<data_count; i++)
          DataClose[i]=close[start+i];
    //--- lambda max
       double lambda_max=0.0;
       bool res=DataClose.L1TrendFilterLambdaMax(lambda_max);
       if(res)
         {
          PrintFormat("lambda_max=%f (%s,%s) Coef=%f lambda=%f",
                      lambda_max,Symbol(),EnumToString(Period()),CoefLambda,lambda_max*CoefLambda);
         }
    //--- L1 filter
       vector<double> filtered_data;
       filtered_data.Resize(data_count);
       res=DataClose.L1TrendFilter(CoefLambda,true,filtered_data);
       if(res)
         {
          for(int i=0; i<data_count; i++)
            {
             double residual=close[start+i]-filtered_data[i];
             Volatility[start+i]=residual;
            }
         }
    //---
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    

    Il risultato del calcolo è mostrato nella Figura 11.


    Figura 11. Indicatore L1Volatility.mq5

    Fig.11. Indicatore L1Volatility.mq5



    3.4.4.2. L1VolatilitySmoothed.mq5 — Indicatore di volatilità residua smussata

    Questo indicatore rappresenta una versione smussata di L1Volatility, in cui viene applicata una media mobile semplice (SMA).

    La smussatura consente:

    • Riduzione del rumore a breve termine e dei valori anomali;
    • Visualizzazione più chiara e interpretabile;
    • Concentrarsi sulle variazioni persistenti della volatilità.

    Questo indicatore è utile per le strategie che richiedono la valutazione dei trend di volatilità a lungo termine, ad esempio nei sistemi di trading adattivi o per filtrare i falsi segnali durante le fasi di mercato laterali e di trend.

    Il codice dell'indicatore L1VolatilitySmoothed.mq5 è riportato di seguito.

    //+------------------------------------------------------------------+
    //|                                         L1VolatilitySmoothed.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property indicator_separate_window
    #property indicator_buffers 1
    #property indicator_plots   1
    #property indicator_label1  "L1VolatilitySmoothed"
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  clrMediumVioletRed
    #property indicator_width1  2
    //---
    input int    BarsToShow = 1000;  // Number of bars to calculate L1
    input double CoefLambda = 0.015; // Lambda in lambda_max units
    input int    SmoothPeriod = 10;  // Smooth period
    //---
    double VolSmoothed[];
    //+------------------------------------------------------------------+
    //| Indicator initialization function                                |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       SetIndexBuffer(0, VolSmoothed, INDICATOR_DATA);
       ArrayInitialize(VolSmoothed, EMPTY_VALUE);
       PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE);
       IndicatorSetInteger(INDICATOR_DIGITS, _Digits);
    //---
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| Indicator iteration function                                     |
    //+------------------------------------------------------------------+
    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[])
      {
       if(rates_total<BarsToShow)
         {
          ArrayInitialize(VolSmoothed,EMPTY_VALUE);
          return(0);
         }
    //--- recalc only on new bar
       static datetime last_bar_time = 0;
       if(time[0] == last_bar_time && prev_calculated > 0)
          return(prev_calculated);
       last_bar_time=time[0];
    //---
       int start=rates_total-BarsToShow;
       for(int i=0; i<start; i++)
          VolSmoothed[i]=EMPTY_VALUE;
    //--- copy close prices
       vector<double> price(BarsToShow);
       for(int i=0; i<BarsToShow; i++)
          price[i] = close[start+i];
       vector<double> l1(BarsToShow);
       bool res=price.L1TrendFilter(CoefLambda,true,l1);
       if(res)
         {
          //--- calculate raw volatility
          vector<double> rawVol(BarsToShow);
          for(int i=0; i<BarsToShow; i++)
             rawVol[i]=close[start+i]-l1[i];
          //--- apply simple moving average smoothing
          for(int i=0; i<BarsToShow; i++)
            {
             double sum = 0.0;
             int count = 0;
             for(int j=MathMax(0,i-SmoothPeriod+1); j<=i; j++)
               {
                sum+=rawVol[j];
                count++;
               }
             VolSmoothed[start+i]=sum/count;
            }
         }
    //---
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    

    La Figura 12 mostra gli indicatori L1Volatility.mq5 e L1VolatilitySmoothed.mq5.

    Figura 12. Indicatori L1Volatility.mq5 e L1VolatilitySmoothed.mq5

    Fig.12. Indicatori L1Volatility.mq5 e L1VolatilitySmoothed.mq5



    3.4.4.3. L1VolatilityAbsolute.mq5 — Indicatore di volatilità assoluta

    L'indicatore calcola il valore assoluto della differenza tra i prezzi di chiusura e il trend L1.

    Caratteristiche e applicazioni:

    • Ignora la direzione del movimento e valuta solo l'ampiezza della fluttuazione;
    • Utile per analizzare l'ampiezza delle oscillazioni del prezzo indipendentemente dalla direzione del trend;
    • Utile per sistemi basati su statistiche di valori estremi e analisi del rischio.

    La volatilità assoluta riflette la reale entità delle deviazioni di prezzo, consentendo al trader di osservare la forza del movimento di mercato senza essere distratto dalla sua direzione.

    Il codice dell'indicatore L1VolatilityAbsolute.mq5 è riportato di seguito.

    //+------------------------------------------------------------------+
    //|                                         L1VolatilityAbsolute.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property indicator_separate_window
    #property indicator_buffers 1
    #property indicator_plots   1
    //---
    #property indicator_label1  "L1VolatilityAbsolute"
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  clrOrange
    #property indicator_width1  2
    //---
    input int    BarsToShow = 1000;  // Number of bars to calculate L1
    input double CoefLambda = 0.015; // Lambda in lambda_max units
    //---
    double Vol[];
    //+------------------------------------------------------------------+
    //| Indicator initialization function                                |
    //+------------------------------------------------------------------+
    int OnInit()
      {
       SetIndexBuffer(0,Vol,INDICATOR_DATA);
       ArrayInitialize(Vol,EMPTY_VALUE);
       PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
       IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
    //---
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| Indicator iteration function                                     |
    //+------------------------------------------------------------------+
    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[])
      {
    //---
       static bool warned=false;
       if(rates_total < BarsToShow)
         {
          if(!warned)
            {
             Print("Waiting bars ",BarsToShow);
             warned=true;
            }
          ArrayInitialize(Vol,EMPTY_VALUE);
          return(0);
         }
       static datetime last_bar=0;
       bool new_bar=(time[0]!=last_bar);
    //---
       if(!(prev_calculated==0 || new_bar || rates_total!=prev_calculated))
          return(prev_calculated);
    //---
       last_bar=time[0];
       int start=rates_total-BarsToShow;
       int N=BarsToShow;
       for(int i=0; i<start; i++)
          Vol[i]=EMPTY_VALUE;
    //---
       vector<double> price;
       price.Resize(N);
       for(int i=0; i<N; i++)
          price[i]=close[start+i];
       vector<double> l1;
       l1.Resize(N);
       bool res=price.L1TrendFilter(CoefLambda,true,l1);
       if(res)
         {
          for(int i=0; i<N; i++)
             Vol[start+i]=MathAbs(close[start+i]-l1[i]);
         }
    //---
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    

    Un esempio del calcolo dell'indicatore è mostrato nella Figura 13.


    Figura 13. Indicatore L1VolatilityAbsolute.mq5

    Fig.13. Indicatore L1VolatilityAbsolute.mq5


    3.4.4.4. L1VolatilityNormalized.mq5 — Indicatore di volatilità normalizzata

    L'indicatore normalizza la volatilità utilizzando l'ATR (Average True Range) insieme al trend L1.

    Calcola il rapporto tra la deviazione assoluta del prezzo dal trend e l'intervallo di prezzo medio nel periodo ATR. La normalizzazione elimina la dipendenza dalla scala dei prezzi, consentendo il confronto tra diversi strumenti e timeframe.

    Le applicazioni includono:

    • Identificazione dei movimenti di mercato relativamente forti e deboli;
    • Confronto della volatilità tra diversi asset;
    • Valutazione delle condizioni di mercato indipendentemente dal livello dei prezzi.

    Il codice dell'indicatore L1VolatilityNormalized.mq5 è riportato di seguito.

    //+------------------------------------------------------------------+
    //|                                       L1VolatilityNormalized.mq5 |
    //|                             Copyright 2000-2026, MetaQuotes Ltd. |
    //|                                             https://www.mql5.com |
    //+------------------------------------------------------------------+
    #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
    #property link      "https://www.mql5.com"
    #property version   "1.00"
    #property indicator_separate_window
    #property indicator_buffers 1
    #property indicator_plots   1
    #property indicator_label1  "L1VolatilityNormalized"
    #property indicator_type1   DRAW_LINE
    #property indicator_color1  clrDodgerBlue
    #property indicator_width1  2
    //---
    input int    BarsToShow = 1000;  // Number of bars to calculate L1
    input double CoefLambda = 0.015; // Lambda in lambda_max units
    //---
    double VolNormalized[];
    //---
    //+------------------------------------------------------------------+
    //| Indicator initialization function                                |
    //+------------------------------------------------------------------+
    int OnInit()
      {
    //--- prepare
       SetIndexBuffer(0, VolNormalized,INDICATOR_DATA);
       ArrayInitialize(VolNormalized,EMPTY_VALUE);
       PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
       IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
    //---
       return(INIT_SUCCEEDED);
      }
    //+------------------------------------------------------------------+
    //| Indicator iteration function                                     |
    //+------------------------------------------------------------------+
    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[])
      {
    //--- check bars
       static bool warned=false;
       if(rates_total<BarsToShow)
         {
          if(!warned)
            {
             Print("Waiting for enough bars: ",BarsToShow);
             warned=true;
            }
          ArrayInitialize(VolNormalized,EMPTY_VALUE);
          return(0);
         }
    //--- check new bar
       static datetime last_bar_time=0;
       bool new_bar=(time[0]!=last_bar_time);
       bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated);
       if(!need_recalc)
          return(prev_calculated);
       last_bar_time=time[0];
       int start=rates_total-BarsToShow;
    //---
       for(int i=0; i<start; i++)
          VolNormalized[i]=EMPTY_VALUE;
    //--- copy close prices
       vector<double> price(BarsToShow);
       for(int i=0; i<BarsToShow; i++)
          price[i]=close[start+i];
    //---
       vector<double> l1(BarsToShow);
       bool res=price.L1TrendFilter(CoefLambda,true,l1);
       if(res)
         {
          //--- compute normalized volatility
          double mean=0.0;
          double stddev=0.0;
          for(int i=0; i<BarsToShow; i++)
             mean+=close[start+i]-l1[i];
          mean/=BarsToShow;
          //---
          for(int i=0; i<BarsToShow; i++)
             stddev+=MathPow(close[start+i]-l1[i]-mean,2);
          stddev=MathSqrt(stddev/BarsToShow);
          //---
          for(int i=0; i<BarsToShow; i++)
             VolNormalized[start+i]=stddev>0?(close[start+i]-l1[i])/stddev:0;
         }
    //---
       return(rates_total);
      }
    //+------------------------------------------------------------------+
    

    Il risultato del calcolo è mostrato nella Figura 14.

    Figura 14. Indicatore L1VolatilityNormalized.mq5

    Fig.14. Indicatore L1VolatilityNormalized.mq5


      3.4.4.5. L1VolatilityNormalizedSmoothed.mq5 — Indicatore di volatilità normalizzata e smussata

      Questo indicatore estende l'approccio di normalizzazione aggiungendo la smussatura tramite media mobile esponenziale (EMA).

      Vantaggi:

      • Riduce l'influenza del rumore a breve termine e dei picchi improvvisi;
      • Produce un profilo di volatilità più chiaro e interpretabile;
      • Aiuta a valutare la volatilità persistente e l'attuale regime di mercato.

      Questo indicatore è particolarmente utile per le strategie adattive che richiedono una stima stabile della volatilità, ad esempio nella selezione automatica delle modalità di trading.

      Il codice dell'indicatore L1VolatilityNormalizedSmoothed.mq5 è riportato di seguito.

      //+------------------------------------------------------------------+
      //|                               L1VolatilityNormalizedSmoothed.mq5 |
      //|                             Copyright 2000-2026, MetaQuotes Ltd. |
      //|                                             https://www.mql5.com |
      //+------------------------------------------------------------------+
      #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
      #property link      "https://www.mql5.com"
      #property version   "1.00"
      #property indicator_separate_window
      #property indicator_buffers 1
      #property indicator_plots   1
      
      #property indicator_label1  "L1VolatilityNormalizedSmoothed"
      #property indicator_type1   DRAW_LINE
      #property indicator_color1  clrDeepSkyBlue
      #property indicator_width1  2
      //---
      input int    BarsToShow   = 1000;  // Number of bars to calculate L1
      input double CoefLambda   = 0.015; // Lambda in lambda_max units
      input int    SmoothPeriod = 10;    // EMA smoothing period (1=no smoothing)
      //---
      double NormVolSmooth[];
      //+------------------------------------------------------------------+
      //| Indicator initialization function                                |
      //+------------------------------------------------------------------+
      int OnInit()
        {
      //--- prepare
         SetIndexBuffer(0,NormVolSmooth,INDICATOR_DATA);
         ArrayInitialize(NormVolSmooth,EMPTY_VALUE);
         PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
         IndicatorSetInteger(INDICATOR_DIGITS,_Digits);
      //---
         return(INIT_SUCCEEDED);
        }
      //+------------------------------------------------------------------+
      //| Indicator iteration function                                     |
      //+------------------------------------------------------------------+
      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[])
        {
      //--- check bars
         static bool warned=false;
         if(rates_total < BarsToShow)
           {
            if(!warned)
              {
               Print("Waiting for enough bars: ",BarsToShow);
               warned=true;
              }
            ArrayInitialize(NormVolSmooth,EMPTY_VALUE);
            return(0);
           }
      //--- check  new bar
         static datetime last_bar_time=0;
         bool new_bar=(time[0]!=last_bar_time);
         bool need_recalc= (prev_calculated==0) || new_bar || (rates_total!=prev_calculated);
         if(!need_recalc)
            return(prev_calculated);
         last_bar_time=time[0];
         int start=rates_total-BarsToShow;
      //---
         for(int i=0; i<start; i++)
            NormVolSmooth[i]=EMPTY_VALUE;
      //--- copy close prices
         vector<double> price(BarsToShow);
         for(int i=0; i<BarsToShow; i++)
            price[i]=close[start+i];
      //---
         vector<double> l1(BarsToShow);
         bool res=price.L1TrendFilter(CoefLambda,true,l1);
         if(res)
           {
            //--- compute normalized volatility
            vector<double> VolNormalized(BarsToShow);
            double mean = 0, stddev = 0;
            for(int i=0; i<BarsToShow; i++)
               mean += close[start+i]-l1[i];
            mean /= BarsToShow;
            //---
            for(int i=0; i<BarsToShow; i++)
               stddev += MathPow(close[start+i]-l1[i]-mean,2);
            stddev = MathSqrt(stddev/BarsToShow);
            //---
            for(int i=0; i<BarsToShow; i++)
               VolNormalized[i]=stddev>0 ? (close[start+i]-l1[i])/stddev: 0;
            //--- EMA smoothing
            vector<double> Smooth(BarsToShow);
            double alpha=(SmoothPeriod<=1) ? 1.0: 2.0/(SmoothPeriod+1.0);
            //---
            Smooth[0] = VolNormalized[0];
            for(int i=1; i<BarsToShow; i++)
               Smooth[i]=alpha*VolNormalized[i]+(1.0-alpha)*Smooth[i-1];
            //--- copy to indicator buffer
            for(int i=0; i<BarsToShow; i++)
               NormVolSmooth[start+i]=Smooth[i];
           }
      //---
         return(rates_total);
        }
      //+------------------------------------------------------------------+
      

      Il risultato del calcolo è mostrato nella Figura 15.

      Figura 15. Indicatori L1VolatilityNormalized.mq5 e L1VolatilityNormalizedSmoothed.mq5

      Fig.15. Indicatori L1VolatilityNormalized.mq5 e L1VolatilityNormalizedSmoothed.mq5



      3.4.4.6. L1VolatilityRegime.mq5 — Indicatore di rilevamento del regime di mercato

      L'indicatore classifica l'attuale regime di mercato in base alla volatilità normalizzata e smussata, identificando quattro stati di mercato.

      Caratteristiche dell'indicatore:

      • Completamente autonomo e non richiede dati esterni;
      • Fornisce una chiara visualizzazione delle dinamiche di mercato per l'adozione di strategie adattive;
      • I parametri di soglia LowVolThresh e HighVolThresh possono essere regolati per diversi strumenti e timeframe.
      Valore Regime Descrizione
      0 Range
      Bassa volatilità, mercato laterale
      1 Trend
      Volatilità moderata, presenza di un trend
      2 Espansione
      Forte movimento, espansione del mercato
      3 Panico Volatilità estrema, movimenti bruschi

      Tabella 3. Regimi dell'indicatore L1VolatilityRegime.mq5

      • Determina rapidamente l'attuale regime di mercato;
      • Adatta le strategie di trading alle condizioni prevalenti;
      • Riduce il rischio durante le oscillazioni estreme e migliora l'efficienza delle operazioni di trading.

      Il codice dell'indicatore L1VolatilityRegime.mq5 è riportato di seguito.

      //+------------------------------------------------------------------+
      //|                                           L1VolatilityRegime.mq5 |
      //|                             Copyright 2000-2026, MetaQuotes Ltd. |
      //|                                             https://www.mql5.com |
      //+------------------------------------------------------------------+
      #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
      #property link      "https://www.mql5.com"
      #property version   "1.00"
      #property indicator_separate_window
      #property indicator_buffers 1
      #property indicator_plots   1
      //---
      #property indicator_label1  "L1 Volatility Regime"
      #property indicator_type1   DRAW_LINE
      #property indicator_color1  clrRoyalBlue
      #property indicator_width1  2
      //--- input parameters
      input int    BarsToShow    = 1000;  // Number of bars to calculate L1
      input double CoefLambda    = 0.015; // Lambda in lambda_max units
      input int    ATRPeriod     = 14;    // ATR period
      input int    SmoothPeriod  = 10;    // Smooth period
      input double L1MoveThresh  = 0.0;   // Move volatility
      input double LowVolThresh  = 0.5;   // Low volatility
      input double HighVolThresh = 1.5;   // High volatility
      input double PanicMult     = 2.0;   // Panic volatility
      //---
      double Regime[];
      //+------------------------------------------------------------------+
      //| Indicator initialization function                                |
      //+------------------------------------------------------------------+
      int OnInit()
        {
         SetIndexBuffer(0, Regime, INDICATOR_DATA);
         PlotIndexSetDouble(0,PLOT_EMPTY_VALUE, EMPTY_VALUE);
         IndicatorSetInteger(INDICATOR_DIGITS, 0);
      //---
         return(INIT_SUCCEEDED);
        }
      //+------------------------------------------------------------------+
      //| Indicator iteration function                                     |
      //+------------------------------------------------------------------+
      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[])
        {
      //--- check bars
         if(rates_total<BarsToShow+ATRPeriod)
           {
            ArrayInitialize(Regime,EMPTY_VALUE);
            return(0);
           }
      //--- check new bar
         static datetime last_bar_time=0;
         bool new_bar=(time[0]!=last_bar_time);
         bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated);
         if(!need_recalc)
            return(prev_calculated);
         last_bar_time=time[0];
      //---
         int start=rates_total-BarsToShow;
         int count=BarsToShow;
      //---
         for(int i=0; i<start; i++)
            Regime[i]=EMPTY_VALUE;
      //---
         vector<double> DataClose(count),DataHigh(count),DataLow(count);
         for(int i=0; i<count; i++)
           {
            DataClose[i]=close[start+i];
            DataHigh[i]=high[start+i];
            DataLow[i]=low[start+i];
           }
      //---
         vector<double> L1(count);
         bool res=DataClose.L1TrendFilter(CoefLambda,true,L1);
         if(!res)
            return(prev_calculated);
      //---
         vector<double> TR(count),ATR(count);
         for(int i=0; i<count; i++)
           {
            if(i==0)
               TR[i]=DataHigh[i]-DataLow[i];
            else
              {
               double h_l=DataHigh[i]-DataLow[i];
               double h_pc=MathAbs(DataHigh[i]-DataClose[i-1]);
               double l_pc=MathAbs(DataLow[i]-DataClose[i-1]);
               TR[i]=MathMax(h_l,MathMax(h_pc,l_pc));
              }
            int from=MathMax(0,i-ATRPeriod+1);
            double sumTR=0.0;
            int n = i-from+1;
            for(int j=from; j<=i;j++)
               sumTR += TR[j];
            ATR[i]=sumTR/n;
           }
      //---
         vector<double> NormVol(count), SmoothVol(count);
         for(int i=0; i<count; i++)
            NormVol[i]=(ATR[i]>0) ? MathAbs(DataClose[i]-L1[i])/ATR[i] : 0;
         double alpha=2.0/(SmoothPeriod+1.0);
         SmoothVol[0]=NormVol[0];
         for(int i=1; i<count; i++)
            SmoothVol[i]=alpha*NormVol[i]+(1.0-alpha)*SmoothVol[i-1];
      //---
         for(int i=0; i<count; i++)
           {
            double vol=SmoothVol[i];
            double deltaL1=(i>0) ? (L1[i]-L1[i-1]): 0.0;
            if(vol<LowVolThresh)
               Regime[start+i]=0; // Range
            else
               if(vol>=LowVolThresh && vol<HighVolThresh)
                  Regime[start+i]=(MathAbs(deltaL1)>L1MoveThresh) ? 1:0; // Trend/Range
               else
                  if(vol>=HighVolThresh && vol<HighVolThresh*PanicMult)
                     Regime[start+i]=2; // Expansion
                  else
                     Regime[start+i]=3; // Panic
           }
      //---
         return(rates_total);
        }
      //+------------------------------------------------------------------+
      

      Il risultato del calcolo è mostrato nella Figura 16.

      Figura 16. Indicatore L1VolatilityRegime.mq5

      Fig.16. Indicatore L1VolatilityRegime.mq5

        Per maggiore comodità, è possibile utilizzare anche una versione con visualizzazione del regime tramite codifica a colori.

        Il codice dell'indicatore L1VolatilityRegimeColor.mq5 è riportato di seguito.

        //+------------------------------------------------------------------+
        //|                                      L1VolatilityRegimeColor.mq5 |
        //|                             Copyright 2000-2026, MetaQuotes Ltd. |
        //|                                             https://www.mql5.com |
        //+------------------------------------------------------------------+
        #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
        #property link      "https://www.mql5.com"
        #property version   "1.00"
        #property indicator_separate_window
        #property indicator_buffers 4
        #property indicator_plots   4
        //---
        #property indicator_label1  "Range"
        #property indicator_type1   DRAW_LINE
        #property indicator_color1  clrDodgerBlue
        #property indicator_width1  2
        //---
        #property indicator_label2  "Trend"
        #property indicator_type2   DRAW_LINE
        #property indicator_color2  clrLime
        #property indicator_width2  2
        //---
        #property indicator_label3  "Expansion"
        #property indicator_type3   DRAW_LINE
        #property indicator_color3  clrOrange
        #property indicator_width3  2
        //---
        #property indicator_label4  "Panic"
        #property indicator_type4   DRAW_LINE
        #property indicator_color4  clrRed
        #property indicator_width4  2
        //--- input parameters
        input int    BarsToShow    = 1000;  // Number of bars to calculate L1
        input double CoefLambda    = 0.015; // Lambda in lambda_max units
        input int    ATRPeriod     = 14;    // ATR period
        input int    SmoothPeriod  = 10;    // Smooth period
        input double L1MoveThresh  = 0.0;   // Move volatility
        input double LowVolThresh  = 0.5;   // Low volatility
        input double HighVolThresh = 1.5;   // High volatility
        input double PanicMult     = 2.0;   // Panic volatility
        //--- buffers
        double Regime[];
        double BufRange[], BufTrend[], BufExpansion[], BufPanic[];
        //+------------------------------------------------------------------+
        //| Indicator initialization function                                |
        //+------------------------------------------------------------------+
        int OnInit()
          {
           SetIndexBuffer(0,BufRange,INDICATOR_DATA);
           SetIndexBuffer(1,BufTrend,INDICATOR_DATA);
           SetIndexBuffer(2,BufExpansion,INDICATOR_DATA);
           SetIndexBuffer(3,BufPanic,INDICATOR_DATA);
        //---
           PlotIndexSetDouble(0,PLOT_EMPTY_VALUE,EMPTY_VALUE);
           PlotIndexSetDouble(1,PLOT_EMPTY_VALUE,EMPTY_VALUE);
           PlotIndexSetDouble(2,PLOT_EMPTY_VALUE,EMPTY_VALUE);
           PlotIndexSetDouble(3,PLOT_EMPTY_VALUE,EMPTY_VALUE);
        //---
           IndicatorSetInteger(INDICATOR_DIGITS,0);
           return(INIT_SUCCEEDED);
          }
        //+------------------------------------------------------------------+
        //| Indicator iteration function                                     |
        //+------------------------------------------------------------------+
        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[])
          {
           if(rates_total<BarsToShow+ATRPeriod)
             {
              ArrayInitialize(Regime,EMPTY_VALUE);
              ArrayInitialize(BufRange,EMPTY_VALUE);
              ArrayInitialize(BufTrend,EMPTY_VALUE);
              ArrayInitialize(BufExpansion,EMPTY_VALUE);
              ArrayInitialize(BufPanic,EMPTY_VALUE);
              return(0);
             }
        //--- new bars
           static datetime last_bar_time=0;
           bool new_bar=(time[0]!=last_bar_time);
           bool need_recalc=(prev_calculated==0) || new_bar || (rates_total!=prev_calculated);
           if(!need_recalc)
              return(prev_calculated);
           last_bar_time=time[0];
        //---
           int start=rates_total-BarsToShow;
           int count=BarsToShow;
        //---
           ArrayResize(Regime,rates_total);
           for(int i=0;i<start;i++)
              Regime[i]=EMPTY_VALUE;
        //---
           vector<double> DataClose(count),DataHigh(count),DataLow(count);
           for(int i=0; i<count; i++)
             {
              DataClose[i]=close[start+i];
              DataHigh[i]=high[start+i];
              DataLow[i]=low[start+i];
             }
        //---
           vector<double> L1(count);
           bool res=DataClose.L1TrendFilter(CoefLambda,true,L1);
           if(!res)
              return(prev_calculated);
        //---
           vector<double> TR(count),ATR(count);
           for(int i=0; i<count; i++)
             {
              if(i==0)
                 TR[i]=DataHigh[i]-DataLow[i];
              else
                {
                 double h_l = DataHigh[i]-DataLow[i];
                 double h_pc = MathAbs(DataHigh[i]-DataClose[i-1]);
                 double l_pc = MathAbs(DataLow[i]-DataClose[i-1]);
                 TR[i] = MathMax(h_l, MathMax(h_pc, l_pc));
                }
              int from=MathMax(0,i-ATRPeriod+1);
              double sumTR=0;
              int n=i-from+1;
              for(int j=from; j<=i; j++)
                 sumTR+=TR[j];
              ATR[i]=sumTR/n;
             }
        //---
           vector<double> NormVol(count), SmoothVol(count);
           for(int i=0;i<count;i++)
              NormVol[i]=(ATR[i]>0) ? MathAbs(DataClose[i]-L1[i])/ATR[i] : 0;
        //---
           double alpha=2.0/(SmoothPeriod+1.0);
           SmoothVol[0]=NormVol[0];
           for(int i=1; i<count; i++)
              SmoothVol[i]=alpha*NormVol[i]+(1.0-alpha)*SmoothVol[i-1];
        //--- calc Regime[]
           for(int i=0; i<count; i++)
             {
              double vol=SmoothVol[i];
              double deltaL1=(i>0) ? (L1[i]-L1[i-1]):0.0;
              if(vol<LowVolThresh)
                 Regime[start+i]=0;
              else
                 if(vol<HighVolThresh)
                    Regime[start+i]=(MathAbs(deltaL1)>L1MoveThresh) ? 1:0;
                 else
                    if(vol<HighVolThresh*PanicMult)
                       Regime[start+i]=2;
                    else
                       Regime[start+i]=3;
             }
        //--- buffers
           for(int i=0; i<rates_total; i++)
             {
              BufRange[i] = (Regime[i]==0) ? Regime[i]: EMPTY_VALUE;
              BufTrend[i] = (Regime[i]==1) ? Regime[i]: EMPTY_VALUE;
              BufExpansion[i] = (Regime[i]==2) ? Regime[i]: EMPTY_VALUE;
              BufPanic[i] = (Regime[i]==3) ? Regime[i]: EMPTY_VALUE;
             }
        //---
           return(rates_total);
          }
        //+------------------------------------------------------------------+
        

        Il risultato combinato del calcolo è mostrato nella Figura 17.

        Figura 17. Indicatori L1VolatilityRegime.mq5 e L1VolatilityRegimeColor.mq5

        Fig.17. Indicatori L1VolatilityRegime.mq5 e L1VolatilityRegimeColor.mq5


        Le figure 18-20 presentano esempi congiunti degli indicatori di volatilità per EURGBP, AUDCAD e CHFJPY.

        Figura 18. Indicatori di volatilità per EURGBP

        Fig.18. Indicatori di volatilità per EURGBP


        Figura 19. Indicatori di volatilità per AUDCAD

        Fig.19. Indicatori di volatilità per AUDCAD


        Figura 20. Indicatori di volatilità per CHFJPY

        Fig.20. Indicatori di volatilità per CHFJPY


        3.5. Utilizzo del trend L1 nelle strategie di trading

        In questa sezione, prendiamo in esame le strategie di trading basate su Media Mobile, MACD, ADX ed EMA, con diverse opzioni per l'applicazione di filtri ai segnali di trading.

        L'aggiunta di filtri ai segnali di trading consente di migliorare le caratteristiche dei sistemi di trading. Per analizzare l'efficacia dell'utilizzo dei filtri negli Expert Advisor presentati, salveremo i dati relativi al saldo e all’equity (su ogni nuova barra) in file separati e utilizzeremo uno script Python per visualizzare i risultati dei sistemi di trading in diverse modalità.

        Tutti gli Expert Advisor presentati hanno la stessa architettura.

        Principio generale per allineare il trend L1 con i segnali di trading

        • Il filtro di apertura (L1FilterOpen = true) consente di aprire posizioni solo nella direzione del trend dominante.
        • Il filtro di uscita (L1FilterClose = true) aiuta a mantenere le posizioni durante i trend forti e riduce le uscite premature durante le correzioni locali.

        Parametri di input (comuni a tutti gli Expert Advisor):

        //--- L1 filter parameters
        input int    L1TotalBars    = 1000;   // Total bars for L1 filter
        input bool   L1FilterOpen   = false;  // Use filter for Open
        input bool   L1FilterClose  = false;  // Use filter for Close
        input double L1CoefLambda   = 0.2;    // Lambda in lambda_max units
        //--- save statistics
        input bool   SaveStatistics = false;  // Save statistics to file

        Salvataggio dei dati relativi al saldo e all’equity nel file

        Il parametro di input SaveStatistics consente di salvare i valori correnti di tempo, prezzo di chiusura, saldo, equity, ecc., in un file ad ogni nuova barra nella cartella: terminal_data_folder\Tester\Agent-127.0.0.1-3000\MQL5\Files.

        La funzione per il salvataggio dei dati viene chiamata all'interno di OnTick() e dipende dal valore del parametro di input bool SaveStatistics.

        //+------------------------------------------------------------------+
        //| Expert OnTick function                                           |
        //+------------------------------------------------------------------+
        void OnTick()
          {
        //--- trade only at new bar
           if(!IsNewBar())
              return;
        //--- check trade conditions
           if(SelectPosition())
              CheckForClose();
           else
              CheckForOpen();
        //--- save account statistics
           if(SaveStatistics)
              SaveAccountStatistics();
          }

        Il prefisso nel nome del file salvato dipende dalla combinazione di L1FilterOpen e L1FilterClose.

        Il nome del file dipende dalla strategia e dal simbolo e viene formato nella funzione di inizializzazione dell'Expert Advisor:

        //+------------------------------------------------------------------+
        //| PrepareStrategyFileName                                          |
        //+------------------------------------------------------------------+
        string PrepareStrategyFileName(string strategy_name)
          {
           int v=0;
           if(L1FilterOpen)
              v=v | 1;
        //---
           if(L1FilterClose)
              v=v | 2;
        //---
           string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt";
           return filename;
          }
        //+------------------------------------------------------------------+
        //| Expert initialization                                            |
        //+------------------------------------------------------------------+
        int OnInit()
          {
        //--- prepare filename
           ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName);
        //--- delete old file if exists
           if(FileIsExist(ExtStrategyFileName))
              FileDelete(ExtStrategyFileName);
        //---
           return INIT_SUCCEEDED;
          }

        Applicazione di filtri per i segnali di trading in diverse modalità:

        //+------------------------------------------------------------------+
        //| Save account statistics to file                                  |
        //+------------------------------------------------------------------+
        void SaveAccountStatistics()
          {
        //--- check file name
           if(ExtStrategyFileName=="")
              return;
        //---
           int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI);
           if(file==INVALID_HANDLE)
             {
              Print("File open error: ",GetLastError());
              return;
             }
        //--- append
           FileSeek(file,0,SEEK_END);
        //--- account data
           double balance     = AccountInfoDouble(ACCOUNT_BALANCE);
           double equity      = AccountInfoDouble(ACCOUNT_EQUITY);
           double margin      = AccountInfoDouble(ACCOUNT_MARGIN);
           double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
           double margin_lvl  = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
        //--- volume
           double volume=0.0;
           if(PositionSelect(_Symbol))
              volume=PositionGetDouble(POSITION_VOLUME);
        //--- time
           datetime t[1];
           if(CopyTime(_Symbol,_Period,0,1,t)<=0)
             {
              FileClose(file);
              return;
             }
           double current_close[1];
           if(CopyClose(_Symbol,_Period,0,1,current_close)<=0)
             {
              FileClose(file);
              return;
             }
           string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS),
                                    balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]);
        //---
           FileWrite(file,line);
        //---
           FileClose(file);
          }

        Applicazione dei filtri sui segnali di trading in diverse modalità

        Esecuzione sequenziale dell'Expert Advisor nel tester di strategia con tutte e 4 le combinazioni:

        1. L1FilterOpen = false, L1FilterClose = false (trading senza filtri);
        2. L1FilterOpen = true, L1FilterClose = false (filtraggio ingressi);
        3. L1FilterOpen = false, L1FilterClose = true (filtraggio uscite);
        4. L1FilterOpen = true, L1FilterClose = true (filtraggio ingressi e uscite).

        produce file come: 0_MA_EURUSD.txt, 1_MA_EURUSD.txt, 2_MA_EURUSD.txt, 3_MA_EURUSD.txt.

        Questi file contengono dati relativi al saldo/equity e consentono di confrontare l'efficacia del filtraggio dei segnali di trading utilizzando l'allineamento del trend L1.

        Visualizzazione dei dati

        Per creare grafici combinati, copiate i 4 file in una cartella separata ed eseguite lo script Python (ad esempio, "C:\data\").

        import pandas as pd
        import matplotlib.pyplot as plt
        import os
        
        # --- folder for charts
        output_dir = "C:\\data\\charts\\"
        os.makedirs(output_dir, exist_ok=True)
        
        symbol = "EURUSD" 
        name_strategy = "MA"
        file_strategy = name_strategy+"_"+symbol 
        title_strategy = " ("+symbol+" "+name_strategy+" strategy+filters)"
        file_prefix = symbol+"_"+name_strategy+"_"
        
        # --- files
        files = [
            "C:\\data\\0_"+file_strategy+".txt",
            "C:\\data\\1_"+file_strategy+".txt",
            "C:\\data\\2_"+file_strategy+".txt",
            "C:\\data\\3_"+file_strategy+".txt"
        ]
        
        # --- labels
        labels = [
            "No filters",
            "Open L1 filter",
            "Close L1 filter",
            "Open+Close L1 filter"
        ]
        
        # --- load data
        def load_file(filename):
            df = pd.read_csv(
                filename,
                sep=";",
                header=None,
                names=[
                    "time",
                    "balance",
                    "equity",
                    "margin",
                    "free_margin",
                    "margin_level",
                    "volume",
                    "close"
                ]
            )
            df["time"] = pd.to_datetime(df["time"])
            return df
        
        # --- close price chart
        plt.figure(figsize=(10,6), dpi=100)
        for file, label in zip(files, labels):
            df = load_file(file)
        plt.plot(df["time"], df["close"], color='gray')
        plt.title(symbol+" Close Price")
        plt.xlabel("Time")
        plt.ylabel("closing price")
        plt.legend()
        plt.grid()
        plt.tight_layout()
        plt.savefig(output_dir + file_prefix+"close_price.png", dpi=100)
        plt.show()
            
        # --- balance chart
        plt.figure(figsize=(10,6), dpi=100)
        for file, label in zip(files, labels):
            df = load_file(file)
            plt.plot(df["time"], df["balance"], label=label)
        plt.title("Balance" + title_strategy)
        plt.xlabel("Time")
        plt.ylabel("Balance")
        plt.legend()
        plt.grid()
        plt.tight_layout()
        plt.savefig(output_dir + file_prefix+"balance.png", dpi=100)
        plt.show()
        plt.close()
        
        # --- equity chart
        plt.figure(figsize=(10,6), dpi=100)
        for file, label in zip(files, labels):
            df = load_file(file)
            plt.plot(df["time"], df["equity"], label=label)
        plt.title("Equity" + title_strategy)
        plt.xlabel("Time")
        plt.ylabel("Equity")
        plt.legend()
        plt.grid()
        plt.tight_layout()
        plt.savefig(output_dir + file_prefix+"equity.png", dpi=100)
        plt.show()
        plt.close()
        
        #--- balance + equity chart
        plt.figure(figsize=(10,6), dpi=100)
        for i, (file, label) in enumerate(zip(files, labels)):
        
            df = load_file(file)
            # --- get matplotlib color
            color = plt.rcParams["axes.prop_cycle"].by_key()["color"][i % 10]
            #--- equity — solid line
            plt.plot(
                df["time"],
                df["equity"],
                color=color,
                linestyle="-",
                label=f"{label} equity"
            )
            #--- balance — dashed line
            plt.plot(
                df["time"],
                df["balance"],
                color=color,
                linestyle="--",
                label=f"{label} balance"
            )
        plt.title("Balance + Equity" + title_strategy)
        plt.xlabel("Time")
        plt.ylabel("Value")
        plt.legend()
        plt.grid()
        plt.tight_layout()
        plt.savefig(output_dir+file_prefix+"balance_equity.png", dpi=100)
        plt.show()
        plt.close()


        Implementazione dei filtri per i segnali di trading

        I parametri L1TotalBars, L1FilterOpen, L1FilterClose e L1CoefLambda definiscono le impostazioni di calcolo del trend L1 e il loro utilizzo nel filtraggio dei segnali di trading.

        Calcolo del trend L1 e allineamento con i segnali di trading

        In tutti gli Expert Advisor viene applicato un ulteriore filtro utilizzando l'analisi del trend L1:

          1. Viene creata una serie di prezzi smussata utilizzando le ultime L1TotalBars;
          2. La variazione del trend, indicata con delta, si calcola come differenza tra gli ultimi due valori filtrati.
            Se delta > 0 — trend rialzista; se delta < 0 — trend ribassista.
            //+------------------------------------------------------------------+
            //| CheckTrendL1                                                     |
            //+------------------------------------------------------------------+
            double CheckTrendL1()
              {
               int max_bars=L1TotalBars;
               MqlRates rates_data[];
               ArrayResize(rates_data,max_bars);
               ArraySetAsSeries(rates_data,false);
               if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars)
                 {
                  Print("CopyRates failed for L1Trend");
                  return(0);
                 }
            //--- prepare data (close prices vector)
               int data_count=max_bars;
               vector<double> data_close;
               data_close.Resize(data_count);
               for(int i=0; i<data_count; i++)
                  data_close[i] = rates_data[i].close;
            //--- calculate L1 filter
               vector<double> data_filtered;
               data_filtered.Resize(data_count);
               double dp=0.0;
               bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered);
               if(res)
                  dp = data_filtered[data_count-1]-data_filtered[data_count-2];
            //---
               return(dp);
              }
            

            Il parametro L1CoefLambda è specificato in unità di λmax, rendendo il filtraggio robusto alla volatilità e al numero di barre.

            Filtro di ingresso del trade

            Se L1FilterOpen = true:

            • I segnali BUY vengono ignorati quando il trend L1 è negativo;
            • I segnali SELL vengono ignorati quando il trend L1 è positivo.

            I trade vengono aperti solo nella direzione del trend dominante.

            //+------------------------------------------------------------------+
            //| CheckForOpen                                                     |
            //+------------------------------------------------------------------+
            void CheckForOpen()
              {
               ENUM_ORDER_TYPE signal;
               if(!GetTradeSignal(signal))
                  return;
               if(signal == WRONG_VALUE)
                  return;
            //--- L1 filter
               if(L1FilterOpen)
                 {
                  double delta = CheckTrendL1();
                  if(signal == ORDER_TYPE_BUY && delta < 0)
                    {
                     signal = WRONG_VALUE;
                     PrintFormat("Open BUY signal cancelled by L1 trend delta=%.5f", delta);
                    }
                  if(signal == ORDER_TYPE_SELL && delta > 0)
                    {
                     signal = WRONG_VALUE;
                     PrintFormat("Open SELL signal cancelled by L1 trend delta=%.5f", delta);
                    }
                 }
            //---
               if(signal == WRONG_VALUE)
                  return;
            //---
               if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol,_Period)<L1TotalBars)
                  return;
            //---
               double price = (signal==ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol,SYMBOL_ASK): SymbolInfoDouble(_Symbol,SYMBOL_BID);
            //---
               ExtTrade.PositionOpen(_Symbol, signal, TradeLot, price, 0, 0);
              }


            Filtro di uscita del trade

            Se L1FilterClose = true:

            • Le posizioni BUY non vengono chiuse dai segnali di inversione finché il trend L1 rimane al rialzo;
            • Le posizioni SELL non vengono chiuse finché il trend L1 rimane al ribasso.

            Questo aiuta a mantenere le posizioni durante i trend forti e riduce le uscite premature durante le correzioni locali.

            //+------------------------------------------------------------------+
            //| CheckForClose                                                    |
            //+------------------------------------------------------------------+
            void CheckForClose()
              {
            //--- check position
               if(!PositionSelect(_Symbol))
                  return;
            //--- check position magic
               if(PositionGetInteger(POSITION_MAGIC)!=MA_MAGIC)
                  return;
            //--- check trade signal
               ENUM_ORDER_TYPE signal;
               if(!GetTradeSignal(signal))
                  return;
            //---
               long type = PositionGetInteger(POSITION_TYPE);
               bool close_signal = false;
            //---
               if(type == POSITION_TYPE_BUY && signal == ORDER_TYPE_SELL)
                  close_signal = true;
               if(type == POSITION_TYPE_SELL && signal == ORDER_TYPE_BUY)
                  close_signal = true;
            //--- check L1 filter
               if(L1FilterClose)
                 {
                  double delta = CheckTrendL1();
                  if(type == POSITION_TYPE_BUY && delta > 0)
                    {
                     close_signal = false;
                     PrintFormat("Close BUY signal cancelled by L1 trend delta=%.5f", delta);
                    }
                  if(type == POSITION_TYPE_SELL && delta < 0)
                    {
                     close_signal = false;
                     PrintFormat("Close SELL signal cancelled by L1 trend delta=%.5f", delta);
                    }
                 }
            //---
               if(close_signal && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>=L1TotalBars)
                  ExtTrade.PositionClose(_Symbol,3);
              }



            3.5.1. Strategia di trading MovingAverage

            Come primo esempio, consideriamo un Expert Advisor che opera sulla base della classica strategia di trend following basata sulla Media Mobile.

            I segnali di trading vengono generati in base all'incrocio tra il prezzo di chiusura e la media mobile:

            • BUY — quando il prezzo di chiusura incrocia la media mobile dal basso verso l'alto;
            • SELL — quando il prezzo di chiusura incrocia la media mobile dall'alto verso il basso.

            I segnali vengono valutati utilizzando le ultime due barre chiuse, il che elimina l'influenza della barra corrente non ancora completata e riduce i falsi segnali.

            Inoltre, i filtri di ingresso e uscita dei trade vengono applicati in base alle impostazioni del trend L1 (L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda).

            Codice dell'Expert Advisor MovingAverageFilteredL1.mq5:

            //+------------------------------------------------------------------+
            //|                                      MovingAverageFilteredL1.mq5 |
            //|                             Copyright 2000-2026, MetaQuotes Ltd. |
            //|                                             https://www.mql5.com |
            //+------------------------------------------------------------------+
            #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
            #property link      "https://www.mql5.com"
            #property version   "1.00"
            //--- best MovingAverage parameters for EURUSD,H1,2025
            input int    MovingPeriod   = 61;     // MA period
            input int    MovingShift    = 0;      // MA shift
            //--- trade volume
            input double TradeLot       = 0.1;    // Lot size
            //--- L1 filter parameters
            input int    L1TotalBars    = 1000;   // Total bars for L1 filter
            input bool   L1FilterOpen   = false;  // Use filter for Open
            input bool   L1FilterClose  = false;  // Use filter for Close
            input double L1CoefLambda   = 0.2;    // Lambda in lambda_max units
            //--- save statistics
            input bool   SaveStatistics = false;  // Save statistics to file
            //---
            #define MA_MAGIC 1234501
            #include <Trade\Trade.mqh>
            CTrade ExtTrade;
            int    ExtHandle = INVALID_HANDLE;
            bool   ExtHedging = false;
            string ExtStrategyName="MA";
            string ExtStrategyFileName="";
            //+------------------------------------------------------------------+
            //| Check new bar                                                    |
            //+------------------------------------------------------------------+
            bool IsNewBar()
              {
               static datetime last_time=0;
               datetime t[1];
            //---
               if(CopyTime(_Symbol,_Period,0,1,t)>0)
                 {
                  if(t[0]!=last_time)
                    {
                     last_time=t[0];
                     return(true);
                    }
                 }
               return(false);
              }
            //+------------------------------------------------------------------+
            //| CheckTrendL1                                                     |
            //+------------------------------------------------------------------+
            double CheckTrendL1()
              {
               int max_bars=L1TotalBars;
               MqlRates rates_data[];
               ArrayResize(rates_data,max_bars);
               ArraySetAsSeries(rates_data,false);
               if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars)
                 {
                  Print("CopyRates failed for L1Trend");
                  return(0);
                 }
            //--- prepare data (close prices vector)
               int data_count=max_bars;
               vector<double> data_close;
               data_close.Resize(data_count);
               for(int i=0; i<data_count; i++)
                  data_close[i] = rates_data[i].close;
            //--- calculate L1 filter
               vector<double> data_filtered;
               data_filtered.Resize(data_count);
               double dp=0.0;
               bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered);
               if(res)
                  dp = data_filtered[data_count-1]-data_filtered[data_count-2];
            //---
               return(dp);
              }
            //+------------------------------------------------------------------+
            //| GetTradeSignal                                                   |
            //+------------------------------------------------------------------+
            bool GetTradeSignal(ENUM_ORDER_TYPE &signal)
              {
               signal = WRONG_VALUE;
               MqlRates bars[];
               double ma[];
               ArraySetAsSeries(bars,true);
               ArraySetAsSeries(ma,true);
               ArrayResize(bars,2);
               ArrayResize(ma,2);
            //-- two last closed bars
               if(CopyRates(_Symbol,_Period,2,2,bars) != 2)
                 {
                  Print("CopyRates failed");
                  return(false);
                 }
               if(CopyBuffer(ExtHandle,0,2,2,ma) != 2)
                 {
                  Print("CopyBuffer failed");
                  return(false);
                 }
               double close_prev = bars[1].close;
               double close_last = bars[0].close;
               double ma_prev = ma[1];
               double ma_last = ma[0];
            //--- check MA crossover
               if(close_prev < ma_prev && close_last > ma_last)
                  signal = ORDER_TYPE_BUY;
               else
                  if(close_prev > ma_prev && close_last < ma_last)
                     signal = ORDER_TYPE_SELL;
            //--- log
            //   PrintFormat("PrevBar: time=%s close=%.5f ma=%.5f | LastBar: time=%s close=%.5f ma=%.5f | Signal=%s",
            //               TimeToString(bars[0].time,TIME_DATE|TIME_MINUTES), close_prev, ma_prev,
            //               TimeToString(bars[1].time,TIME_DATE|TIME_MINUTES), close_last, ma_last,
            //               (signal==ORDER_TYPE_BUY?"BUY":signal==ORDER_TYPE_SELL?"SELL":"NONE"));
            //---
               return(true);
              }
            //+------------------------------------------------------------------+
            //| CheckForOpen                                                     |
            //+------------------------------------------------------------------+
            void CheckForOpen()
              {
               ENUM_ORDER_TYPE signal;
               if(!GetTradeSignal(signal))
                  return;
               if(signal == WRONG_VALUE)
                  return;
            //--- L1 filter
               if(L1FilterOpen)
                 {
                  double delta = CheckTrendL1();
                  if(signal == ORDER_TYPE_BUY && delta < 0)
                    {
                     signal = WRONG_VALUE;
                     PrintFormat("Open BUY signal cancelled by L1 trend delta=%.5f", delta);
                    }
                  if(signal == ORDER_TYPE_SELL && delta > 0)
                    {
                     signal = WRONG_VALUE;
                     PrintFormat("Open SELL signal cancelled by L1 trend delta=%.5f", delta);
                    }
                 }
            //---
               if(signal == WRONG_VALUE)
                  return;
            //---
               if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol,_Period)<L1TotalBars)
                  return;
            //---
               double price = (signal==ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol,SYMBOL_ASK): SymbolInfoDouble(_Symbol,SYMBOL_BID);
            //---
               ExtTrade.PositionOpen(_Symbol, signal, TradeLot, price, 0, 0);
              }
            //+------------------------------------------------------------------+
            //| CheckForClose                                                    |
            //+------------------------------------------------------------------+
            void CheckForClose()
              {
            //--- check position
               if(!PositionSelect(_Symbol))
                  return;
            //--- check position magic
               if(PositionGetInteger(POSITION_MAGIC)!=MA_MAGIC)
                  return;
            //--- check trade signal
               ENUM_ORDER_TYPE signal;
               if(!GetTradeSignal(signal))
                  return;
            //---
               long type = PositionGetInteger(POSITION_TYPE);
               bool close_signal = false;
            //---
               if(type == POSITION_TYPE_BUY && signal == ORDER_TYPE_SELL)
                  close_signal = true;
               if(type == POSITION_TYPE_SELL && signal == ORDER_TYPE_BUY)
                  close_signal = true;
            //--- check L1 filter
               if(L1FilterClose)
                 {
                  double delta = CheckTrendL1();
                  if(type == POSITION_TYPE_BUY && delta > 0)
                    {
                     close_signal = false;
                     PrintFormat("Close BUY signal cancelled by L1 trend delta=%.5f", delta);
                    }
                  if(type == POSITION_TYPE_SELL && delta < 0)
                    {
                     close_signal = false;
                     PrintFormat("Close SELL signal cancelled by L1 trend delta=%.5f", delta);
                    }
                 }
            //---
               if(close_signal && TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) && Bars(_Symbol,_Period)>=L1TotalBars)
                  ExtTrade.PositionClose(_Symbol,3);
              }
            //+------------------------------------------------------------------+
            //| SelectPosition                                                   |
            //+------------------------------------------------------------------+
            bool SelectPosition()
              {
               bool res = false;
               if(ExtHedging)
                 {
                  uint total = PositionsTotal();
                  for(uint i=0; i<total; i++)
                    {
                     string sym = PositionGetSymbol(i);
                     if(sym == _Symbol && PositionGetInteger(POSITION_MAGIC)==MA_MAGIC)
                       {
                        res = true;
                        break;
                       }
                    }
                 }
               else
                 {
                  if(PositionSelect(_Symbol))
                     res = (PositionGetInteger(POSITION_MAGIC)==MA_MAGIC);
                 }
               return(res);
              }
            //+------------------------------------------------------------------+
            //| Expert initialization function                                   |
            //+------------------------------------------------------------------+
            int OnInit()
              {
            //--- check parameters
               if(MovingPeriod<=0)
                 {
                  Print("Error: MovingPeriod parameter must be positive");
                  return(INIT_PARAMETERS_INCORRECT);
                 }
               ExtHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
               ExtTrade.SetExpertMagicNumber(MA_MAGIC);
               ExtTrade.SetMarginMode();
               ExtTrade.SetTypeFillingBySymbol(_Symbol);
            //--- prepare indicator
               ExtHandle = iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE);
               if(ExtHandle==INVALID_HANDLE)
                 {
                  Print("Failed to create MA handle");
                  return(INIT_FAILED);
                 }
            //--- prepare filename
               ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName);
            //--- delete old file if exists
               if(FileIsExist(ExtStrategyFileName))
                  FileDelete(ExtStrategyFileName);
            //---
               return(INIT_SUCCEEDED);
              }
            //+------------------------------------------------------------------+
            //| PrepareStrategyFileName                                          |
            //+------------------------------------------------------------------+
            string PrepareStrategyFileName(string strategy_name)
              {
               int v=0;
               if(L1FilterOpen)
                  v=v | 1;
            //---
               if(L1FilterClose)
                  v=v | 2;
            //---
               string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt";
               return(filename);
              }
            //+------------------------------------------------------------------+
            //| Save account statistics to file                                  |
            //+------------------------------------------------------------------+
            void SaveAccountStatistics()
              {
            //--- check file name
               if(ExtStrategyFileName=="")
                  return;
            //---
               int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI);
               if(file==INVALID_HANDLE)
                 {
                  Print("File open error: ",GetLastError());
                  return;
                 }
            //--- append
               FileSeek(file,0,SEEK_END);
            //--- account data
               double balance     = AccountInfoDouble(ACCOUNT_BALANCE);
               double equity      = AccountInfoDouble(ACCOUNT_EQUITY);
               double margin      = AccountInfoDouble(ACCOUNT_MARGIN);
               double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
               double margin_lvl  = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
            //--- volume
               double volume=0.0;
               if(PositionSelect(_Symbol))
                  volume=PositionGetDouble(POSITION_VOLUME);
            //--- time
               datetime t[1];
               if(CopyTime(_Symbol,_Period,0,1,t)<=0)
                 {
                  FileClose(file);
                  return;
                 }
               double current_close[1];
               if(CopyClose(_Symbol,_Period,0,1,current_close)<=0)
                 {
                  FileClose(file);
                  return;
                 }
               string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS),
                                        balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]);
            //---
               FileWrite(file,line);
            //---
               FileClose(file);
              }
            //+------------------------------------------------------------------+
            //| Expert OnTick function                                           |
            //+------------------------------------------------------------------+
            void OnTick()
              {
            //--- trade only at new bar
               if(!IsNewBar())
                  return;
            //--- check trade conditions
               if(SelectPosition())
                  CheckForClose();
               else
                  CheckForOpen();
            //--- save account statistics
               if(SaveStatistics)
                  SaveAccountStatistics();
              }
            //+------------------------------------------------------------------+
            //| Expert deinitialization function                                 |
            //+------------------------------------------------------------------+
            void OnDeinit(const int reason)
              {
            //--- save account statistics
               if(SaveStatistics)
                  SaveAccountStatistics();
            //---
               IndicatorRelease(ExtHandle);
              }
            //+------------------------------------------------------------------+
            

            3.5.1.1. Metodologia generale per la valutazione dell'efficienza del filtro di trend L1

            Per valutare l'efficacia del filtro L1, è necessario:

            1. Individuare la migliore combinazione di parametri per la strategia di trading che massimizza il profitto.
              Si consiglia di migliorare i segnali di trading delle strategie con le migliori prestazioni.

            2. Considerare i risultati dei test per 4 varianti di utilizzo del filtro (impostando i parametri L1FilterOpen / L1FilterClose):
              • Trading senza filtri;
              • Trading con filtro di ingresso;
              • Trading con filtro di uscita;
              • Operazioni di trading con filtri sia in ingresso che in uscita.
              Salvare i risultati in un file (parametro di input SaveStatistics=true). I file risultanti devono essere copiati in una cartella separata (negli esempi, C:\Data\).

            3. Eseguite uno script Python per generare grafici combinati.

            Considerate questi passaggi prendendo come esempio l'expert advisor MovingAverageFilteredL1.mq5, operando su EURUSD, timeframe H1, periodo di test anno 2025.

            Per l'operazione di filtraggio, utilizzeremo un numero fisso di barre per il calcolo del trend: L1TotalBars = 1000. Il parametro di regolarizzazione λ verrà specificato in unità di λmax, utilizzando un valore fisso L1CoefLambda = 0.2.

            Parametri di Input:

            //--- best MovingAverage parameters for EURUSD,H1,2025
            input int    MovingPeriod   = 64;     // MA period
            input int    MovingShift    = 0;      // MA shift
            //--- trade volume
            input double TradeLot       = 0.1;    // Lot size
            //--- L1 filter parameters
            input int    L1TotalBars    = 1000;   // Total bars for L1 filter
            input bool   L1FilterOpen   = false;  // Use filter for Open
            input bool   L1FilterClose  = false;  // Use filter for Close
            input double L1CoefLambda   = 0.2;    // Lambda in lambda_max units
            //--- save statistics
            input bool   SaveStatistics = false;  // Save statistics to file

            Nelle impostazioni di ottimizzazione, specificare il simbolo EURUSD, timeframe H1 e il periodo di test dal 01/01/2025 al 31/12/2025.

            Fig.21. Impostazioni di ottimizzazione dell'Expert Advisor MovingAverageFilteredL1.mq5

            Figura 21. Impostazioni di ottimizzazione dell'Expert Advisor MovingAverageFilteredL1.mq5


            Per un'ottimizzazione rapida, utilizzeremo la modalità "1 minute OHLC" (questa strategia opera solo su nuove barre, quindi questa approssimazione è accettabile) e selezioneremo l'algoritmo di ottimizzazione "Algoritmo rapido basato sulla genetica" con l'impostazione "Balance max".

              Fig.22. Parametri di ottimizzazione dell'Expert Advisor MovingAverageFilteredL1.mq5

            Fig.22. Parametri di ottimizzazione dell'Expert Advisor MovingAverageFilteredL1.mq5


            Poiché in questa fase il nostro obiettivo è trovare i parametri ottimali della strategia di trading, disabilitiamo tutti i filtri e il salvataggio dei file nei parametri di ottimizzazione.

            Per semplicità, ottimizzeremo un solo parametro, "Periodo MA", nell'intervallo da 1 a 800, con passo 1.

            Fig.23. Risultati dell'ottimizzazione dell'Expert Advisor MovingAverageFilteredL1.mq5

            Fig.23. Risultati dell'ottimizzazione dell'Expert Advisor MovingAverageFilteredL1.mq5


            L'ottimizzazione del parametro "MA Period" ha richiesto meno di 1 minuto; i risultati e l'elenco dei valori migliori sono mostrati nella Figura 23.

            Per test più accurati, seleziona "Ogni tick basato su tick reali" nelle impostazioni di test:


            Fig. 24. Impostazioni di test per l'Expert Advisor MovingAverageFilteredL1.mq5

            Fig.24. Impostazioni di test per l'Expert Advisor MovingAverageFilteredL1.mq5


            Eseguite un singolo test con il valore migliore “MA Period” = 64:

            Fig.25. Migliori parametri di ottimizzazione dell'Expert Advisor MovingAverageFilteredL1.mq5

            Fig.25. Parametri di ottimizzazione ottimali per l'Expert Advisor MovingAverageFilteredL1.mq5


            Fig.26. Risultati dei test con i parametri migliori per Expert Advisor MovingAverageFilteredL1.mq5

            Fig.26. Risultati dei test con i parametri migliori per Expert Advisor MovingAverageFilteredL1.mq5


            Ora è necessario eseguire i test salvando i dati su file.

            Per fare ciò, impostate SaveStatistics = true ed eseguite il test con 4 combinazioni di filtri per i segnali di trading. 

            Fig.27. Parametri di test per il salvataggio dei risultati su file per l'Expert Advisor MovingAverageFilteredL1.mq5

            Fig.27. Parametri di test per il salvataggio dei risultati su file per l'Expert Advisor MovingAverageFilteredL1.mq5


            Dopo aver eseguito i test con tutte e 4 le combinazioni, la directory del tester conterrà i seguenti file: 0_MA_EURUSD.txt, 1_MA_EURUSD.txt, 2_MA_EURUSD.txt e 3_MA_EURUSD.txt.

            Contengono l'ora, il prezzo di chiusura, nonché il saldo e il patrimonio netto per ogni barra dell'intervallo di test.

            Crea una directory separata e copia i file al suo interno (in questo esempio, C:\Data).


            Fig.28. File con i risultati dei test dell'Expert Advisor MovingAverageFilteredL1.mq5 per tutte e 4 le modalità di filtro

            Fig.28. File contenenti i risultati dei test per tutte e 4 le modalità di filtro.


            L'analisi dei dati verrà eseguita utilizzando uno script Python:

            import pandas as pd
            import matplotlib.pyplot as plt
            import os
            
            # --- folder for charts
            output_dir = "C:\\data\\charts\\"
            os.makedirs(output_dir, exist_ok=True)
            
            
            symbol = "EURUSD" 
            name_strategy = "MA"
            file_strategy = name_strategy+"_"+symbol 
            title_strategy = " ("+symbol+" "+name_strategy+" strategy+filters)"
            file_prefix = symbol+"_"+name_strategy+"_"
            
            # --- files
            files = [
                "C:\\data\\0_"+file_strategy+".txt",
                "C:\\data\\1_"+file_strategy+".txt",
                "C:\\data\\2_"+file_strategy+".txt",
                "C:\\data\\3_"+file_strategy+".txt"
            ]
            
            # --- labels
            labels = [
                "No filters",
                "Open L1 filter",
                "Close L1 filter",
                "Open+Close L1 filter"
            ]
            
            # --- load data
            def load_file(filename):
                df = pd.read_csv(
                    filename,
                    sep=";",
                    header=None,
                    names=[
                        "time",
                        "balance",
                        "equity",
                        "margin",
                        "free_margin",
                        "margin_level",
                        "volume",
                        "close"
                    ]
                )
                df["time"] = pd.to_datetime(df["time"])
                return df
            
            # --- close price chart
            plt.figure(figsize=(10,6), dpi=100)
            for file, label in zip(files, labels):
                df = load_file(file)
            plt.plot(df["time"], df["close"], color='gray')
            plt.title(symbol+" Close Price")
            plt.xlabel("Time")
            plt.ylabel("closing price")
            plt.legend()
            plt.grid()
            plt.tight_layout()
            plt.savefig(output_dir + file_prefix+"close_price.png", dpi=100)
            plt.show()
                
            # --- balance chart
            plt.figure(figsize=(10,6), dpi=100)
            for file, label in zip(files, labels):
                df = load_file(file)
                plt.plot(df["time"], df["balance"], label=label)
            plt.title("Balance" + title_strategy)
            plt.xlabel("Time")
            plt.ylabel("Balance")
            plt.legend()
            plt.grid()
            plt.tight_layout()
            plt.savefig(output_dir + file_prefix+"balance.png", dpi=100)
            plt.show()
            plt.close()
            
            # --- equity chart
            plt.figure(figsize=(10,6), dpi=100)
            for file, label in zip(files, labels):
                df = load_file(file)
                plt.plot(df["time"], df["equity"], label=label)
            plt.title("Equity" + title_strategy)
            plt.xlabel("Time")
            plt.ylabel("Equity")
            plt.legend()
            plt.grid()
            plt.tight_layout()
            plt.savefig(output_dir + file_prefix+"equity.png", dpi=100)
            plt.show()
            plt.close()
            
            
            #--- balance + equity chart
            plt.figure(figsize=(10,6), dpi=100)
            for i, (file, label) in enumerate(zip(files, labels)):
            
                df = load_file(file)
                # --- get matplotlib color
                color = plt.rcParams["axes.prop_cycle"].by_key()["color"][i % 10]
                #--- equity — solid line
                plt.plot(
                    df["time"],
                    df["equity"],
                    color=color,
                    linestyle="-",
                    label=f"{label} equity"
                )
                #--- balance — dashed line
                plt.plot(
                    df["time"],
                    df["balance"],
                    color=color,
                    linestyle="--",
                    label=f"{label} balance"
                )
            plt.title("Balance + Equity" + title_strategy)
            plt.xlabel("Time")
            plt.ylabel("Value")
            plt.legend()
            plt.grid()
            plt.tight_layout()
            plt.savefig(output_dir+file_prefix+"balance_equity.png", dpi=100)
            plt.show()
            plt.close()
            
            

            Per eseguire lo script Python in MetaEditor, premere "Compila" (Fig. 29).


            Fig.29. Script PlotData.py in MetaEditor

            Fig.29. Script PlotData.py in MetaEditor


            Dopo aver eseguito PlotData.py, verranno visualizzati i seguenti grafici:

            1. Serie di prezzi EURUSD (prezzi di chiusura su ciascuna barra);
            2. Grafici del saldo per tutte le modalità di filtro;
            3. Grafici dell’equity per tutte le modalità di filtro;
            4. Grafici combinati di saldo e equity (per valutare la riduzione del drawdown).

            Anche i grafici verranno salvati nella cartella C:\Data\Charts\ in formato PNG.


            Figura 30. Grafico dei prezzi EURUSD per il periodo di test

            Fig.30. Grafico dei prezzi EURUSD per il periodo di test


            Fig.31. Grafici del saldo per l'Expert Advisor MovingAverageFilteredL1.mq5 per le diverse modalità di filtro

            Fig.31. Grafici del saldo per l'Expert Advisor MovingAverageFilteredL1.mq5 per le diverse modalità di filtro


            Fig.32. Grafici dell’equity per l'Expert Advisor MovingAverageFilteredL1.mq5

            Fig.32. Grafici dell’equity per l'Expert Advisor MovingAverageFilteredL1.mq5 per le diverse modalità di filtro


            Fig.33. Grafici combinati di saldo e equity per l'Expert Advisor MovingAverageFilteredL1.mq5 per le diverse modalità di filtro

            Fig.33. Grafici combinati di saldo e equity per l'Expert Advisor MovingAverageFilteredL1.mq5 per le diverse modalità di filtro


            Per eseguire script Python in MetaEditor, installate Python (versione 3.14 nell'esempio) e specificate il suo percorso nelle impostazioni.


            Figura 34. Impostazioni Python in MetaEditor

            Fig.34. Impostazioni Python in MetaEditor


            Lo script utilizza le librerie pandas e matplotlib. Se non sono installate:

            pip install pandas
            pip install matplotlib


            3.5.1.2. Risultati dell'applicazione dei filtri L1 (strategia MovingAverage)

            I risultati (grafici del saldo, equity e combinati) sono illustrati nelle figure 35-37.

            Colori:

            1. Blu (strategia senza filtri);
            2. Verde (filtro L1 sulla chiusura della posizione);
            3. Rosso (filtro L1 sia in apertura che in chiusura);
            4. Arancione (filtro L1 in apertura);

            Fig.35. Grafici del saldo per l'Expert Advisor MovingAverageFilteredL1.mq5 per le diverse modalità di filtro

            Fig.35. Grafici del saldo per l'Expert Advisor MovingAverageFilteredL1.mq5 per le diverse modalità di filtro



            Fig.36. Grafici dell’equity per l'Expert Advisor MovingAverageFilteredL1.mq5 per le diverse modalità di filtro

            Fig.36. Grafici dell’equity per l'Expert Advisor MovingAverageFilteredL1.mq5 per le diverse modalità di filtro


            Fig.37. Grafici combinati di saldo e equity per l'Expert Advisor MovingAverageFilteredL1.mq5 per le diverse modalità di filtro

            Fig.37. Grafici combinati di saldo e equity per l'Expert Advisor MovingAverageFilteredL1.mq5 per le diverse modalità di filtro



            3.5.2. Strategia di trading MACD

            Ad esempio, considerate un expert advisor che effettua operazioni di trading basandosi sui segnali di una strategia di trading costruita sull'indicatore MACD (Moving Average Convergence/Divergence).

            Segnali di trading

            I segnali vengono generati quando la linea principale del MACD incrocia la linea di segnale:

            1. BUY — la linea principale del MACD incrocia la linea di segnale dal basso verso l'alto.
            2. SELL — la linea principale del MACD incrocia la linea di segnale dall'alto verso il basso.

            I segnali vengono analizzati solo alla chiusura della barra, il che riduce il numero di false attivazioni all'interno della barra stessa.

            Inoltre, i filtri di ingresso e uscita dei trade vengono utilizzati in base alle impostazioni del filtro di trend L1 (L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda).

            Codice dell'Expert Advisor MACDFilteredL1.mq5:

            //+------------------------------------------------------------------+
            //|                                               MACDFilteredL1.mq5 |
            //|                             Copyright 2000-2026, MetaQuotes Ltd. |
            //|                                             https://www.mql5.com |
            //+------------------------------------------------------------------+
            #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
            #property link      "https://www.mql5.com"
            #property version   "1.00"
            //--- best MACD parameters for USDCHF,H1,2025
            input int    FastEMA        = 43;     // Fast EMA
            input int    SlowEMA        = 59;     // Slow EMA
            input int    SignalEMA      = 37;     // SignalEMA
            //--- trade volume
            input double TradeLot       = 0.1;    // Lot size
            //--- L1 filter parameters
            input int    L1TotalBars    = 1000;   // Total bars for L1 filter
            input bool   L1FilterOpen   = false;  // Use filter for Open
            input bool   L1FilterClose  = false;  // Use filter for Close
            input double L1CoefLambda   = 0.2;    // Lambda in lambda_max units
            //--- save statistics
            input bool   SaveStatistics = false;  // Save statistics to file
            //---
            #define MACD_MAGIC 1234502
            #include <Trade\Trade.mqh>
            int    ExtHandle = INVALID_HANDLE;
            bool   ExtHedging = false;
            CTrade ExtTrade;
            string ExtStrategyName="MACD";
            string ExtStrategyFileName="";
            //+------------------------------------------------------------------+
            //| Check new bar                                                    |
            //+------------------------------------------------------------------+
            bool IsNewBar()
              {
               static datetime last_time=0;
               datetime t[1];
            //---
               if(CopyTime(_Symbol,_Period,0,1,t)>0)
                 {
                  if(t[0]!=last_time)
                    {
                     last_time=t[0];
                     return(true);
                    }
                 }
               return(false);
              }
            //+------------------------------------------------------------------+
            //| CheckTrendL1                                                     |
            //+------------------------------------------------------------------+
            double CheckTrendL1()
              {
               int max_bars=L1TotalBars;
               MqlRates rates_data[];
               ArrayResize(rates_data,max_bars);
               ArraySetAsSeries(rates_data,false);
               if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars)
                 {
                  Print("CopyRates failed for L1Trend");
                  return(0);
                 }
            //--- prepare data (close prices vector)
               int data_count=max_bars;
               vector<double> data_close;
               data_close.Resize(data_count);
               for(int i=0; i<data_count; i++)
                  data_close[i] = rates_data[i].close;
            //--- calculate L1 filter
               vector<double> data_filtered;
               data_filtered.Resize(data_count);
               double dp=0.0;
               bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered);
               if(res)
                  dp = data_filtered[data_count-1]-data_filtered[data_count-2];
            //---
               return(dp);
              }
            //+------------------------------------------------------------------+
            //| GetTradeSignal(MACD)                                             |
            //+------------------------------------------------------------------+
            bool GetTradeSignal(ENUM_ORDER_TYPE &signal)
              {
               signal = WRONG_VALUE;
               double macd_main[];
               double macd_signal[];
            //---
               ArrayResize(macd_main,2);
               ArrayResize(macd_signal,2);
            //---
               ArraySetAsSeries(macd_main, true);
               ArraySetAsSeries(macd_signal, true);
            //--- buffer 0 = MACD main, buffer 1 = signal line
               if(CopyBuffer(ExtHandle,0,1,2,macd_main)!=2)
                  return(false);
               if(CopyBuffer(ExtHandle,1,1,2,macd_signal)!=2)
                  return(false);
            //---
               double main_prev   = macd_main[1];
               double main_last   = macd_main[0];
               double signal_prev = macd_signal[1];
               double signal_last = macd_signal[0];
            //--- MACD crossover
               if(main_prev < signal_prev && main_last > signal_last)
                  signal = ORDER_TYPE_BUY;
               else
                  if(main_prev > signal_prev && main_last < signal_last)
                     signal = ORDER_TYPE_SELL;
            //---
               return(true);
              }
            //+------------------------------------------------------------------+
            //| CheckForOpen                                                     |
            //+------------------------------------------------------------------+
            void CheckForOpen()
              {
               ENUM_ORDER_TYPE signal;
               if(!GetTradeSignal(signal) || signal == WRONG_VALUE)
                  return;
            //---
               if(L1FilterOpen)
                 {
                  double dp = CheckTrendL1();
                  if(signal == ORDER_TYPE_BUY && dp < 0)
                     return;
                  if(signal == ORDER_TYPE_SELL && dp > 0)
                     return;
                 }
            //---
               if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol, _Period) < L1TotalBars)
                  return;
            //---
               double price = (signal == ORDER_TYPE_BUY)
                              ? SymbolInfoDouble(_Symbol, SYMBOL_ASK)
                              : SymbolInfoDouble(_Symbol, SYMBOL_BID);
            //---
               ExtTrade.PositionOpen(_Symbol, signal, TradeLot, price, 0, 0);
              }
            //+------------------------------------------------------------------+
            //| CheckForClose                                                    |
            //+------------------------------------------------------------------+
            void CheckForClose()
              {
            //--- check position
               if(!PositionSelect(_Symbol))
                  return;
            //--- check position magic
               if(PositionGetInteger(POSITION_MAGIC)!=MACD_MAGIC)
                  return;
            //--- check trade signal
               ENUM_ORDER_TYPE signal;
               if(!GetTradeSignal(signal))
                  return;
            //---
               long type = PositionGetInteger(POSITION_TYPE);
               bool close_signal = false;
            //---
               if(type == POSITION_TYPE_BUY  && signal == ORDER_TYPE_SELL)
                  close_signal = true;
               if(type == POSITION_TYPE_SELL && signal == ORDER_TYPE_BUY)
                  close_signal = true;
            //--- check L1 filter
               if(L1FilterClose)
                 {
                  double dp = CheckTrendL1();
                  if(type == POSITION_TYPE_BUY && dp > 0)
                    {
                     close_signal = false;
                     PrintFormat("Close BUY signal cancelled by L1 trend dp=%.5f", dp);
                    }
                  if(type == POSITION_TYPE_SELL && dp < 0)
                    {
                     close_signal = false;
                     PrintFormat("Close SELL signal cancelled by L1 trend dp=%.5f", dp);
                    }
                 }
            //---
               if(close_signal)
                  ExtTrade.PositionClose(_Symbol, 3);
              }
            //+------------------------------------------------------------------+
            //| SelectPosition                                                   |
            //+------------------------------------------------------------------+
            bool SelectPosition()
              {
               bool res = false;
               if(ExtHedging)
                 {
                  uint total = PositionsTotal();
                  for(uint i=0; i<total; i++)
                    {
                     string sym = PositionGetSymbol(i);
                     if(sym == _Symbol && PositionGetInteger(POSITION_MAGIC)==MACD_MAGIC)
                       {
                        res = true;
                        break;
                       }
                    }
                 }
               else
                 {
                  if(PositionSelect(_Symbol))
                     res = (PositionGetInteger(POSITION_MAGIC)==MACD_MAGIC);
                 }
               return(res);
              }
            //+------------------------------------------------------------------+
            //| Expert initialization                                            |
            //+------------------------------------------------------------------+
            int OnInit()
              {
            //--- check parameters
               if(FastEMA <= 0 || SlowEMA <= 0 || SignalEMA <= 0)
                 {
                  Print("Error: MACD parameters must be positive");
                  return(INIT_PARAMETERS_INCORRECT);
                 }
               if(FastEMA >= SlowEMA)
                 {
                  Print("FastEMA must be less than SlowEMA");
                  return(INIT_PARAMETERS_INCORRECT);
                 }
            //---
               ExtHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
               ExtTrade.SetExpertMagicNumber(MACD_MAGIC);
               ExtTrade.SetMarginMode();
               ExtTrade.SetTypeFillingBySymbol(_Symbol);
            //--- prepare indicator
               ExtHandle=iMACD(_Symbol,_Period,FastEMA,SlowEMA,SignalEMA,PRICE_CLOSE);
               if(ExtHandle==INVALID_HANDLE)
                 {
                  Print("Failed to create MACD handle");
                  return(INIT_FAILED);
                 }
            //--- prepare filename
               ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName);
            //--- delete old file if exists
               if(FileIsExist(ExtStrategyFileName))
                  FileDelete(ExtStrategyFileName);
            //---
               return(INIT_SUCCEEDED);
              }
            //+------------------------------------------------------------------+
            //| PrepareStrategyFileName                                          |
            //+------------------------------------------------------------------+
            string PrepareStrategyFileName(string strategy_name)
              {
               int v=0;
               if(L1FilterOpen)
                  v=v | 1;
            //---
               if(L1FilterClose)
                  v=v | 2;
            //---
               string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt";
               return(filename);
              }
            //+------------------------------------------------------------------+
            //| Save account statistics to file                                  |
            //+------------------------------------------------------------------+
            void SaveAccountStatistics()
              {
            //--- check file name
               if(ExtStrategyFileName=="")
                  return;
            //---
               int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI);
               if(file==INVALID_HANDLE)
                 {
                  Print("File open error: ",GetLastError());
                  return;
                 }
            //--- append
               FileSeek(file,0,SEEK_END);
            //--- account data
               double balance     = AccountInfoDouble(ACCOUNT_BALANCE);
               double equity      = AccountInfoDouble(ACCOUNT_EQUITY);
               double margin      = AccountInfoDouble(ACCOUNT_MARGIN);
               double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
               double margin_lvl  = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
            //--- volume
               double volume=0.0;
               if(PositionSelect(_Symbol))
                  volume=PositionGetDouble(POSITION_VOLUME);
            //--- time
               datetime t[1];
               if(CopyTime(_Symbol,_Period,0,1,t)<=0)
                 {
                  FileClose(file);
                  return;
                 }
               double current_close[1];
               if(CopyClose(_Symbol,_Period,0,1,current_close)<=0)
                 {
                  FileClose(file);
                  return;
                 }
               string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS),
                                        balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]);
            //---
               FileWrite(file,line);
            //---
               FileClose(file);
              }
            //+------------------------------------------------------------------+
            //| Expert OnTick function                                           |
            //+------------------------------------------------------------------+
            void OnTick()
              {
            //--- trade only at new bar
               if(!IsNewBar())
                  return;
            //--- check trade conditions
               if(SelectPosition())
                  CheckForClose();
               else
                  CheckForOpen();
            //--- save account statistics
               if(SaveStatistics)
                  SaveAccountStatistics();
              }
            //+------------------------------------------------------------------+
            //| Expert deinitialization                                          |
            //+------------------------------------------------------------------+
            void OnDeinit(const int reason)
              {
            //--- save account statistics
               if(SaveStatistics)
                  SaveAccountStatistics();
            //---
               if(ExtHandle != INVALID_HANDLE)
                  IndicatorRelease(ExtHandle);
              }
            //+------------------------------------------------------------------+
            

            Le impostazioni di test sono illustrate nella Figura 38.

            Fig.38. Impostazioni delo Strategy Tester dell'Expert Advisor MACDFilteredL1.mq5

            Fig.38. Impostazioni dello Strategy Tester dell'Expert Advisor MACDFilteredL1.mq5


            Fig.39. Parametri di test dell'Expert Advisor MACDFilteredL1.mq5

            Fig.39. Parametri di test dell'Expert Advisor MACDFilteredL1.mq5


            Fig.40. Risultati dei test dell'Expert Advisor MACDFilteredL1.mq5

            Fig.40. Risultati dei test dell'Expert Advisor MACDFilteredL1.mq5


            Figura 41. Parametri di test per il salvataggio dei risultati su file per l'Expert Advisor MACDFilteredL1.mq5

            Fig.41. Parametri di test per il salvataggio dei risultati su file per l'Expert Advisor MACDFilteredL1.mq5


            Dopo aver eseguito i test nello strategy tester con diverse configurazioni di filtro, i file x_MACD_EURUSD.txt appariranno nella directory del tester.

            I file devono essere copiati nella cartella C:\Data\ e lo script PlotData.py deve essere eseguito.


            Fig.42. File con i risultati dei test per l'Expert Advisor MACDFiltered.mq5 per le diverse modalità di filtro

            Fig.42. File contenenti i risultati dei test per l'Expert Advisor MACDFiltered.mq5 per le diverse modalità di filtro.



            3.5.2.1. Risultati dell'applicazione dei filtri L1 (strategia MACD)

            I risultati sono illustrati nelle figure 43-45.

            Fig.43. Grafici del saldo dell'Expert Advisor MACDFilteredL1.mq5 per le diverse modalità di filtro

            Fig.43. Grafici del saldo dell'Expert Advisor MACDFilteredL1.mq5 per le diverse modalità di filtro


            Fig.44. Grafico dell’equity dell'Expert Advisor MACDFilteredL1.mq5 per le diverse modalità di filtro

            Fig.44. Grafico dell’equity dell'Expert Advisor MACDFilteredL1.mq5 per le diverse modalità di filtro


            Fig.45. Grafici combinati di saldo e patrimonio netto dell'Expert Advisor MACDFilteredL1.mq5 per le diverse modalità di filtro

            Figura 45. Grafici combinati di saldo e equity dell'Expert Advisor MACDFilteredL1.mq5 per le diverse modalità di filtro


            3.5.3. Strategia di trading ADX

            Come ulteriore esempio, consideriamo l'Expert Advisor ADXFilteredL1.mq5, che implementa una strategia di trend following basata sull’ Average Directional Movement Index(ADX).

            I principali segnali di trading si basano sull'analisi delle linee +DI e −DI, nonché sul livello ADX, che caratterizza la forza del trend.

            I segnali sono definiti come segue:

            • BUY: +DI incrocia −DI dal basso verso l'alto;
            • SELL: +DI incrocia −DI dall'alto verso il basso.

            Inoltre, viene preso in considerazione il valore ADX. Se l'ADX è inferiore alla soglia ADXTrendLevel, il mercato viene considerato in debole trend o in fase di consolidamento e il segnale viene ignorato.

            Vengono utilizzati i valori dell'indicatore relativi alle ultime due barre chiuse, escludendo l'influenza della barra corrente non ancora completata e riducendo i falsi segnali.

            I filtri di ingresso e di uscita vengono applicati anche in base alle impostazioni del trend L1 (L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda).

            Il codice dell'Expert Advisor ADXFilteredL1.mq5 è riportato di seguito.

            //+------------------------------------------------------------------+
            //|                                                ADXFilteredL1.mq5 |
            //|                             Copyright 2000-2026, MetaQuotes Ltd. |
            //|                                              http://www.mql5.com |
            //+------------------------------------------------------------------+
            #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
            #property link      "https://www.mql5.com"
            #property version   "1.00"
            //--- best ADX parameters for EURUSD,H1,2025
            input int    ADXPeriod      = 65;     // ADX Period
            input double ADXTrendLevel  = 7;      // ADX Trend Level
            //--- trade volume
            input double TradeLot       = 0.1;    // Lot size
            //--- L1 filter parameters
            input int    L1TotalBars    = 1000;   // Total bars for L1 filter
            input bool   L1FilterOpen   = false;  // Use filter for Open
            input bool   L1FilterClose  = false;  // Use filter for Close
            input double L1CoefLambda   = 0.2;    // Lambda in lambda_max units
            //--- save statistics
            input bool   SaveStatistics = false;  // Save statistics to file
            //---
            #define ADX_MAGIC 1234503
            #include <Trade\Trade.mqh>
            CTrade ExtTrade;
            int    ExtHandle = INVALID_HANDLE;
            bool   ExtHedging = false;
            string ExtStrategyName="ADX";
            string ExtStrategyFileName="";
            //+------------------------------------------------------------------+
            //| Check new bar                                                    |
            //+------------------------------------------------------------------+
            bool IsNewBar()
              {
               static datetime last_time=0;
               datetime t[1];
            //---
               if(CopyTime(_Symbol,_Period,0,1,t)>0)
                 {
                  if(t[0]!=last_time)
                    {
                     last_time=t[0];
                     return(true);
                    }
                 }
               return(false);
              }
            //+------------------------------------------------------------------+
            //| CheckTrendL1                                                     |
            //+------------------------------------------------------------------+
            double CheckTrendL1()
              {
               int max_bars=L1TotalBars;
               MqlRates rates_data[];
               ArrayResize(rates_data,max_bars);
               ArraySetAsSeries(rates_data,false);
               if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars)
                 {
                  Print("CopyRates failed for L1Trend");
                  return(0);
                 }
            //--- prepare data (close prices vector)
               int data_count=max_bars;
               vector<double> data_close;
               data_close.Resize(data_count);
               for(int i=0; i<data_count; i++)
                  data_close[i] = rates_data[i].close;
            //--- calculate L1 filter
               vector<double> data_filtered;
               data_filtered.Resize(data_count);
               double dp=0.0;
               bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered);
               if(res)
                  dp = data_filtered[data_count-1] - data_filtered[data_count-2];
            //---
               return(dp);
              }
            //+------------------------------------------------------------------+
            //| GetTradeSignal (ADX)                                             |
            //+------------------------------------------------------------------+
            bool GetTradeSignal(ENUM_ORDER_TYPE &signal)
              {
               signal=WRONG_VALUE;
               double adx[],plusdi[],minusdi[];
               ArrayResize(adx,2);
               ArrayResize(plusdi,2);
               ArrayResize(minusdi,2);
            //---
               ArraySetAsSeries(adx,true);
               ArraySetAsSeries(plusdi,true);
               ArraySetAsSeries(minusdi,true);
            //--- buffer0 = ADX
               if(CopyBuffer(ExtHandle,0,1,2,adx)!=2)
                  return(false);
            //--- buffer1 = +DI
               if(CopyBuffer(ExtHandle,1,1,2,plusdi)!=2)
                  return(false);
            //--- buffer2 = -DI
               if(CopyBuffer(ExtHandle,2,1,2,minusdi)!=2)
                  return(false);
               double adx_last=adx[0];
               double plus_prev=plusdi[1];
               double plus_last=plusdi[0];
               double minus_prev=minusdi[1];
               double minus_last=minusdi[0];
            //--- strong trend required
               if(adx_last<ADXTrendLevel)
                  return(true);
            //--- +DI cross -DI
               if(plus_prev<minus_prev && plus_last>minus_last)
                  signal=ORDER_TYPE_BUY;
               else
                  if(plus_prev>minus_prev && plus_last<minus_last)
                     signal=ORDER_TYPE_SELL;
            //---
               return(true);
              }
            //+------------------------------------------------------------------+
            //| CheckForOpen                                                     |
            //+------------------------------------------------------------------+
            void CheckForOpen()
              {
               ENUM_ORDER_TYPE signal;
            //---
               if(!GetTradeSignal(signal) || signal==WRONG_VALUE)
                  return;
            //---
               if(L1FilterOpen)
                 {
                  double dp=CheckTrendL1();
                  if(signal==ORDER_TYPE_BUY && dp<0)
                     return;
                  if(signal==ORDER_TYPE_SELL && dp>0)
                     return;
                 }
            //---
               if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol,_Period)<L1TotalBars)
                  return;
            //---
               double price=(signal==ORDER_TYPE_BUY)
                            ? SymbolInfoDouble(_Symbol,SYMBOL_ASK)
                            : SymbolInfoDouble(_Symbol,SYMBOL_BID);
            //---
               ExtTrade.PositionOpen(_Symbol,signal,TradeLot,price,0,0);
              }
            //+------------------------------------------------------------------+
            //| CheckForClose                                                    |
            //+------------------------------------------------------------------+
            void CheckForClose()
              {
            //--- check position
               if(!PositionSelect(_Symbol))
                  return;
            //--- check position magic
               if(PositionGetInteger(POSITION_MAGIC)!=ADX_MAGIC)
                  return;
            //--- check trade signal
               ENUM_ORDER_TYPE signal;
               if(!GetTradeSignal(signal))
                  return;
            //---
               long type=PositionGetInteger(POSITION_TYPE);
               bool close_signal=false;
            //---
               if(type==POSITION_TYPE_BUY && signal==ORDER_TYPE_SELL)
                  close_signal=true;
            //---
               if(type==POSITION_TYPE_SELL && signal==ORDER_TYPE_BUY)
                  close_signal=true;
            //--- check L1 filter
            //--- check L1 filter
               if(L1FilterClose)
                 {
                  double dp = CheckTrendL1();
                  if(type == POSITION_TYPE_BUY && dp > 0)
                    {
                     close_signal = false;
                     PrintFormat("Close BUY signal cancelled by L1 trend dp=%.5f", dp);
                    }
                  if(type == POSITION_TYPE_SELL && dp < 0)
                    {
                     close_signal = false;
                     PrintFormat("Close SELL signal cancelled by L1 trend dp=%.5f", dp);
                    }
                 }
            //---
               if(close_signal)
                  ExtTrade.PositionClose(_Symbol,3);
              }
            //+------------------------------------------------------------------+
            //| SelectPosition                                                   |
            //+------------------------------------------------------------------+
            bool SelectPosition()
              {
               bool res = false;
               if(ExtHedging)
                 {
                  uint total = PositionsTotal();
                  for(uint i=0; i<total; i++)
                    {
                     string sym = PositionGetSymbol(i);
                     if(sym == _Symbol && PositionGetInteger(POSITION_MAGIC)==ADX_MAGIC)
                       {
                        res = true;
                        break;
                       }
                    }
                 }
               else
                 {
                  if(PositionSelect(_Symbol))
                     res = (PositionGetInteger(POSITION_MAGIC)==ADX_MAGIC);
                 }
               return(res);
              }
            //+------------------------------------------------------------------+
            //| Expert initialization                                            |
            //+------------------------------------------------------------------+
            int OnInit()
              {
               ExtHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
               ExtTrade.SetExpertMagicNumber(ADX_MAGIC);
               ExtTrade.SetMarginMode();
               ExtTrade.SetTypeFillingBySymbol(_Symbol);
            //--- prepare indicator
               ExtHandle=iADX(_Symbol,_Period,ADXPeriod);
               if(ExtHandle==INVALID_HANDLE)
                 {
                  Print("ADX handle error");
                  return(INIT_FAILED);
                 }
            //--- prepare filename
               ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName);
            //--- delete old file if exists
               if(FileIsExist(ExtStrategyFileName))
                  FileDelete(ExtStrategyFileName);
            //---
               return(INIT_SUCCEEDED);
              }
            //+------------------------------------------------------------------+
            //| PrepareStrategyFileName                                          |
            //+------------------------------------------------------------------+
            string PrepareStrategyFileName(string strategy_name)
              {
               int v=0;
               if(L1FilterOpen)
                  v=v | 1;
            //---
               if(L1FilterClose)
                  v=v | 2;
            //---
               string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt";
               return(filename);
              }
            //+------------------------------------------------------------------+
            //| Save account statistics to file                                  |
            //+------------------------------------------------------------------+
            void SaveAccountStatistics()
              {
            //--- check file name
               if(ExtStrategyFileName=="")
                  return;
            //---
               int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI);
               if(file==INVALID_HANDLE)
                 {
                  Print("File open error: ",GetLastError());
                  return;
                 }
            //--- append
               FileSeek(file,0,SEEK_END);
            //--- account data
               double balance     = AccountInfoDouble(ACCOUNT_BALANCE);
               double equity      = AccountInfoDouble(ACCOUNT_EQUITY);
               double margin      = AccountInfoDouble(ACCOUNT_MARGIN);
               double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
               double margin_lvl  = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
            //--- volume
               double volume=0.0;
               if(PositionSelect(_Symbol))
                  volume=PositionGetDouble(POSITION_VOLUME);
            //--- time
               datetime t[1];
               if(CopyTime(_Symbol,_Period,0,1,t)<=0)
                 {
                  FileClose(file);
                  return;
                 }
               double current_close[1];
               if(CopyClose(_Symbol,_Period,0,1,current_close)<=0)
                 {
                  FileClose(file);
                  return;
                 }
               string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS),
                                        balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]);
            //---
               FileWrite(file,line);
            //---
               FileClose(file);
              }
            //+------------------------------------------------------------------+
            //| Expert OnTick function                                           |
            //+------------------------------------------------------------------+
            void OnTick()
              {
            //--- trade only at new bar
               if(!IsNewBar())
                  return;
            //--- check trade conditions
               if(SelectPosition())
                  CheckForClose();
               else
                  CheckForOpen();
            //--- save account statistics
               if(SaveStatistics)
                  SaveAccountStatistics();
              }
            //+------------------------------------------------------------------+
            //| Expert deinitialization                                          |
            //+------------------------------------------------------------------+
            void OnDeinit(const int reason)
              {
            //--- save account statistics
               if(SaveStatistics)
                  SaveAccountStatistics();
            //--- release indicator handle
               if(ExtHandle!=INVALID_HANDLE)
                  IndicatorRelease(ExtHandle);
              }
            //+------------------------------------------------------------------+
            

            Le impostazioni di test, i parametri e i risultati sono illustrati nelle figure 46-48.


            Fig.46. Impostazioni di test per l'Expert Advisor ADXFilteredL1.mq5

            Fig.46. Impostazioni di test per l'Expert Advisor ADXFilteredL1.mq5


            Figura 47. Parametri di test per l'Expert Advisor ADXFilteredL1.mq5

            Fig.47. Parametri di test per l'Expert Advisor ADXFilteredL1.mq5


            Fig.48. Risultati dei test per l'Expert Advisor ADXFilteredL1.mq5

            Figura 48. Risultati dei test per l'Expert Advisor ADXFilteredL1.mq5


            Per eseguire il test, è necessario effettuare 4 prove con diverse impostazioni del filtro:

            Figura 49. Parametri di test per il salvataggio dei risultati su file per l'Expert Advisor ADXFilteredL1.mq5

            Fig.49. Parametri di test per il salvataggio dei risultati su file per l'Expert Advisor ADXFilteredL1.mq5

            Dopodiché, i file appariranno nella directory del tester; dovranno essere copiati in C:\Data ed elaborati con PlotData.py.


            Fig.50. File con i risultati dei test per l'Expert Advisor ADXFiltered.mq5 con le diverse impostazioni del filtro

            Fig.50. File contenenti i risultati dei test per l'Expert Advisor ADXFiltered.mq5 con le diverse impostazioni di filtro.



            3.5.3.1. Risultati dell'applicazione dei filtri L1 alla strategia ADX

            I risultati sono illustrati nelle figure 51-53.

            Fig.51. Grafici del saldo dell'Expert Advisor ADXFilteredL1.mq5 con le diverse modalità di filtro

            Fig.51. Grafici del saldo dell'Expert Advisor ADXFilteredL1.mq5 con le diverse modalità di filtro


            Fig.52. Grafici dell’equity dell'Expert Advisor ADXFilteredL1.mq5 con le diverse modalità di filtro

            Fig.52. Grafici dell’equity dell'Expert Advisor ADXFilteredL1.mq5 con le diverse modalità di filtro


            Fig.53. Grafici combinati di saldo e equity dell'Expert Advisor ADXFilteredL1.mq5 con le diverse modalità di filtro

            Fig.53. Grafici combinati di saldo e equity dell'Expert Advisor ADXFilteredL1.mq5 con le diverse modalità di filtro.



            3.5.4. Strategia di trading basata sull'incrocio delle medie mobili esponenziali (EMA).

            Consideriamo l'Expert Advisor EMAFilteredL1.mq5, che implementa una strategia di trading basata sul trend following, fondata sull'incrocio di due medie mobili esponenziali (EMA).

            La strategia utilizza due medie mobili:

            1. FastEMA — media mobile esponenziale veloce;
            2. SlowEMA — media mobile esponenziale lenta.

            I segnali di trading si formano nel modo seguente:

            • BUY: quando la EMA veloce incrocia la EMA lenta dal basso verso l'alto;
            • SELL: quando la EMA veloce incrocia la EMA lenta dall'alto verso il basso.

            Per l'analisi, vengono utilizzati i valori degli indicatori sulle ultime due barre chiuse, il che elimina l'influenza della barra corrente non ancora completata e riduce il numero di falsi segnali.

            Inoltre, i filtri di ingresso e uscita dal trade vengono applicati in base alle impostazioni del filtro di trend L1 (L1TotalBars, L1FilterOpen, L1FilterClose, L1CoefLambda).

            Il codice dell'Expert Advisor EMAFilteredL1.mq5 è riportato di seguito.

            //+------------------------------------------------------------------+
            //|                                                EMAFilteredL1.mq5 |
            //|                             Copyright 2000-2026, MetaQuotes Ltd. |
            //|                                              http://www.mql5.com |
            //+------------------------------------------------------------------+
            #property copyright "Copyright 2000-2026, MetaQuotes Ltd."
            #property link      "https://www.mql5.com"
            #property version   "1.00"
            //--- best EMA parameters for EURUSD,H1,2025
            input int    FastEMA       = 29;     // Fast EMA
            input int    SlowEMA       = 101;     // Slow EMA
            //--- trade volume
            input double TradeLot      = 0.1;    // Lot size
            //--- L1 filter parameters
            input int    L1TotalBars    = 1000;  // Total bars for L1 filter
            input bool   L1FilterOpen  = false;  // Use filter for Open
            input bool   L1FilterClose = false;  // Use filter for Close
            input double L1CoefLambda  = 0.2;    // Lambda in lambda_max units
            //--- save statistics
            input bool   SaveStatistics = false; // Save statistics to file
            //---
            #define EMA_MAGIC 1234503
            #include <Trade\Trade.mqh>
            CTrade ExtTrade;
            int    ExtHandle = INVALID_HANDLE;
            bool   ExtHedging = false;
            int    FastHandle, SlowHandle;
            string ExtStrategyName="EMA";
            string ExtStrategyFileName="";
            //+------------------------------------------------------------------+
            //| Check new bar                                                    |
            //+------------------------------------------------------------------+
            bool IsNewBar()
              {
               static datetime last_time=0;
               datetime t[1];
            //---
               if(CopyTime(_Symbol,_Period,0,1,t)>0)
                 {
                  if(t[0]!=last_time)
                    {
                     last_time=t[0];
                     return(true);
                    }
                 }
               return(false);
              }
            //+------------------------------------------------------------------+
            //| CheckTrendL1                                                     |
            //+------------------------------------------------------------------+
            double CheckTrendL1()
              {
               int max_bars=L1TotalBars;
               MqlRates rates_data[];
               ArrayResize(rates_data,max_bars);
               ArraySetAsSeries(rates_data,false);
               if(CopyRates(_Symbol,_Period,0,max_bars,rates_data) != max_bars)
                 {
                  Print("CopyRates failed for L1Trend");
                  return(0);
                 }
            //--- prepare data (close prices vector)
               int data_count=max_bars;
               vector<double> data_close;
               data_close.Resize(data_count);
               for(int i=0; i<data_count; i++)
                  data_close[i] = rates_data[i].close;
            //--- calculate L1 filter
               vector<double> data_filtered;
               data_filtered.Resize(data_count);
               double dp=0.0;
               bool res=data_close.L1TrendFilter(L1CoefLambda,true,data_filtered);
               if(res)
                  dp = data_filtered[data_count-1] - data_filtered[data_count-2];
            //---
               return(dp);
              }
            //+------------------------------------------------------------------+
            //| GetTradeSignal (2EMA crossover)                                  |
            //+------------------------------------------------------------------+
            bool GetTradeSignal(ENUM_ORDER_TYPE &signal)
              {
               signal=WRONG_VALUE;
            //---
               double fast[],slow[];
               ArrayResize(fast,2);
               ArrayResize(slow,2);
            //---
               ArraySetAsSeries(fast,true);
               ArraySetAsSeries(slow,true);
            //---
               if(CopyBuffer(FastHandle,0,1,2,fast)!=2)
                  return(false);
            //---
               if(CopyBuffer(SlowHandle,0,1,2,slow)!=2)
                  return(false);
            //---
               if(fast[1]<slow[1] && fast[0]>slow[0])
                  signal=ORDER_TYPE_BUY;
               if(fast[1]>slow[1] && fast[0]<slow[0])
                  signal=ORDER_TYPE_SELL;
            //---
               return(true);
              }
            //+------------------------------------------------------------------+
            //| CheckForOpen                                                     |
            //+------------------------------------------------------------------+
            void CheckForOpen()
              {
               ENUM_ORDER_TYPE signal;
            //---
               if(!GetTradeSignal(signal) || signal==WRONG_VALUE)
                  return;
            //---
               if(L1FilterOpen)
                 {
                  double dp=CheckTrendL1();
            //---
                  if(signal==ORDER_TYPE_BUY && dp<0)
                     return;
                  if(signal==ORDER_TYPE_SELL && dp>0)
                     return;
                 }
            //---
               if(!TerminalInfoInteger(TERMINAL_TRADE_ALLOWED) || Bars(_Symbol,_Period)<L1TotalBars)
                  return;
            //---
               double price=(signal==ORDER_TYPE_BUY)
                            ? SymbolInfoDouble(_Symbol,SYMBOL_ASK)
                            : SymbolInfoDouble(_Symbol,SYMBOL_BID);
            //---
               ExtTrade.PositionOpen(_Symbol,signal,TradeLot,price,0,0);
              }
            //+------------------------------------------------------------------+
            //| CheckForClose                                                    |
            //+------------------------------------------------------------------+
            void CheckForClose()
              {
            //--- check position
               if(!PositionSelect(_Symbol))
                  return;
            //--- check position magic
               if(PositionGetInteger(POSITION_MAGIC)!=EMA_MAGIC)
                  return;
            //--- check trade signal
               ENUM_ORDER_TYPE signal;
               if(!GetTradeSignal(signal))
                  return;
            //---
               long type=PositionGetInteger(POSITION_TYPE);
               bool close_signal=false;
            //---
               if(type==POSITION_TYPE_BUY && signal==ORDER_TYPE_SELL)
                  close_signal=true;
            //---
               if(type==POSITION_TYPE_SELL && signal==ORDER_TYPE_BUY)
                  close_signal=true;
            //--- check L1 filter
               if(L1FilterClose)
                 {
                  double dp = CheckTrendL1();
                  if(type == POSITION_TYPE_BUY && dp > 0)
                    {
                     close_signal = false;
                     PrintFormat("Close BUY signal cancelled by L1 trend dp=%.5f", dp);
                    }
                  if(type == POSITION_TYPE_SELL && dp < 0)
                    {
                     close_signal = false;
                     PrintFormat("Close SELL signal cancelled by L1 trend dp=%.5f", dp);
                    }
                 }
            //---
               if(close_signal)
                  ExtTrade.PositionClose(_Symbol,3);
              }
            //+------------------------------------------------------------------+
            //| SelectPosition                                                   |
            //+------------------------------------------------------------------+
            bool SelectPosition()
              {
               bool res = false;
               if(ExtHedging)
                 {
                  uint total = PositionsTotal();
                  for(uint i=0; i<total; i++)
                    {
                     string sym = PositionGetSymbol(i);
                     if(sym == _Symbol && PositionGetInteger(POSITION_MAGIC)==EMA_MAGIC)
                       {
                        res = true;
                        break;
                       }
                    }
                 }
               else
                 {
                  if(PositionSelect(_Symbol))
                     res = (PositionGetInteger(POSITION_MAGIC)==EMA_MAGIC);
                 }
               return(res);
              }
            //+------------------------------------------------------------------+
            //| Expert initialization                                            |
            //+------------------------------------------------------------------+
            int OnInit()
              {
               ExtHedging = (AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING);
               ExtTrade.SetExpertMagicNumber(EMA_MAGIC);
               ExtTrade.SetMarginMode();
               ExtTrade.SetTypeFillingBySymbol(_Symbol);
            //--- prepare indicators
               FastHandle=iMA(_Symbol,_Period,FastEMA,0,MODE_EMA,PRICE_CLOSE);
               SlowHandle=iMA(_Symbol,_Period,SlowEMA,0,MODE_EMA,PRICE_CLOSE);
               if(FastHandle==INVALID_HANDLE||SlowHandle==INVALID_HANDLE)
                  return(INIT_FAILED);
            //--- prepare filename
               ExtStrategyFileName=PrepareStrategyFileName(ExtStrategyName);
            //--- delete old file if exists
               if(FileIsExist(ExtStrategyFileName))
                  FileDelete(ExtStrategyFileName);
            //---
               return(INIT_SUCCEEDED);
              }
            //+------------------------------------------------------------------+
            //| PrepareStrategyFileName                                          |
            //+------------------------------------------------------------------+
            string PrepareStrategyFileName(string strategy_name)
              {
               int v=0;
               if(L1FilterOpen)
                  v=v | 1;
            //---
               if(L1FilterClose)
                  v=v | 2;
            //---
               string filename=IntegerToString(v)+"_"+strategy_name+"_"+_Symbol+".txt";
               return(filename);
              }
            //+------------------------------------------------------------------+
            //| Save account statistics to file                                  |
            //+------------------------------------------------------------------+
            void SaveAccountStatistics()
              {
            //--- check file name
               if(ExtStrategyFileName=="")
                  return;
            //---
               int file=FileOpen(ExtStrategyFileName,FILE_WRITE|FILE_READ|FILE_TXT|FILE_SHARE_WRITE|FILE_ANSI);
               if(file==INVALID_HANDLE)
                 {
                  Print("File open error: ",GetLastError());
                  return;
                 }
            //--- append
               FileSeek(file,0,SEEK_END);
            //--- account data
               double balance     = AccountInfoDouble(ACCOUNT_BALANCE);
               double equity      = AccountInfoDouble(ACCOUNT_EQUITY);
               double margin      = AccountInfoDouble(ACCOUNT_MARGIN);
               double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
               double margin_lvl  = AccountInfoDouble(ACCOUNT_MARGIN_LEVEL);
            //--- volume
               double volume=0.0;
               if(PositionSelect(_Symbol))
                  volume=PositionGetDouble(POSITION_VOLUME);
            //--- time
               datetime t[1];
               if(CopyTime(_Symbol,_Period,0,1,t)<=0)
                 {
                  FileClose(file);
                  return;
                 }
               double current_close[1];
               if(CopyClose(_Symbol,_Period,0,1,current_close)<=0)
                 {
                  FileClose(file);
                  return;
                 }
               string line=StringFormat("%s;%.2f;%.2f;%.2f;%.2f;%.2f;%.2f;%f",TimeToString(t[0],TIME_DATE|TIME_SECONDS),
                                        balance,equity,margin,free_margin,margin_lvl,volume,current_close[0]);
            //---
               FileWrite(file,line);
            //---
               FileClose(file);
              }
            //+------------------------------------------------------------------+
            //| Expert OnTick function                                           |
            //+------------------------------------------------------------------+
            void OnTick()
              {
            //--- trade only at new bar
               if(!IsNewBar())
                  return;
            //--- check trade conditions
               if(SelectPosition())
                  CheckForClose();
               else
                  CheckForOpen();
            //--- save account statistics
               if(SaveStatistics)
                  SaveAccountStatistics();
              }
            //+------------------------------------------------------------------+
            //| Expert deinitialization                                          |
            //+------------------------------------------------------------------+
            void OnDeinit(const int reason)
              {
            //--- save account statistics
               if(SaveStatistics)
                  SaveAccountStatistics();
            //--- release indicator handles
               if(FastHandle!=INVALID_HANDLE)
                  IndicatorRelease(FastHandle);
            //---
               if(SlowHandle!=INVALID_HANDLE)
                  IndicatorRelease(SlowHandle);
              }
            //+------------------------------------------------------------------+

            Le impostazioni, i parametri e i risultati dei test dell'Expert Advisor EMAFilteredL1.mq5 sono illustrati nelle figure 54–56.

            Fig.54. Impostazioni di test per l'Expert Advisor EMAFilteredL1.mq5

            Fig.54. Impostazioni di test per l'Expert Advisor EMAFilteredL1.mq5


            Fig. 55. Parametri di test per l'Expert Advisor EMAFilteredL1.mq5

            Fig.55. Parametri di test per l'Expert Advisor EMAFilteredL1.mq5


            Fig. 56. Risultati dei test per l'Expert Advisor EMAFilteredL1.mq5

            Fig.56. Risultati dei test per l'Expert Advisor EMAFilteredL1.mq5


            Per analizzare l'efficacia dei filtri L1, è necessario eseguire in sequenza l'Expert Advisor nel tester con diverse modalità di filtro:

            Fig.57. Parametri di test per il salvataggio dei risultati su file per l'Expert Advisor EMAFilteredL1.mq5

            Fig.57. Parametri di test per il salvataggio dei risultati su file per l'Expert Advisor EMAFilteredL1.mq5


            Di conseguenza, nella cartella del tester verranno creati i file; questi dovranno essere copiati nella directory specificata nello script Python PlotData.py quindi si esegue lo script con le seguenti impostazioni: symbol = "EURUSD", name_strategy = "EMA".


            Fig.58. File con i risultati dei test dell'Expert Advisor EMAFilteredL1.mq5 in diverse modalità di filtro dei segnali di trading

            Fig.58. File contenenti i risultati dei test dell'Expert Advisor EMAFilteredL1.mq5 in diverse modalità di filtro dei segnali di trading.



            3.5.4.1. Risultati dell'applicazione dei filtri L1 ai segnali di trading della strategia EMA.

            I risultati sono illustrati nelle figure 59-61.

            Fig.59. Grafici del saldo dell'Expert Advisor EMAFilteredL1.mq5 per le diverse modalità di filtro

            Fig.59. Grafici del saldo dell'Expert Advisor EMAFilteredL1.mq5 per le diverse modalità di filtro.


            Fig.60. Grafici dell’equity dell'Expert Advisor EMAFilteredL1.mq5 per diverse modalità di filtro

            Fig.60. Grafici dell’equity dell'Expert Advisor EMAFilteredL1.mq5 per diverse modalità di filtro


            Fig.61. Grafici combinati di saldo e dell’equity dell'Expert Advisor EMAFilteredL1.mq5 per le diverse modalità di filtro

            Fig.61. Grafici combinati di saldo e dell’equity dell'Expert Advisor EMAFilteredL1.mq5 per le diverse modalità di filtro.




            3.5.5. Riepilogo sull'utilizzo del filtro L1 nelle strategie di trading MovingAverage, MACD, ADX ed EMA.

            Negli esempi considerati, le strategie di trading sono state testate sulla coppia di valute EURUSD, timeframe H1, per l'anno 2025 (Fig. 62).

            Fig. 62. Grafico dei prezzi EURUSD durante il periodo di prova (2025, H1, prezzi di chiusura)

            Fig.62. Grafico dei prezzi EURUSD durante il periodo di prova (2025, H1, prezzi di chiusura)


            Fig.63. Grafici del saldo delle strategie MovingAverage, MACD, ADX ed EMA con le diverse modalità di filtro

            Fig.63. Grafici del saldo delle strategie MovingAverage, MACD, ADX ed EMA con le diverse modalità di filtro.


            L'analisi delle strategie MovingAverage, MACD, ADX ed EMA ha dimostrato che i risultati migliori si ottengono applicando il filtro L1 nella fase di chiusura della posizione (evidenziato in verde sui grafici). L'utilizzo del filtro in uscita sopprime efficacemente le inversioni provocate da rumore e i falsi segnali, consentendo di mantenere le posizioni nella direzione di un trend stabile. Ciò si traduce in un aumento dei profitti e del Profit Factor, nonché in una riduzione del drawdown massimo.

            L'utilizzo del filtro L1 nella fase di apertura della posizione (evidenziato in arancione) si è rivelato meno efficace, poiché un filtro aggiuntivo limita il numero di ingressi e porta a perdere parte dei movimenti profittevoli senza un miglioramento proporzionale della qualità dei trade.


            Fig.64.  Grafici del saldo e dell’equity delle strategie MovingAverage, MACD, ADX ed EMA con le diverse modalità di filtro

            Fig.64. Grafici del saldo e dell’equity delle strategie MovingAverage, MACD, ADX ed EMA con le diverse modalità di filtro.


            Pertanto, il filtro L1 dei segnali di trading alla chiusura della posizione aumenta la stabilità del sistema di trading, riduce la sensibilità alle fluttuazioni di prezzo a breve termine e migliora il rapporto profitto/rischio. Rispetto alle medie mobili classiche, il filtro L1 distingue meglio le correzioni temporanee dalle reali inversioni di trend, consentendo un utilizzo più efficiente delle strategie di trend following.

            Occorre prestare attenzione al comportamento delle curve di Saldo e Equity quando si fa trading con segnali allineati al filtro di trend L1 (Fig. 64). Quando si opera seguendo il trend, l’Equity è spesso superiore al Saldo, il che migliora significativamente gli indicatori di rischio e riduce il drawdown. Pertanto, l'allineamento con il trend migliora anche le caratteristiche dei sistemi di trading (riduce il drawdown e i rischi).

            Inoltre, allineando i segnali di trading al trend L1, il numero di trade diminuisce mentre la loro qualità aumenta, il che influisce positivamente anche sulle caratteristiche statistiche generali delle strategie di trading.


            № Strategia Profitto netto totale, USD % Buy and Hold
            1  Buy and Hold 1363.8 100%
            2  MovingAverage (senza filtri) 1001.03 73.4%
            3  MovingAverage (filtro L1 in ingresso) 107.65 7.89%
            4  MovingAverage (filtro L1 in uscita) 1342.5 98.43%
            5  MovingAverage (filtro L1 in ingresso + uscita) 986.16 72.31 %
            6  MACD (senza filtri) 997.79 73.16 %
            7  MACD (filtro L1 in ingresso) 140.13 10.27 %
            8  MACD (filtro L1 in uscita) 1359.52 99.69 %
            9  MACD (filtro L1 in ingresso + uscita) 697.54 51.15 %
            10  ADX (senza filtri) 791.99 58.07 %
            11  ADX (filtro L1 in ingresso) -50.9 -3.73 %
            12  ADX (filtro L1 in uscita) 940.39 68.95 %
            13  ADX (filtro L1 in ingresso + uscita) 430.05 31.53 %
            14  EMA (senza filtri) 957.3 70.19 %
            15  EMA (filtro L1 in ingresso) -173.35 -12.71 %
            16  EMA (filtro L1 in uscita) 1258.99 92.31 %
            17  EMA (filtro L1 in ingresso + uscita) -131.41 -9.64%

            Tabella 4. Risultati di profitto totali derivanti dall'utilizzo del filtro L1 nelle strategie MovingAverage, MACD, ADX ed EMA rispetto alla strategia Buy and Hold.


            Secondo la Tabella 4, l'utilizzo del filtro L1 in fase di chiusura della posizione ha migliorato la redditività per tutte le strategie.

            Se si considera il risultato della strategia Buy and Hold ($1363,8) come il 100% dell'intero movimento di tendenza, si ottiene:

            1. Il profitto derivante da MovingAverage è aumentato dal 73.4% al 98.43%;
            2. Il profitto MACD è aumentato dal 73.16% al 99.69%;
            3. Il profitto di ADX è aumentato dal 58.07% al 68.5%;
            4. L'utile dell'EMA è aumentato dal 70.19% al 92.31%.

            Come si può notare, l'utilizzo del filtro L1 ha permesso alle strategie Media Mobile, MACD ed EMA di aumentare i profitti del 22-26%, catturando la maggior parte del movimento del trend (98.43%, 99.69% e 92.31%) e avvicinandosi al risultato della strategia Buy and Hold. L'utile della strategia ADX è aumentato del 10%.

            Negli esempi sono state considerate le strategie con parametri che hanno prodotto i valori di saldo più elevati (ovvero, tra le migliori soluzioni possibili). Sono evidenziati in blu. I risultati dimostrano che anche queste soluzioni più profittevoli possono essere ulteriormente migliorate mediante un ulteriore filtraggio dei segnali di trading attraverso l'allineamento con il trend L1. Alcune strategie hanno mostrato solo lievi miglioramenti (per ADX, le curve verdi sono vicine a quelle blu, indicando la prossimità alla soluzione di bilanciamento ottimale). La qualità dei segnali di trading di una strategia (ottimalità dei parametri selezionati) può essere valutata in base al miglioramento ottenuto con tale filtro L1.

            In particolare, in questo caso è stato preso in considerazione un mercato EURUSD caratterizzato da un forte trend (Fig. 62). Per altri regimi e strumenti di mercato, i risultati saranno diversi. Inoltre, il trend L1 è stato costruito sul timeframe H1 con un parametro di regolarizzazione λ = 0.2·λmax. Per altri strumenti e timeframe, i valori appropriati di questo coefficiente possono essere stimati utilizzando gli indicatori di trend L1.


            Conclusioni

            Il filtro di tendenza L1 ha dimostrato la sua utilità pratica come strumento per separare il rumore locale dai reali cambiamenti di trend.

            Il metodo produce un andamento lineare a tratti con punti di rottura automatici e una comoda scala di regolazione tramite λmax, che elimina il problema dell'adattamento manuale dei parametri.

            A livello di integrazione pratica, è stato fornito un toolkit completo: funzioni per il calcolo di λmax e del filtro L1, tre indicatori (L1Trend, L1TrendSlope, L1TrendSlopeSign), sette indicatori di volatilità del trend L1 (L1Volatility, L1VolatilitySmoothed, L1VolatilityAbsolute, L1VolatilityNormalized, L1VolatilityNormalizedSmoothed, L1VolatilityRegime, L1VolatilityRegimeColor), modelli di Expert Advisor e un protocollo di test riproducibile (quattro modalità: nessun filtro, filtro di ingresso, filtro di uscita, entrambi i filtri; salvataggio dei risultati e script di visualizzazione Python).

            Va inoltre notato che il filtro di trend L1 può essere utilizzato per l'etichettatura dei dati nell'apprendimento automatico. In particolare, nell'articolo “Developing Trend Trading Strategies Using Machine Learning”, la determinazione del trend viene eseguita utilizzando derivati dei prezzi smussati dal filtro Savitzky-Golay. Un approccio simile può essere implementato utilizzando il filtro L1, dove il trend viene approssimato da funzioni lineari a tratti e l'intensità del trend su ciascun segmento è naturalmente correlata alla pendenza del segmento corrispondente.


            Consigli pratici:

            • Utilizzare la regolarizzazione relativa: λ = coef_lambda_max · λmax. Per la maggior parte delle attività, utilizzare un coefficiente compreso tra 0.04 e 0.25; per dettagli più precisi, ≈0.02–0.04; per approssimazioni grossolane e rilevamento del regime, ≈0.12–0.25.
            • Nella maggior parte dei casi, il filtro L1 risulta più efficace se applicato alla chiusura delle posizioni (mantenendo i trend profittevoli e riducendo le uscite premature). Applicandolo alle entrate, spesso si riduce il numero di trade senza un miglioramento proporzionale della qualità.
            • Per l'analisi del trend corrente, si utilizza una semplice regola: delta = x_filtered[last] − x_filtered[last−1]. Il segno di delta indica la direzione del trend L1 dominante.

            Limitazioni: l'effetto dipende dallo strumento, dal timeframe e dal regime di mercato; è necessaria una convalida su dati storici con metriche selezionate.

            I moduli MQL5 e il protocollo di test proposti consentono di verificare rapidamente le ipotesi e di selezionare i parametri di funzionamento più adatti per uno specifico sistema di trading.

            Tutti i codici dell'articolo sono disponibili anche nel progetto pubblico "MQL5\Shared Projects\ L1Trend".


            Esempi

            Tipo File Descrizione
            Script MQL5\Scripts\TestL1Trend.mq5
            Script di test per il calcolo del trend L1 sui dati del modello (random walk)
            Script MQL5\Scripts\TestL1TrendFloatDouble.mq5 Script di test per il calcolo del trend L1 sui dati del modello (random walk) per vettori double e float
            Script MQL5\Scripts\TestL1TrendFilterSP500.mq5 Script di test per il calcolo del trend L1 sui dati di quotazione dell'indice SP500
            File di dati MQL5\Files\snp500.txt
            File di dati per lo script di test (log della serie storica dei prezzi dell'indice SP500)
            Script MQL5\Scripts\TestScalingBrownianMotion.mq5 Script per il calcolo della legge di potenza di λmax per il moto browniano
            Script MQL5\Scripts\TestScalingSymbol.mq5 Script per il calcolo della legge di potenza di λmax per le serie di prezzi di un dato simbolo
            Indicatore MQL5\Indicators\L1TrendFilter.mq5 Indicatore per il calcolo del trend L1
            Indicatore
            MQL5\Indicators\L1TrendFilter_Slope.mq5 Indicatore per il calcolo del tasso di variazione del trend L1
            Indicatore
            MQL5\Indicators\L1TrendFilter_SlopeSign.mq5 Indicatore per il calcolo del segno di variazione del trend L1
            Indicatore
            MQL5\Indicators\L1Volatility.mq5 Indicatore per il calcolo della volatilità residua (differenza tra i prezzi di chiusura e il valore del trend L1)
            Indicatore
            MQL5\Indicators\L1VolatilitySmoothed.mq5 Indicatore per il calcolo della volatilità residua smussata
            Indicatore
            MQL5\Indicators\L1VolatilityAbsolute.mq5 Indicatore per il calcolo della volatilità assoluta
            Indicatore
            MQL5\Indicators\L1VolatilityNormalized.mq5 Indicatore per il calcolo della volatilità normalizzata utilizzando l'ATR (Average True Range) e il trend L1.
            Indicatore
            MQL5\Indicators\L1VolatilityNormalizedSmoothed.mq5 Indicatore per il calcolo della volatilità normalizzata smussata
            Indicatore
            MQL5\Indicators\L1VolatilityRegime.mq5 Indicatore di rilevamento del regime di mercato
            Indicatore
            MQL5\Indicators\L1VolatilityRegimeColor.mq5 Versione a colori dell'indicatore di rilevamento del regime di mercato
            Expert Advisor MQL5\Experts\MovingAverageFilteredL1.mq5 Expert Advisor per il trading basato sulla strategia MovingAverage con filtro L1
            Expert Advisor
            MQL5\Experts\MACDFilteredL1.mq5 Expert Advisor per il trading basato sulla strategia MACD con filtro L1
            Expert Advisor MQL5\Experts\ADXFilteredL1.mq5 Expert Advisor per il trading basato sulla strategia ADX con filtro L1
            Expert Advisor MQL5\Experts\EMAFilteredL1.mq5 Expert Advisor per il trading basato sull'incrocio di due EMA con filtro L1
            script Python MQL5\Scripts\PlotData.py Script Python per analizzare l'efficacia dell'applicazione del filtro L1 ai segnali di trading.

            Tabella 5. Descrizione dei codici di programma utilizzati nell'articolo



            Tradotto dal russo da MetaQuotes Ltd.
            Articolo originale: https://www.mql5.com/ru/articles/21142

            Ultimi commenti | Vai alla discussione (9)
            Quantum
            Quantum | 20 apr 2026 a 20:12
            Renat Akhtyamov #:

            Più o meno così:

            La suddivisione in trend dipende in larga misura dal parametro di regolarizzazione lambda: minore è il valore di lambda, più brevi sono i trend che è in grado di rilevare.
            Negli esempi in esame sono stati utilizzati valori fissi di lambda espressi in unità lambda=0,2*lambda_max. Il calcolo in unità lambda_max consente in parte di adattarsi ai dati. Il valore stesso di lambda_max dipende dalla geometria della serie (dalle ampiezze relative), ovvero dalla volatilità.

            Bisogna tenere presente che un trend presenta diverse fasi e un proprio ciclo di vita. Pertanto , è necessario un meccanismo di adattamento al trend corrente, ovvero gestire in qualche modo lambda e individuare la suddivisione ottimale del trend: questo problema non è ancora stato risolto.

            Se la strategia stessa su un determinato intervallo non genera profitti, nemmeno il filtro potrà essere d’aiuto.
            I risultati migliori dovrebbero essere ottenuti in un mercato con un trend ideale; un esempio è stato il seguente: EURUSD, 2025, H1 (parametri ottimali: MovingAverage period=61).

            EURUSD

            Opzioni del tester

            Parametri di prova

            Risultati dei test senza filtro

            Filtro L1 Close

            L1 Chiudi filtro

            Qui vediamo che il filtro di uscita ha contribuito ad aumentare il profitto nel tratto di mercato in trend.


            Variante della stessa strategia con l’aggiunta di posizioni durante le correzioni:

            Opzioni di test con "aggiungi"

            Opzioni di test con "Aggiungi posizioni"

            senza aggiunte:

            Con aggiunte:


            Gli intervalli con mercato flat contengono piccoli trend locali e, per tenerne conto correttamente, è necessario utilizzare valori più bassi del parametro lambda (quando viene utilizzato come filtro di uscita).
            Inoltre, i valori ottimali dei parametri MovingAverage nell'intervallo di mercato flat devono essere diversi. Cioè, i periodi ottimali delle medie nel secondo intervallo sono cambiati (ma durante l’ottimizzazione nel tester, i parametri individuati generano il massimo profitto rispetto a tutti gli altri sull’intero intervallo di ottimizzazione).

            Quantum
            Quantum | 20 apr 2026 a 21:08

            Verifichiamo i risultati sull'intervallo flat con diversi valori di lambda.

            Senza filtri:

            Con filtro di uscita lambda=0,2*lambda_max

            Con filtro lambda = 0,001 lambda_max (tendenze più sottili)

            Pertanto, nel tratto flat con lambda = 0,001 lambda_max si riesce a migliorare il risultato ottenuto senza filtri e a tenere conto dei piccoli trend locali.

            Tuttavia, la variante con filtro lambda=0,2*lambda_max ha mostrato in questo caso una redditività inferiore rispetto alla strategia senza filtri.

            Quantum
            Quantum | 20 apr 2026 a 21:30

            Opzione con l'aggiunta di posizioni (con lambda diversi) sui trend locali all'interno di un flat

            Senza filtri:


            Con filtro lambda=0,2*lambda_max e apertura di posizioni durante le correzioni:

            Con filtro lambda=0,001*lambda_max e aggiunta durante le correzioni:

            La variante con filtro lambda=0,2*lambda_max e aggiunta durante le correzioni ha mostrato un risultato migliore rispetto alla variante senza filtri.

            L'aggiunta di piccoli trend locali (lambda=0,001*lambda_max) durante le correzioni all'interno dell'intervallo di flat ha permesso di aumentare il profitto della strategia originale senza filtri (e di migliorare il profitto della variante con lambda=0,2*lambda_max).

            Renat Akhtyamov
            Renat Akhtyamov | 21 apr 2026 a 08:06
            Quantum #:

            Variante con l'aggiunta di posizioni (con lambda diversi) sui trend locali all'interno di un flat

            Senza filtri:


            Con filtro lambda=0,2*lambda_max e aggiunta durante le correzioni:

            Con un filtro lambda=0,001*lambda_max e l'aggiunta delle correzioni:

            La variante con filtro lambda=0,2*lambda_max e l'aggiunta alle correzioni ha mostrato un risultato migliore rispetto alla variante senza filtri.

            L'aggiunta alle correzioni di piccole tendenze locali (lambda = 0,001*lambda_max) all'interno dell'intervallo di fluttuazione ha permesso di aumentare il profitto della strategia originale senza filtri (e di migliorare il profitto della variante con lambda = 0,2*lambda_max).

            Fate trading, almeno su un conto demo

            la comprensione arriverà con l’esperienza e dopo aver atteso 10 mesi di lavoro infruttuoso

            Vladimir196803
            Vladimir196803 | 25 lug 2026 a 23:50

            Grazie per averci presentato due nuove funzioni per i vettori. Sono molto utili. Avete svolto un lavoro notevole, ma purtroppo tutti i vostri indicatori presentano un forte redrawing: anche sui dati storici i picchi si trasformano in valori opposti o scompaiono. Non a caso li avete paragonati al filtro di Hodrick-Prescott, che si ridisegna ; si potrebbero paragonare anche alla TMA centrata e ad alcuni altri. Sui timeframe più lunghi, una rottura può comparire 300 candele indietro; si potrebbe fare trading per un anno (perdendo il deposito) contro il trend. Per il trading reale né gli indicatori né gli expert advisor basati su di essi sono adatti, poiché cercano di prevedere il futuro.

            In realtà, sulla base di queste funzioni è possibile creare indicatori che non si ridisegnano. Nel caso dei vostri indicatori, basta semplicemente attendere 1000 candele e ripetere il calcolo a partire dalla fine del periodo precedente, ma ciò è inutile ai fini dell’applicazione pratica; esistono alternative più interessanti. Buona fortuna per le vostre ricerche.

            Arriva il Nuovo MetaTrader 5 e MQL5 Arriva il Nuovo MetaTrader 5 e MQL5
            Questa è solo una panoramica di MetaTrader 5. Non posso descrivere tutte le nuove funzionalità del sistema per un periodo di tempo così breve: i test sono iniziati il 09.09.2009. Questa è una data simbolica e sono sicuro che sarà un numero fortunato. Sono passati alcuni giorni da quando ho ricevuto la versione beta del terminale MetaTrader 5 e MQL5. Non sono riuscito a provare tutte le sue funzionalità, ma sono già sorpreso.
            Pubblica il codice del tuo articolo su MQL5 Algo Forge in 10 minuti: Una guida passo passo Pubblica il codice del tuo articolo su MQL5 Algo Forge in 10 minuti: Una guida passo passo
            L'articolo fornisce una guida passo passo su come migrare il codice da un progetto pubblicato a un progetto MQL5 Algo Forge completo. Configurerai l'ambiente e l'autenticazione in MetaEditor, creerai un progetto in Shared Projects, selezionerai il tipo, organizzerai i file, aggiungerai il file README.md, controllerai la codifica e la compilazione, effettuerai il commit delle modifiche su Git e renderai il repository accessibile pubblicamente. L'articolo contribuisce a costruire una struttura di lavoro e a preservare la cronologia delle versioni per facilitare la consultazione da parte dei lettori.
            Utilizza i canali MQL5.community e le chat di gruppo Utilizza i canali MQL5.community e le chat di gruppo
            Il sito web MQL5.com riunisce trader di tutto il mondo. Gli utenti pubblicano articoli, condividono codici gratuiti, vendono prodotti nel Market, offrono servizi da freelance e copiano segnali di trading. Puoi comunicare con loro sul Forum, nelle chat dei trader e nei canali MetaTrader.
            Dal “Best Pass” a soluzioni robuste: Esplorazione della superficie di ottimizzazione in MetaTrader 5 Dal “Best Pass” a soluzioni robuste: Esplorazione della superficie di ottimizzazione in MetaTrader 5
            L'articolo esamina un approccio ingegneristico all'ottimizzazione di un Expert Advisor in MetaTrader 5: dalla raccolta di metriche personalizzate tramite Optimization Frames all'analisi della superficie dei parametri. Un semplice modello EMA/RSI basato sugli eventi dimostra l'esportazione in formato CSV, lo smussamento e la valutazione della stabilità locale in Python. L'obiettivo è individuare aree di configurazione stabili e convalidarle tramite ottimizzazione forward per un'implementazione affidabile.