Quant Buffet放轻松,别过度思虑

非流动经营资产变化的影响

登录后收藏

学术论文

Accrual Reliability, Earnings Persistence and Stock Prices

作者斯科特·A·理查德森

机构
  • ?理查德·G·斯隆,马克·T·索利曼和伊瑞姆·图纳,伦敦商学院
  • ?阿卡迪安资产管理,南加州大学 - 莱文塔尔会计学院,南加州大学 - 马歇尔商学院,伦敦商学院。
论文摘要

这篇论文通过将应计项的可靠性与收益持续性联系起来,扩展了Sloan(1996)的研究。我们构建了一个模型,表明不太可靠的应计项会导致较低的收益持续性。接着,我们开发了一个综合性的资产负债表应计项分类,并根据应计项的可靠性对每个类别进行评级。实证测试普遍确认,不太可靠的应计项类别会导致较低的收益持续性,而投资者没有完全预见到这一点,导致证券的显著定价错误。我们得出结论,识别财务报表中不可靠信息的成本是显著的。

策略概要

该策略专注于在AMEX、NYSE和NASDAQ交易的非金融股票。每年,投资者计算非流动经营资产的年度变化,定义为总资产减去流动资产,再减去投资和预付款。根据这一变化,将股票按十等分排序。投资者对变化最负的股票(即最低增长或最大下降)做多,对变化最正的股票(即最大增长)做空。头寸等权重分配,投资组合每年重新平衡一次,旨在利用经营资产管理的变化。

策略合理性

∆ΝCOA的主要组成部分是固定资产(PP&E)和无形资产。这些应计项涉及相当大的不确定性。因此,拥有较低水平非流动经营资产且其变化为负的公司,应该会表现优于那些发生较大变化的公司。

回测表现

波动率7.26%
夏普比率1.67
索提诺比率-0.145
胜率52%

完整 Python 代码

from AlgorithmImports import *
class EffectofChangeinNonCurrentOperatingAssets(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2010, 1, 1)
self.SetCash(100000)
market:Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol

self.long:List[Symbol] = []
self.short:List[Symbol] = []
# Non-Current Operating Assets.
self.operating_assets:Dict[Symbol, float] = {}
self.quantile:int = 10
self.leverage:int = 5
self.rebalance_month:int = 12
self.min_share_price:float = 5.
self.exchange_codes:List[str] = ['NYS', 'NAS', 'ASE']
self.fundamental_count:int = 500
self.fundamental_sorting_key = lambda x: x.DollarVolume
self.selection_flag:bool = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.FundamentalSelectionFunction)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
self.Schedule.On(self.DateRules.MonthEnd(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())
    security.SetLeverage(self.leverage)
    
def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
if not self.selection_flag:
    return Universe.Unchanged

selected:List[Fundamental] = [
    x for x in fundamental if x.HasFundamentalData and x.Price >= self.min_share_price and x.Market == 'usa' and x.SecurityReference.ExchangeId in self.exchange_codes and \
    not np.isnan(x.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths) and x.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths != 0 and \
    not np.isnan(x.FinancialStatements.BalanceSheet.CurrentAssets.TwelveMonths) and x.FinancialStatements.BalanceSheet.CurrentAssets.TwelveMonths != 0 and \
    not np.isnan(x.FinancialStatements.BalanceSheet.InvestmentsAndAdvances.TwelveMonths) and x.FinancialStatements.BalanceSheet.InvestmentsAndAdvances.TwelveMonths != 0 and \
    x.AssetClassification.MorningstarSectorCode != MorningstarSectorCode.FinancialServices
]
if len(selected) > self.fundamental_count:
    selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]]
    
d_assets:Dict[Symbol, float] = {}
for stock in selected:
    symbol:Symbol = stock.Symbol

    if symbol not in self.operating_assets:
        self.operating_assets[symbol] = -1.
    
    assets:float = stock.FinancialStatements.BalanceSheet.TotalAssets.TwelveMonths - \
            stock.FinancialStatements.BalanceSheet.CurrentAssets.TwelveMonths - \
            stock.FinancialStatements.BalanceSheet.InvestmentsAndAdvances.TwelveMonths
    if symbol in self.operating_assets and self.operating_assets[symbol] not in [-1, 0]:
        d_assets[symbol] = assets / self.operating_assets[symbol] - 1
    # Update assets value.
    self.operating_assets[symbol] = assets
    
# NOTE: Get rid of old advertisment records so we work with latest values.
for symbol in self.operating_assets:
    if symbol not in [x.Symbol for x in selected]:
        self.operating_assets[symbol] = -1

if len(d_assets) >= self.quantile:
    sorted_by_assets:List = sorted(d_assets.items(), key = lambda x: x[1], reverse = True)
    quantile:int = int(len(sorted_by_assets) / self.quantile)
    self.long = [x[0] for x in sorted_by_assets[-quantile:]]
    self.short = [x[0] for x in sorted_by_assets[:quantile]]

return self.long + self.short

def OnData(self, data: Slice) -> None:
if not self.selection_flag:
    return
self.selection_flag = False

# order execution
targets:List[PortfolioTarget] = []
for i, portfolio in enumerate([self.long, self.short]):
    for symbol in portfolio:
        if symbol in data and data[symbol]:
            targets.append(PortfolioTarget(symbol, ((-1) ** i) / len(portfolio)))
		
self.SetHoldings(targets, True)
self.long.clear()
self.short.clear()
def Selection(self) -> None:
if self.Time.month == self.rebalance_month:
    self.selection_flag = True        
# Custom fee model.
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))