How to create pyfolio round trip tearsheet?

@runout 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.

@runout ok, I will check with ranaroussi then. Thank you.

@runout 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/pandasdocs/stable/whatsnew/v0.20.0.html#whatsnew0200apibreakingdeprecategroupaggdictThis 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.

@runout said in How to create pyfolio round trip tearsheet?:
When you get your dictionary to this stage, you need the datetime key and the second column, which is value, to send to quantstats.
Is there any fundamental reason why you ignore the first column (which is cash, if I understand it correctly)?
I suggest to add the cash value and the positions value in order to obtain the overall portfolio value. 
@andi I just realized that the second column is already the actual portfolio value (i.e cash + positions).