How to create pyfolio round trip tearsheet?
-
@run-out I realized there was a html copy being saved into local directory. So I guess it's deemed successful? Initially I thought a new browser will automatically pop up to show the results.
-
@balibou That's positive. I use the below code and now I'm able to see the html tearsheet saved in my local drive. But not sure why you have the html error.
qs.reports.html(df_values, "SPY",output="qs.html")
-
You are really into a quantstats problem at this point.
-
@run-out ok, I will check with ranaroussi then. Thank you.
-
@run-out I came out with a workaround, by just opening the file since it's already saved. : )
webbrowser.open('qs.html')
-
-
With the second alternative,
pf.create_round_trip_tear_sheet(returns, positions=positions, transactions=transactions)
, you do get the error that you have mentioned.
You will need to go to the pyfolio files and make certain changes. At this point I am not sure whether I should go to pyfolio github repository and do a pull request because my changes work for what I need in the context of backtrader. I am in no way certain that making these changes will not break something else in pyfolio.
In round_trips.py, I made the following changes
Originalgrouped_price = (t.groupby(('block_dir', 'block_time')).apply(vwap))
Modified to:
grouped_price = (t.groupby(['block_dir', 'block_time']).apply(vwap))
After you solve this error, as the pyfolio code is able to run further you will get a few more errors. As I understood, it is because pandas moved ahead and deprecated certain functionality. Link below:
https://pandas.pydata.org/pandas-docs/stable/whatsnew/v0.20.0.html#whatsnew-0200-api-breaking-deprecate-group-agg-dictThis functionality was used to pass dicts to
agg_all_long_short
Earlier code was:
PNL_STATS = OrderedDict([ ('Total profit', lambda x: x.sum()), ('Gross profit', lambda x: x[x > 0].sum()), ('Gross loss', lambda x: x[x < 0].sum()), ('Profit factor', lambda x: x[x > 0].sum() / x[x < 0].abs().sum() if x[x < 0].abs().sum() != 0 else np.nan), ('Avg. trade net profit', 'mean'), ('Avg. winning trade', lambda x: x[x > 0].mean()), ('Avg. losing trade', lambda x: x[x < 0].mean()), ('Ratio Avg. Win:Avg. Loss', lambda x: x[x > 0].mean() / x[x < 0].abs().mean() if x[x < 0].abs().mean() != 0 else np.nan), ('Largest winning trade', 'max'), ('Largest losing trade', 'min'),])
Change this to:
PNL_STATS = ( (lambda x: x.sum()), ( lambda x: x[x > 0].sum()), (lambda x: x[x < 0].sum()), ( lambda x: x[x > 0].sum() / x[x < 0].abs().sum() if x[x < 0].abs().sum() != 0 else np.nan), ('mean'), (lambda x: x[x > 0].mean()), ( lambda x: x[x < 0].mean()), ( lambda x: x[x > 0].mean() / x[x < 0].abs().mean() if x[x < 0].abs().mean() != 0 else np.nan), ('max'), ('min'), )
Please make similar changes for the other dicts:
Original codeSUMMARY_STATS = OrderedDict([('Total number of round_trips', 'count'), ('Percent profitable', lambda x: len(x[x > 0]) / float(len(x))), ('Winning round_trips', lambda x: len(x[x > 0])), ('Losing round_trips', lambda x: len(x[x < 0])), ('Even round_trips', lambda x: len(x[x == 0])), ])
Modified code
SUMMARY_STATS = ( ('count'), (lambda x: len(x[x > 0]) / float(len(x))), (lambda x: len(x[x > 0])), (lambda x: len(x[x < 0])), (lambda x: len(x[x == 0])), )
And so on for even the other dicts:
RETURN_STATS
DURATION_STATS
Essentially, you now cannot pass rename logic in the dicts themselves. This also means that for you will need to create a list of the column names and pass it yourself.
In
def gen_round_trip_stats(round_trips):
this is the updated code Main changes are that we are creating a list of column names col_list before each function call to pass to the agg_all_long_short function (for reasons explained above - pandas deprecating nested renaming)
col_list = ['Total profit', 'Gross profit', 'Gross loss', 'Profit factor', 'Avg. trade net profit', 'Avg. winning trade', 'Avg. losing trade', 'Ratio Avg. Win:Avg. Loss', 'Largest winning trade', 'Largest losing trade'] stats['pnl'] = agg_all_long_short(round_trips, 'pnl', PNL_STATS, col_list) col_list = ['Total number of round_trips','Percent profitable', 'Winning round_trips', 'Losing round_trips', 'Even round_trips'] stats['summary'] = agg_all_long_short(round_trips, 'pnl', SUMMARY_STATS, col_list) col_list = ['Average duration', 'Median duration','Longest duration', 'Shortest duration'] stats['duration'] = agg_all_long_short(round_trips, 'duration', DURATION_STATS, col_list) col_list = ['Avg returns all round_trips','Avg returns winning','Avg returns losing', 'Median returns all round_trips', 'Median returns winning','Median returns losing', 'Largest winning trade', 'Largest losing trade' ] stats['returns'] = agg_all_long_short(round_trips, 'rt_returns', RETURN_STATS, col_list)
In function definition:
originaldef agg_all_long_short(round_trips, col, stats_dict):
modified (adding col_list argument)
def agg_all_long_short(round_trips, col, stats_dict, col_list):
A few more changes in tears.py
Original code:sns.distplot(trades.rt_returns.dropna() * 100, kde=False, ax=ax_pnl_per_round_trip_pct)
Modified code:
sns.distplot(trades.returns.dropna() * 100, kde=False, ax=ax_pnl_per_round_trip_pct)
With these changes the round_trip functionality should work. If any problems are faced please reply here. It will be helpful for the community.
Thanks.