Report delle performance di una strategia in Python

Report delle performance di una strategia in Python – parte 1

Sommario

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à:

  1. Il componente principale dell’intero progetto è la classe “PerformanceReport”, implementata all’interno del file “main.py”.
  2. 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.
  3. Il file “template.html”, nella cartella “templates”, è modello HTML dove  inseriamo i nostri oggetti.
  4. 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”:

				
					<pre lang="html">
<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8" />
    <title>App</title>
    <meta name= "viewport"content="width=device-width, initial-scale=1" />
    <script charset="utf-8" src="static/app.js"></script>
    <link rel="stylesheet" href="static/app.css" />
</head>
<body>
    <h1>Trading Backtest Report</h1>
    <p>{{ test_variable }}</p>
</body>
</html>
</pre>
				
			
Possiamo vedere che 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:
Report delle performance di una strategia in Python
Abbiamo verificato che il nucleo del framework funziona correttamente! Passiamo quindi alla creazione dei grafici e dei dati che popoleranno il nostro report finale.

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 file main.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()

				
			
Le principali modifiche sono l’aggiunta del metodo 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:
				
					<pre lang="html">
<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <title>App</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <script charset="utf-8" src="static/app.js"></script>
    <link rel="stylesheet" href="static/app.css">
</head>
<body>
    <h1>Trading Backtest Report</h1>
    <p>{{ equity_curve }}</p>
</body>
</html>
</pre>
				
			
L’unica modifica al codice precedente è il nome della variabile tra le parentesi graffe cambiato in 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:
Report delle performance di una strategia in Python

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()

				
			
mentre il file template.html diventa:
				
					<pre lang="html">
<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <title>App</title>
    
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
    
    <link rel="stylesheet" href="static/app.css">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    
    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
    
    <script charset="utf-8" src="static/app.js"></script>
</head>
<body>
    <div class="container">
    <h1>Trading Backtest Report</h1>
    <div class="row">
        <div class="col-sm-6">{{ perf_chart|safe }}</div>
        <div class="col-sm-6"></div>
    </div>
</div>
</body>
</html>
</pre>
				
			

Otteniamo il seguente risultato:

Report-Performance-Grafico

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:

				
					<pre lang="html">
<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8">
    <title>App</title>
    
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css" integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4" crossorigin="anonymous">
    
    <link rel="stylesheet" href="static/app.css">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    
    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
    
    <script charset="utf-8" src="static/app.js"></script>
</head>
<body>
    <div class="container">
    <h1>Trading Backtest Report</h1>
        <div class="row">
            <div class="col-sm-6">{{ perf_chart|safe }}</div>
            <div class="col-sm-6">{{ drawdown_chart|safe }}</div>
        </div>
    </div>
</body>
</html>
</pre>
				
			

Apriamo il file “report.html” con un browser vediamo 2 grafici come segue.

Report-Performance-Grafico-Drawdown

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

Benvenuto su DataTrading!

Sono Gianluca, ingegnere software e data scientist. Sono appassionato di coding, finanza e trading. Leggi la mia storia.

Ho creato DataTrading per aiutare le altre persone ad utilizzare nuovi approcci e nuovi strumenti, ed applicarli correttamente al mondo del trading.

DataTrading vuole essere un punto di ritrovo per scambiare esperienze, opinioni ed idee.

SCRIVIMI SU TELEGRAM

Per informazioni, suggerimenti, collaborazioni...

Torna in alto
Scroll to Top