Report delle performance di una strategia in Python

Report delle performance di una strategia in Python – parte 4

Sommario

Questo articolo è la quarta parte della “mini-serie” che descrivere come implementare un report delle performance di una strategia in Python per verificare i risultati di un backtest. La serie offre una panoramica per creare un programma che genera un report delle prestazioni in un formato HTML piacevole e dall’aspetto elegante, da visualizzare in un brower. Partiamo dal codice descritto nell‘articolo precedente nei file main.py e template.html e ricreiamo la struttura delle cartelle e dei file previsti dal progetto, come descritto nella prima parte di serie. Siamo pronti a proseguire  con ulteriori modifiche.

Come anticipato nel precedente articolo, vediamo come aggiungere un po’ di funzionalità avanzate e molto utili per la creazione di report delle prestazioni di una strategia. Molti trader sistematici sono interessati alle analisi Monte Carlo e alle informazioni che può offrire, oltre a quelle statistiche create dalla serie di rendimenti effettivi della strategia di investimento/trading in esame.

Le simulazioni Monte Carlo

Il motivo principale per cui  usiamo i metodi Monte Carlo è provare a modellare la “incertezza” e vedere le distribuzioni di risultati possibili ed alternativi, invece che le stime puntuali. In altre parole, la serie di rendimenti inseriti nel file “data.csv” è un possibile andamento dei rendimenti effettivi, sia che si tratti dell’output di un backtest simulato, o addirittura di un track record di strategia dal vivo. In teoria ci sono infiniti possibili andamenti che la serie dei rendimenti avrebbe potuto avere lungo il tempo. I movimenti dei mercati sottostanti, come  per ogni cosa nella vita, sono soggetti a un elemento di rumore e casualità. In effetti, le probabilità che la strategia funzioni esattamente allo stesso modo in futuro sono esattamente pari a zero.

Questo concetto potrebbe essere difficile da capire. Perché la probabilità che qualcosa accada è pari a zero quando è evidente che è già accaduto in passato? Un altro modo di pensare è il seguente. Se qualcuno vi chiedesse di prevedere quale sarà il prezzo dell’indice S&P 500 alla fine del prossimo anno, come rispondereste? Prova a indovinare e dai loro una cifra? Se lo fai ti sbaglieresti, te lo garantisco.

La logica deriva dal fatto che i rendimenti degli asset finanziari e delle strategie di investimento sono considerati “variabili aleatorie continue”. Vale a dire che possono assumere un numero infinito di possibili valori. La strategia potrebbe restituire il 10% in un anno… o potrebbe essere il 10,5%, o in realtà potrebbe essere il 10,52%… che ne dici del 10,528%? Possiamo continuare ed ottenere una cifra sempre più precisa quindi in teoria ci sono un numero infinito di possibili risultati. Se esiste un numero infinito di possibili risultati per una variabile, le possibilità che riusciamo ad individuare  il risultato finale sono pari a zero.

In questo scenario entrano in gioco le funzioni di densità di probabilità. Queste funzioni permettono di assegnare una probabilità che un qualsiasi valore, di un insieme continuo di valori, possa verificarsi. Puoi consultare questa documentazione se vuoi approfondire i concetti matematici. Cosa c’entra tutto questo con la simulazione Monte Carlo? Vogliamo usare la simulazione MC per incorporare “incertezza/casualità” nel modello. Possiamo usare questo modello per simulare un gran numero di possibili risultati che  potrebbero verificarsi realisticamente, a partire dalle caratteristiche alla base della strategia.

Queste molteplici simulazioni, se modellate correttamente, permetto di produrre una “distribuzione” dei risultati, invece di fare affidamento su un unico “risultato puntuale” che è stato prodotto dai dati contenuti nel file csv. Da questa distribuzione dei risultati, possiamo iniziare ad assegnare le probabilità alle possibilità di finire entro determinati intervalli di detti risultati.

Calcolare e visualizzare le simulazioni

Iniziamo a lavorare sul codice con un esempio pratico in modo da rendere più chiaro l’approccio per costruire un report delle performance di una strategia in Python. La prima cosa da fare è aggiornare il nostro codice con un nuovo metodo che prende l’equity della strategia come input e restituisce un DataFrame contenente un numero  specifico di simulazioni Monte Carlo. Le simulazioni sono generate tramite un modello basato sul rendimento sottostante e sulle caratteristiche di volatilità della stessa serie  di equity.

Dopo aver ottenuto l’output della simulazione, possiamo aggiungere un altro metodo per generare un grafico e visualizzare i risultati.

Il codice aggiornato per il file main.py è il seguente.

				
					import os
import math
import pandas as pd
import numpy as np
import random
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())
        equity_curve_ffn_stats = self.get_ffn_stats(self.equity_curve)
        benchmark_curve_ffn_stats = self.get_ffn_stats(self.benchmark_curve)
        kpi_table = self.create_kpi_table(equity_curve_ffn_stats)
        kpi_table_full = self.create_kpi_table_full([equity_curve_ffn_stats, benchmark_curve_ffn_stats])
        kpi_table_1, kpi_table_2, kpi_table_3, kpi_table_4, kpi_table_5 = self.split_kpi_table(kpi_table_full)
        daily_ret_hist = self.plot_daily_histogram()
        daily_ret_box = self.plot_daily_box()
        simulations = 250
        periods = 252
        monte_carlo_results, monte_carlo_hist = self.run_monte_carlo_parametric(self.equity_curve.pct_change().dropna(),
                                                                                periods, simulations)
        mc_chart = self.plot_mc_chart(monte_carlo_results)
        html_out = template.render(perf_chart=perf_chart, drawdown_chart=drawdown_chart, monthly_table=monthly_table,
                                   kpi_table=kpi_table, kpi_table_1=kpi_table_1, kpi_table_2=kpi_table_2,
                                   kpi_table_3=kpi_table_3, kpi_table_4=kpi_table_4, kpi_table_5=kpi_table_5,
                                   daily_ret_hist=daily_ret_hist, daily_ret_box=daily_ret_box, mc_chart=mc_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',
            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',
            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):
        return_series.rename('weighted rets', inplace=True)
        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.replace(0.0, "", inplace=True)

        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_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']
        kpi_table2.loc['daily_sharpe'] = np.round(kpi_table.loc['daily_sharpe'].values[0], 2)
        return kpi_table2.to_html(classes="table table-hover table-bordered table-striped", header=False)

    def create_kpi_table_full(self, ffn_dict_list):
        df_list = [pd.DataFrame.from_dict(x, orient='index') for x in ffn_dict_list]
        kpi_table_full = pd.concat(df_list, axis=1)
        return kpi_table_full

    def split_kpi_table(self, kpi_table_full):
        kpi_table_1 = kpi_table_full.iloc[3:16].to_html(classes="table table-hover table-bordered table-striped",
                                                        header=False)
        kpi_table_2 = kpi_table_full.iloc[16:24].to_html(classes="table table-hover table-bordered table-striped",
                                                         header=False)
        kpi_table_3 = kpi_table_full.iloc[24:32].to_html(classes="table table-hover table-bordered table-striped",
                                                         header=False)
        kpi_table_4 = kpi_table_full.iloc[32:40].to_html(classes="table table-hover table-bordered table-striped",
                                                         header=False)
        kpi_table_5 = kpi_table_full.iloc[40:].to_html(classes="table table-hover table-bordered table-striped",
                                                       header=False)
        return kpi_table_1, kpi_table_2, kpi_table_3, kpi_table_4, kpi_table_5

    def run_monte_carlo_parametric(self, returns, trading_days, simulations):
        df_list = []
        result = []
        S = 100
        T = trading_days
        mu = returns.mean()
        vol = returns.std()

        for i in range(simulations):

            daily_returns = np.random.normal(mu / T, vol / math.sqrt(T), T) + 1
            price_list = [S]

            for x in daily_returns:
                price_list.append(price_list[-1] * x)

            df_list.append(pd.DataFrame(price_list))
            result.append(price_list[-1])
        df_master = pd.concat(df_list, axis=1)
        df_master.columns = range(len(df_master.columns))

        return df_master, result

    def plot_daily_histogram(self):
        trace0 = go.Histogram(x=self.equity_curve.pct_change().values,
                              name="Strategy",
                              opacity=0.75,
                              marker=dict(
                                  color=('rgb(22, 96, 167)')),
                              xbins=dict(
                                  size=0.0025
                              ))
        trace1 = go.Histogram(x=self.benchmark_curve.pct_change().values,
                              name="Benchmark",
                              opacity=0.75,
                              marker=dict(
                                  color=('rgb(22, 96, 0)')),
                              xbins=dict(
                                  size=0.0025
                              ))
        data = [trace0, trace1]
        layout = go.Layout(
            title='Histogram of Strategy and Benchmark Daily Returns',
            autosize=True,
            height=600,
            hovermode='closest',
            barmode='overlay'
        )
        daily_ret_hist = plotly.offline.plot({"data": data, "layout": layout},
                                             include_plotlyjs=False,
                                             output_type='div')

        return daily_ret_hist

    def plot_daily_box(self):
        trace0 = go.Box(y=self.equity_curve.pct_change().values,
                        name="Strategy",
                        marker=dict(
                            color=('rgb(22, 96, 167)')))
        trace1 = go.Box(y=self.benchmark_curve.pct_change().values,
                        name="Benchmark",
                        marker=dict(
                            color=('rgb(22, 96, 0)')))
        data = [trace0, trace1]

        layout = go.Layout(
            title='Boxplot of Strategy and Benchmark Daily Returns',
            autosize=True,
            height=600,
            yaxis=dict(
                zeroline=False
            )
        )

        box_plot = plotly.offline.plot({"data": data, "layout": layout}, include_plotlyjs=False,
                                       output_type='div')

        return box_plot

    def get_random_rgb(self):
        col = 'rgb' + str((random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
        return col

    def plot_mc_chart(self, monte_carlo_results):
        mc_trace_list = []
        for col in monte_carlo_results.columns:
            rgb = self.get_random_rgb()
            trace = go.Scatter(
                x=monte_carlo_results.index.tolist(),
                y=monte_carlo_results[col].values.tolist(),
                name='mc data',
                yaxis='y2',
                line=dict(color=(rgb)))
            mc_trace_list.append(trace)

        layout_mc = go.Layout(
            title='Monte Carlo Parametric',
            yaxis=dict(title='Equity'),
            autosize=True,
            height=600,
            showlegend=False,
            hovermode='closest'
        )
        mc_chart = plotly.offline.plot({"data": mc_trace_list,
                                        "layout": layout_mc}, include_plotlyjs=False,
                                       output_type='div')
        return mc_chart


if __name__ == "__main__":
    report = PerformanceReport('data.csv')
    report.generate_html_report()
				
			
Mentre il file template.html  è aggiornato 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">
            <div class="row">
        <h1>Trading Backtest Report</h1>
        </div>
    <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>
        <div class="row">
            <div class="col-sm-12">
                                  {{ monthly_table|safe }}
            </div>
        </div>
    <hr>
        <div class="row">
            <div class="col-sm-12">{{ drawdown_chart|safe }}</div>
        </div>
        <hr>
        <div class="row"><h5>KPI Tables</h5></div>
        <div class="row">
           
            <div class="col-sm-6"> 
                                  {{ kpi_table_1|safe }}<br>
                                  {{ kpi_table_5|safe }}
            </div>
            <div class="col-sm-6">{{ kpi_table_2|safe }}<br>
                                  {{ kpi_table_3|safe }}<br>
                                  {{ kpi_table_4|safe }}<br>
            </div>
        </div>
        <hr>
        <div class="row">
            <div class="col-sm-12">{{ daily_ret_hist|safe }}</div>
        </div>
        <hr>
        <div class="row">
                <div class="col-sm-12">{{ daily_ret_box|safe }}</div>
        </div>
        <hr>
        <div class="row">
                <div class="col-sm-12">{{ mc_chart|safe }}</div>
        </div>
    </div>
</body>
</html>

				
			

Otteniamo il seguente un grafico delle simulazioni Monte Carlo.

Report delle performance di una strategia in Python

 

Report con la distribuzione delle simulazioni

Abbiamo visto cosa intendiamo quando parliamo di “generare una gamma di risultati”. Possiamo vedere chiaramente che le possibilità di essere in grado di prevedere l’andamento dei rendimenti tendono essere nulle, dopo aver modellato l’effetto della casualità, anche con gli stessi input di rendimento atteso e volatilità. Se osserviamo l’intervallo di valori sull’asse y, vediamo che ci sono serie di rendimenti che arrivano fino a 180 (a partire da un indice di 100) e altre che arrivano fino a 60. È un intervallo piuttosto ampio per quella che presumiamo essere la “stessa” strategia.

Bene, questo è più o meno il punto fondamentale di questo esercizio: permetterci di quantificare le possibilità di finire in diversi punti finali all’interno dell’insieme “piuttosto grande” dei risultati possibili.

È un po’ difficile vedere cosa sta realmente accadendo tra tutte quelle linee del grafico precedente. Dobbiamo quindi prevedere un modo migliore per visualizzare la distribuzione dei valori finali. Possiamo prevedere un istogramma o un grafico di distribuzione.

Aggiungendo questi grafici al nostro report, il codice aggiornato del file main.py diventa come segue.

				
					import os
import math
import pandas as pd
import numpy as np
import random
import plotly
import plotly.graph_objs as go
import plotly.figure_factory as ff
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())
        equity_curve_ffn_stats = self.get_ffn_stats(self.equity_curve)
        benchmark_curve_ffn_stats = self.get_ffn_stats(self.benchmark_curve)
        kpi_table = self.create_kpi_table(equity_curve_ffn_stats)
        kpi_table_full = self.create_kpi_table_full([equity_curve_ffn_stats, benchmark_curve_ffn_stats])
        kpi_table_1, kpi_table_2, kpi_table_3, kpi_table_4, kpi_table_5 = self.split_kpi_table(kpi_table_full)
        daily_ret_hist = self.plot_daily_histogram()
        daily_ret_box = self.plot_daily_box()
        simulations = 250
        periods = 252
        monte_carlo_results, monte_carlo_hist = self.run_monte_carlo_parametric(self.equity_curve.pct_change().dropna(),
                                                                                periods, simulations)
        mc_chart = self.plot_mc_chart(monte_carlo_results)
        mc_dist_chart = self.plot_mc_dist_chart(monte_carlo_hist)
        mc_hist_chart = self.plot_mc_hist_chart(monte_carlo_hist)

        html_out = template.render(perf_chart=perf_chart, drawdown_chart=drawdown_chart, monthly_table=monthly_table,
                                   kpi_table=kpi_table, kpi_table_1=kpi_table_1, kpi_table_2=kpi_table_2,
                                   kpi_table_3=kpi_table_3, kpi_table_4=kpi_table_4, kpi_table_5=kpi_table_5,
                                   daily_ret_hist=daily_ret_hist, daily_ret_box=daily_ret_box, mc_chart=mc_chart,
                                   mc_dist_chart=mc_dist_chart, mc_hist_chart=mc_hist_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',
            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',
            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):
        return_series.rename('weighted rets', inplace=True)
        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.replace(0.0, "", inplace=True)

        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_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']
        kpi_table2.loc['daily_sharpe'] = np.round(kpi_table.loc['daily_sharpe'].values[0], 2)
        return kpi_table2.to_html(classes="table table-hover table-bordered table-striped", header=False)

    def create_kpi_table_full(self, ffn_dict_list):
        df_list = [pd.DataFrame.from_dict(x, orient='index') for x in ffn_dict_list]
        kpi_table_full = pd.concat(df_list, axis=1)
        return kpi_table_full

    def split_kpi_table(self, kpi_table_full):
        kpi_table_1 = kpi_table_full.iloc[3:16].to_html(classes="table table-hover table-bordered table-striped",
                                                        header=False)
        kpi_table_2 = kpi_table_full.iloc[16:24].to_html(classes="table table-hover table-bordered table-striped",
                                                         header=False)
        kpi_table_3 = kpi_table_full.iloc[24:32].to_html(classes="table table-hover table-bordered table-striped",
                                                         header=False)
        kpi_table_4 = kpi_table_full.iloc[32:40].to_html(classes="table table-hover table-bordered table-striped",
                                                         header=False)
        kpi_table_5 = kpi_table_full.iloc[40:].to_html(classes="table table-hover table-bordered table-striped",
                                                       header=False)
        return kpi_table_1, kpi_table_2, kpi_table_3, kpi_table_4, kpi_table_5

    def run_monte_carlo_parametric(self, returns, trading_days, simulations):
        df_list = []
        result = []
        S = 100
        T = trading_days
        mu = returns.mean()
        vol = returns.std()

        for i in range(simulations):

            daily_returns = np.random.normal(mu / T, vol / math.sqrt(T), T) + 1

            price_list = [S]

            for x in daily_returns:
                price_list.append(price_list[-1] * x)

            df_list.append(pd.DataFrame(price_list))
            result.append(price_list[-1])
        df_master = pd.concat(df_list, axis=1)
        df_master.columns = range(len(df_master.columns))

        return df_master, result

    def plot_daily_histogram(self):
        trace0 = go.Histogram(x=self.equity_curve.pct_change().values,
                              name="Strategy",
                              opacity=0.75,
                              marker=dict(
                                  color=('rgb(22, 96, 167)')),
                              xbins=dict(
                                  size=0.0025
                              ))
        trace1 = go.Histogram(x=self.benchmark_curve.pct_change().values,
                              name="Benchmark",
                              opacity=0.75,
                              marker=dict(
                                  color=('rgb(22, 96, 0)')),
                              xbins=dict(
                                  size=0.0025
                              ))
        data = [trace0, trace1]
        layout = go.Layout(
            title='Histogram of Strategy and Benchmark Daily Returns',
            autosize=True,
            height=600,
            hovermode='closest',
            barmode='overlay'
        )
        daily_ret_hist = plotly.offline.plot({"data": data, "layout": layout},
                                             include_plotlyjs=False,
                                             output_type='div')

        return daily_ret_hist

    def plot_daily_box(self):
        trace0 = go.Box(y=self.equity_curve.pct_change().values,
                        name="Strategy",
                        marker=dict(
                            color=('rgb(22, 96, 167)')))
        trace1 = go.Box(y=self.benchmark_curve.pct_change().values,
                        name="Benchmark",
                        marker=dict(
                            color=('rgb(22, 96, 0)')))
        data = [trace0, trace1]

        layout = go.Layout(
            title='Boxplot of Strategy and Benchmark Daily Returns',
            autosize=True,
            height=600,
            yaxis=dict(
                zeroline=False
            )
        )

        box_plot = plotly.offline.plot({"data": data, "layout": layout}, include_plotlyjs=False,
                                       output_type='div')

        return box_plot

    def get_random_rgb(self):
        col = 'rgb' + str((random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
        return col

    def plot_mc_chart(self, monte_carlo_results):
        mc_trace_list = []
        for col in monte_carlo_results.columns:
            rgb = self.get_random_rgb()
            trace = go.Scatter(
                x=monte_carlo_results.index.tolist(),
                y=monte_carlo_results[col].values.tolist(),
                name='mc data',
                yaxis='y2',
                line=dict(color=(rgb)))
            mc_trace_list.append(trace)

        layout_mc = go.Layout(
            title='Monte Carlo Parametric',
            yaxis=dict(title='Equity'),
            autosize=True,
            height=600,
            showlegend=False,
            hovermode='closest'
        )
        mc_chart = plotly.offline.plot({"data": mc_trace_list,
                                        "layout": layout_mc}, include_plotlyjs=False,
                                       output_type='div')
        return mc_chart

    def plot_mc_dist_chart(self, monte_carlo_hist):
        fig = ff.create_distplot([monte_carlo_hist], group_labels=['Strategy Monte Carlo Returns'], show_rug=False)
        fig['layout'].update(autosize=True, height=600,
                             title="Parametric Monte Carlo Simulations <br> (Distribution Plot - Ending Equity)",
                             showlegend=False)
        dist_plot_MC_parametric = plotly.offline.plot(fig, include_plotlyjs=False,
                                                      output_type='div')
        return dist_plot_MC_parametric

    def plot_mc_hist_chart(self, monte_carlo_hist):
        layout_mc = go.Layout(
            title="Parametric Monte Carlo Simulations <br> (Histogram - Ending Equity)",
            autosize=True,
            height=600,
            showlegend=False,
            hovermode='closest'
        )

        data_mc_hist = [go.Histogram(x=monte_carlo_hist,
                                     histnorm='probability')]
        mc_hist = plotly.offline.plot({"data": data_mc_hist, "layout": layout_mc}, include_plotlyjs=False,
                                      output_type='div')
        return mc_hist


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

Il file template.html  è aggiornato 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">
            <div class="row">
        <h1>Trading Backtest Report</h1>
        </div>
    <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>
        <div class="row">
            <div class="col-sm-12">
                                  {{ monthly_table|safe }}
            </div>
        </div>
    <hr>
        <div class="row">
            <div class="col-sm-12">{{ drawdown_chart|safe }}</div>
        </div>
        <hr>
        <div class="row"><h5>KPI Tables</h5></div>
        <div class="row">
           
            <div class="col-sm-6"> 
                                  {{ kpi_table_1|safe }}<br>
                                  {{ kpi_table_5|safe }}
            </div>
            <div class="col-sm-6">{{ kpi_table_2|safe }}<br>
                                  {{ kpi_table_3|safe }}<br>
                                  {{ kpi_table_4|safe }}<br>
            </div>
        </div>
        <hr>
        <div class="row">
            <div class="col-sm-12">{{ daily_ret_hist|safe }}</div>
        </div>
        <hr>
        <div class="row">
                <div class="col-sm-12">{{ daily_ret_box|safe }}</div>
        </div>
        <hr>
        <div class="row">
                <div class="col-sm-12">{{ mc_chart|safe }}</div>
        </div>
        <hr>
        <div class="row">
                <div class="col-sm-6">{{ mc_dist_chart|safe }}</div>
                <div class="col-sm-6">{{ mc_hist_chart|safe }}</div>
        </div>
        <hr>
    </div>
</body>
</html>

				
			

Otteniamo i seguenti 2 grafici nella parte inferiore del  report.

Report-Performance-Grafico-Montecarlo-Distribuzione

 

Report con le statistiche delle simulazioni

Dai risultati di queste simulati possiamo identificare i vari quantili dei rendimenti che hanno una % di probabilità che si verifichi. Ad esempio, in teoria abbiamo un 5% di probabilità che il risultato sia superiore al valore trovato al 95° quantile o inferiore a quello al 5° quantile. Una probabilità del 10% che  sia superiore al 90° quantile o inferiore al 10° quantile, e cosi via.

Oltre a estrarre questi valori dalla distribuzione dei valori finali, abbiamo estratto le simulazioni migliori e peggiori dalle simulazioni e abbiamo visualizzato i loro KPI e statistiche. Questi sono tutti mostrati sotto l’istogramma e il grafico di distribuzione.

Il codice per “main.py” è stato aggiornato come segue.

				
					import os
import math
import pandas as pd
import numpy as np
import random
import datetime
import plotly
import plotly.graph_objs as go
import plotly.figure_factory as ff
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())
        equity_curve_ffn_stats = self.get_ffn_stats(self.equity_curve)

        benchmark_curve_ffn_stats = self.get_ffn_stats(self.benchmark_curve)
        kpi_table = self.create_kpi_table(equity_curve_ffn_stats)
        kpi_table_full = self.create_kpi_table_full([equity_curve_ffn_stats, benchmark_curve_ffn_stats])
        kpi_table_1, kpi_table_2, kpi_table_3, kpi_table_4, kpi_table_5 = self.split_kpi_table(kpi_table_full)
        daily_ret_hist = self.plot_daily_histogram()
        daily_ret_box = self.plot_daily_box()
        simulations = 250
        periods = 252
        monte_carlo_results, monte_carlo_hist, mc_max_dd_list = self.run_monte_carlo_parametric(
            self.equity_curve.pct_change().dropna(), periods, simulations)
        mc_chart = self.plot_mc_chart(monte_carlo_results)
        mc_dist_chart = self.plot_mc_dist_chart(monte_carlo_hist)
        mc_hist_chart = self.plot_mc_hist_chart(monte_carlo_hist)
        mc_best_worst_df = self.extract_best_worst(monte_carlo_results, periods)
        best_df_ffn_stats = self.get_ffn_stats(mc_best_worst_df['Best'])
        worst_df_ffn_stats = self.get_ffn_stats(mc_best_worst_df['Worst'])
        best_df_kpi = self.create_kpi_table(best_df_ffn_stats)
        worst_df_kpi = self.create_kpi_table(worst_df_ffn_stats)
        # mc_5perc,mc_95perc = self.calc_mc_var(monte_carlo_results,5)
        mc_dict_perf = self.mc_perf_probs(monte_carlo_results)
        mc_dict_dd = self.mc_dd_probs(mc_max_dd_list)
        html_out = template.render(perf_chart=perf_chart, drawdown_chart=drawdown_chart, monthly_table=monthly_table,
                                   kpi_table=kpi_table, kpi_table_1=kpi_table_1, kpi_table_2=kpi_table_2,
                                   kpi_table_3=kpi_table_3, kpi_table_4=kpi_table_4, kpi_table_5=kpi_table_5,
                                   daily_ret_hist=daily_ret_hist, daily_ret_box=daily_ret_box, mc_chart=mc_chart,
                                   mc_dist_chart=mc_dist_chart, mc_hist_chart=mc_hist_chart, best_df_kpi=best_df_kpi,
                                   worst_df_kpi=worst_df_kpi, mc_dict_perf=mc_dict_perf, mc_dict_dd=mc_dict_dd)
        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',
            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',
            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):
        return_series.rename('weighted rets', inplace=True)
        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.replace(0.0, "", inplace=True)

        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_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']
        kpi_table2.loc['daily_sharpe'] = np.round(kpi_table.loc['daily_sharpe'].values[0], 2)
        return kpi_table2.to_html(classes="table table-hover table-bordered table-striped", header=False)

    def create_kpi_table_full(self, ffn_dict_list):
        df_list = [pd.DataFrame.from_dict(x, orient='index') for x in ffn_dict_list]
        kpi_table_full = pd.concat(df_list, axis=1)
        return kpi_table_full

    def split_kpi_table(self, kpi_table_full):
        kpi_table_1 = kpi_table_full.iloc[3:16].to_html(classes="table table-hover table-bordered table-striped",
                                                        header=False)
        kpi_table_2 = kpi_table_full.iloc[16:24].to_html(classes="table table-hover table-bordered table-striped",
                                                         header=False)
        kpi_table_3 = kpi_table_full.iloc[24:32].to_html(classes="table table-hover table-bordered table-striped",
                                                         header=False)
        kpi_table_4 = kpi_table_full.iloc[32:40].to_html(classes="table table-hover table-bordered table-striped",
                                                         header=False)
        kpi_table_5 = kpi_table_full.iloc[40:].to_html(classes="table table-hover table-bordered table-striped",
                                                       header=False)
        return kpi_table_1, kpi_table_2, kpi_table_3, kpi_table_4, kpi_table_5

    def run_monte_carlo_parametric(self, returns, trading_days, simulations):

        df_list = []
        result = []
        S = 100
        T = trading_days
        mu = returns.mean()
        vol = returns.std()
        dd_result = []
        for i in range(simulations):

            daily_returns = np.random.normal(mu / T, vol / math.sqrt(T), T) + 1

            price_list = [S]

            for x in daily_returns:
                price_list.append(price_list[-1] * x)
            df = pd.DataFrame(price_list)
            max_dd = ffn.calc_max_drawdown(df)
            dd_result.append(max_dd)
            df_list.append(df)
            result.append(price_list[-1])
        df_master = pd.concat(df_list, axis=1)
        df_master.columns = range(len(df_master.columns))

        return df_master, result, dd_result

    def extract_best_worst(self, monte_carlo_results, trading_days):
        today = datetime.datetime.today().strftime('%d/%m/%Y')
        date_range = pd.bdate_range(end=today, periods=trading_days + 1, freq='B')
        monte_carlo_results.columns = range(len(monte_carlo_results.columns))
        last_row = monte_carlo_results.iloc[-1]
        max_col = last_row.idxmax()
        best_df = monte_carlo_results[max_col]
        min_col = last_row.idxmin()
        worst_df = monte_carlo_results[min_col]
        best_df = best_df.to_frame().set_index(date_range)
        worst_df = worst_df.to_frame().set_index(date_range)
        mc_best_worst_df = pd.concat([best_df, worst_df], axis=1)
        mc_best_worst_df.columns = ['Best', 'Worst']

        return mc_best_worst_df

    def calc_mc_var(self, monte_carlo_results, confidence):

        mc_as_array = np.array(monte_carlo_results)
        mc_low_perc = round(((np.percentile(mc_as_array, 100 - confidence) / 100) - 1) * 100, 2)
        mc_high_perc = round(((np.percentile(mc_as_array, confidence) / 100) - 1) * 100, 2)
        return mc_low_perc, mc_high_perc

    def mc_perf_probs(self, monte_carlo_results):
        mc_as_array = np.array(monte_carlo_results)
        mc_5perc = round(((np.percentile(mc_as_array, 5) / 100) - 1) * 100, 2)
        mc_95perc = round(((np.percentile(mc_as_array, 95) / 100) - 1) * 100, 2)
        mc_1perc = round(((np.percentile(mc_as_array, 1) / 100) - 1) * 100, 2)
        mc_10perc = round(((np.percentile(mc_as_array, 10) / 100) - 1) * 100, 2)
        mc_20perc = round(((np.percentile(mc_as_array, 20) / 100) - 1) * 100, 2)
        mc_30perc = round(((np.percentile(mc_as_array, 30) / 100) - 1) * 100, 2)
        mc_40perc = round(((np.percentile(mc_as_array, 40) / 100) - 1) * 100, 2)
        mc_50perc = round(((np.percentile(mc_as_array, 50) / 100) - 1) * 100, 2)
        mc_60perc = round(((np.percentile(mc_as_array, 60) / 100) - 1) * 100, 2)
        mc_70perc = round(((np.percentile(mc_as_array, 70) / 100) - 1) * 100, 2)
        mc_80perc = round(((np.percentile(mc_as_array, 80) / 100) - 1) * 100, 2)
        mc_90perc = round(((np.percentile(mc_as_array, 90) / 100) - 1) * 100, 2)
        mc_99perc = round(((np.percentile(mc_as_array, 99) / 100) - 1) * 100, 2)

        mc_dict_perf = {
            'mc_1perc': mc_1perc,
            'mc_5perc': mc_5perc,
            'mc_10perc': mc_10perc,
            'mc_20perc': mc_20perc,
            'mc_30perc': mc_30perc,
            'mc_40perc': mc_40perc,
            'mc_50perc': mc_50perc,
            'mc_60perc': mc_60perc,
            'mc_70perc': mc_70perc,
            'mc_80perc': mc_80perc,
            'mc_90perc': mc_90perc,
            'mc_95perc': mc_95perc,
            'mc_99perc': mc_99perc
        }
        return mc_dict_perf

    def mc_dd_probs(self, mc_max_dd_list):
        mc_as_array_dd = np.array(mc_max_dd_list)
        mc_5perc_dd = round((np.percentile(mc_as_array_dd, 5)) * 100, 2)
        mc_95perc_dd = round((np.percentile(mc_as_array_dd, 95)) * 100, 2)

        mc_1perc_dd = round((np.percentile(mc_as_array_dd, 1)) * 100, 2)
        mc_10perc_dd = round((np.percentile(mc_as_array_dd, 10)) * 100, 2)
        mc_20perc_dd = round((np.percentile(mc_as_array_dd, 20)) * 100, 2)
        mc_30perc_dd = round((np.percentile(mc_as_array_dd, 30)) * 100, 2)
        mc_40perc_dd = round((np.percentile(mc_as_array_dd, 40)) * 100, 2)
        mc_50perc_dd = round((np.percentile(mc_as_array_dd, 50)) * 100, 2)
        mc_60perc_dd = round((np.percentile(mc_as_array_dd, 60)) * 100, 2)
        mc_70perc_dd = round((np.percentile(mc_as_array_dd, 70)) * 100, 2)
        mc_80perc_dd = round((np.percentile(mc_as_array_dd, 80)) * 100, 2)
        mc_90perc_dd = round((np.percentile(mc_as_array_dd, 90)) * 100, 2)
        mc_99perc_dd = round((np.percentile(mc_as_array_dd, 99)) * 100, 2)

        mc_dict_dd = {
            'mc_1perc_dd': mc_1perc_dd,
            'mc_5perc_dd': mc_5perc_dd,
            'mc_10perc_dd': mc_10perc_dd,
            'mc_20perc_dd': mc_20perc_dd,
            'mc_30perc_dd': mc_30perc_dd,
            'mc_40perc_dd': mc_40perc_dd,
            'mc_50perc_dd': mc_50perc_dd,
            'mc_60perc_dd': mc_60perc_dd,
            'mc_70perc_dd': mc_70perc_dd,
            'mc_80perc_dd': mc_80perc_dd,
            'mc_90perc_dd': mc_90perc_dd,
            'mc_95perc_dd': mc_95perc_dd,
            'mc_99perc_dd': mc_99perc_dd
        }
        return mc_dict_dd

    def plot_daily_histogram(self):
        trace0 = go.Histogram(x=self.equity_curve.pct_change().values,
                              name="Strategy",
                              opacity=0.75,
                              marker=dict(
                                  color=('rgb(22, 96, 167)')),
                              xbins=dict(
                                  size=0.0025
                              ))
        trace1 = go.Histogram(x=self.benchmark_curve.pct_change().values,
                              name="Benchmark",
                              opacity=0.75,
                              marker=dict(
                                  color=('rgb(22, 96, 0)')),
                              xbins=dict(
                                  size=0.0025
                              ))
        data = [trace0, trace1]
        layout = go.Layout(
            title='Histogram of Strategy and Benchmark Daily Returns',
            autosize=True,
            height=600,
            hovermode='closest',
            barmode='overlay'
        )
        daily_ret_hist = plotly.offline.plot({"data": data, "layout": layout},
                                             include_plotlyjs=False,
                                             output_type='div')

        return daily_ret_hist

    def plot_daily_box(self):
        trace0 = go.Box(y=self.equity_curve.pct_change().values,
                        name="Strategy",
                        marker=dict(
                            color=('rgb(22, 96, 167)')))
        trace1 = go.Box(y=self.benchmark_curve.pct_change().values,
                        name="Benchmark",
                        marker=dict(
                            color=('rgb(22, 96, 0)')))
        data = [trace0, trace1]

        layout = go.Layout(
            title='Boxplot of Strategy and Benchmark Daily Returns',
            autosize=True,
            height=600,
            yaxis=dict(
                zeroline=False
            )
        )

        box_plot = plotly.offline.plot({"data": data, "layout": layout}, include_plotlyjs=False,
                                       output_type='div')

        return box_plot

    def get_random_rgb(self):
        col = 'rgb' + str((random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)))
        return col

    def plot_mc_chart(self, monte_carlo_results):
        mc_trace_list = []
        for col in monte_carlo_results.columns:
            rgb = self.get_random_rgb()
            trace = go.Scatter(
                x=monte_carlo_results.index.tolist(),
                y=monte_carlo_results[col].values.tolist(),
                name='mc data',
                yaxis='y2',
                line=dict(color=(rgb)))
            mc_trace_list.append(trace)

        layout_mc = go.Layout(
            title='Monte Carlo Parametric',
            yaxis=dict(title='Equity'),
            autosize=True,
            height=600,
            showlegend=False,
            hovermode='closest'
        )
        mc_chart = plotly.offline.plot({"data": mc_trace_list,
                                        "layout": layout_mc}, include_plotlyjs=False,
                                       output_type='div')
        return mc_chart

    def plot_mc_dist_chart(self, monte_carlo_hist):
        fig = ff.create_distplot([monte_carlo_hist], group_labels=['Strategy Monte Carlo Returns'], show_rug=False)
        fig['layout'].update(autosize=True, height=600,
                             title="Parametric Monte Carlo Simulations <br> (Distribution Plot - Ending Equity)",
                             showlegend=False)
        dist_plot_MC_parametric = plotly.offline.plot(fig, include_plotlyjs=False,
                                                      output_type='div')
        return dist_plot_MC_parametric

    def plot_mc_hist_chart(self, monte_carlo_hist):
        layout_mc = go.Layout(
            title="Parametric Monte Carlo Simulations <br> (Histogram - Ending Equity)",
            autosize=True,
            height=600,
            showlegend=False,
            hovermode='closest'
        )

        data_mc_hist = [go.Histogram(x=monte_carlo_hist,
                                     histnorm='probability')]
        mc_hist = plotly.offline.plot({"data": data_mc_hist, "layout": layout_mc}, include_plotlyjs=False,
                                      output_type='div')
        return mc_hist


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

Come sempre abbiamo aggiornato anche il file template.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">
        <div class="row">
            <h1>Trading Backtest Report</h1>
        </div>
        <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>
        <div class="row">
            <div class="col-sm-12">
                                  {{ monthly_table|safe }}
            </div>
        </div>
        <hr>
        <div class="row">
            <div class="col-sm-12">{{ drawdown_chart|safe }}</div>
        </div>
        <hr>
        <div class="row"><h5>KPI Tables</h5></div>
        <div class="row">

            <div class="col-sm-6">
                                  {{ kpi_table_1|safe }}<br>
                                  {{ kpi_table_5|safe }}
            </div>
            <div class="col-sm-6">{{ kpi_table_2|safe }}<br>
                                  {{ kpi_table_3|safe }}<br>
                                  {{ kpi_table_4|safe }}<br>
            </div>
        </div>
        <hr>
        <div class="row">
            <div class="col-sm-12">{{ daily_ret_hist|safe }}</div>
        </div>
        <hr>
        <div class="row">
                <div class="col-sm-12">{{ daily_ret_box|safe }}</div>
        </div>
        <hr>
        <div class="row">
                <div class="col-sm-12">{{ mc_chart|safe }}</div>
        </div>
        <hr>
        <div class="row">
                <div class="col-sm-6">{{ mc_dist_chart|safe }}</div>
                <div class="col-sm-6">{{ mc_hist_chart|safe }}</div>
        </div>
        <hr>
        <div class="row">
            <div class="col-sm-6"><h5>Best MC Run KPI Table</h5>{{ best_df_kpi|safe }}</div>
            <div class="col-sm-6"><h5>Worst MC Run KPI Table</h5>{{ worst_df_kpi|safe }}</div>
        </div>
        <hr>
        <div class="row">
            <div class="col-sm-6">
                <b><center>Monte Carlo (Sampled) Confidence Levels</center></b>
                <table data-toggle="table" class="table table-hover table-bordered table-striped">
                    <colgroup>
                        <col style="width:33%">
                        <col style="width:33%">
                        <col style="width:33%">
                    </colgroup>
                    <thead>
                        <tr>
                            <th>KPI</th>
                            <th data-cell-style="cellStyle">Annual Return(%)</th>
                            <th data-cell-style="cellStyle">Maximum  Drawdown (%)</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td><b>1%<b></b></b></td>
                            <td>{{mc_dict_perf.mc_99perc}}</td>
                            <td>{{mc_dict_dd.mc_99perc_dd}}</td>
                        </tr>
                        <tr>
                            <td><b>5%<b></b></b></td>
                            <td>{{mc_dict_perf.mc_95perc}}</td>
                            <td>{{mc_dict_dd.mc_95perc_dd}}</td>
                        </tr>
                        <tr>
                            <td><b>10%<b></b></b></td>
                            <td>{{mc_dict_perf.mc_90perc}}</td>
                            <td>{{mc_dict_dd.mc_90perc_dd}}</td>
                        </tr>
                        <tr>
                            <td><b>20%<b></b></b></td>
                            <td>{{mc_dict_perf.mc_80perc}}</td>
                            <td>{{mc_dict_dd.mc_80perc_dd}}</td>
                        </tr>
                        <tr>
                            <td><b>30%<b></b></b></td>
                            <td>{{mc_dict_perf.mc_70perc}}</td>
                            <td>{{mc_dict_dd.mc_70perc_dd}}</td>
                        </tr>
                        <tr>
                            <td><b>40%</b></td>
                            <td>{{mc_dict_perf.mc_60perc}}</td>
                            <td>{{mc_dict_dd.mc_60perc_dd}}</td>
                        </tr>
                        <tr>
                            <td><b>50%</b></td>
                            <td>{{mc_dict_perf.mc_50perc}}</td>
                            <td>{{mc_dict_dd.mc_50perc_dd}}</td>
                        </tr>
                        <tr>
                            <td><b>60%</b></td>
                            <td>{{mc_dict_perf.mc_40perc}}</td>
                            <td>{{mc_dict_dd.mc_40perc_dd}}</td>
                        </tr>
                        <tr>
                            <td><b>70%</b></td>
                            <td>{{mc_dict_perf.mc_30perc}}</td>
                            <td>{{mc_dict_dd.mc_30perc_dd}}</td>
                        </tr>
                        <tr>
                            <td><b>80%</b></td>
                            <td>{{mc_dict_perf.mc_20perc}}</td>
                            <td>{{mc_dict_dd.mc_20perc_dd}}</td>
                        </tr>
                        <tr>
                            <td><b>90%</b></td>
                            <td>{{mc_dict_perf.mc_10perc}}</td>
                            <td>{{mc_dict_dd.mc_10perc_dd}}</td>
                        </tr>
                        <tr>
                            <td><b>95%</b></td>
                            <td>{{mc_dict_perf.mc_5perc}}</td>
                            <td>{{mc_dict_dd.mc_5perc_dd}}</td>
                        </tr>
                        <tr>
                            <td><b>99%</b></td>
                            <td>{{mc_dict_perf.mc_1perc}}</td>
                            <td>{{mc_dict_dd.mc_1perc_dd}}</td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </div>
    </div>
</body>
</html>

				
			

Il report ottenuto presenta le seguenti tre tabelle aggiuntive.

Report-Performance-Grafico-Montecarlo-Quantili

Con questo articolo abbiamo descritto la maggior parte delle informazioni necessarie in un report e come possiamo creare e aggiungere altri grafici e tabelle statistiche.

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