Value Effect in Unprofitable Firms
Log in to collectAcademic paper
The Valuation of Loss Firms: A Stock Market Perspective
Hannes Mohrschladt; Susanne Siedhoff
- DEUniversity of Münster
- ?University of Muenster - Finance Center
Strategy in a nutshell
This strategy targets U.S. stocks with negative earnings. Stocks are sorted into quintiles based on the revenue-to-price (RP) ratio. The portfolio goes long on the highest RP stocks and short on the lowest RP stocks among loss firms. Portfolios are value-weighted by market capitalization and rebalanced monthly to capture cross-sectional return differences among loss-making companies.
Economic rationale
Loss firms are often harder to value due to weak earnings, leading to mispricing. Revenue-to-price and book-to-market ratios serve as predictive signals, as investor biases, informed trading, and limits to arbitrage amplify return spreads in these firms.
Backtest performance
Annualised return14.84%
Volatility36.55%
Beta0.198
Sharpe ratio0.41
Sortino ratio0.12
Win rate54%
Full Python code
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"))