mirror of
https://github.com/wassname/catalyst.git
synced 2026-06-28 09:27:19 +08:00
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:
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user