From 3ae02281dab935210ced8db1ab65b91e800a6156 Mon Sep 17 00:00:00 2001 From: fawce Date: Fri, 8 Feb 2013 17:40:41 -0500 Subject: [PATCH] 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. --- tests/test_perf_tracking.py | 34 +++++++++++++++++++++------------- zipline/finance/performance.py | 29 +++++++++++++++++++++-------- 2 files changed, 42 insertions(+), 21 deletions(-) diff --git a/tests/test_perf_tracking.py b/tests/test_perf_tracking.py index 6757df57..ebafcbfa 100644 --- a/tests/test_perf_tracking.py +++ b/tests/test_perf_tracking.py @@ -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 diff --git a/zipline/finance/performance.py b/zipline/finance/performance.py index fe523121..54fd82c7 100644 --- a/zipline/finance/performance.py +++ b/zipline/finance/performance.py @@ -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)