Nella precedente lezione del corso per creare un trading system event-driven abbiamo descritto la gerarchia della classe ExecutionHandler. In questa lezione introduciamo l’implementazione delle metriche per il calcolo delle performance di una strategia usando la curva equity precedentemente costruita nell’oggetto Portfolio.
Calcolo delle Performance
Una metrica fondamentale nel calcolo delle performance di una strategia è lo Sharpe Ratio. In una precedente lezione abbiamo definito la formula dello Sharpe Ratio(annualizzato) come segue:
\(\begin{eqnarray*}
S_A = \sqrt{N} \frac{\mathbb{E}(R_a – R_b)}{\sqrt{\text{Var} (R_a – R_b)}}
\end{eqnarray*}\)
Dove \(R_a\) è il flusso dei rendimenti della curva equity e \(R_b\) è un indice di riferimento, come uno specifico tasso di interesse o un indice azionario.
Il massimo drawdown e la durata del drawdown sono due ulteriori misure che gli investitori utilizzano per valutare il rischio in un portafoglio. Il primo rappresenta è più grande discesa, la correzione, da un precedente massimo relativo o massimo assoluto, della curva equity, mentre il secondo è definito come il numero di periodi di trading in cui si verifica.
Vediamo ora come implementare il Sharpe Ratio, il drawdown massimo e la durata del drawdown come misure delle prestazioni del portafoglio da utilizzare nel trading system event-driven sviluppato in Python.
Implementazione delle misure delle prestazioni
Il primo passo per implementare il calcolo delle perfomance di un trading system è creare un nuovo file performance.py
, Dobbiamo codificare le funzioni per calcolare il Sharpe Ratio e le informazioni sul drawdown. Come per la maggior parte delle classi che prevedono elevati carichi computanzionali, abbiamo bisogno di importare NumPy e Pandas:
# performance.py
import numpy as np
import pandas as pd;
Calcolare il Sharpe Ratio
Il Sharpe Ratio misura il rapporto rischio/rendimento (è solo una delle tante misure disponibili!) e richiede un parametro: il numero di periodi da considerare per annualizzare il valore.
Di solito, questo valore è fissato su 252, che rappresenta i giorni di negoziazione annuali negli Stati Uniti. Tuttavia, se la strategia apre e chiude posizioni ogni ora, è necessario adattare il Sharpe per annualizzarlo correttamente. In questo caso, si imposta il parametro periods
su 252 * 6.5 = 1638, che corrisponde al numero di ore di trading annuali negli Stati Uniti. Se si effettua trading sui minuti, il parametro va impostato su 252 * 6.5 * 60 = 98280.
La funzione create_sharpe_ratio
calcola il rapporto tra la media dei rendimenti percentuali e la deviazione standard dei rendimenti percentuali, ridimensionata in base al fattore periods
. Questa operazione avviene su una Serie di Pandas denominata returns
:
# performance.py
def create_sharpe_ratio(returns, periods=252):
"""
Crea il Sharpe ratio per la strategia, basato su a benchmark
pari a zero (ovvero nessuna informazione sui tassi privi di rischio).
Parametri:
returns - Una serie panda che rappresenta i rendimenti percentuali nel periodo.
periods - Giornaliero (252), orario (252 * 6,5), minuto (252 * 6,5 * 60) ecc.
"""
return np.sqrt(periods) * (np.mean(returns)) / np.std(returns)
Calcolo del Drawdown
Una misura importante nel calcolo delle performance di una strategia è il “drawdown”. Questo indicatore rappresenta la distanza tra un massimo relativo e un minimo relativo lungo la curva equity.
La funzione create_drawdowns
calcola sia il drawdown massimo che la durata massima del drawdown. Il drawdown massimo corrisponde alla discesa più elevata tra un massimo e un minimo relativi, mentre la durata massima del drawdown si riferisce al numero di periodi in cui si verifica questa discesa.
È importante interpretare correttamente la durata del drawdown, poiché questo fattore indica i periodi di trading e non può essere tradotto direttamente in un’unità temporale, come i “giorni”.
La funzione inizia creando due oggetti Serie di Pandas che rappresentano il drawdown e la durata di ogni “barra” di trading. Successivamente, determina l’attuale high water mark (HWM), verificando se la curva equity supera i picchi precedenti.
Il drawdown si calcola come la differenza tra l’attuale HWM e la curva equity. Se il valore risulta negativo, la durata aumenta per ogni barra che si verifica fino al raggiungimento del prossimo HWM. Infine, la funzione restituisce il massimo di ciascuna delle due serie.
# performance.py
def create_drawdowns(pnl):
"""
Calcola il massimo drawdown tra il picco e il minimo della curva PnL
così come la durata del drawdown. Richiede che il pnl_returns
sia una serie di pandas.
Parametri:
pnl - Una serie pandas che rappresenta i rendimenti percentuali del periodo.
Restituisce:
Drawdown, duration - Massimo drawdown picco-minimo e relativa durata.
"""
# Calcola la curva cumulativa dei rendimenti
# e imposta un "High Water Mark"
# Quindi crea le serie dei drawdown e relative durate
hwm = [0]
idx = pnl.index
drawdown = pd.Series(index = idx)
duration = pd.Series(index = idx)
# Ciclo sul range dell'indice
for t in range(1, len(idx)):
cur_hwm = max(hwm[t-1], pnl[t])
hwm.append(cur_hwm)
dd = (hwm[t] - pnl[t])
drawdown[t]= dd
duration[t]= (0 if drawdown[t] == 0 else duration[t-1] + 1)
return drawdown, drawdown.max(), duration.max()
Gestione delle Performance
Per utilizzare correttamente le misure, è fondamentale disporre di un metodo per il calcolo delle performance dopo aver effettuato un backtest, ossia quando è disponibile una curva di equity adeguata.
Inoltre, è necessario associare questo metodo a una struttura di oggetti specifica. Poiché le misure di rendimento derivano dal portafoglio, è logico implementare i calcoli delle performance all’interno di un metodo della classe Portfolio
.
Il primo passo consiste nell’aprire portfolio.py
e importare le funzioni di calcolo delle performance.
# portfolio.py
.. # Other imports
from performance import create_sharpe_ratio, create_drawdowns
Poiché Portfolio
è una classe base astratta, si deve associare un metodo a una delle sue classi derivate, che in questo caso corrisponde a NaivePortfolio
. Quindi si crea un metodo chiamato output_summary_stats
che elabora la curva equity del portafoglio per generare le informazioni relative allo Sharpe e drawdown.
Il metodo è semplice. Utilizza semplicemente le due misure di performance e le applica direttamente al DataFrame Pandas relativo alla curva equity, restituendo le statistiche come una lista di tuple in un formato “user-friendly”:
# portfolio.py
..
..
class NaivePortfolio(object):
..
..
def output_summary_stats(self):
"""
Crea un elenco di statistiche di riepilogo per il portafoglio
come lo Sharpe Ratio e le informazioni sul drowdown.
"""
total_return = self.equity_curve['equity_curve'][-1]
returns = self.equity_curve['returns']
pnl = self.equity_curve['equity_curve']
sharpe_ratio = create_sharpe_ratio(returns)
drawdown, max_dd, dd_duration = create_drawdowns(pnl)
self.equity_curve['drawdown'] = drawdown
stats = [("Total Return", "%0.2f%%" % \
((total_return - 1.0) * 100.0)),
("Sharpe Ratio", "%0.2f" % sharpe_ratio),
("Max Drawdown", "%0.2f%%" % (max_dd * 100.0)),
("Drawdown Duration", "%d" % dd_duration)]
self.equity_curve.to_csv('equity.csv')
return stats
Conclusione
In questa lezione abbiamo descritto un calcolo delle performance di un portfolio molto semplice. Non considera l’analisi dei singoli trade o altre misure del rapporto rischio/rendimento. Tuttavia, è facile estendere questa analisi, aggiungendo nuovi metodi in performance.py
e integrandoli in output_summary_stats
secondo le necessità.
Il codice completo presentato in questa lezione, basato sul sistema di trading event-driven TQTradingSystem, è disponibile nel seguente repository GitHub: https://github.com/tradingquant-it/TQTradingSystem.”