
“该策略涉及根据账面市值比的不确定性对纽约证券交易所最大的500只股票进行排序,做多排名前十的股票,做空排名后十的股票,并每月重新平衡。”
资产类别: 股票 | 地区: 美国 | 周期: 每月 | 市场: 股票 | 关键词: 价值、不确定性
I. 策略概要
投资范围包括纽约证券交易所最大的500只股票。为了计算账面市值比的不确定性(UNC),计算过去12个月每日预期账面市值比的标准差,并按其均值进行缩放。预期账面市值比基于估计的股权账面价值和净收入,并根据股息和市值进行调整。股票根据UNC分为十个等份,做多排名前十的股票,做空排名后十的股票。该策略采用价值加权,每月重新平衡,旨在从账面市值比的波动中获利。
II. 策略合理性
该论文发现高UNC股票与生产力和消费风险相关,导致由于风险补偿而产生更高的预期回报。高UNC股票也可能对系统性风险因素有更大的敞口,为其溢价提供了基于风险的解释。此外,高UNC股票可能反映了较低的信息质量和对未来盈利能力更大的不确定性,从而影响其账面市值比。该策略表明,最高UNC十分位的股票产生显著的正阿尔法,而最低十分位的股票显示出不显著的阿尔法,这表明高UNC股票表现优异。这种溢价无法用现有风险因素解释,并且该策略稳健,不受小盘股或非流动性股票的影响。
III. 来源论文
The Value Uncertainty Premium [点击查看论文]
- 图兰·G·巴利(Turan G. Bali)、卢卡·德尔·维夫(Luca Del Viv)、梅娜塔拉·埃尔·赫夫纳维(Menatalla El Hefnawy)、列诺斯·特里乔尔吉斯(Lenos Trigeorgis),乔治城大学麦克多诺商学院,ESADE 商学院,马德里康普顿斯大学(UCM)金融研究学院(CUNEF),塞浦路斯大学公共与工商管理系;伦敦国王学院;麻省理工学院斯隆管理学院
<摘要>
我们调查了账面市值比(UNC)的时间序列波动性是否在股票回报中定价。UNC捕捉了公司现有资产和实物期权组合当前价值的不确定性,并反映了这些期权行使时的价内程度和不确定性变化。UNC还与信息风险、公司不灵活性和投资波动性相关,并获得正溢价。对高UNC公司做多、对低UNC公司做空的投资策略每年产生13%的风险调整回报。UNC溢价由面临更高信息风险的高UNC(不灵活)公司的出色表现驱动,并且无法用已建立的风险因素和公司特征来解释。


IV. 回测表现
| 年化回报 | 12.01% |
| 波动率 | 14.62% |
| β值 | 0.019 |
| 夏普比率 | 0.55 |
| 索提诺比率 | 0.199 |
| 最大回撤 | N/A |
| 胜率 | 50% |
V. 完整的 Python 代码
from AlgorithmImports import *
from numpy import isnan
from typing import List, Dict
class TheValueUncertaintyPremium(QCAlgorithm):
def Initialize(self) -> None:
self.SetStartDate(2010, 1, 1)
self.SetCash(100_000)
self.exchange_codes: List[str] = ['NYS']
self.weight: Dict[Symbol, float] = {}
self.quantile: int = 10
self.leverage: int = 10
self.bm_by_symbol: Dict[Symbol, RollingWindow] = {}
self.m_period: int = 12
self.required_estimate_quarter: int = 4
self.universe_selection_period: int = 12
self.recent_EPS_estimate: Dict[Symbol, Dict[int, List[float]]] = {}
market: Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.already_subscribed:list[Symbol] = []
self.last_fundamental: List[Symbol] = []
self.fundamental_count: int = 500
self.fundamental_sorting_key = lambda x: x.DollarVolume
self.selection_flag: bool = False
self.rebalance_flag: bool = False
self.UniverseSettings.Leverage = self.leverage
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.FundamentalSelectionFunction)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
self.Schedule.On(self.DateRules.MonthStart(market), self.TimeRules.AfterMarketOpen(market), self.Selection)
def OnSecuritiesChanged(self, changes: SecurityChanges) -> None:
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
if not self.selection_flag:
return Universe.Unchanged
self.selection_flag = False
# select new universe once a period
if self.Time.month % self.universe_selection_period == 0:
selected: List[Fundamental] = [
x for x in fundamental
if x.HasFundamentalData
and x.Market == 'usa'
and x.MarketCap != 0
and x.SecurityReference.ExchangeId in self.exchange_codes
and not isnan(x.ValuationRatios.ForwardDividend) and x.ValuationRatios.ForwardDividend
and not isnan(x.FinancialStatements.BalanceSheet.TotalAssets.ThreeMonths) and x.FinancialStatements.BalanceSheet.TotalAssets.ThreeMonths != 0
and not isnan(x.EarningReports.BasicAverageShares.ThreeMonths) and x.EarningReports.BasicAverageShares.ThreeMonths != 0
and not isnan(x.FinancialStatements.BalanceSheet.CurrentLiabilities.ThreeMonths) and x.FinancialStatements.BalanceSheet.CurrentLiabilities.ThreeMonths
]
if len(selected) > self.fundamental_count:
selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]]
for stock in selected:
symbol: Symbol = stock.Symbol
if symbol not in self.already_subscribed:
self.AddData(EstimizeEstimate, symbol)
self.already_subscribed.append(symbol)
self.last_fundamental = selected
self.rebalance_flag = True
UNC: Dict[Symbol, float] = {}
for stock in self.last_fundamental:
symbol: Symbol = stock.Symbol
# store pb value
if symbol not in self.bm_by_symbol:
self.bm_by_symbol[symbol] = RollingWindow[float](self.m_period)
# calculate UNC
if self.bm_by_symbol[symbol].IsReady:
bm_values: List[float] = [x for x in self.bm_by_symbol[symbol]]
bm_std: float = np.std(bm_values)
pb_mean: float = np.mean(bm_values)
UNC[stock] = bm_std / pb_mean
# get net income estimate
if symbol.Value in self.recent_EPS_estimate and self.Time.year in self.recent_EPS_estimate[symbol.Value]:
current_be: float = stock.FinancialStatements.BalanceSheet.TotalAssets.ThreeMonths - stock.FinancialStatements.BalanceSheet.CurrentLiabilities.ThreeMonths
# NOTE Expected net income for the end of fiscal year y, given the information available up to day d, is estimated as the product of expected earnings per share given by the mean of analysts’ forecasts up to day d and the total number of shares outstanding.
estimated_net_income: float = np.mean(self.recent_EPS_estimate[symbol.Value][self.Time.year]) * stock.EarningReports.BasicAverageShares.ThreeMonths
expected_dividend: float = stock.ValuationRatios.ForwardDividend
expected_be: float = current_be + estimated_net_income - expected_dividend
bm: float = expected_be / stock.MarketCap
self.bm_by_symbol[symbol].Add(bm)
else:
if self.bm_by_symbol[symbol].Count != 0:
self.bm_by_symbol[symbol].Reset()
if len(UNC) >= self.quantile:
sorted_by_perf: List[Tuple[Symbol, float]] = sorted(UNC.items(), key = lambda x:x[1], reverse=True)
quantile: int = int(len(sorted_by_perf) / self.quantile)
long: List[Fundamental] = [x[0] for x in sorted_by_perf[:quantile]]
short: List[Fundamental] = [x[0] for x in sorted_by_perf[-quantile:]]
# market cap weighting
for i, portfolio in enumerate([long, short]):
mc_sum: float = sum(list(map(lambda stock: stock.MarketCap, portfolio)))
for stock in portfolio:
self.weight[stock.Symbol] = ((-1)**i) * stock.MarketCap / mc_sum
return list(self.weight.keys())
def OnData(self, slice: Slice) -> None:
# store latest EPS Estimize estimate
estimize: Dict[Symbol, float] = slice.Get(EstimizeEstimate)
for symbol, value in estimize.items():
ticker: str = symbol.Value
# store eond of the year estimates indexed by fiscal year
if value.FiscalQuarter == self.required_estimate_quarter:
if ticker not in self.recent_EPS_estimate:
self.recent_EPS_estimate[ticker] = {}
if value.FiscalYear not in self.recent_EPS_estimate[ticker]:
self.recent_EPS_estimate[ticker][value.FiscalYear] = []
self.recent_EPS_estimate[ticker][value.FiscalYear].append(value.Eps)
if not self.rebalance_flag:
return
self.rebalance_flag = False
# trade execution
portfolio: List[PortfolioTarget] = [PortfolioTarget(symbol, w) for symbol, w in self.weight.items() if slice.contains_key(symbol) and slice[symbol]]
self.SetHoldings(portfolio, True)
self.weight.clear()
def Selection(self) -> None:
self.selection_flag = True
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters: OrderFeeParameters) -> OrderFee:
fee: float = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))