该策略每月卖出到期的平值跨式期权(包括看涨和看跌期权),以获得5%的期权溢价。为对冲市场大幅下跌风险,购买15%虚值认沽期权作为保护。所获期权溢价和现金余额将投资于股指。每月末,进行系统调整和重新平衡,以保持与战略目标和市场状况一致。

策略概述

该策略每月卖出一个月到期的平值跨式期权(卖出看涨和看跌期权)以获得5%的期权溢价。为对冲大幅市场下跌的风险,以买入价购买15%虚值认沽期权作为保护。获得的期权溢价和现金余额则投资于股指。该过程在每月末进行系统调整和重新平衡,以确保与战略投资目标和市场状况保持一致。

策略合理性

许多学者认为,波动率溢价的产生是因为投资者害怕负回报和股指波动,因此愿意支付额外费用购买认沽期权作为保护。另一种解释是“比索问题”或黑天鹅事件理论,认为溢价是对罕见但重大事件风险的补偿,这些事件虽然可能发生,但在观测期内尚未发生。然而,这种观点受到质疑,因为证据表明,如果要完全抵消波动率溢价,则需要以不现实的频率发生重大市场下跌,这使得比索问题的解释对一些学者来说不太具有说服力。

论文来源

Expected Option Returns [点击浏览原文]

<摘要>

本文在主流资产定价理论的背景下探讨了期权的预期回报。在温和假设下,看涨期权的预期回报高于其基础资产,并且随着行权价的提高而增加。同样,看跌期权的预期回报低于无风险利率,并且也随着行权价的提高而增加。在不同时间段和回报频率下,标普500和100指数期权回报强烈表现出这些特征。在更强的假设下,期权预期回报是期权贝塔的线性函数。Fama-MacBeth风格的期权回报回归产生的风险溢价接近预期市场回报。然而,回归的截距显著低于零。因此,平值零贝塔跨式期权组合每周平均损失约3%。其他市场的零贝塔跨式期权组合也持续亏损。这些发现表明,期权回报中定价了某种附加因素,如系统性的随机波动性。

回测表现

年化收益率26.0%
波动率19.0%
Beta0.60
夏普比率0.59
索提诺比率0.48
最大回撤24.9%
胜率47%

完整python代码

from AlgoLib import *

class ExploitVolatilityRiskPremium(XXX):
    '''
    A strategy that exploits the volatility risk premium in the SPY ETF. It involves selling at-the-money straddles,
    buying out-of-the-money puts for protection, and investing in the SPY ETF with remaining funds.
    '''

    def Initialize(self):
        '''
        Initializes the algorithm settings, including start date, initial cash, equity, and options.
        Sets leverage for the equity and filters for the option chain.
        '''
        
        self.SetStartDate(2012, 1, 1)  # Set the backtest start date
        self.SetCash(100000)  # Set the initial cash for the backtest
        
        equity = self.AddEquity("SPY", Resolution.Minute)  # Add SPY ETF as an equity with minute resolution
        equity.SetLeverage(5)  # Set leverage for the equity
        self.spy_symbol = equity.Symbol  # Save the SPY symbol for later use
        
        option_contract = self.AddOption("SPY", Resolution.Minute)  # Add SPY option with minute resolution
        option_contract.SetFilter(-20, 20, 30, 60)  # Set a filter for strike price range and expiration days
        
        self.prev_day = -1  # Initialize a variable to track the previous day processed
        
    def OnData(self, data):
        '''
        Executes the trading logic once per day when new data is received.
        Sells at-the-money straddles, buys out-of-the-money puts for protection,
        and allocates remaining funds to SPY ETF.
        '''
        
        # Execute logic once per day
        if self.Time.day == self.prev_day:
            return  # Skip if already processed this day
        self.prev_day = self.Time.day  # Update the previous day processed
            
        for option_chain in data.OptionChains:  # Iterate through each option chain in the data
            chains = option_chain.Value  # Get the actual option chain

            if not self.Portfolio.Invested:  # Check if the portfolio is not yet invested
                calls = [opt for opt in chains if opt.Right == OptionRight.Call]  # Filter for call options
                puts = [opt for opt in chains if opt.Right == OptionRight.Put]  # Filter for put options
                
                if not calls or not puts:
                    return  # Skip if no calls or puts available
            
                market_price = self.Securities[self.spy_symbol].Price  # Get the current market price of SPY
                expiry_dates = [opt.Expiry for opt in puts]  # Get expiry dates for puts
                
                # Choose the expiry close to one month away
                chosen_expiry = min(expiry_dates, key=lambda x: abs((x - self.Time).days - 30))
                strike_prices = [opt.Strike for opt in puts]  # Get strike prices for puts
                
                # Select the at-the-money (ATM) strike
                atm_strike = min(strike_prices, key=lambda x: abs(x - market_price))
                
                # Select the 15% out-of-the-money (OTM) put strike
                otm_put_strike = min(strike_prices, key=lambda x: abs(x - 0.85 * market_price))
        
                atm_calls = [opt for opt in calls if opt.Expiry == chosen_expiry and opt.Strike == atm_strike]  # Filter for ATM calls
                atm_puts = [opt for opt in puts if opt.Expiry == chosen_expiry and opt.Strike == atm_strike]  # Filter for ATM puts
                otm_puts = [opt for opt in puts if opt.Expiry == chosen_expiry and opt.Strike == otm_put_strike]  # Filter for OTM puts
        
                if atm_calls and atm_puts and otm_puts:
                    qty = int(self.Portfolio.MarginRemaining / (market_price * 100))  # Calculate quantity to trade

                    # Sell ATM straddle (both call and put)
                    self.Sell(atm_calls[0].Symbol, qty)
                    self.Sell(atm_puts[0].Symbol, qty)
                    
                    # Buy OTM put as a protective measure
                    self.Buy(otm_puts[0].Symbol, qty)
                    
                    # Allocate remaining funds to SPY ETF
                    self.SetHoldings(self.spy_symbol, 1)
        
            # Liquidate SPY holding if it's the only position left
            if len([x.Key for x in self.Portfolio if x.Value.Invested]) == 1:
                self.Liquidate(self.spy_symbol)

Leave a Reply

Discover more from Quant Buffet

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

Continue reading