Fixed bugs in the sequence of dividend payment calculations. Previously, we were using midnight of the current trading day in market close. That meant that we were "rewinding" the clock, and then checking the ex_date and pay_date. As a result, we were delaying payments by one day.

With this patch, on the close of markets we "fast forward" to midnight of the
next trading day and calculate the dividend payments. This patch assumes that
the dividend dates are all at midnight UTC.
This commit is contained in:
fawce
2013-02-15 22:52:38 -05:00
committed by Eddie Hebert
parent ccd3ab33e7
commit 3ae02281da
2 changed files with 42 additions and 21 deletions
+21 -13
View File
@@ -58,14 +58,19 @@ class TestDividendPerformance(unittest.TestCase):
dividend = factory.create_dividend(
1,
10.00,
events[0].dt,
# declared date, when the algorithm finds out about
# the dividend
events[1].dt,
# ex_date, when the algorithm is credited with the
# dividend
events[1].dt,
# pay date, when the algorithm receives the dividend.
events[2].dt
)
txn = factory.create_txn(1, 10.0, 100, events[0].dt)
events[0].TRANSACTION = txn
events.insert(1, dividend)
txn = factory.create_txn(1, 10.0, 100, self.dt+oneday)
events[2].TRANSACTION = txn
perf_tracker = perf.PerformanceTracker(self.trading_environment)
transformed_events = list(perf_tracker.transform(
((event.dt, [event]) for event in events))
@@ -94,10 +99,13 @@ class TestDividendPerformance(unittest.TestCase):
daily_returns = [event['daily_perf']['returns'] for event in results]
self.assertEqual(daily_returns, [0.0, 0.0, 0.10, 0.0, 0.0])
cash_flows = [event['daily_perf']['capital_used'] for event in results]
self.assertEqual(cash_flows, [0, -1000, 1000, 0, 0])
self.assertEqual(cash_flows, [-1000, 0, 1000, 0, 0])
cumulative_cash_flows = \
[event['cumulative_perf']['capital_used'] for event in results]
self.assertEqual(cumulative_cash_flows, [0, -1000, 0, 0, 0])
self.assertEqual(cumulative_cash_flows, [-1000, -1000, 0, 0, 0])
cash_pos = \
[event['cumulative_perf']['ending_cash'] for event in results]
self.assertEqual(cash_pos, [9000, 9000, 10000, 10000, 10000])
def test_post_ex_long_position_receives_no_dividend(self):
#post some trades in the market
@@ -165,8 +173,8 @@ class TestDividendPerformance(unittest.TestCase):
events[3].dt
)
buy_txn = factory.create_txn(1, 10.0, 100, events[1].dt)
events[1].TRANSACTION = buy_txn
buy_txn = factory.create_txn(1, 10.0, 100, events[0].dt)
events[0].TRANSACTION = buy_txn
sell_txn = factory.create_txn(1, 10.0, -100, events[2].dt)
events[2].TRANSACTION = sell_txn
events.insert(1, dividend)
@@ -192,10 +200,10 @@ class TestDividendPerformance(unittest.TestCase):
daily_returns = [event['daily_perf']['returns'] for event in results]
self.assertEqual(daily_returns, [0, 0, 0, 0.1, 0])
cash_flows = [event['daily_perf']['capital_used'] for event in results]
self.assertEqual(cash_flows, [0, -1000, 1000, 1000, 0])
self.assertEqual(cash_flows, [-1000, 0, 1000, 1000, 0])
cumulative_cash_flows = \
[event['cumulative_perf']['capital_used'] for event in results]
self.assertEqual(cumulative_cash_flows, [0, -1000, 0, 1000, 1000])
self.assertEqual(cumulative_cash_flows, [-1000, -1000, 0, 1000, 1000])
def test_buy_and_sell_before_ex(self):
#post some trades in the market
@@ -316,9 +324,9 @@ class TestDividendPerformance(unittest.TestCase):
events[2].dt
)
events.insert(1, dividend)
txn = factory.create_txn(1, 10.0, -100, self.dt+oneday)
events[2].TRANSACTION = txn
events[0].TRANSACTION = txn
events.insert(0, dividend)
perf_tracker = perf.PerformanceTracker(self.trading_environment)
transformed_events = list(perf_tracker.transform(
((event.dt, [event]) for event in events))
@@ -341,10 +349,10 @@ class TestDividendPerformance(unittest.TestCase):
daily_returns = [event['daily_perf']['returns'] for event in results]
self.assertEqual(daily_returns, [0.0, 0.0, -0.1, 0.0, 0.0])
cash_flows = [event['daily_perf']['capital_used'] for event in results]
self.assertEqual(cash_flows, [0, 1000, -1000, 0, 0])
self.assertEqual(cash_flows, [1000, 0, -1000, 0, 0])
cumulative_cash_flows = \
[event['cumulative_perf']['capital_used'] for event in results]
self.assertEqual(cumulative_cash_flows, [0, 1000, 0, 0, 0])
self.assertEqual(cumulative_cash_flows, [1000, 1000, 0, 0, 0])
def test_no_position_receives_no_dividend(self):
#post some trades in the market
+21 -8
View File
@@ -278,10 +278,6 @@ class PerformanceTracker(object):
# add the return results from today to the list of DailyReturn objects.
todays_date = self.market_close.replace(hour=0, minute=0, second=0)
self.cumulative_performance.update_dividends(todays_date)
self.todays_performance.update_dividends(todays_date)
todays_return_obj = risk.DailyReturn(
todays_date,
self.todays_performance.returns
@@ -326,6 +322,15 @@ Last successful date: %s" % self.market_open)
self.todays_performance.period_open = self.market_open
self.todays_performance.period_close = self.market_close
# The dividend calculation for the daily needs to be made
# after the rollover. midnight_between is the last midnight
# hour between the close of markets and the next open. To
# make sure midnight_between matches identically with
# dividend data dates, it is in UTC.
midnight_between = self.market_open.replace(hour=0, minute=0, second=0)
self.cumulative_performance.update_dividends(midnight_between)
self.todays_performance.update_dividends(midnight_between)
return daily_update
def handle_simulation_end(self):
@@ -368,17 +373,25 @@ class Position(object):
self.last_sale_date = 0.0
self.dividends = []
def update_dividends(self, dt):
def update_dividends(self, midnight_utc):
"""
midnight_utc is the 0 hour for the current (not yet open) trading day.
This method will be invoked at the end of the market
close handling, before the next market open.
"""
payment = 0.0
unpaid_dividends = []
for dividend in self.dividends:
if dt == dividend.ex_date:
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
dividend.payment = self.amount * dividend.net_amount
if dividend.net_amount:
dividend.payment = self.amount * dividend.net_amount
else:
dividend.payment = self.amount * dividend.gross_amount
if dt == dividend.pay_date:
if midnight_utc == dividend.pay_date:
# if it is the payment date, include this
# dividend's actual payment (calculated on
# ex_date)