The strategy invests in firms with diverse management teams by going long on the top quintile and short on the bottom quintile, based on diversity scores calculated from executive data.

I. STRATEGY IN A NUTSHELL

This strategy trades NYSE, AMEX, and NASDAQ stocks by going long on firms with the most diverse management teams and short on firms with the most homogeneous teams. Stocks are equally weighted, held for one year, and the portfolio is rebalanced annually.

II. ECONOMIC RATIONALE

The anomaly arises from a combination of quality premiums and mispricing. Diverse management may signal higher firm quality, while firms with low analyst coverage are more prone to mispricing, allowing the strategy to capture alpha.

III. SOURCE PAPER

Diversity Investing [Click to Open PDF]

Alberto Manconi ,A. Emanuele Rizzo and Oliver Spalt.Bocconi University – Department of Finance; Centre for Economic Policy Research (CEPR).Nova School of Business and Economics.University of Mannheim – Business School; European Corporate Governance Institute (ECGI)

<Abstract>

Top management team diversity matters for stock returns. We develop a new text–based measure of team diversity and apply it to a sample of over 40,000 top executives in U.S. firms from 2001 to 2014. Buying firms with diverse teams and selling firms with homogenous teams — a strategy we call “diversity investing” — outperforms leading asset pricing anomalies over our sample period on a value-weighted basis. We examine a range of possible explanations and find strong evidence for the view that analysts and investors have downward-biased return expectations on firms with diverse teams, consistent with a mispricing explanation for diversity returns.

IV. BACKTEST PERFORMANCE

Annualised Return6.55%
Volatility7.38%
Beta0.769
Sharpe Ratio0.89
Sortino Ratio0.236
Maximum DrawdownN/A
Win Rate77%

V. FULL PYTHON CODE

from AlgorithmImports import *
from io import StringIO
from pandas.core.frame import dataframe
from typing import List, Dict
import pandas as pd
# endregion
class ManagementDiversityStrategy(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2000, 1, 1)
        self.SetCash(100000)
        
        self.market: Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
        self.leverage: int = 3
        self.selection_month: int = 6
        self.short_allocation: float = -.0
        self.weights: Dict[Symbol, float] = {}
        # source: https://www.fair360.com/top-50-list/2023/
        top_diversity_firms: str = self.Download('data.quantpedia.com/backtesting_data/economic/top50_diversity_firms.csv')
        self.top_diversity_firms_df: dataframe = pd.read_csv(StringIO(top_diversity_firms), delimiter=';')
        self.selection_flag: bool = False
        self.settings.daily_precise_end_time = False
        self.Settings.MinimumOrderMarginPortfolioPercentage = 0.
        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.FundamentalSelectionFunction)
        self.Schedule.On(self.DateRules.MonthStart(self.market), self.TimeRules.AfterMarketOpen(self.market), 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]:
        # selection in the beginning of June
        if not self.selection_flag:
            return Universe.Unchanged
        selected: Dict[str, Fundamental] = {
            x.Symbol.Value: x for x in fundamental if x.HasFundamentalData and x.MarketCap != 0
        }
        long: List[Fundamental] = []
        if str(self.Time.year) in list(self.top_diversity_firms_df.columns):
            long = [selected[x] for x in self.top_diversity_firms_df[str(self.Time.year)].values if x in selected]
        else:
            self.Liquidate()
        if len(long) != 0:
            # calculate weights based on values
            self.weights[self.market] = self.short_allocation
            sum_long: float = sum([x.MarketCap for x in long])
            for stock in long:
                self.weights[stock.Symbol] = stock.MarketCap / sum_long
        return list(self.weights.keys())
    def OnData(self, data: Slice) -> None:
        # yearly rebalance
        if not self.selection_flag:
            return
        self.selection_flag = False
        targets: List[PortfolioTarget] = [PortfolioTarget(symbol, weight) for symbol, weight in self.weights.items() if symbol in data and data[symbol]]      
        self.SetHoldings(targets, True)
            
        self.weights.clear()
    def Selection(self) -> None:
        if self.Time.month == self.selection_month:
            self.selection_flag = True
    
# Custom fee model
class CustomFeeModel(FeeModel):
    def GetOrderFee(self, parameters: OrderFeeParameters) -> OrderFee:
        fee: float = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
        return OrderFee(CashAmount(fee, "USD"))

VI. Backtest Performance

Leave a Reply

Discover more from Quant Buffet

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

Continue reading