Skip to content
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

Closed
wants to merge 11 commits into from
36 changes: 31 additions & 5 deletions pyfolio/plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,10 +611,18 @@ 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)
# check to see if we're just rendering a single cone, or multiple
# cones at various stddevs, defined as elements of cone_std
if type(cone_std) is list:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of having separate logic for list and float case. I would convert every float to a list, then have one loop.

cone_df = timeseries.cone_rolling(
returns,
num_stdev=cone_std[0],
cone_fit_end_date=live_start_date)
else:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can just replace cone_std with the list and remove the else statement that way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for pointing that out (I actually didn't realize i could re-use a parameter variable like that. Is this only possible because the parameter does not have risk of actually just being a pointer to an upstream variable? I'm always confused as to when Python treats an input parameter as merely a pointer to the upstream variable)

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]

Expand All @@ -641,7 +649,25 @@ def plot_rolling_returns(
ax.fill_between(cone_df_live.index,
cone_df_live.sd_down,
cone_df_live.sd_up,
color='red', alpha=0.30)
color='steelblue', alpha=0.25)

if type(cone_std) is list:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we generate the cones in one loop?

for cone_i in list(range(1, len(cone_std))):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for cone_std_i in cone_std:

cone_df = timeseries.cone_rolling(
returns,
num_stdev=cone_std[cone_i],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

num_stdev=cone_std_i,

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]
temp_keep_days = cone_df_live.index < returns.index[-1]
cone_df_live = cone_df_live[temp_keep_days]

ax.fill_between(cone_df_live.index,
cone_df_live.sd_down,
cone_df_live.sd_up,
color='steelblue', alpha=0.25)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would take this whole logic and put it into a local function (def draw_cone()) that gets called inside the loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I basically did the quickest implementation to touch the least amount of code so yea it can definitely be cleaned up further. I figured it's not that computationally expensive so went to simple rather than elegant route

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's worth our time to go the elegant route. Technical debt has a very high interest rate.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok that makes sense - Yeah I can do this tonight most likely.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I committed the update that factors out the cone rendering into a local draw_cone. definitely cleaner! by cleaning it up it also let me easily turn off the cone in the volatility match case -- was only a 3 "word" change so I snuck it in here instead of its own PR--yeah I know not great practice ;)

Sent from iPhone

On Tue, Oct 13, 2015 at 1:58 PM -0700, "Thomas Wiecki" notifications@github.com wrote:

In pyfolio/plotting.py:

  •            for cone_i in list(range(1, len(cone_std))):
    
  •                cone_df = timeseries.cone_rolling(
    
  •                    returns,
    
  •                    num_stdev=cone_std[cone_i],
    
  •                    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]
    
  •                temp_keep_days = cone_df_live.index < returns.index[-1]
    
  •                cone_df_live = cone_df_live[temp_keep_days]
    
  •                ax.fill_between(cone_df_live.index,
    
  •                                cone_df_live.sd_down,
    
  •                                cone_df_live.sd_up,
    
  •                                color='steelblue', alpha=0.25)
    

I think it's worth our time to go the elegant route. Technical debt has a very high interest rate.


Reply to this email directly or view it on GitHub.


ax.axhline(1.0, linestyle='--', color='black', lw=2)
ax.set_ylabel('Cumulative returns')
Expand Down
4 changes: 2 additions & 2 deletions pyfolio/tears.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Copy link
Contributor

Choose a reason for hiding this comment

The 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.
Expand Down Expand Up @@ -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):
"""
Expand Down