@@ -21,7 +21,8 @@ class TearsheetStatistics(AbstractStatistics):
2121 """
2222 def __init__ (
2323 self , config , portfolio_handler ,
24- title = None , benchmark = None , periods = 252
24+ title = None , benchmark = None , periods = 252 ,
25+ rolling_sharpe = False
2526 ):
2627 """
2728 Takes in a portfolio handler.
@@ -32,6 +33,7 @@ def __init__(
3233 self .title = '\n ' .join (title )
3334 self .benchmark = benchmark
3435 self .periods = periods
36+ self .rolling_sharpe = rolling_sharpe
3537 self .equity = {}
3638 self .equity_benchmark = {}
3739 self .log_scale = False
@@ -59,6 +61,12 @@ def get_results(self):
5961 # Returns
6062 returns_s = equity_s .pct_change ().fillna (0.0 )
6163
64+ # Rolling Annualised Sharpe
65+ rolling = returns_s .rolling (window = self .periods )
66+ rolling_sharpe_s = np .sqrt (self .periods ) * (
67+ rolling .mean () / rolling .std ()
68+ )
69+
6270 # Cummulative Returns
6371 cum_returns_s = np .exp (np .log (1 + returns_s ).cumsum ())
6472
@@ -78,13 +86,18 @@ def get_results(self):
7886 statistics ["max_drawdown_duration" ] = dd_dur
7987 statistics ["equity" ] = equity_s
8088 statistics ["returns" ] = returns_s
89+ statistics ["rolling_sharpe" ] = rolling_sharpe_s
8190 statistics ["cum_returns" ] = cum_returns_s
8291 statistics ["positions" ] = self ._get_positions ()
8392
8493 # Benchmark statistics if benchmark ticker specified
8594 if self .benchmark is not None :
8695 equity_b = pd .Series (self .equity_benchmark ).sort_index ()
8796 returns_b = equity_b .pct_change ().fillna (0.0 )
97+ rolling_b = returns_b .rolling (window = self .periods )
98+ rolling_sharpe_b = np .sqrt (self .periods ) * (
99+ rolling_b .mean () / rolling_b .std ()
100+ )
88101 cum_returns_b = np .exp (np .log (1 + returns_b ).cumsum ())
89102 dd_b , max_dd_b , dd_dur_b = perf .create_drawdowns (cum_returns_b )
90103 statistics ["sharpe_b" ] = perf .create_sharpe_ratio (returns_b )
@@ -93,6 +106,7 @@ def get_results(self):
93106 statistics ["max_drawdown_duration_b" ] = dd_dur_b
94107 statistics ["equity_b" ] = equity_b
95108 statistics ["returns_b" ] = returns_b
109+ statistics ["rolling_sharpe_b" ] = rolling_sharpe_b
96110 statistics ["cum_returns_b" ] = cum_returns_b
97111
98112 return statistics
@@ -173,6 +187,44 @@ def format_two_dec(x, pos):
173187
174188 return ax
175189
190+ def _plot_rolling_sharpe (self , stats , ax = None , ** kwargs ):
191+ """
192+ Plots the curve of rolling Sharpe ratio.
193+ """
194+ def format_two_dec (x , pos ):
195+ return '%.2f' % x
196+
197+ sharpe = stats ['rolling_sharpe' ]
198+
199+ if ax is None :
200+ ax = plt .gca ()
201+
202+ y_axis_formatter = FuncFormatter (format_two_dec )
203+ ax .yaxis .set_major_formatter (FuncFormatter (y_axis_formatter ))
204+ ax .xaxis .set_tick_params (reset = True )
205+ ax .yaxis .grid (linestyle = ':' )
206+ ax .xaxis .set_major_locator (mdates .YearLocator (1 ))
207+ ax .xaxis .set_major_formatter (mdates .DateFormatter ('%Y' ))
208+ ax .xaxis .grid (linestyle = ':' )
209+
210+ if self .benchmark is not None :
211+ benchmark = stats ['rolling_sharpe_b' ]
212+ benchmark .plot (
213+ lw = 2 , color = 'gray' , label = self .benchmark , alpha = 0.60 ,
214+ ax = ax , ** kwargs
215+ )
216+
217+ sharpe .plot (lw = 2 , color = 'green' , alpha = 0.6 , x_compat = False ,
218+ label = 'Backtest' , ax = ax , ** kwargs )
219+
220+ ax .axvline (sharpe .index [252 ], linestyle = "dashed" , c = "gray" , lw = 2 )
221+ ax .set_ylabel ('Rolling Annualised Sharpe' )
222+ ax .legend (loc = 'best' )
223+ ax .set_xlabel ('' )
224+ plt .setp (ax .get_xticklabels (), visible = True , rotation = 0 , ha = 'center' )
225+
226+ return ax
227+
176228 def _plot_drawdown (self , stats , ax = None , ** kwargs ):
177229 """
178230 Plots the underwater curve
@@ -523,22 +575,29 @@ def plot_results(self, filename=None):
523575 sns .set_style ("whitegrid" )
524576 sns .set_palette ("deep" , desat = .6 )
525577
526- vertical_sections = 5
578+ if self .rolling_sharpe :
579+ offset_index = 1
580+ else :
581+ offset_index = 0
582+ vertical_sections = 5 + offset_index
527583 fig = plt .figure (figsize = (10 , vertical_sections * 3.5 ))
528584 fig .suptitle (self .title , y = 0.94 , weight = 'bold' )
529585 gs = gridspec .GridSpec (vertical_sections , 3 , wspace = 0.25 , hspace = 0.5 )
530586
531587 stats = self .get_results ()
532-
533588 ax_equity = plt .subplot (gs [:2 , :])
534- ax_drawdown = plt .subplot (gs [2 , :])
535- ax_monthly_returns = plt .subplot (gs [3 , :2 ])
536- ax_yearly_returns = plt .subplot (gs [3 , 2 ])
537- ax_txt_curve = plt .subplot (gs [4 , 0 ])
538- ax_txt_trade = plt .subplot (gs [4 , 1 ])
539- ax_txt_time = plt .subplot (gs [4 , 2 ])
589+ if self .rolling_sharpe :
590+ ax_sharpe = plt .subplot (gs [2 , :])
591+ ax_drawdown = plt .subplot (gs [2 + offset_index , :])
592+ ax_monthly_returns = plt .subplot (gs [3 + offset_index , :2 ])
593+ ax_yearly_returns = plt .subplot (gs [3 + offset_index , 2 ])
594+ ax_txt_curve = plt .subplot (gs [4 + offset_index , 0 ])
595+ ax_txt_trade = plt .subplot (gs [4 + offset_index , 1 ])
596+ ax_txt_time = plt .subplot (gs [4 + offset_index , 2 ])
540597
541598 self ._plot_equity (stats , ax = ax_equity )
599+ if self .rolling_sharpe :
600+ self ._plot_rolling_sharpe (stats , ax = ax_sharpe )
542601 self ._plot_drawdown (stats , ax = ax_drawdown )
543602 self ._plot_monthly_returns (stats , ax = ax_monthly_returns )
544603 self ._plot_yearly_returns (stats , ax = ax_yearly_returns )
0 commit comments