Consensus Best Ideas Strategy Using 13F Filings from Active Mutual Fund Managers
Log in to collectAcademic paper
Randolph B. Cohen; Christopher Polk; Bernhard Silli
- ?Harvard Business School - Finance Unit
- London School of Economics and Political Science
- ?London School of Economics
Strategy in a nutshell
Develop a selection of active mutual fund managers and analyze their 13F filings to pinpoint each one's top stock picks. Focus investment efforts on those stocks that emerge as the consensus "best ideas" across the majority of these managers. This approach harnesses the collective wisdom and research of seasoned investment professionals to identify high-potential investment opportunities, aiming to leverage the aggregated expertise for superior investment performance.
Economic rationale
Current investment principles advocate for diversified portfolios, steering mutual fund managers away from concentration in a few assets, often due to the reluctance to deviate substantially from benchmark indices. Yet, the stocks deemed as managers' "best ideas" or high-conviction picks tend to receive a larger allocation within their portfolios, reflecting a deeper understanding and confidence in these selections by the managers. Identifying and tracking these "best ideas" is a practical approach, as these positions reveal where managers place their strongest bets and showcase a blend of expertise and strategic conviction in their investment choices.
Backtest performance
Full Python code
from AlgoLib import *
import numpy as np
from dateutil.relativedelta import relativedelta
class ManagerBestPicksFrom13FFilings(XXX):
def Initialize(self):
self.SetStartDate(2011, 1, 1) # Starting date
self.SetCash(100000) # Initial capital
self.UniverseSettings.Leverage = 5 # Leverage
self.UniverseSettings.Resolution = Resolution.Daily # Data resolution
self.AddUniverse(self.SelectStocksBasedOn13F)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.0
self.delay_months: int = 2 # Delay in months to consider 13F filings
self.update_period: datetime.date
self.stock_weights: dict[Symbol, float] = {}
self.manager_selections: dict[datetime, dict[str, int]] = self.LoadManagerSelections()
self.update_flag: bool = False
reference: Symbol = self.AddEquity('SPY', Resolution.Daily).Symbol
self.Schedule.On(self.DateRules.MonthStart(reference), self.TimeRules.AfterMarketOpen(reference), self.DecideToUpdate)
def OnSecuritiesChanged(self, changes: SecurityChanges):
for security in changes.AddedSecurities:
security.SetFeeModel(AdjustedFeeModel())
def SelectStocksBasedOn13F(self, fundamentals: List[Fundamental]):
if not self.update_flag:
return Universe.Unchanged
target_selection: dict[str, int] = None
lookback_date: datetime.date = self.Time.date() - relativedelta(months=self.delay_months + 1)
current_date: datetime.date = self.Time.date()
# Finding the most recent manager selections
for date, selections in self.manager_selections.items():
if lookback_date < date <= current_date:
target_selection = selections
if target_selection is None:
return []
# Creating the selected universe
selected_symbols = [f.Symbol for f in fundamentals if f.Symbol.Value in target_selection]
# Calculating weights
total_votes = sum(target_selection.values())
for symbol in selected_symbols:
self.stock_weights[symbol] = target_selection[symbol.Value] / total_votes
return selected_symbols
def OnData(self, slice: Slice):
if not self.update_flag:
return
self.update_flag = False
# Executing trades
targets = [PortfolioTarget(symbol, weight) for symbol, weight in self.stock_weights.items() if symbol in slice and slice[symbol]]
self.SetHoldings(targets, True)
self.stock_weights.clear()
def DecideToUpdate(self):
# Update every quarter
if self.Time.month % 3 == 2:
self.update_flag = True
def LoadManagerSelections(self) -> Dict[datetime, Dict[str, int]]:
selections: dict[datetime, dict[str, int]] = {}
data = self.Download('path_to_13F_filings_data.csv')
for line in data.split('\n')[1:]:
parts = line.split(',')
date = datetime.strptime(parts[0], "%Y-%m-%d").date()
if date not in selections:
selections[date] = {}
for ticker in parts[1:]:
if ticker not in selections[date]:
selections[date][ticker] = 0
selections[date][ticker] += 1
self.update_period = date # Last update
return selections
class AdjustedFeeModel(FeeModel):
def GetOrderFee(self, parameters: OrderFeeParameters) -> OrderFee:
fee = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))