Just wanted to report back that I came up with a somewhat reasonable solution, but hoping there's a better/simpler way; let me know!
See code below in case it helps others.
First, execute a moonshot backtest using Python and store results in a dataframe:
from quantrocket.moonshot import backtest, read_moonshot_csv
backtest_results = 'backtest_results.csv'
backtest("big-gap", nlv={'USD':50000},
start_date="2018-01-03", end_date="2018-01-06",
filepath_or_buffer=backtest_results,
details=True, no_cache=True)
backtest_results = read_moonshot_csv(backtest_results)
Extract transactions
Note that I added prices and quantities to the backtest results dataframe within the strategy code.
prior_signal = backtest_results.loc['Signal'].shift(1)
current_signal = backtest_results.loc['Signal']
entries = (current_signal != 0) & (prior_signal == 0) # going from no signal to signal i.e. an entry
# Exits still need to be implemented below, but can grab them like this
exits = (current_signal == 0) & (prior_signal != 0) # going from signal to no signal i.e. an exit
Aggregate values for each column
backtest_datetime = backtest_results.loc['Quantity'][entries].dropna(how='all', axis='columns').dropna(how='all').unstack().unstack().unstack().unstack().unstack().dropna().index.map(lambda x : pd.to_datetime(' '.join( (str(x[0].date()), str(x[1])) )))
backtest_sid = backtest_results.loc['Quantity'][entries].dropna(how='all', axis='columns').dropna(how='all').unstack().unstack().unstack().unstack().unstack().dropna().index.get_level_values(2)
backtest_quantity = backtest_results.loc['Quantity'][entries].dropna(how='all', axis='columns').dropna(how='all').unstack().unstack().unstack().unstack().unstack().dropna()
backtest_price = backtest_results.loc['Price'][entries].dropna(how='all', axis='columns').dropna(how='all').unstack().unstack().unstack().unstack().unstack().dropna()
backtest_cost_basis = backtest_results.loc['MarketValue'][entries].dropna(how='all', axis='columns').dropna(how='all').unstack().unstack().unstack().unstack().unstack().dropna()
Construct transactions dataframe
transactions = pd.DataFrame(index=backtest_datetime.tz_localize('UTC'))
transactions['amount'] = backtest_quantity.values
transactions['price'] = backtest_price.values
transactions['symbol'] = backtest_sid.values
Lastly, using Pyfolio for generate a round trip tearsheet
from moonchart.utils import intraday_to_daily
from pyfolio.quantrocket_utils import pad_initial
results = backtest_results
if "Time" in results.index.names:
results = intraday_to_daily(results)
# pandas DatetimeIndexes are serialized with UTC offsets, and pandas
# parses them back to UTC but doesn't set the tz; pyfolio needs tz-aware
if not results.index.get_level_values("Date").tz:
results = results.tz_localize("UTC", level="Date")
returns = results.loc["Return"].sum(axis=1)
positions = results.loc["NetExposure"]
positions["cash"] = 1 - positions.sum(axis=1)
returns.name = "returns"
returns = pad_initial(returns)
# Generate tearsheet
warnings.filterwarnings('ignore')
pf.create_round_trip_tear_sheet(returns, positions=positions, transactions=transactions)