The net current asset value per share (NCAV/MV) is a key metric for value investors. NCAV/MV is the balance sheet current assets minus all the firm's (current and long-term) liabilities divided by the number of shares outstanding. This rule forms portfolios filled with stocks with market capitalization lower than the amount of cash plus inventories they own. A lot of these stocks are so cheap because the companies are in distress and will eventually go bankrupt. But the rest of the stocks bought with an extreme discount statistically gain more, and the overall portfolio return is highly positive.
This algorithm was derived from Quantpedia, where it was originally meant for an investment universe of the London Exchange. For users interested in seeing more strategies like this check out the Strategy Library.
The first step is the coarse and fine universe selection. Using coarse selection, we create an investment universe with stocks that have fundamental data and a price greater than $5.
self.filtered_coarse = [x.Symbol for x in coarse if (x.HasFundamentalData) and (float(x.AdjustedPrice) > 5)]
During fine universe selection, we take the stocks and calculate their net capital asset value per share. The stocks whose net capital asset value per share is above 1.5 are chosen for the portfolio.
def FineSelectionFunction(self, fine):
if self.yearly_rebalance:
fine = [x for x in fine if (float(x.FinancialStatements.BalanceSheet.CurrentAssets.Value) > 0)
and (float(x.EarningReports.BasicAverageShares.Value) > 0)
and (float(x.FinancialStatements.BalanceSheet.CurrentLiabilities.Value) > 0)
and (float(x.FinancialStatements.BalanceSheet.TotalNonCurrentLiabilitiesNetMinorityInterest.Value) > 0)
and (x.CompanyReference.IndustryTemplateCode!="B")
and (x.CompanyReference.IndustryTemplateCode!="I")]
for i in fine:
total_liabilities = float(i.FinancialStatements.BalanceSheet.CurrentLiabilities.Value)+float(i.FinancialStatements.BalanceSheet.TotalNonCurrentLiabilitiesNetMinorityInterest.Value)
i.ncav = (float(i.FinancialStatements.BalanceSheet.CurrentAssets.Value) - total_liabilities)/float(i.EarningReports.BasicAverageShares.Value)
self.filtered_fine = [i.Symbol for i in fine if (i.ncav > 1.5)]
return self.filtered_fine
else:
return []
In OnData(), we buy all the stocks in the filtered list. The portfolio is rebalanced every year at the start of July.
Miae Kim
Hello, I am studying NCAV too.
I found the same strategy but has a better return rate.
https://quantpedia.com/strategies/net-current-asset-value-effect/fine = [x for x in fine if x.EarningReports.BasicAverageShares.ThreeMonths > 0 and x.MarketCap != 0 and x.ValuationRatios.WorkingCapitalPerShare != 0] sorted_by_market_cap = sorted(fine, key = lambda x:x.MarketCap, reverse=True) top_by_market_cap = [x for x in sorted_by_market_cap[:self.coarse_count]] # NCAV/MV calc. self.long = [x.Symbol for x in top_by_market_cap if ((x.ValuationRatios.WorkingCapitalPerShare * x.EarningReports.BasicAverageShares.ThreeMonths) / x.MarketCap) > 1.5]
I also wrote code on my side but I have a way worse backtest result.
for i in fine: total_liabilities = i.FinancialStatements.BalanceSheet.TotalLiabilitiesAsReported.TwelveMonths current_assets = i.FinancialStatements.BalanceSheet.CurrentAssets.TwelveMonths i.ncav = (current_assets - total_liabilities)/float(i.MarketCap)
Miae Kim
Thanks for sharing, I modified your OnData and fineSelection. and showed better
Compounding Annual Return from 10% to 25%
1. Your Ondata has error.
Your `OnData()`, will not hold the filtered_fine when we select a new Symbol that is not invested in the current Portfolio. The below code will avoid this error.
def OnData(self, data):
if not self.yearly_rebalance: return
if self.filtered_fine:
stocks_invested = [x.Key for x in self.Portfolio]
for i in stocks_invested:
#liquidate the stocks not in the filtered NCAV/MV list
if i not in self.filtered_fine:
self.Liquidate(i)
#purchase the stocks in the list
elif i in self.filtered_fine:
self.SetHoldings(i, 1/len(self.filtered_fine))
self.yearly_rebalance = False
def OnData(self, data): if not self.yearly_rebalance: return stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested] for symbol in stocks_invested: if symbol not in self.filtered_fine: self.Liquidate(symbol) for symbol in self.filtered_fine: if self.Securities[symbol].Price != 0 and self.Securities[symbol].IsTradable: # Prevent error message. self.SetHoldings(symbol, 1 / len(self.filtered_fine)) self.yearly_rebalance = False
2. NCAV calc.
First, I am not sure `TotalNonCurrentLiabilitiesNetMinorityInterest` is the right value to select. This focus on NetMinorityInterest.
Second, this divides by BasicAverageSahres, which is not MarketCap.
total_liabilities = float(i.FinancialStatements.BalanceSheet.CurrentLiabilities.Value)+float(i.FinancialStatements.BalanceSheet.TotalNonCurrentLiabilitiesNetMinorityInterest.Value)
i.ncav = (float(i.FinancialStatements.BalanceSheet.CurrentAssets.Value) - total_liabilities)/float(i.EarningReports.BasicAverageShares.Value)
I used WorkingCaptial which is an asset - total liabilities.
self.filtered_fine = [x.Symbol for x in fine if ((x.BalanceSheet.WorkingCapital) / x.MarketCap) > 1.5]
Mislav Sagovac
, but I got an error:
Does anybody see what could be the cause ?
Louis Szeto
Hi Mislav
We cannot reproduce the issue, and we're able to run your code snippet without error. Can you retest please and see if the problem persists?
Best
Louis
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
Gurumeher Sawhney
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!