
“该策略交易非金融类AMEX、NYSE和NASDAQ股票,针对长期负债增加幅度最大者做多,针对负债变化最小者做空,进行年度再平衡并采用等权重分配。”
资产类别: 股票 | 地区: 美国 | 周期: 每年 | 市场: 股票 | 关键词: 债务
I. 策略概要
该策略针对AMEX、NYSE和NASDAQ的非金融股票。每年,投资者计算长期财务负债的年度变化,包括长期债务、当前负债中的债务和优先股。根据这些变化,股票被排名为十个分位。投资者对长期负债增加最多的股票做多,对变化最小的股票做空。各头寸等权重配置,投资组合每年重新平衡,利用财务负债的变化来识别潜在的股票价格趋势。
II. 策略合理性
财务负债,包括债务、资本化租赁义务和优先股,按发行时设定的贴现率计算未来现金义务的现值。公司不得考虑预计的未支付义务,从而最大程度地减少其计量中的主观性。这确保了与财务负债相关的应计项在可靠性上优于其他财务指标。该客观估值过程增强了这些负债的可信度,使其成为评估公司财务健康和义务时可靠的财务分析和策略开发组成部分。
III. 来源论文
Accrual Reliability, Earnings Persistence and Stock Prices [点击查看论文]
- Scott A. Richardson, Richard G. Sloan, Mark T. Soliman 和 Irem Tuna。伦敦商学院;阿卡迪安资产管理。南加州大学 – 莱文塔尔会计学院。南加州大学 – 马歇尔商学院。伦敦商学院。
<摘要>
这篇论文扩展了Sloan(1996)的研究,将应计可靠性与收益持久性联系起来。我们构建了一个模型,表明较不可靠的应计项会导致较低的收益持久性。然后,我们开发了一个综合的资产负债表应计分类,并根据基础应计项的可靠性对每个类别进行评级。实证测试通常确认,较不可靠的应计类别会导致较低的收益持久性,并且投资者未能完全预见到收益持久性的降低,从而导致显著的证券误定价。我们得出结论,在财务报表中确认不可靠信息存在显著的成本。


IV. 回测表现
| 年化回报 | 10.5% |
| 波动率 | 6.5% |
| β值 | 0.073 |
| 夏普比率 | 1.62 |
| 索提诺比率 | 0.012 |
| 最大回撤 | N/A |
| 胜率 | 55% |
V. 完整的 Python 代码
from AlgorithmImports import *
from typing import Dict, List
import numpy as np
class LongTermDebtFactorWithinStocks(QCAlgorithm):
def Initialize(self) -> None:
self.SetStartDate(2008, 1, 1)
self.SetCash(100_000)
self.UniverseSettings.Leverage = 10
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.FundamentalSelectionFunction)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.0
self.exchange_codes: List[str] = ['NYS', 'NAS', 'ASE']
self.fundamental_count: int = 3_000
self.fundamental_sorting_key = lambda x: x.MarketCap
self.fin_sector_code: int = 103
self.rebalancing_month: int = 1
self.quantile: int = 10
self.selection_flag: bool = True
self.last_year_liabilities: Dict[Symbol, float] = {}
self.long_symbols: List[Symbol] = []
self.short_symbols: List[Symbol] = []
market: Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.Schedule.On(self.DateRules.MonthStart(market), self.TimeRules.AfterMarketOpen(market), self.Selection)
self.settings.daily_precise_end_time = False
def OnSecuritiesChanged(self, changes: SecurityChanges) -> None:
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
for security in changes.RemovedSecurities:
if security.Symbol in self.last_year_liabilities:
del self.last_year_liabilities[security.Symbol]
def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
if not self.selection_flag:
return Universe.Unchanged
filtered: List[Fundamental] = [
f for f in fundamental if f.HasFundamentalData
and f.SecurityReference.ExchangeId in self.exchange_codes
and not np.isnan(f.MarketCap)
and f.MarketCap != 0
and not np.isnan(f.FinancialStatements.BalanceSheet.TradingandFinancialLiabilities.TwelveMonths)
and f.FinancialStatements.BalanceSheet.TradingandFinancialLiabilities.TwelveMonths > 0
and f.asset_classification.morningstar_industry_code != self.fin_sector_code
]
sorted_filter: List[Fundamental] = sorted(filtered,
key=self.fundamental_sorting_key,
reverse=True)[:self.fundamental_count]
change_in_liabilities: Dict[Symbol, float] = {}
for f in sorted_filter:
liabilities: float = f.FinancialStatements.BalanceSheet.TradingandFinancialLiabilities.TwelveMonths
if f.Symbol not in self.last_year_liabilities:
self.last_year_liabilities[f.Symbol] = liabilities
continue
change_in_liabilities[f.Symbol] = liabilities / self.last_year_liabilities[f.Symbol] - 1
if len(change_in_liabilities) >= self.quantile:
# Sorting by change in Longterm financial liabilities
sorted_by_liabilities: List = sorted(change_in_liabilities.items(), key=lambda x: x[1], reverse=True)
decile: int = int(len(sorted_by_liabilities) / self.quantile)
self.long_symbols = [x[0] for x in sorted_by_liabilities[:decile]]
self.short_symbols = [x[0] for x in sorted_by_liabilities[-decile:]]
return self.long_symbols + self.short_symbols
def OnData(self, slice: Slice) -> None:
if not self.selection_flag:
return
self.selection_flag = False
# Trade execution
targets: List[PortfolioTarget] = []
for i, portfolio in enumerate([self.long_symbols, self.short_symbols]):
for symbol in portfolio:
if slice.ContainsKey(symbol) and slice[symbol] is not None:
targets.append(PortfolioTarget(symbol, ((-1) ** i) / len(portfolio)))
self.SetHoldings(targets, True)
self.long_symbols.clear()
self.short_symbols.clear()
def Selection(self) -> None:
if self.Time.month == self.rebalancing_month:
self.selection_flag = True
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))