
The strategy shorts NYSE, AMEX, and NASDAQ stocks after CEO CNBC interviews, holds for ten days, and hedges with S&P 500 futures to capture abnormal returns from market reactions.
ASSET CLASS: futures, stocks | REGION: United States | FREQUENCY:
Daily | MARKET: equities | KEYWORD: CEO Interviews, Effect
I. STRATEGY IN A NUTSHELL
The strategy shorts NYSE, AMEX, and NASDAQ stocks whose CEOs were interviewed on CNBC the previous day. Positions are held for 10 days, equally weighted, and hedged with long S&P 500 futures to isolate abnormal returns from CEO media appearances.
II. ECONOMIC RATIONALE
Research shows individual investors tend to buy “newsworthy” stocks after media coverage, while informed traders or insiders sell into this demand, creating short-term overpricing opportunities.
III. SOURCE PAPER
CEO Interviews on CNBC [Click to Open PDF]
Young Han Kim and Felix Meschke.Sungkyunkwan University.University of Kansas – Finance Area.
<Abstract>
We investigate whether media attention systematically affects stock prices through the trading of individual investors by exploiting the substantial discrepancy between perceived and actual information content of 6,937 CEO interviews on CNBC. The average cumulative abnormal stock return over the [-2, 0] trading day window is 1.62%, followed by reversion of 1.08% over the following ten trading days. The magnitude of price response is positively correlated with the viewership as well as the language tone of the CEO. Individual investors and short sellers are key drivers of the pricing pattern. We document evidence of information cascade coming from CNBC interviews.


IV. BACKTEST PERFORMANCE
| Annualised Return | 32.08% |
| Volatility | 13.28% |
| Beta | -0.108 |
| Sharpe Ratio | 2.11 |
| Sortino Ratio | -0.331 |
| Maximum Drawdown | N/A |
| Win Rate | 47% |
V. FULL PYTHON CODE
from AlgorithmImports import *
from typing import Dict, List
import json
#endregion
class CEOInterviewsEffect(QCAlgorithm):
def Initialize(self) -> None:
self.SetStartDate(2010, 1, 1) # Interviews data starts in December 2006
self.SetCash(100_000)
self.UniverseSettings.Leverage = 10
self.UniverseSettings.Resolution = Resolution.Minute
self.AddUniverse(self.FundamentalSelectionFunction)
self.Settings.MinimumOrderMarginPortfolioPercentage = 0.0
self.settings.daily_precise_end_time = False
self.holding_period: int = 10 # Days
self.selection_flag: bool = False
self.interviews_data: Dict[date, List[str]] = {}
self.selected_securities: Dict[str, Symbol] = {}
self.opened_short_positions_period: Dict[Symbol, int] = {}
self.universe_tickers: Set(str) = set()
url: str = 'data.quantpedia.com/backtesting_data/index/sp500_ceo_interviews.json'
response: str = self.Download(url)
ceo_interviews: List[Dict[str, str]] = json.loads(response)
for interview_data in ceo_interviews:
date: datetime.date = datetime.strptime(interview_data['date'], '%d.%m.%Y').date()
self.interviews_data[date] = []
for ticker in interview_data['tickers']:
self.universe_tickers.add(ticker)
self.interviews_data[date].append(ticker)
self.market: Symbol = self.AddEquity('SPY', Resolution.Minute).Symbol
self.Schedule.On(
self.DateRules.MonthStart(self.market),
self.TimeRules.BeforeMarketClose(self.market),
self.Selection)
self.Schedule.On(
self.DateRules.EveryDay(self.market),
self.TimeRules.BeforeMarketClose(self.market, 1),
self.ManageTrade)
def OnSecuritiesChanged(self, changes: SecurityChanges) -> None:
for security in changes.AddedSecurities:
security.SetFeeModel(CustomFeeModel())
def FundamentalSelectionFunction(self, fundamental: List[Fundamental]) -> List[Symbol]:
# Rebalance monthly
if not self.selection_flag:
return Universe.Unchanged
self.selection_flag = False
self.selected_securities = {
f.Symbol.Value: f.Symbol for f in fundamental
if f.Symbol.Value in self.universe_tickers
}
return list(self.selected_securities.values())
def ManageTrade(self) -> None:
# Liquidate opened symbols
symbols_to_remove: List[Symbol] = []
rebalance_flag: bool = False
for symbol in self.opened_short_positions_period:
holding_period_remaining: int = self.opened_short_positions_period[symbol]
if holding_period_remaining == 0:
symbols_to_remove.append(symbol)
else:
self.opened_short_positions_period[symbol] -= 1
for symbol in symbols_to_remove:
rebalance_flag = True
self.Liquidate(symbol)
del self.opened_short_positions_period[symbol]
# Storing symbol of stocks, which CEOs had interview today
short_symbols: List[Symbol] = []
curr_date: datetime.date = self.Time.date()
if curr_date in self.interviews_data:
tickers: List[str] = self.interviews_data[curr_date]
for ticker in tickers:
if ticker not in self.selected_securities:
continue
rebalance_flag = True
symbol: Symbol = self.selected_securities[ticker]
self.opened_short_positions_period[symbol] = self.holding_period
if rebalance_flag:
opened_shorts: int = len(self.opened_short_positions_period)
if opened_shorts != 0:
# Rebalance whole trade selection alongside the hedge
for symbol in self.opened_short_positions_period:
price: float = self.Securities[symbol].Price
if price != 0:
self.SetHoldings(symbol, -1 / opened_shorts)
# Hedge with S&P500 future
self.SetHoldings(self.market, 1)
else:
# Liquidate hedge
self.Liquidate(self.market)
def Selection(self) -> None:
self.selection_flag = True
class CustomFeeModel(FeeModel):
def GetOrderFee(self, parameters: OrderFeeParameters) -> OrderFee:
fee: float = parameters.Security.Price * parameters.Order.AbsoluteQuantity * 0.00005
return OrderFee(CashAmount(fee, "USD"))