ENH: Allow for stock dividends, and in particular, Google's

recent 2 for 1 stock split, where 1 class C share was distributed
for each share of class A held.

Now a dividend can specify a sid and ratio of stock that will be paid
to owners of the original security.  If the ratio is 2.0, then for every
existing share, two shares will be paid.
This commit is contained in:
Richard Frank
2014-03-28 17:29:13 -04:00
parent 0e4f3f957a
commit f21bbe58fc
4 changed files with 109 additions and 12 deletions
+50
View File
@@ -348,6 +348,56 @@ class TestDividendPerformance(unittest.TestCase):
[event['cumulative_perf']['ending_cash'] for event in results]
self.assertEqual(cash_pos, [9000, 9000, 10000, 10000, 10000])
def test_long_position_receives_stock_dividend(self):
with trading.TradingEnvironment():
# post some trades in the market
events = []
for sid in (1, 2):
events.extend(
factory.create_trade_history(
sid,
[10, 10, 10, 10, 10],
[100, 100, 100, 100, 100],
oneday,
self.sim_params)
)
dividend = factory.create_stock_dividend(
1,
payment_sid=2,
ratio=2,
# declared date, when the algorithm finds out about
# the dividend
declared_date=events[1].dt,
# ex_date, when the algorithm is credited with the
# dividend
ex_date=events[1].dt,
# pay date, when the algorithm receives the dividend.
pay_date=events[2].dt
)
txn = create_txn(events[0], 10.0, 100)
events.insert(0, txn)
events.insert(1, dividend)
results = calculate_results(self, events)
self.assertEqual(len(results), 5)
cumulative_returns = \
[event['cumulative_perf']['returns'] for event in results]
self.assertEqual(cumulative_returns, [0.0, 0.0, 0.2, 0.2, 0.2])
daily_returns = [event['daily_perf']['returns']
for event in results]
self.assertEqual(daily_returns, [0.0, 0.0, 0.2, 0.0, 0.0])
cash_flows = [event['daily_perf']['capital_used']
for event in results]
self.assertEqual(cash_flows, [-1000, 0, 0, 0, 0])
cumulative_cash_flows = \
[event['cumulative_perf']['capital_used'] for event in results]
self.assertEqual(cumulative_cash_flows, [-1000] * 5)
cash_pos = \
[event['cumulative_perf']['ending_cash'] for event in results]
self.assertEqual(cash_pos, [9000] * 5)
def test_post_ex_long_position_receives_no_dividend(self):
# post some trades in the market
events = factory.create_trade_history(
+13 -2
View File
@@ -75,7 +75,7 @@ import logbook
import numpy as np
import pandas as pd
from collections import OrderedDict, defaultdict
from collections import Counter, OrderedDict, defaultdict
from six import iteritems, itervalues
@@ -170,8 +170,19 @@ class PerformancePeriod(object):
payment has been disbursed.
"""
cash_payments = 0.0
stock_payments = Counter() # maps sid to number of shares paid
for sid, pos in iteritems(self.positions):
cash_payments += pos.update_dividends(todays_date)
cash_payment, stock_payment = pos.update_dividends(todays_date)
cash_payments += cash_payment
stock_payments.update(stock_payment)
for stock, payment in iteritems(stock_payments):
position = self.positions[stock]
position.amount += payment
self.ensure_position_index(stock)
self._position_amounts[stock] = position.amount
self._position_last_sale_prices[stock] = \
position.last_sale_price
# credit our cash balance with the dividend payments, or
# if we are short, debit our cash balance with the
+25 -6
View File
@@ -36,6 +36,8 @@ from __future__ import division
import logbook
import math
from collections import Counter
log = logbook.Logger('Performance')
@@ -57,28 +59,45 @@ class Position(object):
This method will be invoked at the end of the market
close handling, before the next market open.
"""
payment = 0.0
cash_payment = 0.0
stock_payment = Counter() # maps sid to number of shares paid
unpaid_dividends = []
for dividend in self.dividends:
if midnight_utc == dividend.ex_date:
# if we own shares at midnight of the div_ex date
# we are entitled to the dividend.
dividend.amount_on_ex_date = self.amount
if dividend.net_amount:
dividend.payment = self.amount * dividend.net_amount
# stock dividend
if dividend.payment_sid:
# ie, 33.333
raw_share_count = self.amount * float(dividend.ratio)
# ie, 33
dividend.stock_payment = math.floor(raw_share_count)
else:
dividend.payment = self.amount * dividend.gross_amount
dividend.stock_payment = None
# cash dividend
if dividend.net_amount:
dividend.cash_payment = self.amount * dividend.net_amount
elif dividend.gross_amount:
dividend.cash_payment = self.amount * dividend.gross_amount
else:
dividend.cash_payment = None
if midnight_utc == dividend.pay_date:
# if it is the payment date, include this
# dividend's actual payment (calculated on
# ex_date)
payment += dividend.payment
if dividend.stock_payment:
stock_payment[dividend.payment_sid] += \
dividend.stock_payment
if dividend.cash_payment:
cash_payment += dividend.cash_payment
else:
unpaid_dividends.append(dividend)
self.dividends = unpaid_dividends
return payment
return cash_payment, stock_payment
def add_dividend(self, dividend):
self.dividends.append(dividend)
+21 -4
View File
@@ -138,10 +138,11 @@ def create_dividend(sid, payment, declared_date, ex_date, pay_date):
'sid': sid,
'gross_amount': payment,
'net_amount': payment,
'dt': declared_date.replace(hour=0, minute=0, second=0, microsecond=0),
'ex_date': ex_date.replace(hour=0, minute=0, second=0, microsecond=0),
'pay_date': pay_date.replace(hour=0, minute=0, second=0,
microsecond=0),
'payment_sid': None,
'ratio': None,
'dt': pd.tslib.normalize_date(declared_date),
'ex_date': pd.tslib.normalize_date(ex_date),
'pay_date': pd.tslib.normalize_date(pay_date),
'type': DATASOURCE_TYPE.DIVIDEND,
'source_id': 'MockDividendSource'
})
@@ -149,6 +150,22 @@ def create_dividend(sid, payment, declared_date, ex_date, pay_date):
return div
def create_stock_dividend(sid, payment_sid, ratio, declared_date,
ex_date, pay_date):
return Event({
'sid': sid,
'payment_sid': payment_sid,
'ratio': ratio,
'net_amount': None,
'gross_amount': None,
'dt': pd.tslib.normalize_date(declared_date),
'ex_date': pd.tslib.normalize_date(ex_date),
'pay_date': pd.tslib.normalize_date(pay_date),
'type': DATASOURCE_TYPE.DIVIDEND,
'source_id': 'MockDividendSource'
})
def create_split(sid, ratio, date):
return Event({
'sid': sid,