mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-06 05:14:38 +08:00
Adds dividends to performance tracking.
Algorithm returns and the risk calculations that depend on them now include cash dividends. This commit does _not_ provide an API for user algorithms to access dividends. PerformanceTracker expects the dividend data to arrive as events, similar to the way that Trades arrive. Dividends are expected to have adjusted payment amounts that are inline with adjusted trades. PerformanceTracker maintains state of all the unpaid dividends in the position objects held in PerformancePeriod. Dividend objects contain all the relevant dates (declared, ex, payment) as well as net and gross amounts. Dividends are removed from the list as they are paid. Cash flow is not incremented until the payment day. This creates the possibility of a dividend being owed but not paid or realized before the end of a test. For example, a dividend with an ex_date of today may have a pay date 2 weeks in the future. Right now the algorithm does not receive any credit for unpaid dividends. Tests cover buying/selling around the ex_date and payment_date, and checking that the performance calculated is as expected.
This commit is contained in:
+377
-56
@@ -17,7 +17,6 @@ import collections
|
||||
|
||||
import unittest
|
||||
from nose_parameterized import parameterized
|
||||
import random
|
||||
import datetime
|
||||
import pytz
|
||||
import itertools
|
||||
@@ -28,55 +27,377 @@ import zipline.finance.performance as perf
|
||||
from zipline.utils.protocol_utils import ndict
|
||||
|
||||
from zipline.gens.composites import date_sorted_sources
|
||||
|
||||
from zipline.finance.trading import TradingEnvironment
|
||||
from zipline.utils.factory import create_random_trading_environment
|
||||
|
||||
|
||||
class TestPerformance(unittest.TestCase):
|
||||
onesec = datetime.timedelta(seconds=1)
|
||||
oneday = datetime.timedelta(days=1)
|
||||
tradingday = datetime.timedelta(hours=6, minutes=30)
|
||||
|
||||
|
||||
class TestDividendPerformance(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.onesec = datetime.timedelta(seconds=1)
|
||||
self.oneday = datetime.timedelta(days=1)
|
||||
self.tradingday = datetime.timedelta(hours=6, minutes=30)
|
||||
|
||||
self.trading_environment, self.dt, self.end_dt = self.create_env()
|
||||
self.trading_environment, self.dt, self.end_dt = \
|
||||
create_random_trading_environment()
|
||||
|
||||
def create_env(self, start_dt=None):
|
||||
benchmark_returns, treasury_curves = \
|
||||
factory.load_market_data()
|
||||
self.trading_environment.capital_base = 10e3
|
||||
|
||||
if not start_dt:
|
||||
for n in range(100):
|
||||
|
||||
random_index = random.randint(
|
||||
0,
|
||||
len(treasury_curves)
|
||||
)
|
||||
|
||||
start_dt = treasury_curves.keys()[random_index]
|
||||
end_dt = start_dt + datetime.timedelta(days=365)
|
||||
|
||||
now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
|
||||
|
||||
if end_dt <= now:
|
||||
break
|
||||
else:
|
||||
end_dt = start_dt + datetime.timedelta(days=365)
|
||||
now = datetime.datetime.utcnow().replace(tzinfo=pytz.utc)
|
||||
|
||||
assert end_dt <= now, """
|
||||
failed to find a date suitable daterange after 100 attempts. please double
|
||||
check treasury and benchmark data in findb, and re-run the test."""
|
||||
assert start_dt < end_dt, "start_dt must be less than end_dt"
|
||||
|
||||
trading_environment = TradingEnvironment(
|
||||
benchmark_returns,
|
||||
treasury_curves,
|
||||
period_start=start_dt,
|
||||
period_end=end_dt
|
||||
def test_long_position_receives_dividend(self):
|
||||
#post some trades in the market
|
||||
events = factory.create_trade_history(
|
||||
1,
|
||||
[10, 10, 10, 10, 10],
|
||||
[100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.trading_environment
|
||||
)
|
||||
|
||||
return trading_environment, start_dt, end_dt
|
||||
dividend = factory.create_dividend(
|
||||
1,
|
||||
10.00,
|
||||
events[0].dt,
|
||||
events[1].dt,
|
||||
events[2].dt
|
||||
)
|
||||
|
||||
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))
|
||||
)
|
||||
|
||||
#flatten the list of events
|
||||
results = []
|
||||
for te in transformed_events:
|
||||
for event in te[1]:
|
||||
for message in event.perf_messages:
|
||||
results.append(message)
|
||||
|
||||
perf_messages, risk = perf_tracker.handle_simulation_end()
|
||||
results.append(perf_messages[0])
|
||||
|
||||
self.assertEqual(results[0]['daily_perf']['period_open'], events[0].dt)
|
||||
self.assertEqual(
|
||||
results[-1]['daily_perf']['period_open'],
|
||||
events[-1].dt
|
||||
)
|
||||
|
||||
self.assertEqual(len(results), 5)
|
||||
cumulative_returns = \
|
||||
[event['cumulative_perf']['returns'] for event in results]
|
||||
self.assertEqual(cumulative_returns, [0.0, 0.0, 0.1, 0.1, 0.1])
|
||||
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])
|
||||
cumulative_cash_flows = \
|
||||
[event['cumulative_perf']['capital_used'] for event in results]
|
||||
self.assertEqual(cumulative_cash_flows, [0, -1000, 0, 0, 0])
|
||||
|
||||
def test_post_ex_long_position_receives_no_dividend(self):
|
||||
#post some trades in the market
|
||||
events = factory.create_trade_history(
|
||||
1,
|
||||
[10, 10, 10, 10, 10],
|
||||
[100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.trading_environment
|
||||
)
|
||||
|
||||
dividend = factory.create_dividend(
|
||||
1,
|
||||
10.00,
|
||||
events[0].dt,
|
||||
events[1].dt,
|
||||
events[2].dt
|
||||
)
|
||||
|
||||
events.insert(1, dividend)
|
||||
txn = factory.create_txn(1, 10.0, 100, events[3].dt)
|
||||
events[3].TRANSACTION = txn
|
||||
perf_tracker = perf.PerformanceTracker(self.trading_environment)
|
||||
transformed_events = list(perf_tracker.transform(
|
||||
((event.dt, [event]) for event in events))
|
||||
)
|
||||
|
||||
#flatten the list of events
|
||||
results = []
|
||||
for te in transformed_events:
|
||||
for event in te[1]:
|
||||
for message in event.perf_messages:
|
||||
results.append(message)
|
||||
|
||||
perf_messages, risk = perf_tracker.handle_simulation_end()
|
||||
results.append(perf_messages[0])
|
||||
|
||||
self.assertEqual(len(results), 5)
|
||||
cumulative_returns = \
|
||||
[event['cumulative_perf']['returns'] for event in results]
|
||||
self.assertEqual(cumulative_returns, [0, 0, 0, 0, 0])
|
||||
daily_returns = [event['daily_perf']['returns'] for event in results]
|
||||
self.assertEqual(daily_returns, [0, 0, 0, 0, 0])
|
||||
cash_flows = [event['daily_perf']['capital_used'] for event in results]
|
||||
self.assertEqual(cash_flows, [0, 0, -1000, 0, 0])
|
||||
cumulative_cash_flows = \
|
||||
[event['cumulative_perf']['capital_used'] for event in results]
|
||||
self.assertEqual(cumulative_cash_flows, [0, 0, -1000, -1000, -1000])
|
||||
|
||||
def test_selling_before_dividend_payment_still_gets_paid(self):
|
||||
#post some trades in the market
|
||||
events = factory.create_trade_history(
|
||||
1,
|
||||
[10, 10, 10, 10, 10],
|
||||
[100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.trading_environment
|
||||
)
|
||||
|
||||
dividend = factory.create_dividend(
|
||||
1,
|
||||
10.00,
|
||||
events[0].dt,
|
||||
events[1].dt,
|
||||
events[3].dt
|
||||
)
|
||||
|
||||
buy_txn = factory.create_txn(1, 10.0, 100, events[1].dt)
|
||||
events[1].TRANSACTION = buy_txn
|
||||
sell_txn = factory.create_txn(1, 10.0, -100, events[2].dt)
|
||||
events[2].TRANSACTION = sell_txn
|
||||
events.insert(1, dividend)
|
||||
perf_tracker = perf.PerformanceTracker(self.trading_environment)
|
||||
transformed_events = list(perf_tracker.transform(
|
||||
((event.dt, [event]) for event in events))
|
||||
)
|
||||
|
||||
#flatten the list of events
|
||||
results = []
|
||||
for te in transformed_events:
|
||||
for event in te[1]:
|
||||
for message in event.perf_messages:
|
||||
results.append(message)
|
||||
|
||||
perf_messages, risk = perf_tracker.handle_simulation_end()
|
||||
results.append(perf_messages[0])
|
||||
|
||||
self.assertEqual(len(results), 5)
|
||||
cumulative_returns = \
|
||||
[event['cumulative_perf']['returns'] for event in results]
|
||||
self.assertEqual(cumulative_returns, [0, 0, 0, 0.1, 0.1])
|
||||
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])
|
||||
cumulative_cash_flows = \
|
||||
[event['cumulative_perf']['capital_used'] for event in results]
|
||||
self.assertEqual(cumulative_cash_flows, [0, -1000, 0, 1000, 1000])
|
||||
|
||||
def test_buy_and_sell_before_ex(self):
|
||||
#post some trades in the market
|
||||
events = factory.create_trade_history(
|
||||
1,
|
||||
[10, 10, 10, 10, 10, 10],
|
||||
[100, 100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.trading_environment
|
||||
)
|
||||
|
||||
dividend = factory.create_dividend(
|
||||
1,
|
||||
10.00,
|
||||
events[3].dt,
|
||||
events[4].dt,
|
||||
events[5].dt
|
||||
)
|
||||
|
||||
buy_txn = factory.create_txn(1, 10.0, 100, events[1].dt)
|
||||
events[1].TRANSACTION = buy_txn
|
||||
sell_txn = factory.create_txn(1, 10.0, -100, events[2].dt)
|
||||
events[2].TRANSACTION = sell_txn
|
||||
events.insert(1, dividend)
|
||||
perf_tracker = perf.PerformanceTracker(self.trading_environment)
|
||||
transformed_events = list(perf_tracker.transform(
|
||||
((event.dt, [event]) for event in events))
|
||||
)
|
||||
|
||||
#flatten the list of events
|
||||
results = []
|
||||
for te in transformed_events:
|
||||
for event in te[1]:
|
||||
for message in event.perf_messages:
|
||||
results.append(message)
|
||||
|
||||
perf_messages, risk = perf_tracker.handle_simulation_end()
|
||||
results.append(perf_messages[0])
|
||||
|
||||
self.assertEqual(len(results), 6)
|
||||
cumulative_returns = \
|
||||
[event['cumulative_perf']['returns'] for event in results]
|
||||
self.assertEqual(cumulative_returns, [0, 0, 0, 0, 0, 0])
|
||||
daily_returns = [event['daily_perf']['returns'] for event in results]
|
||||
self.assertEqual(daily_returns, [0, 0, 0, 0, 0, 0])
|
||||
cash_flows = [event['daily_perf']['capital_used'] for event in results]
|
||||
self.assertEqual(cash_flows, [0, -1000, 1000, 0, 0, 0])
|
||||
cumulative_cash_flows = \
|
||||
[event['cumulative_perf']['capital_used'] for event in results]
|
||||
self.assertEqual(cumulative_cash_flows, [0, -1000, 0, 0, 0, 0])
|
||||
|
||||
def test_ending_before_pay_date(self):
|
||||
#post some trades in the market
|
||||
events = factory.create_trade_history(
|
||||
1,
|
||||
[10, 10, 10, 10, 10],
|
||||
[100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.trading_environment
|
||||
)
|
||||
|
||||
dividend = factory.create_dividend(
|
||||
1,
|
||||
10.00,
|
||||
events[0].dt,
|
||||
events[1].dt,
|
||||
events[-1].dt + 10*oneday
|
||||
)
|
||||
|
||||
buy_txn = factory.create_txn(1, 10.0, 100, events[1].dt)
|
||||
events[1].TRANSACTION = buy_txn
|
||||
events.insert(1, dividend)
|
||||
perf_tracker = perf.PerformanceTracker(self.trading_environment)
|
||||
transformed_events = list(perf_tracker.transform(
|
||||
((event.dt, [event]) for event in events))
|
||||
)
|
||||
|
||||
#flatten the list of events
|
||||
results = []
|
||||
for te in transformed_events:
|
||||
for event in te[1]:
|
||||
for message in event.perf_messages:
|
||||
results.append(message)
|
||||
|
||||
perf_messages, risk = perf_tracker.handle_simulation_end()
|
||||
results.append(perf_messages[0])
|
||||
|
||||
self.assertEqual(len(results), 5)
|
||||
cumulative_returns = \
|
||||
[event['cumulative_perf']['returns'] for event in results]
|
||||
self.assertEqual(cumulative_returns, [0, 0, 0, 0.0, 0.0])
|
||||
daily_returns = [event['daily_perf']['returns'] for event in results]
|
||||
self.assertEqual(daily_returns, [0, 0, 0, 0, 0])
|
||||
cash_flows = [event['daily_perf']['capital_used'] for event in results]
|
||||
self.assertEqual(cash_flows, [0, -1000, 0, 0, 0])
|
||||
cumulative_cash_flows = \
|
||||
[event['cumulative_perf']['capital_used'] for event in results]
|
||||
self.assertEqual(
|
||||
cumulative_cash_flows,
|
||||
[0, -1000, -1000, -1000, -1000]
|
||||
)
|
||||
|
||||
def test_short_position_receives_no_dividend(self):
|
||||
#post some trades in the market
|
||||
events = factory.create_trade_history(
|
||||
1,
|
||||
[10, 10, 10, 10, 10],
|
||||
[100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.trading_environment
|
||||
)
|
||||
|
||||
dividend = factory.create_dividend(
|
||||
1,
|
||||
10.00,
|
||||
events[0].dt,
|
||||
events[1].dt,
|
||||
events[2].dt
|
||||
)
|
||||
|
||||
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))
|
||||
)
|
||||
|
||||
#flatten the list of events
|
||||
results = []
|
||||
for te in transformed_events:
|
||||
for event in te[1]:
|
||||
for message in event.perf_messages:
|
||||
results.append(message)
|
||||
|
||||
perf_messages, risk = perf_tracker.handle_simulation_end()
|
||||
results.append(perf_messages[0])
|
||||
|
||||
self.assertEqual(len(results), 5)
|
||||
cumulative_returns = \
|
||||
[event['cumulative_perf']['returns'] for event in results]
|
||||
self.assertEqual(cumulative_returns, [0.0, 0.0, 0.0, 0.0, 0.0])
|
||||
daily_returns = [event['daily_perf']['returns'] for event in results]
|
||||
self.assertEqual(daily_returns, [0.0, 0.0, 0.0, 0.0, 0.0])
|
||||
cash_flows = [event['daily_perf']['capital_used'] for event in results]
|
||||
self.assertEqual(cash_flows, [0, 1000, 0, 0, 0])
|
||||
cumulative_cash_flows = \
|
||||
[event['cumulative_perf']['capital_used'] for event in results]
|
||||
self.assertEqual(cumulative_cash_flows, [0, 1000, 1000, 1000, 1000])
|
||||
|
||||
def test_no_position_receives_no_dividend(self):
|
||||
#post some trades in the market
|
||||
events = factory.create_trade_history(
|
||||
1,
|
||||
[10, 10, 10, 10, 10],
|
||||
[100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.trading_environment
|
||||
)
|
||||
|
||||
dividend = factory.create_dividend(
|
||||
1,
|
||||
10.00,
|
||||
events[0].dt,
|
||||
events[1].dt,
|
||||
events[2].dt
|
||||
)
|
||||
|
||||
events.insert(1, dividend)
|
||||
perf_tracker = perf.PerformanceTracker(self.trading_environment)
|
||||
transformed_events = list(perf_tracker.transform(
|
||||
((event.dt, [event]) for event in events))
|
||||
)
|
||||
|
||||
#flatten the list of events
|
||||
results = []
|
||||
for te in transformed_events:
|
||||
for event in te[1]:
|
||||
for message in event.perf_messages:
|
||||
results.append(message)
|
||||
|
||||
perf_messages, risk = perf_tracker.handle_simulation_end()
|
||||
results.append(perf_messages[0])
|
||||
|
||||
self.assertEqual(len(results), 5)
|
||||
cumulative_returns = \
|
||||
[event['cumulative_perf']['returns'] for event in results]
|
||||
self.assertEqual(cumulative_returns, [0.0, 0.0, 0.0, 0.0, 0.0])
|
||||
daily_returns = [event['daily_perf']['returns'] for event in results]
|
||||
self.assertEqual(daily_returns, [0.0, 0.0, 0.0, 0.0, 0.0])
|
||||
cash_flows = [event['daily_perf']['capital_used'] for event in results]
|
||||
self.assertEqual(cash_flows, [0, 0, 0, 0, 0])
|
||||
cumulative_cash_flows = \
|
||||
[event['cumulative_perf']['capital_used'] for event in results]
|
||||
self.assertEqual(cumulative_cash_flows, [0, 0, 0, 0, 0])
|
||||
|
||||
|
||||
class TestPositionPerformance(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.trading_environment, self.dt, self.end_dt = \
|
||||
create_random_trading_environment()
|
||||
|
||||
def test_long_position(self):
|
||||
"""
|
||||
@@ -88,11 +409,11 @@ check treasury and benchmark data in findb, and re-run the test."""
|
||||
1,
|
||||
[10, 10, 10, 11],
|
||||
[100, 100, 100, 100],
|
||||
self.onesec,
|
||||
onesec,
|
||||
self.trading_environment
|
||||
)
|
||||
|
||||
txn = factory.create_txn(1, 10.0, 100, self.dt + self.onesec)
|
||||
txn = factory.create_txn(1, 10.0, 100, self.dt + onesec)
|
||||
pp = perf.PerformancePeriod(1000.0)
|
||||
|
||||
pp.execute_transaction(txn)
|
||||
@@ -102,7 +423,7 @@ check treasury and benchmark data in findb, and re-run the test."""
|
||||
pp.calculate_performance()
|
||||
|
||||
self.assertEqual(
|
||||
pp.period_capital_used,
|
||||
pp.period_cash_flow,
|
||||
-1 * txn.price * txn.amount,
|
||||
"capital used should be equal to the opposite of the transaction \
|
||||
cost of sole txn in test"
|
||||
@@ -157,13 +478,13 @@ single short-sale transaction"""
|
||||
1,
|
||||
[10, 10, 10, 11, 10, 9],
|
||||
[100, 100, 100, 100, 100, 100],
|
||||
self.onesec,
|
||||
onesec,
|
||||
self.trading_environment
|
||||
)
|
||||
|
||||
trades_1 = trades[:-2]
|
||||
|
||||
txn = factory.create_txn(1, 10.0, -100, self.dt + self.onesec)
|
||||
txn = factory.create_txn(1, 10.0, -100, self.dt + onesec)
|
||||
pp = perf.PerformancePeriod(1000.0)
|
||||
|
||||
pp.execute_transaction(txn)
|
||||
@@ -173,7 +494,7 @@ single short-sale transaction"""
|
||||
pp.calculate_performance()
|
||||
|
||||
self.assertEqual(
|
||||
pp.period_capital_used,
|
||||
pp.period_cash_flow,
|
||||
-1 * txn.price * txn.amount,
|
||||
"capital used should be equal to the opposite of the transaction\
|
||||
cost of sole txn in test"
|
||||
@@ -230,7 +551,7 @@ single short-sale transaction"""
|
||||
pp.calculate_performance()
|
||||
|
||||
self.assertEqual(
|
||||
pp.period_capital_used,
|
||||
pp.period_cash_flow,
|
||||
0,
|
||||
"capital used should be zero, there were no transactions in \
|
||||
performance period"
|
||||
@@ -292,7 +613,7 @@ single short-sale transaction"""
|
||||
ppTotal.calculate_performance()
|
||||
|
||||
self.assertEqual(
|
||||
ppTotal.period_capital_used,
|
||||
ppTotal.period_cash_flow,
|
||||
-1 * txn.price * txn.amount,
|
||||
"capital used should be equal to the opposite of the transaction \
|
||||
cost of sole txn in test"
|
||||
@@ -347,7 +668,7 @@ trade after cover"""
|
||||
1,
|
||||
[10, 10, 10, 11, 9, 8, 7, 8, 9, 10],
|
||||
[100, 100, 100, 100, 100, 100, 100, 100, 100, 100],
|
||||
self.onesec,
|
||||
onesec,
|
||||
self.trading_environment
|
||||
)
|
||||
|
||||
@@ -355,10 +676,10 @@ trade after cover"""
|
||||
1,
|
||||
10.0,
|
||||
-100,
|
||||
self.dt + self.onesec
|
||||
self.dt + onesec
|
||||
)
|
||||
|
||||
cover_txn = factory.create_txn(1, 7.0, 100, self.dt + self.onesec * 6)
|
||||
cover_txn = factory.create_txn(1, 7.0, 100, self.dt + onesec * 6)
|
||||
pp = perf.PerformancePeriod(1000.0)
|
||||
|
||||
pp.execute_transaction(short_txn)
|
||||
@@ -373,7 +694,7 @@ trade after cover"""
|
||||
cover_txn_cost = cover_txn.price * cover_txn.amount
|
||||
|
||||
self.assertEqual(
|
||||
pp.period_capital_used,
|
||||
pp.period_cash_flow,
|
||||
-1 * short_txn_cost - cover_txn_cost,
|
||||
"capital used should be equal to the net transaction costs"
|
||||
)
|
||||
@@ -426,7 +747,7 @@ shares in position"
|
||||
1,
|
||||
[10, 11, 11, 12],
|
||||
[100, 100, 100, 100],
|
||||
self.onesec,
|
||||
onesec,
|
||||
self.trading_environment
|
||||
)
|
||||
|
||||
@@ -434,7 +755,7 @@ shares in position"
|
||||
1,
|
||||
[10, 11, 11, 12],
|
||||
[100, 100, 100, 100],
|
||||
self.onesec,
|
||||
onesec,
|
||||
self.trading_environment
|
||||
)
|
||||
|
||||
@@ -470,13 +791,13 @@ shares in position"
|
||||
1,
|
||||
10.0,
|
||||
-100,
|
||||
self.dt + self.onesec * 4)
|
||||
self.dt + onesec * 4)
|
||||
|
||||
down_tick = factory.create_trade(
|
||||
1,
|
||||
10.0,
|
||||
100,
|
||||
trades[-1].dt + self.onesec)
|
||||
trades[-1].dt + onesec)
|
||||
|
||||
pp.rollover()
|
||||
|
||||
|
||||
@@ -302,6 +302,7 @@ class TestBatchTransform(TestCase):
|
||||
"First three iterations should return None." + "\n" +
|
||||
"i.e. no returned values until window is full'" +
|
||||
"%s" % (algo.history_return_price_decorator,))
|
||||
|
||||
# After three Nones, the next value should be a data frame
|
||||
self.assertTrue(isinstance(
|
||||
algo.history_return_price_class[wl],
|
||||
|
||||
+104
-27
@@ -90,8 +90,9 @@ omitted).
|
||||
| ending_value | the total market value of the positions held at the |
|
||||
| | end of the period |
|
||||
+---------------+------------------------------------------------------+
|
||||
| capital_used | the net capital consumed (positive means spent) by |
|
||||
| | buying and selling securities in the period |
|
||||
| cash_flow | the cash flow in the period (negative means spent) |
|
||||
| | from buying and selling securities in the period. |
|
||||
| | Includes dividend payments in the period as well. |
|
||||
+---------------+------------------------------------------------------+
|
||||
| starting_value| the total market value of the positions held at the |
|
||||
| | start of the period |
|
||||
@@ -212,13 +213,15 @@ class PerformanceTracker(object):
|
||||
new_snapshot = []
|
||||
|
||||
for event in snapshot:
|
||||
event.perf_messages = self.process_event(event)
|
||||
event.portfolio = self.get_portfolio()
|
||||
messages = self.process_event(event)
|
||||
if messages is not None:
|
||||
event.perf_messages = messages
|
||||
event.portfolio = self.get_portfolio()
|
||||
|
||||
del event['TRANSACTION']
|
||||
new_snapshot.append(event)
|
||||
new_snapshot.append(event)
|
||||
|
||||
yield date, new_snapshot
|
||||
if len(new_snapshot) > 0:
|
||||
yield date, new_snapshot
|
||||
|
||||
def get_portfolio(self):
|
||||
return self.cumulative_performance.as_portfolio()
|
||||
@@ -241,21 +244,31 @@ class PerformanceTracker(object):
|
||||
|
||||
def process_event(self, event):
|
||||
|
||||
messages = []
|
||||
|
||||
messages = None
|
||||
self.event_count += 1
|
||||
|
||||
while event.dt > self.market_close:
|
||||
messages.append(self.handle_market_close())
|
||||
if event.type == zp.DATASOURCE_TYPE.TRADE:
|
||||
messages = []
|
||||
while event.dt > self.market_close:
|
||||
messages.append(self.handle_market_close())
|
||||
|
||||
if event.TRANSACTION:
|
||||
self.txn_count += 1
|
||||
self.cumulative_performance.execute_transaction(event.TRANSACTION)
|
||||
self.todays_performance.execute_transaction(event.TRANSACTION)
|
||||
if event.TRANSACTION:
|
||||
self.txn_count += 1
|
||||
self.cumulative_performance.execute_transaction(
|
||||
event.TRANSACTION
|
||||
)
|
||||
self.todays_performance.execute_transaction(event.TRANSACTION)
|
||||
|
||||
#update last sale
|
||||
self.cumulative_performance.update_last_sale(event)
|
||||
self.todays_performance.update_last_sale(event)
|
||||
#update last sale
|
||||
self.cumulative_performance.update_last_sale(event)
|
||||
self.todays_performance.update_last_sale(event)
|
||||
del event['TRANSACTION']
|
||||
|
||||
elif event.type == zp.DATASOURCE_TYPE.DIVIDEND:
|
||||
# TODO: confirm with @ehebert that positions objects
|
||||
# are shared. (and that it is ok).
|
||||
self.cumulative_performance.add_dividend(event)
|
||||
self.todays_performance.add_dividend(event)
|
||||
|
||||
#calculate performance as of last trade
|
||||
self.cumulative_performance.calculate_performance()
|
||||
@@ -267,6 +280,10 @@ 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
|
||||
@@ -322,7 +339,6 @@ Last successful date: %s" % self.market_open)
|
||||
# not trigger an end of day, so we trigger the final
|
||||
# market close(s) here
|
||||
perf_messages = []
|
||||
|
||||
while self.last_close > self.market_close:
|
||||
perf_messages.append(self.handle_market_close())
|
||||
|
||||
@@ -352,6 +368,33 @@ class Position(object):
|
||||
self.cost_basis = 0.0 # per share
|
||||
self.last_sale_price = 0.0
|
||||
self.last_sale_date = 0.0
|
||||
self.dividends = []
|
||||
|
||||
def update_dividends(self, dt):
|
||||
# TODO: should I have asserts for the dt to be at
|
||||
# midnight?
|
||||
payment = 0.0
|
||||
unpaid_dividends = []
|
||||
for dividend in self.dividends:
|
||||
if dt == 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 dt == dividend.pay_date:
|
||||
# if it is the payment date, include this
|
||||
# dividend's actual payment (calculated on
|
||||
# ex_date)
|
||||
payment += dividend.payment
|
||||
else:
|
||||
unpaid_dividends.append(dividend)
|
||||
|
||||
self.dividends = unpaid_dividends
|
||||
return payment
|
||||
|
||||
def add_dividend(self, dividend):
|
||||
self.dividends.append(dividend)
|
||||
|
||||
def update(self, txn):
|
||||
if(self.sid != txn.sid):
|
||||
@@ -408,7 +451,7 @@ class PerformancePeriod(object):
|
||||
self.period_close = period_close
|
||||
|
||||
self.ending_value = 0.0
|
||||
self.period_capital_used = 0.0
|
||||
self.period_cash_flow = 0.0
|
||||
self.pnl = 0.0
|
||||
#sid => position object
|
||||
self.positions = positiondict()
|
||||
@@ -439,7 +482,7 @@ class PerformancePeriod(object):
|
||||
def rollover(self):
|
||||
self.starting_value = self.ending_value
|
||||
self.starting_cash = self.ending_cash
|
||||
self.period_capital_used = 0.0
|
||||
self.period_cash_flow = 0.0
|
||||
self.pnl = 0.0
|
||||
self.processed_transactions = []
|
||||
self.cumulative_capital_used = 0.0
|
||||
@@ -457,11 +500,40 @@ class PerformancePeriod(object):
|
||||
self._position_last_sale_prices, [0])
|
||||
return index
|
||||
|
||||
def add_dividend(self, div):
|
||||
# The dividend is received on midnight of the dividend
|
||||
# declared date. We calculate the dividends based on the amount of
|
||||
# stock owned on midnight of the ex dividend date. However, the cash
|
||||
# is not dispersed until the payment date, which is
|
||||
# included in the event.
|
||||
self.positions[div.sid].add_dividend(div)
|
||||
|
||||
def update_dividends(self, todays_date):
|
||||
"""
|
||||
Check the payment date and ex date against today's date
|
||||
to detrmine if we are owed a dividend payment or if the
|
||||
payment has been disbursed.
|
||||
"""
|
||||
cash_payments = 0.0
|
||||
for sid, pos in self.positions.iteritems():
|
||||
cash_payments += pos.update_dividends(todays_date)
|
||||
|
||||
if cash_payments > 0.0:
|
||||
# credit our cash balance with the dividend payments
|
||||
self.period_cash_flow += cash_payments
|
||||
# debit our cumulative cash spent with the dividend
|
||||
# payments
|
||||
self.cumulative_capital_used -= cash_payments
|
||||
|
||||
# recalculate performance, including the dividend
|
||||
# paymtents
|
||||
self.calculate_performance()
|
||||
|
||||
def calculate_performance(self):
|
||||
self.ending_value = self.calculate_positions_value()
|
||||
|
||||
total_at_start = self.starting_cash + self.starting_value
|
||||
self.ending_cash = self.starting_cash + self.period_capital_used
|
||||
self.ending_cash = self.starting_cash + self.period_cash_flow
|
||||
total_at_end = self.ending_cash + self.ending_value
|
||||
|
||||
self.pnl = total_at_end - total_at_start
|
||||
@@ -478,7 +550,7 @@ class PerformancePeriod(object):
|
||||
index = self.index_for_position(txn.sid)
|
||||
self._position_amounts[index] = position.amount
|
||||
|
||||
self.period_capital_used += -1 * txn.price * txn.amount
|
||||
self.period_cash_flow += -1 * txn.price * txn.amount
|
||||
|
||||
# Max Leverage
|
||||
# ---------------
|
||||
@@ -522,7 +594,9 @@ class PerformancePeriod(object):
|
||||
def __core_dict(self):
|
||||
rval = {
|
||||
'ending_value': self.ending_value,
|
||||
'capital_used': self.period_capital_used,
|
||||
# this field is renamed to capital_used for backward
|
||||
# compatibility.
|
||||
'capital_used': self.period_cash_flow,
|
||||
'starting_value': self.starting_value,
|
||||
'starting_cash': self.starting_cash,
|
||||
'ending_cash': self.ending_cash,
|
||||
@@ -567,7 +641,9 @@ class PerformancePeriod(object):
|
||||
# as_portfolio is called in an inner loop,
|
||||
# so repeated object creation becomes too expensive
|
||||
portfolio = self._portfolio_store
|
||||
portfolio.capital_used = self.period_capital_used,
|
||||
# maintaining the old name for the portfolio field for
|
||||
# backward compatibility
|
||||
portfolio.capital_used = self.period_cash_flow
|
||||
portfolio.starting_cash = self.starting_cash
|
||||
portfolio.portfolio_value = self.ending_cash + self.ending_value
|
||||
portfolio.pnl = self.pnl
|
||||
@@ -583,6 +659,7 @@ class PerformancePeriod(object):
|
||||
positions = self._positions_store
|
||||
|
||||
for sid, pos in self.positions.iteritems():
|
||||
|
||||
if sid not in positions:
|
||||
positions[sid] = zp.Position(sid)
|
||||
position = positions[sid]
|
||||
@@ -595,8 +672,8 @@ class PerformancePeriod(object):
|
||||
def get_positions_list(self):
|
||||
positions = []
|
||||
for sid, pos in self.positions.iteritems():
|
||||
cur = pos.to_dict()
|
||||
positions.append(cur)
|
||||
if pos.amount != 0:
|
||||
positions.append(pos.to_dict())
|
||||
return positions
|
||||
|
||||
|
||||
|
||||
@@ -71,6 +71,7 @@ def create_trade(sid, price, amount, datetime, source_id="test_factory"):
|
||||
trade.low = price * .95
|
||||
trade.high = price * 1.05
|
||||
trade.volume = amount
|
||||
trade.TRANSACTION = None
|
||||
|
||||
return trade
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ from datetime import datetime, timedelta
|
||||
|
||||
import zipline.finance.risk as risk
|
||||
from zipline.utils.date_utils import tuple_to_date
|
||||
from zipline.protocol import Event
|
||||
from zipline.protocol import Event, DATASOURCE_TYPE
|
||||
from zipline.sources import (SpecificEquityTrades,
|
||||
DataFrameSource,
|
||||
DataPanelSource)
|
||||
@@ -119,6 +119,38 @@ def create_trading_environment(year=2006, start=None, end=None,
|
||||
return trading_environment
|
||||
|
||||
|
||||
def create_random_trading_environment():
|
||||
benchmark_returns, treasury_curves = load_market_data()
|
||||
|
||||
for n in range(100):
|
||||
|
||||
random_index = random.randint(
|
||||
0,
|
||||
len(treasury_curves)
|
||||
)
|
||||
|
||||
start_dt = treasury_curves.keys()[random_index]
|
||||
end_dt = start_dt + timedelta(days=365)
|
||||
|
||||
now = datetime.utcnow().replace(tzinfo=pytz.utc)
|
||||
|
||||
if end_dt <= now:
|
||||
break
|
||||
|
||||
assert end_dt <= now, """
|
||||
failed to find a suitable daterange after 100 attempts. please double
|
||||
check treasury and benchmark data in findb, and re-run the test."""
|
||||
|
||||
trading_environment = TradingEnvironment(
|
||||
benchmark_returns,
|
||||
treasury_curves,
|
||||
period_start=start_dt,
|
||||
period_end=end_dt
|
||||
)
|
||||
|
||||
return trading_environment, start_dt, end_dt
|
||||
|
||||
|
||||
def get_next_trading_dt(current, interval, trading_calendar):
|
||||
next = current
|
||||
while True:
|
||||
@@ -143,6 +175,20 @@ def create_trade_history(sid, prices, amounts, interval, trading_calendar,
|
||||
return trades
|
||||
|
||||
|
||||
def create_dividend(sid, payment, declared_date, ex_date, pay_date):
|
||||
div = Event({
|
||||
'sid': sid,
|
||||
'gross_amount': payment,
|
||||
'net_amount': payment,
|
||||
'dt': declared_date.replace(hour=0, minute=0, second=0),
|
||||
'ex_date': ex_date.replace(hour=0, minute=0, second=0),
|
||||
'pay_date': pay_date.replace(hour=0, minute=0, second=0),
|
||||
'type': DATASOURCE_TYPE.DIVIDEND
|
||||
})
|
||||
|
||||
return div
|
||||
|
||||
|
||||
def create_txn(sid, price, amount, datetime):
|
||||
txn = Event({
|
||||
'sid': sid,
|
||||
|
||||
Reference in New Issue
Block a user