“该策略涉及美国股票的delta对冲看涨期权,选择货币性在0.7到1.3之间的期权。头寸每月建立,重新平衡,并持有至期权到期,等权重。”

I. 策略概要

投资范围包括在每月第三个星期五到期的美国股票期权,不包括零未平仓合约或交易量的流动性不足的期权。投资组合是delta对冲看涨期权策略,将期权与标的资产中的负delta头寸相结合。选择货币性在0.7到1.3之间的期权,并在每个月的第三个星期五建立头寸,在四周后到期的期权中建立空头头寸。投资组合持有至到期日,并等权重。每月进行重新平衡,保持delta中性头寸。

II. 策略合理性

投资者通常对短期期权的精确到期天数等细节表现出注意力不集中,尽管这些信息很容易获取。这种注意力不集中可以用两个因素解释。首先,期权交易者被认为比典型的股票交易者更成熟;其次,财务报表中的公司特定信息需要大量处理,而期权的到期日很容易识别,只需极少的努力。简单信息经常被忽视这一事实反映了投资中的行为偏差。投资者往往更关注本月到期的期权,而不是下月到期的期权,从而导致到期月份的市场效应更强。

III. 来源论文

Inattention in the Options Market [点击查看论文]

<摘要>

美国股票的期权通常在每个月的第三个星期五到期,这意味着两个连续到期日之间相隔四周或五周。我们发现,当到期日之间相隔四周时,从一个到期日持有到下一个到期日的期权实现的每周调整后回报明显较低。我们认为这种定价错误是由于投资者注意力不集中造成的,并基于财报公布日期和更接近到期日的定价模式提供了进一步的支持证据。在控制大量期权和股票特征后,结果仍然非常显著,并且对各种子样本和估计程序都具有稳健性。我们的发现对校准期权定价模型以及从期权价格中提取信息以预测未来变量具有潜在的重要意义。

IV. 回测表现

年化回报9.4%
波动率N/A
β值0.56
夏普比率N/A
索提诺比率-0.174
最大回撤N/A
胜率49%

V. 完整的 Python 代码

from AlgorithmImports import *
#endregion
class MispricingofxOptionsWithDifferentTimeToMaturity(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2015, 1, 1)
        self.SetCash(1000000)
        
        self.min_share_price:int = 5
        self.min_expiry:int = 18
        self.max_expiry:int = 22
        self.percentage_traded:float = 1
        self.selected_symbols:list[Symbol] = {}
        self.subscribed_contracts:dict[Symbol, Contracts] = {}
        
        self.weeks_counter:int = 0
        self.rebalance_period:int = 3
        self.weekday_num:int = 3 # represents thursday
        self.market_symbol:Symbol = self.AddEquity('SPY', Resolution.Minute).Symbol
        self.recent_day:int = -1
        self.recent_month:int = -1
        self.fundamental_sorting_key = lambda x: x.DollarVolume
        self.fundamental_count:int = 100
        self.selection_flag:bool = False
        self.settings.daily_precise_end_time = False
        self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
        self.UniverseSettings.Resolution = Resolution.Minute
        self.AddUniverse(self.FundamentalSelectionFunction)
        self.SetSecurityInitializer(lambda x: x.SetDataNormalizationMode(DataNormalizationMode.Raw))
        self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw
    def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
        # rebalance, when contracts expiried
        if not self.selection_flag:
            return Universe.Unchanged
        
        selected:list = [
            x for x in fundamental if x.HasFundamentalData and x.Market == 'usa' and x.Price >= self.min_share_price
        ]
        if len(selected) > self.fundamental_count:
            selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]]
        self.selected_symbols = list(map(lambda stock: stock.Symbol, selected))
        
        return self.selected_symbols
    def OnData(self, data: Slice):
        curr_date:datetime.date = self.Time.date()
        # execute once a day
        if self.recent_day != curr_date.day:
            self.recent_day = curr_date.day
            if self.recent_month != curr_date.month:
                self.recent_month = curr_date.month
                self.weeks_counter = 0
            
            # check if any of the subscribed contracts expired
            for symbol in self.selected_symbols:
                if symbol in self.subscribed_contracts and self.subscribed_contracts[symbol].expiry_date <= self.Time.date():
                    # remove expired contracts
                    for contract in self.subscribed_contracts[symbol].contracts:
                        if self.Securities[contract].IsTradable:
                            # self.RemoveSecurity(contract)
                            self.Liquidate(contract)
                        
                    del self.subscribed_contracts[symbol]
        
            if curr_date.weekday() == self.weekday_num:
                # increase week counter at the specific day of the week
                self.weeks_counter += 1
                # allow rebalance on the third thursday of the month,
                # because stocks and contracts will be subscribed on the third friday of the month
                if self.weeks_counter % self.rebalance_period == 0:
                    self.subscribed_contracts.clear()
                    self.selected_symbols.clear()
                    self.selection_flag = True
                    return
            
            # subscribe to new contracts after selection
            if len(self.subscribed_contracts) == 0 and self.selection_flag:
                for symbol in self.selected_symbols:
                    # get all contracts for current stock symbol
                    contracts:list[Symbol] = self.OptionChainProvider.GetOptionContractList(symbol, self.Time)
                    underlying_price:float = self.Securities[symbol].Price
                    
                    strikes:list = [i.ID.StrikePrice for i in contracts]
                    
                    # can't filter contracts, if there isn't any strike price
                    if len(strikes) <= 0 or underlying_price == 0:
                        continue
                    
                    call:Symbol|None = self.FilterContracts(strikes, contracts, underlying_price)
                    if call:
                        subscriptions:list = self.SubscriptionManager.SubscriptionDataConfigService.GetSubscriptionDataConfigs(call.Underlying)
                        if subscriptions:
                            self.AddContract(call)
                            expiry_date:datetime.date = call.ID.Date.date()                    
                            self.subscribed_contracts[symbol] = Contracts(expiry_date, underlying_price, [call])
        # this triggers next minute after new contracts subscription
        elif len(self.subscribed_contracts) != 0 and self.selection_flag:
            self.selection_flag = False # this makes sure, there will be no other trades until next selection
            # trade execution
            self.Liquidate()
            length:int = len(self.selected_symbols)
            for symbol in self.selected_symbols:
                if symbol in data and data[symbol]:
                    if symbol not in self.subscribed_contracts:
                        continue
                    call = self.subscribed_contracts[symbol].contracts[0]
                    underlying_price:float = self.subscribed_contracts[symbol].underlying_price
                    
                    options_q:int = int(((self.Portfolio.TotalPortfolioValue * self.percentage_traded) / length) / (underlying_price * 100))
                    
                    if call in data and data[call] != 0 and symbol in data and data[symbol]:
                        self.Sell(call, options_q)
                        # delta hedge
                        self.SetHoldings(symbol, (1 / length) * self.percentage_traded)
        
    def FilterContracts(self, strikes:list, contracts:list, underlying_price:float):
        ''' filter call contracts from contracts parameter '''
        ''' return call contract '''
        
        result = None
        atm_strike:float = min(strikes, key=lambda x: abs(x-underlying_price))
        
        # filtred contracts based on option rights and strikes
        atm_calls:list[Symbol] = [i for i in contracts if i.ID.OptionRight == OptionRight.Call and 
                                                i.ID.StrikePrice == atm_strike and 
                                                self.min_expiry <= (i.ID.Date - self.Time).days <= self.max_expiry]
        if len(atm_calls) > 0:
            # sort by expiry
            result = sorted(atm_calls, key = lambda item: item.ID.Date, reverse=True)[0]
        return result
        
    def AddContract(self, contract) -> None:
        ''' subscribe option contract, set price mondel and normalization mode '''
        option = self.AddOptionContract(contract, Resolution.Minute)
        option.PriceModel = OptionPriceModels.BlackScholes()
        
class Contracts():
    def __init__(self, expiry_date, underlying_price, contracts):
        self.expiry_date = expiry_date
        self.underlying_price = underlying_price
        self.contracts = contracts

发表评论

了解 Quant Buffet 的更多信息

立即订阅以继续阅读并访问完整档案。

继续阅读