Re-implemented the Calendar API.

Instead of having separate ExchangeCalendar and TradingSchedule objects, we
now just have TradingCalendar.  The TradingCalendar keeps track of each
session (defined as a contiguous set of minutes between an open and a close).
It's also responsible for handling the grouping logic of any given minute
to its containing session, or the next/previous session if it's not a market
minute for the given calendar.
This commit is contained in:
Jean Bredeche
2016-06-20 10:30:31 -04:00
parent db4e06055c
commit 6fb4923cc7
71 changed files with 3119 additions and 3981 deletions
+123 -79
View File
@@ -96,7 +96,7 @@ from zipline.testing.fixtures import (
WithSimParams,
WithTradingEnvironment,
WithTmpDir,
WithTradingSchedule,
WithTradingCalendar,
ZiplineTestCase,
)
from zipline.test_algorithms import (
@@ -313,7 +313,6 @@ def handle_data(context, data):
aapl_dt = data.current(sid(1), "last_traded")
assert_equal(aapl_dt, get_datetime())
"""
algo = TradingAlgorithm(script=algo_text,
sim_params=self.sim_params,
env=self.env)
@@ -533,31 +532,48 @@ def handle_data(context, data):
self.assertIs(composer, zipline.utils.events.ComposedRule.lazy_and)
def test_asset_lookup(self):
algo = TradingAlgorithm(env=self.env)
# this date doesn't matter
start_session = pd.Timestamp("2000-01-01", tz="UTC")
# Test before either PLAY existed
algo.sim_params.period_end = pd.Timestamp('2001-12-01', tz='UTC')
algo.sim_params = algo.sim_params.create_new(
start_session,
pd.Timestamp('2001-12-01', tz='UTC')
)
with self.assertRaises(SymbolNotFound):
algo.symbol('PLAY')
with self.assertRaises(SymbolNotFound):
algo.symbols('PLAY')
# Test when first PLAY exists
algo.sim_params.period_end = pd.Timestamp('2002-12-01', tz='UTC')
algo.sim_params = algo.sim_params.create_new(
start_session,
pd.Timestamp('2002-12-01', tz='UTC')
)
list_result = algo.symbols('PLAY')
self.assertEqual(3, list_result[0])
# Test after first PLAY ends
algo.sim_params.period_end = pd.Timestamp('2004-12-01', tz='UTC')
algo.sim_params = algo.sim_params.create_new(
start_session,
pd.Timestamp('2004-12-01', tz='UTC')
)
self.assertEqual(3, algo.symbol('PLAY'))
# Test after second PLAY begins
algo.sim_params.period_end = pd.Timestamp('2005-12-01', tz='UTC')
algo.sim_params = algo.sim_params.create_new(
start_session,
pd.Timestamp('2005-12-01', tz='UTC')
)
self.assertEqual(4, algo.symbol('PLAY'))
# Test after second PLAY ends
algo.sim_params.period_end = pd.Timestamp('2006-12-01', tz='UTC')
algo.sim_params = algo.sim_params.create_new(
start_session,
pd.Timestamp('2006-12-01', tz='UTC')
)
self.assertEqual(4, algo.symbol('PLAY'))
list_result = algo.symbols('PLAY')
self.assertEqual(4, list_result[0])
@@ -710,7 +726,10 @@ def handle_data(context, data):
# Set the period end to a date after the period end
# dates for our assets.
algo.sim_params.period_end = pd.Timestamp('2015-01-01', tz='UTC')
algo.sim_params = algo.sim_params.create_new(
algo.sim_params.start_session,
pd.Timestamp('2015-01-01', tz='UTC')
)
# With no symbol lookup date set, we will use the period end date
# for the as_of_date, resulting here in the asset with the earlier
@@ -753,10 +772,10 @@ class TestTransformAlgorithm(WithLogger,
[100, 100, 100, 300],
timedelta(days=1),
cls.sim_params,
cls.trading_schedule,
cls.trading_calendar,
) for sid in cls.sids
},
index=cls.sim_params.trading_days,
index=cls.sim_params.sessions,
)
@classmethod
@@ -914,9 +933,10 @@ def before_trading_start(context, data):
asset133 = self.env.asset_finder.retrieve_asset(133)
sim_params = SimulationParameters(
period_start=asset133.start_date,
period_end=asset133.end_date,
data_frequency="minute"
start_session=asset133.start_date,
end_session=asset133.end_date,
data_frequency="minute",
trading_calendar=self.trading_calendar
)
algo = TradingAlgorithm(
@@ -942,20 +962,20 @@ def before_trading_start(context, data):
(TestOrderPercentAlgorithm,)
])
def test_minute_data(self, algo_class):
period_start = pd.Timestamp('2002-1-2', tz='UTC')
start_session = pd.Timestamp('2002-1-2', tz='UTC')
period_end = pd.Timestamp('2002-1-4', tz='UTC')
equities = pd.DataFrame([{
'start_date': period_start,
'start_date': start_session,
'end_date': period_end + timedelta(days=1)
}] * 2)
with TempDirectory() as tempdir, \
tmp_trading_env(equities=equities) as env:
sim_params = SimulationParameters(
period_start=period_start,
period_end=period_end,
start_session=start_session,
end_session=period_end,
capital_base=float("1.0e5"),
data_frequency='minute',
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
data_portal = create_data_portal(
@@ -963,7 +983,7 @@ def before_trading_start(context, data):
tempdir,
sim_params,
equities.index,
self.trading_schedule,
self.trading_calendar,
)
algo = algo_class(sim_params=sim_params, env=env)
algo.run(data_portal)
@@ -1015,6 +1035,9 @@ class TestBeforeTradingStart(WithDataPortal,
SIM_PARAMS_DATA_FREQUENCY = 'minute'
EQUITY_DAILY_BAR_LOOKBACK_DAYS = EQUITY_MINUTE_BAR_LOOKBACK_DAYS = 1
DATA_PORTAL_FIRST_TRADING_DAY = pd.Timestamp("2016-01-05", tz='UTC')
EQUITY_MINUTE_BAR_START_DATE = pd.Timestamp("2016-01-05", tz='UTC')
data_start = ASSET_FINDER_EQUITY_START_DATE = pd.Timestamp(
'2016-01-05',
tz='utc',
@@ -1026,7 +1049,7 @@ class TestBeforeTradingStart(WithDataPortal,
@classmethod
def make_equity_minute_bar_data(cls):
asset_minutes = \
cls.trading_schedule.execution_minutes_for_days_in_range(
cls.trading_calendar.minutes_in_range(
cls.data_start,
cls.END_DATE,
)
@@ -1045,15 +1068,15 @@ class TestBeforeTradingStart(WithDataPortal,
split_data.iloc[780:] = split_data.iloc[780:] / 2.0
for sid in (1, 8554):
yield sid, create_minute_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
cls.data_start,
cls.sim_params.period_end,
cls.sim_params.end_session,
)
yield 2, create_minute_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
cls.data_start,
cls.sim_params.period_end,
cls.sim_params.end_session,
50,
)
yield cls.SPLIT_ASSET_SID, split_data
@@ -1072,9 +1095,9 @@ class TestBeforeTradingStart(WithDataPortal,
def make_equity_daily_bar_data(cls):
for sid in cls.ASSET_FINDER_EQUITY_SIDS:
yield sid, create_daily_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
cls.data_start,
cls.sim_params.period_end,
cls.sim_params.end_session,
)
def test_data_in_bts_minute(self):
@@ -1253,7 +1276,7 @@ class TestBeforeTradingStart(WithDataPortal,
if not context.ordered:
order(sid(1), 1)
context.ordered = True
context.hd_acount = context.account
context.hd_account = context.account
""")
algo = TradingAlgorithm(
@@ -1410,14 +1433,14 @@ class TestAlgoScript(WithLogger,
[100] * days,
timedelta(days=1),
cls.sim_params,
cls.trading_schedule),
cls.trading_calendar),
3: factory.create_trade_history(
3,
[10.0] * days,
[100] * days,
timedelta(days=1),
cls.sim_params,
cls.trading_schedule)
cls.trading_calendar)
},
index=cls.equity_daily_bar_days,
)
@@ -1556,9 +1579,9 @@ def handle_data(context, data):
env=self.env,
)
trades = factory.create_daily_trade_source(
[0], self.sim_params, self.env, self.trading_schedule)
[0], self.sim_params, self.env, self.trading_calendar)
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder, self.trading_schedule, tempdir,
self.env.asset_finder, self.trading_calendar, tempdir,
self.sim_params, {0: trades})
results = test_algo.run(data_portal)
@@ -1644,9 +1667,9 @@ def handle_data(context, data):
def test_order_dead_asset(self):
# after asset 0 is dead
params = SimulationParameters(
period_start=pd.Timestamp("2007-01-03", tz='UTC'),
period_end=pd.Timestamp("2007-01-05", tz='UTC'),
trading_schedule=self.trading_schedule,
start_session=pd.Timestamp("2007-01-03", tz='UTC'),
end_session=pd.Timestamp("2007-01-05", tz='UTC'),
trading_calendar=self.trading_calendar,
)
# order method shouldn't blow up
@@ -1725,9 +1748,15 @@ def handle_data(context, data):
Test that api methods on the data object can be called with positional
arguments.
"""
params = SimulationParameters(
start_session=pd.Timestamp("2006-01-10", tz='UTC'),
end_session=pd.Timestamp("2006-01-11", tz='UTC'),
trading_calendar=self.trading_calendar,
)
test_algo = TradingAlgorithm(
script=call_without_kwargs,
sim_params=self.sim_params,
sim_params=params,
env=self.env,
)
test_algo.run(self.data_portal)
@@ -1737,9 +1766,15 @@ def handle_data(context, data):
Test that api methods on the data object can be called with keyword
arguments.
"""
params = SimulationParameters(
start_session=pd.Timestamp("2006-01-10", tz='UTC'),
end_session=pd.Timestamp("2006-01-11", tz='UTC'),
trading_calendar=self.trading_calendar,
)
test_algo = TradingAlgorithm(
script=call_with_kwargs,
sim_params=self.sim_params,
sim_params=params,
env=self.env,
)
test_algo.run(self.data_portal)
@@ -1785,6 +1820,12 @@ def handle_data(context, data):
self.assertEqual(expected, cm.exception.args[0])
def test_empty_asset_list_to_history(self):
params = SimulationParameters(
start_session=pd.Timestamp("2006-01-10", tz='UTC'),
end_session=pd.Timestamp("2006-01-11", tz='UTC'),
trading_calendar=self.trading_calendar,
)
algo = TradingAlgorithm(
script=dedent("""
def initialize(context):
@@ -1793,7 +1834,7 @@ def handle_data(context, data):
def handle_data(context, data):
data.history([], "price", 5, '1d')
"""),
sim_params=self.sim_params,
sim_params=params,
env=self.env
)
@@ -1946,7 +1987,7 @@ class TestCapitalChanges(WithLogger,
@classmethod
def make_equity_minute_bar_data(cls):
minutes = cls.trading_schedule.execution_minutes_for_days_in_range(
minutes = cls.trading_calendar.minutes_in_range(
pd.Timestamp('2006-01-03', tz='UTC'),
pd.Timestamp('2006-01-09', tz='UTC')
)
@@ -1958,14 +1999,14 @@ class TestCapitalChanges(WithLogger,
[10000] * len(minutes),
timedelta(minutes=1),
cls.sim_params,
cls.trading_schedule),
cls.trading_calendar),
},
index=pd.DatetimeIndex(minutes),
)
@classmethod
def make_equity_daily_bar_data(cls):
days = cls.trading_schedule.execution_days_in_range(
days = cls.trading_calendar.minutes_in_range(
pd.Timestamp('2006-01-03', tz='UTC'),
pd.Timestamp('2006-01-09', tz='UTC')
)
@@ -1977,7 +2018,7 @@ class TestCapitalChanges(WithLogger,
[10000] * len(days),
timedelta(days=1),
cls.sim_params,
cls.trading_schedule),
cls.trading_calendar),
},
index=pd.DatetimeIndex(days),
)
@@ -2733,7 +2774,7 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
tempdir,
sim_params,
[1],
self.trading_schedule,
self.trading_calendar,
)
def handle_data(algo, data):
@@ -2841,7 +2882,7 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
def test_asset_date_bounds(self):
metadata = pd.DataFrame([{
'start_date': self.sim_params.period_start,
'start_date': self.sim_params.start_session,
'end_date': '2020-01-01',
}])
with TempDirectory() as tempdir, \
@@ -2855,7 +2896,7 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
tempdir,
self.sim_params,
[0],
self.trading_schedule,
self.trading_calendar,
)
algo.run(data_portal)
@@ -2870,7 +2911,7 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
tempdir,
self.sim_params,
[0],
self.trading_schedule,
self.trading_calendar,
)
algo = SetAssetDateBoundsAlgorithm(
sim_params=self.sim_params,
@@ -2890,7 +2931,7 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
tempdir,
self.sim_params,
[0],
self.trading_schedule,
self.trading_calendar,
)
algo = SetAssetDateBoundsAlgorithm(
sim_params=self.sim_params,
@@ -2916,10 +2957,10 @@ class TestAccountControls(WithDataPortal, WithSimParams, ZiplineTestCase):
[100, 100, 100, 300],
timedelta(days=1),
cls.sim_params,
cls.trading_schedule,
cls.trading_calendar,
),
},
index=cls.sim_params.trading_days,
index=cls.sim_params.sessions,
)
def _check_algo(self,
@@ -3063,18 +3104,19 @@ class TestFutureFlip(WithDataPortal, WithSimParams, ZiplineTestCase):
[1e9, 1e9],
timedelta(days=1),
cls.sim_params,
cls.trading_schedule,
cls.trading_calendar,
),
},
index=cls.sim_params.trading_days,
index=cls.sim_params.sessions,
)
@skip('broken in zipline 1.0.0')
def test_flip_algo(self):
metadata = {1: {'symbol': 'TEST',
'start_date': self.sim_params.trading_days[0],
'end_date': self.trading_schedule.next_execution_day(
self.sim_params.trading_days[-1]),
'end_date': self.trading_calendar.next_session_label(
self.sim_params.sessions[-1]
),
'multiplier': 5}}
self.env.write_data(futures_data=metadata)
@@ -3174,9 +3216,9 @@ class TestOrderCancelation(WithDataPortal,
@classmethod
def make_equity_minute_bar_data(cls):
asset_minutes = \
cls.trading_schedule.execution_minutes_for_days_in_range(
cls.sim_params.period_start,
cls.sim_params.period_end,
cls.trading_calendar.minutes_for_sessions_in_range(
cls.sim_params.start_session,
cls.sim_params.end_session,
)
minutes_count = len(asset_minutes)
@@ -3204,7 +3246,7 @@ class TestOrderCancelation(WithDataPortal,
'close': np.full(3, 1),
'volume': np.full(3, 1),
},
index=cls.sim_params.trading_days,
index=cls.sim_params.sessions,
)
def prep_algo(self, cancelation_string, data_frequency="minute",
@@ -3214,9 +3256,9 @@ class TestOrderCancelation(WithDataPortal,
script=code,
env=self.env,
sim_params=SimulationParameters(
period_start=self.sim_params.period_start,
period_end=self.sim_params.period_end,
trading_schedule=self.trading_schedule,
start_session=self.sim_params.start_session,
end_session=self.sim_params.end_session,
trading_calendar=self.trading_calendar,
data_frequency=data_frequency,
emission_rate='minute' if minute_emission else 'daily'
)
@@ -3329,7 +3371,7 @@ class TestOrderCancelation(WithDataPortal,
self.assertFalse(log_catcher.has_warnings)
class TestEquityAutoClose(WithTmpDir, WithTradingSchedule, ZiplineTestCase):
class TestEquityAutoClose(WithTmpDir, WithTradingCalendar, ZiplineTestCase):
"""
Tests if delisted equities are properly removed from a portfolio holding
positions in said equities.
@@ -3337,11 +3379,11 @@ class TestEquityAutoClose(WithTmpDir, WithTradingSchedule, ZiplineTestCase):
@classmethod
def init_class_fixtures(cls):
super(TestEquityAutoClose, cls).init_class_fixtures()
trading_days = cls.trading_schedule.all_execution_days
trading_sessions = cls.trading_calendar.all_sessions
start_date = pd.Timestamp('2015-01-05', tz='UTC')
start_date_loc = trading_days.get_loc(start_date)
start_date_loc = trading_sessions.get_loc(start_date)
test_duration = 7
cls.test_days = trading_days[
cls.test_days = trading_sessions[
start_date_loc:start_date_loc + test_duration
]
cls.first_asset_expiration = cls.test_days[2]
@@ -3353,7 +3395,7 @@ class TestEquityAutoClose(WithTmpDir, WithTradingSchedule, ZiplineTestCase):
num_assets=3,
start_date=self.test_days[0],
first_end=self.first_asset_expiration,
frequency=self.trading_schedule.day,
frequency=self.trading_calendar.day,
periods_between_ends=2,
auto_close_delta=auto_close_delta,
)
@@ -3361,10 +3403,10 @@ class TestEquityAutoClose(WithTmpDir, WithTradingSchedule, ZiplineTestCase):
sids = asset_info.index
env = self.enter_instance_context(tmp_trading_env(equities=asset_info))
market_opens = self.trading_schedule.schedule.market_open.loc[
market_opens = self.trading_calendar.schedule.market_open.loc[
self.test_days
]
market_closes = self.trading_schedule.schedule.market_close.loc[
market_closes = self.trading_calendar.schedule.market_close.loc[
self.test_days
]
@@ -3382,17 +3424,17 @@ class TestEquityAutoClose(WithTmpDir, WithTradingSchedule, ZiplineTestCase):
frequency=frequency
)
path = self.tmpdir.getpath("testdaily.bcolz")
BcolzDailyBarWriter(path, dates).write(
BcolzDailyBarWriter(path, dates, self.trading_calendar).write(
iteritems(trade_data_by_sid),
)
reader = BcolzDailyBarReader(path)
data_portal = DataPortal(
env.asset_finder, self.trading_schedule,
env.asset_finder, self.trading_calendar,
first_trading_day=reader.first_trading_day,
equity_daily_reader=reader,
)
elif frequency == 'minute':
dates = self.trading_schedule.execution_minutes_for_days_in_range(
dates = self.trading_calendar.minutes_for_sessions_in_range(
self.test_days[0],
self.test_days[-1],
)
@@ -3417,7 +3459,7 @@ class TestEquityAutoClose(WithTmpDir, WithTradingSchedule, ZiplineTestCase):
)
reader = BcolzMinuteBarReader(self.tmpdir.path)
data_portal = DataPortal(
env.asset_finder, self.trading_schedule,
env.asset_finder, self.trading_calendar,
first_trading_day=reader.first_trading_day,
equity_minute_reader=reader,
)
@@ -3443,7 +3485,9 @@ class TestEquityAutoClose(WithTmpDir, WithTradingSchedule, ZiplineTestCase):
else:
final_prices = {
asset.sid: trade_data_by_sid[asset.sid].loc[
self.trading_schedule.start_and_end(asset.end_date)[1]
self.trading_calendar.open_and_close_for_session(
asset.end_date
)[1]
].close
for asset in assets
}
@@ -3515,7 +3559,7 @@ class TestEquityAutoClose(WithTmpDir, WithTradingSchedule, ZiplineTestCase):
Make sure that after an equity gets delisted, our portfolio holds the
correct number of equities and correct amount of cash.
"""
auto_close_delta = self.trading_schedule.day * auto_close_lag
auto_close_delta = self.trading_calendar.day * auto_close_lag
resources = self.make_data(auto_close_delta, 'daily', capital_base)
assets = resources.assets
@@ -3594,7 +3638,7 @@ class TestEquityAutoClose(WithTmpDir, WithTradingSchedule, ZiplineTestCase):
# Check expected long/short counts.
# We have longs if order_size > 0.
# We have shrots if order_size < 0.
# We have shrots if order_size > 0.
self.assertEqual(algo.num_positions, expected_num_positions)
if order_size > 0:
self.assertEqual(
@@ -3675,7 +3719,7 @@ class TestEquityAutoClose(WithTmpDir, WithTradingSchedule, ZiplineTestCase):
canceled. Unless an equity is auto closed, any open orders for that
equity will persist indefinitely.
"""
auto_close_delta = self.trading_schedule.day
auto_close_delta = self.trading_calendar.day
resources = self.make_data(auto_close_delta, 'daily')
env = resources.env
assets = resources.assets
@@ -3747,7 +3791,7 @@ class TestEquityAutoClose(WithTmpDir, WithTradingSchedule, ZiplineTestCase):
)
def test_minutely_delisted_equities(self):
resources = self.make_data(self.trading_schedule.day, 'minute')
resources = self.make_data(self.trading_calendar.day, 'minute')
env = resources.env
assets = resources.assets
@@ -3933,9 +3977,9 @@ class TestOrderAfterDelist(WithTradingEnvironment, ZiplineTestCase):
script=algo_code,
env=self.env,
sim_params=SimulationParameters(
period_start=pd.Timestamp("2016-01-06", tz='UTC'),
period_end=pd.Timestamp("2016-01-07", tz='UTC'),
trading_schedule=self.trading_schedule,
start_session=pd.Timestamp("2016-01-06", tz='UTC'),
end_session=pd.Timestamp("2016-01-07", tz='UTC'),
trading_calendar=self.trading_calendar,
data_frequency="minute"
)
)