“该策略做空纽约证券交易所、美国证券交易所和纳斯达克中资产增长最高的十分位数中债务增长最高的25%的股票,采用价值加权和年度再平衡,以利用财务过度扩张。”

I. 策略概要

该策略的目标是纽约证券交易所、美国证券交易所和纳斯达克的股票,每年(6月底)按总资产增长对其进行排序,并将其分为十分位数。在资产增长最高的十分位数中,股票按债务增长进一步排名。投资者做空该十分位数中债务增长最高的25%的股票。投资组合采用价值加权,并每年进行再平衡,重点关注资产和债务增长较高的公司,以利用潜在的过度扩张或财务压力,通过系统的空头头寸优化回报。

II. 策略合理性

这种异常现象可以用“粉饰报表”的动机来解释,即管理者优先考虑报告的收益。如果债务成本低于收益价格比,债务融资可以提高每股收益(EPS)。在过去业绩不佳、分析师预期过于乐观或盈利能力下降的管理层压力下,这种担忧变得尤为突出。管理者对短期收益的关注通常会影响融资决策,可能导致旨在满足预期或在短期内提高感知业绩的次优财务策略。

III. 来源论文

融资资产增长 [点击查看论文]

<摘要>

我们记录了一种债务异常现象的存在,该现象是对资产增长异常现象的补充:对于给定的资产增长率,发行更多债务的公司以及偿还更多债务的公司,在资产增长日历年之后的6个月开始的12个月内,其股票回报率较低。在探讨债务发行原因时,我们发现,分析师预期更加过于乐观、投资盈利能力下降以及收益价格比相对较高的公司的管理层,倾向于更多地依赖债务融资。另一方面,对于给定的资产增长率,偿还更多债务的公司往往盈利能力有所提高,但定价过高。我们还发现,融资决策受到先前债务比率、资产增长率、盈利能力和首席执行官薪酬敏感性的影响。我们从管理层激励、信号传递和市场择时的角度解释我们的结果。

IV. 回测表现

年化回报11.35%
波动率17.02%
β值-0.588
夏普比率0.43
索提诺比率-0.208
最大回撤N/A
胜率47%

V. 完整的 Python 代码

from AlgorithmImports import *
from numpy import isnan
class DebtGrowthEffectCombinedwithAssetGrowthEffect(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        
        self.quantile:int = 10
        self.sec_quantile:int = 4
        self.traded_percentage:float = .5
        self.leverage:int = 5
        self.min_share_price:float = 5.
        self.exchange_codes:List[str] = ['NYS', 'NAS', 'ASE']
        market:Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
        self.fundamental_count:int = 3000
        self.fundamental_sorting_key = lambda x: x.MarketCap
        # Last year's debt value.
        self.latest_debt:List[Symbol, float] = {}
        self.weight:Dict[Symbol, float] = {}
        
        self.selection_flag:bool = False
        self.UniverseSettings.Resolution = Resolution.Daily
        self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
        self.AddUniverse(self.FundamentalSelectionFunction)
        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.Market == 'usa' and \
            x.Price >= self.min_share_price and x.SecurityReference.ExchangeId in self.exchange_codes and \
            not isnan(x.OperationRatios.TotalAssetsGrowth.OneYear) and x.OperationRatios.TotalAssetsGrowth.OneYear != 0 and \
            not isnan(x.FinancialStatements.BalanceSheet.TotalDebt.TwelveMonths) and x.FinancialStatements.BalanceSheet.TotalDebt.TwelveMonths != 0
        ]
        if len(selected) > self.fundamental_count:
            selected = [x for x in sorted(selected, key=self.fundamental_sorting_key, reverse=True)[:self.fundamental_count]]
        growth_data:Dict[Fundamental, float] = {}
        
        for stock in selected:
            symbol:Symbol = stock.Symbol
            if symbol not in self.latest_debt:
                # Previous year's data.
                self.latest_debt[symbol] = None
            
            asset_growth:float = stock.OperationRatios.TotalAssetsGrowth.OneYear
            debt:float = stock.FinancialStatements.BalanceSheet.TotalDebt.TwelveMonths
            
            # Previous year's data is ready.
            if self.latest_debt[symbol]:
                debt_growth:float = (debt - self.latest_debt[symbol]) / self.latest_debt[symbol]
                growth_data[stock] = (asset_growth, debt_growth)
                
            self.latest_debt[symbol] = debt
        
        # Remove not updated symbols.
        updated_symbols:List[Symbol] = [x.Symbol for x in selected]
        not_updated:List[Symbol] = [x for x in self.latest_debt if x not in updated_symbols]
        for symbol in not_updated:
            del self.latest_debt[symbol]
        
        if len(growth_data) >= self.sec_quantile * self.quantile:
            # Sort by asset and debt growth.
            sorted_by_asset_growth:List = sorted(growth_data.items(), key = lambda x: x[1][0], reverse = True)
            quantile:int = int(len(sorted_by_asset_growth) / self.quantile)
            high_by_asset_growth = [x for x in sorted_by_asset_growth[:quantile]]
            
            sorted_by_debt_growth:List = sorted(high_by_asset_growth, key = lambda x: x[1][1], reverse = True)
            quantile = int(len(sorted_by_debt_growth) / self.sec_quantile)
            short:List[Fundamental] = [x for x in sorted_by_debt_growth[:quantile]]
            
            total_market_cap:float = sum([x[0].MarketCap for x in short])
            for stock, _ in short:
                self.weight[stock.Symbol] = -(stock.MarketCap / total_market_cap) * self.traded_percentage
        
        return list(self.weight.keys())
    
    def OnData(self, data: Slice) -> None:
        if not self.selection_flag:
            return
        self.selection_flag = False
        
        # trade execution
        portfolio:List[PortfolioTarget] = [PortfolioTarget(symbol, w) for symbol, w in self.weight.items() if symbol in data and data[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):
        fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))

发表评论

了解 Quant Buffet 的更多信息

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

继续阅读