“该策略投资于在纽约证券交易所、美国证券交易所和纳斯达克上市的美国普通股票。根据收入与价格比率(RP)在每月末将股票分为五个五分位。最终投资组合由负收益的公司组成,策略为做多RP最高的五分位(顶组),做空RP最低的五分位(底组)。投资组合按市值加权,每月再平衡。”

策略概述

投资领域由在纽约证券交易所、美国证券交易所或纳斯达克交易的美国普通股票构成。研究人员从COMPUSTAT和CRSP获取数据。股票根据收入与价格比率(RP)在每月 t−1t-1t−1 月底分配到五个五分位投资组合中。最终投资组合仅由具有负收益的公司构成,策略为做多(买入)收入与价格比率最高的五分位(顶组),并做空(卖出)收入与价格比率最低的五分位(底组)中的亏损公司。该投资组合按市值加权(即公司的市值决定组合的权重),并每月定期再平衡。

策略合理性

该论文提出,账面市值比率和收入价格比率可以正向预测亏损公司的股票回报横截面,月回报差异超过1%,且统计显著性较高。另一方面,盈利公司中的价值效应较小。由于盈利状况较差,亏损公司通常需要筹集额外资本,使其更难估值且更容易被错误定价。潜在机制包括对亏损公司的偏见性预期、基于亏损公司价值效应的知情交易,以及套利限制。

论文来源

The Valuation of Loss Firms: A Stock Market Perspective [点击浏览原文]

<摘要>

近年来,上市公司中盈利为负的比例已增加至40%以上。鉴于这些亏损公司的基本价值难以确定,我们预期这些公司中将出现特别强烈的价值效应。我们的研究发现,与盈利公司相比,账面市值比率和收入价格比率的回报预测能力在亏损公司中显著更强。进一步对财务分析师、盈利公告回报、卖空活动、期权交易以及套利限制的分析支持了行为机制的主要发现。

回测表现

年化收益率14.84%
波动率36.55%
Beta0.198
夏普比率0.41
索提诺比率0.12
最大回撤N/A
胜率54%

完整python代码

from AlgorithmImports import *
# endregion

class ValueEffectInUnprofitableFirms(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        
        self.leverage:int = 5
        self.quantile:int = 5

        market:Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
        self.weight:Dict[Symbol, float] = {}

        self.fundamental_count:int = 1000
        self.selection_flag:bool = False
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.FundamentalSelectionFunction)
        self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
        self.Schedule.On(self.DateRules.MonthStart(market), self.TimeRules.BeforeMarketClose(market, 0), self.Selection)

    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] = sorted([x for x in fundamental if x.HasFundamentalData and x.MarketCap and \
            ((x.SecurityReference.ExchangeId == "NYS") or (x.SecurityReference.ExchangeId == "NAS") or (x.SecurityReference.ExchangeId == "ASE")) and \
            x.FinancialStatements.IncomeStatement.TotalRevenue.ThreeMonths != 0
            and not np.isnan(x.FinancialStatements.IncomeStatement.TotalRevenue.ThreeMonths)], 
            key = lambda x: x.DollarVolume)[-self.fundamental_count:]

        revenue_price_ratio:Dict[Fundamental, float] = { stock : (stock.FinancialStatements.IncomeStatement.TotalRevenue.ThreeMonths / stock.AdjustedPrice) \
            for stock in selected }

        if len(revenue_price_ratio) < self.quantile:
            return Universe.Unchanged

        quantile:int = int(len(revenue_price_ratio) / self.quantile)
        sorted_by_ratio:List[Fundamental] = [x[0] for x in sorted(revenue_price_ratio.items(), key=lambda item: item[1])]
        long:List[Fundamental] = sorted_by_ratio[-quantile:]
        short:List[Fundamental] = sorted_by_ratio[:quantile]

        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, data: Slice) -> None:
        # rebalance monthly
        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"))

Leave a Reply

Discover more from Quant Buffet

Subscribe now to keep reading and get access to the full archive.

Continue reading