Skip to content

Commit 0040047

Browse files
Justin Lenttwiecki
Justin Lent
authored andcommitted
ENH add ability to plot multiple stdev regions for the OOS cone in plotting.plot_rolling_returns. Update calling functions to use default of plotting these multiple regions: 1.0, 1.5, 2.0 stdevs
1 parent 1da0d9c commit 0040047

File tree

2 files changed

+46
-33
lines changed

2 files changed

+46
-33
lines changed

pyfolio/plotting.py

+37-25
Original file line numberDiff line numberDiff line change
@@ -552,8 +552,9 @@ def plot_rolling_returns(
552552
live_start_date : datetime, optional
553553
The point in time when the strategy began live trading, after
554554
its backtest period.
555-
cone_std : float, optional
556-
The standard deviation to use for the cone plots.
555+
cone_std : float, or tuple, optional
556+
If float, The standard deviation to use for the cone plots.
557+
If tuple, Tuple of standard deviation values to use for the cone plots
557558
- The cone is a normal distribution with this standard deviation
558559
centered around a linear regression.
559560
legend_loc : matplotlib.loc, optional
@@ -573,6 +574,23 @@ def plot_rolling_returns(
573574
The axes that were plotted on.
574575
575576
"""
577+
def draw_cone(returns, num_stdev, live_start_date, ax):
578+
cone_df = timeseries.cone_rolling(
579+
returns,
580+
num_stdev=num_stdev,
581+
cone_fit_end_date=live_start_date)
582+
583+
cone_in_sample = cone_df[cone_df.index < live_start_date]
584+
cone_out_of_sample = cone_df[cone_df.index > live_start_date]
585+
cone_out_of_sample = cone_out_of_sample[
586+
cone_out_of_sample.index < returns.index[-1]]
587+
588+
ax.fill_between(cone_out_of_sample.index,
589+
cone_out_of_sample.sd_down,
590+
cone_out_of_sample.sd_up,
591+
color='steelblue', alpha=0.25)
592+
593+
return cone_in_sample, cone_out_of_sample
576594

577595
if ax is None:
578596
ax = plt.gca()
@@ -582,12 +600,9 @@ def plot_rolling_returns(
582600
'factor_returns.')
583601
elif volatility_match and factor_returns is not None:
584602
bmark_vol = factor_returns.loc[returns.index].std()
585-
df_cum_rets = timeseries.cum_returns(
586-
(returns / returns.std()) * bmark_vol,
587-
1.0
588-
)
589-
else:
590-
df_cum_rets = timeseries.cum_returns(returns, 1.0)
603+
returns = (returns / returns.std()) * bmark_vol
604+
605+
df_cum_rets = timeseries.cum_returns(returns, 1.0)
591606

592607
y_axis_formatter = FuncFormatter(utils.one_dec_places)
593608
ax.yaxis.set_major_formatter(FuncFormatter(y_axis_formatter))
@@ -611,25 +626,27 @@ def plot_rolling_returns(
611626
label='Live', ax=ax, **kwargs)
612627

613628
if cone_std is not None:
614-
cone_df = timeseries.cone_rolling(
615-
returns,
616-
num_stdev=cone_std,
617-
cone_fit_end_date=live_start_date)
618-
619-
cone_df_fit = cone_df[cone_df.index < live_start_date]
620-
621-
cone_df_live = cone_df[cone_df.index > live_start_date]
622-
cone_df_live = cone_df_live[cone_df_live.index < returns.index[-1]]
623-
624-
cone_df_fit['line'].plot(
629+
# check to see if cone_std was passed as a single value and,
630+
# if so, just convert to list automatically
631+
if isinstance(cone_std, float):
632+
cone_std = [cone_std]
633+
634+
for cone_i in cone_std:
635+
cone_in_sample, cone_out_of_sample = draw_cone(
636+
returns,
637+
cone_i,
638+
live_start_date,
639+
ax)
640+
641+
cone_in_sample['line'].plot(
625642
ax=ax,
626643
ls='--',
627644
label='Backtest trend',
628645
lw=2,
629646
color='forestgreen',
630647
alpha=0.7,
631648
**kwargs)
632-
cone_df_live['line'].plot(
649+
cone_out_of_sample['line'].plot(
633650
ax=ax,
634651
ls='--',
635652
label='Predicted trend',
@@ -638,11 +655,6 @@ def plot_rolling_returns(
638655
alpha=0.7,
639656
**kwargs)
640657

641-
ax.fill_between(cone_df_live.index,
642-
cone_df_live.sd_down,
643-
cone_df_live.sd_up,
644-
color='red', alpha=0.30)
645-
646658
ax.axhline(1.0, linestyle='--', color='black', lw=2)
647659
ax.set_ylabel('Cumulative returns')
648660
ax.set_title('Cumulative Returns')

pyfolio/tears.py

+9-8
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def create_full_tear_sheet(returns, positions=None, transactions=None,
4343
gross_lev=None,
4444
live_start_date=None, bayesian=False,
4545
sector_mappings=None,
46-
cone_std=1.0, set_context=True):
46+
cone_std=(1.0, 1.5, 2.0), set_context=True):
4747
"""
4848
Generate a number of tear sheets that are useful
4949
for analyzing a strategy's performance.
@@ -95,8 +95,9 @@ def create_full_tear_sheet(returns, positions=None, transactions=None,
9595
after its backtest period.
9696
bayesian: boolean, optional
9797
If True, causes the generation of a Bayesian tear sheet.
98-
cone_std : float, optional
99-
The standard deviation to use for the cone plots.
98+
cone_std : float, or tuple, optional
99+
If float, The standard deviation to use for the cone plots.
100+
If tuple, Tuple of standard deviation values to use for the cone plots
100101
- The cone is a normal distribution with this standard deviation
101102
centered around a linear regression.
102103
set_context : boolean, optional
@@ -126,7 +127,6 @@ def create_full_tear_sheet(returns, positions=None, transactions=None,
126127
if positions is not None:
127128
create_position_tear_sheet(returns, positions,
128129
gross_lev=gross_lev,
129-
sector_mappings=sector_mappings,
130130
set_context=set_context)
131131

132132
if transactions is not None:
@@ -142,7 +142,7 @@ def create_full_tear_sheet(returns, positions=None, transactions=None,
142142

143143
@plotting_context
144144
def create_returns_tear_sheet(returns, live_start_date=None,
145-
cone_std=1.0,
145+
cone_std=(1.0, 1.5, 2.0),
146146
benchmark_rets=None,
147147
return_fig=False):
148148
"""
@@ -164,8 +164,9 @@ def create_returns_tear_sheet(returns, live_start_date=None,
164164
live_start_date : datetime, optional
165165
The point in time when the strategy began live trading,
166166
after its backtest period.
167-
cone_std : float, optional
168-
The standard deviation to use for the cone plots.
167+
cone_std : float, or tuple, optional
168+
If float, The standard deviation to use for the cone plots.
169+
If tuple, Tuple of standard deviation values to use for the cone plots
169170
- The cone is a normal distribution with this standard deviation
170171
centered around a linear regression.
171172
benchmark_rets : pd.Series, optional
@@ -232,7 +233,7 @@ def create_returns_tear_sheet(returns, live_start_date=None,
232233
returns,
233234
factor_returns=benchmark_rets,
234235
live_start_date=live_start_date,
235-
cone_std=cone_std,
236+
cone_std=None,
236237
volatility_match=True,
237238
ax=ax_rolling_returns_vol_match)
238239
ax_rolling_returns_vol_match.set_title(

0 commit comments

Comments
 (0)