Source code for fynance.backtest.plot_tools

#!/usr/bin/env python3
# coding: utf-8

""" Functions to plot backtest. """

# Built-in packages

# External packages
import numpy as np
import seaborn as sns
from matplotlib import pyplot as plt

# Internal packages
from fynance.backtest.print_stats import set_text_stats
from fynance.features.metrics import drawdown, roll_sharpe
from fynance.features.money_management import iso_vol

# Set plot style
plt.style.use('seaborn-v0_8')

__all__ = ['display_perf']

# =========================================================================== #
#                              Printer Tools                                  #
# =========================================================================== #


def compute_perf(logret, signal, fee):
    fees = np.zeros(logret.shape)
    fees[1:] = (signal[1:] - signal[:-1]) * fee
    pctret = np.exp(logret) - 1 - fees

    return np.cumprod(pctret * signal + 1)


[docs] def display_perf( y_idx, y_est, period=252, title='', params_iv={}, plot_drawdown=True, plot_roll_sharpe=True, x_axis=None, underlying='Underlying', win=252, fees=0, ): """ Plot performance and print KPI of backtest. Print dynamic plot of performance indicators (perf, rolling sharpe and draw down) of a strategy (raw and iso-volatility) versus its underlying. Parameters ---------- y_idx : np.ndarray[np.float64, ndim=1] Time series of log-returns of the underlying. y_est : np.ndarray[np.float64, ndim=1] Time series of the signal's strategy. period : int, optional Number of period per year. Default is 252. title : str or list of str, optional Title of performance strategy, default is empty. plot_drawdown : bool, optional If true plot drawdowns, default is True. plot_roll_sharpe : bool, optional If true plot rolling sharpe ratios, default is True. x_axis : list or np.asarray, optional x-axis to plot (e.g. list of dates). underlying : str, optional Name of the underlying, default is 'Underlying'. win : int, optional Size of the window of rolling sharpe, default is 252. fees : float, optional Fees to apply at the strategy performance. Returns ------- perf_idx : np.ndarray[np.float64, ndim=1] Time series of underlying performance. perf_est : np.ndarray[np.float64, ndim=1] Time series of raw strategy performance. perf_ivo : np.ndarray[np.float64, ndim=1] Time series of iso-vol strategy performance. See Also -------- PlotBackTest, set_text_stats """ if x_axis is None: x_axis = range(y_idx.size) # Compute perf. perf_idx = np.exp(np.cumsum(y_idx)) perf_est = compute_perf(y_idx, y_est, fees) iv = iso_vol(np.exp(np.cumsum(y_idx)), **params_iv) perf_ivo = compute_perf(y_idx, y_est * iv, fees) # Print stats. table txt = set_text_stats( y_idx, period=period, Strategy=y_est, Strat_IsoVol=y_est * iv, underlying=underlying, fees=fees ) print(txt) # Plot results n = 1 + plot_roll_sharpe + plot_drawdown f, ax = plt.subplots(n, 1, figsize=(9, 6), sharex=True) if n == 1: ax_perf, ax_dd, ax_dd = ax, None, None ax_perf.set_xlabel('Date') elif n == 2: ax_perf = ax[0] (ax_dd, ax_roll) = (ax[1], None) if plot_drawdown else (None, ax[1]) ax[-1].set_xlabel('Date') else: ax_perf, ax_dd, ax_roll = ax[0], ax[1], ax[2] # Plot performances ax_perf.plot( x_axis, 100 * perf_est, color=sns.xkcd_rgb["pale red"], LineWidth=2. ) ax_perf.plot( x_axis, 100 * perf_ivo, color=sns.xkcd_rgb["medium green"], LineWidth=1.8 ) ax_perf.plot( x_axis, 100 * perf_idx, color=sns.xkcd_rgb["denim blue"], LineWidth=1.5 ) # Set notify motion function def motion(event): N = len(ax_perf.lines[0].get_ydata()) w, h = f.get_size_inches() * f.dpi - 200 x = max(event.x - 100, 0) j = int(x / w * N) ax_perf.legend([ 'Strategy: {:.0f} %'.format(ax_perf.lines[0].get_ydata()[j] - 100), 'Strat Iso-Vol: {:.0f} %'.format(ax_perf.lines[1].get_ydata()[j] - 100), '{}: {:.0f} %'.format(underlying, ax_perf.lines[2].get_ydata()[j] - 100), ], loc='upper left', frameon=True, fontsize=10) if plot_drawdown: ax_dd.legend([ 'Strategy: {:.2f} %'.format(ax_dd.lines[0].get_ydata()[j]), 'Strat Iso-Vol: {:.2f} %'.format(ax_dd.lines[1].get_ydata()[j]), '{}: {:.2f} %'.format(underlying, ax_dd.lines[2].get_ydata()[j]), ], loc='upper left', frameon=True, fontsize=10) if plot_roll_sharpe: ax_roll.legend([ 'Strategy: {:.2f}'.format(ax_roll.lines[0].get_ydata()[j]), 'Strat Iso-Vol: {:.2f}'.format(ax_roll.lines[1].get_ydata()[j]), '{}: {:.2f}'.format(underlying, ax_roll.lines[2].get_ydata()[j]), ], loc='upper left', frameon=True, fontsize=10) ax_perf.legend( ['Strategy', 'Strat Iso-Vol', underlying], loc='upper left', frameon=True, fontsize=10 ) ax_perf.set_ylabel('Perf.') ax_perf.set_yscale('log') ax_perf.set_title(title) ax_perf.tick_params(axis='x', rotation=30, labelsize=10) # Plot DrawDowns if plot_drawdown: ax_dd.plot( x_axis, 100 * drawdown(perf_est), color=sns.xkcd_rgb["pale red"], LineWidth=1.4 ) ax_dd.plot( x_axis, 100 * drawdown(perf_ivo), color=sns.xkcd_rgb["medium green"], LineWidth=1.2 ) ax_dd.plot( x_axis, 100 * drawdown(perf_idx), color=sns.xkcd_rgb["denim blue"], LineWidth=1. ) ax_dd.set_ylabel('% DrawDown') ax_dd.set_title('DrawDown in percentage') ax_dd.tick_params(axis='x', rotation=30, labelsize=10) # Plot rolling Sharpe ratio if plot_roll_sharpe: ax_roll.plot( x_axis, roll_sharpe(perf_est, period=period, w=win), color=sns.xkcd_rgb["pale red"], LineWidth=1.4 ) ax_roll.plot( x_axis, roll_sharpe(perf_ivo, period=period, w=win), color=sns.xkcd_rgb["medium green"], LineWidth=1.2 ) ax_roll.plot( x_axis, roll_sharpe(perf_idx, period=period, w=win), color=sns.xkcd_rgb["denim blue"], LineWidth=1. ) ax_roll.set_ylabel('Sharpe ratio') ax_roll.set_yscale('log') ax_roll.set_xlabel('Date') ax_roll.set_title('Rolling Sharpe ratio') ax_roll.tick_params(axis='x', rotation=30, labelsize=10) f.canvas.mpl_connect('motion_notify_event', motion) plt.show() return perf_idx, perf_est, perf_ivo