import pandas as pd
from moonshot import Moonshot
from moonshot.commission import PercentageCommission
from quantrocket.fundamental import get_ibkr_shortable_shares_reindexed_like
from quantrocket.master import get_securities_reindexed_like
class DeadCatDrop(Moonshot):
CODE = "dead-cat-drop"
DB = None
DB_FIELDS = ["Open","Close","Volume"]
MIN_DOLLAR_VOLUME = 1000000
MAX_WEIGHT_PER_POSITION = 0.1
LIMIT_TO_CURRENCY = None
COMMISSION_CLASS = None
POSITIONS_CLOSED_DAILY = True
CONSTRAIN_SHORTABLE = False
def prices_to_signals(self, prices: pd.DataFrame):
closes = prices.loc["Close"]
dollar_volumes = prices.loc["Volume"] * closes
avg_dollar_volumes = dollar_volumes.rolling(window=22).mean()
are_eligible = avg_dollar_volumes >= self.MIN_DOLLAR_VOLUME
sectypes = get_securities_reindexed_like(
closes, "edi_SecTypeCode").loc["edi_SecTypeCode"]
are_eligible &= sectypes == "EQS"
if self.LIMIT_TO_CURRENCY:
currencies = get_securities_reindexed_like(
closes, "Currency").loc["Currency"]
are_eligible &= currencies == self.LIMIT_TO_CURRENCY
prior_returns = (closes - closes.shift()) / closes.shift()
big_losers = prior_returns <= -0.10
short_signals = big_losers & are_eligible
return -short_signals.astype(int)
def signals_to_target_weights(self, signals: pd.DataFrame, prices: pd.DataFrame):
weights = self.allocate_fixed_weights_capped(signals, weight=self.MAX_WEIGHT_PER_POSITION)
return weights
def limit_position_sizes(self, prices: pd.DataFrame):
max_shares_for_shorts = None
if self.CONSTRAIN_SHORTABLE:
t = f"09:00:00 {self.TIMEZONE}"
shortable_shares = get_ibkr_shortable_shares_reindexed_like(prices.loc["Close"], t)
max_shares_for_shorts = shortable_shares.shift(-1)
return None, max_shares_for_shorts
def target_weights_to_positions(self, weights: pd.DataFrame, prices: pd.DataFrame):
positions = weights.shift()
return positions
def positions_to_gross_returns(self, positions: pd.DataFrame, prices: pd.DataFrame):
closes = prices.loc["Close"]
opens = prices.loc["Open"]
pct_changes = (closes - opens) / opens.where(opens > 0)
gross_returns = pct_changes * positions
return gross_returns
class DeadCatDropCanada(DeadCatDrop):
CODE = "dead-cat-drop-canada"
DB = "edi-canada-1d"
TIMEZONE = "America/Toronto"
MIN_DOLLAR_VOLUME = 1000000.0
LIMIT_TO_CURRENCY = "CAD"
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"
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"
class JapanStockTieredCommission(PercentageCommission):
BROKER_COMMISSION_RATE = 0.0005
EXCHANGE_FEE_RATE = 0.00002 + 0.000004
MIN_COMMISSION = 80.00
class DeadCatDropJapan(DeadCatDrop):
CODE = "dead-cat-drop-japan"
DB = "edi-japan-1d"
TIMEZONE = "Japan"
MIN_DOLLAR_VOLUME = 100000000.0
LIMIT_TO_CURRENCY = "JPY"
class DeadCatDropSweden(DeadCatDrop):
CODE = "dead-cat-drop-sweden"
DB = "edi-sweden-1d"
TIMEZONE = "Europe/Stockholm"
MIN_DOLLAR_VOLUME = 8000000.0
LIMIT_TO_CURRENCY = "SEK"
class DeadCatDropSwitzerland(DeadCatDrop):
CODE = "dead-cat-drop-switzerland"
DB = "edi-switzerland-1d"
TIMEZONE = "Europe/Zurich"
MIN_DOLLAR_VOLUME = 1000000.0
LIMIT_TO_CURRENCY = "CHF"
class DeadCatDropUK(DeadCatDrop):
CODE = "dead-cat-drop-uk"
DB = "edi-uk-1d"
TIMEZONE = "Europe/London"
MIN_DOLLAR_VOLUME = 100000000.0
LIMIT_TO_CURRENCY = "GBX"