La matematica nel trading: rapporti di Sharpe e Sortino

MetaQuotes | 11 aprile, 2022

Il ritorno sugli investimenti è l'indicatore più ovvio che gli investitori e i trader principianti utilizzano per l'analisi dell'efficienza del trading. I trader professionisti utilizzano strumenti più affidabili per analizzare le strategie, come i rapporti di Sharpe e Sortino, tra gli altri. In questo articolo prenderemo in considerazione semplici esempi per capire come vengono calcolati questi rapporti. Le specifiche della valutazione delle strategie di trading sono state precedentemente considerate nell'articolo "La matematica nel trading. Come stimare i risultati di trading". Si consiglia di leggere l'articolo per aggiornare le conoscenze o per imparare qualcosa di nuovo.


Rapporto di Sharpe

Gli investitori e i trader esperti spesso utilizzano più strategie e investono in asset diversi nel tentativo di ottenere risultati costanti. Questo è uno dei concetti di investimento intelligente che implica la creazione di un portafoglio di investimenti. Ogni portafoglio di titoli/strategie ha i propri parametri di rischio e rendimento, che in qualche modo dovrebbero essere confrontati.

Uno degli strumenti più referenziati per tale confronto è il rapporto di Sharpe, sviluppato nel 1966 dal premio Nobel William F. Sharpe. Il calcolo del rapporto utilizza le metriche di performance di base, tra cui il tasso di rendimento medio, la deviazione standard del rendimento e il rendimento privo di rischio.

Lo svantaggio del rapporto di Sharpe è che i dati di origine utilizzati per l'analisi devono essere distribuiti normalmente. In altre parole, il grafico della distribuzione dei rendimenti dovrebbe essere simmetrico e non dovrebbe avere picchi o cali bruschi.

Il rapporto di Sharpe viene calcolato utilizzando la seguente formula:

Rapporto di Sharpe = (Rendimento - SenzaRischio)/Std

Dove:


Rendimento

Il rendimento è calcolato come variazione del valore delle attività in un certo intervallo. I valori di rendimento vengono utilizzati per lo stesso periodo di tempo per il quale viene calcolato il rapporto di Sharpe. Solitamente viene considerato il rapporto di Sharpe annuale, ma è anche possibile calcolare valori trimestrali, mensili o anche giornalieri. Il rendimento si calcola con la seguente formula:

Rendimento[i] = (Chiusura[i]-Chiusura[i-1])/Chiusura[i-1]

Dove:

In altre parole, il rendimento può essere scritto come variazione relativa del valore delle attività nel periodo selezionato:

Rendimento[i] = Delta[i]/Precedente

Dove:

      Per calcolare il rapporto di Sharpe per un periodo di un anno utilizzando valori giornalieri, dobbiamo utilizzare i valori di rendimento per ogni giorno dell'anno e calcolare il rendimento giornaliero medio come somma dei rendimenti divisa per il numero di giorni del calcolo. 

      Rendimento = Somma(Rendimento[i])/N

      dove N è il numero di giorni.


      Rendimento privo di rischio

      Il concetto di rendimento privo di rischio è condizionato, poiché c'è sempre un rischio. Poiché il rapporto di Sharpe viene utilizzato per confrontare diverse strategie/portafogli negli stessi intervalli di tempo, nella formula è possibile utilizzare il rendimento zero senza rischio. Cioè,

      Senza rischio = 0


      Deviazione standard o rendimento

      La deviazione standard mostra come le variabili casuali si discostano da un valore medio. Innanzitutto, viene calcolato il valore medio del rendimento, quindi vengono sommate le deviazioni al quadrato dei rendimenti dal valore medio. La somma risultante viene divisa per il numero di rendimenti per ottenere la dispersione. La radice quadrata della dispersione è la deviazione standard.

      D = Somma((Rendimento - Rendimento[i])^2 )/N
      
      STD = SQRT(D)
      

      Un esempio di calcolo della deviazione standard è fornito nell'articolo menzionato in precedenza.


      Calcolare il rapporto di Sharpe su qualsiasi intervallo di tempo e convertirlo in un valore annuale

      Il metodo di calcolo del rapporto di Sharpe non è cambiato dal 1966. La variabile ha ricevuto il suo nome moderno dopo che questa metodologia di calcolo è stata ampiamente riconosciuta. A quel tempo, le valutazioni della performance di fondi e portafogli si basavano sui rendimenti ottenuti in diversi anni. Successivamente, i calcoli sono stati effettuati sui dati mensili, mentre il rapporto di Sharpe risultante è stato mappato in un valore annuale.

      Il rapporto di Sharpe può essere facilmente ricondotto da diversi periodi e timeframe a un valore annuale. Questo viene fatto moltiplicando il valore risultante per la radice quadrata del rapporto tra l'intervallo annuale e quello corrente. Consideriamo il seguente esempio.

      Supponiamo di aver calcolato il rapporto di Sharpe utilizzando i valori di rendimento giornalieri — SharpeDaily. Il risultato deve essere convertito nel valore annuale SharpeAnnual. Il rapporto annuale è proporzionale alla radice quadrata del rapporto dei periodi, ovvero quanti intervalli giornalieri rientrano in un anno. Poiché ci sono 252 giorni lavorativi in un anno, il rapporto di Sharpe giornaliero basato sul rendimento dovrebbe essere moltiplicato per la radice quadrata di 252. Questo sarà il rapporto di Sharpe annuale:

      SharpeAnnual = SQRT(252)*SharpeDaily // 252 giorni lavorativi in un anno

      Se il valore viene calcolato in base all'intervallo di tempo H1, utilizziamo lo stesso principio: convertiamo prima SharpeHourly in SharpeDaily, quindi calcoliamo il rapporto di Sharpe annuale. Una barra D1 include 24 barre H1, motivo per cui la formula sarà la seguente:

      SharpeDaily = SQRT(24)*SharpeHourly   // 24 ore incluse in D1

      Non tutti gli strumenti finanziari sono negoziati 24 ore su 24. Ma questo non è importante quando si valutano le strategie di trading nel tester per lo stesso strumento finanziario, poiché il confronto viene eseguito per lo stesso intervallo di test e lo stesso periodo di tempo.


      Valutare le strategie utilizzando il rapporto di Sharpe

      A seconda della strategia/performance del portafoglio, il rapporto di Sharpe può assumere valori diversi, anche negativi. La conversione del rapporto di Sharpe in un valore annuale ne consente l'interpretazione in modo classico:
      Valore
       Significato  Descrizione
       Rapporto di Sharpe < 0 Male La strategia non è redditizia
       0 < Rapporto di Sharpe  < 1.0
      Non definito
      Il rischio non è ripagato. Tali strategie possono essere prese in considerazione quando non ci sono alternative
       Rapporto di Sharpe ≥ 1.0
      Bene
      Se il rapporto di Sharpe è maggiore di uno, ciò può significare che il rischio viene ripagato e che il portafoglio/la strategia può mostrare risultati positivi
       Rapporto di Sharpe ≥ 3.0 Molto bene Un valore alto indica che la probabilità di ottenere una perdita in ogni singola operazione è molto bassa

      Non dimentichiamo che il coefficiente di Sharpe è una variabile statistica regolare. Riflette il rapporto tra rendimento e rischio. Pertanto, quando si analizzano diversi portafogli e strategie, è importante correlare il rapporto di Sharpe con i valori consigliati o confrontarlo con i valori rilevanti.


      Calcolo del rapporto di Sharpe per EURUSD, 2020

      Il rapporto di Sharpe è stato originariamente sviluppato per valutare portafogli che di solito sono costituiti da molti titoli. Il valore delle azioni cambia ogni giorno e il valore del portafoglio cambia di conseguenza. Una variazione del valore e dei rendimenti può essere misurata in qualsiasi lasso di tempo. Vediamo i calcoli per EURUSD.

      I calcoli verranno eseguiti su due timeframe, H1 e D1. Quindi, convertiremo i risultati in valori annuali e li confronteremo per vedere se c'è una differenza. Per i calcoli utilizzeremo i prezzi di chiusura delle barre del 2020.

      Codice in MQL5

      //+------------------------------------------------------------------+
      //| Script program start function                                    |
      //+------------------------------------------------------------------+
      void OnStart()
        {
      //---
         double H1_close[],D1_close[];
         double h1_returns[],d1_returns[];
         datetime from = D'01.01.2020';
         datetime to = D'01.01.2021';
         int bars = CopyClose("EURUSD",PERIOD_H1,from,to,H1_close);
         if(bars == -1)
            Print("CopyClose(\"EURUSD\",PERIOD_H1,01.01.2020,01.01.2021 failed. Error ",GetLastError());
         else
           {
            Print("\nCalculate the mean and standard deviation of returns on H1 bars");
            Print("H1 bars=",ArraySize(H1_close));
            GetReturns(H1_close,h1_returns);
            double average = ArrayMean(h1_returns);
            PrintFormat("H1 average=%G",average);
            double std = ArrayStd(h1_returns);
            PrintFormat("H1 std=%G",std);
            double sharpe_H1 = average / std;
            PrintFormat("H1 Sharpe=%G",sharpe_H1);
            double sharpe_annual_H1 = sharpe_H1 * MathSqrt(ArraySize(h1_returns));
            Print("Sharpe_annual(H1)=", sharpe_annual_H1);
           }
      
         bars = CopyClose("EURUSD",PERIOD_D1,from,to,D1_close);
         if(bars == -1)
            Print("CopyClose(\"EURUSD\",PERIOD_D1,01.01.2020,01.01.2021 failed. Error ",GetLastError());
         else
           {
            Print("\nCalculate the mean and standard deviation of returns on D1 bars");     
            Print("D1 bars=",ArraySize(D1_close));
            GetReturns(D1_close,d1_returns);
            double average = ArrayMean(d1_returns);
            PrintFormat("D1 average=%G",average);
            double std = ArrayStd(d1_returns);
            PrintFormat("D1 std=%G",std);
            double sharpe_D1 = average / std;
            double sharpe_annual_D1 = sharpe_D1 * MathSqrt(ArraySize(d1_returns));
            Print("Sharpe_annual(H1)=", sharpe_annual_D1);
           }
        }
      
      //+------------------------------------------------------------------+
      //|  Fills the returns[] array of returns                            |
      //+------------------------------------------------------------------+
      void GetReturns(const double & values[], double & returns[])
        {
         int size = ArraySize(values);
      //--- if less than 2 values, return an empty array of returns
         if(size < 2)
           {
            ArrayResize(returns,0);
            PrintFormat("%s: Error. ArraySize(values)=%d",size);
            return;
           }
         else
           {
            //--- fill returns in a loop
            ArrayResize(returns, size - 1);
            double delta;
            for(int i = 1; i < size; i++)
              {
               returns[i - 1] = 0;
               if(values[i - 1] != 0)
                 {
                  delta = values[i] - values[i - 1];
                  returns[i - 1] = delta / values[i - 1];
                 }
              }
           }
      //---
        }
      //+------------------------------------------------------------------+
      //|  Calculates the average number of array elements                 |
      //+------------------------------------------------------------------+
      double ArrayMean(const double & array[])
        {
         int size = ArraySize(array);
         if(size < 1)
           {
            PrintFormat("%s: Error, array is empty",__FUNCTION__);
            return(0);
           }
         double mean = 0;
         for(int i = 0; i < size; i++)
            mean += array[i];
         mean /= size;
         return(mean);
        }
      //+------------------------------------------------------------------+
      //|  Calculates the standard deviation of array elements             |
      //+------------------------------------------------------------------+
      double ArrayStd(const double & array[])
        {
         int size = ArraySize(array);
         if(size < 1)
           {
            PrintFormat("%s: Error, array is empty",__FUNCTION__);
            return(0);
           }
         double mean = ArrayMean(array);
         double std = 0;
         for(int i = 0; i < size; i++)
            std += (array[i] - mean) * (array[i] - mean);
         std /= size;
         std = MathSqrt(std);
         return(std);
        }  
      //+------------------------------------------------------------------+
      
      /*
      Result
      
      Calculate the mean and standard deviation of returns on H1 bars
      H1 bars:6226
      H1 average=1.44468E-05
      H1 std=0.00101979
      H1 Sharpe=0.0141664
      Sharpe_annual(H1)=1.117708053392263
      
      Calculate the mean and standard deviation of returns on D1 bars
      D1 bars:260
      D1 average=0.000355823
      D1 std=0.00470188
      Sharpe_annual(H1)=1.2179005039019222
      
      */
      

      Codice Python per calcolare usando la Libreria MetaTrader 5

      import math
      from datetime import datetime
      import MetaTrader5 as mt5
      
      # display data on the MetaTrader 5 package
      print("MetaTrader5 package author: ", mt5.__author__)
      print("MetaTrader5 package version: ", mt5.__version__)
      
      # import the 'pandas' module for displaying data obtained in the tabular form
      import pandas as pd
      
      pd.set_option('display.max_columns', 50)  # how many columns to show
      pd.set_option('display.width', 1500)  # max width of the table to show
      # import pytz module for working with the time zone
      import pytz
      
      # establish connection to the MetaTrader 5 terminal
      if not mt5.initialize():
          print("initialize() failed")
          mt5.shutdown()
      
      # set time zone to UTC
      timezone = pytz.timezone("Etc/UTC")
      # create datetime objects in the UTC timezone to avoid the local time zone offset
      utc_from = datetime(2020, 1, 1, tzinfo=timezone)
      utc_to = datetime(2020, 12, 31, hour=23, minute=59, second=59, tzinfo=timezone)
      # get EURUSD H1 bars in the interval 2020.01.01 00:00 - 2020.31.12 13:00 in the UTC timezone
      rates_H1 = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_H1, utc_from, utc_to)
      # also get D1 bars in the interval 2020.01.01 00:00 - 2020.31.12 13:00 in the UTC timezone
      rates_D1 = mt5.copy_rates_range("EURUSD", mt5.TIMEFRAME_D1, utc_from, utc_to)
      # shut down connection to the MetaTrader 5 terminal and continue processing obtained bars
      mt5.shutdown()
      
      # create DataFrame out of the obtained data
      rates_frame = pd.DataFrame(rates_H1)
      
      # add the "Return" column
      rates_frame['return'] = 0.0
      # now calculate the returns as return[i] = (close[i] - close[i-1])/close[i-1]
      prev_close = 0.0
      for i, row in rates_frame.iterrows():
          close = row['close']
          rates_frame.at[i, 'return'] = close / prev_close - 1 if prev_close != 0.0 else 0.0
          prev_close = close
      
      print("\nCalculate the mean and standard deviation of returns on H1 bars")
      print('H1 rates:', rates_frame.shape[0])
      ret_average = rates_frame[1:]['return'].mean()  # skip the first row with zero return
      print('H1 return average=', ret_average)
      ret_std = rates_frame[1:]['return'].std(ddof=0) # skip the first row with zero return
      print('H1 return std =', ret_std)
      sharpe_H1 = ret_average / ret_std
      print('H1 Sharpe = Average/STD = ', sharpe_H1)
      
      sharpe_annual_H1 = sharpe_H1 * math.sqrt(rates_H1.shape[0]-1)
      print('Sharpe_annual(H1) =', sharpe_annual_H1)
      
      # now calculate the Sharpe ratio on the D1 timeframe
      rates_daily = pd.DataFrame(rates_D1)
      
      # add the "Return" column
      rates_daily['return'] = 0.0
      # calculate returns
      prev_return = 0.0
      for i, row in rates_daily.iterrows():
          close = row['close']
          rates_daily.at[i, 'return'] = close / prev_return - 1 if prev_return != 0.0 else 0.0
          prev_return = close
      
      print("\nCalculate the mean and standard deviation of returns on D1 bars")
      print('D1 rates:', rates_daily.shape[0])
      daily_average = rates_daily[1:]['return'].mean()
      print('D1 return average=', daily_average)
      daily_std = rates_daily[1:]['return'].std(ddof=0)
      print('D1 return std =', daily_std)
      sharpe_daily = daily_average / daily_std
      print('D1 Sharpe =', sharpe_daily)
      
      sharpe_annual_D1 = sharpe_daily * math.sqrt(rates_daily.shape[0]-1)
      print('Sharpe_annual(D1) =', sharpe_annual_D1)
      
      Result
      Calculate the mean and standard deviation of returns on H1 bars
      
      H1 rates: 6226
      H1 return average= 1.4446773215242986e-05
      H1 return std = 0.0010197932969323495
      H1 Sharpe = Average/STD = 0.014166373968823358
      Sharpe_annual(H1) = 1.117708053392236
      
      Calculate the mean and standard deviation of returns on D1 bars
      D1 rates: 260
      D1 return average= 0.0003558228355051694
      D1 return std = 0.004701883757646081
      D1 Sharpe = 0.07567665511222807
      Sharpe_annual(D1) = 1.2179005039019217 
      
      

      Come puoi vedere, i risultati del calcolo MQL5 e Python sono gli stessi. I codici sorgente sono allegati di seguito (CalculateSharpe_2TF).

      I rapporti di Sharpe annuali calcolati dalle barre H1 e D1 differiscono: 1,117708 e 1,217900, rispettivamente. Proviamo a scoprire il motivo.


      Calcolo del rapporto di Sharpe annuale su EURUSD per il 2020 su tutti i timeframe

      Ora, calcoliamo il rapporto di Sharpe annuale su tutti i timeframe. Per fare ciò, raccogliamo i dati ottenuti in una tabella:

      Di seguito è riportato il blocco del codice di calcolo. Il codice completo è disponibile nel file CalculateSharpe_All_TF.mq5 allegato all'articolo.

      //--- structure to print statistics to log
      struct Stats
        {
         string            TF;
         int               Minutes;
         int               Rates;
         double            Avg;
         double            Std;
         double            SharpeTF;
         double            SharpeAnnual;
        };
      //--- array of statistics by timeframes
      Stats stats[];
      //+------------------------------------------------------------------+
      //| Script program start function                                    |
      //+------------------------------------------------------------------+
      void OnStart()
        {
      //--- arrays for close prices
         double H1_close[],D1_close[];
      //--- arrays of returns
         double h1_returns[],d1_returns[];
      //--- arrays of timeframes on which the Sharpe coefficient will be calculated
         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,PERIOD_H2,PERIOD_H3,PERIOD_H4,
                                         PERIOD_H6,PERIOD_H8,PERIOD_H12,PERIOD_D1,PERIOD_W1,PERIOD_MN1
                                        };
      
         ArrayResize(stats,ArraySize(timeframes));
      //--- timeseries request parameters
         string symbol = Symbol();
         datetime from = D'01.01.2020';
         datetime to = D'01.01.2021';
         Print(symbol);
         for(int i = 0; i < ArraySize(timeframes); i++)
           {
            //--- get the array of returns on the specified timeframe
            double returns[];
            GetReturns(symbol,timeframes[i],from,to,returns);
            //--- calculate statistics
            GetStats(returns,avr,std,sharpe);
            double sharpe_annual = sharpe * MathSqrt(ArraySize(returns));
            PrintFormat("%s  aver=%G%%   std=%G%%  sharpe=%G  sharpe_annual=%G",
                        EnumToString(timeframes[i]), avr * 100,std * 100,sharpe,sharpe_annual);
            //--- fill the statistics structure
            Stats row;
            string tf_str = EnumToString(timeframes[i]);
            StringReplace(tf_str,"PERIOD_","");
            row.TF = tf_str;
            row.Minutes = PeriodSeconds(timeframes[i]) / 60;
            row.Rates = ArraySize(returns);
            row.Avg = avr;
            row.Std = std;
            row.SharpeTF = sharpe;
            row.SharpeAnnual = sharpe_annual;
            //--- add a row for the timeframe statistics
            stats[i] = row;
           }
      //--- print statistics on all timeframes to log
         ArrayPrint(stats,8);
        }
      
      /*
      Result
      
            [TF] [Minutes] [Rates]      [Avg]      [Std] [SharpeTF] [SharpeAnnual]
      [ 0] "M1"          1  373023 0.00000024 0.00168942 0.00168942     1.03182116
      [ 1] "M2"          2  186573 0.00000048 0.00239916 0.00239916     1.03629642
      [ 2] "M3"          3  124419 0.00000072 0.00296516 0.00296516     1.04590258
      [ 3] "M4"          4   93302 0.00000096 0.00341717 0.00341717     1.04378592
      [ 4] "M5"          5   74637 0.00000120 0.00379747 0.00379747     1.03746116
      [ 5] "M6"          6   62248 0.00000143 0.00420265 0.00420265     1.04854166
      [ 6] "M10"        10   37349 0.00000239 0.00542100 0.00542100     1.04765562
      [ 7] "M12"        12   31124 0.00000286 0.00601079 0.00601079     1.06042363
      [ 8] "M15"        15   24900 0.00000358 0.00671964 0.00671964     1.06034161
      [ 9] "M20"        20   18675 0.00000477 0.00778573 0.00778573     1.06397070
      [10] "M30"        30   12450 0.00000716 0.00966963 0.00966963     1.07893298
      [11] "H1"         60    6225 0.00001445 0.01416637 0.01416637     1.11770805
      [12] "H2"        120    3115 0.00002880 0.01978455 0.01978455     1.10421905
      [13] "H3"        180    2076 0.00004305 0.02463458 0.02463458     1.12242890
      [14] "H4"        240    1558 0.00005746 0.02871564 0.02871564     1.13344977
      [15] "H6"        360    1038 0.00008643 0.03496339 0.03496339     1.12645075
      [16] "H8"        480     779 0.00011508 0.03992838 0.03992838     1.11442404
      [17] "H12"       720     519 0.00017188 0.05364323 0.05364323     1.22207717
      [18] "D1"       1440     259 0.00035582 0.07567666 0.07567666     1.21790050
      [19] "W1"      10080      51 0.00193306 0.14317328 0.14317328     1.02246174
      [20] "MN1"     43200      12 0.00765726 0.43113365 0.43113365     1.49349076
      
      */
      

      Costruiamo un istogramma del rapporto di Sharpe su EURUSD per il 2020 sui diversi timeframe. Si può notare qui che i calcoli sui timeframe, da M1 a M30, danno risultati ravvicinati: da 1,03 a 1,08. I risultati più incoerenti sono stati ottenuti su intervalli di tempo da H12 a MN1.

      Calcolo del rapporto di Sharpe annuale per EURUSD, per il 2020, su diversi timeframes


      Calcolo del rapporto di Sharpe per GBPUSD, USDJPY e USDCHF per il 2020

      LEseguiamo calcoli simili per altre tre coppie di valute.

      GBPUSD, i valori del rapporto di Sharpe sono simili su timeframe da M1 a H12.

      Calcolo del rapporto di Sharpe annuale per GBPUSD, per il 2020, su diversi timeframe


      USDJPY, i valori sono vicini anche sui timeframe da M1 a H12: da -0,56 a -0,60.

      Calcolo del rapporto di Sharpe annuale per USDJPY , per il 2020, su diversi timeframe


      USDCHF, valori simili sono stati ottenuti sui timeframe da M1 a M30. All'aumentare del tempo, il rapporto di Sharpe fluttua.

      Calcolo del rapporto di Sharpe annuale per USDCHF, per il 2020, su diversi timeframe

      Pertanto, sulla base degli esempi di quattro delle maggiori coppie di valute, possiamo concludere che i calcoli più stabili del rapporto di Sharpe si ottengono su timeframe da M1 a M30. Ciò significa che è meglio calcolare il rapporto utilizzando i rendimenti dei timeframe più bassi, quando si vogliono confrontare strategie che lavorano su simboli diversi.


      Calcolo del rapporto di Sharpe annuale su EURUSD per il 2020 in mesi

      Usiamo i rendimenti mensili di ogni mese del 2020 e calcoliamo il rapporto di Sharpe annuale su timeframe da M1 a H1. Il codice completo dello script CalculateSharpe_Months.mq5 è allegato all'articolo.

      //--- structure to store returns
      struct Return
        {
         double            ret;   // return
         datetime          time;  // date
         int               month; // month
        };
      //+------------------------------------------------------------------+
      //| Script program start function                                    |
      //+------------------------------------------------------------------+
      void OnStart()
        {
         SharpeMonths sharpe_by_months[];
      //--- arrays of timeframes on which the Sharpe coefficient will be calculated
         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
                                        };
         ArrayResize(sharpe_by_months,ArraySize(timeframes));
      //--- timeseries request parameters
         string symbol = Symbol();
         datetime from = D'01.01.2020';
         datetime to = D'01.01.2021';
         Print("Calculate Sharpe Annual on ",symbol, " for 2020 year");
         for(int i = 0; i < ArraySize(timeframes); i++)
           {
            //--- get the array of returns on the specified timeframe
            Return returns[];
            GetReturns(symbol,timeframes[i],from,to,returns);
            double avr,std,sharpe;
            //--- Calculate statistics for the year
            GetStats(returns,avr,std,sharpe);
            string tf_str = EnumToString(timeframes[i]);
            //--- calculate the annual Sharpe ratio for each month
            SharpeMonths sharpe_months_on_tf;
            sharpe_months_on_tf.SetTimeFrame(tf_str);
            //--- select returns for i-th month
            for(int m = 1; m <= 12; m++)
              {
               Return month_returns[];
               GetReturnsByMonth(returns,m,month_returns);
               //--- Calculate statistics for the year
               double sharpe_annual = CalculateSharpeAnnual(timeframes[i],month_returns);
               sharpe_months_on_tf.Sharpe(m,sharpe_annual);
              }
            //--- add Sharpe ratio for 12 months on timeframe i
            sharpe_by_months[i] = sharpe_months_on_tf;
           }
      //--- display the table of annual Sharpe values by months on all timeframes
         ArrayPrint(sharpe_by_months,3);
        }
      
      /*
      Result
      
      Calculate Sharpe Annual on EURUSD for 2020 year
                   [TF]  [Jan]  [Feb] [Marc]  [Apr] [May] [June] [July] [Aug] [Sept]  [Oct] [Nov] [Dec]
      [ 0] "PERIOD_M1"  -2.856 -1.340  0.120 -0.929 2.276  1.534  6.836 2.154 -2.697 -1.194 3.891 4.140
      [ 1] "PERIOD_M2"  -2.919 -1.348  0.119 -0.931 2.265  1.528  6.854 2.136 -2.717 -1.213 3.845 4.125
      [ 2] "PERIOD_M3"  -2.965 -1.340  0.118 -0.937 2.276  1.543  6.920 2.159 -2.745 -1.212 3.912 4.121
      [ 3] "PERIOD_M4"  -2.980 -1.341  0.119 -0.937 2.330  1.548  6.830 2.103 -2.765 -1.219 3.937 4.110
      [ 4] "PERIOD_M5"  -2.929 -1.312  0.120 -0.935 2.322  1.550  6.860 2.123 -2.729 -1.239 3.971 4.076
      [ 5] "PERIOD_M6"  -2.945 -1.364  0.119 -0.945 2.273  1.573  6.953 2.144 -2.768 -1.239 3.979 4.082
      [ 6] "PERIOD_M10" -3.033 -1.364  0.119 -0.934 2.361  1.584  6.789 2.063 -2.817 -1.249 4.087 4.065
      [ 7] "PERIOD_M12" -2.952 -1.358  0.118 -0.956 2.317  1.609  6.996 2.070 -2.933 -1.271 4.115 4.014
      [ 8] "PERIOD_M15" -3.053 -1.367  0.118 -0.945 2.377  1.581  7.132 2.078 -2.992 -1.274 4.029 4.047
      [ 9] "PERIOD_M20" -2.998 -1.394  0.117 -0.920 2.394  1.532  6.884 2.065 -3.010 -1.326 4.074 4.040
      [10] "PERIOD_M30" -3.008 -1.359  0.116 -0.957 2.379  1.585  7.346 2.084 -2.934 -1.323 4.139 4.034
      [11] "PERIOD_H1"  -2.815 -1.373  0.116 -0.966 2.398  1.601  7.311 2.221 -3.136 -1.374 4.309 4.284
      
      */
      

      Si può notare che i valori del rapporto annuale per ogni mese sono molto vicini su tutti i timeframe su cui abbiamo eseguito i calcoli. Per una migliore presentazione, eseguiamo il rendering dei risultati come una superficie 3D utilizzando un diagramma di Excel.

      Grafico 3D del rapporto di Sharpe annuale per EURUSD del 2020 per mese e timeframe

      Il diagramma mostra chiaramente che i valori del rapporto di Sharpe annuale cambiano ogni mese. Dipende da come EURUSD variava in questo mese. D'altra parte, il rapporto di Sharpe annuale per ogni mese su tutti i timeframe quasi non cambia.

      Pertanto, il rapporto di Sharpe annuale può essere calcolato su qualsiasi timeframe, mentre il valore risultante dipende anche dal numero di barre su cui sono stati ottenuti i rendimenti. Significa che questo algoritmo di calcolo può essere utilizzato per test, ottimizzazione e monitoraggio in tempo reale. L'unico prerequisito è avere una matrice sufficientemente ampia di rendimenti.


      Rapporto di Sortino

      Nel calcolo del rapporto di Sharpe, il rischio è la piena volatilità delle quotazioni delle attività, sia in aumento che in diminuzione. Ma l'aumento del valore del portafoglio è positivo per l'investitore, mentre la perdita è legata solo alla sua diminuzione. Pertanto, il rischio effettivo nel rapporto è sopravvalutato. Il rapporto di Sortino sviluppato all'inizio degli anni '90 da Frank Sortino affronta questo problema.

      Come i suoi predecessori, F. Sortino considera il rendimento futuro come una variabile casuale pari alla sua aspettativa matematica, mentre il rischio è considerato una varianza. Il rendimento e il rischio sono determinati sulla base delle quotazioni storiche in un determinato periodo. Come nel calcolo del rapporto di Sharpe, il rendimento è diviso per il rischio.

      Sortino ha osservato che il rischio definito come la varianza totale dei rendimenti (o la piena volatilità) dipende sia da variazioni positive che negative. Sortino ha sostituito la volatilità complessiva totale con la semi-volatilità che considera solo una diminuzione degli asset. La semi-volatilità viene anche definita volatilità dannosa, rischio al ribasso, deviazione al ribasso, volatilità negativa o deviazione standard al ribasso.

      Il calcolo del rapporto Sortino è simile a quello di Sharpe, con l'unica differenza che i rendimenti positivi sono esclusi dal calcolo della volatilità. Ciò riduce la misura del rischio e aumenta il peso del rapporto.

      Rendimenti positivi e negativi


      Esempio di codice che calcola il rapporto di Sortino in base al rapporto di Sharpe. La semidispersione viene calcolata solo utilizzando rendimenti negativi.
      //+------------------------------------------------------------------+
      //|  Calculates Sharpe and Sortino ratios                            |
      //+------------------------------------------------------------------+
      void GetStats(ENUM_TIMEFRAMES timeframe, const double & returns[], double & avr, double & std, double & sharpe, double & sortino)
        {
         avr = ArrayMean(returns);
         std = ArrayStd(returns);
         sharpe = (std == 0) ? 0 : avr / std;
      //--- now, remove negative returns and calculate the Sortino ratio
         double negative_only[];
         int size = ArraySize(returns);
         ArrayResize(negative_only,size);
         ZeroMemory(negative_only);
      //--- copy only negative returns
         for(int i = 0; i < size; i++)
            negative_only[i] = (returns[i] > 0) ? 0 : returns[i];
         double semistd = ArrayStd(negative_only);
         sortino = avr / semistd;   
         return;
        }
      

      Lo script CalculateSortino_All_TF.mq5 allegato a questo articolo genera i seguenti risultati su EURUSD, per il 2020:

            [TF] [Minutes] [Rates]      [Avg]      [Std] [SharpeAnnual] [SortinoAnnual]    [Ratio]
      [ 0] "M1"          1  373023 0.00000024 0.00014182     1.01769617      1.61605380 1.58795310
      [ 1] "M2"          2  186573 0.00000048 0.00019956     1.02194170      1.62401856 1.58914991
      [ 2] "M3"          3  124419 0.00000072 0.00024193     1.03126142      1.64332243 1.59350714
      [ 3] "M4"          4   93302 0.00000096 0.00028000     1.02924195      1.62618200 1.57998030
      [ 4] "M5"          5   74637 0.00000120 0.00031514     1.02303684      1.62286584 1.58632199
      [ 5] "M6"          6   62248 0.00000143 0.00034122     1.03354379      1.63789024 1.58473231
      [ 6] "M10"        10   37349 0.00000239 0.00044072     1.03266766      1.63461839 1.58290848
      [ 7] "M12"        12   31124 0.00000286 0.00047632     1.04525580      1.65215986 1.58062730
      [ 8] "M15"        15   24900 0.00000358 0.00053223     1.04515816      1.65256608 1.58116364
      [ 9] "M20"        20   18675 0.00000477 0.00061229     1.04873529      1.66191269 1.58468272
      [10] "M30"        30   12450 0.00000716 0.00074023     1.06348332      1.68543441 1.58482449
      [11] "H1"         60    6225 0.00001445 0.00101979     1.10170316      1.75890688 1.59653431
      [12] "H2"        120    3115 0.00002880 0.00145565     1.08797046      1.73062372 1.59068999
      [13] "H3"        180    2076 0.00004305 0.00174762     1.10608991      1.77619289 1.60583048
      [14] "H4"        240    1558 0.00005746 0.00200116     1.11659184      1.83085734 1.63968362
      [15] "H6"        360    1038 0.00008643 0.00247188     1.11005321      1.79507001 1.61710267
      [16] "H8"        480     779 0.00011508 0.00288226     1.09784908      1.74255746 1.58724682
      [17] "H12"       720     519 0.00017188 0.00320405     1.20428761      2.11045830 1.75245371
      [18] "D1"       1440     259 0.00035582 0.00470188     1.20132966      2.04624198 1.70331429
      [19] "W1"      10080      51 0.00193306 0.01350157     1.03243721      1.80369984 1.74703102
      [20] "MN1"     43200      12 0.00765726 0.01776075     1.49349076      5.00964481 3.35431926
      

      Si può vedere che in quasi tutti i timeframe il valore di Sortino è 1,60 volte il rapporto di Sharpe. Naturalmente, non ci sarà una dipendenza così chiara nel calcolo dei rapporti in base ai risultati di trading. Pertanto, ha senso confrontare strategie/portafogli utilizzando entrambi i rapporti.

      Rapporti di Sharpe e Sortino su EURUSD per il 2020 in base ai timeframe

      La differenza tra queste due metriche è che il rapporto di Sharpe riflette principalmente la volatilità, mentre il rapporto di Sortino mostra davvero il rapporto o il rendimento per unità di rischio. Non dimentichiamo però che i calcoli vengono eseguiti in base alla storia, quindi buoni risultati non possono garantire profitti futuri.


      Esempio di calcolo del rapporto di Sharpe nel Tester di strategia MetaTrader 5

      Il rapporto di Sharpe è stato originariamente sviluppato per valutare i portafogli contenenti azioni. I prezzi delle azioni cambiano ogni giorno, e quindi anche il valore degli asset cambia ogni giorno. Per impostazione predefinita, le strategie di trading non implicano l'esistenza di posizioni aperte, quindi per parte del tempo lo stato di un conto di trading rimarrà invariato. Significa che quando non ci sono posizioni aperte, riceveremo valori zero di rendimento, e quindi i calcoli di Sharpe saranno sbagliati per loro. Pertanto, i calcoli utilizzeranno solo le barre su cui è cambiato lo stato del conto di trading. L'opzione più adatta è analizzare i valori azionari sull'ultimo tick di ciascuna barra. Ciò consentirà il calcolo del rapporto di Sharpe con qualsiasi modalità di generazione tick nel tester di strategia MetaTrader 5.

      Un altro punto da tenere in considerazione è che l'incremento percentuale del prezzo, che di solito viene calcolato come Rendimento[i]=(ChiusuraCorrente-ChiusuraPrecedente)/ChiusuraPrecedente, presenta un certo svantaggio nei calcoli. È il seguente: se il prezzo scende del 5% e poi cresce del 5%, non otterremo il valore iniziale. Ecco perché, invece del consueto incremento del prezzo relativo, gli studi statistici di solito utilizzano il logaritmo dell'incremento del prezzo. I rendimenti logaritmici (rendimenti logaritmici) non presentano questo svantaggio dei rendimenti lineari. Il valore è calcolato come segue:

      Log_Return =ln(Corrente/Precedente) = ln(Corrente) — ln(Precedente)

      I rendimenti logaritmici sono convenienti perché possono essere sommati poiché la somma dei logaritmi è equivalente al prodotto dei rendimenti relativi.

      Quindi, l'algoritmo di calcolo del rapporto di Sharpe necessita di regolazioni minime.

      //--- calculate the logarithms of increments using the equity array
         for(int i = 1; i < m_bars_counter; i++)
           {
            //--- only add if equity has changed
            if(m_equities[i] != prev_equity)
              {
               log_return = MathLog(m_equities[i] / prev_equity); // increment logarithm
               aver += log_return;            // average logarithm of increments
               AddReturn(log_return);         // fill the array of increment logarithms
               counter++;                     // counter of returns
              }
            prev_equity = m_equities[i];
           }
      //--- if values are not enough for Sharpe calculation, return 0
         if(counter <= 1)
            return(0);
      //--- average value of the increment logarithm
         aver /= counter;
      //--- calculate standard deviation
         for(int i = 0; i < counter; i++)
            std += (m_returns[i] - aver) * (m_returns[i] - aver);
         std /= counter;
         std = MathSqrt(std);
      //--- Sharpe ratio on the current timeframe
         double sharpe = aver / std;
      

      Il codice di calcolo completo è implementato come file inclusivo Sharpe.mqh che è allegato all'articolo. Per calcolare il rapporto di Sharpe come criterio di ottimizzazione personalizzato, collega questo file al tuo Expert Advisor e aggiungi alcune righe di codice. Vediamo come farlo utilizzando il MACD Sample.mq5 EA del pacchetto standard MetaTrader 5 come esempio.

      #define MACD_MAGIC 1234502
      //---
      #include <Trade\Trade.mqh>
      #include <Trade\SymbolInfo.mqh>
      #include <Trade\PositionInfo.mqh>
      #include <Trade\AccountInfo.mqh>
      #include "Sharpe.mqh"
      //---
      input double InpLots          = 0.1;// Lots
      input int    InpTakeProfit    = 50; // Take Profit (in pips)
      input int    InpTrailingStop  = 30; // Trailing Stop Level (in pips)
      input int    InpMACDOpenLevel = 3;  // MACD open level (in pips)
      input int    InpMACDCloseLevel = 2; // MACD close level (in pips)
      input int    InpMATrendPeriod = 26; // MA trend period
      //---
      int ExtTimeOut = 10; // time out in seconds between trade operations
      CReturns   returns;
      ....
      //+------------------------------------------------------------------+
      //| Expert new tick handling function                                |
      //+------------------------------------------------------------------+
      void OnTick(void)
        {
         static datetime limit_time = 0; // last trade processing time + timeout
      //--- add current equity to the array to calculate the Sharpe ratio
         MqlTick tick;
         SymbolInfoTick(_Symbol, tick);
         returns.OnTick(tick.time, AccountInfoDouble(ACCOUNT_EQUITY));
      //--- don't process if timeout
         if(TimeCurrent() >= limit_time)
           {
            //--- check for data
            if(Bars(Symbol(), Period()) > 2 * InpMATrendPeriod)
              {
               //--- change limit time by timeout in seconds if processed
               if(ExtExpert.Processing())
                  limit_time = TimeCurrent() + ExtTimeOut;
              }
           }
        }
      //+------------------------------------------------------------------+
      //| Tester function                                                  |
      //+------------------------------------------------------------------+
      double OnTester(void)
        {
      //--- calculate Sharpe ratio
         double sharpe = returns.OnTester();
         return(sharpe);
        }
      //+------------------------------------------------------------------+
      
      

      Salva il codice risultante come "MACD Sample Sharpe.mq5" - anche il file pertinente è allegato di seguito.

      Eseguiamo un'ottimizzazione genetica per EURUSD M10 2020, selezionando un criterio di ottimizzazione personalizzato.

      Impostazioni del tester per l'ottimizzazione genetica dell'Expert Advisor utilizzando un criterio personalizzato


      I valori del criterio personalizzato ottenuti coincidono con il rapporto di Sharpe calcolato dal tester della strategia. Ora conosci i meccanismi di calcolo e come interpretare i risultati ottenuti.

      Risultati dell'ottimizzazione genetica dell'Expert Advisor utilizzando un criterio personalizzato


      I passaggi con il rapporto di Sharpe più alto non mostrano sempre il profitto più alto nel tester, ma consentono di trovare parametri con un grafico di equità uniforme. Tali grafici di solito non mostrano una forte crescita, ma non ci sono nemmeno grandi cali e drawdown dell'equity.

      Significa che utilizzando l'ottimizzazione con il rapporto di Sharpe, è possibile trovare parametri più stabili, rispetto ad altri criteri di ottimizzazione.

      Grafico che mostra il test di un Expert Advisor con un rapporto di Sharpe di 6,43


      Vantaggi e svantaggi

      I rapporti di Sharpe e Sortino consentono di determinare se il profitto ricevuto copre o meno il rischio associato. Un altro vantaggio rispetto a misure di rischio alternative è che i calcoli possono essere applicati a tutti i tipi di attività. Ad esempio, puoi confrontare l'oro con l'argento utilizzando il rapporto di Sharpe perché non richiede un benchmark esterno specifico per essere valutato. Pertanto, i rapporti possono essere applicati a singole strategie o titoli, nonché a portafogli di attività o strategie.

      Lo svantaggio di questi strumenti è che il calcolo presuppone una distribuzione normale dei rendimenti. In realtà, questo requisito è raramente soddisfatto. Tuttavia, i rapporti di Sharpe e Sortino sono gli strumenti più semplici e comprensibili che consentono il confronto di diverse strategie e portafogli.