Quant BuffetRelax, Not Over Thinking

Synthetic Lending Rates Predict Subsequent Market Return

Log in to collect

Academic paper

Synthetic Lending Rates Predict Subsequent Market Return

AuthorsMatus Padysak

Institute
  • SKComenius University Bratislava
  • ?Comenius University - Faculty of Mathematics, Physics and Informatics
  • ?Quantpedia.com

Strategy in a nutshell

Universe: SPY ETF. Use CBOE borrow intensity data (45-day maturity) to measure synthetic shorting costs. Daily, calculate the change in aggregate borrow intensity:

Positive change → buy SPY at 15:59

Negative change → short SPY at 15:59

Positions are closed the next day at 15:58.

Economic rationale

Changes in borrow intensity reflect shorting demand and market sentiment:

Increase in intensity → lower shorting fees → positive subsequent returns

Decrease in intensity → higher shorting fees → negative subsequent returns

The signal is short-lived, with partial reversal over two days. Correlations with VIX suggest borrow intensity also proxies fear/sentiment, and intraday negative correlation indicates a short-term reversal effect linked to liquidity provision.

Backtest performance

Annualised return15.47%
Volatility19.52%
Beta0.018
Sharpe ratio0.79
Maximum drawdown-28.51%
Win rate50%

Full Python code

from AlgorithmImports import *
#endregion

class SyntheticLendingRatesPredictSubsequentMarketReturn(QCAlgorithm):

def Initialize(self):
self.SetStartDate(2016, 1, 1)
self.SetCash(100000)

self.spy_symbol:Symbol = self.AddEquity('SPY', Resolution.Minute).Symbol  

self.lending_data_symbol:Symbol = self.AddData(
    QuantpediaLendingRates,
    'lending_rate', 
    Resolution.Minute).Symbol

self.last_lending_mean = None          

def OnData(self, data: Slice):
curr_time:datetime.datetime = self.Time

# liquidate at 15:58
if curr_time.hour == 15 and curr_time.minute == 58:
    self.Liquidate(self.spy_symbol) 

# lending rate data came in
if self.lending_data_symbol in data and data[self.lending_data_symbol]:
    curr_lending_mean:float = data[self.lending_data_symbol].Value

    if self.last_lending_mean:
        # calculate daily change in lending rate
        diff:float = curr_lending_mean - self.last_lending_mean

        if diff > 0:
            self.SetHoldings(self.spy_symbol, 1)
        else:
            self.SetHoldings(self.spy_symbol, -1)

    self.last_lending_mean = curr_lending_mean    

# Quantpedia data.
# NOTE: IMPORTANT: Data order must be ascending (datewise)
class QuantpediaLendingRates(PythonData):
def GetSource(self, config, date, isLiveMode):
return SubscriptionDataSource("data.quantpedia.com/backtesting_data/options/lending_rates_day_close_matur_45_days.csv".format(config.Symbol.Value), SubscriptionTransportMedium.RemoteFile, FileFormat.Csv)

def Reader(self, config, line, date, isLiveMode):
data:QuantpediaLendingRates = QuantpediaLendingRates()
data.Symbol = config.Symbol

if not line[0].isdigit(): return None

split:list = line.split(';')

datetime_str:str = split[0] + ', 15:59'

data.Time = datetime.strptime(datetime_str, "%Y-%m-%d, %H:%M")
valid_values:list = list(filter(lambda value: value != '', split[1:]))
valid_values:list = list(map(lambda str_value: float(str_value), valid_values))
data.Value = np.mean(valid_values)

return data