The strategy sells one-month at-the-money straddles for Oil, Gold, and Natural Gas futures monthly, equally weights positions, and delta-hedges daily to maintain primary exposure to market volatility changes.

I. STRATEGY IN A NUTSHELL

The strategy involves continuously selling one-month at-the-money straddles on Oil, Gold, and Natural Gas futures. Each straddle—comprising a call and a put at the same strike and maturity—is equally weighted. Straddles are delta-hedged daily to maintain exposure primarily to changes in market volatility rather than price direction.

II. ECONOMIC RATIONALE

This approach aims to capture the volatility risk premium in commodity markets. By selling straddles, the strategy earns the premium paid by buyers seeking protection or speculative exposure. Daily delta hedging ensures the portfolio responds to volatility changes, not directional moves. Structural market imbalances—high demand from hedgers, limited natural sellers, and reduced leverage during crises—make the volatility risk premium persistent, allowing the strategy to exploit these inefficiencies over time.

III. SOURCE PAPER

The Volatility Risk Premium [Click to Open PDF]

G. Renninson, N. Pedersen

<Abstract>

Elevated global macroeconomic uncertainty and bouts of extreme market turbulence have recently plagued financial markets. This environment has prompted a search for diversifying investment opportunities that lie outside the space of traditional asset classes. This article examines the performance of options strategies that aim to capture a return premium over time as compensation for the risk of losses during sudden increases in market volatility. We show that these “volatility risk premium” strategies deliver attractive risk-adjusted returns across 14 options markets from June 1994 to June 2012. Performance furthermore improves significantly after the crisis in 2008 (see Figure 1). We conclude that the risk-return tradeoff for volatility strategies compares favorably to those of traditional investments such as equities and bonds and that the strategies exhibit relatively low correlations to equity risk. Investors who want to diversify their portfolio’s equity risk exposures should therefore consider making allocations to volatility risk premium strategies. However, successful implementation would require diversification across major options markets (equities, interest rates, currencies and commodities), active risk management and prudent scaling.

IV. BACKTEST PERFORMANCE

Annualised Return6.1%
Volatility5.2%
Beta0.109
Sharpe Ratio1.17
Sortino Ratio-0.299
Maximum Drawdown-8.5%
Win Rate45%

V. FULL PYTHON CODE

from AlgorithmImports import *
class VolatilityRiskPremiuminCommodities(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        
        self.min_expiry = 30
        self.max_expiry = 45
        
        self.symbols = []
        self.contracts = {}
        
        tickers = ['USO', 'GLD', 'UNG']
        
        for ticker in tickers:
            # equity data
            data = self.AddEquity(ticker, Resolution.Daily)
            data.SetLeverage(10)
            data.SetFeeModel(CustomFeeModel())
            
            # change normalization to raw to allow adding etf contracts
            data.SetDataNormalizationMode(DataNormalizationMode.Raw)
            
            self.symbols.append(data.Symbol)
        self.settings.daily_precise_end_time = False
        self.settings.minimum_order_margin_portfolio_percentage = 0.
        self.last_day:int = -1
        self.trade_flag = False
    def OnData(self, data):
        # check once a day
        if self.Time.day == self.last_day:
            return
        self.last_day = self.Time.day
        
        # trade option contracts after selection and make sure, they are ready
        if self.trade_flag and data.OptionChains.Count != 0:
            # next rebalance will be after next selection
            self.trade_flag = False
            
            # sell atm straddle of subscribed contracts
            for symbol, contract_obj in self.contracts.items():
                # get call and put contract
                call, put = contract_obj.contracts
                
                # get underlying price
                underlying_price = contract_obj.underlying_price
                
                options_q = int((self.Portfolio.TotalPortfolioValue / len(self.symbols)) / (underlying_price * 100))
                
                self.Sell(call, options_q)
                self.Sell(put, options_q)
                
                # delta hedge
                self.MarketOrder(symbol, options_q*50)
        # check contracs expiration
        for symbol in self.symbols:
            # close trade once option is 
            if symbol in self.contracts and self.contracts[symbol].expiry_date-timedelta(days=1) <= self.Time.date():
                # liquidate expired contracts
                for contract in self.contracts[symbol].contracts:
                    self.Liquidate(contract)
                self.Liquidate(symbol)
                
                # remove Contracts object for current symbol
                del self.contracts[symbol]
        
        # rebalance, when all option contracts expiried
        if len(self.contracts) == 0:
            self.Liquidate()
            
            # subscribe to new option contracts
            for symbol in self.symbols:
                # get all contracts for current etf
                contracts = self.OptionChainProvider.GetOptionContractList(symbol, self.Time)
                # get current price for etf
                underlying_price = self.Securities[symbol].Price
                
                # get strikes from etf contracts
                strikes = [i.ID.StrikePrice for i in contracts]
                
                # can't filter contracts, if there isn't any strike price
                if len(strikes) <= 0:
                    continue
                
                # filter calls and puts contracts with one month expiry
                calls, puts = self.FilterContracts(strikes, contracts, underlying_price)
                
                # make sure, there is at least one call and put contract
                if len(calls) and len(puts):
                    # sort by expiry
                    call = sorted(calls, key = lambda x: x.ID.Date)[0]
                    put = sorted(puts, key = lambda x: x.ID.Date)[0]
                    
                    subscription = self.SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(call.Underlying)
                    
                    # make sure put and call contract subscription is valid
                    if subscription:
                        # add call contract
                        self.AddContract(call)
                        # add put contract
                        self.AddContract(put)
                        
                        # retrieve expiry date for contracts
                        expiry_date = call.ID.Date.date() if call.ID.Date.date() <= put.ID.Date.date() else put.ID.Date.date()
                        # store contracts with expiry date under etf symbol
                        self.contracts[symbol] = Contracts(expiry_date, underlying_price, [call, put])
            
            # strategy sells subscribed contracts on next day
            self.trade_flag = True
        
    def FilterContracts(self, strikes, contracts, underlying_price):
        ''' filter call and put contracts from contracts parameter '''
        ''' return call and put contracts '''
        
        # Straddle
        call_strike:float = min(strikes, key=lambda x: abs(x-underlying_price))
        put_strike = call_strike
        
        calls = [] # storing call contracts
        puts = [] # storing put contracts
        
        for contract in contracts:
            # check if contract has one month expiry
            if self.min_expiry < (contract.ID.Date - self.Time).days < self.max_expiry:
                # check if contract is call
                if contract.ID.OptionRight == OptionRight.Call and contract.ID.StrikePrice == call_strike:
                    calls.append(contract)
                # check if contract is put
                elif contract.ID.OptionRight == OptionRight.Put and contract.ID.StrikePrice == put_strike:
                    puts.append(contract)
        
        # return filtered calls and puts with one month expiry
        return calls, puts
        
    def AddContract(self, contract):
        ''' subscribe option contract, set price mondel and normalization mode '''
        option = self.AddOptionContract(contract, Resolution.Daily)
        option.PriceModel = OptionPriceModels.CrankNicolsonFD()
class Contracts():
    def __init__(self, expiry_date, underlying_price, contracts):
        self.expiry_date = expiry_date
        self.underlying_price = underlying_price
        self.contracts = contracts
           
# custom fee model
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))

VI. Backtest Performance

Leave a Reply

Discover more from Quant Buffet

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

Continue reading