import pandas as pd
from moonshot import Moonshot
from moonshot.slippage.borrowfee import IBKRBorrowFees
from quantrocket.fundamental import get_ibkr_borrow_fees_reindexed_like
from quantrocket.master import get_securities_reindexed_like
class ShortHighBorrow(Moonshot):
"""
Strategy that shorts stocks with the highest borrow fees.
"""
CODE = "short-high-borrow"
DB = "usstock-1d-bundle"
DB_FIELDS = ["Open", "Close", "Volume"]
BORROW_FEE_TOP_N_PCT = 10
DOLLAR_VOLUME_TOP_N_PCT = 75
SLIPPAGE_CLASSES = IBKRBorrowFees
def prices_to_signals(self, prices: pd.DataFrame):
closes = prices.loc["Close"]
volumes = prices.loc["Volume"]
sec_types = get_securities_reindexed_like(closes, "usstock_SecurityType2").loc["usstock_SecurityType2"]
are_common_stocks = sec_types == "Common Stock"
avg_dollar_volumes = (closes * volumes).rolling(30).mean()
dollar_volume_pct_ranks = avg_dollar_volumes.where(are_common_stocks).rank(axis=1, ascending=False, pct=True)
have_adequate_dollar_volumes = dollar_volume_pct_ranks <= (self.DOLLAR_VOLUME_TOP_N_PCT / 100)
borrow_fees = get_ibkr_borrow_fees_reindexed_like(closes)
borrow_fee_ranks = borrow_fees.where(have_adequate_dollar_volumes).rank(axis=1, ascending=False, pct=True)
short_signals = borrow_fee_ranks <= (self.BORROW_FEE_TOP_N_PCT / 100)
return -short_signals.astype(int)
def signals_to_target_weights(self, signals: pd.DataFrame, prices: pd.DataFrame):
weights = self.allocate_equal_weights(signals)
return weights
def target_weights_to_positions(self, weights: pd.DataFrame, prices: pd.DataFrame):
return weights.shift()
def positions_to_gross_returns(self, positions: pd.DataFrame, prices: pd.DataFrame):
opens = prices.loc["Open"]
gross_returns = opens.pct_change() * positions.shift()
return gross_returns