
“该策略涉及美国股票的delta对冲看涨期权,选择货币性在0.7到1.3之间的期权。头寸每月建立,重新平衡,并持有至期权到期,等权重。”
资产类别: 期权 | 地区: 美国 | 周期: 每月 | 市场: 股票 | 关键词: 股票
I. 策略概要
投资范围包括在每月第三个星期五到期的美国股票期权,不包括零未平仓合约或交易量的流动性不足的期权。投资组合是delta对冲看涨期权策略,将期权与标的资产中的负delta头寸相结合。选择货币性在0.7到1.3之间的期权,并在每个月的第三个星期五建立头寸,在四周后到期的期权中建立空头头寸。投资组合持有至到期日,并等权重。每月进行重新平衡,保持delta中性头寸。
II. 策略合理性
投资者通常对短期期权的精确到期天数等细节表现出注意力不集中,尽管这些信息很容易获取。这种注意力不集中可以用两个因素解释。首先,期权交易者被认为比典型的股票交易者更成熟;其次,财务报表中的公司特定信息需要大量处理,而期权的到期日很容易识别,只需极少的努力。简单信息经常被忽视这一事实反映了投资中的行为偏差。投资者往往更关注本月到期的期权,而不是下月到期的期权,从而导致到期月份的市场效应更强。
III. 来源论文
Inattention in the Options Market [点击查看论文]
- Eisdorfer、Sadka、Zhdanov,美国康涅狄格大学、波士顿学院、宾夕法尼亚州立大学
<摘要>
美国股票的期权通常在每个月的第三个星期五到期,这意味着两个连续到期日之间相隔四周或五周。我们发现,当到期日之间相隔四周时,从一个到期日持有到下一个到期日的期权实现的每周调整后回报明显较低。我们认为这种定价错误是由于投资者注意力不集中造成的,并基于财报公布日期和更接近到期日的定价模式提供了进一步的支持证据。在控制大量期权和股票特征后,结果仍然非常显著,并且对各种子样本和估计程序都具有稳健性。我们的发现对校准期权定价模型以及从期权价格中提取信息以预测未来变量具有潜在的重要意义。


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