Back to Community
CEO Change + Sentiment Signal

CEO changes usually have articles written about them. So why not use the CEO change dataset in combination with a news sentiment dataset to determine whether a CEO change is good or bad, and invest accordingly?

I've attached a notebook where I find matches between the EventVestor CEO Changes dataset and the Accern Alphaone News Sentiment dataset, and do some basic analysis to see if there's any rank correlation between the combined data and price movements. The result is a weak negative rank correlation between news sentiment shortly following a CEO change and relative price movement. This might be a good enough signal to contribute to a long-short trading strategy.

Pipeline and the tools available in Research make it easy to try to find signals of your own to use in a trading strategy. Try cloning this notebook to test out similar techniques on some of the other datasets we have available.

Loading notebook preview...

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 Quantopian. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. No information contained herein should be regarded as a suggestion to engage in or refrain from any investment-related course of action as none of Quantopian nor any of its affiliates is undertaking to provide investment advice, act as an adviser to any plan or entity subject to the Employee Retirement Income Security Act of 1974, as amended, individual retirement account or individual retirement annuity, or give advice in a fiduciary capacity with respect to the materials presented herein. If you are an individual retirement or other investor, contact your financial advisor or other fiduciary unrelated to Quantopian about whether any given investment idea, strategy, product or service described herein may be appropriate for your circumstances. All investments involve risk, including loss of principal. Quantopian 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.

5 responses

Here's a long-short algorithm that tries to make use of the signal uncovered in the above notebook. It doesn't work terribly well, but maybe someone else can find a better way to use this idea.

Clone Algorithm
Total Returns
Max Drawdown
Benchmark Returns
Returns 1 Month 3 Month 6 Month 12 Month
Alpha 1 Month 3 Month 6 Month 12 Month
Beta 1 Month 3 Month 6 Month 12 Month
Sharpe 1 Month 3 Month 6 Month 12 Month
Sortino 1 Month 3 Month 6 Month 12 Month
Volatility 1 Month 3 Month 6 Month 12 Month
Max Drawdown 1 Month 3 Month 6 Month 12 Month
import numpy as np
import pandas as pd
import datetime
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from import alphaone
from import CEOChangeAnnouncements
from quantopian.pipeline.factors import AverageDollarVolume

def initialize(context):
    schedule_function(rebalance, date_rule=date_rules.every_day(),
    schedule_function(record_vars, date_rule=date_rules.every_day(),
    # Initialize an empty DataFrame to store article events for a specified number of days.
    context.targets = pd.DataFrame(columns=['sentiment', 'dollar_volume', 'since_article'])
    context.adv_days = 30      # window_length of AverageDollarVolume to determine how much we can buy
    context.min_positions = 10 # minimum number of positions at leverage 1
    context.linger_days = 12   # number of trading days to keep using an article as a signal
    attach_pipeline(my_pipeline(context), 'my_pipeline')
def my_pipeline(context):
    sentiment = alphaone.article_sentiment.latest
    announcement_date = CEOChangeAnnouncements.asof_date.latest
    dollar_volume = AverageDollarVolume(window_length=context.adv_days)
    return Pipeline(
            'sentiment': sentiment,
            'announcement_date': announcement_date,
            'dollar_volume': dollar_volume
def before_trading_start(context, data): = pipeline_output('my_pipeline')
    # Construct a set of the companies that had a CEO change in the last 5 days.
    context.ceo_data =['announcement_date']
    context.recent_set = \
                    np.arange((get_datetime() - datetime.timedelta(days=4)).date(),
                              (get_datetime() + datetime.timedelta(days=1)).date(),
    # Create a DataFrame containing the companies with a recent CEO change and nonzero sentiment.
    context.sentiment_data =['sentiment'][['sentiment'] >= -0.2]
        context.ceo_sentiment = pd.DataFrame(context.sentiment_data[context.recent_set].dropna())
        context.ceo_sentiment = pd.DataFrame(columns=context.sentiment_data.columns)
    # Keep track of the appropriate sentiments within the last 20 days, dropping those that are too old.
    context.targets['since_article'] += 1
    context.targets = context.targets[context.targets['since_article'] < context.linger_days]
    context.ceo_sentiment['since_article'] = 1
    context.targets = pd.concat([context.targets, context.ceo_sentiment])
    # We regard article sentiment as a signal in the opposite direction.
    if len(context.targets) > 0:
        context.weights = -context.targets['sentiment']
        context.weights = context.weights.groupby(context.weights.index).sum()#.apply(extreme)
        context.weights = pd.Series()
# Given a series, returns the value farthest from 0.
def extreme(s):
    return s[np.argmax(s.abs().reset_index(drop=True))]
def rebalance(context, data):
    orders = {}
    coeff = 1
    counter = 0
    # Keeps track of resources left to spend on long and short.
    to_spend = {1 : context.portfolio.portfolio_value / 2,
                -1 : context.portfolio.portfolio_value / 2}
    while to_spend[1] > 0 or to_spend[-1] > 0:
            stock = context.weights.index[counter * coeff]
        if data.can_trade(stock):
            if stock not in orders:
                # Amount we can spend on a stock depends on its dollar volume, and shouldn't exceed limits.
                dollar_amt = min(['dollar_volume'][stock] / (5 * context.adv_days),
                                 context.portfolio.portfolio_value / context.min_positions,
                price = data.current(stock, 'price')
                count = round(dollar_amt / price) * coeff
                to_spend[coeff] -= abs(count) * price
                if to_spend[coeff] < 0:
                    to_spend[coeff] = 0
                if stock in context.portfolio.positions:
                    count -= context.portfolio.positions[stock].amount
                orders[stock] = count
                del orders[stock]
        if coeff == 1:
            counter += 1
        coeff *= -1
    for stock in orders:
        order(stock, orders[stock])
    for stock in context.portfolio.positions:
        if data.can_trade(stock) and stock not in orders:
            order_target_percent(stock, 0)
def record_vars(context, data):    
There was a runtime error.

Great post! I think this is a great theoretical trade, but it does not pan out. This supports my point that going to great lengths in coming up with sophisticated (and often logical) algos does not pay in terms of effort, risk and finally, money.

I totally disagree! I think this kind of exploration and thinking can lead to true creativity and innovation. Yes, most ideas go nowhere, and there's way more missed than hits, but I don't agree with your conclusion from the evidence available.

"this kind of exploration and thinking can lead to true creativity "

Actually, the opposite is true - true creativity is required for this kind of exploration and thinking.

Again, I have to disagree. My understanding of the creative process is it requires exploratory learning or preparation as a first step, as in this example model: