The strategy shorts S&P 500 out-of-the-money puts monthly, applies 2.0x leverage, invests excess margin in risk-free assets, and rebalances monthly to maintain consistent exposure and optimize returns.

I. STRATEGY IN A NUTSHELL

This strategy shorts out-of-the-money S&P 500 put options each month, using a strike based on 1× monthly index volatility. It applies 2× leverage and invests unused margin in the risk-free asset, rebalancing monthly to maintain consistent exposure and capture option premiums.

II. ECONOMIC RATIONALE

Option writing can earn compensation for jump and volatility risk premia. By systematically replicating these exposures, the strategy can mimic hedge fund returns and capture non-linear risk premia.

III. SOURCE PAPER

The Cost of Capital for Alternative Investments [Click to Open PDF]

Jakub W. Jurek and Erik Stafford.University of Pennsylvania – Finance Department; National Bureau of Economic Research (NBER).Harvard Business School – Finance Unit.

<Abstract>

Traditional risk factor models indicate that hedge funds capture pre-fee alphas of 6% to 10% per annum over the period from 1996 to 2012. At the same time, the hedge fund return series is not reliably distinguishable from the returns of mechanical S&P 500 put-writing strategies. We show that the high excess returns to hedge funds and put-writing are consistent with an equilibrium in which a small subset of investors specialize in bearing downside market risks. Required rates of return in such an equilibrium can dramatically exceed those suggested by traditional models, affecting inference about the attractiveness of these investments.

V. BACKTEST PERFORMANCE

Annualised Return10.3%
Volatility7.7%
Beta1.196
Sharpe Ratio1.34
Sortino Ratio0.394
Maximum Drawdown-21.8%
Win Rate61%

V. FULL PYTHON CODE

import numpy as np
from AlgorithmImports import *
from math import floor
from datetime import datetime
class CloningHedgeFundIndexes(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2013, 1, 1)
        self.SetCash(100000)
        
        self.symbol = self.AddEquity("SPY", Resolution.Minute).Symbol
        self.leverage:float = 2.
        
        # spy consolidator
        self.consolidator:TradeBarConsolidator = TradeBarConsolidator(timedelta(days=1))
        self.consolidator.DataConsolidated += self.CustomHandler
        self.SubscriptionManager.AddConsolidator(self.symbol, self.consolidator)            
        
        self.period:int = 21
        
        # Daily price data.
        self.data:RollingWindow = RollingWindow[float](self.period)
        
        option:Option = self.AddOption("SPY", Resolution.Minute)
        
    def CustomHandler(self, sender: object, consolidated_bar: TradeBar) -> None:
        self.data.Add(consolidated_bar.Close)
        
    def OnData(self, slice:Slice) -> None:
        for i in slice.OptionChains:
            chains:OptionChain = i.Value
            
            if self.Portfolio[self.symbol].Invested:
                self.Liquidate(self.symbol)
            
            if not self.Portfolio.Invested:
                # Market data is ready.
                if self.data.IsReady:
                    market_closes:np.ndarray = np.array(list(self.data))
                    market_returns:np.ndarray = market_closes[:-1] / market_closes[1:] - 1
                    market_std:float = np.std(market_returns)
                    
                    # Divide option chains into put options
                    puts:List = list(filter(lambda x: x.Right == OptionRight.Put, chains))
                    if not puts: return
                    underlying_price:float = self.Securities[self.symbol].Price
                    expiries:List[datetime] = list(map(lambda x: x.Expiry, puts))
    
                    # Determine expiration date nearly one month.
                    expiry:datetime = min(expiries, key=lambda x: abs((x.date() - self.Time.date()).days - 30))
                    strikes:List[float] = list(map(lambda x: x.Strike, puts))
    
                    # Determine out-of-the-money strike.
                    otm_strike:float = min(strikes, key = lambda x:abs(x - float(1 - market_std) * underlying_price))
                    
                    # Leverage calc.
                    otm_put:OptionContract = [i for i in puts if i.Expiry == expiry and i.Strike == otm_strike]
                    q:float = floor(float(self.Portfolio.MarginRemaining / (underlying_price * 100)) * self.leverage)
    
                    if otm_put:
                        # Sell with 2x leverage.
                        self.Sell(otm_put[0].Symbol, q)

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading