Back to Community
Recording dividend income

Hi everyone!

I want to compare some strategies based on dividend income. I know dividends are added to portfolio cash on the payout date and I wonder if someone can recommend (or share) some code that records dividends?

Thanks!

4 responses

Hi all,

Here are the two solutions I came up with. One attempts to count the cash that is accumulating. The other attempts to track based on the dividends per share metric. They produce significantly different results, but I do understand why. The cash accumulating method is probably more accurate, and it's definitely easier. It also shows "bigger" results, which is the only "downside". (I prefer to underestimate vs overestimate).

In the end, my use case is to compare strategies using the "dividend returns" metric and i'll be calculating it the same way for all strategies so, it will be an apples to apples comparison.

Ill attach. both algos here. I'd be interested to see what people think, and if anyone has some helpful ideas or better methods.

Thanks,
Brett

Method 1: Since dividends are paid out as cash additions to the account, I just count the accumulating cash before I rebalance once a year.

Clone Algorithm
1
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
from quantopian.algorithm import order_optimal_portfolio
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 SimpleMovingAverage
from quantopian.pipeline.filters import QTradableStocksUS, Q500US
import quantopian.optimize as opt
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.data.morningstar import Fundamentals as msfun

def set_algo_params(context):
    #########################################################################
    #
    # Algo parameters to tweak
    #
    context.target_stock_qty = 25
    
    #
    # End of algo parmaters  
    #
    #########################################################################

def initialize(context):
    
    set_algo_params(context)
    
    # Schedule our rebalance function to run at the start of
    # each year, shortly after the market opens.
    schedule_function(
        my_rebalance,
        date_rules.month_start(), 
        time_rules.market_open(minutes = 65)
    )
    
    # Record tracking variables at the end of each day.
    context.dividend_income = 0.0
    context.accumulated_cash = -1
    schedule_function(
        record_vars,
        date_rules.every_day(),
        time_rules.market_close(),
    )    

    # Create our pipeline and attach it to our algorithm. 
    attach_pipeline(make_pipeline(context), 'my_pipeline')
    
    # Second pipeline needed because stocks are screened out by the time 
    # dividends are paid.
    #attach_pipeline(make_dps_pipeline(context), 'dps_pipeline')

def s_and_p_universe(context):
    
    # Universe factors
    close_price = USEquityPricing.close.latest
    market_cap = msfun.market_cap.latest

    # Universe Filters
    top_500_market_cap = market_cap.top(500)
    has_pricing_data = close_price.notnull()

    s_and_p_universe = QTradableStocksUS() & top_500_market_cap & has_pricing_data
    
    return s_and_p_universe

def make_pipeline(context):
    
    filtered_universe = s_and_p_universe(context)

    # Algo factors    
    dy = msfun.trailing_dividend_yield.latest    

    # Algo filters
    is_tradeable = dy.top(context.target_stock_qty, mask=filtered_universe)
    
    return Pipeline(
        columns={
            'dy': dy
        },
        screen=is_tradeable
    )

def make_dps_pipeline(context):
    
    dps = msfun.dividend_per_share_earnings_reports.latest
    
    filtered_universe = s_and_p_universe(context) \
        & dps.notnull() & dps.notnan() & dps.isfinite()
    
    return Pipeline(
        columns={
            'dps': dps
        },
        screen=filtered_universe
    )

def compute_target_weights(context, data):
    """
    Compute ordering weights.
    """

    # Initialize empty target weights dictionary.
    # This will map securities to their target weight.
    weights = {}

    # If there are securities in our longs,
    # compute even target weights for each security.
    if context.longs:
        long_weight = 1.0 / len(context.longs)
    else:
        return weights

    # Exit positions in our portfolio if they are not
    # in our longs or shorts lists.
    for security in context.portfolio.positions:
        if security not in context.longs and data.can_trade(security):
            weights[security] = 0

    for security in context.longs:
        weights[security] = long_weight
        
    return weights

def before_trading_start(context, data):
    """
    Get pipeline results.
    """
    
    # Gets our pipeline output every day.
    context.pipe_results = pipeline_output('my_pipeline')
    #context.dps_pipe = pipeline_output('dps_pipeline')

    # Go long in securities and check if they can be traded.
    context.longs = []

    for sec in context.pipe_results.index:
        if data.can_trade(sec):
            context.longs.append(sec)


def my_rebalance(context, data):
    """
    Rebalance yearly
    """
    
    if get_datetime().month not in [1] or get_open_orders(): return 
    
    # Before rebalancing, calculate the dividend income.
    if context.accumulated_cash == -1:
        context.accumulated_cash = 0.0
    else:
        context.accumulated_cash = context.portfolio.cash
            
    context.dividend_income += context.accumulated_cash
        
    log.info('dividend income =  ${:.2f}'.format(context.dividend_income))
    log.info('dividend returns = {:.2f}%'.format(
            ((context.dividend_income / context.portfolio.starting_cash) * 100.0)))
    log.info('ordering {} stocks'.format(len(context.longs)))

    # Calculate target weights to rebalance
    target_weights = compute_target_weights(context, data)

    # If we have target weights, rebalance our portfolio
    if target_weights:
        order_optimal_portfolio(
            objective=opt.TargetWeights(target_weights),
            constraints=[],
        )
        
def record_vars(context, data):
    """
    Plot variables at the end of each day.
    """
    record(cash = context.portfolio.cash)
    record(dividend_income = context.dividend_income)
There was a runtime error.

Method 2: Setup a second pipeline and use the dividend_per_share_earnings_reports factor.

Clone Algorithm
0
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
from quantopian.algorithm import order_optimal_portfolio
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 SimpleMovingAverage
from quantopian.pipeline.filters import QTradableStocksUS, Q500US
import quantopian.optimize as opt
from quantopian.pipeline.data import morningstar
from quantopian.pipeline.data.morningstar import Fundamentals as msfun

def set_algo_params(context):
    #########################################################################
    #
    # Algo parameters to tweak
    #
    context.target_stock_qty = 25
    
    #
    # End of algo parmaters  
    #
    #########################################################################

def initialize(context):
    
    set_algo_params(context)
    
    # Schedule our rebalance function to run at the start of
    # each year, shortly after the market opens.
    schedule_function(
        my_rebalance,
        date_rules.month_start(), 
        time_rules.market_open(minutes = 65)
    )
    
    # Record tracking variables at the end of each day.
    context.dividend_income = 0.0
    schedule_function(
        record_vars,
        date_rules.every_day(),
        time_rules.market_close(),
    )    

    # Create our pipeline and attach it to our algorithm. 
    attach_pipeline(make_pipeline(context), 'my_pipeline')
    
    # Second pipeline needed because stocks are screened out by the time 
    # dividends are paid.
    attach_pipeline(make_dps_pipeline(context), 'dps_pipeline')

def s_and_p_universe(context):
    
    # Universe factors
    close_price = USEquityPricing.close.latest
    market_cap = msfun.market_cap.latest

    # Universe Filters
    top_500_market_cap = market_cap.top(500)
    has_pricing_data = close_price.notnull()

    s_and_p_universe = QTradableStocksUS() & top_500_market_cap & has_pricing_data
    
    return s_and_p_universe

def make_pipeline(context):
    
    filtered_universe = s_and_p_universe(context)

    # Algo factors    
    dy = msfun.trailing_dividend_yield.latest    

    # Algo filters
    is_tradeable = dy.top(context.target_stock_qty, mask=filtered_universe)
    
    return Pipeline(
        columns={
            'dy': dy
        },
        screen=is_tradeable
    )

def make_dps_pipeline(context):
    
    dps = msfun.dividend_per_share_earnings_reports.latest
    
    filtered_universe = s_and_p_universe(context) \
        & dps.notnull() & dps.notnan() & dps.isfinite()
    
    return Pipeline(
        columns={
            'dps': dps
        },
        screen=filtered_universe
    )

def compute_target_weights(context, data):
    """
    Compute ordering weights.
    """

    # Initialize empty target weights dictionary.
    # This will map securities to their target weight.
    weights = {}

    # If there are securities in our longs,
    # compute even target weights for each security.
    if context.longs:
        long_weight = 1.0 / len(context.longs)
    else:
        return weights

    # Exit positions in our portfolio if they are not
    # in our longs or shorts lists.
    for security in context.portfolio.positions:
        if security not in context.longs and data.can_trade(security):
            weights[security] = 0

    for security in context.longs:
        weights[security] = long_weight
        
    return weights

def before_trading_start(context, data):
    """
    Get pipeline results.
    """
    
    # Gets our pipeline output every day.
    context.pipe_results = pipeline_output('my_pipeline')
    context.dps_pipe = pipeline_output('dps_pipeline')

    # Go long in securities and check if they can be traded.
    context.longs = []

    for sec in context.pipe_results.index:
        if data.can_trade(sec):
            context.longs.append(sec)


def my_rebalance(context, data):
    """
    Rebalance yearly
    """
    
    if get_datetime().month not in [1] or get_open_orders(): return 
    
    # Before rebalancing, calculate the dividend income.
    # For each stock, get the yearly dividend paid, times that by the num shares,
    # then add to dividend income.
    for sec in context.portfolio.positions:
        if sec in context.dps_pipe.index:
            dps = context.dps_pipe.get_value(sec, 'dps') 
            shares = context.portfolio.positions[sec].amount
            context.dividend_income += dps*shares
        
    log.info('dividend income =  ${:.2f}'.format(context.dividend_income))
    log.info('dividend returns = {:.2f}%'.format(
            ((context.dividend_income / context.portfolio.starting_cash) * 100.0)))
    log.info('ordering {} stocks'.format(len(context.longs)))

    # Calculate target weights to rebalance
    target_weights = compute_target_weights(context, data)

    # If we have target weights, rebalance our portfolio
    if target_weights:
        order_optimal_portfolio(
            objective=opt.TargetWeights(target_weights),
            constraints=[],
        )
        
def record_vars(context, data):
    """
    Plot variables at the end of each day.
    """
    record(cash = context.portfolio.cash)
    record(dividend_income = context.dividend_income)
There was a runtime error.

Well, now of course, i want to test an algo that trades once a month and both my solutions no longer work. Anyone have any ideas?