Cloning Hedge Fund Indexes
Log in to collectAcademic paper
The Cost of Capital for Alternative Investments
Jakub W. Jurek; Erik Stafford
- National Bureau of Economic Research
- University of Pennsylvania
- ?National Bureau of Economic Research (NBER)
- ?University of Pennsylvania - Finance Department
- ?Harvard Business School - Finance Unit
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.
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.
Backtest performance
Annualised return10.3%
Volatility7.7%
Beta1.196
Sharpe ratio1.34
Sortino ratio0.394
Maximum drawdown-21.8%
Win rate61%
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)