The investment universe includes only specific stocks from the London Exchange, excluding multiple share classes, foreign, lightly regulated, and financial sector companies. Annually in July, an equal-weighted, one-year Buy-and-hold NCAV/MV portfolio is formed, including stocks with an NCAV/MV over 1.5.

STRATEGY IN A NUTSHELL

The investment pool includes stocks from the London Exchange, omitting firms with multiple share classes, foreign entities, lightly regulated market participants, and financial sector businesses. Each July, a portfolio is assembled, selecting stocks boasting an NCAV/MV ratio above 1.5 for inclusion. This portfolio, adopting a Buy-and-hold strategy, remains unchanged for a year. Within this portfolio, all stocks are assigned equal weight.

ECONOMIC RATIONALE

The NCAV rule creates portfolios consisting of stocks whose market capitalization is below their cash plus inventory value. Many of these stocks are priced low because they belong to companies facing financial difficulties, with a significant number on the brink of bankruptcy. However, a portion of these stocks, acquired at substantial discounts, tend to outperform, leading to statistically significant gains. Consequently, despite the inherent risks, the cumulative return of the portfolio tends to be overwhelmingly positive.

SOURCE PAPER

Testing Benjamin Graham’s Net Current Asset Value Strategy in London [Click to Open PDF]

Ying Xiao, School of Business, University of Salford; Glen C. Arnold, School of Business, University of Salford

<Abstract>

It is widely recognized that value strategies – those that invest in stocks with low market values relative to measures of their fundamentals (e.g. low prices relative to earnings, dividends, book assets and cash flows) – tend to show higher returns. In this paper we focus on the early value metric devised and employed by Benjamin Graham – net current asset value to market value (NCAV/MV) – to see if it is still useful in the modern context. Examining stocks listed on the London Stock Exchange for the period 1981 to 2005 we observe that those with an NCAV/MV greater than 1.5 display significantly positive market-adjusted returns (annualized return up to 19.7% per year) over five holding years. We allow for the possibility that the phenomenon being observed is due to the additional return experienced on smaller companies (the “size effect”) and still find an NCAV/MV premium. The profitability of this NCAV/MV strategy in the UK cannot be explained using Capital Asset Pricing Model (CAPM). Further, Fama and French’s three-factor model (FF3M) can not explain the abnormal return of the NCAV/MV strategy. These premiums might be due to irrational pricing.

BACKTEST PERFORMANCE

Annualised Return31.19%
Volatility31.59%
Beta0.835
Sharpe Ratio0.93
Sortino Ratio1.20
Maximum Drawdown-74.8%
Win Rate68%

FULL PYTHON CODE

from AlgoLib import *
import numpy as np

class NetCurrentAssetValueStrategy(XXX):

    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        
        self.UniverseSettings.Leverage = 3
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.SelectStocks)
        self.Settings.MinimumOrderMarginPortfolioPercentage = 0.0

        # Stock Selection Criteria
        self.max_stocks = 3000
        self.market = 'usa'
        self.exclude_fin_sector = 103
        self.ncav_ratio_min = 1.5
        
        self.target_stocks = []

        self.rebalance_period = 7
        self.should_rebalance = True

        self.spy = self.AddEquity('SPY', Resolution.Daily).Symbol
        self.Schedule.On(self.DateRules.MonthStart(self.spy), 
                         self.TimeRules.AfterMarketOpen(self.spy), 
                         self.RebalancePortfolio)

    def SelectStocks(self, fundamental_data: List[Fundamental]) -> List[Symbol]:
        if not self.should_rebalance:
            return Universe.Unchanged

        qualified_stocks = [f for f in fundamental_data if f.HasFundamentalData and
                            f.Market == self.market and
                            f.AssetClassification.MorningstarSectorCode != self.exclude_fin_sector and
                            not np.isnan(f.MarketCap) and f.MarketCap > 0 and
                            not np.isnan(f.ValuationRatios.WorkingCapitalPerShare) and
                            f.ValuationRatios.WorkingCapitalPerShare > 0]

        qualified_stocks = sorted(qualified_stocks, 
                                  key=lambda x: x.MarketCap)[:self.max_stocks]

        self.target_stocks = [stock.Symbol for stock in qualified_stocks if
                              ((stock.ValuationRatios.WorkingCapitalPerShare * 
                                stock.EarningReports.BasicAverageShares.TwelveMonths) /
                               stock.MarketCap) > self.ncav_ratio_min]

        return self.target_stocks
    
    def OnData(self, data: Slice):
        if not self.should_rebalance:
            return
        
        self.should_rebalance = False

        if len(self.target_stocks) > 0:
            weight = 1 / len(self.target_stocks)
            for stock in self.target_stocks:
                if data.ContainsKey(stock):
                    self.SetHoldings(stock, weight)
            self.target_stocks.clear()
    
    def RebalancePortfolio(self):
        if self.Time.month == self.rebalance_period:
            self.should_rebalance = True

    def OnSecuritiesChanged(self, changes: SecurityChanges):
        for security in changes.AddedSecurities:
            security.SetFeeModel(CustomFeeModel())

class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters: OrderFeeParameters) -> OrderFee:
        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