Quant BuffetRelax, Not Over Thinking

Brand Value Asset Pricing Factor

Log in to collect

Academic paper

Popularity: A Bridge between Classical and Behavioral Finance

AuthorsRoger G. Ibbotson; Thomas M. Idzorek; Paul D. Kaplan; James X. Xiong

Institute
  • Capital University
  • Yale University
  • Zebra Technologies (United States)
  • ?Yale School of Management
  • ?Zebra Capital Management, LLC
  • Institut Mines-Télécom Business School
  • ?Morningstar Investment Management
  • CADomtar (Canada)
  • ?Morningstar Canada

Strategy in a nutshell

The strategy invests in US stocks listed in Interbrand’s brand value database, buying the 15 lowest-brand-value stocks each month. Portfolios are equally weighted and rebalanced annually. For companies with multiple brands (e.g., Volkswagen, Audi, BMW, Porsche), brand values are summed. The approach targets undervalued brands to capture potential returns from low-recognition or underappreciated stocks.

Economic rationale

Lower-brand-value stocks tend to earn a “popularity premium,” while high-brand-value stocks deliver lower returns. Behavioral biases and investor preferences drive this mispricing. The strategy focuses on financially stable multinational companies from Interbrand’s top 100 list, meeting criteria such as generating over 30% of revenue internationally, operating in at least three continents, and being listed on regulated exchanges.

Backtest performance

Annualised return11.95%
Volatility16.73%
Beta0.958
Sharpe ratio0.71
Sortino ratio0.563
Win rate81%

Full Python code

from AlgorithmImports import *
import pandas as pd
from io import StringIO
#endregion
class InsiderTradingCombinedwithShareRepurchases(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2000, 1, 1)
self.SetCash(100000)
self.long: List[Symbol] = []
self.brands_by_year_n: Dict[int, List[str]] = {}
market: Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol

# Data source: https://interbrand.com/best-global-brands/
csv_string_file = self.Download(f'data.quantpedia.com/backtesting_data/economic/brand_value_fifteen_lowest.csv')
# take values of each column, expect first one in each row and does not take first row as a header
separated_file = pd.read_csv(StringIO(csv_string_file), sep=';', header=None)

for row in separated_file.itertuples():
    brand_tickers = []
    for ticker in row[2:]:
        brand_tickers.append(ticker)
    self.brands_by_year_n[int(row[1])] = brand_tickers
self.selection_flag: boll = False
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.FundamentalSelectionFunction)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
self.Schedule.On(self.DateRules.MonthStart(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())
def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
if not self.selection_flag:
    return Universe.Unchanged

year_key: int = self.Time.year - 1
if year_key in self.brands_by_year_n:
    self.long = [f.Symbol for f in fundamental if f.Symbol.Value in self.brands_by_year_n[year_key]]
return self.long
def OnData(self, slice: Slice) -> None:
if not self.selection_flag:
    return
self.selection_flag = False

# rebalance
portfolio:List[PortfolioTarget] = [PortfolioTarget(symbol, 1. / len(self.long)) for symbol in self.long if symbol in slice and slice[symbol]]
self.SetHoldings(portfolio, True)
self.long.clear()
    
def Selection(self) -> None:
self.selection_flag = True
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters):
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))