-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ENH Ability to make cones with multiple shades stdev regions #168
Changes from 9 commits
aab448f
b81e207
3cffef4
efcf5ae
8da2192
8300be5
b3c2215
2a0c49c
cf83da2
50540ff
62958f5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -552,8 +552,9 @@ def plot_rolling_returns( | |
live_start_date : datetime, optional | ||
The point in time when the strategy began live trading, after | ||
its backtest period. | ||
cone_std : float, optional | ||
The standard deviation to use for the cone plots. | ||
cone_std : float, or list, optional | ||
If float, The standard deviation to use for the cone plots. | ||
If list, A list of standard deviation values to use for the cone plots | ||
- The cone is a normal distribution with this standard deviation | ||
centered around a linear regression. | ||
legend_loc : matplotlib.loc, optional | ||
|
@@ -573,6 +574,23 @@ def plot_rolling_returns( | |
The axes that were plotted on. | ||
|
||
""" | ||
def draw_cone(returns, num_stdev, live_start_date, ax): | ||
cone_df = timeseries.cone_rolling( | ||
returns, | ||
num_stdev=num_stdev, | ||
cone_fit_end_date=live_start_date) | ||
|
||
cone_in_sample = cone_df[cone_df.index < live_start_date] | ||
cone_out_of_sample = cone_df[cone_df.index > live_start_date] | ||
cone_out_of_sample = cone_out_of_sample[ | ||
cone_out_of_sample.index < returns.index[-1]] | ||
|
||
ax.fill_between(cone_out_of_sample.index, | ||
cone_out_of_sample.sd_down, | ||
cone_out_of_sample.sd_up, | ||
color='steelblue', alpha=0.25) | ||
|
||
return cone_in_sample, cone_out_of_sample | ||
|
||
if ax is None: | ||
ax = plt.gca() | ||
|
@@ -582,6 +600,11 @@ def plot_rolling_returns( | |
'factor_returns.') | ||
elif volatility_match and factor_returns is not None: | ||
bmark_vol = factor_returns.loc[returns.index].std() | ||
# TO-DO: @tweicki 'returns' probably needs to get updated to: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, if volatility_match is True, returns need to be changed/re-assigned as you proposed. That will fix the vol match plot. |
||
# (returns / returns.std()) * bmark_vol if we want to plot | ||
# the cone on this later on. | ||
# Will we need a temp variable to do this? Or can we just re-assign as | ||
# returns = (returns / returns.std()) * bmark_vol | ||
df_cum_rets = timeseries.cum_returns( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can then get rid of this line. |
||
(returns / returns.std()) * bmark_vol, | ||
1.0 | ||
|
@@ -611,25 +634,27 @@ def plot_rolling_returns( | |
label='Live', ax=ax, **kwargs) | ||
|
||
if cone_std is not None: | ||
cone_df = timeseries.cone_rolling( | ||
returns, | ||
num_stdev=cone_std, | ||
cone_fit_end_date=live_start_date) | ||
|
||
cone_df_fit = cone_df[cone_df.index < live_start_date] | ||
|
||
cone_df_live = cone_df[cone_df.index > live_start_date] | ||
cone_df_live = cone_df_live[cone_df_live.index < returns.index[-1]] | ||
|
||
cone_df_fit['line'].plot( | ||
# check to see if cone_std was passed as a single value and, | ||
# if so, just convert to list automatically | ||
if isinstance(cone_std, float): | ||
cone_std = [cone_std] | ||
|
||
for cone_i in cone_std: | ||
cone_in_sample, cone_out_of_sample = draw_cone( | ||
returns, | ||
cone_i, | ||
live_start_date, | ||
ax) | ||
|
||
cone_in_sample['line'].plot( | ||
ax=ax, | ||
ls='--', | ||
label='Backtest trend', | ||
lw=2, | ||
color='forestgreen', | ||
alpha=0.7, | ||
**kwargs) | ||
cone_df_live['line'].plot( | ||
cone_out_of_sample['line'].plot( | ||
ax=ax, | ||
ls='--', | ||
label='Predicted trend', | ||
|
@@ -638,11 +663,6 @@ def plot_rolling_returns( | |
alpha=0.7, | ||
**kwargs) | ||
|
||
ax.fill_between(cone_df_live.index, | ||
cone_df_live.sd_down, | ||
cone_df_live.sd_up, | ||
color='red', alpha=0.30) | ||
|
||
ax.axhline(1.0, linestyle='--', color='black', lw=2) | ||
ax.set_ylabel('Cumulative returns') | ||
ax.set_title('Cumulative Returns') | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,7 +42,7 @@ def create_full_tear_sheet(returns, positions=None, transactions=None, | |
benchmark_rets=None, | ||
gross_lev=None, | ||
live_start_date=None, bayesian=False, | ||
cone_std=1.0, set_context=True): | ||
cone_std=[1.0, 1.5, 2.0], set_context=True): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor stylistic but I'd change these to be tuples. |
||
""" | ||
Generate a number of tear sheets that are useful | ||
for analyzing a strategy's performance. | ||
|
@@ -140,7 +140,7 @@ def create_full_tear_sheet(returns, positions=None, transactions=None, | |
|
||
@plotting_context | ||
def create_returns_tear_sheet(returns, live_start_date=None, | ||
cone_std=1.0, | ||
cone_std=[1.0, 1.5, 2.0], | ||
benchmark_rets=None, | ||
return_fig=False): | ||
""" | ||
|
@@ -230,7 +230,7 @@ def create_returns_tear_sheet(returns, live_start_date=None, | |
returns, | ||
factor_returns=benchmark_rets, | ||
live_start_date=live_start_date, | ||
cone_std=cone_std, | ||
cone_std=None, | ||
volatility_match=True, | ||
ax=ax_rolling_returns_vol_match) | ||
ax_rolling_returns_vol_match.set_title( | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason to prepend the variable with
num_
here?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I wanted to be consistent with the parameter name in timeseries.cone_rolling() which is the only place it gets passed. Is it worth changing for simplicity, or worth keeping because better to be consistent with where it is used downstream? thoughts?