1818
1919class TearsheetStatistics (AbstractStatistics ):
2020 """
21+ Displays a Matplotlib-generated 'one-pager' as often
22+ found in institutional strategy performance reports.
23+
24+ Includes an equity curve, drawdown curve, monthly
25+ returns heatmap, yearly returns summary, strategy-
26+ level statistics and trade-level statistics.
27+
28+ Also includes an optional annualised rolling Sharpe
29+ ratio chart.
2130 """
2231 def __init__ (
2332 self , config , portfolio_handler ,
@@ -88,7 +97,10 @@ def get_results(self):
8897 statistics ["returns" ] = returns_s
8998 statistics ["rolling_sharpe" ] = rolling_sharpe_s
9099 statistics ["cum_returns" ] = cum_returns_s
91- statistics ["positions" ] = self ._get_positions ()
100+
101+ positions = self ._get_positions ()
102+ if positions is not None :
103+ statistics ["positions" ] = positions
92104
93105 # Benchmark statistics if benchmark ticker specified
94106 if self .benchmark is not None :
@@ -123,28 +135,28 @@ def x(p):
123135 a = []
124136 for p in pos :
125137 a .append (p .__dict__ )
126-
127- df = pd . DataFrame ( a )
128-
129- df [ 'avg_bot' ] = df [ 'avg_bot' ]. apply ( x )
130- df [ 'avg_price' ] = df [ 'avg_price' ]. apply ( x )
131- df ['avg_sld ' ] = df ['avg_sld ' ].apply (x )
132- df ['cost_basis ' ] = df ['cost_basis ' ].apply (x )
133- df ['init_commission ' ] = df ['init_commission ' ].apply (x )
134- df ['init_price ' ] = df ['init_price ' ].apply (x )
135- df ['market_value ' ] = df ['market_value ' ].apply (x )
136- df ['net ' ] = df ['net ' ].apply (x )
137- df ['net_incl_comm ' ] = df ['net_incl_comm ' ].apply (x )
138- df ['net_total ' ] = df ['net_total ' ].apply (x )
139- df ['realised_pnl ' ] = df ['realised_pnl ' ].apply (x )
140- df ['total_bot ' ] = df ['total_bot ' ].apply (x )
141- df ['total_commission ' ] = df ['total_commission ' ].apply (x )
142- df ['total_sld ' ] = df ['total_sld ' ].apply (x )
143- df ['unrealised_pnl ' ] = df ['unrealised_pnl ' ].apply (x )
144-
145- df [ 'trade_pct' ] = ( df ['avg_sld ' ] / df ['avg_bot' ] - 1.0 )
146-
147- return df
138+ if len ( a ) == 0 :
139+ # There are no closed positions
140+ return None
141+ else :
142+ df = pd . DataFrame ( a )
143+ df ['avg_bot ' ] = df ['avg_bot ' ].apply (x )
144+ df ['avg_price ' ] = df ['avg_price ' ].apply (x )
145+ df ['avg_sld ' ] = df ['avg_sld ' ].apply (x )
146+ df ['cost_basis ' ] = df ['cost_basis ' ].apply (x )
147+ df ['init_commission ' ] = df ['init_commission ' ].apply (x )
148+ df ['init_price ' ] = df ['init_price ' ].apply (x )
149+ df ['market_value ' ] = df ['market_value ' ].apply (x )
150+ df ['net ' ] = df ['net ' ].apply (x )
151+ df ['net_incl_comm ' ] = df ['net_incl_comm ' ].apply (x )
152+ df ['net_total ' ] = df ['net_total ' ].apply (x )
153+ df ['realised_pnl ' ] = df ['realised_pnl ' ].apply (x )
154+ df ['total_bot ' ] = df ['total_bot ' ].apply (x )
155+ df ['total_commission ' ] = df ['total_commission ' ].apply (x )
156+ df [ 'total_sld' ] = df [ 'total_sld' ]. apply ( x )
157+ df ['unrealised_pnl ' ] = df ['unrealised_pnl' ]. apply ( x )
158+ df [ 'trade_pct' ] = ( df [ 'avg_sld' ] / df [ 'avg_bot' ] - 1.0 )
159+ return df
148160
149161 def _plot_equity (self , stats , ax = None , ** kwargs ):
150162 """
@@ -323,7 +335,14 @@ def format_perc(x, pos):
323335
324336 returns = stats ["returns" ]
325337 cum_returns = stats ['cum_returns' ]
326- positions = stats ['positions' ]
338+
339+ if not 'positions' in stats :
340+ trd_yr = 0
341+ else :
342+ positions = stats ['positions' ]
343+ trd_yr = positions .shape [0 ] / (
344+ (returns .index [- 1 ] - returns .index [0 ]).days / 365.0
345+ )
327346
328347 if ax is None :
329348 ax = plt .gca ()
@@ -337,7 +356,6 @@ def format_perc(x, pos):
337356 sortino = perf .create_sortino_ratio (returns , self .periods )
338357 rsq = perf .rsquared (range (cum_returns .shape [0 ]), cum_returns )
339358 dd , dd_max , dd_dur = perf .create_drawdowns (cum_returns )
340- trd_yr = positions .shape [0 ] / ((returns .index [- 1 ] - returns .index [0 ]).days / 365.0 )
341359
342360 ax .text (0.25 , 8.9 , 'Total Return' , fontsize = 8 )
343361 ax .text (7.50 , 8.9 , '{:.0%}' .format (tot_ret ), fontweight = 'bold' , horizontalalignment = 'right' , fontsize = 8 )
@@ -411,19 +429,29 @@ def format_perc(x, pos):
411429 if ax is None :
412430 ax = plt .gca ()
413431
414- pos = stats ['positions' ]
432+ if not 'positions' in stats :
433+ num_trades = 0
434+ win_pct = "N/A"
435+ win_pct_str = "N/A"
436+ avg_trd_pct = "N/A"
437+ avg_win_pct = "N/A"
438+ avg_loss_pct = "N/A"
439+ max_win_pct = "N/A"
440+ max_loss_pct = "N/A"
441+ else :
442+ pos = stats ['positions' ]
443+ num_trades = pos .shape [0 ]
444+ win_pct = pos [pos ["trade_pct" ] > 0 ].shape [0 ] / float (num_trades )
445+ win_pct_str = '{:.0%}' .format (win_pct )
446+ avg_trd_pct = '{:.2%}' .format (np .mean (pos ["trade_pct" ]))
447+ avg_win_pct = '{:.2%}' .format (np .mean (pos [pos ["trade_pct" ] > 0 ]["trade_pct" ]))
448+ avg_loss_pct = '{:.2%}' .format (np .mean (pos [pos ["trade_pct" ] <= 0 ]["trade_pct" ]))
449+ max_win_pct = '{:.2%}' .format (np .max (pos ["trade_pct" ]))
450+ max_loss_pct = '{:.2%}' .format (np .min (pos ["trade_pct" ]))
415451
416452 y_axis_formatter = FuncFormatter (format_perc )
417453 ax .yaxis .set_major_formatter (FuncFormatter (y_axis_formatter ))
418454
419- num_trades = pos .shape [0 ]
420- win_pct = pos [pos ["trade_pct" ] > 0 ].shape [0 ] / float (num_trades )
421- win_pct_str = '{:.0%}' .format (win_pct )
422- avg_trd_pct = '{:.2%}' .format (np .mean (pos ["trade_pct" ]))
423- avg_win_pct = '{:.2%}' .format (np .mean (pos [pos ["trade_pct" ] > 0 ]["trade_pct" ]))
424- avg_loss_pct = '{:.2%}' .format (np .mean (pos [pos ["trade_pct" ] <= 0 ]["trade_pct" ]))
425- max_win_pct = '{:.2%}' .format (np .max (pos ["trade_pct" ]))
426- max_loss_pct = '{:.2%}' .format (np .min (pos ["trade_pct" ]))
427455 # TODO: Position class needs entry date
428456 max_loss_dt = 'TBD' # pos[pos["trade_pct"] == np.min(pos["trade_pct"])].entry_date.values[0]
429457 avg_dit = '0.0' # = '{:.2f}'.format(np.mean(pos.time_in_pos))
0 commit comments