“该策略交易非金融类AMEX、NYSE和NASDAQ股票,针对长期负债增加幅度最大者做多,针对负债变化最小者做空,进行年度再平衡并采用等权重分配。”

I. 策略概要

该策略针对AMEX、NYSE和NASDAQ的非金融股票。每年,投资者计算长期财务负债的年度变化,包括长期债务、当前负债中的债务和优先股。根据这些变化,股票被排名为十个分位。投资者对长期负债增加最多的股票做多,对变化最小的股票做空。各头寸等权重配置,投资组合每年重新平衡,利用财务负债的变化来识别潜在的股票价格趋势。

II. 策略合理性

财务负债,包括债务、资本化租赁义务和优先股,按发行时设定的贴现率计算未来现金义务的现值。公司不得考虑预计的未支付义务,从而最大程度地减少其计量中的主观性。这确保了与财务负债相关的应计项在可靠性上优于其他财务指标。该客观估值过程增强了这些负债的可信度,使其成为评估公司财务健康和义务时可靠的财务分析和策略开发组成部分。

III. 来源论文

Accrual Reliability, Earnings Persistence and Stock Prices [点击查看论文]

<摘要>

这篇论文扩展了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"))

发表评论

了解 Quant Buffet 的更多信息

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

继续阅读