Quant BuffetRelax, Not Over Thinking

Selling Options on Bond ETFs

Log in to collect

Academic paper

Economic rationale

Most researchers speculate that the volatility premium is caused by investors who strongly dislike negative asset returns and are therefore willing to pay a premium for portfolio insurance offered by options.

Backtest performance

Annualised return17.44%
Volatility18.41%
Beta0.09
Sharpe ratio0.73
Sortino ratio-0.271
Win rate45%

Full Python code

from AlgorithmImports import *
class SellingOptionsonBondETFs(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)

data = self.AddEquity("BIL", Resolution.Minute)
self.bills = data.Symbol
data.SetLeverage(5)

data = self.AddEquity("TLT", Resolution.Minute)
self.symbol = data.Symbol
data.SetLeverage(5)

option = self.AddOption("TLT", Resolution.Minute)
option.SetFilter(-20, 20, 25, 35)

self.last_day = -1

def OnData(self,slice):
# Check once a day.
if self.Time.day == self.last_day:
    return
self.last_day = self.Time.day

for i in slice.OptionChains:
    chains = i.Value
    # Only bill position is opened.
    invested = [x.Key for x in self.Portfolio if x.Value.Invested]
    if len(invested) <= 1:
        calls = list(filter(lambda x: x.Right == OptionRight.Call, chains))
        puts = list(filter(lambda x: x.Right == OptionRight.Put, chains))
        
        if not calls or not puts: return
    
        underlying_price = self.Securities[self.symbol].Price
        expiries = [i.Expiry for i in puts]
        
        # Determine expiration date nearly one month.
        expiry = min(expiries, key=lambda x: abs((x.date()-self.Time.date()).days-30))
        strikes = [i.Strike for i in puts]
        
        # Determine at-the-money strike.
        strike = min(strikes, key=lambda x: abs(x-underlying_price))
        
        atm_call = [i for i in calls if i.Expiry == expiry and i.Strike == strike]
        atm_put = [i for i in puts if i.Expiry == expiry and i.Strike == strike]

        if atm_call and atm_put:
            options_q = int(self.Portfolio.MarginRemaining / (underlying_price * 100))
            # Set max leverage.
            self.Securities[atm_call[0].Symbol].MarginModel = BuyingPowerModel(5)
            self.Securities[atm_put[0].Symbol].MarginModel = BuyingPowerModel(5)
            
            # Sell at-the-money straddle.
            self.Sell(atm_call[0].Symbol, options_q)
            self.Sell(atm_put[0].Symbol, options_q)
            
            # Buy treasury bill.
            self.SetHoldings(self.bills, 1)

    if self.Portfolio.Invested:
        self.Liquidate(self.symbol)