This notebook introduces the Moonshot calendar spread trading strategy code and runs an example backtest.
The calendar spread strategy code is provided in calspread.py.
In prices_to_signals
, we use the function get_contract_nums_reindexed_like
to isolate the bids and asks of the contract months from which we wish to create the calendar spread:
CONTRACT_NUMS = (1,2)
...
# Get a DataFrame of contract numbers and a Boolean mask of the
# contract nums constituting the spread
contract_nums = get_contract_nums_reindexed_like(bids, limit=max(self.CONTRACT_NUMS))
are_month_a_contracts = contract_nums == self.CONTRACT_NUMS[0]
are_month_b_contracts = contract_nums == self.CONTRACT_NUMS[1]
# Get a Series of bids and asks for the respective contract months by
# masking with contract num and taking the mean of each row (relying on
# the fact that the mask leaves only one observation per row)
month_a_bids = bids.where(are_month_a_contracts).mean(axis=1)
month_a_asks = asks.where(are_month_a_contracts).mean(axis=1)
month_b_bids = bids.where(are_month_b_contracts).mean(axis=1)
month_b_asks = asks.where(are_month_b_contracts).mean(axis=1)
To reflect the fact that we must buy at the ask and sell at the bid, we compute the spread differently for the purpose of identifying long vs short opportunities:
# Buying the spread means buying the month A contract at the ask and
# selling the month B contract at the bid
spreads_for_buys = month_a_asks - month_b_bids
...
# Selling the spread means selling the month A contract at the bid
# and buying the month B contract at the ask
spreads_for_sells = month_a_bids - month_b_asks
In positions_to_gross_returns
, we model buying at the ask and selling at the bid:
are_buys = positions.diff() > 0
are_sells = positions.diff() < 0
midpoints = (bids + asks) / 2
trade_prices = asks.where(are_buys).fillna(
bids.where(are_sells)).fillna(midpoints)
gross_returns = trade_prices.pct_change() * positions.shift()
Install the strategy by moving it to the /codeload/moonshot
directory:
# make directory if doesn't exist
!mkdir -p /codeload/moonshot
!mv calspread.py /codeload/moonshot/
The moonshot file contains an example subclass for backtesting the strategy with CL contract months 1 and 2:
class CLCalendarSpreadStrategy(CalendarSpreadStrategy):
CODE = "calspread-cl"
UNIVERSES = "cl-fut"
DB = "cl-1min-bbo"
CONTRACT_NUMS = (1, 2)
BBAND_LOOKBACK_WINDOW = 60
BBAND_STD = 2
COMMISSION_CLASS = NymexCommission
There is also a "frictionless" version that ignores commissions and assumes fills at the bid-ask midpoint.
class FrictionlessCLCalendarSpreadStrategy(CLCalendarSpreadStrategy):
CODE = "calspread-cl-frictionless"
COMMISSION_CLASS = None
FILL_AT_MIDPOINT = True
Now run the backtest (using the version with transaction costs):
We run the backtest in monthly segments (
segment="M"
), which mirrors the sharding logic of our database.
from quantrocket.moonshot import backtest
backtest("calspread-cl",
start_date="2019-01-01",
end_date="2019-08-31",
segment="M",
filepath_or_buffer="calspread_cl_results.csv")
And view the tear sheet:
from moonchart import Tearsheet
Tearsheet.from_moonshot_csv("calspread_cl_results.csv")