Report delle performance di una strategia in Python

Report delle performance di una strategia in Python – parte 2

Sommario

Questo articolo è la seconda parte dell’attuale “mini-serie” che descrivere come implementare un report delle performance di una strategia in Python per verificare i risultati di un backtest. In particolare offre una panoramica su come creare un generatore HTML personalizzabile per un report di una strategia di trading.

Come descritto nel precedente articolo, l’obiettivo è costruire uno strumento grazie al quale è sufficiente un clic per produrre report approfonditi e interattivi relativi alle prestazioni di una strategia. Questo report è fondamentale per analizzare e filtrare il “valore” in termini di risultati del backtest di una strategia di trading. E’ sufficiente inserire nel formato corretto i dati dell’equity della strategia e (facoltativamente) i dati di un’equity benchmark all’interno di un file csv, senza dover ricreare i calcoli e grafici ogni volta.

Il codice descritto nel precedente articolo produce un file “report.html” che contiene i grafici delle equity e dei drawdown.

Indicatori delle prestazioni

Iniziamo con alcune modifiche. Invece di avere due grafici in alto, vogliamo ampliare il “Grafico delle prestazioni” ed aggiungere una tabella “Indicatori delle prestazioni” alla sua destra. In basso vogliamo creare e posizionare una tabella dei rendimenti mensili.

Innanzitutto aggiungiamo la tabella dei rendimenti mensili. Modifichiamo il file main.py come segue.

				
					
import os
import pandas as pd
import numpy as np
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()
        monthly_table = self.create_monthly_table(self.equity_curve.pct_change().dropna(), 1)
        html_out = template.render(perf_chart=perf_chart, drawdown_chart=drawdown_chart, monthly_table=monthly_table)
        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)

    def create_monthly_table(self, return_series, num_of_compenents):
        return_series.rename('weighted rets', inplace=True)
        return_series = (return_series / float(num_of_compenents))
        returns_df_m = pd.DataFrame((return_series + 1).resample('M').prod() - 1)
        returns_df_m['Month'] = returns_df_m.index.month
        monthly_table = returns_df_m[['weighted rets', 'Month']].pivot_table(returns_df_m[['weighted rets', 'Month']], index=returns_df_m.index, columns='Month', aggfunc=np.sum).resample('A')
        monthly_table = monthly_table.aggregate('sum')
        monthly_table.columns = monthly_table.columns.droplevel()

        monthly_table.index = monthly_table.index.year
        monthly_table['YTD'] = ((monthly_table + 1).prod(axis=1) - 1)
        monthly_table = monthly_table * 100

        monthly_table.columns = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
                                 'YTD']
        return monthly_table.round(2).fillna("").to_html(classes="table table-hover table-bordered table-striped")


if __name__ == "__main__":
    report = PerformanceReport('data.csv')
    report.generate_html_report()
    
				
			
Modifichiamo il file template.html come segue:
				
					<!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-8">{{ perf_chart|safe }}</div>
            <div class="col-sm-4"></div>
        </div>
        <div class="row">
            <div class="col-sm-12">{{ monthly_table|safe }}</div>
        </div>
    </div>
</body>
</html>
				
			
Aprendo il file report.html con un browser visualizziamo la seguente pagina:
Report delle performance di una strategia in Python

Passiamo al calcolo delle prestazioni della strategia per la tabella KPI che posizioniamo in alto. Per calcolare i KPI usiamo, e quindi importiamo, la libreria FFN descritta in un precedente articolo.

Per produrre la tabella dei KPI dobbiamo aggiungere alla classe PerformanceReport i nuovi metodi get_ffn_stats e create_kpi_table. Inoltre dobbiamo prevedere alcune modifiche al metodo generate_html per richiamare i due nuovi metodi.

Di seguito riportiamo il codice completo per i 2 file main.py e template.html. In questa serie di articoli siamo riportando molti blocchi di codice per mostrare il processo di “costruzione” iterativo che  dobbiamo fare quando aggiungiamo funzionalità al  report.

				
					
import os
import pandas as pd
import numpy as np
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()
        monthly_table = self.create_monthly_table(self.equity_curve.pct_change().dropna(), 1)
        equity_curve_ffn_stats = self.get_ffn_stats(self.equity_curve)
        kpi_table = self.create_kpi_table(equity_curve_ffn_stats)
        html_out = template.render(perf_chart=perf_chart, drawdown_chart=drawdown_chart, monthly_table=monthly_table,
                                   kpi_table=kpi_table)
        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)

    def create_monthly_table(self, return_series, num_of_compenents):
        return_series.rename('weighted rets', inplace=True)
        return_series = (return_series / float(num_of_compenents))
        returns_df_m = pd.DataFrame((return_series + 1).resample('M').prod() - 1)
        returns_df_m['Month'] = returns_df_m.index.month
        monthly_table = returns_df_m[['weighted rets', 'Month']].pivot_table(returns_df_m[['weighted rets', 'Month']],
                                                                             index=returns_df_m.index, columns='Month',
                                                                             aggfunc=np.sum).resample('A')
        monthly_table = monthly_table.aggregate('sum')
        monthly_table.columns = monthly_table.columns.droplevel()
 
        monthly_table.index = monthly_table.index.year
        monthly_table['YTD'] = ((monthly_table + 1).prod(axis=1) - 1)
        monthly_table = monthly_table * 100
 
        monthly_table.columns = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',
                                 'YTD']
        return monthly_table.round(2).fillna("").to_html(classes="table table-hover table-bordered table-striped")

    def get_ffn_stats(self, equity_series):
        equity_stats = equity_series.calc_stats()
        d = dict(equity_stats.stats)
        return d

    def create_kpi_table(self, ffn_dict):
        kpi_table = pd.DataFrame.from_dict(ffn_dict, orient='index')
        kpi_table.index.name = 'KPI'
        kpi_table.columns = ['Value']
        kpi_table2 = kpi_table.loc[['total_return', 'cagr', 'daily_sharpe',
                                    'daily_vol', 'max_drawdown', 'avg_drawdown']]  # .astype(float)
        kpi_table2['Value'] = pd.Series(["{0:.2f}%".format(val * 100) for val in kpi_table2['Value']],
                                        index=kpi_table2.index)
        kpi_table2.loc['avg_drawdown_days'] = kpi_table.loc['avg_drawdown_days']
        return kpi_table2.to_html(classes="table table-hover table-bordered table-striped", header=False)


if __name__ == "__main__":
    report = PerformanceReport('data.csv')
    report.generate_html_report()

				
			
				
					
<!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>
    <hr>
        <div class="row">
            <div class="col-sm-8">{{ perf_chart|safe }}</div>
            <div class="col-sm-4"><br><br>
                <h5>KPI Table</h5>
                {{ kpi_table|safe }}</div>
        </div>
    <hr>
        <div class="row">
            <h5>Monthly Returns</h5>
            <div class="col-sm-12">{{ monthly_table|safe }}</div>
        </div>
    </div>
</body>
</html>
				
			

Dopo aver eseguito lo script otteniamo il seguente report.

Report delle performance di una strategia in Python

 

Il report finale

Ora aggiungiamo nuovamente il grafico dei drawdown che abbiamo rimosso in precedenza e posizioniamolo sotto la tabella dei rendimenti mensili. Per ora impostiamo la dimensione del grafico pari alla larghezza dello schermo intero, ma possiamo cambiarlo in seguito se necessario. Dato che abbiamo già scritto il codice che crea l’oggetto grafico, e lo abbiamo già iniettato nel template HTML, non ci resta che aggiornare il file “template.html” con un paio di righe di codice.

				
					
<!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>
    <hr>
        <div class="row">
            <div class="col-sm-8">{{ perf_chart|safe }}</div>
            <div class="col-sm-4"><br><br>
                <h5>KPI Table</h5>
                {{ kpi_table|safe }}</div>
        </div>
    <hr>
        <div class="row">
            <h5>Monthly Returns</h5>
            <div class="col-sm-12">{{ monthly_table|safe }}</div>
        </div>
    <hr>
        <div class="row">
            <div class="col-sm-12">{{ drawdown_chart|safe }}</div>
        </div>
    </div>
</body>
</html>

				
			
Dopo avere aggiornato il file template.html ed eseguito nuovamente il file main.py, otteniamo il seguente report.
Report-Performance-Grafico-Finale

Il report sta iniziando a prendere forma. Siamo riusciti ad aggiungere alcuni dei componenti principali che troviamo in qualsiasi report delle performance di una strategia di trading. A questo punto, dobbiamo scegliere quali elementi/statistiche/grafici vogliamo aggiungere, in modo iterativo passo dopo passo.

Nel prossimo articolo descriviamo come calcolare, creare e inserire rapidamente un’intera gamma di componenti nel report. Dopodiché vediamo alcune funzionalità più complesse come i calcoli per il Var con Monte-Carlo e il bootstrapping del rendimento della strategia.

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