mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-20 12:20:29 +08:00
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:
+21
-13
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user