The strategy code for the dead-cat-drop strategy is provided in dead-cat-drop.py.
In prices_to_signals
, the strategy first computes a dollar volume filter to screen out illiquid stocks:
closes = prices.loc["Close"]
# Compute dollar volume mask
dollar_volumes = prices.loc["Volume"] * closes
avg_dollar_volumes = dollar_volumes.rolling(window=22).mean()
are_eligible = avg_dollar_volumes >= self.MIN_DOLLAR_VOLUME
We limit the universe to equity shares (EQS), thus excluding ETFs, Depository Receipts, and other security types:
sectypes = get_securities_reindexed_like(
closes, "edi_SecTypeCode").loc["edi_SecTypeCode"]
are_eligible &= sectypes == "EQS"
Finally, we identify the stocks that fell 10%:
# Compute big losers mask
prior_returns = (closes - closes.shift()) / closes.shift()
big_losers = prior_returns <= -0.10
short_signals = big_losers & are_eligible
return -short_signals.astype(int)
We short these stocks on the next day's open and exit on the close.
The recommended Moonshot paradigm when backtesting multiple markets is to implement the strategy logic in a base class, then create subclasses for each exchange/market with the appropriate exchange-specific parameters.
For this strategy, the exchange-specific parameters are:
CODE
: every strategy requires a unique codeDB
: the database(s) to query. Note that we group several Eurozone countries into a single strategy.TIMEZONE
: some exchanges contain listings in multiple timezones; this parameter tells Moonshot what timezone to convert the data to. For valid timezones, query the securities master database or import pytz
and look at pytz.all_timezones
MIN_DOLLAR_VOLUME
: used to filter out illiquid securities. This is expressed in the local currency of the exchange.LIMIT_TO_CURRENCY
: some exchanges contain listings in multiple currencies. To ensure the MIN_DOLLAR_VOLUME
filter works as expected, we limit to the country's primary currency.The following code was used to generate the subclasses which were then pasted into the strategy file. If you are using different exchanges, adjust the code below and paste your exchanges in the strategy file:
exchanges = (
# name DB TIMEZONE MIN_DOLLAR_VOLUME LIMIT_TO_CURRENCY
("Canada", "edi-canada-1d", "America/Toronto", 1e6, "CAD"),
("Eurozone", ["edi-belgium-1d",
"edi-france-1d",
"edi-germany-1d",
"edi-netherlands-1d"], "Europe/Paris", 1e6, "EUR"),
("Hongkong", "edi-hongkong-1d", "Asia/Hong_Kong", 8e6, "HKD"),
("Japan", "edi-japan-1d", "Japan", 1e8, "JPY"),
("Sweden", "edi-sweden-1d", "Europe/Stockholm", 8e6, "SEK"),
("Switzerland", "edi-switzerland-1d", "Europe/Zurich", 1e6, "CHF"),
("UK", "edi-uk-1d", "Europe/London", 1e8, "GBX")
)
for name, db, timezone, min_dollar_volume, limit_to_currency in exchanges:
print('''
# {name}
class DeadCatDrop{name}(DeadCatDrop):
CODE = "dead-cat-drop-{name_lower}"
DB = {db}
TIMEZONE = "{timezone}"
MIN_DOLLAR_VOLUME = {min_dollar_volume}
LIMIT_TO_CURRENCY = "{limit_to_currency}"'''.format(
name=name,
name_lower=name.lower(),
db=db if isinstance(db, list) else f'"{db}"',
timezone=timezone,
min_dollar_volume=min_dollar_volume,
limit_to_currency=limit_to_currency
))
To "install" the strategy, execute the following cell to move the strategy file to the /codeload/moonshot
directory, where Moonshot looks:
# make directory if doesn't exist
!mkdir -p /codeload/moonshot
!mv dead-cat-drop.py /codeload/moonshot/