Back to Community
Dividend Harvest with Eventvestor

This strategy tries to hold each stock for as little time as possible to receive the dividend, and then jumps to the next. It buys a stock near the close for stocks that are 1 day ahead of their ex-dividend dates, and then sells it immediately on the open.

Because dividends aren't paid immediately, I tried to track the round trip returns though the record function. The payoff should be the value f the dividend minus the change in portfolio value while holding the stock. To estimate the gain, I record the portfolio value before I enter my trades, the number of shares and the dividend per share, and the portfolio value after I exit the trades.

I wasn't expecting this to be a perfect match to the returns, but my estimate is about 15x higher than the backtester returns. Can anyone explain where the difference is coming from or where I've made a mistake? I'm guessing that I'm somehow missing the record date and dividend along with it.

Also, one interesting thing is that it looks, to me, like you can see the spread of quant trading through the returns. It does really well up until the 2010's, and performance slows down as I would assume more and more people implemented quant strategies. If anyone has access to the premium data, it'd be really interesting to see how it performs over the last two year!!

Clone Algorithm
85
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
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
"""
This is a template algorithm on Quantopian for you to adapt and fill in.
"""
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import AverageDollarVolume
from quantopian.pipeline.filters.morningstar import Q1500US
from quantopian.pipeline.filters import StaticAssets
from quantopian.pipeline.factors.eventvestor import BusinessDaysUntilNextExDate
from quantopian.pipeline.data.eventvestor import DividendsByExDate
import pandas as pd

 
def initialize(context):
    set_slippage(slippage.FixedSlippage(spread=0))
    set_commission(commission.PerTrade(cost=0))
    """
    Called once at the start of the algorithm.
    """   
    # Rebalance every day, 1 hour after market open.
    context.close_pv = 0
    context.gains = 0
    context.spy = sid(8554)
    context.ta = False
    context.trades = pd.DataFrame(columns=['buy','amount','div','sell'])
        
    schedule_function(open_pos, date_rules.every_day(), time_rules.market_close(hours=1))
    schedule_function(pos_check, date_rules.every_day(), time_rules.market_close())
    
    schedule_function(close_pos, date_rules.every_day(), time_rules.market_open())
    
    schedule_function(trade_analysis, date_rules.every_day(), time_rules.market_open(hours=1))
     
    # Create our dynamic stock selector.
    attach_pipeline(make_pipeline(), 'my_pipeline')
         
def make_pipeline():
    """
    A function to create our dynamic stock selector (pipeline). Documentation on
    pipeline can be found here: https://www.quantopian.com/help#pipeline-title
    """
    
    # Base universe set to the Q500US
    base_universe = Q1500US()
    '''jnj = sid(4151)
    xom = sid(8347)
    base_universe = StaticAssets([jnj, xom])
    '''
    # Factor of yesterday's close price.
    div_ex_date = BusinessDaysUntilNextExDate()
    div_amount = DividendsByExDate.next_amount.latest
    #sector = 'fill in later with sector specific etfs'
    #longs = (div_ex_date == 1)
    
     
    pipe = Pipeline(
        screen = base_universe,
        columns = {
            'div_days': div_ex_date,
            'div_amt': div_amount,
    #        'longs': longs
        }
    )
    return pipe
 
def before_trading_start(context, data):

    context.output = pipeline_output('my_pipeline')
    '''
    Div days is the number of business days until the 'ex dividend date'. Must hold the security at close of the day before ex dividend date to receive the dividend. Ergo, we want to go long the stocks with div_days equal to 1. 
    '''
    context.longs = context.output[context.output['div_days'] == 1.0]

def open_pos(context, data):
    context.order_ids = {}
    context.close_pv = context.portfolio.portfolio_value
    y = not context.longs.empty
    '''open up our positions with a market hedge'''
    if y:
        for security in context.longs.index:
            if data.can_trade(security):
                order_target_percent(security, .05)
                order_percent(sid(8554), -.05)
        #turn on trade analysis
        context.ta = True
           
def pos_check(context, data):
    my_pos = context.portfolio.positions
    context.todays_dividends = 0
    while get_open_orders():
            pass
    for security in context.longs.index:
        div = context.longs['div_amt'].loc[security]
        shares = my_pos[security].amount
        context.todays_dividends += div*shares
        
    record(gains = context.gains, leverage = context.account.leverage)
        
def close_pos(context, data):
    '''close all our overnight positions'''
    if context.ta:
        my_pos = context.portfolio.positions
        for security in my_pos:
            if security not in context.longs:
                order_target(security, 0)
                  
def trade_analysis(context, data):
    if context.ta:
        '''wait to make sure we don't preemptively evaluate the trades'''
        while get_open_orders():
            pass
        
        '''take post-trade pv minus pre-trade pv to determine full cost of trade'''
        costs = context.portfolio.portfolio_value - context.close_pv
        context.gains += context.todays_dividends + costs
        print context.todays_dividends - costs
        #turn off trade analysis
        context.ta = False
        
        
        
        
        
        
        
        
There was a runtime error.
1 response

Figured out that I was double counting the dividend by tracking the change in overnight portfolio value without accounting for any dividends that may have been paid. That accounted for the bulk of the difference, but my new tracker is still showing about 2x the expected gains than the backtest returns.

This backtest is without the SPY hedge that is in the first backtest, keeps leverage under control, and starts with higher capital. Also, I stop all trading in April 2015 to allow for all dividends to be paid before the end of the backtest.

Clone Algorithm
85
Loading...
Total Returns
--
Alpha
--
Beta
--
Sharpe
--
Sortino
--
Max Drawdown
--
Benchmark Returns
--
Volatility
--
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
"""
This is a template algorithm on Quantopian for you to adapt and fill in.
"""
import numpy as np
from quantopian.algorithm import attach_pipeline, pipeline_output
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import AverageDollarVolume
from quantopian.pipeline.filters.morningstar import Q1500US
from quantopian.pipeline.filters import StaticAssets
from quantopian.pipeline.factors.eventvestor import BusinessDaysUntilNextExDate, BusinessDaysSincePreviousExDate
from quantopian.pipeline.data.eventvestor import DividendsByExDate
from quantopian.pipeline.classifiers.morningstar import Sector
import pandas as pd

 
def initialize(context):
    set_slippage(slippage.FixedSlippage(spread=0))
    set_commission(commission.PerTrade(cost=0))
    """
    Called once at the start of the algorithm.
    """   
    # Rebalance every day, 1 hour after market open.
    context.close_pv = context.portfolio.starting_cash
    context.weight = 1.0
    context.costs = 0
    context.open_cash = context.portfolio.starting_cash
    context.close_cash = context.portfolio.starting_cash
    context.dividends_assumed = 0
    context.dividends_received = 0
    context.todays_dividends = 0
    context.gains = 0
    context.id_change = 0
    context.spy = sid(8554)
    #context.ta = False
    context.trades = pd.DataFrame(columns=['buy','amount','div','sell'])
    context.sectors = {'-1': sid(8554),
                       '102': sid(19662),  # XLY Consumer Discrectionary SPDR Fund 102
                       '103': sid(19656),  # XLF Financial SPDR Fund 103
                       '311': sid(19658),  # XLK Technology SPDR Fund 311
                       '308': sid(40768),
                       '309': sid(19655),  # XLE Energy SPDR Fund 309
                       '206': sid(19661),  # XLV Health Care SPRD Fund 206
                       '310': sid(19657),  # XLI Industrial SPDR Fund 310
                       '205': sid(19659),  # XLP Consumer Staples SPDR Fund 205
                       '101': sid(19654),  # XLB Materials SPDR Fund 101
                       '104': sid(26669),  # XLRE Real Estate 104
                       '207': sid(19660)}
        
    schedule_function(open_pos, date_rules.every_day(), time_rules.market_close(hours=1))
    schedule_function(pos_check, date_rules.every_day(), time_rules.market_close())
    
    schedule_function(close_pos, date_rules.every_day(), time_rules.market_open())
    
    schedule_function(trade_analysis, date_rules.every_day(), time_rules.market_open(hours=1))
    context.stop_trading = False
    context.count = 0
    schedule_function(trade_stop, date_rules.month_end(), time_rules.market_close())
     
    # Create our dynamic stock selector.
    attach_pipeline(make_pipeline(), 'my_pipeline')


    
def make_pipeline():
    """
    A function to create our dynamic stock selector (pipeline). Documentation on
    pipeline can be found here: https://www.quantopian.com/help#pipeline-title
    """
    
    # Base universe set to the Q500US
    base_universe = Q1500US()
    '''jnj = sid(4151)
    xom = sid(8347)
    base_universe = StaticAssets([jnj, xom])
    '''
    # Factor of yesterday's close price.
    div_ex_date = BusinessDaysUntilNextExDate()
    last_div_date = BusinessDaysSincePreviousExDate()
    div_amount = DividendsByExDate.next_amount.latest
    sector = Sector(mask=base_universe)
    currency = DividendsByExDate.next_currency.latest
    #sector = 'fill in later with sector specific etfs'
    #longs = (div_ex_date == 1)
    
     
    pipe = Pipeline(
        screen = base_universe,
        columns = {
            'div_days': div_ex_date,
            'div_last': last_div_date,
            'div_amt': div_amount,
            'crncy': currency,
            'sector': sector
    #        'longs': longs
        }
    )
    return pipe
 
def before_trading_start(context, data):

    context.output = pipeline_output('my_pipeline')
    '''
    Div days is the number of business days until the 'ex dividend date'. Must hold the security at close of the day before ex dividend date to receive the dividend. Ergo, we want to go long the stocks with div_days equal to 1. 
    '''
    context.longs = context.output.dropna()
    if context.stop_trading:
        context.longs = pd.DataFrame()
    elif not context.longs.empty:
        context.longs = context.longs[context.longs['crncy'] == '$']
        context.longs['longs'] = (context.longs['div_days'] == 1.0)
        context.longs = context.longs[context.longs['longs']]
    return

def close_pos(context, data):
    '''close all our overnight positions'''
    context.open_cash = context.portfolio.cash
    my_pos = context.portfolio.positions
    for security in my_pos:
        if security not in context.longs:
            order_target(security, 0)
    return
                  
def trade_analysis(context, data):
    div_received = context.open_cash - context.close_cash
    context.pv_mid = context.portfolio.portfolio_value
    pv_change =  context.pv_mid - context.close_pv 
    costs = pv_change - div_received
    context.gains += context.todays_dividends + costs
    context.costs -= costs
    context.dividends_received += div_received
    #print context.todays_dividends + costs
        #turn off trade analysis
    return
        
        

def open_pos(context, data):
    context.order_ids = {}
    context.close_pv = context.portfolio.portfolio_value
    context.id_change += context.close_pv - context.pv_mid
    y = not context.longs.empty
    '''open up our positions with a market hedge'''
    if y:
        wght = min([context.weight / float(sum(data.can_trade(context.longs.index))),.05])
        for security in context.longs.index:
            if data.can_trade(security):
                #sctr = context.longs['sector'].loc[security]
                #sector_id = context.sectors[str(sctr)]
                order_target_percent(security, wght)
        #order_target_percent(sid(8554), -wght*sum(data.can_trade(context.longs.index)))
        #turn on trade analysis
        #context.ta = True
    return 
    
def pos_check(context, data):
    my_pos = context.portfolio.positions
    context.todays_dividends = 0
    context.close_cash = context.portfolio.cash
    for security in context.portfolio.positions:
        if security in context.longs.index:
            div = context.longs['div_amt'].loc[security]
            shares = my_pos[security].amount
            context.todays_dividends += (div*shares)
    context.dividends_assumed += context.todays_dividends
        
    record(gains = context.gains, costs = context.costs, assumed = context.dividends_assumed, received = context.dividends_received, intra_day = context.id_change)
    return
        
def trade_stop(context, data):
    context.count += 1
    if context.count == (8*12 + 4):
        context.stop_trading = True
        
        
        
        
        
        
There was a runtime error.