Moonshot Strategy Code

The strategy code for the dead-cat-drop strategy is provided in dead-cat-drop.py.

Strategy Highlights

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.

Exchange-specific subclasses

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 code
  • DB: 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:

In [1]:
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
    ))
# Canada
class DeadCatDropCanada(DeadCatDrop):

    CODE = "dead-cat-drop-canada"
    DB = "edi-canada-1d"
    TIMEZONE = "America/Toronto"
    MIN_DOLLAR_VOLUME = 1000000.0
    LIMIT_TO_CURRENCY = "CAD"

# Eurozone
class DeadCatDropEurozone(DeadCatDrop):

    CODE = "dead-cat-drop-eurozone"
    DB = ['edi-belgium-1d', 'edi-france-1d', 'edi-germany-1d', 'edi-netherlands-1d']
    TIMEZONE = "Europe/Paris"
    MIN_DOLLAR_VOLUME = 1000000.0
    LIMIT_TO_CURRENCY = "EUR"

# Hongkong
class DeadCatDropHongkong(DeadCatDrop):

    CODE = "dead-cat-drop-hongkong"
    DB = "edi-hongkong-1d"
    TIMEZONE = "Asia/Hong_Kong"
    MIN_DOLLAR_VOLUME = 8000000.0
    LIMIT_TO_CURRENCY = "HKD"

# Japan
class DeadCatDropJapan(DeadCatDrop):

    CODE = "dead-cat-drop-japan"
    DB = "edi-japan-1d"
    TIMEZONE = "Japan"
    MIN_DOLLAR_VOLUME = 100000000.0
    LIMIT_TO_CURRENCY = "JPY"

# Sweden
class DeadCatDropSweden(DeadCatDrop):

    CODE = "dead-cat-drop-sweden"
    DB = "edi-sweden-1d"
    TIMEZONE = "Europe/Stockholm"
    MIN_DOLLAR_VOLUME = 8000000.0
    LIMIT_TO_CURRENCY = "SEK"

# Switzerland
class DeadCatDropSwitzerland(DeadCatDrop):

    CODE = "dead-cat-drop-switzerland"
    DB = "edi-switzerland-1d"
    TIMEZONE = "Europe/Zurich"
    MIN_DOLLAR_VOLUME = 1000000.0
    LIMIT_TO_CURRENCY = "CHF"

# UK
class DeadCatDropUK(DeadCatDrop):

    CODE = "dead-cat-drop-uk"
    DB = "edi-uk-1d"
    TIMEZONE = "Europe/London"
    MIN_DOLLAR_VOLUME = 100000000.0
    LIMIT_TO_CURRENCY = "GBX"

Install strategy file

To "install" the strategy, execute the following cell to move the strategy file to the /codeload/moonshot directory, where Moonshot looks:

In [2]:
# make directory if doesn't exist
!mkdir -p /codeload/moonshot

!mv dead-cat-drop.py /codeload/moonshot/