In questo articolo descriviamo come implementare un report delle performance di una strategia in Python per verificare i risultati di un backtest. Questo articolo fa parte di una mini-serie di che offre una panoramica su come creare un generatore HTML personalizzabile per un report di una strategia di trading.
Obiettivo
L’obiettivo è creare uno strumento che crea un file HTML a partire da un file csv che contiene i dati dell’equity di una strategia e (facoltativamente) una serie equity di benchmark. Dobbiamo quindi solamente creare il file csv, posizionalo in una cartella particolare, lanciare uno script. Otteniamo un file HTML che può essere visualizzato nel browser e contiene tutti i tipi di grafici, statistiche e analisi sulle prestazioni della strategia.
Prima di passare all’analisi delle prestazioni e calcolo delle statistiche pertinenti, dobbiamo creare lo “scheletro” del progetto. Questo scheletro contiene tutti i file, i moduli e la logica necessari per generare i file HTML di output più basilari, tramite una semplice variabile “placeholder” per assicurarsi che le cose funzionino come previsto.
Per prima cosa dobbiamo creare la struttura di cartelle necessaria insieme ad alcuni file necessari durante l’implementazione.
Project
|
+– main.py
|
+– templates
| |
| +– template.html
|
+– data
| |
| +– data.csv
|
+– output
| |
| +– report.html
|
+– static
|
+– app.css
|
+– app.js
Inizialmente lasciamo i file vuoti. Torniamo su di loro a tempo debito e apportare le modifiche necessarie in ogni fase del progetto.
Il progetto è composto dalle seguenti parti e funzionalità:
- Il componente principale dell’intero progetto è la classe “PerformanceReport”, implementata all’interno del file “main.py”.
- La classe PerformanceReport sfrutta le potenzialità di jinja2 e le sue funzionalità di “templating”. Possiamo calcolare e creare tutti i grafici e statistiche nel corpo della classe, e quindi “iniettare” questi oggetti come variabili in un template HTML.
- Il file “template.html”, nella cartella “templates”, è modello HTML dove inseriamo i nostri oggetti.
- Jinja2 è usato per convertire tutti gli oggetti e template in una lunga stringa html, che è poi scritta nel file “report.html” nella cartella “output”. Questo file “report.html” è quindi pronto per essere aperto e renderizzato da qualsiasi browser moderno per poter visualizzare i risultati della strategia.
Creare un template
Iniziamo quindi ad organizzare correttamente il codice base in modo da poter istanziare la classe PerformanceReport
, impostare una variabile fittizia da inserire nel modello HTML e quindi convertire tutto in file report.html come output finale.
Creiamo il file main.py
ed aggiungiamo il seguente codice:
import os
from jinja2 import Environment, FileSystemLoader
class PerformanceReport:
""" Report con le statistiche delle performance stats per una data strategia
"""
def __init__(self):
pass
def generate_html(self):
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template("templates/template.html")
placeholder = 'Hello World'
html_out = template.render(test_variable=placeholder)
return html_out
def generate_html_report(self):
""" Restitusice un report HTML con le analisi
"""
html = self.generate_html()
outputdir = "output"
outfile = os.path.join(outputdir, 'report.html')
file = open(outfile, "w")
file.write(html)
file.close()
if __name__ == "__main__":
report = PerformanceReport()
report.generate_html_report()
Notiamo che il codice definisce una variabile placeholder
con un valore di default pari alla stringa “Hello World”. Questa variabile è usata come argomento d’ingresso alla funzione template.render(), facendo riferimento ad essa come test_variable
“. La funzionalità di Jinja2 permette di accedere a quella variabile direttamente dal modello HTML in cui è stata inserita.
A tale scopo dobbiamo aggiungere il seguente codice al file “template.html” nella cartella “templates”:
App
Trading Backtest Report
{{ test_variable }}
test_variable
è stata acquisita e inclusa nell’output dell’HTML racchiudendo il nome della variabile tra 2 parentesi graffe su entrambi i lati. In questo modo il template è in grado di accedere alla variabile placeholder
iniziale che abbiamo definito nella classe PerformanceReport
con il valore di “Hello World”.
Se le sezioni di codice precedenti sono state aggiunte correttamente, possiamo eseguire il file main.py
e quindi controllare il file “report.html” all’interno della cartella “output”.
Aprendo il file “report.html” con un brower, otteniamo quanto segue: 
Acquisizione dei dati
Implementiamo il codice che permette alla classe “PerformanceReport” di leggere nel file csv dalla cartella “data”, analizzarlo e memorizzare i dati come una serie temporale relativa alla curva di equity della strategia e una serie relativa alla curva di equity del benchmark. Il codice del filemain.py
diventa il seguente:
import os
import pandas as pd
from jinja2 import Environment, FileSystemLoader
class PerformanceReport:
""" Report con le statistiche delle performance stats per una data strategia
"""
def __init__(self, infilename):
self.infilename = infilename
self.get_data()
def get_data(self):
basedir = os.path.abspath(os.path.dirname('__file__'))
data_folder = os.path.join(basedir, 'data')
data = pd.read_csv(os.path.join(data_folder, self.infilename), index_col='date',
parse_dates=True, dayfirst=True)
self.equity_curve = data['equity_curve']
if len(data.columns) > 1:
self.benchmark_curve = data['benchmark']
def generate_html(self):
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template("templates/template.html")
equity_curve = self.equity_curve
html_out = template.render(equity_curve=equity_curve)
return html_out
def generate_html_report(self):
""" Restitusice un report HTML con le analisi
"""
html = self.generate_html()
outputdir = "output"
outfile = os.path.join(outputdir, 'report.html')
file = open(outfile, "w")
file.write(html)
file.close()
if __name__ == "__main__":
report = PerformanceReport('data.csv')
report.generate_html_report()
get_data()
che legge e memorizza i dati dal file csv, l’aggiunta dell’argomento infilename
alla classe PerformanceReport
e l’iniezione della variabile equity_curve
nel file HTML.
Ovviamente dobbiamo popolare nel corretto formato il file csv con alcuni dati rilevanti. È possibile scaricare i dati utilizzati in questo esempio al seguente link: data.csv .
Dobbiamo modificare anche il file “template.html” come segue:
App
Trading Backtest Report
{{ equity_curve }}
equity_curve
– in modo da collegarsi correttamente alla variabile che è stata prevista nel modello nella funzione template.render()
.
Quando eseguiamo “main.py” e apriamo il file “report.html” nel browser, otteniamo qualcosa di simile: 
Vediamo come il codice ha correttamente acquisito, memorizzato e quindi iniettato nel modello HTML i dati contenuti nel file csv. I dati sono relativi alla curva di equity di una strategia e, facoltativamente, i dati di equity di un benchmark. Non è visivamente accattivante perchè in questo caso l’obiettivo era evidenziare come funziona la logica per creare e passare variabili tra l’oggetto della classe e il file di report.
Generazione dei grafici
Passiamo finalmente a qualcosa di utile per il processo di generazione dei report. Aggiungiamo 2 metodi nel file main.py
: rebase_series
e plot_performance_chart
, oltre all’importazione di alcune librerie necessarie per usare i grafici Plotly.
Ovviamente dobbiamo modificare anche il codice all’interno di generate_html
in modo da generare la variabile che contiene il grafico delle prestazioni e passarla al rendering del template.
Anche il file “template.html” ha subito diverse modifiche. Abbiamo aggiunto alcuni collegamenti agli script necessari per poter visualizzare correttamente i nostri grafici Plotly e abbiamo aggiunto alcuni nuovi div di righe e colonne per creare la struttura della pagina.
Il codice per il file main.py
è il seguente:
import os
import pandas as pd
import plotly
import plotly.graph_objs as go
from jinja2 import Environment, FileSystemLoader
class PerformanceReport:
""" Report con le statistiche delle performance stats per una data strategia
"""
def __init__(self, infilename):
self.infilename = infilename
self.get_data()
def get_data(self):
basedir = os.path.abspath(os.path.dirname('__file__'))
data_folder = os.path.join(basedir, 'data')
data = pd.read_csv(os.path.join(data_folder, self.infilename), index_col='date',
parse_dates=True, dayfirst=True)
self.equity_curve = data['equity_curve']
if len(data.columns) > 1:
self.benchmark_curve = data['benchmark']
def generate_html(self):
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template("templates/template.html")
perf_chart = self.plot_performance_chart()
html_out = template.render(perf_chart=perf_chart)
return html_out
def generate_html_report(self):
""" Restitusice un report HTML con le analisi
"""
html = self.generate_html()
outputdir = "output"
outfile = os.path.join(outputdir, 'report.html')
file = open(outfile, "w")
file.write(html)
file.close()
def rebase_series(self, series):
return (series / series.iloc[0]) * 100
def plot_performance_chart(self):
trace_equity = go.Scatter(
x=self.equity_curve.index.tolist(),
y=self.rebase_series(self.equity_curve).values.tolist(),
name='strategy',
yaxis='y2',
line=dict(color=('rgb(22, 96, 167)')))
trace_benchmark = go.Scatter(
x=self.benchmark_curve.index.tolist(),
y=self.rebase_series(self.benchmark_curve).values.tolist(),
name='benchmark',
yaxis='y2',
line=dict(color=('rgb(22, 96, 0)')))
layout = go.Layout(
autosize=True,
legend=dict(orientation="h"),
title='Performance Chart',
yaxis=dict(
title='Performance'))
perf_chart = plotly.offline.plot({"data": [trace_equity, trace_benchmark],
"layout": layout}, include_plotlyjs=False,
output_type='div')
return (perf_chart)
if __name__ == "__main__":
report = PerformanceReport('data.csv')
report.generate_html_report()
template.html
diventa:
App
Trading Backtest Report
{{ perf_chart|safe }}
Otteniamo il seguente risultato:

Abbiamo ottenuto un grafico interattivo che mostra le prestazioni relative alla strategia e al relativo benchmark. Concludiamo questo articolo aggiungendo un altro grafico a destra di quello che abbiamo appena creato. Nel nuovo grafico vogliamo mostrare il drawdown della strategia e il drawdown del benchmark.
Non descriviamo le esatte modifiche effettuate nel codice, mostrate nei seguenti snippet aggiornati. Lasciamo al lettore l’analisi del codice e la a verifica di cosa abbiamo modificato e di cosa abbiamo aggiunto.
Leggere il codice di altre persone nel tentativo di capire esattamente cosa è stato implementato e come funziona la logica è un ottimo modo per sviluppare le proprie capacità e abilità di codifica.
Il file main.py
aggiornato è il seguente:
import os
import pandas as pd
import plotly
import plotly.graph_objs as go
import ffn
from jinja2 import Environment, FileSystemLoader
class PerformanceReport:
""" Report con le statistiche delle performance stats per una data strategia
"""
def __init__(self, infilename):
self.infilename = infilename
self.get_data()
def get_data(self):
basedir = os.path.abspath(os.path.dirname('__file__'))
data_folder = os.path.join(basedir, 'data')
data = pd.read_csv(os.path.join(data_folder, self.infilename), index_col='date',
parse_dates=True, dayfirst=True)
self.equity_curve = data['equity_curve']
if len(data.columns) > 1:
self.benchmark_curve = data['benchmark']
def generate_html(self):
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template("templates/template.html")
perf_chart = self.plot_performance_chart()
drawdown_chart = self.plot_drawdown_chart()
html_out = template.render(perf_chart=perf_chart, drawdown_chart=drawdown_chart)
return html_out
def generate_html_report(self):
""" Restitusice un report HTML con le analisi
"""
html = self.generate_html()
outputdir = "output"
outfile = os.path.join(outputdir, 'report.html')
file = open(outfile, "w")
file.write(html)
file.close()
def rebase_series(self, series):
return (series / series.iloc[0]) * 100
def plot_performance_chart(self):
trace_equity = go.Scatter(
x=self.equity_curve.index.tolist(),
y=self.rebase_series(self.equity_curve).values.tolist(),
name='strategy',
yaxis='y2',
line=dict(color=('rgb(22, 96, 167)')))
trace_benchmark = go.Scatter(
x=self.benchmark_curve.index.tolist(),
y=self.rebase_series(self.benchmark_curve).values.tolist(),
name='benchmark',
yaxis='y2',
line=dict(color=('rgb(22, 96, 0)')))
layout = go.Layout(
autosize=True,
legend=dict(orientation="h"),
title='Performance Chart',
yaxis=dict(
title='Performance'))
perf_chart = plotly.offline.plot({"data": [trace_equity, trace_benchmark],
"layout": layout}, include_plotlyjs=False,
output_type='div')
return (perf_chart)
def plot_drawdown_chart(self):
trace_equity_drawdown = go.Scatter(
x=self.equity_curve.to_drawdown_series().index.tolist(),
y=self.equity_curve.to_drawdown_series().values.tolist(),
name='strategy drawdown',
yaxis='y2',
line=dict(color=('rgb(22, 96, 167)')))
trace_benchmark_drawdown = go.Scatter(
x=self.benchmark_curve.to_drawdown_series().index.tolist(),
y=self.benchmark_curve.to_drawdown_series().values.tolist(),
name='benchmark drawdown',
yaxis='y2',
line=dict(color=('rgb(22, 96, 0)')))
layout = go.Layout(
autosize=True,
legend=dict(orientation="h"),
title='Drawdown Chart',
yaxis=dict(
title='Drawdown'))
drawdown_chart = plotly.offline.plot({"data": [trace_equity_drawdown, trace_benchmark_drawdown],
"layout": layout}, include_plotlyjs=False,
output_type='div')
return (drawdown_chart)
if __name__ == "__main__":
report = PerformanceReport('data.csv')
report.generate_html_report()
Mentre il file template.html
aggiornato è il seguente:
App
Trading Backtest Report
{{ perf_chart|safe }}
{{ drawdown_chart|safe }}
Apriamo il file “report.html” con un browser vediamo 2 grafici come segue.

Nel prossimo articolo di questa serie vediamo come calcolare alcune metriche delle prestazioni e ad inserirle in alcune tabelle del documento di report.
Successivamente, vediamo come aggiungere le simulazioni Monte-Carlo e le analisi del Value at Risk e simili.
Codice completo
In questo articolo abbiamo descritto come implementare un report delle performance di una strategia in Python. Per il codice completo riportato in questo articolo, si può consultare il seguente repository di github:
https://github.com/datatrading-info/AnalisiDatiFinanziari