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
+5 -1
View File
@@ -96,7 +96,11 @@ ext_modules = [
Extension(
'zipline.data._minute_bar_internal',
['zipline/data/_minute_bar_internal.pyx']
)
),
Extension(
'zipline.utils.calendars._calendar_helpers',
['zipline/utils/calendars/_calendar_helpers.pyx']
),
]
+2 -2
View File
@@ -111,9 +111,9 @@ class BundleCoreTestCase(WithInstanceTmpDir, ZiplineTestCase):
def test_ingest(self):
start = pd.Timestamp('2014-01-06', tz='utc')
end = pd.Timestamp('2014-01-10', tz='utc')
trading_days = get_calendar('NYSE').all_trading_days
trading_days = get_calendar('NYSE').all_sessions
calendar = trading_days[trading_days.slice_indexer(start, end)]
minutes = get_calendar('NYSE').trading_minutes_for_days_in_range(
minutes = get_calendar('NYSE').minutes_for_sessions_in_range(
calendar[0], calendar[-1]
)
+1 -1
View File
@@ -18,7 +18,7 @@ class YahooBundleTestCase(WithResponses, ZiplineTestCase):
columns = 'open', 'high', 'low', 'close', 'volume'
asset_start = pd.Timestamp('2014-01-02', tz='utc')
asset_end = pd.Timestamp('2014-12-31', tz='utc')
trading_days = get_calendar('NYSE').all_trading_days
trading_days = get_calendar('NYSE').all_sessions
calendar = trading_days[
(trading_days >= asset_start) &
(trading_days <= asset_end)
+23 -14
View File
@@ -45,7 +45,7 @@ from zipline.data.minute_bars import (
from zipline.testing.fixtures import (
WithInstanceTmpDir,
WithTradingSchedule,
WithTradingCalendar,
ZiplineTestCase,
)
@@ -56,17 +56,20 @@ TEST_CALENDAR_START = Timestamp('2014-06-02', tz='UTC')
TEST_CALENDAR_STOP = Timestamp('2015-12-31', tz='UTC')
class BcolzMinuteBarTestCase(WithTradingSchedule, WithInstanceTmpDir,
class BcolzMinuteBarTestCase(WithTradingCalendar, WithInstanceTmpDir,
ZiplineTestCase):
@classmethod
def init_class_fixtures(cls):
super(BcolzMinuteBarTestCase, cls).init_class_fixtures()
trading_days = cls.trading_schedule.trading_sessions(
TEST_CALENDAR_START, TEST_CALENDAR_STOP
)
cls.market_opens = trading_days.market_open
cls.market_closes = trading_days.market_close
cal = cls.trading_calendar.schedule.loc[
TEST_CALENDAR_START:TEST_CALENDAR_STOP
]
cls.market_opens = cal.market_open
cls.market_closes = cal.market_close
cls.test_calendar_start = cls.market_opens.index[0]
cls.test_calendar_stop = cls.market_opens.index[-1]
@@ -798,9 +801,9 @@ class BcolzMinuteBarTestCase(WithTradingSchedule, WithInstanceTmpDir,
data = {sids[0]: data_1, sids[1]: data_2}
start_minute_loc = \
self.trading_schedule.all_execution_minutes.get_loc(minutes[0])
self.trading_calendar.all_minutes.get_loc(minutes[0])
minute_locs = [
self.trading_schedule.all_execution_minutes.get_loc(minute)
self.trading_calendar.all_minutes.get_loc(minute)
- start_minute_loc
for minute in minutes
]
@@ -822,9 +825,11 @@ class BcolzMinuteBarTestCase(WithTradingSchedule, WithInstanceTmpDir,
'close': arange(1, 781),
'volume': arange(1, 781)
}
dts = array(self.trading_schedule.execution_minutes_for_days_in_range(
start_day, end_day
dts = array(self.trading_calendar.minutes_for_sessions_in_range(
self.trading_calendar.minute_to_session_label(start_day),
self.trading_calendar.minute_to_session_label(end_day)
))
self.writer.write_cols(sid, dts, cols)
self.assertEqual(
@@ -866,9 +871,13 @@ class BcolzMinuteBarTestCase(WithTradingSchedule, WithInstanceTmpDir,
'close': arange(1, 601),
'volume': arange(1, 601)
}
dts = array(self.trading_schedule.execution_minutes_for_days_in_range(
start_day, end_day
))
dts = array(
self.trading_calendar.minutes_for_sessions_in_range(
self.trading_calendar.minute_to_session_label(start_day),
self.trading_calendar.minute_to_session_label(end_day)
)
)
self.writer.write_cols(sid, dts, cols)
self.assertEqual(
+11 -11
View File
@@ -46,7 +46,6 @@ from zipline.testing.fixtures import (
WithBcolzEquityDailyBarReader,
ZiplineTestCase,
)
from zipline.utils.calendars import get_calendar
TEST_CALENDAR_START = Timestamp('2015-06-01', tz='UTC')
TEST_CALENDAR_STOP = Timestamp('2015-06-30', tz='UTC')
@@ -97,16 +96,17 @@ class BcolzDailyBarTestCase(WithBcolzEquityDailyBarReader, ZiplineTestCase):
@classmethod
def init_class_fixtures(cls):
super(BcolzDailyBarTestCase, cls).init_class_fixtures()
cls.trading_days = get_calendar('NYSE').trading_days(
TEST_CALENDAR_START, TEST_CALENDAR_STOP
).index
cls.sessions = cls.trading_calendar.sessions_in_range(
cls.trading_calendar.minute_to_session_label(TEST_CALENDAR_START),
cls.trading_calendar.minute_to_session_label(TEST_CALENDAR_STOP)
)
@property
def assets(self):
return EQUITY_INFO.index
def trading_days_between(self, start, end):
return self.trading_days[self.trading_days.slice_indexer(start, end)]
return self.sessions[self.sessions.slice_indexer(start, end)]
def asset_start(self, asset_id):
return asset_start(EQUITY_INFO, asset_id)
@@ -181,14 +181,14 @@ class BcolzDailyBarTestCase(WithBcolzEquityDailyBarReader, ZiplineTestCase):
expected_calendar_offset,
)
assert_index_equal(
self.trading_days,
self.sessions,
DatetimeIndex(result.attrs['calendar'], tz='UTC'),
)
def test_read_first_trading_day(self):
self.assertEqual(
self.bcolz_equity_daily_bar_reader.first_trading_day,
self.trading_days[0],
self.sessions[0],
)
def _check_read_results(self, columns, assets, start_date, end_date):
@@ -234,7 +234,7 @@ class BcolzDailyBarTestCase(WithBcolzEquityDailyBarReader, ZiplineTestCase):
columns,
self.assets,
start_date=self.asset_start(asset),
end_date=self.trading_days[-1],
end_date=self.sessions[-1],
)
def test_start_on_asset_end(self):
@@ -248,7 +248,7 @@ class BcolzDailyBarTestCase(WithBcolzEquityDailyBarReader, ZiplineTestCase):
columns,
self.assets,
start_date=self.asset_end(asset),
end_date=self.trading_days[-1],
end_date=self.sessions[-1],
)
def test_end_on_asset_start(self):
@@ -261,7 +261,7 @@ class BcolzDailyBarTestCase(WithBcolzEquityDailyBarReader, ZiplineTestCase):
self._check_read_results(
columns,
self.assets,
start_date=self.trading_days[0],
start_date=self.sessions[0],
end_date=self.asset_start(asset),
)
@@ -275,7 +275,7 @@ class BcolzDailyBarTestCase(WithBcolzEquityDailyBarReader, ZiplineTestCase):
self._check_read_results(
columns,
self.assets,
start_date=self.trading_days[0],
start_date=self.sessions[0],
end_date=self.asset_end(asset),
)
+4 -4
View File
@@ -91,10 +91,10 @@ class SlippageTestCase(WithSimParams, WithDataPortal, ZiplineTestCase):
start=normalize_date(self.minutes[0]),
end=normalize_date(self.minutes[-1])
)
with tmp_bcolz_equity_minute_bar_reader(self.trading_schedule, days, assets) \
with tmp_bcolz_equity_minute_bar_reader(self.trading_calendar, days, assets) \
as reader:
data_portal = DataPortal(
self.env.asset_finder, self.trading_schedule,
self.env.asset_finder, self.trading_calendar,
first_trading_day=reader.first_trading_day,
equity_minute_reader=reader,
)
@@ -481,10 +481,10 @@ class SlippageTestCase(WithSimParams, WithDataPortal, ZiplineTestCase):
start=normalize_date(self.minutes[0]),
end=normalize_date(self.minutes[-1])
)
with tmp_bcolz_equity_minute_bar_reader(self.trading_schedule, days, assets) \
with tmp_bcolz_equity_minute_bar_reader(self.trading_calendar, days, assets) \
as reader:
data_portal = DataPortal(
self.env.asset_finder, self.trading_schedule,
self.env.asset_finder, self.trading_calendar,
first_trading_day=reader.first_trading_day,
equity_minute_reader=reader,
)
+3 -3
View File
@@ -17,7 +17,7 @@ from zipline.testing import (
ExplodingObject,
tmp_asset_finder,
)
from zipline.testing.fixtures import ZiplineTestCase, WithTradingSchedule
from zipline.testing.fixtures import ZiplineTestCase, WithTradingCalendar
from zipline.utils.functional import dzip_exact
from zipline.utils.pandas_utils import explode
@@ -50,14 +50,14 @@ def with_defaults(**default_funcs):
with_default_shape = with_defaults(shape=lambda self: self.default_shape)
class BasePipelineTestCase(WithTradingSchedule, ZiplineTestCase):
class BasePipelineTestCase(WithTradingCalendar, ZiplineTestCase):
@classmethod
def init_class_fixtures(cls):
super(BasePipelineTestCase, cls).init_class_fixtures()
cls.__calendar = date_range('2014', '2015',
freq=cls.trading_schedule.day)
freq=cls.trading_calendar.day)
cls.__assets = assets = Int64Index(arange(1, 20))
cls.__tmp_finder_ctx = tmp_asset_finder(
equities=make_simple_equity_info(
+10 -10
View File
@@ -782,7 +782,7 @@ class FrameInputTestCase(WithTradingEnvironment, ZiplineTestCase):
cls.dates = date_range(
cls.start,
cls.end,
freq=cls.trading_schedule.day,
freq=cls.trading_calendar.day,
tz='UTC',
)
cls.assets = cls.asset_finder.retrieve_all(cls.asset_ids)
@@ -886,7 +886,7 @@ class SyntheticBcolzTestCase(WithAdjustmentReader,
cls.equity_info = ret = make_rotating_equity_info(
num_assets=6,
first_start=cls.first_asset_start,
frequency=cls.trading_schedule.day,
frequency=cls.trading_calendar.day,
periods_between_starts=4,
asset_lifetime=8,
)
@@ -941,15 +941,15 @@ class SyntheticBcolzTestCase(WithAdjustmentReader,
def test_SMA(self):
engine = SimplePipelineEngine(
lambda column: self.pipeline_loader,
self.trading_schedule.all_execution_days,
self.trading_calendar.all_sessions,
self.asset_finder,
)
window_length = 5
asset_ids = self.all_asset_ids
dates = date_range(
self.first_asset_start + self.trading_schedule.day,
self.first_asset_start + self.trading_calendar.day,
self.last_asset_end,
freq=self.trading_schedule.day,
freq=self.trading_calendar.day,
)
dates_to_test = dates[window_length:]
@@ -969,7 +969,7 @@ class SyntheticBcolzTestCase(WithAdjustmentReader,
# **previous** day's data.
expected_raw = rolling_mean(
expected_bar_values_2d(
dates - self.trading_schedule.day,
dates - self.trading_calendar.day,
self.equity_info,
'close',
),
@@ -995,15 +995,15 @@ class SyntheticBcolzTestCase(WithAdjustmentReader,
# valuable.
engine = SimplePipelineEngine(
lambda column: self.pipeline_loader,
self.trading_schedule.all_execution_days,
self.trading_calendar.all_sessions,
self.asset_finder,
)
window_length = 5
asset_ids = self.all_asset_ids
dates = date_range(
self.first_asset_start + self.trading_schedule.day,
self.first_asset_start + self.trading_calendar.day,
self.last_asset_end,
freq=self.trading_schedule.day,
freq=self.trading_calendar.day,
)
dates_to_test = dates[window_length:]
@@ -1039,7 +1039,7 @@ class ParameterizedFactorTestCase(WithTradingEnvironment, ZiplineTestCase):
@classmethod
def init_class_fixtures(cls):
super(ParameterizedFactorTestCase, cls).init_class_fixtures()
day = cls.trading_schedule.day
day = cls.trading_calendar.day
cls.dates = dates = date_range(
'2015-02-01',
+10 -11
View File
@@ -24,22 +24,21 @@ from zipline.pipeline.data import USEquityPricing
from zipline.pipeline.loaders.frame import (
DataFrameLoader,
)
from zipline.utils.calendars import default_nyse_schedule
trading_day = default_nyse_schedule.day
from zipline.utils.calendars import get_calendar
class DataFrameLoaderTestCase(TestCase):
def setUp(self):
self.trading_day = get_calendar("NYSE").day
self.nsids = 5
self.ndates = 20
self.sids = Int64Index(range(self.nsids))
self.dates = DatetimeIndex(
start='2014-01-02',
freq=trading_day,
freq=self.trading_day,
periods=self.ndates,
)
@@ -161,17 +160,17 @@ class DataFrameLoaderTestCase(TestCase):
},
{ # Date Before Known Data
'sid': 2,
'start_date': self.dates[0] - (2 * trading_day),
'end_date': self.dates[0] - trading_day,
'apply_date': self.dates[0] - trading_day,
'start_date': self.dates[0] - (2 * self.trading_day),
'end_date': self.dates[0] - self.trading_day,
'apply_date': self.dates[0] - self.trading_day,
'value': -9999.0,
'kind': OVERWRITE,
},
{ # Date After Known Data
'sid': 2,
'start_date': self.dates[-1] + trading_day,
'end_date': self.dates[-1] + (2 * trading_day),
'apply_date': self.dates[-1] + (3 * trading_day),
'start_date': self.dates[-1] + self.trading_day,
'end_date': self.dates[-1] + (2 * self.trading_day),
'apply_date': self.dates[-1] + (3 * self.trading_day),
'value': -9999.0,
'kind': OVERWRITE,
},
+25 -22
View File
@@ -60,8 +60,7 @@ from zipline.testing.fixtures import (
WithDataPortal,
ZiplineTestCase,
)
from zipline.utils.calendars import default_nyse_schedule
from zipline.utils.calendars import get_calendar
TEST_RESOURCE_PATH = join(
dirname(dirname(realpath(__file__))), # zipline_repo/tests
@@ -70,9 +69,6 @@ TEST_RESOURCE_PATH = join(
)
trading_day = default_nyse_schedule.day
def rolling_vwap(df, length):
"Simple rolling vwap implementation for testing"
closes = df['close'].values
@@ -90,7 +86,8 @@ class ClosesOnly(WithDataPortal, ZiplineTestCase):
sids = 1, 2, 3
START_DATE = pd.Timestamp('2014-01-01', tz='utc')
END_DATE = pd.Timestamp('2014-02-01', tz='utc')
dates = date_range(START_DATE, END_DATE, freq=trading_day, tz='utc')
dates = date_range(START_DATE, END_DATE, freq=get_calendar("NYSE").day,
tz='utc')
@classmethod
def make_equity_info(cls):
@@ -145,9 +142,11 @@ class ClosesOnly(WithDataPortal, ZiplineTestCase):
cls.last_asset_end = max(cls.equity_info.end_date)
cls.assets = cls.asset_finder.retrieve_all(cls.sids)
cls.trading_day = cls.trading_calendar.day
# Add a split for 'A' on its second date.
cls.split_asset = cls.assets[0]
cls.split_date = cls.split_asset.start_date + trading_day
cls.split_date = cls.split_asset.start_date + cls.trading_day
cls.split_ratio = 0.5
cls.adjustments = DataFrame.from_records([
{
@@ -199,8 +198,8 @@ class ClosesOnly(WithDataPortal, ZiplineTestCase):
handle_data=late_attach,
data_frequency='daily',
get_pipeline_loader=lambda column: self.pipeline_loader,
start=self.first_asset_start - trading_day,
end=self.last_asset_end + trading_day,
start=self.first_asset_start - self.trading_day,
end=self.last_asset_end + self.trading_day,
env=self.env,
)
@@ -216,8 +215,8 @@ class ClosesOnly(WithDataPortal, ZiplineTestCase):
handle_data=barf,
data_frequency='daily',
get_pipeline_loader=lambda column: self.pipeline_loader,
start=self.first_asset_start - trading_day,
end=self.last_asset_end + trading_day,
start=self.first_asset_start - self.trading_day,
end=self.last_asset_end + self.trading_day,
env=self.env,
)
@@ -245,8 +244,8 @@ class ClosesOnly(WithDataPortal, ZiplineTestCase):
before_trading_start=before_trading_start,
data_frequency='daily',
get_pipeline_loader=lambda column: self.pipeline_loader,
start=self.first_asset_start - trading_day,
end=self.last_asset_end + trading_day,
start=self.first_asset_start - self.trading_day,
end=self.last_asset_end + self.trading_day,
env=self.env,
)
@@ -273,8 +272,8 @@ class ClosesOnly(WithDataPortal, ZiplineTestCase):
before_trading_start=before_trading_start,
data_frequency='daily',
get_pipeline_loader=lambda column: self.pipeline_loader,
start=self.first_asset_start - trading_day,
end=self.last_asset_end + trading_day,
start=self.first_asset_start - self.trading_day,
end=self.last_asset_end + self.trading_day,
env=self.env,
)
@@ -308,7 +307,7 @@ class ClosesOnly(WithDataPortal, ZiplineTestCase):
for asset in self.assets:
# Assets should appear iff they exist today and yesterday.
exists_today = self.exists(date, asset)
existed_yesterday = self.exists(date - trading_day, asset)
existed_yesterday = self.exists(date - self.trading_day, asset)
if exists_today and existed_yesterday:
latest = results.loc[asset, 'close']
self.assertEqual(latest, self.expected_close(date, asset))
@@ -437,7 +436,7 @@ class PipelineAlgorithmTestCase(WithBcolzEquityDailyBarReaderFromCSVs,
raw_vwap[:split_loc - 1],
adj_vwap[split_loc - 1:]
]
).shift(1, trading_day)
).shift(1, self.trading_calendar.day)
# Make sure all the expected vwaps have the same dates.
vwap_dates = vwaps[1][self.AAPL].index
@@ -449,11 +448,13 @@ class PipelineAlgorithmTestCase(WithBcolzEquityDailyBarReaderFromCSVs,
# Spot check expectations near the AAPL split.
# length 1 vwap for the morning before the split should be the close
# price of the previous day.
before_split = vwaps[1][AAPL].loc[split_date - trading_day]
before_split = vwaps[1][AAPL].loc[split_date -
self.trading_calendar.day]
assert_almost_equal(before_split, 647.3499, decimal=2)
assert_almost_equal(
before_split,
raw[AAPL].loc[split_date - (2 * trading_day), 'close'],
raw[AAPL].loc[split_date - (2 * self.trading_calendar.day),
'close'],
decimal=2,
)
@@ -463,13 +464,15 @@ class PipelineAlgorithmTestCase(WithBcolzEquityDailyBarReaderFromCSVs,
assert_almost_equal(on_split, 645.5700 / split_ratio, decimal=2)
assert_almost_equal(
on_split,
raw[AAPL].loc[split_date - trading_day, 'close'] / split_ratio,
raw[AAPL].loc[split_date -
self.trading_calendar.day, 'close'] / split_ratio,
decimal=2,
)
# length 1 vwap on the day after the split should be the as-traded
# close on the split day.
after_split = vwaps[1][AAPL].loc[split_date + trading_day]
after_split = vwaps[1][AAPL].loc[split_date +
self.trading_calendar.day]
assert_almost_equal(after_split, 93.69999, decimal=2)
assert_almost_equal(
after_split,
@@ -601,7 +604,7 @@ class PipelineAlgorithmTestCase(WithBcolzEquityDailyBarReaderFromCSVs,
# For ensuring we call before_trading_start.
count = [0]
current_day = default_nyse_schedule.next_execution_day(
current_day = self.trading_calendar.next_session_label(
self.pipeline_loader.raw_price_loader.last_available_dt,
)
+1 -1
View File
@@ -6616,4 +6616,4 @@
2016-04-01 00:00:00+00:00,2016-04-01 13:31:00+00:00,2016-04-01 20:00:00+00:00
2016-04-04 00:00:00+00:00,2016-04-04 13:31:00+00:00,2016-04-04 20:00:00+00:00
2016-04-05 00:00:00+00:00,2016-04-05 13:31:00+00:00,2016-04-05 20:00:00+00:00
2016-04-06 00:00:00+00:00,2016-04-06 13:31:00+00:00,2016-04-06 20:00:00+00:00
2016-04-06 00:00:00+00:00,2016-04-06 13:31:00+00:00,2016-04-06 20:00:00+00:00
1 market_open market_close
6616 2016-04-01 00:00:00+00:00 2016-04-01 13:31:00+00:00 2016-04-01 20:00:00+00:00
6617 2016-04-04 00:00:00+00:00 2016-04-04 13:31:00+00:00 2016-04-04 20:00:00+00:00
6618 2016-04-05 00:00:00+00:00 2016-04-05 13:31:00+00:00 2016-04-05 20:00:00+00:00
6619 2016-04-06 00:00:00+00:00 2016-04-06 13:31:00+00:00 2016-04-06 20:00:00+00:00
@@ -27,7 +27,7 @@ def pricing_for_sid(sid):
def column(name):
return np.arange(252) + 1 + sid * 10000 + modifier[name] * 1000
trading_days = get_calendar('NYSE').all_trading_days
trading_days = get_calendar('NYSE').all_sessions
return pd.DataFrame(
data={
+8 -16
View File
@@ -1,5 +1,5 @@
#
# Copyright 2015 Quantopian, Inc.
# Copyright 2016 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -13,9 +13,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import datetime
import numpy as np
import pytz
import pandas as pd
import zipline.finance.risk as risk
from zipline.utils import factory
@@ -31,20 +30,13 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
def init_instance_fixtures(self):
super(TestRisk, self).init_instance_fixtures()
start_date = datetime.datetime(
year=2006,
month=1,
day=1,
hour=0,
minute=0,
tzinfo=pytz.utc)
end_date = datetime.datetime(
year=2006, month=12, day=29, tzinfo=pytz.utc)
start_session = pd.Timestamp("2006-01-01", tz='UTC')
end_session = pd.Timestamp("2006-12-29", tz='UTC')
self.sim_params = SimulationParameters(
period_start=start_date,
period_end=end_date,
trading_schedule=self.trading_schedule,
start_session=start_session,
end_session=end_session,
trading_calendar=self.trading_calendar,
)
self.algo_returns_06 = factory.create_returns_from_list(
@@ -55,7 +47,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
self.cumulative_metrics_06 = risk.RiskMetricsCumulative(
self.sim_params,
treasury_curves=self.env.treasury_curves,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
for dt, returns in answer_key.RETURNS_DATA.iterrows():
+60 -73
View File
@@ -1,5 +1,5 @@
#
# Copyright 2013 Quantopian, Inc.
# Copyright 2016 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -15,6 +15,7 @@
import datetime
import calendar
import pandas as pd
import numpy as np
import pytz
@@ -39,20 +40,17 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
def init_instance_fixtures(self):
super(TestRisk, self).init_instance_fixtures()
start_date = datetime.datetime(
year=2006,
month=1,
day=1,
hour=0,
minute=0,
tzinfo=pytz.utc)
end_date = datetime.datetime(
year=2006, month=12, day=31, tzinfo=pytz.utc)
start_session = pd.Timestamp("2006-01-01", tz='UTC')
end_session = self.trading_calendar.minute_to_session_label(
pd.Timestamp("2006-12-31", tz='UTC'),
direction="previous"
)
self.sim_params = SimulationParameters(
period_start=start_date,
period_end=end_date,
trading_schedule=self.trading_schedule,
start_session=start_session,
end_session=end_session,
trading_calendar=self.trading_calendar,
)
self.algo_returns_06 = factory.create_returns_from_list(
@@ -67,28 +65,14 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
self.algo_returns_06,
self.sim_params,
benchmark_returns=self.benchmark_returns_06,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
)
start_08 = datetime.datetime(
year=2008,
month=1,
day=1,
hour=0,
minute=0,
tzinfo=pytz.utc)
end_08 = datetime.datetime(
year=2008,
month=12,
day=31,
tzinfo=pytz.utc
)
self.sim_params08 = SimulationParameters(
period_start=start_08,
period_end=end_08,
trading_schedule=self.trading_schedule,
start_session=pd.Timestamp("2008-01-01", tz='UTC'),
end_session=pd.Timestamp("2008-12-31", tz='UTC'),
trading_calendar=self.trading_calendar,
)
def test_factory(self):
@@ -106,7 +90,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
returns.index[0],
returns.index[-1],
returns,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
benchmark_returns=self.env.benchmark_returns,
treasury_curves=self.env.treasury_curves,
)
@@ -134,7 +118,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
def test_trading_days_06(self):
returns = factory.create_returns_from_range(self.sim_params)
metrics = risk.RiskReport(returns, self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
benchmark_returns=self.env.benchmark_returns)
self.assertEqual([x.num_trading_days for x in metrics.year_periods],
@@ -361,7 +345,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
def test_benchmark_returns_08(self):
returns = factory.create_returns_from_range(self.sim_params08)
metrics = risk.RiskReport(returns, self.sim_params08,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
benchmark_returns=self.env.benchmark_returns)
@@ -410,7 +394,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
def test_trading_days_08(self):
returns = factory.create_returns_from_range(self.sim_params08)
metrics = risk.RiskReport(returns, self.sim_params08,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
benchmark_returns=self.env.benchmark_returns)
self.assertEqual([x.num_trading_days for x in metrics.year_periods],
@@ -422,7 +406,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
def test_benchmark_volatility_08(self):
returns = factory.create_returns_from_range(self.sim_params08)
metrics = risk.RiskReport(returns, self.sim_params08,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
benchmark_returns=self.env.benchmark_returns)
@@ -473,7 +457,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
def test_treasury_returns_06(self):
returns = factory.create_returns_from_range(self.sim_params)
metrics = risk.RiskReport(returns, self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
benchmark_returns=self.env.benchmark_returns)
self.assertEqual([round(x.treasury_period_return, 4)
@@ -518,52 +502,55 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
[0.0500])
def test_benchmarkrange(self):
self.check_year_range(
datetime.datetime(
year=2008, month=1, day=1, tzinfo=pytz.utc),
2)
start_session = self.trading_calendar.minute_to_session_label(
pd.Timestamp("2008-01-01", tz='UTC')
)
end_session = self.trading_calendar.minute_to_session_label(
pd.Timestamp("2010-01-01", tz='UTC'), direction="previous"
)
sim_params = SimulationParameters(
start_session=start_session,
end_session=end_session,
trading_calendar=self.trading_calendar,
)
returns = factory.create_returns_from_range(sim_params)
metrics = risk.RiskReport(returns, self.sim_params,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
benchmark_returns=self.env.benchmark_returns)
self.check_metrics(metrics, 24, start_session)
# self.check_year_range(
# datetime.datetime(
# year=2008, month=1, day=1, tzinfo=pytz.utc),
# 2)
def test_partial_month(self):
start = datetime.datetime(
year=1991,
month=1,
day=1,
hour=0,
minute=0,
tzinfo=pytz.utc)
start_session = self.trading_calendar.minute_to_session_label(
pd.Timestamp("1991-01-01", tz='UTC')
)
# 1992 and 1996 were leap years
total_days = 365 * 5 + 2
end = start + datetime.timedelta(days=total_days)
end_session = start_session + datetime.timedelta(days=total_days)
sim_params90s = SimulationParameters(
period_start=start,
period_end=end,
trading_schedule=self.trading_schedule,
start_session=start_session,
end_session=end_session,
trading_calendar=self.trading_calendar,
)
returns = factory.create_returns_from_range(sim_params90s)
returns = returns[:-10] # truncate the returns series to end mid-month
metrics = risk.RiskReport(returns, sim_params90s,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
benchmark_returns=self.env.benchmark_returns)
total_months = 60
self.check_metrics(metrics, total_months, start)
def check_year_range(self, start_date, years):
sim_params = SimulationParameters(
period_start=start_date,
period_end=start_date.replace(year=(start_date.year + years)),
trading_schedule=self.trading_schedule,
)
returns = factory.create_returns_from_range(sim_params)
metrics = risk.RiskReport(returns, self.sim_params,
trading_schedule=self.trading_schedule,
treasury_curves=self.env.treasury_curves,
benchmark_returns=self.env.benchmark_returns)
total_months = years * 12
self.check_metrics(metrics, total_months, start_date)
self.check_metrics(metrics, total_months, start_session)
def check_metrics(self, metrics, total_months, start_date):
"""
@@ -621,7 +608,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
def assert_range_length(self, col, total_months,
period_length, start_date):
if(period_length > total_months):
if (period_length > total_months):
self.assertEqual(len(col), 0)
else:
self.assertEqual(
@@ -633,11 +620,11 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
calculated end:{end}".format(total_months=total_months,
period_length=period_length,
start_date=start_date,
end=col[-1].end_date,
end=col[-1]._end_session,
actual=len(col))
)
self.assert_month(start_date.month, col[-1].end_date.month)
self.assert_last_day(col[-1].end_date)
self.assert_month(start_date.month, col[-1]._end_session.month)
self.assert_last_day(col[-1]._end_session)
def test_sparse_benchmark(self):
benchmark_returns = self.benchmark_returns_06.copy()
@@ -648,7 +635,7 @@ class TestRisk(WithTradingEnvironment, ZiplineTestCase):
self.algo_returns_06,
self.sim_params,
benchmark_returns=benchmark_returns,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
treasury_curves=self.env.treasury_curves,
)
for risk_period in chain.from_iterable(itervalues(report.to_dict())):
+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"
)
)
+15 -19
View File
@@ -124,7 +124,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
def make_equity_minute_bar_data(cls):
for sid in cls.sids:
yield sid, create_minute_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
cls.SIM_PARAMS_START,
cls.SIM_PARAMS_END,
)
@@ -133,7 +133,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
def make_equity_daily_bar_data(cls):
for sid in cls.sids:
yield sid, create_daily_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
cls.SIM_PARAMS_START,
cls.SIM_PARAMS_END,
)
@@ -179,11 +179,11 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
similar) and the new data API(data.current(sid(N), field) and
similar) hit the same code paths on the DataPortal.
"""
test_start_minute = self.trading_schedule.execution_minutes_for_day(
self.sim_params.trading_days[0]
test_start_minute = self.trading_calendar.minutes_for_session(
self.sim_params.sessions[0]
)[1]
test_end_minute = self.trading_schedule.execution_minutes_for_day(
self.sim_params.trading_days[0]
test_end_minute = self.trading_calendar.minutes_for_session(
self.sim_params.sessions[0]
)[-1]
bar_data = BarData(
self.data_portal,
@@ -257,10 +257,10 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
)
test_sim_params = SimulationParameters(
period_start=test_start_minute,
period_end=test_end_minute,
start_session=test_start_minute,
end_session=test_end_minute,
data_frequency="minute",
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
history_algorithm = self.create_algo(
@@ -375,13 +375,9 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("default", ZiplineDeprecationWarning)
sim_params = SimulationParameters(
period_start=self.sim_params.trading_days[1],
period_end=self.sim_params.period_end,
capital_base=self.sim_params.capital_base,
data_frequency=self.sim_params.data_frequency,
emission_rate=self.sim_params.emission_rate,
trading_schedule=self.trading_schedule,
sim_params = self.sim_params.create_new(
self.sim_params.sessions[1],
self.sim_params.end_session
)
algo = self.create_algo(history_algo,
@@ -421,10 +417,10 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
warnings.simplefilter("default", ZiplineDeprecationWarning)
sim_params = SimulationParameters(
period_start=self.sim_params.trading_days[8],
period_end=self.sim_params.trading_days[-1],
start_session=self.sim_params.sessions[8],
end_session=self.sim_params.sessions[-1],
data_frequency="minute",
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
algo = self.create_algo(simple_transforms_algo,
+3 -3
View File
@@ -82,7 +82,7 @@ from zipline.testing.predicates import assert_equal
from zipline.testing.fixtures import (
WithAssetFinder,
ZiplineTestCase,
WithTradingSchedule,
WithTradingCalendar,
)
@@ -396,7 +396,7 @@ class TestFuture(WithAssetFinder, ZiplineTestCase):
TestFuture.asset_finder.lookup_future_symbol('XXX99')
class AssetFinderTestCase(WithTradingSchedule, ZiplineTestCase):
class AssetFinderTestCase(WithTradingCalendar, ZiplineTestCase):
asset_finder_type = AssetFinder
def write_assets(self, **kwargs):
@@ -776,7 +776,7 @@ class AssetFinderTestCase(WithTradingSchedule, ZiplineTestCase):
def test_compute_lifetimes(self):
num_assets = 4
trading_day = self.trading_schedule.day
trading_day = self.trading_calendar.day
first_start = pd.Timestamp('2015-04-01', tz='UTC')
frame = make_rotating_equity_info(
+25 -22
View File
@@ -12,6 +12,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from datetime import timedelta
from nose_parameterized import parameterized
import numpy as np
import pandas as pd
@@ -110,21 +111,21 @@ class TestMinuteBarData(WithBarDataChecks,
# illiquid_split_asset trades every 10 minutes
for sid in (1, cls.SPLIT_ASSET_SID):
yield sid, create_minute_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
cls.equity_minute_bar_days[0],
cls.equity_minute_bar_days[-1],
)
for sid in (2, cls.ILLIQUID_SPLIT_ASSET_SID):
yield sid, create_minute_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
cls.equity_minute_bar_days[0],
cls.equity_minute_bar_days[-1],
10,
)
yield cls.HILARIOUSLY_ILLIQUID_ASSET_SID, create_minute_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
cls.equity_minute_bar_days[0],
cls.equity_minute_bar_days[-1],
50,
@@ -165,8 +166,8 @@ class TestMinuteBarData(WithBarDataChecks,
def test_minute_before_assets_trading(self):
# grab minutes that include the day before the asset start
minutes = self.trading_schedule.execution_minutes_for_day(
self.trading_schedule.previous_execution_day(
minutes = self.trading_calendar.minutes_for_session(
self.trading_calendar.previous_session_label(
self.equity_minute_bar_days[0]
)
)
@@ -194,7 +195,7 @@ class TestMinuteBarData(WithBarDataChecks,
self.assertTrue(asset_value is pd.NaT)
def test_regular_minute(self):
minutes = self.trading_schedule.execution_minutes_for_day(
minutes = self.trading_calendar.minutes_for_session(
self.equity_minute_bar_days[0]
)
@@ -282,11 +283,13 @@ class TestMinuteBarData(WithBarDataChecks,
self.assertEqual(minute, asset2_value)
else:
last_traded_minute = minutes[(idx // 10) * 10]
self.assertEqual(last_traded_minute - 1,
asset2_value)
self.assertEqual(
last_traded_minute - timedelta(minutes=1),
asset2_value
)
def test_minute_of_last_day(self):
minutes = self.trading_schedule.execution_minutes_for_day(
minutes = self.trading_calendar.minutes_for_session(
self.equity_daily_bar_days[-1],
)
@@ -298,13 +301,13 @@ class TestMinuteBarData(WithBarDataChecks,
self.assertTrue(bar_data.can_trade(self.ASSET2))
def test_minute_after_assets_stopped(self):
minutes = self.trading_schedule.execution_minutes_for_day(
self.trading_schedule.next_execution_day(
minutes = self.trading_calendar.minutes_for_session(
self.trading_calendar.next_session_label(
self.equity_minute_bar_days[-1]
)
)
last_trading_minute = self.trading_schedule.execution_minutes_for_day(
last_trading_minute = self.trading_calendar.minutes_for_session(
self.equity_minute_bar_days[-1]
)[-1]
@@ -346,9 +349,9 @@ class TestMinuteBarData(WithBarDataChecks,
)
# ... but that's it's not applied when using spot value
minutes = self.trading_schedule.execution_minutes_for_days_in_range(
start=self.equity_minute_bar_days[0],
end=self.equity_minute_bar_days[1]
minutes = self.trading_calendar.minutes_for_sessions_in_range(
self.equity_minute_bar_days[0],
self.equity_minute_bar_days[1]
)
for idx, minute in enumerate(minutes):
@@ -361,10 +364,10 @@ class TestMinuteBarData(WithBarDataChecks,
def test_spot_price_is_adjusted_if_needed(self):
# on cls.days[1], the first 9 minutes of ILLIQUID_SPLIT_ASSET are
# missing. let's get them.
day0_minutes = self.trading_schedule.execution_minutes_for_day(
day0_minutes = self.trading_calendar.minutes_for_session(
self.equity_minute_bar_days[0]
)
day1_minutes = self.trading_schedule.execution_minutes_for_day(
day1_minutes = self.trading_calendar.minutes_for_session(
self.equity_minute_bar_days[1]
)
@@ -438,7 +441,7 @@ class TestMinuteBarData(WithBarDataChecks,
def test_can_trade_at_midnight(self):
# make sure that if we use `can_trade` at midnight, we don't pretend
# we're in the previous day's last minute
the_day_after = self.trading_schedule.next_execution_day(
the_day_after = self.trading_calendar.next_session_label(
self.equity_minute_bar_days[-1]
)
@@ -609,7 +612,7 @@ class TestDailyBarData(WithBarDataChecks,
def make_equity_daily_bar_data(cls):
for sid in cls.sids:
yield sid, create_daily_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
cls.equity_daily_bar_days[0],
cls.equity_daily_bar_days[-1],
interval=2 - sid % 2
@@ -642,8 +645,8 @@ class TestDailyBarData(WithBarDataChecks,
cls.ASSETS = [cls.ASSET1, cls.ASSET2]
def test_day_before_assets_trading(self):
# use the day before self.equity_daily_bar_days[0]
day = self.trading_schedule.previous_execution_day(
# use the day before self.bcolz_daily_bar_days[0]
day = self.trading_calendar.previous_session_label(
self.equity_daily_bar_days[0]
)
@@ -748,7 +751,7 @@ class TestDailyBarData(WithBarDataChecks,
def test_after_assets_dead(self):
# both assets end on self.day[-1], so let's try the next day
next_day = self.trading_schedule.next_execution_day(
next_day = self.trading_calendar.next_session_label(
self.equity_daily_bar_days[-1]
)
+24 -24
View File
@@ -30,12 +30,12 @@ from zipline.testing import (
from zipline.testing.fixtures import (
WithDataPortal,
WithSimParams,
WithTradingSchedule,
WithTradingCalendar,
ZiplineTestCase,
)
class TestBenchmark(WithDataPortal, WithSimParams, WithTradingSchedule,
class TestBenchmark(WithDataPortal, WithSimParams, WithTradingCalendar,
ZiplineTestCase):
START_DATE = pd.Timestamp('2006-01-03', tz='utc')
END_DATE = pd.Timestamp('2006-12-29', tz='utc')
@@ -70,9 +70,9 @@ class TestBenchmark(WithDataPortal, WithSimParams, WithTradingSchedule,
@classmethod
def make_stock_dividends_data(cls):
declared_date = cls.sim_params.trading_days[45]
ex_date = cls.sim_params.trading_days[50]
record_date = pay_date = cls.sim_params.trading_days[55]
declared_date = cls.sim_params.sessions[45]
ex_date = cls.sim_params.sessions[50]
record_date = pay_date = cls.sim_params.sessions[55]
return pd.DataFrame({
'sid': np.array([4], dtype=np.uint32),
'payment_sid': np.array([5], dtype=np.uint32),
@@ -84,10 +84,10 @@ class TestBenchmark(WithDataPortal, WithSimParams, WithTradingSchedule,
})
def test_normal(self):
days_to_use = self.sim_params.trading_days[1:]
days_to_use = self.sim_params.sessions[1:]
source = BenchmarkSource(
1, self.env, self.trading_schedule, days_to_use, self.data_portal
1, self.env, self.trading_calendar, days_to_use, self.data_portal
)
# should be the equivalent of getting the price history, then doing
@@ -113,14 +113,14 @@ class TestBenchmark(WithDataPortal, WithSimParams, WithTradingSchedule,
BenchmarkSource(
3,
self.env,
self.trading_schedule,
self.sim_params.trading_days[1:],
self.trading_calendar,
self.sim_params.sessions[1:],
self.data_portal
)
self.assertEqual(
'3 does not exist on %s. It started trading on %s.' %
(self.sim_params.trading_days[1], benchmark_start),
(self.sim_params.sessions[1], benchmark_start),
exc.exception.message
)
@@ -128,33 +128,33 @@ class TestBenchmark(WithDataPortal, WithSimParams, WithTradingSchedule,
BenchmarkSource(
3,
self.env,
self.trading_schedule,
self.sim_params.trading_days[120:],
self.trading_calendar,
self.sim_params.sessions[120:],
self.data_portal
)
self.assertEqual(
'3 does not exist on %s. It stopped trading on %s.' %
(self.sim_params.trading_days[-1], benchmark_end),
(self.sim_params.sessions[-1], benchmark_end),
exc2.exception.message
)
def test_asset_IPOed_same_day(self):
# gotta get some minute data up in here.
# add sid 4 for a couple of days
minutes = self.trading_schedule.execution_minutes_for_days_in_range(
self.sim_params.trading_days[0],
self.sim_params.trading_days[5]
minutes = self.trading_calendar.minutes_for_sessions_in_range(
self.sim_params.sessions[0],
self.sim_params.sessions[5]
)
tmp_reader = tmp_bcolz_equity_minute_bar_reader(
self.trading_schedule,
self.trading_schedule.all_execution_days,
self.trading_calendar,
self.trading_calendar.all_sessions,
create_minute_bar_data(minutes, [2]),
)
with tmp_reader as reader:
data_portal = DataPortal(
self.env.asset_finder, self.trading_schedule,
self.env.asset_finder, self.trading_calendar,
first_trading_day=reader.first_trading_day,
equity_minute_reader=reader,
equity_daily_reader=self.bcolz_equity_daily_bar_reader,
@@ -164,12 +164,12 @@ class TestBenchmark(WithDataPortal, WithSimParams, WithTradingSchedule,
source = BenchmarkSource(
2,
self.env,
self.trading_schedule,
self.sim_params.trading_days,
self.trading_calendar,
self.sim_params.sessions,
data_portal
)
days_to_use = self.sim_params.trading_days
days_to_use = self.sim_params.sessions
# first value should be 0.0, coming from daily data
self.assertAlmostEquals(0.0, source.get_value(days_to_use[0]))
@@ -193,8 +193,8 @@ class TestBenchmark(WithDataPortal, WithSimParams, WithTradingSchedule,
with self.assertRaises(InvalidBenchmarkAsset) as exc:
BenchmarkSource(
4, self.env, self.trading_schedule,
self.sim_params.trading_days, self.data_portal
4, self.env, self.trading_calendar,
self.sim_params.sessions, self.data_portal
)
self.assertEqual("4 cannot be used as the benchmark because it has a "
+6 -6
View File
@@ -58,7 +58,7 @@ class BlotterTestCase(WithLogger,
'close': [50, 50],
'volume': [100, 400],
},
index=cls.sim_params.trading_days,
index=cls.sim_params.sessions,
)
yield 25, pd.DataFrame(
{
@@ -68,7 +68,7 @@ class BlotterTestCase(WithLogger,
'close': [50, 50],
'volume': [100, 400],
},
index=cls.sim_params.trading_days,
index=cls.sim_params.sessions,
)
@parameterized.expand([(MarketOrder(), None, None),
@@ -218,10 +218,10 @@ class BlotterTestCase(WithLogger,
blotter.slippage_func = FixedSlippage()
filled_id = blotter.order(asset_24, 100, MarketOrder())
filled_order = None
blotter.current_dt = self.sim_params.trading_days[-1]
blotter.current_dt = self.sim_params.sessions[-1]
bar_data = BarData(
self.data_portal,
lambda: self.sim_params.trading_days[-1],
lambda: self.sim_params.sessions[-1],
self.sim_params.data_frequency,
)
txns, _, closed_orders = blotter.get_transactions(bar_data)
@@ -270,8 +270,8 @@ class BlotterTestCase(WithLogger,
self.assertEqual(cancelled_order.id, held_order.id)
self.assertEqual(cancelled_order.status, ORDER_STATUS.CANCELLED)
for data in ([100, self.sim_params.trading_days[0]],
[400, self.sim_params.trading_days[1]]):
for data in ([100, self.sim_params.sessions[0]],
[400, self.sim_params.sessions[1]]):
# Verify that incoming fills will change the order status.
trade_amt = data[0]
dt = data[1]
+3 -3
View File
@@ -131,7 +131,7 @@ class CommissionAlgorithmTests(WithDataPortal, WithSimParams, ZiplineTestCase):
@classmethod
def make_equity_daily_bar_data(cls):
num_days = len(cls.sim_params.trading_days)
num_days = len(cls.sim_params.sessions)
return trades_by_sid_to_dfs(
{
@@ -141,10 +141,10 @@ class CommissionAlgorithmTests(WithDataPortal, WithSimParams, ZiplineTestCase):
[100.0] * num_days,
timedelta(days=1),
cls.sim_params,
trading_schedule=cls.trading_schedule,
trading_calendar=cls.trading_calendar,
),
},
index=cls.sim_params.trading_days,
index=cls.sim_params.sessions,
)
def get_results(self, algo_code):
+3 -3
View File
@@ -27,7 +27,7 @@ class TestDataPortal(WithTradingEnvironment, ZiplineTestCase):
super(TestDataPortal, self).init_instance_fixtures()
self.data_portal = DataPortal(self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
first_trading_day=None)
def test_bar_count_for_simple_transforms(self):
@@ -42,7 +42,7 @@ class TestDataPortal(WithTradingEnvironment, ZiplineTestCase):
# half an hour into july 9, getting a 4-"day" window should get us
# all the minutes of 7/6, 7/7, 7/8, and 31 minutes of 7/9
july_9_dt = self.trading_schedule.start_and_end(
july_9_dt = self.trading_calendar.open_and_close_for_session(
pd.Timestamp("2015-07-09", tz='UTC')
)[0] + Timedelta("30 minutes")
@@ -65,7 +65,7 @@ class TestDataPortal(WithTradingEnvironment, ZiplineTestCase):
# half an hour into nov 30, getting a 4-"day" window should get us
# all the minutes of 11/24, 11/25, 11/27 (half day!), and 31 minutes
# of 11/30
nov_30_dt = self.trading_schedule.start_and_end(
nov_30_dt = self.trading_calendar.open_and_close_for_session(
pd.Timestamp("2015-11-30", tz='UTC')
)[0] + Timedelta("30 minutes")
-341
View File
@@ -1,341 +0,0 @@
#
# Copyright 2016 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from os.path import (
abspath,
dirname,
join,
)
from unittest import TestCase
from collections import namedtuple
import pandas as pd
import pytz
from pandas import (
read_csv,
datetime,
Timestamp,
Timedelta,
date_range,
)
from pandas.util.testing import assert_frame_equal
from zipline.errors import (
CalendarNameCollision,
InvalidCalendarName,
)
from zipline.utils.calendars.exchange_calendar_nyse import NYSEExchangeCalendar
from zipline.utils.calendars.exchange_calendar import(
register_calendar,
deregister_calendar,
get_calendar,
clear_calendars,
)
class CalendarRegistrationTestCase(TestCase):
def setUp(self):
self.dummy_cal_type = namedtuple('DummyCal', ('name'))
def tearDown(self):
clear_calendars()
def test_register_calendar(self):
# Build a fake calendar
dummy_cal = self.dummy_cal_type('DMY')
# Try to register and retrieve the calendar
register_calendar(dummy_cal)
retr_cal = get_calendar('DMY')
self.assertEqual(dummy_cal, retr_cal)
# Try to register again, expecting a name collision
with self.assertRaises(CalendarNameCollision):
register_calendar(dummy_cal)
# Deregister the calendar and ensure that it is removed
deregister_calendar('DMY')
with self.assertRaises(InvalidCalendarName):
get_calendar('DMY')
def test_force_registration(self):
dummy_nyse = self.dummy_cal_type('NYSE')
# Get the actual NYSE calendar
real_nyse = get_calendar('NYSE')
# Force a registration of the dummy NYSE
register_calendar(dummy_nyse, force=True)
# Ensure that the dummy overwrote the real calendar
retr_cal = get_calendar('NYSE')
self.assertNotEqual(real_nyse, retr_cal)
class ExchangeCalendarTestBase(object):
# Override in subclasses.
answer_key_filename = None
calendar_class = None
@staticmethod
def load_answer_key(filename):
"""
Load a CSV from tests/resources/calendars/{filename}.csv
"""
fullpath = join(
dirname(abspath(__file__)),
'resources',
'calendars',
filename + '.csv',
)
return read_csv(
fullpath,
index_col=0,
# NOTE: Merely passing parse_dates=True doesn't cause pandas to set
# the dtype correctly, and passing all reasonable inputs to the
# dtype kwarg cause read_csv to barf.
parse_dates=[0, 1, 2],
).tz_localize('UTC')
@classmethod
def setupClass(cls):
cls.answers = cls.load_answer_key(cls.answer_key_filename)
cls.start_date = cls.answers.index[0]
cls.end_date = cls.answers.index[-1]
cls.calendar = cls.calendar_class(cls.start_date, cls.end_date)
def test_calculated_against_csv(self):
assert_frame_equal(self.calendar.schedule, self.answers)
def test_is_open_on_minute(self):
for market_minute in self.answers.market_open:
market_minute_utc = market_minute.tz_localize('UTC')
# The exchange should be classified as open on its first minute
self.assertTrue(
self.calendar.is_open_on_minute(market_minute_utc)
)
# Decrement minute by one, to minute where the market was not open
pre_market = market_minute_utc - pd.Timedelta(minutes=1)
self.assertFalse(
self.calendar.is_open_on_minute(pre_market)
)
def test_open_and_close(self):
for index, row in self.answers.iterrows():
o_and_c = self.calendar.open_and_close(index)
self.assertEqual(o_and_c[0],
row['market_open'].tz_localize('UTC'))
self.assertEqual(o_and_c[1],
row['market_close'].tz_localize('UTC'))
def test_no_nones_from_open_and_close(self):
"""
Ensures that, for all minutes in a week, the open_and_close method
never returns a tuple of Nones.
"""
start_week = Timestamp('11/18/2012 12:00AM', tz='EST')
end_week = start_week + Timedelta(days=7)
minutes_in_week = date_range(start_week, end_week, freq='Min')
for dt in minutes_in_week:
open, close = self.calendar.open_and_close(dt)
self.assertIsNotNone(open, "Open value is None")
self.assertIsNotNone(close, "Close value is None")
class NYSECalendarTestCase(ExchangeCalendarTestBase, TestCase):
answer_key_filename = 'nyse'
calendar_class = NYSEExchangeCalendar
def test_newyears(self):
"""
Check whether the ExchangeCalendar contains certain dates.
"""
# January 2012
# Su Mo Tu We Th Fr Sa
# 1 2 3 4 5 6 7
# 8 9 10 11 12 13 14
# 15 16 17 18 19 20 21
# 22 23 24 25 26 27 28
# 29 30 31
start_dt = Timestamp('1/1/12', tz='UTC')
end_dt = Timestamp('12/31/13', tz='UTC')
trading_days = self.calendar.trading_days(start=start_dt, end=end_dt)
day_after_new_years_sunday = datetime(
2012, 1, 2, tzinfo=pytz.utc)
self.assertNotIn(day_after_new_years_sunday,
trading_days.index,
"""
If NYE falls on a weekend, {0} the Monday after is a holiday.
""".strip().format(day_after_new_years_sunday)
)
first_trading_day_after_new_years_sunday = datetime(
2012, 1, 3, tzinfo=pytz.utc)
self.assertIn(first_trading_day_after_new_years_sunday,
trading_days.index,
"""
If NYE falls on a weekend, {0} the Tuesday after is the first trading day.
""".strip().format(first_trading_day_after_new_years_sunday)
)
# January 2013
# Su Mo Tu We Th Fr Sa
# 1 2 3 4 5
# 6 7 8 9 10 11 12
# 13 14 15 16 17 18 19
# 20 21 22 23 24 25 26
# 27 28 29 30 31
new_years_day = datetime(
2013, 1, 1, tzinfo=pytz.utc)
self.assertNotIn(new_years_day,
trading_days.index,
"""
If NYE falls during the week, e.g. {0}, it is a holiday.
""".strip().format(new_years_day)
)
first_trading_day_after_new_years = datetime(
2013, 1, 2, tzinfo=pytz.utc)
self.assertIn(first_trading_day_after_new_years,
trading_days.index,
"""
If the day after NYE falls during the week, {0} \
is the first trading day.
""".strip().format(first_trading_day_after_new_years)
)
def test_thanksgiving(self):
"""
Check ExchangeCalendar Thanksgiving dates.
"""
# November 2005
# Su Mo Tu We Th Fr Sa
# 1 2 3 4 5
# 6 7 8 9 10 11 12
# 13 14 15 16 17 18 19
# 20 21 22 23 24 25 26
# 27 28 29 30
start_dt = Timestamp('1/1/05', tz='UTC')
end_dt = Timestamp('12/31/12', tz='UTC')
trading_days = self.calendar.trading_days(start=start_dt,
end=end_dt)
thanksgiving_with_four_weeks = datetime(
2005, 11, 24, tzinfo=pytz.utc)
self.assertNotIn(thanksgiving_with_four_weeks,
trading_days.index,
"""
If Nov has 4 Thursdays, {0} Thanksgiving is the last Thursady.
""".strip().format(thanksgiving_with_four_weeks)
)
# November 2006
# Su Mo Tu We Th Fr Sa
# 1 2 3 4
# 5 6 7 8 9 10 11
# 12 13 14 15 16 17 18
# 19 20 21 22 23 24 25
# 26 27 28 29 30
thanksgiving_with_five_weeks = datetime(
2006, 11, 23, tzinfo=pytz.utc)
self.assertNotIn(thanksgiving_with_five_weeks,
trading_days.index,
"""
If Nov has 5 Thursdays, {0} Thanksgiving is not the last week.
""".strip().format(thanksgiving_with_five_weeks)
)
first_trading_day_after_new_years_sunday = datetime(
2012, 1, 3, tzinfo=pytz.utc)
self.assertIn(first_trading_day_after_new_years_sunday,
trading_days.index,
"""
If NYE falls on a weekend, {0} the Tuesday after is the first trading day.
""".strip().format(first_trading_day_after_new_years_sunday)
)
def test_day_after_thanksgiving(self):
# November 2012
# Su Mo Tu We Th Fr Sa
# 1 2 3
# 4 5 6 7 8 9 10
# 11 12 13 14 15 16 17
# 18 19 20 21 22 23 24
# 25 26 27 28 29 30
fourth_friday_open = Timestamp('11/23/2012 11:00AM', tz='EST')
fourth_friday = Timestamp('11/23/2012 3:00PM', tz='EST')
self.assertTrue(self.calendar.is_open_on_minute(fourth_friday_open))
self.assertFalse(self.calendar.is_open_on_minute(fourth_friday))
# November 2013
# Su Mo Tu We Th Fr Sa
# 1 2
# 3 4 5 6 7 8 9
# 10 11 12 13 14 15 16
# 17 18 19 20 21 22 23
# 24 25 26 27 28 29 30
fifth_friday_open = Timestamp('11/29/2013 11:00AM', tz='EST')
fifth_friday = Timestamp('11/29/2013 3:00PM', tz='EST')
self.assertTrue(self.calendar.is_open_on_minute(fifth_friday_open))
self.assertFalse(self.calendar.is_open_on_minute(fifth_friday))
def test_early_close_independence_day_thursday(self):
"""
Until 2013, the market closed early the Friday after an
Independence Day on Thursday. Since then, the early close is on
Wednesday.
"""
# July 2002
# Su Mo Tu We Th Fr Sa
# 1 2 3 4 5 6
# 7 8 9 10 11 12 13
# 14 15 16 17 18 19 20
# 21 22 23 24 25 26 27
# 28 29 30 31
wednesday_before = Timestamp('7/3/2002 3:00PM', tz='EST')
friday_after_open = Timestamp('7/5/2002 11:00AM', tz='EST')
friday_after = Timestamp('7/5/2002 3:00PM', tz='EST')
self.assertTrue(self.calendar.is_open_on_minute(wednesday_before))
self.assertTrue(self.calendar.is_open_on_minute(friday_after_open))
self.assertFalse(self.calendar.is_open_on_minute(friday_after))
# July 2013
# Su Mo Tu We Th Fr Sa
# 1 2 3 4 5 6
# 7 8 9 10 11 12 13
# 14 15 16 17 18 19 20
# 21 22 23 24 25 26 27
# 28 29 30 31
wednesday_before = Timestamp('7/3/2013 3:00PM', tz='EST')
friday_after_open = Timestamp('7/5/2013 11:00AM', tz='EST')
friday_after = Timestamp('7/5/2013 3:00PM', tz='EST')
self.assertFalse(self.calendar.is_open_on_minute(wednesday_before))
self.assertTrue(self.calendar.is_open_on_minute(friday_after_open))
self.assertTrue(self.calendar.is_open_on_minute(friday_after))
+2 -2
View File
@@ -109,7 +109,7 @@ class FetcherTestCase(WithResponses,
)
results = test_algo.run(FetcherDataPortal(self.env,
self.trading_schedule))
self.trading_calendar))
return results
@@ -143,7 +143,7 @@ def handle_data(context, data):
# the minutely emission packets here. TradingAlgorithm.run() only
# returns daily packets.
test_algo.data_portal = FetcherDataPortal(self.env,
self.trading_schedule)
self.trading_calendar)
gen = test_algo.get_generator()
perf_packets = list(gen)
+28 -17
View File
@@ -198,7 +198,7 @@ class FinanceTestCase(WithLogger,
data_frequency="minute"
)
minutes = self.trading_schedule.execution_minute_window(
minutes = self.trading_calendar.minutes_window(
sim_params.first_open,
int((trade_interval.total_seconds() / 60) * trade_count)
+ 100)
@@ -216,9 +216,15 @@ class FinanceTestCase(WithLogger,
}
write_bcolz_minute_data(
self.trading_schedule,
self.trading_schedule.execution_days_in_range(minutes[0],
minutes[-1]),
self.trading_calendar,
self.trading_calendar.sessions_in_range(
self.trading_calendar.minute_to_session_label(
minutes[0]
),
self.trading_calendar.minute_to_session_label(
minutes[-1]
)
),
tempdir.path,
iteritems(assets),
)
@@ -226,7 +232,7 @@ class FinanceTestCase(WithLogger,
equity_minute_reader = BcolzMinuteBarReader(tempdir.path)
data_portal = DataPortal(
env.asset_finder, self.trading_schedule,
env.asset_finder, self.trading_calendar,
first_trading_day=equity_minute_reader.first_trading_day,
equity_minute_reader=equity_minute_reader,
)
@@ -235,7 +241,7 @@ class FinanceTestCase(WithLogger,
data_frequency="daily"
)
days = sim_params.trading_days
days = sim_params.sessions
assets = {
1: pd.DataFrame({
@@ -249,12 +255,14 @@ class FinanceTestCase(WithLogger,
}
path = os.path.join(tempdir.path, "testdata.bcolz")
BcolzDailyBarWriter(path, days).write(assets.items())
BcolzDailyBarWriter(path, days, self.trading_calendar).write(
assets.items()
)
equity_daily_reader = BcolzDailyBarReader(path)
data_portal = DataPortal(
env.asset_finder, self.trading_schedule,
env.asset_finder, self.trading_calendar,
first_trading_day=equity_daily_reader.first_trading_day,
equity_daily_reader=equity_daily_reader,
)
@@ -275,7 +283,7 @@ class FinanceTestCase(WithLogger,
else:
alternator = 1
tracker = PerformanceTracker(sim_params, self.trading_schedule,
tracker = PerformanceTracker(sim_params, self.trading_calendar,
self.env)
# replicate what tradesim does by going through every minute or day
@@ -391,10 +399,10 @@ class TradingEnvironmentTestCase(WithLogger,
"""
def test_simulation_parameters(self):
sp = SimulationParameters(
period_start=datetime(2008, 1, 1, tzinfo=pytz.utc),
period_end=datetime(2008, 12, 31, tzinfo=pytz.utc),
start_session=pd.Timestamp("2008-01-01", tz='UTC'),
end_session=pd.Timestamp("2008-12-31", tz='UTC'),
capital_base=100000,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
self.assertTrue(sp.last_close.month == 12)
@@ -412,10 +420,10 @@ class TradingEnvironmentTestCase(WithLogger,
# 27 28 29 30 31
params = SimulationParameters(
period_start=datetime(2007, 12, 31, tzinfo=pytz.utc),
period_end=datetime(2008, 1, 7, tzinfo=pytz.utc),
start_session=pd.Timestamp("2007-12-31", tz='UTC'),
end_session=pd.Timestamp("2008-01-07", tz='UTC'),
capital_base=100000,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
expected_trading_days = (
@@ -431,6 +439,9 @@ class TradingEnvironmentTestCase(WithLogger,
)
num_expected_trading_days = 5
self.assertEquals(num_expected_trading_days, params.days_in_period)
self.assertEquals(
num_expected_trading_days,
len(params.sessions)
)
np.testing.assert_array_equal(expected_trading_days,
params.trading_days.tolist())
params.sessions.tolist())
+58 -59
View File
@@ -79,9 +79,9 @@ class WithHistory(WithDataPortal):
@classmethod
def init_class_fixtures(cls):
super(WithHistory, cls).init_class_fixtures()
cls.trading_days = cls.trading_schedule.execution_days_in_range(
start=cls.TRADING_START_DT,
end=cls.TRADING_END_DT
cls.trading_days = cls.trading_calendar.sessions_in_range(
cls.TRADING_START_DT,
cls.TRADING_END_DT
)
cls.ASSET1 = cls.asset_finder.retrieve_asset(1)
@@ -457,24 +457,24 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
for sid in sids:
asset = cls.asset_finder.retrieve_asset(sid)
data[sid] = create_minute_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
asset.start_date,
asset.end_date,
start_val=2,
)
data[1] = create_minute_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
pd.Timestamp('2014-01-03', tz='utc'),
pd.Timestamp('2016-01-30', tz='utc'),
pd.Timestamp('2016-01-29', tz='utc'),
start_val=2,
)
asset2 = cls.asset_finder.retrieve_asset(2)
data[asset2.sid] = create_minute_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
asset2.start_date,
cls.trading_schedule.previous_execution_day(asset2.end_date),
cls.trading_calendar.previous_session_label(asset2.end_date),
start_val=2,
minute_blacklist=[
pd.Timestamp('2015-01-08 14:31', tz='UTC'),
@@ -489,29 +489,29 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
# the thousands place.
data[cls.MERGER_ASSET_SID] = data[cls.SPLIT_ASSET_SID] = pd.concat((
create_minute_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
pd.Timestamp('2015-01-05', tz='UTC'),
pd.Timestamp('2015-01-05', tz='UTC'),
start_val=8000),
create_minute_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
pd.Timestamp('2015-01-06', tz='UTC'),
pd.Timestamp('2015-01-06', tz='UTC'),
start_val=2000),
create_minute_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
pd.Timestamp('2015-01-07', tz='UTC'),
pd.Timestamp('2015-01-07', tz='UTC'),
start_val=1000),
create_minute_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
pd.Timestamp('2015-01-08', tz='UTC'),
pd.Timestamp('2015-01-08', tz='UTC'),
start_val=1000)
))
asset3 = cls.asset_finder.retrieve_asset(3)
data[3] = create_minute_df_for_asset(
cls.trading_schedule,
cls.trading_calendar,
asset3.start_date,
asset3.end_date,
start_val=2,
@@ -536,12 +536,12 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
end = pd.Timestamp('2014-04-10', tz='UTC')
sim_params = SimulationParameters(
period_start=start,
period_end=end,
start_session=start,
end_session=end,
capital_base=float('1.0e5'),
data_frequency='minute',
emission_rate='daily',
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
test_algo = TradingAlgorithm(
@@ -564,7 +564,7 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
# before any of the adjustments, 1/4 and 1/5
window1 = self.data_portal.get_history_window(
[asset],
self.trading_schedule.start_and_end(jan5)[1],
self.trading_calendar.open_and_close_for_session(jan5)[1],
2,
'1d',
'close'
@@ -625,7 +625,7 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
# before any of the dividends
window1 = self.data_portal.get_history_window(
[asset],
self.trading_schedule.start_and_end(jan5)[1],
self.trading_calendar.open_and_close_for_session(jan5)[1],
2,
'1d',
'close'
@@ -680,8 +680,8 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
def test_minute_before_assets_trading(self):
# since asset2 and asset3 both started trading on 1/5/2015, let's do
# some history windows that are completely before that
minutes = self.trading_schedule.execution_minutes_for_day(
self.trading_schedule.previous_execution_day(pd.Timestamp(
minutes = self.trading_calendar.minutes_for_session(
self.trading_calendar.previous_session_label(pd.Timestamp(
'2015-01-05', tz='UTC'
))
)[0:60]
@@ -730,7 +730,7 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
# 10 minutes
asset = self.env.asset_finder.retrieve_asset(sid)
minutes = self.trading_schedule.execution_minutes_for_day(
minutes = self.trading_calendar.minutes_for_session(
pd.Timestamp('2015-01-05', tz='UTC')
)[0:60]
@@ -741,8 +741,11 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
def test_minute_midnight(self):
midnight = pd.Timestamp('2015-01-06', tz='UTC')
last_minute = self.trading_schedule.start_and_end(
self.trading_schedule.previous_execution_day(midnight)
last_minute = self.trading_calendar.open_and_close_for_session(
self.trading_calendar.minute_to_session_label(
midnight,
direction="previous"
)
)[1]
midnight_bar_data = \
@@ -761,7 +764,7 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
def test_minute_after_asset_stopped(self):
# SHORT_ASSET's last day was 2015-01-06
# get some history windows that straddle the end
minutes = self.trading_schedule.execution_minutes_for_day(
minutes = self.trading_calendar.minutes_for_session(
pd.Timestamp('2015-01-07', tz='UTC')
)[0:60]
@@ -856,7 +859,7 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
# before any of the adjustments, last 10 minutes of jan 5
window1 = self.data_portal.get_history_window(
[asset],
self.trading_schedule.start_and_end(jan5)[1],
self.trading_calendar.open_and_close_for_session(jan5)[1],
10,
'1m',
'close'
@@ -1105,21 +1108,21 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
def test_minute_different_lifetimes(self):
# at trading start, only asset1 existed
day = self.trading_schedule.next_execution_day(self.TRADING_START_DT)
day = self.trading_calendar.next_session_label(self.TRADING_START_DT)
asset1_minutes = \
self.trading_schedule.execution_minutes_for_days_in_range(
start=self.ASSET1.start_date,
end=self.ASSET1.end_date
self.trading_calendar.minutes_for_sessions_in_range(
self.ASSET1.start_date,
self.ASSET1.end_date
)
asset1_idx = asset1_minutes.searchsorted(
self.trading_schedule.start_and_end(day)[0]
self.trading_calendar.open_and_close_for_session(day)[0]
)
window = self.data_portal.get_history_window(
[self.ASSET1, self.ASSET2],
self.trading_schedule.start_and_end(day)[0],
self.trading_calendar.open_and_close_for_session(day)[0],
100,
'1m',
'close'
@@ -1137,7 +1140,7 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
def test_history_window_before_first_trading_day(self):
# trading_start is 2/3/2014
# get a history window that starts before that, and ends after that
first_day_minutes = self.trading_schedule.execution_minutes_for_day(
first_day_minutes = self.trading_calendar.minutes_for_session(
self.TRADING_START_DT
)
exp_msg = (
@@ -1157,7 +1160,7 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
# January 2015 has both daily and minute data for ASSET2
day = pd.Timestamp('2015-01-07', tz='UTC')
minutes = self.trading_schedule.execution_minutes_for_day(day)
minutes = self.trading_calendar.minutes_for_session(day)
# minute data, baseline:
# Jan 5: 2 to 391
@@ -1221,7 +1224,7 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
# January 2015 has both daily and minute data for ASSET2
day = pd.Timestamp('2015-01-08', tz='UTC')
minutes = self.trading_schedule.execution_minutes_for_day(day)
minutes = self.trading_calendar.minutes_for_session(day)
# minute data, baseline:
# Jan 5: 2 to 391
@@ -1340,28 +1343,27 @@ class DailyEquityHistoryTestCase(WithHistory, ZiplineTestCase):
@classmethod
def create_df_for_asset(cls, start_day, end_day, interval=1,
force_zeroes=False):
days = cls.trading_schedule.execution_days_in_range(start_day,
end_day)
days_count = len(days)
sessions = cls.trading_calendar.sessions_in_range(start_day, end_day)
sessions_count = len(sessions)
# default to 2 because the low array subtracts 1, and we don't
# want to start with a 0
days_arr = np.array(range(2, days_count + 2))
sessions_arr = np.array(range(2, sessions_count + 2))
df = pd.DataFrame(
{
'open': days_arr + 1,
'high': days_arr + 2,
'low': days_arr - 1,
'close': days_arr,
'volume': 100 * days_arr,
'open': sessions_arr + 1,
'high': sessions_arr + 2,
'low': sessions_arr - 1,
'close': sessions_arr,
'volume': 100 * sessions_arr,
},
index=days,
index=sessions,
)
if interval > 1:
counter = 0
while counter < days_count:
while counter < sessions_count:
df[counter:(counter + interval - 1)] = 0
counter += interval
@@ -1370,9 +1372,9 @@ class DailyEquityHistoryTestCase(WithHistory, ZiplineTestCase):
def test_daily_before_assets_trading(self):
# asset2 and asset3 both started trading in 2015
days = self.trading_schedule.execution_days_in_range(
start=pd.Timestamp('2014-12-15', tz='UTC'),
end=pd.Timestamp('2014-12-18', tz='UTC'),
days = self.trading_calendar.sessions_in_range(
pd.Timestamp('2014-12-15', tz='UTC'),
pd.Timestamp('2014-12-18', tz='UTC'),
)
for idx, day in enumerate(days):
@@ -1406,12 +1408,9 @@ class DailyEquityHistoryTestCase(WithHistory, ZiplineTestCase):
# 10 days
# get the first 30 days of 2015
jan5 = pd.Timestamp('2015-01-04')
jan5 = pd.Timestamp('2015-01-05')
days = self.trading_schedule.execution_days_in_range(
start=jan5,
end=self.trading_schedule.add_execution_days(30, jan5)
)
days = self.trading_calendar.sessions_window(jan5, 30)
for idx, day in enumerate(days):
self.verify_regular_dt(idx, day, 'daily')
@@ -1453,9 +1452,9 @@ class DailyEquityHistoryTestCase(WithHistory, ZiplineTestCase):
def test_daily_after_asset_stopped(self):
# SHORT_ASSET trades on 1/5, 1/6, that's it.
days = self.trading_schedule.execution_days_in_range(
start=pd.Timestamp('2015-01-07', tz='UTC'),
end=pd.Timestamp('2015-01-08', tz='UTC')
days = self.trading_calendar.sessions_in_range(
pd.Timestamp('2015-01-07', tz='UTC'),
pd.Timestamp('2015-01-08', tz='UTC')
)
# days has 1/7, 1/8
@@ -1644,7 +1643,7 @@ class DailyEquityHistoryTestCase(WithHistory, ZiplineTestCase):
def test_history_window_before_first_trading_day(self):
# trading_start is 2/3/2014
# get a history window that starts before that, and ends after that
second_day = self.trading_schedule.next_execution_day(
second_day = self.trading_calendar.next_session_label(
self.TRADING_START_DT
)
@@ -1673,7 +1672,7 @@ class DailyEquityHistoryTestCase(WithHistory, ZiplineTestCase):
# Use a minute to force minute mode.
first_minute = \
self.trading_schedule.schedule.market_open[self.TRADING_START_DT]
self.trading_calendar.schedule.market_open[self.TRADING_START_DT]
with self.assertRaisesRegexp(HistoryWindowStartsBeforeData, exp_msg):
self.data_portal.get_history_window(
@@ -1803,7 +1802,7 @@ class MinuteToDailyAggregationTestCase(WithBcolzEquityMinuteBarReader,
# Set up a fresh data portal for each test, since order of calling
# needs to be tested.
self.equity_daily_aggregator = DailyHistoryAggregator(
self.trading_schedule.schedule.market_open,
self.trading_calendar.schedule.market_open,
self.bcolz_equity_minute_bar_reader,
)
+97 -88
View File
@@ -57,10 +57,10 @@ from zipline.testing.fixtures import (
WithSimParams,
WithTmpDir,
WithTradingEnvironment,
WithTradingSchedule,
WithTradingCalendar,
ZiplineTestCase,
)
from zipline.utils.calendars import default_nyse_schedule
from zipline.utils.calendars import get_calendar
logger = logging.getLogger('Test Perf Tracking')
@@ -177,13 +177,13 @@ def calculate_results(sim_params,
splits = splits or {}
commissions = commissions or {}
perf_tracker = perf.PerformanceTracker(sim_params,
default_nyse_schedule,
env)
perf_tracker = perf.PerformanceTracker(
sim_params, get_calendar("NYSE"), env
)
results = []
for date in sim_params.trading_days:
for date in sim_params.sessions:
for txn in filter(lambda txn: txn.dt == date, txns):
# Process txns for this date.
perf_tracker.process_transaction(txn)
@@ -216,7 +216,7 @@ def check_perf_tracker_serialization(perf_tracker):
'txn_count',
'market_open',
'last_close',
'period_start',
'start_session',
'day_count',
'capital_base',
'market_close',
@@ -243,9 +243,9 @@ def setup_env_data(env, sim_params, sids, futures_sids=[]):
data = {}
for sid in sids:
data[sid] = {
"start_date": sim_params.trading_days[0],
"end_date": default_nyse_schedule.next_execution_day(
sim_params.trading_days[-1]
"start_date": sim_params.sessions[0],
"end_date": get_calendar("NYSE").next_session_label(
sim_params.sessions[-1]
)
}
@@ -254,9 +254,10 @@ def setup_env_data(env, sim_params, sids, futures_sids=[]):
futures_data = {}
for future_sid in futures_sids:
futures_data[future_sid] = {
"start_date": sim_params.trading_days[0],
"end_date": default_nyse_schedule.next_execution_day(
sim_params.trading_days[-1]
"start_date": sim_params.sessions[0],
# (obviously) FIXME once we have a future calendar
"end_date": get_calendar("NYSE").next_session_label(
sim_params.sessions[-1]
),
"multiplier": 100
}
@@ -280,7 +281,7 @@ class TestSplitPerformance(WithSimParams, WithTmpDir, ZiplineTestCase):
# if multiple positions all have splits at the same time, verify that
# the total leftover cash is correct
perf_tracker = perf.PerformanceTracker(self.sim_params,
self.trading_schedule,
self.trading_calendar,
self.env)
asset1 = self.asset_finder.retrieve_asset(1)
@@ -310,14 +311,14 @@ class TestSplitPerformance(WithSimParams, WithTmpDir, ZiplineTestCase):
[100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
# set up a long position in sid 1
# 100 shares at $20 apiece = $2000 position
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.tmpdir,
self.sim_params,
{1: events},
@@ -422,7 +423,7 @@ class TestDividendPerformance(WithSimParams,
after = factory.get_next_trading_dt(
before,
timedelta(days=1),
self.trading_schedule,
self.trading_calendar,
)
self.assertEqual(after.hour, 13)
@@ -434,7 +435,7 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
@@ -442,7 +443,7 @@ class TestDividendPerformance(WithSimParams,
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.trading_schedule.all_execution_days,
self.trading_calendar.all_sessions,
)
splits = mergers = create_empty_splits_mergers_frame()
dividends = pd.DataFrame({
@@ -457,7 +458,7 @@ class TestDividendPerformance(WithSimParams,
adjustment_reader = SQLiteAdjustmentReader(dbpath)
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{1: events},
@@ -500,7 +501,7 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
@@ -508,7 +509,7 @@ class TestDividendPerformance(WithSimParams,
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.trading_schedule.all_execution_days,
self.trading_calendar.all_sessions
)
splits = mergers = create_empty_splits_mergers_frame()
dividends = pd.DataFrame({
@@ -534,7 +535,7 @@ class TestDividendPerformance(WithSimParams,
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
events,
@@ -575,7 +576,7 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar
)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
@@ -583,7 +584,7 @@ class TestDividendPerformance(WithSimParams,
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.trading_schedule.all_execution_days,
self.trading_calendar.all_sessions
)
splits = mergers = create_empty_splits_mergers_frame()
dividends = pd.DataFrame({
@@ -599,7 +600,7 @@ class TestDividendPerformance(WithSimParams,
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{1: events},
@@ -637,7 +638,7 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
@@ -645,7 +646,7 @@ class TestDividendPerformance(WithSimParams,
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.trading_schedule.all_execution_days,
self.trading_calendar.all_sessions,
)
splits = mergers = create_empty_splits_mergers_frame()
dividends = pd.DataFrame({
@@ -661,7 +662,7 @@ class TestDividendPerformance(WithSimParams,
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{1: events},
@@ -693,6 +694,8 @@ class TestDividendPerformance(WithSimParams,
[-1000, -1000, 0, 1000, 1000, 1000])
def test_buy_and_sell_before_ex(self):
# need a six-day simparam
# post some trades in the market
events = factory.create_trade_history(
self.asset1,
@@ -700,14 +703,14 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.trading_schedule.all_execution_days,
self.trading_calendar.all_sessions,
)
splits = mergers = create_empty_splits_mergers_frame()
@@ -724,7 +727,7 @@ class TestDividendPerformance(WithSimParams,
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{1: events},
@@ -761,21 +764,21 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
pay_date = self.sim_params.first_open
# find pay date that is much later.
for i in range(30):
pay_date = factory.get_next_trading_dt(pay_date, oneday,
self.trading_schedule)
self.trading_calendar)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.trading_schedule.all_execution_days,
self.trading_calendar.all_sessions,
)
splits = mergers = create_empty_splits_mergers_frame()
dividends = pd.DataFrame({
@@ -791,7 +794,7 @@ class TestDividendPerformance(WithSimParams,
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{1: events},
@@ -829,7 +832,7 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
@@ -837,7 +840,7 @@ class TestDividendPerformance(WithSimParams,
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.trading_schedule.all_execution_days,
self.trading_calendar.all_sessions,
)
splits = mergers = create_empty_splits_mergers_frame()
dividends = pd.DataFrame({
@@ -853,7 +856,7 @@ class TestDividendPerformance(WithSimParams,
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{1: events},
@@ -888,7 +891,7 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
@@ -896,7 +899,7 @@ class TestDividendPerformance(WithSimParams,
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.trading_schedule.all_execution_days,
self.trading_calendar.all_sessions,
)
splits = mergers = create_empty_splits_mergers_frame()
dividends = pd.DataFrame({
@@ -912,7 +915,7 @@ class TestDividendPerformance(WithSimParams,
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{1: events},
@@ -941,11 +944,11 @@ class TestDividendPerformance(WithSimParams,
# post some trades in the market
events = factory.create_trade_history(
self.asset1,
[10, 10, 10, 10, 10],
[100, 100, 100, 100, 100],
[10, 10, 10, 10, 10, 10],
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
@@ -953,7 +956,7 @@ class TestDividendPerformance(WithSimParams,
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.trading_schedule.all_execution_days,
self.trading_calendar.all_sessions,
)
splits = mergers = create_empty_splits_mergers_frame()
dividends = pd.DataFrame({
@@ -963,7 +966,11 @@ class TestDividendPerformance(WithSimParams,
'ex_date': np.array([events[-2].dt], dtype='datetime64[ns]'),
'record_date': np.array([events[0].dt], dtype='datetime64[ns]'),
'pay_date': np.array(
[self.trading_schedule.next_execution_day(events[-1].dt)],
[self.trading_calendar.next_session_label(
self.trading_calendar.minute_to_session_label(
events[-1].dt
)
)],
dtype='datetime64[ns]'),
})
writer.write(splits, mergers, dividends)
@@ -973,16 +980,18 @@ class TestDividendPerformance(WithSimParams,
sim_params = create_simulation_parameters(
num_days=6,
capital_base=10e3,
start=self.sim_params.period_start,
end=self.sim_params.period_end
start=self.sim_params.start_session,
end=self.sim_params.end_session
)
sim_params.period_end = events[-1].dt
sim_params.update_internal_from_trading_schedule(self.trading_schedule)
sim_params = sim_params.create_new(
sim_params.start_session,
events[-1].dt
)
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
sim_params,
{1: events},
@@ -997,18 +1006,18 @@ class TestDividendPerformance(WithSimParams,
txns=txns,
)
self.assertEqual(len(results), 5)
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, 0.0, 0.0])
self.assertEqual(cumulative_returns, [0.0, 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])
self.assertEqual(daily_returns, [0.0, 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, [-1000, 0, 0, 0, 0])
self.assertEqual(cash_flows, [-1000, 0, 0, 0, 0, 0])
cumulative_cash_flows = \
[event['cumulative_perf']['capital_used'] for event in results]
self.assertEqual(cumulative_cash_flows,
[-1000, -1000, -1000, -1000, -1000])
[-1000, -1000, -1000, -1000, -1000, -1000])
class TestDividendPerformanceHolidayStyle(TestDividendPerformance):
@@ -1022,7 +1031,7 @@ class TestDividendPerformanceHolidayStyle(TestDividendPerformance):
END_DATE = pd.Timestamp('2003-12-08', tz='utc')
class TestPositionPerformance(WithInstanceTmpDir, WithTradingSchedule,
class TestPositionPerformance(WithInstanceTmpDir, WithTradingCalendar,
ZiplineTestCase):
def create_environment_stuff(self,
num_days=4,
@@ -1072,7 +1081,7 @@ class TestPositionPerformance(WithInstanceTmpDir, WithTradingSchedule,
[100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
trades_2 = factory.create_trade_history(
@@ -1081,12 +1090,12 @@ class TestPositionPerformance(WithInstanceTmpDir, WithTradingSchedule,
[100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{1: trades_1, 2: trades_2}
@@ -1178,12 +1187,12 @@ class TestPositionPerformance(WithInstanceTmpDir, WithTradingSchedule,
[100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{1: trades})
@@ -1270,12 +1279,12 @@ class TestPositionPerformance(WithInstanceTmpDir, WithTradingSchedule,
[100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{1: trades})
@@ -1284,8 +1293,8 @@ class TestPositionPerformance(WithInstanceTmpDir, WithTradingSchedule,
self.sim_params.data_frequency)
pp = perf.PerformancePeriod(1000.0, self.env.asset_finder,
self.sim_params.data_frequency,
period_open=self.sim_params.period_start,
period_close=self.sim_params.period_end)
period_open=self.sim_params.start_session,
period_close=self.sim_params.end_session)
pp.position_tracker = pt
pt.execute_transaction(txn)
@@ -1386,14 +1395,14 @@ single short-sale transaction"""
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
trades_1 = trades[:-2]
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{1: trades})
@@ -1620,12 +1629,12 @@ cost of sole txn in test"
[100, 100, 100, 100],
oneday,
sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{3: trades}
@@ -1740,12 +1749,12 @@ single short-sale transaction"""
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{3: trades}
@@ -1985,12 +1994,12 @@ trade after cover"""
[100, 100, 100, 100, 100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{1: trades})
@@ -2072,14 +2081,14 @@ shares in position"
[100, 100, 100, 100, 100],
oneday,
self.sim_params,
self.trading_schedule,
self.trading_calendar,
)
trades = factory.create_trade_history(*history_args)
transactions = factory.create_txn_history(*history_args)[:4]
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{1: trades})
@@ -2090,8 +2099,8 @@ shares in position"
1000.0,
self.env.asset_finder,
self.sim_params.data_frequency,
period_open=self.sim_params.period_start,
period_close=self.sim_params.trading_days[-1]
period_open=self.sim_params.start_session,
period_close=self.sim_params.sessions[-1]
)
pp.position_tracker = pt
@@ -2198,7 +2207,7 @@ shares in position"
[200, -100, -100, 100, -300, 100, 500, 400],
oneday,
self.sim_params,
self.trading_schedule,
self.trading_calendar,
)
cost_bases = [10, 10, 0, 8, 9, 9, 13, 13.5]
@@ -2234,12 +2243,12 @@ shares in position"
[100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{1: trades})
@@ -2248,8 +2257,8 @@ shares in position"
self.sim_params.data_frequency)
pp = perf.PerformancePeriod(1000.0, self.env.asset_finder,
self.sim_params.data_frequency,
period_open=self.sim_params.period_start,
period_close=self.sim_params.period_end)
period_open=self.sim_params.start_session,
period_close=self.sim_params.end_session)
pp.position_tracker = pt
pt.execute_transaction(txn)
@@ -2279,12 +2288,12 @@ shares in position"
[100, 100, 100, 100],
oneday,
self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
data_portal = create_data_portal_from_trade_history(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
self.instance_tmpdir,
self.sim_params,
{1: trades})
@@ -2293,8 +2302,8 @@ shares in position"
self.sim_params.data_frequency)
pp = perf.PerformancePeriod(1000.0, self.env.asset_finder,
self.sim_params.data_frequency,
period_open=self.sim_params.period_start,
period_close=self.sim_params.period_end)
period_open=self.sim_params.start_session,
period_close=self.sim_params.end_session)
pp.position_tracker = pt
pt.execute_transaction(txn)
+22 -19
View File
@@ -14,7 +14,7 @@ from zipline.testing import (
)
from zipline.testing.fixtures import (
WithLogger,
WithTradingSchedule,
WithTradingCalendar,
ZiplineTestCase,
)
from zipline.utils import factory
@@ -67,7 +67,7 @@ class IterateRLAlgo(TradingAlgorithm):
self.found = True
class SecurityListTestCase(WithLogger, WithTradingSchedule, ZiplineTestCase):
class SecurityListTestCase(WithLogger, WithTradingCalendar, ZiplineTestCase):
@classmethod
def init_class_fixtures(cls):
@@ -75,7 +75,7 @@ class SecurityListTestCase(WithLogger, WithTradingSchedule, ZiplineTestCase):
# this is ugly, but we need to create two different
# TradingEnvironment/DataPortal pairs
start = list(LEVERAGED_ETFS.keys())[0]
cls.start = pd.Timestamp(list(LEVERAGED_ETFS.keys())[0])
end = pd.Timestamp('2015-02-17', tz='utc')
cls.extra_knowledge_date = pd.Timestamp('2015-01-27', tz='utc')
cls.trading_day_before_first_kd = pd.Timestamp('2015-01-23', tz='utc')
@@ -83,15 +83,16 @@ class SecurityListTestCase(WithLogger, WithTradingSchedule, ZiplineTestCase):
cls.env = cls.enter_class_context(tmp_trading_env(
equities=pd.DataFrame.from_records([{
'start_date': start,
'start_date': cls.start,
'end_date': end,
'symbol': symbol
} for symbol in symbols]),
))
cls.sim_params = factory.create_simulation_parameters(
start=start,
start=cls.start,
num_days=4,
trading_schedule=cls.trading_schedule
trading_calendar=cls.trading_calendar
)
cls.sim_params2 = sp2 = factory.create_simulation_parameters(
@@ -100,8 +101,8 @@ class SecurityListTestCase(WithLogger, WithTradingSchedule, ZiplineTestCase):
cls.env2 = cls.enter_class_context(tmp_trading_env(
equities=pd.DataFrame.from_records([{
'start_date': sp2.period_start,
'end_date': sp2.period_end,
'start_date': sp2.start_session,
'end_date': sp2.end_session,
'symbol': symbol
} for symbol in symbols]),
))
@@ -114,7 +115,7 @@ class SecurityListTestCase(WithLogger, WithTradingSchedule, ZiplineTestCase):
tempdir=cls.tempdir,
sim_params=cls.sim_params,
sids=range(0, 5),
trading_schedule=cls.trading_schedule,
trading_calendar=cls.trading_calendar,
)
cls.data_portal2 = create_data_portal(
@@ -122,7 +123,7 @@ class SecurityListTestCase(WithLogger, WithTradingSchedule, ZiplineTestCase):
tempdir=cls.tempdir2,
sim_params=cls.sim_params2,
sids=range(0, 5),
trading_schedule=cls.trading_schedule,
trading_calendar=cls.trading_calendar,
)
def test_iterate_over_restricted_list(self):
@@ -136,7 +137,7 @@ class SecurityListTestCase(WithLogger, WithTradingSchedule, ZiplineTestCase):
# set the knowledge date to the first day of the
# leveraged etf knowledge date.
def get_datetime():
return list(LEVERAGED_ETFS.keys())[0]
return self.start
rl = SecurityListSet(get_datetime, self.env.asset_finder)
# assert that a sample from the leveraged list are in restricted
@@ -217,15 +218,16 @@ class SecurityListTestCase(WithLogger, WithTradingSchedule, ZiplineTestCase):
def test_algo_with_rl_violation_after_knowledge_date(self):
sim_params = factory.create_simulation_parameters(
start=list(
LEVERAGED_ETFS.keys())[0] + timedelta(days=7), num_days=5)
start=self.start + timedelta(days=7),
num_days=5
)
data_portal = create_data_portal(
self.env.asset_finder,
self.tempdir,
sim_params=sim_params,
sids=range(0, 5),
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
algo = RestrictedAlgoWithoutCheck(symbol='BZQ',
@@ -243,8 +245,9 @@ class SecurityListTestCase(WithLogger, WithTradingSchedule, ZiplineTestCase):
set is still disallowed.
"""
sim_params = factory.create_simulation_parameters(
start=list(
LEVERAGED_ETFS.keys())[0] + timedelta(days=7), num_days=4)
start=self.start + timedelta(days=7),
num_days=4
)
with security_list_copy():
add_security_data(['AAPL'], [])
@@ -262,8 +265,8 @@ class SecurityListTestCase(WithLogger, WithTradingSchedule, ZiplineTestCase):
)
equities = pd.DataFrame.from_records([{
'symbol': 'BZQ',
'start_date': sim_params.period_start,
'end_date': sim_params.period_end,
'start_date': sim_params.start_session,
'end_date': sim_params.end_session,
}])
with TempDirectory() as new_tempdir, \
security_list_copy(), \
@@ -277,7 +280,7 @@ class SecurityListTestCase(WithLogger, WithTradingSchedule, ZiplineTestCase):
new_tempdir,
sim_params,
range(0, 5),
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
algo = RestrictedAlgoWithoutCheck(
+8 -7
View File
@@ -53,13 +53,13 @@ class TestTradeSimulation(TestCase):
self.fake_minutely_benchmark):
algo = NoopAlgorithm(sim_params=params)
algo.run(FakeDataPortal())
self.assertEqual(algo.perf_tracker.day_count, 1.0)
self.assertEqual(len(algo.perf_tracker.sim_params.sessions), 1)
@parameterized.expand([('%s_%s_%s' % (num_days, freq, emission_rate),
num_days, freq, emission_rate)
@parameterized.expand([('%s_%s_%s' % (num_sessions, freq, emission_rate),
num_sessions, freq, emission_rate)
for freq in FREQUENCIES
for emission_rate in FREQUENCIES
for num_days in range(1, 4)
for num_sessions in range(1, 4)
if FREQUENCIES[emission_rate] <= FREQUENCIES[freq]])
def test_before_trading_start(self, test_name, num_days, freq,
emission_rate):
@@ -75,9 +75,10 @@ class TestTradeSimulation(TestCase):
algo = BeforeTradingAlgorithm(sim_params=params)
algo.run(FakeDataPortal())
self.assertEqual(algo.perf_tracker.day_count, num_days)
self.assertEqual(len(algo.perf_tracker.sim_params.sessions),
num_days)
self.assertTrue(params.trading_days.equals(
self.assertTrue(params.sessions.equals(
pd.DatetimeIndex(algo.before_trading_at)),
"Expected %s but was %s."
% (params.trading_days, algo.before_trading_at))
% (params.sessions, algo.before_trading_at))
+762
View File
@@ -0,0 +1,762 @@
#
# Copyright 2016 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from os.path import (
abspath,
dirname,
join,
)
from unittest import TestCase
from collections import namedtuple
import numpy as np
import pandas as pd
from pandas import (
read_csv,
Timestamp,
)
from pandas.util.testing import assert_index_equal
from zipline.errors import (
CalendarNameCollision,
InvalidCalendarName,
)
from zipline.utils.calendars.exchange_calendar_nyse import NYSEExchangeCalendar
from zipline.utils.calendars import(
register_calendar,
deregister_calendar,
get_calendar,
clear_calendars,
)
class CalendarRegistrationTestCase(TestCase):
def setUp(self):
self.dummy_cal_type = namedtuple('DummyCal', ('name'))
def tearDown(self):
clear_calendars()
def test_register_calendar(self):
# Build a fake calendar
dummy_cal = self.dummy_cal_type('DMY')
# Try to register and retrieve the calendar
register_calendar(dummy_cal)
retr_cal = get_calendar('DMY')
self.assertEqual(dummy_cal, retr_cal)
# Try to register again, expecting a name collision
with self.assertRaises(CalendarNameCollision):
register_calendar(dummy_cal)
# Deregister the calendar and ensure that it is removed
deregister_calendar('DMY')
with self.assertRaises(InvalidCalendarName):
get_calendar('DMY')
def test_force_registration(self):
dummy_nyse = self.dummy_cal_type('NYSE')
# Get the actual NYSE calendar
real_nyse = get_calendar('NYSE')
# Force a registration of the dummy NYSE
register_calendar(dummy_nyse, force=True)
# Ensure that the dummy overwrote the real calendar
retr_cal = get_calendar('NYSE')
self.assertNotEqual(real_nyse, retr_cal)
class ExchangeCalendarTestBase(object):
# Override in subclasses.
answer_key_filename = None
calendar_class = None
@staticmethod
def load_answer_key(filename):
"""
Load a CSV from tests/resources/calendars/{filename}.csv
"""
fullpath = join(
dirname(abspath(__file__)),
'resources',
'calendars',
filename + '.csv',
)
return read_csv(
fullpath,
index_col=0,
# NOTE: Merely passing parse_dates=True doesn't cause pandas to set
# the dtype correctly, and passing all reasonable inputs to the
# dtype kwarg cause read_csv to barf.
parse_dates=[0, 1, 2],
date_parser=lambda x: pd.Timestamp(x, tz='UTC')
)
@classmethod
def setupClass(cls):
cls.answers = cls.load_answer_key(cls.answer_key_filename)
cls.start_date = cls.answers.index[0]
cls.end_date = cls.answers.index[-1]
cls.calendar = cls.calendar_class(cls.start_date, cls.end_date)
cls.one_minute = pd.Timedelta(minutes=1)
cls.one_hour = pd.Timedelta(hours=1)
def test_calculated_against_csv(self):
assert_index_equal(self.calendar.schedule.index, self.answers.index)
def test_is_open_on_minute(self):
one_minute = pd.Timedelta(minutes=1)
for market_minute in self.answers.market_open:
market_minute_utc = market_minute
# The exchange should be classified as open on its first minute
self.assertTrue(self.calendar.is_open_on_minute(market_minute_utc))
# Decrement minute by one, to minute where the market was not open
pre_market = market_minute_utc - one_minute
self.assertFalse(self.calendar.is_open_on_minute(pre_market))
for market_minute in self.answers.market_close:
close_minute_utc = market_minute
# should be open on its last minute
self.assertTrue(self.calendar.is_open_on_minute(close_minute_utc))
# increment minute by one minute, should be closed
post_market = close_minute_utc + one_minute
self.assertFalse(self.calendar.is_open_on_minute(post_market))
def _verify_minute(self, calendar, minute,
next_open_answer, prev_open_answer,
next_close_answer, prev_close_answer):
self.assertEqual(
calendar.next_open(minute),
next_open_answer
)
self.assertEqual(
self.calendar.previous_open(minute),
prev_open_answer
)
self.assertEqual(
self.calendar.next_close(minute),
next_close_answer
)
self.assertEqual(
self.calendar.previous_close(minute),
prev_close_answer
)
def test_next_prev_open_close(self):
# for each session, check:
# - the minute before the open
# - the first minute of the session
# - the second minute of the session
# - the minute before the close
# - the last minute of the session
# - the first minute after the close
answers_to_use = self.answers[1:-2]
for idx, info in enumerate(answers_to_use.iterrows()):
open_minute = info[1].iloc[0]
close_minute = info[1].iloc[1]
minute_before_open = open_minute - self.one_minute
# answers_to_use starts at the second element of self.answers,
# so self.answers.iloc[idx] is one element before, and
# self.answers.iloc[idx + 2] is one element after the current
# element
previous_open = self.answers.iloc[idx].market_open
next_open = self.answers.iloc[idx + 2].market_open
previous_close = self.answers.iloc[idx].market_close
next_close = self.answers.iloc[idx + 2].market_close
# minute before open
self._verify_minute(
self.calendar, minute_before_open, open_minute, previous_open,
close_minute, previous_close
)
# open minute
self._verify_minute(
self.calendar, open_minute, next_open, previous_open,
close_minute, previous_close
)
# second minute of session
self._verify_minute(
self.calendar, open_minute + self.one_minute, next_open,
open_minute, close_minute, previous_close
)
# minute before the close
self._verify_minute(
self.calendar, close_minute - self.one_minute, next_open,
open_minute, close_minute, previous_close
)
# the close
self._verify_minute(
self.calendar, close_minute, next_open, open_minute,
next_close, previous_close
)
# minute after the close
self._verify_minute(
self.calendar, close_minute + self.one_minute, next_open,
open_minute, next_close, close_minute
)
def test_next_prev_minute(self):
all_minutes = self.calendar.all_minutes
# test 20,000 minutes because it takes too long to do the rest.
for idx, minute in enumerate(all_minutes[1:20000]):
self.assertEqual(
all_minutes[idx + 2],
self.calendar.next_minute(minute)
)
self.assertEqual(
all_minutes[idx],
self.calendar.previous_minute(minute)
)
# test a couple of non-market minutes
for open_minute in self.answers.market_open[1:]:
hour_before_open = open_minute - self.one_hour
self.assertEqual(
open_minute,
self.calendar.next_minute(hour_before_open)
)
for close_minute in self.answers.market_close[1:]:
hour_after_close = close_minute + self.one_hour
self.assertEqual(
close_minute,
self.calendar.previous_minute(hour_after_close)
)
def test_minute_to_session_label(self):
for idx, info in enumerate(self.answers[1:-2].iterrows()):
session_label = info[1].name
open_minute = info[1].iloc[0]
close_minute = info[1].iloc[1]
hour_into_session = open_minute + self.one_hour
minute_before_session = open_minute - self.one_minute
minute_after_session = close_minute + self.one_minute
next_session_label = self.answers.iloc[idx + 2].name
previous_session_label = self.answers.iloc[idx].name
# verify that minutes inside a session resolve correctly
minutes_that_resolve_to_this_session = [
self.calendar.minute_to_session_label(open_minute),
self.calendar.minute_to_session_label(open_minute,
direction="next"),
self.calendar.minute_to_session_label(open_minute,
direction="previous"),
self.calendar.minute_to_session_label(open_minute,
direction="none"),
self.calendar.minute_to_session_label(hour_into_session),
self.calendar.minute_to_session_label(hour_into_session,
direction="next"),
self.calendar.minute_to_session_label(hour_into_session,
direction="previous"),
self.calendar.minute_to_session_label(hour_into_session,
direction="none"),
self.calendar.minute_to_session_label(close_minute),
self.calendar.minute_to_session_label(close_minute,
direction="next"),
self.calendar.minute_to_session_label(close_minute,
direction="previous"),
self.calendar.minute_to_session_label(close_minute,
direction="none"),
self.calendar.minute_to_session_label(minute_before_session),
self.calendar.minute_to_session_label(
minute_before_session,
direction="next"
),
self.calendar.minute_to_session_label(
minute_after_session,
direction="previous"
),
session_label
]
self.assertTrue(all(x == minutes_that_resolve_to_this_session[0]
for x in minutes_that_resolve_to_this_session))
minutes_that_resolve_to_next_session = [
self.calendar.minute_to_session_label(minute_after_session),
self.calendar.minute_to_session_label(minute_after_session,
direction="next"),
next_session_label
]
self.assertTrue(all(x == minutes_that_resolve_to_next_session[0]
for x in minutes_that_resolve_to_next_session))
self.assertEqual(
self.calendar.minute_to_session_label(minute_before_session,
direction="previous"),
previous_session_label
)
# make sure that exceptions are raised at the right time
with self.assertRaises(ValueError):
self.calendar.minute_to_session_label(open_minute, "asdf")
with self.assertRaises(ValueError):
self.calendar.minute_to_session_label(minute_before_session,
direction="none")
def test_next_prev_session(self):
session_labels = self.answers.index[1:-2]
max_idx = len(session_labels) - 1
# the very first session
first_session_label = self.answers.index[0]
with self.assertRaises(ValueError):
self.calendar.previous_session_label(first_session_label)
# all the sessions in the middle
for idx, session_label in enumerate(session_labels):
if idx < max_idx:
self.assertEqual(
self.calendar.next_session_label(session_label),
session_labels[idx + 1]
)
if idx > 0:
self.assertEqual(
self.calendar.previous_session_label(session_label),
session_labels[idx - 1]
)
# the very last session
last_session_label = self.answers.index[-1]
with self.assertRaises(ValueError):
self.calendar.next_session_label(last_session_label)
@staticmethod
def _find_full_session(calendar):
for session_label in calendar.schedule.index:
if session_label not in calendar.early_closes:
return session_label
return None
def test_minutes_for_period(self):
# full session
# find a session that isn't an early close. start from the first
# session, should be quick.
full_session_label = self._find_full_session(self.calendar)
if full_session_label is None:
raise ValueError("Cannot find a full session to test!")
minutes = self.calendar.minutes_for_session(full_session_label)
_open, _close = self.calendar.open_and_close_for_session(
full_session_label
)
np.testing.assert_array_equal(
minutes,
pd.date_range(start=_open, end=_close, freq="min")
)
# early close period
early_close_session_label = self.calendar.early_closes[0]
minutes_for_early_close = \
self.calendar.minutes_for_session(early_close_session_label)
_open, _close = self.calendar.open_and_close_for_session(
early_close_session_label
)
np.testing.assert_array_equal(
minutes_for_early_close,
pd.date_range(start=_open, end=_close, freq="min")
)
def test_sessions_in_range(self):
# pick two sessions
session_count = len(self.calendar.schedule.index)
first_idx = session_count / 3
second_idx = 2 * first_idx
first_session_label = self.calendar.schedule.index[first_idx]
second_session_label = self.calendar.schedule.index[second_idx]
answer_key = \
self.calendar.schedule.index[first_idx:second_idx + 1]
np.testing.assert_array_equal(
answer_key,
self.calendar.sessions_in_range(first_session_label,
second_session_label)
)
def _get_session_block(self):
# find and return a (full session, early close session, full session)
# block
shortened_session = self.calendar.early_closes[0]
shortened_session_idx = \
self.calendar.schedule.index.get_loc(shortened_session)
session_before = self.calendar.schedule.index[
shortened_session_idx - 1
]
session_after = self.calendar.schedule.index[shortened_session_idx + 1]
return [session_before, shortened_session, session_after]
def test_minutes_in_range(self):
sessions = self._get_session_block()
first_open, first_close = self.calendar.open_and_close_for_session(
sessions[0]
)
minute_before_first_open = first_open - self.one_minute
middle_open, middle_close = \
self.calendar.open_and_close_for_session(sessions[1])
last_open, last_close = self.calendar.open_and_close_for_session(
sessions[-1]
)
minute_after_last_close = last_close + self.one_minute
# get all the minutes between first_open and last_close
minutes1 = self.calendar.minutes_in_range(
first_open,
last_close
)
minutes2 = self.calendar.minutes_in_range(
minute_before_first_open,
minute_after_last_close
)
np.testing.assert_array_equal(minutes1, minutes2)
# manually construct the minutes
all_minutes = np.concatenate([
pd.date_range(
start=first_open,
end=first_close,
freq="min"
),
pd.date_range(
start=middle_open,
end=middle_close,
freq="min"
),
pd.date_range(
start=last_open,
end=last_close,
freq="min"
)
])
np.testing.assert_array_equal(all_minutes, minutes1)
def test_minutes_for_sessions_in_range(self):
sessions = self._get_session_block()
minutes = self.calendar.minutes_for_sessions_in_range(
sessions[0],
sessions[-1]
)
# do it manually
session0_minutes = self.calendar.minutes_for_session(sessions[0])
session1_minutes = self.calendar.minutes_for_session(sessions[1])
session2_minutes = self.calendar.minutes_for_session(sessions[2])
concatenated_minutes = np.concatenate([
session0_minutes.values,
session1_minutes.values,
session2_minutes.values
])
np.testing.assert_array_equal(
concatenated_minutes,
minutes.values
)
def test_sessions_window(self):
sessions = self._get_session_block()
np.testing.assert_array_equal(
self.calendar.sessions_window(sessions[0], len(sessions) - 1),
self.calendar.sessions_in_range(sessions[0], sessions[-1])
)
np.testing.assert_array_equal(
self.calendar.sessions_window(
sessions[-1],
-1 * (len(sessions) - 1)),
self.calendar.sessions_in_range(sessions[0], sessions[-1])
)
def test_session_distance(self):
sessions = self._get_session_block()
self.assertEqual(2, self.calendar.session_distance(sessions[0],
sessions[-1]))
def test_open_and_close_for_session(self):
for index, row in self.answers.iterrows():
session_label = row.name
open_answer = row.iloc[0]
close_answer = row.iloc[1]
found_open, found_close = \
self.calendar.open_and_close_for_session(session_label)
self.assertEqual(open_answer, found_open)
self.assertEqual(close_answer, found_close)
class NYSECalendarTestCase(ExchangeCalendarTestBase, TestCase):
answer_key_filename = 'nyse'
calendar_class = NYSEExchangeCalendar
def test_2012(self):
# holidays we expect:
holidays_2012 = [
pd.Timestamp("2012-01-02", tz='UTC'),
pd.Timestamp("2012-01-16", tz='UTC'),
pd.Timestamp("2012-02-20", tz='UTC'),
pd.Timestamp("2012-04-06", tz='UTC'),
pd.Timestamp("2012-05-28", tz='UTC'),
pd.Timestamp("2012-07-04", tz='UTC'),
pd.Timestamp("2012-09-03", tz='UTC'),
pd.Timestamp("2012-11-22", tz='UTC'),
pd.Timestamp("2012-12-25", tz='UTC')
]
for session_label in holidays_2012:
self.assertNotIn(session_label, self.calendar.all_sessions)
# early closes we expect:
early_closes_2012 = [
pd.Timestamp("2012-07-03", tz='UTC'),
pd.Timestamp("2012-11-23", tz='UTC'),
pd.Timestamp("2012-12-24", tz='UTC')
]
for early_close_session_label in early_closes_2012:
self.assertIn(early_close_session_label,
self.calendar.early_closes)
def test_special_holidays(self):
# 9/11
# Sept 11, 12, 13, 14 2001
self.assertNotIn(pd.Period("9/11/2001"), self.calendar.all_sessions)
self.assertNotIn(pd.Period("9/12/2001"), self.calendar.all_sessions)
self.assertNotIn(pd.Period("9/13/2001"), self.calendar.all_sessions)
self.assertNotIn(pd.Period("9/14/2001"), self.calendar.all_sessions)
# Hurricane Sandy
# Oct 29, 30 2012
self.assertNotIn(pd.Period("10/29/2012"), self.calendar.all_sessions)
self.assertNotIn(pd.Period("10/30/2012"), self.calendar.all_sessions)
# various national days of mourning
# Gerald Ford - 1/2/2007
self.assertNotIn(pd.Period("1/2/2007"), self.calendar.all_sessions)
# Ronald Reagan - 6/11/2004
self.assertNotIn(pd.Period("6/11/2004"), self.calendar.all_sessions)
# Richard Nixon - 4/27/1994
self.assertNotIn(pd.Period("4/27/1994"), self.calendar.all_sessions)
def test_new_years(self):
"""
Check whether the TradingCalendar contains certain dates.
"""
# January 2012
# Su Mo Tu We Th Fr Sa
# 1 2 3 4 5 6 7
# 8 9 10 11 12 13 14
# 15 16 17 18 19 20 21
# 22 23 24 25 26 27 28
# 29 30 31
start_session = pd.Timestamp("2012-01-02", tz='UTC')
end_session = pd.Timestamp("2013-12-31", tz='UTC')
sessions = self.calendar.sessions_in_range(start_session, end_session)
day_after_new_years_sunday = pd.Timestamp("2012-01-02",
tz='UTC')
self.assertNotIn(day_after_new_years_sunday, sessions,
"""
If NYE falls on a weekend, {0} the Monday after is a holiday.
""".strip().format(day_after_new_years_sunday)
)
first_trading_day_after_new_years_sunday = pd.Timestamp("2012-01-03",
tz='UTC')
self.assertIn(first_trading_day_after_new_years_sunday, sessions,
"""
If NYE falls on a weekend, {0} the Tuesday after is the first trading day.
""".strip().format(first_trading_day_after_new_years_sunday)
)
# January 2013
# Su Mo Tu We Th Fr Sa
# 1 2 3 4 5
# 6 7 8 9 10 11 12
# 13 14 15 16 17 18 19
# 20 21 22 23 24 25 26
# 27 28 29 30 31
new_years_day = pd.Timestamp("2013-01-01", tz='UTC')
self.assertNotIn(new_years_day, sessions,
"""
If NYE falls during the week, e.g. {0}, it is a holiday.
""".strip().format(new_years_day)
)
first_trading_day_after_new_years = pd.Timestamp("2013-01-02",
tz='UTC')
self.assertIn(first_trading_day_after_new_years, sessions,
"""
If the day after NYE falls during the week, {0} \
is the first trading day.
""".strip().format(first_trading_day_after_new_years)
)
def test_thanksgiving(self):
"""
Check TradingCalendar Thanksgiving dates.
"""
# November 2005
# Su Mo Tu We Th Fr Sa
# 1 2 3 4 5
# 6 7 8 9 10 11 12
# 13 14 15 16 17 18 19
# 20 21 22 23 24 25 26
# 27 28 29 30
start_session_label = pd.Timestamp('2005-01-01', tz='UTC')
end_session_label = pd.Timestamp('2012-12-31', tz='UTC')
sessions = self.calendar.sessions_in_range(start_session_label,
end_session_label)
thanksgiving_with_four_weeks = pd.Timestamp("2005-11-24", tz='UTC')
self.assertNotIn(thanksgiving_with_four_weeks, sessions,
"""
If Nov has 4 Thursdays, {0} Thanksgiving is the last Thursday.
""".strip().format(thanksgiving_with_four_weeks)
)
# November 2006
# Su Mo Tu We Th Fr Sa
# 1 2 3 4
# 5 6 7 8 9 10 11
# 12 13 14 15 16 17 18
# 19 20 21 22 23 24 25
# 26 27 28 29 30
thanksgiving_with_five_weeks = pd.Timestamp("2006-11-23", tz='UTC')
self.assertNotIn(thanksgiving_with_five_weeks, sessions,
"""
If Nov has 5 Thursdays, {0} Thanksgiving is not the last week.
""".strip().format(thanksgiving_with_five_weeks)
)
first_trading_day_after_new_years_sunday = pd.Timestamp("2012-01-03",
tz='UTC')
self.assertIn(first_trading_day_after_new_years_sunday, sessions,
"""
If NYE falls on a weekend, {0} the Tuesday after is the first trading day.
""".strip().format(first_trading_day_after_new_years_sunday)
)
def test_day_after_thanksgiving(self):
# November 2012
# Su Mo Tu We Th Fr Sa
# 1 2 3
# 4 5 6 7 8 9 10
# 11 12 13 14 15 16 17
# 18 19 20 21 22 23 24
# 25 26 27 28 29 30
fourth_friday_open = Timestamp('11/23/2012 11:00AM', tz='EST')
fourth_friday = Timestamp('11/23/2012 3:00PM', tz='EST')
self.assertTrue(self.calendar.is_open_on_minute(fourth_friday_open))
self.assertFalse(self.calendar.is_open_on_minute(fourth_friday))
# November 2013
# Su Mo Tu We Th Fr Sa
# 1 2
# 3 4 5 6 7 8 9
# 10 11 12 13 14 15 16
# 17 18 19 20 21 22 23
# 24 25 26 27 28 29 30
fifth_friday_open = Timestamp('11/29/2013 11:00AM', tz='EST')
fifth_friday = Timestamp('11/29/2013 3:00PM', tz='EST')
self.assertTrue(self.calendar.is_open_on_minute(fifth_friday_open))
self.assertFalse(self.calendar.is_open_on_minute(fifth_friday))
def test_early_close_independence_day_thursday(self):
"""
Until 2013, the market closed early the Friday after an
Independence Day on Thursday. Since then, the early close is on
Wednesday.
"""
# July 2002
# Su Mo Tu We Th Fr Sa
# 1 2 3 4 5 6
# 7 8 9 10 11 12 13
# 14 15 16 17 18 19 20
# 21 22 23 24 25 26 27
# 28 29 30 31
wednesday_before = Timestamp('7/3/2002 3:00PM', tz='EST')
friday_after_open = Timestamp('7/5/2002 11:00AM', tz='EST')
friday_after = Timestamp('7/5/2002 3:00PM', tz='EST')
self.assertTrue(self.calendar.is_open_on_minute(wednesday_before))
self.assertTrue(self.calendar.is_open_on_minute(friday_after_open))
self.assertFalse(self.calendar.is_open_on_minute(friday_after))
# July 2013
# Su Mo Tu We Th Fr Sa
# 1 2 3 4 5 6
# 7 8 9 10 11 12 13
# 14 15 16 17 18 19 20
# 21 22 23 24 25 26 27
# 28 29 30 31
wednesday_before = Timestamp('7/3/2013 3:00PM', tz='EST')
friday_after_open = Timestamp('7/5/2013 11:00AM', tz='EST')
friday_after = Timestamp('7/5/2013 3:00PM', tz='EST')
self.assertFalse(self.calendar.is_open_on_minute(wednesday_before))
self.assertTrue(self.calendar.is_open_on_minute(friday_after_open))
self.assertTrue(self.calendar.is_open_on_minute(friday_after))
-109
View File
@@ -1,109 +0,0 @@
from unittest import TestCase
from pandas import (
Timestamp,
date_range,
DatetimeIndex
)
import numpy as np
from zipline.utils.calendars import (
get_calendar,
ExchangeTradingSchedule,
normalize_date,
)
class TestExchangeTradingSchedule(TestCase):
@classmethod
def setUpClass(cls):
cls.nyse_cal = get_calendar('NYSE')
cls.nyse_exchange_schedule = ExchangeTradingSchedule(cal=cls.nyse_cal)
def test_nyse_data_availability_time(self):
"""
Ensure that the NYSE schedule's data availability time is the market
open.
"""
# This is a time on the day after Thanksgiving when the market was open
test_dt = Timestamp('11/23/2012 11:00AM', tz='EST')
test_date = normalize_date(test_dt)
desired_data_time = Timestamp('11/23/2012 9:31AM', tz='EST')
# Get the data availability time from the NYSE schedule
data_time = self.nyse_exchange_schedule.data_availability_time(
date=test_date
)
# Check the schedule answer against the hard-coded answer
self.assertEqual(data_time, desired_data_time,
"Data availability time is not the market open")
def test_nyse_execution_time(self):
"""
Runs a series of times through both the NYSE calendar and NYSE
schedule, ensuring that the schedule and calendar agree.
"""
# Get all of the minutes in a 24-hour day
start_range = Timestamp('11/23/2012 12:00AM', tz='EST')
end_range = Timestamp('11/23/2012 11:59PM', tz='EST')
time_range = date_range(start_range, end_range, freq='Min')
for dt in time_range:
cal_open = self.nyse_cal.is_open_on_minute(dt)
sched_exec = self.nyse_exchange_schedule.is_executing_on_minute(dt)
self.assertEqual(
cal_open, sched_exec,
"Mismatch between schedule: %s and calendar: %s at time %s"
% (cal_open, sched_exec, dt)
)
def test_execution_minute_window_forward(self):
dt = Timestamp("11/23/2016 15:00", tz='EST').tz_convert("UTC")
# 61 minutes left on 11/23, closed 11/24, only 210 minutes on 11/25
minutes = self.nyse_exchange_schedule.execution_minute_window(dt, 300)
np.testing.assert_array_equal(
minutes[0:61],
DatetimeIndex(
start=Timestamp("2016-11-23 20:00", tz='UTC'),
end=Timestamp("2016-11-23 21:00", tz='UTC'),
freq="min"
)
)
np.testing.assert_array_equal(
minutes[61:271],
DatetimeIndex(
start=Timestamp("2016-11-25 14:31", tz='UTC'),
end=Timestamp("2016-11-25 18:00", tz='UTC'),
freq="min"
)
)
np.testing.assert_array_equal(
minutes[271:],
DatetimeIndex(
start=Timestamp("2016-11-28 14:31", tz='UTC'),
end=Timestamp("2016-11-28 14:59", tz='UTC'),
freq="min"
)
)
def test_execution_minute_window_backward(self):
end_dt = Timestamp("2016-11-28 14:59", tz='UTC')
start_dt = Timestamp("2016-11-23 20:00", tz='UTC')
from_end_minutes = \
self.nyse_exchange_schedule.execution_minute_window(end_dt, -300)
from_start_minutes = \
self.nyse_exchange_schedule.execution_minute_window(start_dt, 300)
np.testing.assert_array_equal(
from_end_minutes,
from_start_minutes
)
+84 -73
View File
@@ -1,5 +1,5 @@
#
# Copyright 2014 Quantopian, Inc.
# Copyright 2016 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -48,16 +48,11 @@ from zipline.utils.events import (
Event,
MAX_MONTH_RANGE,
MAX_WEEK_RANGE,
TradingDayOfMonthRule,
TradingDayOfWeekRule
)
# A day known to be a half day.
HALF_DAY = datetime.datetime(year=2014, month=7, day=3)
# A day known to be a full day.
FULL_DAY = datetime.datetime(year=2014, month=9, day=24)
def param_range(*args):
return ([n] for n in range(*args))
@@ -210,18 +205,18 @@ def minutes_for_days(ordered_days=False):
# optimization in AfterOpen and BeforeClose, we rely on the fact that
# the clock only ever moves forward in a simulation. For those cases,
# we guarantee that the list of trading days we test is ordered.
ordered_day_list = random.sample(list(cal.all_trading_days), 500)
ordered_day_list.sort()
ordered_session_list = random.sample(list(cal.all_sessions), 500)
ordered_session_list.sort()
def day_picker(day):
return ordered_day_list[day]
def session_picker(day):
return ordered_session_list[day]
else:
# Other than AfterOpen and BeforeClose, we don't rely on the the nature
# of the clock, so we don't care.
def day_picker(day):
return random.choice(cal.all_trading_days[:-1])
def session_picker(day):
return random.choice(cal.all_sessions[:-1])
return ((cal.trading_minutes_for_day(day_picker(cnt)),)
return ((cal.minutes_for_session(session_picker(cnt)),)
for cnt in range(500))
@@ -250,11 +245,14 @@ class RuleTestCase(TestCase):
if not self.class_:
return # This is the base class testing, it is always complete.
classes_to_ignore = [TradingDayOfWeekRule, TradingDayOfMonthRule]
dem = {
k for k, v in iteritems(vars(zipline.utils.events))
if isinstance(v, type) and
issubclass(v, self.class_) and
v is not self.class_ and
v not in classes_to_ignore and
not isabstract(v)
}
ds = {
@@ -278,18 +276,18 @@ class TestStatelessRules(RuleTestCase):
cls.nyse_cal = get_calendar('NYSE')
# First day of 09/2014 is closed whereas that for 10/2014 is open
cls.sept_days = cls.nyse_cal.trading_days_in_range(
pd.Timestamp('2014-09-01'),
pd.Timestamp('2014-09-30'),
cls.sept_sessions = cls.nyse_cal.sessions_in_range(
pd.Timestamp('2014-09-01', tz='UTC'),
pd.Timestamp('2014-09-30', tz='UTC'),
)
cls.oct_days = cls.nyse_cal.trading_days_in_range(
pd.Timestamp('2014-10-01'),
pd.Timestamp('2014-10-31'),
cls.oct_sessions = cls.nyse_cal.sessions_in_range(
pd.Timestamp('2014-10-01', tz='UTC'),
pd.Timestamp('2014-10-31', tz='UTC'),
)
cls.sept_week = cls.nyse_cal.trading_minutes_for_days_in_range(
datetime.date(year=2014, month=9, day=21),
datetime.date(year=2014, month=9, day=26),
cls.sept_week = cls.nyse_cal.minutes_for_sessions_in_range(
pd.Timestamp("2014-09-22", tz='UTC'),
pd.Timestamp("2014-09-26", tz='UTC')
)
@subtest(minutes_for_days(), 'ms')
@@ -323,14 +321,18 @@ class TestStatelessRules(RuleTestCase):
else:
self.assertTrue(should_trigger(m))
@subtest(minutes_for_days(), 'ms')
def test_NotHalfDay(self, ms):
cal = get_calendar('NYSE')
def test_NotHalfDay(self):
rule = NotHalfDay()
rule.cal = cal
should_trigger = rule.should_trigger
self.assertTrue(should_trigger(FULL_DAY))
self.assertFalse(should_trigger(HALF_DAY))
rule.cal = self.nyse_cal
half_day_period = pd.Timestamp("2014-07-03", tz='UTC')
full_day_period = pd.Timestamp("2014-09-24", tz='UTC')
for minute in self.nyse_cal.minutes_for_session(half_day_period):
self.assertFalse(rule.should_trigger(minute))
for minute in self.nyse_cal.minutes_for_session(full_day_period):
self.assertTrue(rule.should_trigger(minute))
def test_NthTradingDayOfWeek_day_zero(self):
"""
@@ -340,9 +342,10 @@ class TestStatelessRules(RuleTestCase):
cal = get_calendar('NYSE')
rule = NthTradingDayOfWeek(0)
rule.cal = cal
self.assertTrue(
rule.should_trigger(self.nyse_cal.all_trading_days[0])
first_open = self.nyse_cal.open_and_close_for_session(
self.nyse_cal.all_sessions[0]
)
self.assertTrue(first_open)
@subtest(param_range(MAX_WEEK_RANGE), 'n')
def test_NthTradingDayOfWeek(self, n):
@@ -350,14 +353,18 @@ class TestStatelessRules(RuleTestCase):
rule = NthTradingDayOfWeek(n)
rule.cal = cal
should_trigger = rule.should_trigger
prev_day = self.sept_week[0].date()
prev_period = self.nyse_cal.minute_to_session_label(self.sept_week[0])
n_tdays = 0
for m in self.sept_week:
if prev_day < m.date():
n_tdays += 1
prev_day = m.date()
for minute in self.sept_week:
period = self.nyse_cal.minute_to_session_label(
minute, direction="none"
)
if should_trigger(m):
if prev_period < period:
n_tdays += 1
prev_period = period
if should_trigger(minute):
self.assertEqual(n_tdays, n)
else:
self.assertNotEqual(n_tdays, n)
@@ -368,14 +375,17 @@ class TestStatelessRules(RuleTestCase):
rule = NDaysBeforeLastTradingDayOfWeek(n)
rule.cal = cal
should_trigger = rule.should_trigger
for m in self.sept_week:
if should_trigger(m):
for minute in self.sept_week:
if should_trigger(minute):
n_tdays = 0
date = m.to_datetime().date()
next_date = self.nyse_cal.next_trading_day(date)
while next_date.weekday() > date.weekday():
date = next_date
next_date = self.nyse_cal.next_trading_day(date)
session = self.nyse_cal.minute_to_session_label(
minute,
direction="none"
)
next_session = self.nyse_cal.next_session_label(session)
while next_session.dayofweek > session.dayofweek:
session = next_session
next_session = self.nyse_cal.next_session_label(session)
n_tdays += 1
self.assertEqual(n_tdays, n)
@@ -397,39 +407,40 @@ class TestStatelessRules(RuleTestCase):
for that week, that the trigger is recalculated for next week.
"""
sim_start = pd.Timestamp('01-06-2014', tz='UTC') + \
sim_start = pd.Timestamp('2014-01-06', tz='UTC') + \
timedelta(days=start_offset)
jan_minutes = self.nyse_cal.trading_minutes_for_days_in_range(
datetime.date(year=2014, month=1, day=6) +
timedelta(days=start_offset),
datetime.date(year=2014, month=1, day=31)
delta = timedelta(days=start_offset)
jan_minutes = self.nyse_cal.minutes_for_sessions_in_range(
pd.Timestamp("2014-01-06", tz='UTC') + delta,
pd.Timestamp("2014-01-31", tz='UTC')
)
if type == 'week_start':
rule = NthTradingDayOfWeek
# Expect to trigger on the first trading day of the week, plus the
# offset
trigger_dates = [
trigger_periods = [
pd.Timestamp('2014-01-06', tz='UTC'),
pd.Timestamp('2014-01-13', tz='UTC'),
pd.Timestamp('2014-01-21', tz='UTC'),
pd.Timestamp('2014-01-27', tz='UTC'),
]
trigger_dates = \
[x + timedelta(days=rule_offset) for x in trigger_dates]
trigger_periods = \
[x + timedelta(days=rule_offset) for x in trigger_periods]
else:
rule = NDaysBeforeLastTradingDayOfWeek
# Expect to trigger on the last trading day of the week, minus the
# offset
trigger_dates = [
trigger_periods = [
pd.Timestamp('2014-01-10', tz='UTC'),
pd.Timestamp('2014-01-17', tz='UTC'),
pd.Timestamp('2014-01-24', tz='UTC'),
pd.Timestamp('2014-01-31', tz='UTC'),
]
trigger_dates = \
[x - timedelta(days=rule_offset) for x in trigger_dates]
trigger_periods = \
[x - timedelta(days=rule_offset) for x in trigger_periods]
rule.cal = self.nyse_cal
should_trigger = rule(rule_offset).should_trigger
@@ -437,23 +448,23 @@ class TestStatelessRules(RuleTestCase):
# If offset is 4, there is not enough trading days in the short week,
# and so it should not trigger
if rule_offset == 4:
del trigger_dates[2]
del trigger_periods[2]
# Filter out trigger dates that happen before the simulation starts
trigger_dates = [x for x in trigger_dates if x >= sim_start]
trigger_periods = [x for x in trigger_periods if x >= sim_start]
# Get all the minutes on the trigger dates
trigger_dts = self.nyse_cal.trading_minutes_for_day(trigger_dates[0])
for dt in trigger_dates[1:]:
trigger_dts += self.nyse_cal.trading_minutes_for_day(dt)
trigger_minutes = self.nyse_cal.minutes_for_session(trigger_periods[0])
for period in trigger_periods[1:]:
trigger_minutes += self.nyse_cal.minutes_for_session(period)
expected_n_triggered = len(trigger_dts)
trigger_dts = iter(trigger_dts)
expected_n_triggered = len(trigger_minutes)
trigger_minutes_iter = iter(trigger_minutes)
n_triggered = 0
for m in jan_minutes:
if should_trigger(m):
self.assertEqual(m, next(trigger_dts))
self.assertEqual(m, next(trigger_minutes_iter))
n_triggered += 1
self.assertEqual(n_triggered, expected_n_triggered)
@@ -471,9 +482,9 @@ class TestStatelessRules(RuleTestCase):
should_trigger = composed_rule.should_trigger
week_minutes = self.nyse_cal.trading_minutes_for_days_in_range(
datetime.date(year=2014, month=1, day=6),
datetime.date(year=2014, month=1, day=10)
week_minutes = self.nyse_cal.minutes_for_sessions_in_range(
pd.Timestamp("2014-01-06", tz='UTC'),
pd.Timestamp("2014-01-10", tz='UTC')
)
dt = pd.Timestamp('2014-01-06 14:30:00', tz='UTC')
@@ -495,9 +506,9 @@ class TestStatelessRules(RuleTestCase):
rule = NthTradingDayOfMonth(n)
rule.cal = cal
should_trigger = rule.should_trigger
for days_list in (self.sept_days, self.oct_days):
for n_tdays, d in enumerate(days_list):
for m in self.nyse_cal.trading_minutes_for_day(d):
for sessions_list in (self.sept_sessions, self.oct_sessions):
for n_tdays, session in enumerate(sessions_list):
for m in self.nyse_cal.minutes_for_session(session):
if should_trigger(m):
self.assertEqual(n_tdays, n)
else:
@@ -509,8 +520,8 @@ class TestStatelessRules(RuleTestCase):
rule = NDaysBeforeLastTradingDayOfMonth(n)
rule.cal = cal
should_trigger = rule.should_trigger
for n_days_before, d in enumerate(reversed(self.sept_days)):
for m in self.nyse_cal.trading_minutes_for_day(d):
for n_days_before, session in enumerate(reversed(self.oct_sessions)):
for m in self.nyse_cal.minutes_for_session(session):
if should_trigger(m):
self.assertEqual(n_days_before, n)
else:
+1 -1
View File
@@ -224,7 +224,7 @@ cdef class BarData:
if self._adjust_minutes:
dt = \
self.data_portal.trading_schedule.previous_execution_minute(dt)
self.data_portal.trading_calendar.previous_minute(dt)
return dt
+44 -52
View File
@@ -56,8 +56,7 @@ from zipline.errors import (
CannotOrderDelistedAsset,
UnsupportedCancelPolicy,
SetCancelPolicyPostInit,
OrderInBeforeTradingStart,
ScheduleFunctionWithoutCalendar,
OrderInBeforeTradingStart
)
from zipline.finance.trading import TradingEnvironment
from zipline.finance.blotter import Blotter
@@ -98,10 +97,8 @@ from zipline.utils.api_support import (
from zipline.utils.input_validation import ensure_upper_case, error_keywords
from zipline.utils.cache import CachedObject, Expired
from zipline.utils.calendars import (
default_nyse_schedule,
ExchangeTradingSchedule,
)
from zipline.utils.calendars import get_calendar
import zipline.utils.events
from zipline.utils.events import (
EventManager,
@@ -282,9 +279,9 @@ class TradingAlgorithm(object):
)
# If a schedule has been provided, pop it. Otherwise, use NYSE.
self.trading_schedule = kwargs.pop(
'trading_schedule',
default_nyse_schedule,
self.trading_calendar = kwargs.pop(
'trading_calendar',
get_calendar("NYSE")
)
# set the capital base
@@ -295,11 +292,7 @@ class TradingAlgorithm(object):
capital_base=self.capital_base,
start=kwargs.pop('start', None),
end=kwargs.pop('end', None),
trading_schedule=self.trading_schedule,
)
else:
self.sim_params.update_internal_from_trading_schedule(
self.trading_schedule
trading_calendar=self.trading_calendar,
)
self.perf_tracker = None
@@ -427,7 +420,7 @@ class TradingAlgorithm(object):
if get_loader is not None:
self.engine = SimplePipelineEngine(
get_loader,
self.trading_schedule.all_execution_days,
self.trading_calendar.all_sessions,
self.asset_finder,
)
else:
@@ -500,8 +493,8 @@ class TradingAlgorithm(object):
If the clock property is not set, then create one based on frequency.
"""
if self.sim_params.data_frequency == 'minute':
trading_o_and_c = self.trading_schedule.schedule.ix[
self.sim_params.trading_days]
trading_o_and_c = self.trading_calendar.schedule.ix[
self.sim_params.sessions]
market_opens = trading_o_and_c['market_open'].values.astype(
'datetime64[ns]').astype(np.int64)
market_closes = trading_o_and_c['market_close'].values.astype(
@@ -510,21 +503,21 @@ class TradingAlgorithm(object):
minutely_emission = self.sim_params.emission_rate == "minute"
clock = MinuteSimulationClock(
self.sim_params.trading_days,
self.sim_params.sessions,
market_opens,
market_closes,
minutely_emission
)
return clock
else:
return DailySimulationClock(self.sim_params.trading_days)
return DailySimulationClock(self.sim_params.sessions)
def _create_benchmark_source(self):
return BenchmarkSource(
benchmark_sid=self.benchmark_sid,
env=self.trading_environment,
trading_schedule=self.trading_schedule,
trading_days=self.sim_params.trading_days,
trading_calendar=self.trading_calendar,
sessions=self.sim_params.sessions,
data_portal=self.data_portal,
emission_rate=self.sim_params.emission_rate,
)
@@ -538,12 +531,12 @@ class TradingAlgorithm(object):
# None so that it will be overwritten here.
self.perf_tracker = PerformanceTracker(
sim_params=self.sim_params,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
env=self.trading_environment,
)
# Set the dt initially to the period start by forcing it to change.
self.on_dt_changed(self.sim_params.period_start)
self.on_dt_changed(self.sim_params.start_session)
if not self.initialized:
self.initialize(*self.initialize_args, **self.initialize_kwargs)
@@ -613,12 +606,9 @@ class TradingAlgorithm(object):
# For compatibility with existing examples allow start/end
# to be inferred.
if overwrite_sim_params:
self.sim_params.period_start = data.major_axis[0]
self.sim_params.period_end = data.major_axis[-1]
# Changing period_start and period_close might require
# updating of first_open and last_close.
self.sim_params.update_internal_from_trading_schedule(
trading_schedule=self.trading_schedule
self.sim_params = self.sim_params.create_new(
data.major_axis[0],
data.major_axis[1]
)
copy_panel = data.rename(
@@ -637,12 +627,12 @@ class TradingAlgorithm(object):
)
)
equity_daily_reader = PanelDailyBarReader(
self.trading_schedule.all_execution_days,
self.trading_calendar.all_sessions,
copy_panel,
)
self.data_portal = DataPortal(
self.asset_finder,
self.trading_schedule,
self.trading_calendar,
first_trading_day=equity_daily_reader.first_trading_day,
equity_daily_reader=equity_daily_reader,
)
@@ -743,8 +733,8 @@ class TradingAlgorithm(object):
elif new_sids:
frame_to_write = make_simple_equity_info(
new_sids,
start_date=self.sim_params.period_start,
end_date=self.sim_params.period_end,
start_date=self.sim_params.start_session,
end_date=self.sim_params.end_session,
symbols=map(str, new_sids),
)
elif new_symbols:
@@ -754,7 +744,7 @@ class TradingAlgorithm(object):
frame_to_write = make_simple_equity_info(
sids=fake_sids,
start_date=as_of_date,
end_date=self.sim_params.period_end,
end_date=self.sim_params.end_session,
symbols=new_symbols,
)
else:
@@ -914,9 +904,9 @@ class TradingAlgorithm(object):
pre_func,
post_func,
self.asset_finder,
self.trading_schedule.day,
self.sim_params.period_start,
self.sim_params.period_end,
self.trading_calendar.day,
self.sim_params.start_session,
self.sim_params.end_session,
date_column,
date_format,
timezone,
@@ -992,11 +982,7 @@ class TradingAlgorithm(object):
# Note that the ExchangeTradingSchedule is currently the only
# TradingSchedule class, so this is unlikely to be hit
# TODO The calendar should be a required arg for schedule_function
if not isinstance(self.trading_schedule, ExchangeTradingSchedule):
raise ScheduleFunctionWithoutCalendar(
schedule=self.trading_schedule
)
cal = self.trading_schedule._exchange_calendar
cal = self.trading_calendar
self.add_event(
make_eventrule(date_rule, time_rule, cal, half_days),
@@ -1074,9 +1060,9 @@ class TradingAlgorithm(object):
:func:`zipline.api.set_symbol_lookup_date`
"""
# If the user has not set the symbol lookup date,
# use the period_end as the date for sybmol->sid resolution.
# use the end_session as the date for sybmol->sid resolution.
_lookup_date = self._symbol_lookup_date if self._symbol_lookup_date is not None \
else self.sim_params.period_end
else self.sim_params.end_session
return self.asset_finder.lookup_symbol(
symbol_str,
@@ -1963,7 +1949,7 @@ class TradingAlgorithm(object):
# If we are in before_trading_start, we need to get the window
# as of the previous market minute
adjusted_dt = \
self.data_portal.trading_schedule.previous_execution_minute(
self.trading_calendar.previous_minute(
self.datetime
)
@@ -2223,7 +2209,7 @@ class TradingAlgorithm(object):
# day.
return pd.DataFrame(index=[], columns=data.columns)
def _run_pipeline(self, pipeline, start_date, chunksize):
def _run_pipeline(self, pipeline, start_session, chunksize):
"""
Compute `pipeline`, providing values for at least `start_date`.
@@ -2241,19 +2227,25 @@ class TradingAlgorithm(object):
--------
PipelineEngine.run_pipeline
"""
days = self.trading_schedule.all_execution_days
sessions = self.trading_calendar.all_sessions
# Load data starting from the previous trading day...
start_date_loc = days.get_loc(start_date)
start_date_loc = sessions.get_loc(start_session)
# ...continuing until either the day before the simulation end, or
# until chunksize days of data have been loaded.
sim_end = self.sim_params.last_close.normalize()
end_loc = min(start_date_loc + chunksize, days.get_loc(sim_end))
end_date = days[end_loc]
sim_end_session = self.sim_params.end_session
end_loc = min(
start_date_loc + chunksize,
sessions.get_loc(sim_end_session)
)
end_session = sessions[end_loc]
return \
self.engine.run_pipeline(pipeline, start_date, end_date), end_date
self.engine.run_pipeline(pipeline, start_session, end_session), \
end_session
##################
# End Pipeline API
+1 -1
View File
@@ -32,7 +32,7 @@ from zipline.utils.preprocess import preprocess
from zipline.utils.calendars import get_calendar
nyse_cal = get_calendar('NYSE')
trading_days = nyse_cal.all_trading_days
trading_days = nyse_cal.all_sessions
open_and_closes = nyse_cal.schedule
+41 -32
View File
@@ -467,9 +467,10 @@ class DataPortal(object):
Parameters
----------
env : TradingEnvironment
The trading environment for the simulation. This includes the trading
calendar and benchmark data.
asset_finder : zipline.assets.assets.AssetFinder
The AssetFinder instance used to resolve assets.
trading_calendar: zipline.utils.calendar.exchange_calendar.TradingCalendar
The calendar instance used to provide minute->session information.
first_trading_day : pd.Timestamp
The first trading day for the simulation.
equity_daily_reader : BcolzDailyBarReader, optional
@@ -496,7 +497,7 @@ class DataPortal(object):
"""
def __init__(self,
asset_finder,
trading_schedule,
trading_calendar,
first_trading_day,
equity_daily_reader=None,
equity_minute_reader=None,
@@ -504,7 +505,7 @@ class DataPortal(object):
future_minute_reader=None,
adjustment_reader=None):
self.trading_schedule = trading_schedule
self.trading_calendar = trading_calendar
self.asset_finder = asset_finder
self.views = {}
@@ -536,7 +537,7 @@ class DataPortal(object):
self._equity_daily_reader = equity_daily_reader
if self._equity_daily_reader is not None:
self._equity_history_loader = USEquityDailyHistoryLoader(
self.trading_schedule,
self.trading_calendar,
self._equity_daily_reader,
self._adjustment_reader
)
@@ -546,10 +547,10 @@ class DataPortal(object):
if self._equity_minute_reader is not None:
self._equity_daily_aggregator = DailyHistoryAggregator(
self.trading_schedule.schedule.market_open,
self.trading_calendar.schedule.market_open,
self._equity_minute_reader)
self._equity_minute_history_loader = USEquityMinuteHistoryLoader(
self.trading_schedule,
self.trading_calendar,
self._equity_minute_reader,
self._adjustment_reader
)
@@ -560,19 +561,19 @@ class DataPortal(object):
# Get the first trading minute
self._first_trading_minute, _ = (
self.trading_schedule.start_and_end(self._first_trading_day)
self.trading_calendar.open_and_close_for_session(
self._first_trading_day
)
if self._first_trading_day is not None else (None, None)
)
# Store the locs of the first day and first minute
self._first_trading_day_loc = (
self.trading_schedule.all_execution_days.get_loc(
self.trading_schedule.session_date(self._first_trading_day)
)
self.trading_calendar.all_sessions.get_loc(self._first_trading_day)
if self._first_trading_day is not None else None
)
self._first_trading_minute_loc = (
self.trading_schedule.all_execution_minutes.get_loc(
self.trading_calendar.all_minutes.get_loc(
self._first_trading_minute
)
if self._first_trading_minute is not None else None
@@ -612,9 +613,9 @@ class DataPortal(object):
# asset -> df. In other words,
# self.augmented_sources_map['days_to_cover']['AAPL'] gives us the df
# holding that data.
source_date_index = self.trading_schedule.execution_days_in_range(
start=sim_params.period_start,
end=sim_params.period_end
source_date_index = self.trading_calendar.sessions_in_range(
sim_params.start_session,
sim_params.end_session
)
# Break the source_df up into one dataframe per sid. This lets
@@ -1031,13 +1032,13 @@ class DataPortal(object):
spot_value=value
)
else:
found_dt -= self.trading_schedule.day
found_dt -= self.trading_calendar.day
except NoDataOnDate:
return np.nan
@remember_last
def _get_days_for_window(self, end_date, bar_count):
tds = self.trading_schedule.all_execution_days
tds = self.trading_calendar.all_sessions
end_loc = tds.get_loc(end_date)
start_loc = end_loc - bar_count + 1
if start_loc < self._first_trading_day_loc:
@@ -1096,7 +1097,7 @@ class DataPortal(object):
# get all the minutes for the days NOT including today
for day in days_for_window[:-1]:
minutes = self.trading_schedule.execution_minutes_for_day(day)
minutes = self.sessions_in_range.minutes_for_session(day)
values_for_day = np.zeros(len(minutes), dtype=np.float64)
@@ -1111,7 +1112,7 @@ class DataPortal(object):
# get the minutes for today
last_day_minutes = pd.date_range(
start=self.trading_schedule.start_and_end(end_dt)[0],
start=self.trading_calendar.open_and_close_for_session(end_dt)[0],
end=end_dt,
freq="T"
)
@@ -1190,9 +1191,9 @@ class DataPortal(object):
def _handle_history_out_of_bounds(self, bar_count):
suggested_start_day = (
self.trading_schedule.all_execution_minutes[
self.trading_calendar.all_minutes[
self._first_trading_minute_loc + bar_count
] + self.trading_schedule.day
] + self.trading_calendar.day
).date()
raise HistoryWindowStartsBeforeData(
@@ -1209,7 +1210,7 @@ class DataPortal(object):
"""
# get all the minutes for this window
try:
minutes_for_window = self.trading_schedule.execution_minute_window(
minutes_for_window = self.trading_calendar.minutes_window(
end_dt, -bar_count
)
except KeyError:
@@ -1728,21 +1729,29 @@ class DataPortal(object):
# we get all the minutes for the last (bars - 1) days, then add
# all the minutes so far today. the +2 is to account for ignoring
# today, and the previous day, in doing the math.
previous_day = \
self.trading_schedule.previous_execution_day(ending_minute)
days = self.trading_schedule.execution_days_in_range(
self.trading_schedule.add_execution_days(-days_count + 2,
previous_day),
previous_day,
session_for_minute = self.trading_calendar.minute_to_session_label(
ending_minute
)
previous_session = self.trading_calendar.previous_session_label(
session_for_minute
)
sessions = self.trading_calendar.sessions_in_range(
self.trading_calendar.sessions_window(previous_session,
-days_count + 2)[0],
previous_session,
)
minutes_count = sum(
210 if day in self.trading_schedule.early_ends
else 390 for day in days
len(self.trading_calendar.minutes_for_session(session))
for session in sessions
)
# add the minutes for today
today_open = self.trading_schedule.start_and_end(ending_minute)[0]
today_open = self.trading_calendar.open_and_close_for_session(
session_for_minute
)[0]
minutes_count += \
((ending_minute - today_open).total_seconds() // 60) + 1
+1 -1
View File
@@ -47,7 +47,7 @@ ONE_HOUR = pd.Timedelta(hours=1)
nyse_cal = get_calendar('NYSE')
trading_day_nyse = nyse_cal.day
trading_days_nyse = nyse_cal.all_trading_days
trading_days_nyse = nyse_cal.all_sessions
def last_modified_time(path):
+2 -1
View File
@@ -280,13 +280,14 @@ class BcolzMinuteBarWriter(object):
minutes_per_day,
ohlc_ratio=OHLC_RATIO,
expectedlen=DEFAULT_EXPECTEDLEN):
self._rootdir = rootdir
self._first_trading_day = first_trading_day
self._market_opens = market_opens[
market_opens.index.slice_indexer(start=self._first_trading_day)]
self._market_closes = market_closes[
market_closes.index.slice_indexer(start=self._first_trading_day)]
self._trading_days = market_opens.index
self._trading_days = self._market_opens.index
self._minutes_per_day = minutes_per_day
self._expectedlen = expectedlen
self._ohlc_ratio = ohlc_ratio
+5 -3
View File
@@ -75,6 +75,8 @@ class USEquityHistoryLoader(with_metaclass(ABCMeta)):
Parameters
----------
trading_calendar: TradingCalendar
Contains the grouping logic needed to assign minutes to periods.
reader : DailyBarReader, MinuteBarReader
Reader for pricing bars.
adjustment_reader : SQLiteAdjustmentReader
@@ -82,9 +84,9 @@ class USEquityHistoryLoader(with_metaclass(ABCMeta)):
"""
FIELDS = ('open', 'high', 'low', 'close', 'volume')
def __init__(self, trading_schedule, reader, adjustment_reader,
def __init__(self, trading_calendar, reader, adjustment_reader,
sid_cache_size=1000):
self.trading_schedule = trading_schedule
self.trading_calendar = trading_calendar
self._reader = reader
self._adjustments_reader = adjustment_reader
self._window_blocks = {
@@ -404,7 +406,7 @@ class USEquityMinuteHistoryLoader(USEquityHistoryLoader):
@lazyval
def _calendar(self):
mm = self.trading_schedule.all_execution_minutes
mm = self.trading_calendar.all_minutes
return mm[mm.slice_indexer(start=self._reader.first_trading_day,
end=self._reader.last_available_dt)]
+9 -6
View File
@@ -189,7 +189,7 @@ class BcolzDailyBarWriter(object):
----------
filename : str
The location at which we should write our output.
calendar : pandas.DatetimeIndex
sessions : pandas.DatetimeIndex
Calendar to use to compute asset calendar offsets.
See Also
@@ -204,8 +204,9 @@ class BcolzDailyBarWriter(object):
'volume': float64,
}
def __init__(self, filename, calendar):
def __init__(self, filename, sessions, calendar):
self._filename = filename
self._sessions = sessions
self._calendar = calendar
@property
@@ -299,7 +300,7 @@ class BcolzDailyBarWriter(object):
}
earliest_date = None
calendar = self._calendar
sessions = self._sessions
if assets is not None:
@apply
@@ -342,8 +343,10 @@ class BcolzDailyBarWriter(object):
# in the stored data and the first date of **this** asset. This
# offset used for output alignment by the reader.
asset_first_day = table['day'][0]
calendar_offset[asset_key] = calendar.get_loc(
Timestamp(asset_first_day, unit='s', tz='UTC'),
calendar_offset[asset_key] = sessions.get_loc(
self._calendar.minute_to_session_label(
Timestamp(asset_first_day, unit='s', tz='UTC')
)
)
# This writes the table to disk.
@@ -363,7 +366,7 @@ class BcolzDailyBarWriter(object):
full_table.attrs['first_row'] = first_row
full_table.attrs['last_row'] = last_row
full_table.attrs['calendar_offset'] = calendar_offset
full_table.attrs['calendar'] = calendar.asi8.tolist()
full_table.attrs['calendar'] = sessions.asi8.tolist()
full_table.flush()
return full_table
+1 -1
View File
@@ -642,7 +642,7 @@ class InvalidCalendarName(ZiplineError):
Raised when a calendar with an invalid name is requested.
"""
msg = (
"The requested ExchangeCalendar, {calendar_name}, does not exist."
"The requested TradingCalendar, {calendar_name}, does not exist."
)
@@ -384,7 +384,7 @@ class PositionTracker(object):
last_sale_price = data_portal.get_adjusted_value(
asset,
'price',
data_portal.trading_schedule.previous_execution_minute(dt),
data_portal.trading_calendar.previous_minute(dt),
dt,
self.data_frequency
)
+46 -43
View File
@@ -60,7 +60,6 @@ Performance Tracking
from __future__ import division
import logbook
from datetime import datetime
import pandas as pd
from pandas.tseries.tools import normalize_date
@@ -78,52 +77,52 @@ class PerformanceTracker(object):
"""
Tracks the performance of the algorithm.
"""
def __init__(self, sim_params, trading_schedule, env):
def __init__(self, sim_params, trading_calendar, env):
self.sim_params = sim_params
self.trading_schedule = trading_schedule
self.trading_calendar = trading_calendar
self.asset_finder = env.asset_finder
self.treasury_curves = env.treasury_curves
self.period_start = self.sim_params.period_start
self.period_end = self.sim_params.period_end
self.period_start = self.sim_params.start_session
self.period_end = self.sim_params.end_session
self.last_close = self.sim_params.last_close
first_open = self.sim_params.first_open.tz_convert(trading_schedule.tz)
self.day = pd.Timestamp(datetime(first_open.year, first_open.month,
first_open.day), tz='UTC')
self.market_open, self.market_close = trading_schedule.start_and_end(
self.day
)
self.total_days = self.sim_params.days_in_period
self._current_session = self.sim_params.start_session
self.market_open, self.market_close = \
self.trading_calendar.open_and_close_for_session(
self._current_session
)
self.total_session_count = len(self.sim_params.sessions)
self.capital_base = self.sim_params.capital_base
self.emission_rate = sim_params.emission_rate
self.trading_days = trading_schedule.trading_dates(
self.period_start, self.period_end
)
self.position_tracker = PositionTracker(
asset_finder=env.asset_finder,
data_frequency=self.sim_params.data_frequency)
data_frequency=self.sim_params.data_frequency
)
if self.emission_rate == 'daily':
self.all_benchmark_returns = pd.Series(
index=self.trading_days)
index=self.sim_params.sessions
)
self.cumulative_risk_metrics = \
risk.RiskMetricsCumulative(
self.sim_params,
self.treasury_curves,
self.trading_schedule
self.trading_calendar
)
elif self.emission_rate == 'minute':
self.all_benchmark_returns = pd.Series(index=pd.date_range(
self.sim_params.first_open, self.sim_params.last_close,
freq='Min'))
freq='Min')
)
self.cumulative_risk_metrics = \
risk.RiskMetricsCumulative(
self.sim_params,
self.treasury_curves,
self.trading_schedule,
self.trading_calendar,
create_first_day_stats=True
)
@@ -165,7 +164,7 @@ class PerformanceTracker(object):
self.saved_dt = self.period_start
# one indexed so that we reach 100%
self.day_count = 0.0
self.session_count = 0.0
self.txn_count = 0
self.account_needs_update = True
@@ -182,7 +181,7 @@ class PerformanceTracker(object):
# Fake a value
return 1.0
elif self.emission_rate == 'daily':
return self.day_count / self.total_days
return self.session_count / self.total_session_count
def set_date(self, date):
if self.emission_rate == 'minute':
@@ -280,7 +279,7 @@ class PerformanceTracker(object):
if txn:
self.process_transaction(txn)
def check_upcoming_dividends(self, next_trading_day, adjustment_reader):
def check_upcoming_dividends(self, next_session, adjustment_reader):
"""
Check if we currently own any stocks with dividends whose ex_date is
the next trading day. Track how much we should be payed on those
@@ -301,13 +300,13 @@ class PerformanceTracker(object):
if held_sids:
cash_dividends = adjustment_reader.get_dividends_with_ex_date(
held_sids,
next_trading_day,
next_session,
self.asset_finder
)
stock_dividends = adjustment_reader.\
get_stock_dividends_with_ex_date(
held_sids,
next_trading_day,
next_session,
self.asset_finder
)
@@ -316,7 +315,7 @@ class PerformanceTracker(object):
stock_dividends
)
net_cash_payment = position_tracker.pay_dividends(next_trading_day)
net_cash_payment = position_tracker.pay_dividends(next_session)
if not net_cash_payment:
return
@@ -368,7 +367,7 @@ class PerformanceTracker(object):
_______
A daily perf packet.
"""
completed_date = self.day
completed_session = self._current_session
if self.emission_rate == 'daily':
# this method is called for both minutely and daily emissions, but
@@ -378,25 +377,25 @@ class PerformanceTracker(object):
self.update_performance()
account = self.get_account(False)
benchmark_value = self.all_benchmark_returns[completed_date]
benchmark_value = self.all_benchmark_returns[completed_session]
self.cumulative_risk_metrics.update(
completed_date,
completed_session,
self.todays_performance.returns,
benchmark_value,
account.leverage)
# increment the day counter before we move markers forward.
self.day_count += 1.0
self.session_count += 1.0
# Get the next trading day and, if it is past the bounds of this
# simulation, return the daily perf packet
try:
next_trading_day = self.trading_schedule.next_execution_day(
completed_date
next_session = self.trading_calendar.next_session_label(
completed_session
)
except NoFurtherDataError:
next_trading_day = None
next_session = None
# Take a snapshot of our current performance to return to the
# browser.
@@ -408,24 +407,26 @@ class PerformanceTracker(object):
if self.market_close >= self.last_close:
return daily_update
# If the next trading day is irrelevant, then return the daily packet
if (next_session is None) or (next_session >= self.last_close):
return daily_update
# move the market day markers forward
# TODO Is this redundant with next_trading_day above?
self.day = self.trading_schedule.next_execution_day(self.day)
self._current_session = next_session
self.market_open, self.market_close = \
self.trading_schedule.start_and_end(self.day)
self.trading_calendar.open_and_close_for_session(
self._current_session
)
# Roll over positions to current day.
self.todays_performance.rollover()
self.todays_performance.period_open = self.market_open
self.todays_performance.period_close = self.market_close
# If the next trading day is irrelevant, then return the daily packet
if (next_trading_day is None) or (next_trading_day >= self.last_close):
return daily_update
# Check for any dividends, then return the daily perf packet
self.check_upcoming_dividends(
next_trading_day=next_trading_day,
next_session=next_session,
adjustment_reader=data_portal._adjustment_reader
)
@@ -438,7 +439,8 @@ class PerformanceTracker(object):
"""
log_msg = "Simulated {n} trading days out of {m}."
log.info(log_msg.format(n=int(self.day_count), m=self.total_days))
log.info(log_msg.format(n=int(self.session_count),
m=self.total_session_count))
log.info("first open: {d}".format(
d=self.sim_params.first_open))
log.info("last close: {d}".format(
@@ -451,12 +453,13 @@ class PerformanceTracker(object):
index=self.cumulative_risk_metrics.cont_index,
data=self.cumulative_risk_metrics.algorithm_returns_cont)
acl = self.cumulative_risk_metrics.algorithm_cumulative_leverages
self.risk_report = risk.RiskReport(
ars,
self.sim_params,
benchmark_returns=bms,
algorithm_leverages=acl,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
treasury_curves=self.treasury_curves,
)
+16 -20
View File
@@ -86,38 +86,34 @@ class RiskMetricsCumulative(object):
'information',
)
def __init__(self, sim_params, treasury_curves, trading_schedule,
def __init__(self, sim_params, treasury_curves, trading_calendar,
create_first_day_stats=False):
self.treasury_curves = treasury_curves
self.trading_schedule = trading_schedule
self.start_date = sim_params.period_start.replace(
hour=0, minute=0, second=0, microsecond=0
)
self.end_date = sim_params.period_end.replace(
hour=0, minute=0, second=0, microsecond=0
)
self.trading_calendar = trading_calendar
self.start_session = sim_params.start_session
self.end_session = sim_params.end_session
self.trading_days = trading_schedule.trading_dates(
self.start_date, self.end_date
self.sessions = trading_calendar.sessions_in_range(
self.start_session, self.end_session
)
# Hold on to the trading day before the start,
# used for index of the zero return value when forcing returns
# on the first day.
self.day_before_start = self.start_date - self.trading_days.freq
self.day_before_start = self.start_session - self.sessions.freq
last_day = normalize_date(sim_params.period_end)
if last_day not in self.trading_days:
last_day = normalize_date(sim_params.end_session)
if last_day not in self.sessions:
last_day = pd.tseries.index.DatetimeIndex(
[last_day]
)
self.trading_days = self.trading_days.append(last_day)
self.sessions = self.sessions.append(last_day)
self.sim_params = sim_params
self.create_first_day_stats = create_first_day_stats
cont_index = self.trading_days
cont_index = self.sessions
self.cont_index = cont_index
self.cont_len = len(self.cont_index)
@@ -164,7 +160,7 @@ class RiskMetricsCumulative(object):
self.max_leverages = empty_cont.copy()
self.max_leverage = 0
self.current_max = -np.inf
self.daily_treasury = pd.Series(index=self.trading_days)
self.daily_treasury = pd.Series(index=self.sessions)
self.treasury_period_return = np.nan
self.num_trading_days = 0
@@ -249,8 +245,8 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
message = message.format(
bm_count=len(self.benchmark_returns),
algo_count=len(self.algorithm_returns),
start=self.start_date,
end=self.end_date,
start=self.start_session,
end=self.end_session,
dt=dt
)
raise Exception(message)
@@ -269,9 +265,9 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
if np.isnan(self.daily_treasury[treasury_end]):
treasury_period_return = choose_treasury(
self.treasury_curves,
self.start_date,
self.start_session,
treasury_end,
self.trading_schedule,
self.trading_calendar,
)
self.daily_treasury[treasury_end] = treasury_period_return
self.treasury_period_return = self.daily_treasury[treasury_end]
+19 -20
View File
@@ -41,12 +41,12 @@ choose_treasury = functools.partial(risk.choose_treasury,
class RiskMetricsPeriod(object):
def __init__(self, start_date, end_date, returns, trading_schedule,
def __init__(self, start_session, end_session, returns, trading_calendar,
treasury_curves, benchmark_returns, algorithm_leverages=None):
if treasury_curves.index[-1] >= start_date:
mask = ((treasury_curves.index >= start_date) &
(treasury_curves.index <= end_date))
if treasury_curves.index[-1] >= start_session:
mask = ((treasury_curves.index >= start_session) &
(treasury_curves.index <= end_session))
self.treasury_curves = treasury_curves[mask]
else:
@@ -54,16 +54,16 @@ class RiskMetricsPeriod(object):
# so we'll use the last available treasury curve
self.treasury_curves = treasury_curves[-1:]
self.start_date = start_date
self.end_date = end_date
self.trading_schedule = trading_schedule
self._start_session = start_session
self._end_session = end_session
self.trading_calendar = trading_calendar
trading_dates = trading_schedule.trading_dates(
start=self.start_date,
end=self.end_date,
trading_sessions = trading_calendar.sessions_in_range(
self._start_session,
self._end_session,
)
self.algorithm_returns = self.mask_returns_to_period(returns,
trading_dates)
trading_sessions)
# Benchmark needs to be masked to the same dates as the algo returns
self.benchmark_returns = self.mask_returns_to_period(
@@ -75,7 +75,6 @@ class RiskMetricsPeriod(object):
self.calculate_metrics()
def calculate_metrics(self):
self.benchmark_period_returns = \
self.calculate_period_returns(self.benchmark_returns)
@@ -90,8 +89,8 @@ class RiskMetricsPeriod(object):
message = message.format(
bm_count=len(self.benchmark_returns),
algo_count=len(self.algorithm_returns),
start=self.start_date,
end=self.end_date
start=self._start_session,
end=self._end_session
)
raise Exception(message)
@@ -108,9 +107,9 @@ class RiskMetricsPeriod(object):
self.algorithm_returns)
self.treasury_period_return = choose_treasury(
self.treasury_curves,
self.start_date,
self.end_date,
self.trading_schedule,
self._start_session,
self._end_session,
self.trading_calendar,
)
self.sharpe = self.calculate_sharpe()
# The consumer currently expects a 0.0 value for sharpe in period,
@@ -137,7 +136,7 @@ class RiskMetricsPeriod(object):
Creates a dictionary representing the state of the risk report.
Returns a dict object of the form:
"""
period_label = self.end_date.strftime("%Y-%m")
period_label = self._end_session.strftime("%Y-%m")
rval = {
'trading_days': self.num_trading_days,
'benchmark_volatility': self.benchmark_volatility,
@@ -198,8 +197,8 @@ class RiskMetricsPeriod(object):
trade_day_mask = returns.index.normalize().isin(trading_days)
mask = ((returns.index >= self.start_date) &
(returns.index <= self.end_date) & trade_day_mask)
mask = ((returns.index >= self._start_session) &
(returns.index <= self._end_session) & trade_day_mask)
returns = returns[mask]
return returns
+26 -18
View File
@@ -67,7 +67,7 @@ log = logbook.Logger('Risk Report')
class RiskReport(object):
def __init__(self, algorithm_returns, sim_params, trading_schedule,
def __init__(self, algorithm_returns, sim_params, trading_calendar,
treasury_curves, benchmark_returns,
algorithm_leverages=None):
"""
@@ -80,23 +80,30 @@ class RiskReport(object):
self.algorithm_returns = algorithm_returns
self.sim_params = sim_params
self.trading_schedule = trading_schedule
self.trading_calendar = trading_calendar
self.treasury_curves = treasury_curves
self.benchmark_returns = benchmark_returns
self.algorithm_leverages = algorithm_leverages
if len(self.algorithm_returns) == 0:
start_date = self.sim_params.period_start
end_date = self.sim_params.period_end
start_session = self.sim_params.start_session
end_session = self.sim_params.end_session
else:
start_date = self.algorithm_returns.index[0]
end_date = self.algorithm_returns.index[-1]
start_session = self.algorithm_returns.index[0]
end_session = self.algorithm_returns.index[-1]
self.month_periods = self.periods_in_range(1, start_date, end_date)
self.three_month_periods = self.periods_in_range(3, start_date,
end_date)
self.six_month_periods = self.periods_in_range(6, start_date, end_date)
self.year_periods = self.periods_in_range(12, start_date, end_date)
self.month_periods = self.periods_in_range(
1, start_session, end_session
)
self.three_month_periods = self.periods_in_range(
3, start_session, end_session
)
self.six_month_periods = self.periods_in_range(
6, start_session, end_session
)
self.year_periods = self.periods_in_range(
12, start_session, end_session
)
def to_dict(self):
"""
@@ -120,10 +127,10 @@ class RiskReport(object):
'twelve_month': [x.to_dict() for x in self.year_periods],
}
def periods_in_range(self, months_per, start, end):
def periods_in_range(self, months_per, start_session, end_session):
one_day = datetime.timedelta(days=1)
ends = []
cur_start = start.replace(day=1)
cur_start = start_session.replace(day=1)
# in edge cases (all sids filtered out, start/end are adjacent)
# a test will not generate any returns data
@@ -132,17 +139,18 @@ class RiskReport(object):
# ensure that we have an end at the end of a calendar month, in case
# the return series ends mid-month...
the_end = end.replace(day=1) + relativedelta(months=1) - one_day
the_end = end_session.replace(day=1) + relativedelta(months=1) - \
one_day
while True:
cur_end = cur_start + relativedelta(months=months_per) - one_day
if(cur_end > the_end):
if cur_end > the_end:
break
cur_period_metrics = RiskMetricsPeriod(
start_date=cur_start,
end_date=cur_end,
start_session=cur_start,
end_session=cur_end,
returns=self.algorithm_returns,
benchmark_returns=self.benchmark_returns,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
treasury_curves=self.treasury_curves,
algorithm_leverages=self.algorithm_leverages,
)
+14 -15
View File
@@ -228,8 +228,8 @@ def select_treasury_duration(start_date, end_date):
return treasury_duration
def choose_treasury(select_treasury, treasury_curves, start_date, end_date,
trading_schedule, compound=True):
def choose_treasury(select_treasury, treasury_curves, start_session,
end_session, trading_calendar, compound=True):
"""
Find the latest known interest rate for a given duration within a date
range.
@@ -237,48 +237,47 @@ def choose_treasury(select_treasury, treasury_curves, start_date, end_date,
If we find one but it's more than a trading day ago from the date we're
looking for, then we log a warning
"""
treasury_duration = select_treasury(start_date, end_date)
end_day = end_date.replace(hour=0, minute=0, second=0, microsecond=0)
treasury_duration = select_treasury(start_session, end_session)
search_day = None
if end_day in treasury_curves.index:
if end_session in treasury_curves.index:
rate = get_treasury_rate(treasury_curves,
treasury_duration,
end_day)
end_session)
if rate is not None:
search_day = end_day
search_day = end_session
if not search_day:
# in case end date is not a trading day or there is no treasury
# data, search for the previous day with an interest rate.
search_days = treasury_curves.index
# Find rightmost value less than or equal to end_day
i = search_days.searchsorted(end_day)
# Find rightmost value less than or equal to end_session
i = search_days.searchsorted(end_session)
for prev_day in search_days[i - 1::-1]:
rate = get_treasury_rate(treasury_curves,
treasury_duration,
prev_day)
if rate is not None:
search_day = prev_day
search_dist = trading_schedule.execution_day_distance(
end_date, prev_day
search_dist = trading_calendar.session_distance(
end_session, prev_day
)
break
if search_day:
if (search_dist is None or search_dist > 1) and \
search_days[0] <= end_day <= search_days[-1]:
search_days[0] <= end_session <= search_days[-1]:
message = "No rate within 1 trading day of end date = \
{dt} and term = {term}. Using {search_day}. Check that date doesn't exceed \
treasury history range."
message = message.format(dt=end_date,
message = message.format(dt=end_session,
term=treasury_duration,
search_day=search_day)
log.warn(message)
if search_day:
td = end_date - start_date
td = end_session - start_session
if compound:
return rate * (td.days + 1) / 365
else:
@@ -287,7 +286,7 @@ treasury history range."
message = "No rate for end date = {dt} and term = {term}. Check \
that date doesn't exceed treasury history range."
message = message.format(
dt=end_date,
dt=end_session,
term=treasury_duration
)
raise Exception(message)
+116 -64
View File
@@ -1,5 +1,5 @@
#
# Copyright 2015 Quantopian, Inc.
# Copyright 2016 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -14,14 +14,15 @@
# limitations under the License.
import logbook
import datetime
import pandas as pd
from pandas.tslib import normalize_date
from six import string_types
from sqlalchemy import create_engine
from zipline.assets import AssetDBWriter, AssetFinder
from zipline.data.loader import load_market_data
from zipline.utils.calendars import default_nyse_schedule
from zipline.utils.calendars import get_calendar
from zipline.utils.memoize import remember_last
log = logbook.Logger('Trading')
@@ -78,7 +79,7 @@ class TradingEnvironment(object):
load=None,
bm_symbol='^GSPC',
exchange_tz="US/Eastern",
trading_schedule=default_nyse_schedule,
trading_calendar=None,
asset_db_path=':memory:'
):
@@ -86,9 +87,12 @@ class TradingEnvironment(object):
if not load:
load = load_market_data
if not trading_calendar:
trading_calendar = get_calendar("NYSE")
self.benchmark_returns, self.treasury_curves = load(
trading_schedule.day,
trading_schedule.schedule.index,
trading_calendar.day,
trading_calendar.schedule.index,
self.bm_symbol,
)
@@ -118,86 +122,134 @@ class TradingEnvironment(object):
class SimulationParameters(object):
def __init__(self, period_start, period_end,
def __init__(self, start_session, end_session,
trading_calendar,
capital_base=10e3,
emission_rate='daily',
data_frequency='daily',
trading_schedule=None,
arena='backtest'):
self.period_start = period_start
self.period_end = period_end
self.capital_base = capital_base
assert type(start_session) == pd.Timestamp
assert type(end_session) == pd.Timestamp
self.emission_rate = emission_rate
self.data_frequency = data_frequency
# copied to algorithm's environment for runtime access
self.arena = arena
if trading_schedule is not None:
self.update_internal_from_trading_schedule(
trading_schedule=trading_schedule
)
def update_internal_from_trading_schedule(self, trading_schedule):
assert self.period_start <= self.period_end, \
assert trading_calendar is not None, \
"Must pass in trading calendar!"
assert start_session <= end_session, \
"Period start falls after period end."
assert self.period_start <= trading_schedule.last_execution_day, \
assert start_session <= trading_calendar.last_trading_session, \
"Period start falls after the last known trading day."
assert self.period_end >= trading_schedule.first_execution_day, \
assert end_session >= trading_calendar.first_trading_session, \
"Period end falls before the first known trading day."
self.first_open = self._calculate_first_open(trading_schedule)
self.last_close = self._calculate_last_close(trading_schedule)
# chop off any minutes or hours on the given start and end dates,
# as we only support session labels here (and we represent session
# labels as midnight UTC).
self._start_session = normalize_date(start_session)
self._end_session = normalize_date(end_session)
self._capital_base = capital_base
# Take the length of an inclusive slice of trading dates
self.trading_days = trading_schedule.trading_dates(
self.first_open, self.last_close
self._emission_rate = emission_rate
self._data_frequency = data_frequency
# copied to algorithm's environment for runtime access
self._arena = arena
self._trading_calendar = trading_calendar
if not trading_calendar.is_session(self._start_session):
# if the start date is not a valid session in this calendar,
# push it forward to the first valid session
self._start_session = trading_calendar.minute_to_session_label(
self._start_session
)
if not trading_calendar.is_session(self._end_session):
# if the end date is not a valid session in this calendar,
# pull it backward to the last valid session before the given
# end date.
self._end_session = trading_calendar.minute_to_session_label(
self._end_session, direction="previous"
)
self._first_open = trading_calendar.open_and_close_for_session(
self._start_session
)[0]
self._last_close = trading_calendar.open_and_close_for_session(
self._end_session
)[1]
@property
def capital_base(self):
return self._capital_base
@property
def emission_rate(self):
return self._emission_rate
@property
def data_frequency(self):
return self._data_frequency
@data_frequency.setter
def data_frequency(self, val):
self._data_frequency = val
@property
def arena(self):
return self._arena
@arena.setter
def arena(self, val):
self._arena = val
@property
def start_session(self):
return self._start_session
@property
def end_session(self):
return self._end_session
@property
def first_open(self):
return self._first_open
@property
def last_close(self):
return self._last_close
@property
@remember_last
def sessions(self):
return self._trading_calendar.sessions_in_range(
self.start_session,
self.end_session
)
self.days_in_period = len(self.trading_days)
def _calculate_first_open(self, trading_schedule):
"""
Finds the first trading day on or after self.period_start.
"""
first_open = self.period_start
one_day = datetime.timedelta(days=1)
while not trading_schedule.is_executing_on_day(first_open):
first_open = first_open + one_day
mkt_open, _ = trading_schedule.start_and_end(first_open)
return mkt_open
def _calculate_last_close(self, trading_schedule):
"""
Finds the last trading day on or before self.period_end
"""
last_close = self.period_end
one_day = datetime.timedelta(days=1)
while not trading_schedule.is_executing_on_day(last_close):
last_close = last_close - one_day
_, mkt_close = trading_schedule.start_and_end(last_close)
return mkt_close
def create_new(self, start_session, end_session):
return SimulationParameters(
start_session,
end_session,
self._trading_calendar,
capital_base=self.capital_base,
emission_rate=self.emission_rate,
data_frequency=self.data_frequency,
arena=self.arena
)
def __repr__(self):
return """
{class_name}(
period_start={period_start},
period_end={period_end},
start_session={start_session},
end_session={end_session},
capital_base={capital_base},
data_frequency={data_frequency},
emission_rate={emission_rate},
first_open={first_open},
last_close={last_close})\
""".format(class_name=self.__class__.__name__,
period_start=self.period_start,
period_end=self.period_end,
start_session=self.start_session,
end_session=self.end_session,
capital_base=self.capital_base,
data_frequency=self.data_frequency,
emission_rate=self.emission_rate,
+1 -3
View File
@@ -215,9 +215,7 @@ class AlgorithmSimulator(object):
# perspective as we have technically not "advanced" to the
# current dt yet.
algo.perf_tracker.position_tracker.sync_last_sale_prices(
self.algo.trading_schedule.previous_execution_minute(
dt
),
self.algo.trading_calendar.previous_minute(dt),
False,
self.data_portal
)
@@ -22,7 +22,7 @@ from zipline.data.us_equity_pricing import (
)
from zipline.lib.adjusted_array import AdjustedArray
from zipline.errors import NoFurtherDataError
from zipline.utils.calendars import default_nyse_schedule
from zipline.utils.calendars import get_calendar
from .base import PipelineLoader
@@ -40,7 +40,7 @@ class USEquityPricingLoader(PipelineLoader):
self.raw_price_loader = raw_price_loader
self.adjustments_loader = adjustments_loader
self._calendar = default_nyse_schedule.all_execution_days
self._calendar = get_calendar("NYSE").all_sessions
@classmethod
def from_files(cls, pricing_path, adjustments_path):
+18 -18
View File
@@ -23,15 +23,15 @@ from zipline.errors import (
class BenchmarkSource(object):
def __init__(self, benchmark_sid, env, trading_schedule, trading_days,
def __init__(self, benchmark_sid, env, trading_calendar, sessions,
data_portal, emission_rate="daily"):
self.benchmark_sid = benchmark_sid
self.env = env
self.trading_days = trading_days
self.sessions = sessions
self.emission_rate = emission_rate
self.data_portal = data_portal
if len(trading_days) == 0:
if len(sessions) == 0:
self._precalculated_series = pd.Series()
elif self.benchmark_sid:
benchmark_asset = self.env.asset_finder.retrieve_asset(
@@ -42,22 +42,22 @@ class BenchmarkSource(object):
self._precalculated_series = \
self._initialize_precalculated_series(
benchmark_asset,
trading_schedule,
self.trading_days,
trading_calendar,
self.sessions,
self.data_portal
)
else:
# get benchmark info from trading environment, which defaults to
# downloading data from Yahoo.
daily_series = \
env.benchmark_returns[trading_days[0]:trading_days[-1]]
env.benchmark_returns[sessions[0]:sessions[-1]]
if self.emission_rate == "minute":
# we need to take the env's benchmark returns, which are daily,
# and resample them to minute
minutes = trading_schedule.execution_minutes_for_days_in_range(
start=trading_days[0],
end=trading_days[-1]
minutes = trading_calendar.minutes_for_sessions_in_range(
sessions[0],
sessions[-1]
)
minute_series = daily_series.reindex(
@@ -78,7 +78,7 @@ class BenchmarkSource(object):
# as benchmark.
stock_dividends = \
self.data_portal.get_stock_dividends(self.benchmark_sid,
self.trading_days)
self.sessions)
if len(stock_dividends) > 0:
raise InvalidBenchmarkAsset(
@@ -86,23 +86,23 @@ class BenchmarkSource(object):
dt=stock_dividends[0]["ex_date"]
)
if benchmark_asset.start_date > self.trading_days[0]:
if benchmark_asset.start_date > self.sessions[0]:
# the asset started trading after the first simulation day
raise BenchmarkAssetNotAvailableTooEarly(
sid=str(self.benchmark_sid),
dt=self.trading_days[0],
dt=self.sessions[0],
start_dt=benchmark_asset.start_date
)
if benchmark_asset.end_date < self.trading_days[-1]:
if benchmark_asset.end_date < self.sessions[-1]:
# the asset stopped trading before the last simulation day
raise BenchmarkAssetNotAvailableTooLate(
sid=str(self.benchmark_sid),
dt=self.trading_days[-1],
dt=self.sessions[-1],
end_dt=benchmark_asset.end_date
)
def _initialize_precalculated_series(self, asset, trading_schedule,
def _initialize_precalculated_series(self, asset, trading_calendar,
trading_days, data_portal):
"""
Internal method that pre-calculates the benchmark return series for
@@ -112,7 +112,7 @@ class BenchmarkSource(object):
----------
asset: Asset to use
trading_schedule: TradingSchedule
trading_calendar: TradingCalendar
trading_days: pd.DateTimeIndex
@@ -137,8 +137,8 @@ class BenchmarkSource(object):
change from close to close.
"""
if self.emission_rate == "minute":
minutes = trading_schedule.execution_minutes_for_days_in_range(
self.trading_days[0], self.trading_days[-1]
minutes = trading_calendar.minutes_for_sessions_in_range(
self.sessions[0], self.sessions[-1]
)
benchmark_series = data_portal.get_history_window(
[asset],
+17 -13
View File
@@ -52,7 +52,7 @@ def create_trade(sid, price, amount, datetime, source_id="test_factory"):
def date_gen(start,
end,
trading_schedule,
trading_calendar,
delta=timedelta(minutes=1),
repeats=None):
"""
@@ -73,15 +73,19 @@ def date_gen(start,
"""
cur = cur + delta
if not (trading_schedule.is_executing_on_day
if daily_delta
else trading_schedule.is_executing_on_minute)(cur):
if daily_delta:
return trading_schedule.next_execution_day(cur)
else:
return trading_schedule.next_start_and_end(cur)[0]
else:
currently_executing = \
(daily_delta and (cur in trading_calendar.all_sessions)) or \
(trading_calendar.is_open_on_minute(cur))
if currently_executing:
return cur
else:
if daily_delta:
return trading_calendar.minute_to_session_label(cur)
else:
return trading_calendar.open_and_close_for_session(
trading_calendar.minute_to_session_label(cur)
)[0]
# yield count trade events, all on trading days, and
# during trading hours.
@@ -109,12 +113,12 @@ class SpecificEquityTrades(object):
delta : timedelta between internal events
filter : filter to remove the sids
"""
def __init__(self, env, trading_schedule, *args, **kwargs):
def __init__(self, env, trading_calendar, *args, **kwargs):
# We shouldn't get any positional arguments.
assert len(args) == 0
self.env = env
self.trading_schedule = trading_schedule
self.trading_calendar = trading_calendar
# Default to None for event_list and filter.
self.event_list = kwargs.get('event_list')
@@ -206,14 +210,14 @@ class SpecificEquityTrades(object):
end=self.end,
delta=self.delta,
repeats=len(self.sids),
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
else:
date_generator = date_gen(
start=self.start,
end=self.end,
delta=self.delta,
trading_schedule=self.trading_schedule,
trading_calendar=self.trading_calendar,
)
source_id = self.get_hash()
+52 -43
View File
@@ -49,7 +49,7 @@ from zipline.pipeline.loaders.testing import make_seeded_random_loader
from zipline.utils import security_list
from zipline.utils.input_validation import expect_dimensions
from zipline.utils.sentinel import sentinel
from zipline.utils.calendars import default_nyse_schedule
from zipline.utils.calendars import get_calendar
import numpy as np
from numpy import float64
@@ -410,10 +410,19 @@ class ExplodingObject(object):
raise UnexpectedAttributeAccess(name)
def write_minute_data(trading_schedule, tempdir, minutes, sids):
def write_minute_data(trading_calendar, tempdir, minutes, sids):
first_session = trading_calendar.minute_to_session_label(
minutes[0], direction="none"
)
last_session = trading_calendar.minute_to_session_label(
minutes[-1], direction="none"
)
sessions = trading_calendar.sessions_in_range(first_session, last_session)
write_bcolz_minute_data(
trading_schedule,
trading_schedule.execution_days_in_range(minutes[0], minutes[-1]),
trading_calendar,
sessions,
tempdir.path,
create_minute_bar_data(minutes, sids),
)
@@ -435,8 +444,8 @@ def create_minute_bar_data(minutes, sids):
)
def create_daily_bar_data(trading_days, sids):
length = len(trading_days)
def create_daily_bar_data(sessions, sids):
length = len(sessions)
for sid_idx, sid in enumerate(sids):
yield sid, pd.DataFrame(
{
@@ -445,56 +454,57 @@ def create_daily_bar_data(trading_days, sids):
"low": (np.array(range(8, 8 + length)) + sid_idx),
"close": (np.array(range(10, 10 + length)) + sid_idx),
"volume": np.array(range(100, 100 + length)) + sid_idx,
"day": [day.value for day in trading_days]
"day": [session.value for session in sessions]
},
index=trading_days,
index=sessions,
)
def write_daily_data(tempdir, sim_params, sids):
def write_daily_data(tempdir, sim_params, sids, trading_calendar):
path = os.path.join(tempdir.path, "testdaily.bcolz")
BcolzDailyBarWriter(path, sim_params.trading_days).write(
create_daily_bar_data(sim_params.trading_days, sids),
BcolzDailyBarWriter(path, sim_params.sessions, trading_calendar).write(
create_daily_bar_data(sim_params.sessions, sids),
)
return path
def create_data_portal(asset_finder, tempdir, sim_params, sids,
trading_schedule, adjustment_reader=None):
trading_calendar, adjustment_reader=None):
if sim_params.data_frequency == "daily":
daily_path = write_daily_data(tempdir, sim_params, sids)
daily_path = write_daily_data(tempdir, sim_params, sids,
trading_calendar)
equity_daily_reader = BcolzDailyBarReader(daily_path)
return DataPortal(
asset_finder, trading_schedule,
asset_finder, trading_calendar,
first_trading_day=equity_daily_reader.first_trading_day,
equity_daily_reader=equity_daily_reader,
adjustment_reader=adjustment_reader
)
else:
minutes = trading_schedule.execution_minutes_for_days_in_range(
minutes = trading_calendar.minutes_in_range(
sim_params.first_open,
sim_params.last_close
)
minute_path = write_minute_data(trading_schedule, tempdir, minutes,
minute_path = write_minute_data(trading_calendar, tempdir, minutes,
sids)
equity_minute_reader = BcolzMinuteBarReader(minute_path)
return DataPortal(
asset_finder, trading_schedule,
asset_finder, trading_calendar,
first_trading_day=equity_minute_reader.first_trading_day,
equity_minute_reader=equity_minute_reader,
adjustment_reader=adjustment_reader
)
def write_bcolz_minute_data(trading_schedule, days, path, data):
market_opens = trading_schedule.schedule.loc[days].market_open
market_closes = trading_schedule.schedule.loc[days].market_close
def write_bcolz_minute_data(trading_calendar, days, path, data):
market_opens = trading_calendar.schedule.loc[days].market_open
market_closes = trading_calendar.schedule.loc[days].market_close
BcolzMinuteBarWriter(
days[0],
@@ -505,14 +515,14 @@ def write_bcolz_minute_data(trading_schedule, days, path, data):
).write(data)
def create_minute_df_for_asset(trading_schedule,
def create_minute_df_for_asset(trading_calendar,
start_dt,
end_dt,
interval=1,
start_val=1,
minute_blacklist=None):
asset_minutes = trading_schedule.execution_minutes_for_days_in_range(
asset_minutes = trading_calendar.minutes_for_sessions_in_range(
start_dt, end_dt
)
minutes_count = len(asset_minutes)
@@ -542,9 +552,9 @@ def create_minute_df_for_asset(trading_schedule,
return df
def create_daily_df_for_asset(trading_schedule, start_day, end_day,
def create_daily_df_for_asset(trading_calendar, start_day, end_day,
interval=1):
days = trading_schedule.execution_days_in_range(start_day, end_day)
days = trading_calendar.minutes_in_range(start_day, end_day)
days_count = len(days)
days_arr = np.arange(days_count) + 2
@@ -598,23 +608,23 @@ def trades_by_sid_to_dfs(trades_by_sid, index):
)
def create_data_portal_from_trade_history(asset_finder, trading_schedule,
def create_data_portal_from_trade_history(asset_finder, trading_calendar,
tempdir, sim_params, trades_by_sid):
if sim_params.data_frequency == "daily":
path = os.path.join(tempdir.path, "testdaily.bcolz")
BcolzDailyBarWriter(path, sim_params.trading_days).write(
trades_by_sid_to_dfs(trades_by_sid, sim_params.trading_days),
BcolzDailyBarWriter(path, sim_params.sessions, trading_calendar).write(
trades_by_sid_to_dfs(trades_by_sid, sim_params.sessions),
)
equity_daily_reader = BcolzDailyBarReader(path)
return DataPortal(
asset_finder, trading_schedule,
asset_finder, trading_calendar,
first_trading_day=equity_daily_reader.first_trading_day,
equity_daily_reader=equity_daily_reader,
)
else:
minutes = trading_schedule.execution_minutes_for_days_in_range(
minutes = trading_calendar.minutes_in_range(
sim_params.first_open,
sim_params.last_close
)
@@ -649,11 +659,8 @@ def create_data_portal_from_trade_history(asset_finder, trading_schedule,
}).set_index("dt")
write_bcolz_minute_data(
trading_schedule,
trading_schedule.execution_days_in_range(
sim_params.first_open,
sim_params.last_close
),
trading_calendar,
sim_params.sessions,
tempdir.path,
assets
)
@@ -661,21 +668,23 @@ def create_data_portal_from_trade_history(asset_finder, trading_schedule,
equity_minute_reader = BcolzMinuteBarReader(tempdir.path)
return DataPortal(
asset_finder, trading_schedule,
asset_finder, trading_calendar,
first_trading_day=equity_minute_reader.first_trading_day,
equity_minute_reader=equity_minute_reader,
)
class FakeDataPortal(DataPortal):
def __init__(self, env=None, trading_schedule=default_nyse_schedule,
def __init__(self, env=None, trading_calendar=None,
first_trading_day=None):
if env is None:
env = TradingEnvironment()
if trading_calendar is None:
trading_calendar = get_calendar("NYSE")
super(FakeDataPortal, self).__init__(env.asset_finder,
trading_schedule,
trading_calendar,
first_trading_day)
def get_spot_value(self, asset, field, dt, data_frequency):
@@ -688,8 +697,8 @@ class FakeDataPortal(DataPortal):
ffill=True):
if frequency == "1d":
end_idx = \
self.trading_schedule.all_execution_days.searchsorted(end_dt)
days = self.trading_schedule.all_execution_days[
self.trading_calendar.all_sessions.searchsorted(end_dt)
days = self.trading_calendar.all_sessions[
(end_idx - bar_count + 1):(end_idx + 1)
]
@@ -707,8 +716,8 @@ class FetcherDataPortal(DataPortal):
Mock dataportal that returns fake data for history and non-fetcher
spot value.
"""
def __init__(self, asset_finder, trading_schedule, first_trading_day=None):
super(FetcherDataPortal, self).__init__(asset_finder, trading_schedule,
def __init__(self, asset_finder, trading_calendar, first_trading_day=None):
super(FetcherDataPortal, self).__init__(asset_finder, trading_calendar,
first_trading_day)
def get_spot_value(self, asset, field, dt, data_frequency):
@@ -1023,7 +1032,7 @@ def gen_calendars(start, stop, critical_dates):
yield (all_dates.drop(to_drop),)
# Also test with the trading calendar.
trading_days = default_nyse_schedule.all_execution_days
trading_days = get_calendar("NYSE").all_days
yield (trading_days[trading_days.slice_indexer(start, stop)],)
+58 -49
View File
@@ -37,7 +37,6 @@ from zipline.pipeline import SimplePipelineEngine
from zipline.pipeline.loaders.testing import make_seeded_random_loader
from zipline.utils.calendars import (
get_calendar,
ExchangeTradingSchedule,
)
@@ -364,41 +363,28 @@ class WithAssetFinder(WithDefaultDateBounds):
cls.asset_finder = cls.make_asset_finder()
class WithTradingSchedule(object):
class WithTradingCalendar(object):
"""
ZiplineTestCase mixing providing cls.trading_schedule as a class-level
ZiplineTestCase mixing providing cls.trading_calendar as a class-level
fixture.
After ``init_class_fixtures`` has been called, `cls.trading_schedule` is
populated with a trading schedule.
After ``init_class_fixtures`` has been called, `cls.trading_calendar` is
populated with a trading calendar.
Attributes
----------
TRADING_SCHEDULE_CALENDAR : ExchangeCalendar
The ExchangeCalendar to be wrapped in an ExchangeTradingSchedule.
Methods
-------
make_trading_schedule() -> TradingSchedule
A class method that constructs the trading schedule for the class.
See Also
--------
:class:`zipline.utils.calendars.trading_schedule.TradingSchedule`
TRADING_CALENDAR_STR : str
The identifier of the calendar to use.
"""
TRADING_SCHEDULE_CALENDAR = get_calendar('NYSE')
@classmethod
def make_trading_schedule(cls):
return ExchangeTradingSchedule(cls.TRADING_SCHEDULE_CALENDAR)
TRADING_CALENDAR_STR = 'NYSE'
@classmethod
def init_class_fixtures(cls):
super(WithTradingSchedule, cls).init_class_fixtures()
cls.trading_schedule = cls.make_trading_schedule()
super(WithTradingCalendar, cls).init_class_fixtures()
cls.trading_calendar = get_calendar(cls.TRADING_CALENDAR_STR)
class WithTradingEnvironment(WithAssetFinder, WithTradingSchedule):
class WithTradingEnvironment(WithAssetFinder, WithTradingCalendar):
"""
ZiplineTestCase mixin providing cls.env as a class-level fixture.
@@ -441,7 +427,7 @@ class WithTradingEnvironment(WithAssetFinder, WithTradingSchedule):
return TradingEnvironment(
load=cls.make_load_function(),
asset_db_path=cls.asset_finder.engine,
trading_schedule=cls.trading_schedule,
trading_calendar=cls.trading_calendar,
)
@classmethod
@@ -496,7 +482,7 @@ class WithSimParams(WithTradingEnvironment):
capital_base=cls.SIM_PARAMS_CAPITAL_BASE,
data_frequency=cls.SIM_PARAMS_DATA_FREQUENCY,
emission_rate=cls.SIM_PARAMS_EMISSION_RATE,
trading_schedule=cls.trading_schedule,
trading_calendar=cls.trading_calendar,
)
@classmethod
@@ -505,7 +491,7 @@ class WithSimParams(WithTradingEnvironment):
cls.sim_params = cls.make_simparams()
class WithNYSETradingDays(WithTradingSchedule):
class WithNYSETradingDays(WithTradingCalendar):
"""
ZiplineTestCase mixin providing cls.trading_days as a class-level fixture.
@@ -530,7 +516,7 @@ class WithNYSETradingDays(WithTradingSchedule):
def init_class_fixtures(cls):
super(WithNYSETradingDays, cls).init_class_fixtures()
all_days = cls.trading_schedule.all_execution_days
all_days = cls.trading_calendar.all_sessions
start_loc = all_days.get_loc(cls.DATA_MIN_DAY, 'bfill')
end_loc = all_days.get_loc(cls.DATA_MAX_DAY, 'ffill')
@@ -614,6 +600,7 @@ class WithEquityDailyBarData(WithTradingEnvironment):
zipline.testing.create_daily_bar_data
"""
EQUITY_DAILY_BAR_LOOKBACK_DAYS = 0
EQUITY_DAILY_BAR_USE_FULL_CALENDAR = False
EQUITY_DAILY_BAR_START_DATE = alias('START_DATE')
EQUITY_DAILY_BAR_END_DATE = alias('END_DATE')
@@ -634,9 +621,9 @@ class WithEquityDailyBarData(WithTradingEnvironment):
# source from minute logic.
'volume': 'last'
}
mm = cls.trading_schedule.all_execution_minutes
m_opens = cls.trading_schedule.schedule.market_open
m_closes = cls.trading_schedule.schedule.market_close
mm = cls.trading_calendar.all_minutes
m_opens = cls.trading_calendar.schedule.market_open
m_closes = cls.trading_calendar.schedule.market_close
minute_data = dict(cls.make_equity_minute_bar_data())
@@ -667,15 +654,28 @@ class WithEquityDailyBarData(WithTradingEnvironment):
def init_class_fixtures(cls):
super(WithEquityDailyBarData, cls).init_class_fixtures()
if cls.EQUITY_DAILY_BAR_USE_FULL_CALENDAR:
days = cls.trading_schedule.all_execution_days
days = cls.trading_calendar.all_sessions
else:
days = cls.trading_schedule.execution_days_in_range(
cls.trading_schedule.add_execution_days(
-1 * cls.EQUITY_DAILY_BAR_LOOKBACK_DAYS,
cls.EQUITY_DAILY_BAR_START_DATE,
),
if cls.trading_calendar.is_session(
cls.EQUITY_DAILY_BAR_START_DATE
):
first_session = cls.EQUITY_DAILY_BAR_START_DATE
else:
first_session = cls.trading_calendar.minute_to_session_label(
pd.Timestamp(cls.EQUITY_DAILY_BAR_START_DATE)
)
if cls.EQUITY_DAILY_BAR_LOOKBACK_DAYS > 0:
first_session = cls.trading_calendar.sessions_window(
first_session,
-1 * cls.EQUITY_DAILY_BAR_LOOKBACK_DAYS
)[0]
days = cls.trading_calendar.sessions_in_range(
first_session,
cls.EQUITY_DAILY_BAR_END_DATE,
)
cls.equity_daily_bar_days = days
@@ -746,7 +746,7 @@ class WithBcolzEquityDailyBarReader(WithEquityDailyBarData, WithTmpDir):
days = cls.equity_daily_bar_days
cls.bcolz_daily_bar_ctable = t = getattr(
BcolzDailyBarWriter(p, days),
BcolzDailyBarWriter(p, days, cls.trading_calendar),
cls._write_method_name,
)(cls.make_equity_daily_bar_data())
@@ -813,7 +813,7 @@ class WithEquityMinuteBarData(WithTradingEnvironment):
@classmethod
def make_equity_minute_bar_data(cls):
return create_minute_bar_data(
cls.trading_schedule.execution_minutes_for_days_in_range(
cls.trading_calendar.minutes_for_sessions_in_range(
cls.equity_minute_bar_days[0],
cls.equity_minute_bar_days[-1],
),
@@ -824,15 +824,23 @@ class WithEquityMinuteBarData(WithTradingEnvironment):
def init_class_fixtures(cls):
super(WithEquityMinuteBarData, cls).init_class_fixtures()
if cls.EQUITY_MINUTE_BAR_USE_FULL_CALENDAR:
days = cls.trading_schedule.all_execution_days
days = cls.trading_calendar.all_execution_days
else:
days = cls.trading_schedule.execution_days_in_range(
cls.trading_schedule.add_execution_days(
-1 * cls.EQUITY_MINUTE_BAR_LOOKBACK_DAYS,
cls.EQUITY_MINUTE_BAR_START_DATE,
),
cls.EQUITY_MINUTE_BAR_END_DATE,
first_session = cls.trading_calendar.minute_to_session_label(
pd.Timestamp(cls.EQUITY_MINUTE_BAR_START_DATE)
)
if cls.EQUITY_MINUTE_BAR_LOOKBACK_DAYS > 0:
first_session = cls.trading_calendar.sessions_window(
first_session,
-1 * cls.EQUITY_MINUTE_BAR_LOOKBACK_DAYS
)[0]
days = cls.trading_calendar.sessions_in_range(
first_session,
cls.EQUITY_MINUTE_BAR_END_DATE
)
cls.equity_minute_bar_days = days
@@ -889,11 +897,12 @@ class WithBcolzEquityMinuteBarReader(WithEquityMinuteBarData, WithTmpDir):
cls.bcolz_minute_bar_path = p = \
cls.make_bcolz_minute_bar_rootdir_path()
days = cls.equity_minute_bar_days
writer = BcolzMinuteBarWriter(
days[0],
p,
cls.trading_schedule.schedule.market_open.loc[days],
cls.trading_schedule.schedule.market_close.loc[days],
cls.trading_calendar.schedule.market_open.loc[days],
cls.trading_calendar.schedule.market_close.loc[days],
US_EQUITIES_MINUTES_PER_DAY
)
writer.write(cls.make_equity_minute_bar_data())
@@ -1108,7 +1117,7 @@ class WithDataPortal(WithAdjustmentReader,
return DataPortal(
self.env.asset_finder,
self.trading_schedule,
self.trading_calendar,
first_trading_day=self.DATA_PORTAL_FIRST_TRADING_DAY,
equity_daily_reader=(
self.bcolz_equity_daily_bar_reader
+8 -9
View File
@@ -13,14 +13,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
from .exchange_calendar import (
ExchangeCalendar, get_calendar
from .trading_calendar import TradingCalendar
from .calendar_utils import (
get_calendar,
register_calendar,
deregister_calendar,
clear_calendars
)
from .trading_schedule import (
TradingSchedule, ExchangeTradingSchedule, default_nyse_schedule
)
from .calendar_helpers import normalize_date
__all__ = ['get_calendar', 'ExchangeCalendar', 'TradingSchedule',
'ExchangeTradingSchedule', 'default_nyse_schedule',
'normalize_date']
__all__ = ['get_calendar', 'TradingCalendar', 'register_calendar',
'deregister_calendar', 'clear_calendars']
@@ -0,0 +1,53 @@
from numpy cimport ndarray, long_t
from numpy import searchsorted
cimport cython
@cython.boundscheck(False)
@cython.wraparound(False)
def next_divider_idx(ndarray[long_t, ndim=1] dividers, long_t minute_val):
cdef int divider_idx
cdef long target
divider_idx = searchsorted(dividers, minute_val, side="right")
target = dividers[divider_idx]
if minute_val == target:
# if dt is exactly on the divider, go to the next value
return divider_idx + 1
else:
return divider_idx
@cython.boundscheck(False)
@cython.wraparound(False)
def previous_divider_idx(ndarray[long_t, ndim=1] dividers,
long_t minute_val):
cdef int divider_idx
divider_idx = searchsorted(dividers, minute_val)
if divider_idx == 0:
raise ValueError("Cannot go earlier in calendar!")
return divider_idx - 1
def is_open(ndarray[long_t, ndim=1] opens,
ndarray[long_t, ndim=1] closes,
long_t minute_val):
cdef open_idx, close_idx
open_idx = searchsorted(opens, minute_val)
close_idx = searchsorted(closes, minute_val)
if open_idx != close_idx:
# if the indices are not same, that means the market is open
return True
else:
try:
# if they are the same, it might be the first minute of a
# session
return minute_val == opens[open_idx]
except IndexError:
# this can happen if we're outside the schedule's range (like
# after the last close)
return False
-239
View File
@@ -1,239 +0,0 @@
#
# Copyright 2016 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import pandas as pd
import numpy as np
import bisect
from zipline.errors import NoFurtherDataError
def normalize_date(date):
date = pd.Timestamp(date, tz='UTC')
return pd.tseries.tools.normalize_date(date)
def delta_from_time(t):
"""
Convert a datetime.time into a timedelta.
"""
return pd.Timedelta(
hours=t.hour,
minutes=t.minute,
seconds=t.second,
)
def _get_index(dt, all_trading_days):
"""
Return the index of the given @dt, or the index of the preceding
trading day if the given dt is not in the trading calendar.
"""
ndt = normalize_date(dt)
if ndt in all_trading_days:
return all_trading_days.searchsorted(ndt)
else:
return all_trading_days.searchsorted(ndt) - 1
# The following methods are intended to be inserted in both the
# ExchangeCalendar and TradingSchedule classes.
# These methods live in the helpers module to avoid code duplication.
def next_scheduled_day(date, last_trading_day, is_scheduled_day_hook):
"""
Returns the next session date in the calendar after the provided date.
Parameters
----------
date : Timestamp
The date whose following date is needed.
Returns
-------
Timestamp
The next scheduled date after the provided date.
"""
dt = normalize_date(date)
delta = pd.Timedelta(days=1)
while dt <= last_trading_day:
dt += delta
if is_scheduled_day_hook(dt):
return dt
raise NoFurtherDataError(msg='Cannot find next day after %s' % date)
def previous_scheduled_day(date, first_trading_day, is_scheduled_day_hook):
"""
Returns the previous session date in the calendar before the provided date.
Parameters
----------
date : Timestamp
The date whose previous date is needed.
Returns
-------
Timestamp
The previous scheduled date before the provided date.
"""
dt = normalize_date(date)
delta = pd.Timedelta(days=-1)
while first_trading_day < dt:
dt += delta
if is_scheduled_day_hook(dt):
return dt
raise NoFurtherDataError(msg='Cannot find previous day before %s' % date)
def next_open_and_close(date, open_and_close_hook,
next_scheduled_day_hook):
return open_and_close_hook(next_scheduled_day_hook(date))
def previous_open_and_close(date, open_and_close_hook,
previous_scheduled_day_hook):
return open_and_close_hook(previous_scheduled_day_hook(date))
def scheduled_day_distance(first_date, second_date, all_days):
first_date = normalize_date(first_date)
second_date = normalize_date(second_date)
i = bisect.bisect_left(all_days, first_date)
if i == len(all_days): # nothing found
return None
j = bisect.bisect_left(all_days, second_date)
if j == len(all_days):
return None
distance = j - 1
assert distance >= 0
return distance
def minutes_for_day(day, open_and_close_hook):
start, end = open_and_close_hook(day)
return pd.date_range(start, end, freq='T')
def days_in_range(start, end, all_days):
"""
Get all execution days between start and end,
inclusive.
"""
start_date = normalize_date(start)
end_date = normalize_date(end)
return all_days[all_days.slice_indexer(start_date, end_date)]
def minutes_for_days_in_range(start, end, days_in_range_hook,
minutes_for_day_hook):
"""
Get all execution minutes for the days between start and end,
inclusive.
"""
start_date = normalize_date(start)
end_date = normalize_date(end)
all_minutes = []
for day in days_in_range_hook(start_date, end_date):
day_minutes = minutes_for_day_hook(day)
all_minutes.append(day_minutes)
# Concatenate all minutes and truncate minutes before start/after end.
return pd.DatetimeIndex(np.concatenate(all_minutes), copy=False, tz='UTC')
def add_scheduled_days(n, date, next_scheduled_day_hook,
previous_scheduled_day_hook, all_trading_days):
"""
Adds n trading days to date. If this would fall outside of the
trading calendar, a NoFurtherDataError is raised.
Parameters
----------
n : int
The number of days to add to date, this can be positive or
negative.
date : datetime
The date to add to.
Returns
-------
datetime
n trading days added to date.
"""
if n == 1:
return next_scheduled_day_hook(date)
if n == -1:
return previous_scheduled_day_hook(date)
idx = _get_index(date, all_trading_days) + n
if idx < 0 or idx >= len(all_trading_days):
raise NoFurtherDataError(
msg='Cannot add %d days to %s' % (n, date)
)
return all_trading_days[idx]
def all_scheduled_minutes(all_days, minutes_for_days_in_range_hook):
first_day = all_days[0]
last_day = all_days[-1]
return minutes_for_days_in_range_hook(first_day, last_day)
def next_scheduled_minute(start, is_scheduled_day_hook, open_and_close_hook,
next_open_and_close_hook):
"""
Get the next market minute after @start. This is either the immediate
next minute, the open of the same day if @start is before the market
open on a trading day, or the open of the next market day after @start.
"""
if is_scheduled_day_hook(start):
market_open, market_close = open_and_close_hook(start)
# If start before market open on a trading day, return market open.
if start < market_open:
return market_open
# If start is during trading hours, then get the next minute.
elif start < market_close:
return start + pd.Timedelta(minutes=1)
# If start is not in a trading day, or is after the market close
# then return the open of the *next* trading day.
return next_open_and_close_hook(start)[0]
def previous_scheduled_minute(start, is_scheduled_day_hook,
open_and_close_hook,
previous_open_and_close_hook):
"""
Get the next market minute before @start. This is either the immediate
previous minute, the close of the same day if @start is after the close
on a trading day, or the close of the market day before @start.
"""
if is_scheduled_day_hook(start):
market_open, market_close = open_and_close_hook(start)
# If start after the market close, return market close.
if start > market_close:
return market_close
# If start is during trading hours, then get previous minute.
if start > market_open:
return start - pd.Timedelta(minutes=1)
# If start is not a trading day, or is before the market open
# then return the close of the *previous* trading day.
return previous_open_and_close_hook(start)[1]
+96
View File
@@ -0,0 +1,96 @@
from zipline.errors import (
InvalidCalendarName,
CalendarNameCollision,
)
from zipline.utils.calendars.exchange_calendar_nyse import NYSEExchangeCalendar
from zipline.utils.calendars.exchange_calendar_cme import CMEExchangeCalendar
from zipline.utils.calendars.exchange_calendar_bmf import BMFExchangeCalendar
from zipline.utils.calendars.exchange_calendar_lse import LSEExchangeCalendar
from zipline.utils.calendars.exchange_calendar_tsx import TSXExchangeCalendar
_static_calendars = {}
def get_calendar(name):
"""
Retrieves an instance of an TradingCalendar whose name is given.
Parameters
----------
name : str
The name of the TradingCalendar to be retrieved.
Returns
-------
TradingCalendar
The desired calendar.
"""
if name not in _static_calendars:
if name == 'NYSE':
cal = NYSEExchangeCalendar()
elif name == 'CME':
cal = CMEExchangeCalendar()
elif name == 'BMF':
cal = BMFExchangeCalendar()
elif name == 'LSE':
cal = LSEExchangeCalendar()
elif name == 'TSX':
cal = TSXExchangeCalendar()
else:
raise InvalidCalendarName(calendar_name=name)
register_calendar(cal)
return _static_calendars[name]
def deregister_calendar(cal_name):
"""
If a calendar is registered with the given name, it is de-registered.
Parameters
----------
cal_name : str
The name of the calendar to be deregistered.
"""
try:
_static_calendars.pop(cal_name)
except KeyError:
pass
def clear_calendars():
"""
Deregisters all current registered calendars
"""
_static_calendars.clear()
def register_calendar(calendar, force=False):
"""
Registers a calendar for retrieval by the get_calendar method.
Parameters
----------
calendar : TradingCalendar
The calendar to be registered for retrieval.
force : bool, optional
If True, old calendars will be overwritten on a name collision.
If False, name collisions will raise an exception. Default: False.
Raises
------
CalendarNameCollision
If a calendar is already registered with the given calendar's name.
"""
# If we are forcing the registration, remove an existing calendar with the
# same name.
if force:
deregister_calendar(calendar.name)
# Check if we are already holding a calendar with the same name
if calendar.name in _static_calendars:
raise CalendarNameCollision(calendar_name=calendar.name)
_static_calendars[calendar.name] = calendar
@@ -1,588 +0,0 @@
#
# Copyright 2016 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from abc import (
ABCMeta,
abstractproperty,
abstractmethod,
)
import pandas as pd
import numpy as np
from pandas import (
DataFrame,
date_range,
DateOffset,
DatetimeIndex,
)
from pandas.tseries.offsets import CustomBusinessDay
from six import with_metaclass
from zipline.errors import (
InvalidCalendarName,
CalendarNameCollision,
)
from zipline.utils.memoize import remember_last
from .calendar_helpers import (
next_scheduled_day,
previous_scheduled_day,
next_open_and_close,
previous_open_and_close,
scheduled_day_distance,
minutes_for_day,
days_in_range,
minutes_for_days_in_range,
add_scheduled_days,
next_scheduled_minute,
previous_scheduled_minute,
)
start_default = pd.Timestamp('1990-01-01', tz='UTC')
end_base = pd.Timestamp('today', tz='UTC')
# Give an aggressive buffer for logic that needs to use the next trading
# day or minute.
end_default = end_base + pd.Timedelta(days=365)
NANOS_IN_MINUTE = 60000000000
def days_at_time(days, t, tz, day_offset=0):
"""
Shift an index of days to time t, interpreted in tz.
Overwrites any existing tz info on the input.
Parameters
----------
days : DatetimeIndex
The "base" time which we want to change.
t : datetime.time
The time we want to offset @days by
tz : pytz.timezone
The timezone which these times represent
day_offset : int
The number of days we want to offset @days by
"""
days = DatetimeIndex(days).tz_localize(None).tz_localize(tz)
days_offset = days + DateOffset(day_offset)
return days_offset.shift(
1, freq=DateOffset(hour=t.hour, minute=t.minute, second=t.second)
).tz_convert('UTC')
def holidays_at_time(calendar, start, end, time, tz):
return days_at_time(
calendar.holidays(
# Workaround for https://github.com/pydata/pandas/issues/9825.
start.tz_localize(None),
end.tz_localize(None),
),
time,
tz=tz,
)
def _overwrite_special_dates(midnight_utcs,
opens_or_closes,
special_opens_or_closes):
"""
Overwrite dates in open_or_closes with corresponding dates in
special_opens_or_closes, using midnight_utcs for alignment.
"""
# Short circuit when nothing to apply.
if not len(special_opens_or_closes):
return
len_m, len_oc = len(midnight_utcs), len(opens_or_closes)
if len_m != len_oc:
raise ValueError(
"Found misaligned dates while building calendar.\n"
"Expected midnight_utcs to be the same length as open_or_closes,\n"
"but len(midnight_utcs)=%d, len(open_or_closes)=%d" % len_m, len_oc
)
# Find the array indices corresponding to each special date.
indexer = midnight_utcs.get_indexer(special_opens_or_closes.normalize())
# -1 indicates that no corresponding entry was found. If any -1s are
# present, then we have special dates that doesn't correspond to any
# trading day.
if -1 in indexer:
bad_dates = list(special_opens_or_closes[indexer == -1])
raise ValueError("Special dates %s are not trading days." % bad_dates)
# NOTE: This is a slightly dirty hack. We're in-place overwriting the
# internal data of an Index, which is conceptually immutable. Since we're
# maintaining sorting, this should be ok, but this is a good place to
# sanity check if things start going haywire with calendar computations.
opens_or_closes.values[indexer] = special_opens_or_closes.values
class ExchangeCalendar(with_metaclass(ABCMeta)):
"""
An ExchangeCalendar represents the timing information of a single market
exchange.
Properties
----------
name : str
The name of this exchange calendar.
e.g.: 'NYSE', 'LSE', 'CME Energy'
tz : timezone
The native timezone of the exchange.
"""
def __init__(self, start=start_default, end=end_default):
tz = self.tz
open_offset = self.open_offset
close_offset = self.close_offset
# Define those days on which the exchange is usually open.
self.day = CustomBusinessDay(
holidays=self.holidays_adhoc,
calendar=self.holidays_calendar,
)
# Midnight in UTC for each trading day.
_all_days = date_range(start, end, freq=self.day, tz='UTC')
# `DatetimeIndex`s of standard opens/closes for each day.
self._opens = days_at_time(_all_days, self.open_time, tz, open_offset)
self._closes = days_at_time(
_all_days, self.close_time, tz, close_offset
)
# `DatetimeIndex`s of nonstandard opens/closes
_special_opens = self._special_opens(start, end)
_special_closes = self._special_closes(start, end)
# Overwrite the special opens and closes on top of the standard ones.
_overwrite_special_dates(_all_days, self._opens, _special_opens)
_overwrite_special_dates(_all_days, self._closes, _special_closes)
# In pandas 0.16.1 _opens and _closes will lose their timezone
# information. This looks like it has been resolved in 0.17.1.
# http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#datetime-with-tz # noqa
self.schedule = DataFrame(
index=_all_days,
columns=['market_open', 'market_close'],
data={
'market_open': self._opens,
'market_close': self._closes,
},
dtype='datetime64[ns]',
)
self.first_trading_day = _all_days[0]
self.last_trading_day = _all_days[-1]
self.early_closes = DatetimeIndex(
_special_closes.map(self.session_date)
)
def next_trading_day(self, date):
return next_scheduled_day(
date,
last_trading_day=self.last_trading_day,
is_scheduled_day_hook=self.is_open_on_day,
)
def previous_trading_day(self, date):
return previous_scheduled_day(
date,
first_trading_day=self.first_trading_day,
is_scheduled_day_hook=self.is_open_on_day,
)
def next_open_and_close(self, date):
return next_open_and_close(
date,
open_and_close_hook=self.open_and_close,
next_scheduled_day_hook=self.next_trading_day,
)
def previous_open_and_close(self, date):
return previous_open_and_close(
date,
open_and_close_hook=self.open_and_close,
previous_scheduled_day_hook=self.previous_trading_day,
)
def trading_day_distance(self, first_date, second_date):
return scheduled_day_distance(
first_date, second_date,
all_days=self.all_trading_days,
)
def trading_minutes_for_day(self, day):
return minutes_for_day(
day,
open_and_close_hook=self.open_and_close,
)
def trading_days_in_range(self, start, end):
return days_in_range(
start, end,
all_days=self.all_trading_days,
)
def trading_minutes_for_days_in_range(self, start, end):
return minutes_for_days_in_range(
start, end,
days_in_range_hook=self.trading_days_in_range,
minutes_for_day_hook=self.trading_minutes_for_day,
)
def add_trading_days(self, n, date):
"""
Adds n trading days to date. If this would fall outside of the
ExchangeCalendar, a NoFurtherDataError is raised.
Parameters
----------
n : int
The number of days to add to date, this can be positive or
negative.
date : datetime
The date to add to.
Returns
-------
datetime
n trading days added to date.
"""
return add_scheduled_days(
n, date,
next_scheduled_day_hook=self.next_trading_day,
previous_scheduled_day_hook=self.previous_trading_day,
all_trading_days=self.all_trading_days,
)
def next_trading_minute(self, start):
return next_scheduled_minute(
start,
is_scheduled_day_hook=self.is_open_on_day,
open_and_close_hook=self.open_and_close,
next_open_and_close_hook=self.next_open_and_close,
)
def previous_trading_minute(self, start):
return previous_scheduled_minute(
start,
is_scheduled_day_hook=self.is_open_on_day,
open_and_close_hook=self.open_and_close,
previous_open_and_close_hook=self.previous_open_and_close,
)
def _special_dates(self, calendars, ad_hoc_dates, start_date, end_date):
"""
Union an iterable of pairs of the form
(time, calendar)
and an iterable of pairs of the form
(time, [dates])
(This is shared logic for computing special opens and special closes.)
"""
tz = self.native_timezone
_dates = DatetimeIndex([], tz='UTC').union_many(
[
holidays_at_time(calendar, start_date, end_date, time_, tz)
for time_, calendar in calendars
] + [
days_at_time(datetimes, time_, tz)
for time_, datetimes in ad_hoc_dates
]
)
return _dates[(_dates >= start_date) & (_dates <= end_date)]
def _special_opens(self, start, end):
return self._special_dates(
self.special_opens_calendars,
self.special_opens_adhoc,
start,
end,
)
def _special_closes(self, start, end):
return self._special_dates(
self.special_closes_calendars,
self.special_closes_adhoc,
start,
end,
)
@abstractproperty
def name(self):
"""
The name of this exchange calendar.
E.g.: 'NYSE', 'LSE', 'CME Energy'
"""
raise NotImplementedError()
@abstractproperty
def tz(self):
"""
The native timezone of the exchange.
SD: Not clear that this needs to be exposed.
"""
raise NotImplementedError()
@abstractmethod
def is_open_on_minute(self, dt):
"""
Is the exchange open at minute @dt.
Parameters
----------
dt : Timestamp
Returns
-------
bool
True if exchange is open at the given dt, otherwise False.
"""
raise NotImplementedError()
@abstractmethod
def is_open_on_day(self, dt):
"""
Is the exchange open anytime during @dt.
SD: Need to decide whether this method answers the question:
- Is exchange open at any time during the calendar day containing dt
or
- Is exchange open at any time during the trading session containg dt.
Semantically it seems that the first makes more sense.
Parameters
----------
dt : Timestamp
The UTC-canonicalized date.
Returns
-------
bool
True if exchange is open at any time during @dt.
"""
raise NotImplementedError()
@abstractmethod
def trading_days(self, start, end):
"""
Calculates all of the exchange sessions between the given
start and end.
SD: Presumably @start and @end are UTC-canonicalized, as our exchange
sessions are. If not, then it's not clear how this method should behave
if @start and @end are both in the middle of the day.
Parameters
----------
start : Timestamp
end : Timestamp
Returns
-------
DatetimeIndex
A DatetimeIndex populated with all of the trading days between
the given start and end.
"""
raise NotImplementedError()
@property
def all_trading_days(self):
return self.schedule.index
@property
@remember_last
def all_trading_minutes(self):
opens_in_ns = \
self._opens.values.astype('datetime64[ns]').astype(np.int64)
closes_in_ns = \
self._closes.values.astype('datetime64[ns]').astype(np.int64)
deltas = closes_in_ns - opens_in_ns
# + 1 because we want 390 days per standard day, not 389
daily_sizes = (deltas / NANOS_IN_MINUTE) + 1
num_minutes = np.sum(daily_sizes).astype(np.int64)
# One allocation for the entire thing. This assumes that each day
# represents a contiguous block of minutes, which might not always
# be the case in the future.
all_minutes = np.empty(num_minutes, dtype='datetime64[ns]')
idx = 0
for day_idx, size in enumerate(daily_sizes):
# lots of small allocations, but it's fast enough for now.
all_minutes[idx:(idx + size)] = \
np.arange(
opens_in_ns[day_idx],
closes_in_ns[day_idx] + NANOS_IN_MINUTE,
NANOS_IN_MINUTE
)
idx += size
return DatetimeIndex(all_minutes).tz_localize("UTC")
@abstractmethod
def open_and_close(self, date):
"""
Given a UTC-canonicalized date, returns a tuple of timestamps of the
open and close of the exchange session on that date.
SD: Can @date be an arbitrary datetime, or should we first map it to
and exchange session using session_date. Need to check what the
consumers expect.
Parameters
----------
date : Timestamp
The UTC-canonicalized date whose open and close are needed.
Returns
-------
(Timestamp, Timestamp)
The open and close for the given date.
"""
raise NotImplementedError()
@abstractmethod
def session_date(self, dt):
"""
Given a time, returns the UTC-canonicalized date of the exchange
session in which the time belongs. If the time is not in an exchange
session (while the market is closed), returns the date of the next
exchange session after the time.
Parameters
----------
dt : Timestamp
Returns
-------
Timestamp
The date of the exchange session in which dt belongs.
"""
raise NotImplementedError()
_static_calendars = {}
def get_calendar(name):
"""
Retrieves an instance of an ExchangeCalendar whose name is given.
Parameters
----------
name : str
The name of the ExchangeCalendar to be retrieved.
"""
# First, check if the calendar is already registered
if name not in _static_calendars:
# Check if it is a lazy calendar. If so, build and register it.
if name == 'NYSE':
from zipline.utils.calendars.exchange_calendar_nyse \
import NYSEExchangeCalendar
nyse_cal = NYSEExchangeCalendar()
register_calendar(nyse_cal)
elif name == 'CME':
from zipline.utils.calendars.exchange_calendar_cme \
import CMEExchangeCalendar
cme_cal = CMEExchangeCalendar()
register_calendar(cme_cal)
elif name == 'BMF':
from zipline.utils.calendars.exchange_calendar_bmf \
import BMFExchangeCalendar
bmf_cal = BMFExchangeCalendar()
register_calendar(bmf_cal)
elif name == 'LSE':
from zipline.utils.calendars.exchange_calendar_lse \
import LSEExchangeCalendar
lse_cal = LSEExchangeCalendar()
register_calendar(lse_cal)
elif name == 'TSX':
from zipline.utils.calendars.exchange_calendar_tsx \
import TSXExchangeCalendar
tsx_cal = TSXExchangeCalendar()
register_calendar(tsx_cal)
else:
# It's not a lazy calendar, so raise an exception
raise InvalidCalendarName(calendar_name=name)
return _static_calendars[name]
def deregister_calendar(cal_name):
"""
If a calendar is registered with the given name, it is de-registered.
Parameters
----------
cal_name : str
The name of the calendar to be deregistered.
"""
try:
_static_calendars.pop(cal_name)
except KeyError:
pass
def clear_calendars():
"""
Deregisters all current registered calendars
"""
_static_calendars.clear()
def register_calendar(calendar, force=False):
"""
Registers a calendar for retrieval by the get_calendar method.
Parameters
----------
calendar : ExchangeCalendar
The calendar to be registered for retrieval.
force : bool, optional
If True, old calendars will be overwritten on a name collision.
If False, name collisions will raise an exception. Default: False.
Raises
------
CalendarNameCollision
If a calendar is already registered with the given calendar's name.
"""
# If we are forcing the registration, remove an existing calendar with the
# same name.
if force:
deregister_calendar(calendar.name)
# Check if we are already holding a calendar with the same name
if calendar.name in _static_calendars:
raise CalendarNameCollision(calendar_name=calendar.name)
_static_calendars[calendar.name] = calendar
@@ -1,5 +1,4 @@
from datetime import time
from pandas import Timedelta
from pandas.tseries.holiday import(
AbstractHolidayCalendar,
Holiday,
@@ -9,10 +8,10 @@ from pandas.tseries.holiday import(
)
from pytz import timezone
from zipline.utils.calendars.exchange_calendar import ExchangeCalendar
from zipline.utils.calendars.calendar_helpers import normalize_date
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = range(7)
from .trading_calendar import (
TradingCalendar,
FRIDAY
)
# Universal Confraternization (new years day)
ConfUniversal = Holiday(
@@ -170,7 +169,7 @@ class BMFLateOpenCalendar(AbstractHolidayCalendar):
]
class BMFExchangeCalendar(ExchangeCalendar):
class BMFExchangeCalendar(TradingCalendar):
"""
Exchange calendar for BM&F BOVESPA
@@ -197,8 +196,8 @@ class BMFExchangeCalendar(ExchangeCalendar):
- New Year's Eve (December 31)
"""
exchange_name = 'BMF'
native_timezone = timezone('America/Sao_Paulo')
name = "BMF"
tz = timezone('America/Sao_Paulo')
open_time = time(10, 1)
close_time = time(17)
@@ -217,160 +216,3 @@ class BMFExchangeCalendar(ExchangeCalendar):
special_opens_adhoc = ()
special_closes_adhoc = ()
@property
def name(self):
"""
The name of this exchange calendar.
E.g.: 'NYSE', 'LSE', 'CME Energy'
"""
return self.exchange_name
@property
def tz(self):
"""
The native timezone of the exchange.
"""
return self.native_timezone
def is_open_on_minute(self, dt):
"""
Is the exchange open (accepting orders) at @dt.
Parameters
----------
dt : Timestamp
Returns
-------
bool
True if exchange is open at the given dt, otherwise False.
"""
# Retrieve the exchange session relevant for this datetime
session = self.session_date(dt)
# Retrieve the open and close for this exchange session
open, close = self.open_and_close(session)
# Is @dt within the trading hours for this exchange session
return open <= dt and dt <= close
def is_open_on_day(self, dt):
"""
Is the exchange open (accepting orders) anytime during the calendar day
containing @dt.
Parameters
----------
dt : Timestamp
Returns
-------
bool
True if exchange is open at any time during the day containing @dt
"""
dt_normalized = normalize_date(dt)
return dt_normalized in self.schedule.index
def trading_days(self, start, end):
"""
Calculates all of the exchange sessions between the given
start and end, inclusive.
SD: Should @start and @end are UTC-canonicalized, as our exchange
sessions are. If not, then it's not clear how this method should behave
if @start and @end are both in the middle of the day. Here, I assume we
need to map @start and @end to session.
Parameters
----------
start : Timestamp
end : Timestamp
Returns
-------
DatetimeIndex
A DatetimeIndex populated with all of the trading days between
the given start and end.
"""
start_session = self.session_date(start)
end_session = self.session_date(end)
# Increment end_session by one day, beucase .loc[s:e] return all values
# in the DataFrame up to but not including `e`.
# end_session += Timedelta(days=1)
return self.schedule.loc[start_session:end_session]
def open_and_close(self, dt):
"""
Given a datetime, returns a tuple of timestamps of the
open and close of the exchange session containing the datetime.
SD: Should we accept an arbitrary datetime, or should we first map it
to and exchange session using session_date. Need to check what the
consumers expect. Here, I assume we need to map it to a session.
Parameters
----------
dt : Timestamp
A dt in a session whose open and close are needed.
Returns
-------
(Timestamp, Timestamp)
The open and close for the given dt.
"""
session = self.session_date(dt)
return self._get_open_and_close(session)
def _get_open_and_close(self, session_date):
"""
Retrieves the open and close for a given session.
Parameters
----------
session_date : Timestamp
The canonicalized session_date whose open and close are needed.
Returns
-------
(Timestamp, Timestamp) or (None, None)
The open and close for the given dt, or Nones if the given date is
not a session.
"""
# Return a tuple of nones if the given date is not a session.
if session_date not in self.schedule.index:
return (None, None)
o_and_c = self.schedule.loc[session_date]
# `market_open` and `market_close` should be timezone aware, but pandas
# 0.16.1 does not appear to support this:
# http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#datetime-with-tz # noqa
return (o_and_c['market_open'].tz_localize('UTC'),
o_and_c['market_close'].tz_localize('UTC'))
def session_date(self, dt):
"""
Given a datetime, returns the UTC-canonicalized date of the exchange
session in which the time belongs. If the time is not in an exchange
session (while the market is closed), returns the date of the next
exchange session after the time.
Parameters
----------
dt : Timestamp
A timezone-aware Timestamp.
Returns
-------
Timestamp
The date of the exchange session in which dt belongs.
"""
# Check if the dt is after the market close
# If so, advance to the next day
if self.is_open_on_day(dt):
_, close = self._get_open_and_close(normalize_date(dt))
if dt > close:
dt += Timedelta(days=1)
while not self.is_open_on_day(dt):
dt += Timedelta(days=1)
return normalize_date(dt)
+36 -309
View File
@@ -16,154 +16,44 @@
from datetime import time
from itertools import chain
from dateutil.relativedelta import (
MO,
TH,
)
from pandas import (
date_range,
DateOffset,
Timedelta,
Timestamp,
)
from pandas.tseries.holiday import(
AbstractHolidayCalendar,
GoodFriday,
Holiday,
nearest_workday,
sunday_to_monday,
USLaborDay,
USPresidentsDay,
USThanksgivingDay,
)
from pandas.tseries.offsets import Day
from pandas.tseries.holiday import AbstractHolidayCalendar
from pytz import timezone
from zipline.utils.calendars import ExchangeCalendar
from .calendar_helpers import normalize_date
# Useful resources for making changes to this file:
# http://www.nyse.com/pdfs/closings.pdf
# http://www.stevemorse.org/jcal/whendid.html
# http://www.cmegroup.com/tools-information/holiday-calendar.html
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = range(7)
from .trading_calendar import TradingCalendar
from .us_holidays import (
USNewYearsDay,
Christmas,
ChristmasEveBefore1993,
ChristmasEveInOrAfter1993,
FridayAfterIndependenceDayExcept2013,
MonTuesThursBeforeIndependenceDay,
USBlackFridayInOrAfter1993,
September11Closings,
USNationalDaysofMourning
)
US_CENTRAL = timezone('America/Chicago')
CME_OPEN = time(17)
CME_CLOSE = time(16)
# CME_STANDARD_EARLY_CLOSE = time(13)
# The CME seems to have different holiday rules depending on the type
# of instrument. For example, http://www.cmegroup.com/tools-information/holiday-calendar/files/2016-4th-of-july-holiday-schedule.pdf # noqa
# shows that Equity, Interest Rate, FX, Energy, Metals & DME Products close at
# 1200 CT on July 4, 2016, while Grain, Oilseed & MGEX Products and Livestock,
# Dairy & Lumber products are completely closed.
# For now, we will treat the CME as having a single calendar, and just go with
# the most conservative hours - and treat July 4 as an early close at noon.
CME_STANDARD_EARLY_CLOSE = time(12)
# Does the market open or close on a different calendar day, compared to the
# calendar day assigned by the exchang to this session?
# calendar day assigned by the exchange to this session?
CME_OPEN_OFFSET = -1
CME_CLOSE_OFFSET = 0
# Closings
USNewYearsDay = Holiday(
'New Years Day',
month=1,
day=1,
# When Jan 1 is a Sunday, NYSE observes the subsequent Monday. When Jan 1
# Saturday (as in 2005 and 2011), no holiday is observed.
observance=sunday_to_monday
)
USMemorialDay = Holiday(
# NOTE: The definition for Memorial Day is incorrect as of pandas 0.16.0.
# See https://github.com/pydata/pandas/issues/9760.
'Memorial Day',
month=5,
day=25,
offset=DateOffset(weekday=MO(1)),
)
USMartinLutherKingJrAfter1998 = Holiday(
'Dr. Martin Luther King Jr. Day',
month=1,
day=1,
# The NYSE didn't observe MLK day as a holiday until 1998.
start_date=Timestamp('1998-01-01'),
offset=DateOffset(weekday=MO(3)),
)
USIndependenceDay = Holiday(
'July 4th',
month=7,
day=4,
observance=nearest_workday,
)
Christmas = Holiday(
'Christmas',
month=12,
day=25,
observance=nearest_workday,
)
# Half Days
MonTuesThursBeforeIndependenceDay = Holiday(
# When July 4th is a Tuesday, Wednesday, or Friday, the previous day is a
# half day.
'Mondays, Tuesdays, and Thursdays Before Independence Day',
month=7,
day=3,
days_of_week=(MONDAY, TUESDAY, THURSDAY),
start_date=Timestamp("1995-01-01"),
)
FridayAfterIndependenceDayExcept2013 = Holiday(
# When July 4th is a Thursday, the next day is a half day (except in 2013,
# when, for no explicable reason, Wednesday was a half day instead).
"Fridays after Independence Day that aren't in 2013",
month=7,
day=5,
days_of_week=(FRIDAY,),
observance=lambda dt: None if dt.year == 2013 else dt,
start_date=Timestamp("1995-01-01"),
)
USBlackFridayBefore1993 = Holiday(
'Black Friday',
month=11,
day=1,
# Black Friday was not observed until 1992.
start_date=Timestamp('1992-01-01'),
end_date=Timestamp('1993-01-01'),
offset=[DateOffset(weekday=TH(4)), Day(1)],
)
USBlackFridayInOrAfter1993 = Holiday(
'Black Friday',
month=11,
day=1,
start_date=Timestamp('1993-01-01'),
offset=[DateOffset(weekday=TH(4)), Day(1)],
)
# These have the same definition, but are used in different places because the
# NYSE closed at 2:00 PM on Christmas Eve until 1993.
ChristmasEveBefore1993 = Holiday(
'Christmas Eve',
month=12,
day=24,
end_date=Timestamp('1993-01-01'),
# When Christmas is a Saturday, the 24th is a full holiday.
days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY),
)
ChristmasEveInOrAfter1993 = Holiday(
'Christmas Eve',
month=12,
day=24,
start_date=Timestamp('1993-01-01'),
# When Christmas is a Saturday, the 24th is a full holiday.
days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY),
)
# http://en.wikipedia.org/wiki/Aftermath_of_the_September_11_attacks
September11Closings = date_range('2001-09-11', '2001-09-16', tz='UTC')
# National Days of Mourning
# - President Richard Nixon - April 27, 1994
# - President Ronald W. Reagan - June 11, 2004
# - President Gerald R. Ford - Jan 2, 2007
USNationalDaysofMourning = [
Timestamp('1994-04-27', tz='UTC'),
Timestamp('2004-06-11', tz='UTC'),
Timestamp('2007-01-02', tz='UTC'),
]
CME_CLOSE_OFFSET = -0
class CMEHolidayCalendar(AbstractHolidayCalendar):
@@ -174,14 +64,6 @@ class CMEHolidayCalendar(AbstractHolidayCalendar):
"""
rules = [
USNewYearsDay,
USMartinLutherKingJrAfter1998,
USPresidentsDay,
GoodFriday,
USMemorialDay,
USIndependenceDay,
USLaborDay,
USThanksgivingDay,
USIndependenceDay,
Christmas,
]
@@ -194,15 +76,16 @@ class CMEEarlyCloseCalendar(AbstractHolidayCalendar):
MonTuesThursBeforeIndependenceDay,
FridayAfterIndependenceDayExcept2013,
USBlackFridayInOrAfter1993,
ChristmasEveBefore1993,
ChristmasEveInOrAfter1993,
]
class CMEExchangeCalendar(ExchangeCalendar):
class CMEExchangeCalendar(TradingCalendar):
"""
Exchange calendar for CME
Open Time: 5:00 AM, America/Chicago
Open Time: 5:00 PM, America/Chicago
Close Time: 5:00 PM, America/Chicago
Regularly-Observed Holidays:
@@ -216,19 +99,16 @@ class CMEExchangeCalendar(ExchangeCalendar):
- Thanksgiving (fourth Thursday in November)
- Christmas (observed on nearest weekday to December 25)
NOTE: The CME does not observe the following US Federal Holidays:
NOTE: For the following US Federal Holidays, part of the CME is closed
(Foreign Exchange, Interest Rates) but Commodities, GSCI, Weather & Real
Estate is open. Thus, we don't treat these as holidays.
- Columbus Day
- Veterans Day
Regularly-Observed Early Closes:
- July 3rd (Mondays, Tuesdays, and Thursdays, 1995 onward)
- July 5th (Fridays, 1995 onward, except 2013)
- Christmas Eve (except on Fridays, when the exchange is closed entirely)
- Day After Thanksgiving (aka Black Friday, observed from 1992 onward)
NOTE: Until 1993, the standard early close time for the NYSE was 2:00 PM.
From 1993 onward, it has been 1:00 PM.
Additional Irregularities:
- Closed from 9/11/2001 to 9/16/2001 due to terrorist attacks in NYC.
- Closed on 10/29/2012 and 10/30/2012 due to Hurricane Sandy.
@@ -246,7 +126,8 @@ class CMEExchangeCalendar(ExchangeCalendar):
we've done alright...and we should check if it's a half day.
"""
native_timezone = US_CENTRAL
name = "CME"
tz = US_CENTRAL
open_time = CME_OPEN
close_time = CME_CLOSE
open_offset = CME_OPEN_OFFSET
@@ -263,157 +144,3 @@ class CMEExchangeCalendar(ExchangeCalendar):
special_opens_adhoc = ()
special_closes_adhoc = []
@property
def name(self):
"""
The name of this exchange calendar.
E.g.: 'NYSE', 'LSE', 'CME Energy'
"""
return 'CME'
@property
def tz(self):
"""
The native timezone of the exchange.
SD: Not clear that this needs to be exposed.
"""
return self.native_timezone
def is_open_on_minute(self, dt):
"""
Parameters
----------
dt : Timestamp
Returns
-------
bool
True if exchange is open at the given dt, otherwise False.
"""
# Retrieve the exchange session relevant for this datetime
session = self.session_date(dt)
# Retrieve the opens and closes for this exchange session
session_open, session_close = self.open_and_close(session)
# Is @dt within the trading hours for this exchange session
return (
session_open and session_close and
session_open <= dt <= session_close
)
def is_open_on_day(self, dt):
"""
Is the exchange open (accepting orders) anytime during the calendar day
containing @dt.
Parameters
----------
dt : Timestamp
Returns
-------
bool
True if exchange is open at any time during the day containing @dt
"""
dt_normalized = normalize_date(dt)
return dt_normalized in self.schedule.index
def trading_days(self, start, end):
"""
Calculates all of the exchange sessions between the given
start and end.
SD: Presumably @start and @end are UTC-canonicalized, as our exchange
sessions are. If not, then it's not clear how this method should behave
if @start and @end are both in the middle of the day.
Parameters
----------
start : Timestamp
end : Timestamp
Returns
-------
DatetimeIndex
A DatetimeIndex populated with all of the trading days between
the given start and end.
"""
return self.schedule.index[start:end]
def open_and_close(self, dt):
"""
Given a UTC-canonicalized date, returns a tuple of timestamps of the
open and close of the exchange session on that date.
SD: Can @date be an arbitrary datetime, or should we first map it to
and exchange session using session_date. Need to check what the
consumers expect. Here, I assume we need to map it to a session.
Parameters
----------
session : Timestamp
The UTC-canonicalized session whose open and close are needed.
Returns
-------
(Timestamp, Timestamp)
The open and close for the given date.
"""
session = self.session_date(dt)
return self._get_open_and_close(session)
def _get_open_and_close(self, session_date):
"""
Retrieves the open and close for a given session.
Parameters
----------
session_date : Timestamp
The canonicalized session_date whose open and close are needed.
Returns
-------
(Timestamp, Timestamp) or (None, None)
The open and close for the given dt, or Nones if the given date is
not a session.
"""
# Return a tuple of nones if the given date is not a session.
if session_date not in self.schedule.index:
return (None, None)
o_and_c = self.schedule.loc[session_date]
# `market_open` and `market_close` should be timezone aware, but pandas
# 0.16.1 does not appear to support this:
# http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#datetime-with-tz # noqa
return (o_and_c['market_open'].tz_localize('UTC'),
o_and_c['market_close'].tz_localize('UTC'))
def session_date(self, dt):
"""
Given a time, returns the UTC-canonicalized date of the exchange
session in which the time belongs. If the time is not in an exchange
session (while the market is closed), returns the date of the next
exchange session after the time.
Parameters
----------
dt : Timestamp
A timezone-aware Timestamp.
Returns
-------
Timestamp
The date of the exchange session in which dt belongs.
"""
# Check if the dt is after the market close
# If so, advance to the next day
if self.is_open_on_day(dt):
_, close = self._get_open_and_close(normalize_date(dt))
if dt > close:
dt += Timedelta(days=1)
while not self.is_open_on_day(dt):
dt += Timedelta(days=1)
return normalize_date(dt)
@@ -1,5 +1,4 @@
from datetime import time
from pandas import Timedelta
from pandas.tseries.holiday import(
AbstractHolidayCalendar,
Holiday,
@@ -11,10 +10,11 @@ from pandas.tseries.holiday import(
)
from pytz import timezone
from zipline.utils.calendars.exchange_calendar import ExchangeCalendar
from zipline.utils.calendars.calendar_helpers import normalize_date
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = range(7)
from .trading_calendar import (
TradingCalendar,
MONDAY,
TUESDAY,
)
# New Year's Day
LSENewYearsDay = Holiday(
@@ -93,7 +93,7 @@ class LSEHolidayCalendar(AbstractHolidayCalendar):
]
class LSEExchangeCalendar(ExchangeCalendar):
class LSEExchangeCalendar(TradingCalendar):
"""
Exchange calendar for the London Stock Exchange
@@ -113,8 +113,8 @@ class LSEExchangeCalendar(ExchangeCalendar):
- Dec. 28th (if Boxing Day is on a weekend)
"""
exchange_name = 'LSE'
native_timezone = timezone('Europe/London')
name = 'LSE'
tz = timezone('Europe/London')
open_time = time(8, 1)
close_time = time(16, 30)
open_offset = 0
@@ -128,160 +128,3 @@ class LSEExchangeCalendar(ExchangeCalendar):
special_opens_adhoc = ()
special_closes_adhoc = ()
@property
def name(self):
"""
The name of this exchange calendar.
E.g.: 'NYSE', 'LSE', 'CME Energy'
"""
return self.exchange_name
@property
def tz(self):
"""
The native timezone of the exchange.
"""
return self.native_timezone
def is_open_on_minute(self, dt):
"""
Is the exchange open (accepting orders) at @dt.
Parameters
----------
dt : Timestamp
Returns
-------
bool
True if exchange is open at the given dt, otherwise False.
"""
# Retrieve the exchange session relevant for this datetime
session = self.session_date(dt)
# Retrieve the open and close for this exchange session
open, close = self.open_and_close(session)
# Is @dt within the trading hours for this exchange session
return open <= dt and dt <= close
def is_open_on_day(self, dt):
"""
Is the exchange open (accepting orders) anytime during the calendar day
containing @dt.
Parameters
----------
dt : Timestamp
Returns
-------
bool
True if exchange is open at any time during the day containing @dt
"""
dt_normalized = normalize_date(dt)
return dt_normalized in self.schedule.index
def trading_days(self, start, end):
"""
Calculates all of the exchange sessions between the given
start and end, inclusive.
SD: Should @start and @end are UTC-canonicalized, as our exchange
sessions are. If not, then it's not clear how this method should behave
if @start and @end are both in the middle of the day. Here, I assume we
need to map @start and @end to session.
Parameters
----------
start : Timestamp
end : Timestamp
Returns
-------
DatetimeIndex
A DatetimeIndex populated with all of the trading days between
the given start and end.
"""
start_session = self.session_date(start)
end_session = self.session_date(end)
# Increment end_session by one day, beucase .loc[s:e] return all values
# in the DataFrame up to but not including `e`.
# end_session += Timedelta(days=1)
return self.schedule.loc[start_session:end_session]
def open_and_close(self, dt):
"""
Given a datetime, returns a tuple of timestamps of the
open and close of the exchange session containing the datetime.
SD: Should we accept an arbitrary datetime, or should we first map it
to and exchange session using session_date. Need to check what the
consumers expect. Here, I assume we need to map it to a session.
Parameters
----------
dt : Timestamp
A dt in a session whose open and close are needed.
Returns
-------
(Timestamp, Timestamp)
The open and close for the given dt.
"""
session = self.session_date(dt)
return self._get_open_and_close(session)
def _get_open_and_close(self, session_date):
"""
Retrieves the open and close for a given session.
Parameters
----------
session_date : Timestamp
The canonicalized session_date whose open and close are needed.
Returns
-------
(Timestamp, Timestamp) or (None, None)
The open and close for the given dt, or Nones if the given date is
not a session.
"""
# Return a tuple of nones if the given date is not a session.
if session_date not in self.schedule.index:
return (None, None)
o_and_c = self.schedule.loc[session_date]
# `market_open` and `market_close` should be timezone aware, but pandas
# 0.16.1 does not appear to support this:
# http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#datetime-with-tz # noqa
return (o_and_c['market_open'].tz_localize('UTC'),
o_and_c['market_close'].tz_localize('UTC'))
def session_date(self, dt):
"""
Given a datetime, returns the UTC-canonicalized date of the exchange
session in which the time belongs. If the time is not in an exchange
session (while the market is closed), returns the date of the next
exchange session after the time.
Parameters
----------
dt : Timestamp
A timezone-aware Timestamp.
Returns
-------
Timestamp
The date of the exchange session in which dt belongs.
"""
# Check if the dt is after the market close
# If so, advance to the next day
if self.is_open_on_day(dt):
_, close = self._get_open_and_close(normalize_date(dt))
if dt > close:
dt += Timedelta(days=1)
while not self.is_open_on_day(dt):
dt += Timedelta(days=1)
return normalize_date(dt)
+24 -296
View File
@@ -16,163 +16,48 @@
from datetime import time
from itertools import chain
from dateutil.relativedelta import (
MO,
TH,
)
from pandas import (
date_range,
DateOffset,
Timestamp,
Timedelta,
)
from pandas.tseries.holiday import(
AbstractHolidayCalendar,
GoodFriday,
Holiday,
nearest_workday,
sunday_to_monday,
USLaborDay,
USPresidentsDay,
USThanksgivingDay,
)
from pandas.tseries.offsets import Day
from pytz import timezone
from zipline.utils.pandas_utils import july_5th_holiday_observance
from .exchange_calendar import ExchangeCalendar
from .calendar_helpers import normalize_date
from .trading_calendar import TradingCalendar
from .us_holidays import (
USNewYearsDay,
USMartinLutherKingJrAfter1998,
USMemorialDay,
USIndependenceDay,
Christmas,
MonTuesThursBeforeIndependenceDay,
FridayAfterIndependenceDayExcept2013,
USBlackFridayBefore1993,
USBlackFridayInOrAfter1993,
September11Closings,
HurricaneSandyClosings,
USNationalDaysofMourning,
ChristmasEveBefore1993,
ChristmasEveInOrAfter1993,
)
# Useful resources for making changes to this file:
# http://www.nyse.com/pdfs/closings.pdf
# http://www.stevemorse.org/jcal/whendid.html
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = range(7)
US_EASTERN = timezone('US/Eastern')
NYSE_OPEN = time(9, 31)
NYSE_CLOSE = time(16)
NYSE_STANDARD_EARLY_CLOSE = time(13)
# Does the market open or close on a different calendar day, compared to the
# calendar day assigned by the exchange to this session?
# Whether market opens or closes on a different calendar day, compared to the
# calendar day assigned by the exchange to this session.
NYSE_OPEN_OFFSET = 0
NYSE_CLOSE_OFFSET = 0
# Closings
USNewYearsDay = Holiday(
'New Years Day',
month=1,
day=1,
# When Jan 1 is a Sunday, NYSE observes the subsequent Monday. When Jan 1
# Saturday (as in 2005 and 2011), no holiday is observed.
observance=sunday_to_monday
)
USMemorialDay = Holiday(
# NOTE: The definition for Memorial Day is incorrect as of pandas 0.16.0.
# See https://github.com/pydata/pandas/issues/9760.
'Memorial Day',
month=5,
day=25,
offset=DateOffset(weekday=MO(1)),
)
USMartinLutherKingJrAfter1998 = Holiday(
'Dr. Martin Luther King Jr. Day',
month=1,
day=1,
# The NYSE didn't observe MLK day as a holiday until 1998.
start_date=Timestamp('1998-01-01'),
offset=DateOffset(weekday=MO(3)),
)
USIndependenceDay = Holiday(
'July 4th',
month=7,
day=4,
observance=nearest_workday,
)
Christmas = Holiday(
'Christmas',
month=12,
day=25,
observance=nearest_workday,
)
# Half Days
MonTuesThursBeforeIndependenceDay = Holiday(
# When July 4th is a Tuesday, Wednesday, or Friday, the previous day is a
# half day.
'Mondays, Tuesdays, and Thursdays Before Independence Day',
month=7,
day=3,
days_of_week=(MONDAY, TUESDAY, THURSDAY),
start_date=Timestamp("1995-01-01"),
)
FridayAfterIndependenceDayExcept2013 = Holiday(
# When July 4th is a Thursday, the next day is a half day (except in 2013,
# when, for no explicable reason, Wednesday was a half day instead).
"Fridays after Independence Day that aren't in 2013",
month=7,
day=5,
days_of_week=(FRIDAY,),
# The 2013 observance lambda is pandas version-dependent
observance=july_5th_holiday_observance,
start_date=Timestamp("1995-01-01"),
)
USBlackFridayBefore1993 = Holiday(
'Black Friday',
month=11,
day=1,
# Black Friday was not observed until 1992.
start_date=Timestamp('1992-01-01'),
end_date=Timestamp('1993-01-01'),
offset=[DateOffset(weekday=TH(4)), Day(1)],
)
USBlackFridayInOrAfter1993 = Holiday(
'Black Friday',
month=11,
day=1,
start_date=Timestamp('1993-01-01'),
offset=[DateOffset(weekday=TH(4)), Day(1)],
)
# These have the same definition, but are used in different places because the
# NYSE closed at 2:00 PM on Christmas Eve until 1993.
ChristmasEveBefore1993 = Holiday(
'Christmas Eve',
month=12,
day=24,
end_date=Timestamp('1993-01-01'),
# When Christmas is a Saturday, the 24th is a full holiday.
days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY),
)
ChristmasEveInOrAfter1993 = Holiday(
'Christmas Eve',
month=12,
day=24,
start_date=Timestamp('1993-01-01'),
# When Christmas is a Saturday, the 24th is a full holiday.
days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY),
)
# http://en.wikipedia.org/wiki/Aftermath_of_the_September_11_attacks
September11Closings = date_range('2001-09-11', '2001-09-16', tz='UTC')
# http://en.wikipedia.org/wiki/Hurricane_sandy
HurricaneSandyClosings = date_range(
'2012-10-29',
'2012-10-30',
tz='UTC'
)
# National Days of Mourning
# - President Richard Nixon - April 27, 1994
# - President Ronald W. Reagan - June 11, 2004
# - President Gerald R. Ford - Jan 2, 2007
USNationalDaysofMourning = [
Timestamp('1994-04-27', tz='UTC'),
Timestamp('2004-06-11', tz='UTC'),
Timestamp('2007-01-02', tz='UTC'),
]
class NYSEHolidayCalendar(AbstractHolidayCalendar):
"""
@@ -216,7 +101,7 @@ class NYSEEarlyCloseCalendar(AbstractHolidayCalendar):
]
class NYSEExchangeCalendar(ExchangeCalendar):
class NYSEExchangeCalendar(TradingCalendar):
"""
Exchange calendar for NYSE
@@ -264,8 +149,8 @@ class NYSEExchangeCalendar(ExchangeCalendar):
we've done alright...and we should check if it's a half day.
"""
exchange_name = 'NYSE'
native_timezone = US_EASTERN
name = "NYSE"
tz = US_EASTERN
open_time = NYSE_OPEN
close_time = NYSE_CLOSE
open_offset = NYSE_OPEN_OFFSET
@@ -291,160 +176,3 @@ class NYSEExchangeCalendar(ExchangeCalendar):
'2003-12-26',
'2013-07-03')),
]
@property
def name(self):
"""
The name of this exchange calendar.
E.g.: 'NYSE', 'LSE', 'CME Energy'
"""
return self.exchange_name
@property
def tz(self):
"""
The native timezone of the exchange.
"""
return self.native_timezone
def is_open_on_minute(self, dt):
"""
Is the exchange open (accepting orders) at @dt.
Parameters
----------
dt : Timestamp
Returns
-------
bool
True if exchange is open at the given dt, otherwise False.
"""
# Retrieve the exchange session relevant for this datetime
session = self.session_date(dt)
# Retrieve the open and close for this exchange session
open, close = self.open_and_close(session)
# Is @dt within the trading hours for this exchange session
return open <= dt and dt <= close
def is_open_on_day(self, dt):
"""
Is the exchange open (accepting orders) anytime during the calendar day
containing @dt.
Parameters
----------
dt : Timestamp
Returns
-------
bool
True if exchange is open at any time during the day containing @dt
"""
dt_normalized = normalize_date(dt)
return dt_normalized in self.schedule.index
def trading_days(self, start, end):
"""
Calculates all of the exchange sessions between the given
start and end, inclusive.
SD: Should @start and @end are UTC-canonicalized, as our exchange
sessions are. If not, then it's not clear how this method should behave
if @start and @end are both in the middle of the day. Here, I assume we
need to map @start and @end to session.
Parameters
----------
start : Timestamp
end : Timestamp
Returns
-------
DatetimeIndex
A DatetimeIndex populated with all of the trading days between
the given start and end.
"""
start_session = self.session_date(start)
end_session = self.session_date(end)
# Increment end_session by one day, beucase .loc[s:e] return all values
# in the DataFrame up to but not including `e`.
# end_session += Timedelta(days=1)
return self.schedule.loc[start_session:end_session]
def open_and_close(self, dt):
"""
Given a datetime, returns a tuple of timestamps of the
open and close of the exchange session containing the datetime.
SD: Should we accept an arbitrary datetime, or should we first map it
to and exchange session using session_date. Need to check what the
consumers expect. Here, I assume we need to map it to a session.
Parameters
----------
dt : Timestamp
A dt in a session whose open and close are needed.
Returns
-------
(Timestamp, Timestamp)
The open and close for the given dt.
"""
session = self.session_date(dt)
return self._get_open_and_close(session)
def _get_open_and_close(self, session_date):
"""
Retrieves the open and close for a given session.
Parameters
----------
session_date : Timestamp
The canonicalized session_date whose open and close are needed.
Returns
-------
(Timestamp, Timestamp) or (None, None)
The open and close for the given dt, or Nones if the given date is
not a session.
"""
# Return a tuple of nones if the given date is not a session.
if session_date not in self.schedule.index:
return (None, None)
o_and_c = self.schedule.loc[session_date]
# `market_open` and `market_close` should be timezone aware, but pandas
# 0.16.1 does not appear to support this:
# http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#datetime-with-tz # noqa
return (o_and_c['market_open'].tz_localize('UTC'),
o_and_c['market_close'].tz_localize('UTC'))
def session_date(self, dt):
"""
Given a datetime, returns the UTC-canonicalized date of the exchange
session in which the time belongs. If the time is not in an exchange
session (while the market is closed), returns the date of the next
exchange session after the time.
Parameters
----------
dt : Timestamp
A timezone-aware Timestamp.
Returns
-------
Timestamp
The date of the exchange session in which dt belongs.
"""
# Check if the dt is after the market close
# If so, advance to the next day
if self.is_open_on_day(dt):
_, close = self._get_open_and_close(normalize_date(dt))
if dt > close:
dt += Timedelta(days=1)
while not self.is_open_on_day(dt):
dt += Timedelta(days=1)
return normalize_date(dt)
@@ -1,5 +1,4 @@
from datetime import time
from pandas import Timedelta
from pandas.tseries.holiday import(
AbstractHolidayCalendar,
Holiday,
@@ -10,17 +9,14 @@ from pandas.tseries.holiday import(
)
from pytz import timezone
from zipline.utils.calendars.exchange_calendar import ExchangeCalendar
from zipline.utils.calendars.calendar_helpers import normalize_date
from zipline.utils.calendars.trading_calendar import TradingCalendar
from zipline.utils.calendars.us_holidays import Christmas
from zipline.utils.calendars.exchange_calendar_lse import (
Christmas,
WeekendChristmas,
BoxingDay,
WeekendBoxingDay,
)
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = range(7)
# New Year's Day
TSXNewYearsDay = Holiday(
"New Year's Day",
@@ -95,7 +91,7 @@ class TSXHolidayCalendar(AbstractHolidayCalendar):
]
class TSXExchangeCalendar(ExchangeCalendar):
class TSXExchangeCalendar(TradingCalendar):
"""
Exchange calendar for the Toronto Stock Exchange
@@ -117,8 +113,8 @@ class TSXExchangeCalendar(ExchangeCalendar):
- Dec. 28th (if Boxing Day is on a weekend)
"""
exchange_name = 'TSX'
native_timezone = timezone('Canada/Atlantic')
name = 'TSX'
tz = timezone('Canada/Atlantic')
open_time = time(9, 31)
close_time = time(16)
open_offset = 0
@@ -132,160 +128,3 @@ class TSXExchangeCalendar(ExchangeCalendar):
special_opens_adhoc = ()
special_closes_adhoc = ()
@property
def name(self):
"""
The name of this exchange calendar.
E.g.: 'NYSE', 'LSE', 'CME Energy'
"""
return self.exchange_name
@property
def tz(self):
"""
The native timezone of the exchange.
"""
return self.native_timezone
def is_open_on_minute(self, dt):
"""
Is the exchange open (accepting orders) at @dt.
Parameters
----------
dt : Timestamp
Returns
-------
bool
True if exchange is open at the given dt, otherwise False.
"""
# Retrieve the exchange session relevant for this datetime
session = self.session_date(dt)
# Retrieve the open and close for this exchange session
open, close = self.open_and_close(session)
# Is @dt within the trading hours for this exchange session
return open <= dt and dt <= close
def is_open_on_day(self, dt):
"""
Is the exchange open (accepting orders) anytime during the calendar day
containing @dt.
Parameters
----------
dt : Timestamp
Returns
-------
bool
True if exchange is open at any time during the day containing @dt
"""
dt_normalized = normalize_date(dt)
return dt_normalized in self.schedule.index
def trading_days(self, start, end):
"""
Calculates all of the exchange sessions between the given
start and end, inclusive.
SD: Should @start and @end are UTC-canonicalized, as our exchange
sessions are. If not, then it's not clear how this method should behave
if @start and @end are both in the middle of the day. Here, I assume we
need to map @start and @end to session.
Parameters
----------
start : Timestamp
end : Timestamp
Returns
-------
DatetimeIndex
A DatetimeIndex populated with all of the trading days between
the given start and end.
"""
start_session = self.session_date(start)
end_session = self.session_date(end)
# Increment end_session by one day, beucase .loc[s:e] return all values
# in the DataFrame up to but not including `e`.
# end_session += Timedelta(days=1)
return self.schedule.loc[start_session:end_session]
def open_and_close(self, dt):
"""
Given a datetime, returns a tuple of timestamps of the
open and close of the exchange session containing the datetime.
SD: Should we accept an arbitrary datetime, or should we first map it
to and exchange session using session_date. Need to check what the
consumers expect. Here, I assume we need to map it to a session.
Parameters
----------
dt : Timestamp
A dt in a session whose open and close are needed.
Returns
-------
(Timestamp, Timestamp)
The open and close for the given dt.
"""
session = self.session_date(dt)
return self._get_open_and_close(session)
def _get_open_and_close(self, session_date):
"""
Retrieves the open and close for a given session.
Parameters
----------
session_date : Timestamp
The canonicalized session_date whose open and close are needed.
Returns
-------
(Timestamp, Timestamp) or (None, None)
The open and close for the given dt, or Nones if the given date is
not a session.
"""
# Return a tuple of nones if the given date is not a session.
if session_date not in self.schedule.index:
return (None, None)
o_and_c = self.schedule.loc[session_date]
# `market_open` and `market_close` should be timezone aware, but pandas
# 0.16.1 does not appear to support this:
# http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#datetime-with-tz # noqa
return (o_and_c['market_open'].tz_localize('UTC'),
o_and_c['market_close'].tz_localize('UTC'))
def session_date(self, dt):
"""
Given a datetime, returns the UTC-canonicalized date of the exchange
session in which the time belongs. If the time is not in an exchange
session (while the market is closed), returns the date of the next
exchange session after the time.
Parameters
----------
dt : Timestamp
A timezone-aware Timestamp.
Returns
-------
Timestamp
The date of the exchange session in which dt belongs.
"""
# Check if the dt is after the market close
# If so, advance to the next day
if self.is_open_on_day(dt):
_, close = self._get_open_and_close(normalize_date(dt))
if dt > close:
dt += Timedelta(days=1)
while not self.is_open_on_day(dt):
dt += Timedelta(days=1)
return normalize_date(dt)
+732
View File
@@ -0,0 +1,732 @@
#
# Copyright 2016 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from abc import ABCMeta
from six import with_metaclass
from numpy import searchsorted
import numpy as np
import pandas as pd
from pandas import (
DataFrame,
date_range,
DatetimeIndex,
DateOffset
)
from pandas.tseries.offsets import CustomBusinessDay
from zipline.utils.calendars._calendar_helpers import (
next_divider_idx,
previous_divider_idx,
is_open
)
from zipline.utils.memoize import remember_last
start_default = pd.Timestamp('1990-01-01', tz='UTC')
end_base = pd.Timestamp('today', tz='UTC')
# Give an aggressive buffer for logic that needs to use the next trading
# day or minute.
end_default = end_base + pd.Timedelta(days=365)
NANOS_IN_MINUTE = 60000000000
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = range(7)
class TradingCalendar(with_metaclass(ABCMeta)):
"""
An TradingCalendar represents the timing information of a single market
exchange.
The timing information is made up of two parts: sessions, and opens/closes.
A session represents a contiguous set of minutes, and has a label that is
midnight UTC. It is important to note that a session label should not be
considered a specific point in time, and that midnight UTC is just being
used for convenience.
For each session, we store the open and close time in UTC time.
"""
def __init__(self, start=start_default, end=end_default):
open_offset = self.open_offset
close_offset = self.close_offset
# Define those days on which the exchange is usually open.
self.day = CustomBusinessDay(
holidays=self.holidays_adhoc,
calendar=self.holidays_calendar,
)
# Midnight in UTC for each trading day.
_all_days = date_range(start, end, freq=self.day, tz='UTC')
# `DatetimeIndex`s of standard opens/closes for each day.
self._opens = days_at_time(_all_days, self.open_time, self.tz,
open_offset)
self._closes = days_at_time(
_all_days, self.close_time, self.tz, close_offset
)
# `DatetimeIndex`s of nonstandard opens/closes
_special_opens = self._special_opens(start, end)
_special_closes = self._special_closes(start, end)
# Overwrite the special opens and closes on top of the standard ones.
_overwrite_special_dates(_all_days, self._opens, _special_opens)
_overwrite_special_dates(_all_days, self._closes, _special_closes)
# In pandas 0.16.1 _opens and _closes will lose their timezone
# information. This looks like it has been resolved in 0.17.1.
# http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#datetime-with-tz # noqa
self.schedule = DataFrame(
index=_all_days,
columns=['market_open', 'market_close'],
data={
'market_open': self._opens,
'market_close': self._closes,
},
dtype='datetime64[ns]',
)
self.market_opens_nanos = self.schedule.market_open.values.\
astype(np.int64)
self.market_closes_nanos = self.schedule.market_close.values.\
astype(np.int64)
self._trading_minutes_nanos = self.all_minutes.values.\
astype(np.int64)
self.first_trading_session = _all_days[0]
self.last_trading_session = _all_days[-1]
self._early_closes = pd.DatetimeIndex(
_special_closes.map(self.minute_to_session_label)
)
@property
def opens(self):
return self.schedule.market_open
@property
def closes(self):
return self.schedule.market_close
@property
def early_closes(self):
return self._early_closes
def is_session(self, dt):
"""
Given a dt, returns whether it's a valid session label.
Parameters
----------
dt: pd.Timestamp
The dt that is being tested.
Returns
-------
bool
Whether the given dt is a valid session label.
"""
return dt in self.schedule.index
def is_open_on_minute(self, dt):
"""
Given a dt, return whether this exchange is open at the given dt.
Parameters
----------
dt: pd.Timestamp
The dt for which to check if this exchange is open.
Returns
-------
bool
Whether the exchange is open on this dt.
"""
return is_open(self.market_opens_nanos, self.market_closes_nanos,
dt.value)
def next_open(self, dt):
"""
Given a dt, returns the next open.
If the given dt happens to be a session open, the next session's open
will be returned.
Parameters
----------
dt: pd.Timestamp
The dt for which to get the next open.
Returns
-------
pd.Timestamp
The UTC timestamp of the next open.
"""
idx = next_divider_idx(self.market_opens_nanos, dt.value)
return self.schedule.market_open[idx].tz_localize('UTC')
def next_close(self, dt):
"""
Given a dt, returns the next close.
Parameters
----------
dt: pd.Timestamp
The dt for which to get the next close.
Returns
-------
pd.Timestamp
The UTC timestamp of the next close.
"""
idx = next_divider_idx(self.market_closes_nanos, dt.value)
return self.schedule.market_close[idx].tz_localize('UTC')
def previous_open(self, dt):
"""
Given a dt, returns the previous open.
Parameters
----------
dt: pd.Timestamp
The dt for which to get the previous open.
Returns
-------
pd.Timestamp
The UTC imestamp of the previous open.
"""
idx = previous_divider_idx(self.market_opens_nanos, dt.value)
return self.schedule.market_open[idx].tz_localize('UTC')
def previous_close(self, dt):
"""
Given a dt, returns the previous close.
Parameters
----------
dt: pd.Timestamp
The dt for which to get the previous close.
Returns
-------
pd.Timestamp
The UTC timestamp of the previous close.
"""
idx = previous_divider_idx(self.market_closes_nanos, dt.value)
return self.schedule.market_close[idx].tz_localize('UTC')
def next_minute(self, dt):
"""
Given a dt, return the next exchange minute. If the given dt is not
an exchange minute, returns the next exchange open.
Parameters
----------
dt: pd.Timestamp
The dt for which to get the next exchange minute.
Returns
-------
pd.Timestamp
The next exchange minute.
"""
idx = next_divider_idx(self._trading_minutes_nanos, dt.value)
return self.all_minutes[idx]
def previous_minute(self, dt):
"""
Given a dt, return the previous exchange minute.
Raises KeyError if the given timestamp is not an exchange minute.
Parameters
----------
dt: pd.Timestamp
The dt for which to get the previous exchange minute.
Returns
-------
pd.Timestamp
The previous exchange minute.
"""
idx = previous_divider_idx(self._trading_minutes_nanos, dt.value)
return self.all_minutes[idx]
def next_session_label(self, session_label):
"""
Given a session label, returns the label of the next session.
Parameters
----------
session_label: pd.Timestamp
A session whose next session is desired.
Returns
-------
pd.Timestamp
The next session label (midnight UTC).
Notes
-----
Raises ValueError if the given session is the last session in this
calendar.
"""
idx = self.schedule.index.get_loc(session_label)
try:
return self.schedule.index[idx + 1]
except IndexError:
if idx == len(self.schedule.index) - 1:
raise ValueError("There is no next session as this is the end"
" of the exchange calendar.")
else:
raise
def previous_session_label(self, session_label):
"""
Given a session label, returns the label of the previous session.
Parameters
----------
session_label: pd.Timestamp
A session whose previous session is desired.
Returns
-------
pd.Timestamp
The previous session label (midnight UTC).
Notes
-----
Raises ValueError if the given session is the first session in this
calendar.
"""
idx = self.schedule.index.get_loc(session_label)
if idx == 0:
raise ValueError("There is no previous session as this is the"
" beginning of the exchange calendar.")
return self.schedule.index[idx - 1]
def minutes_for_session(self, session_label):
"""
Given a session label, return the minutes for that session.
Parameters
----------
session_label: pd.Timestamp (midnight UTC)
A session label whose session's minutes are desired.
Returns
-------
pd.DateTimeIndex
All the minutes for the given session.
"""
data = self.schedule.loc[session_label]
return self.all_minutes[
self.all_minutes.slice_indexer(
data.market_open,
data.market_close
)
]
def minutes_window(self, start_dt, count):
try:
start_idx = self.all_minutes.get_loc(start_dt)
except KeyError:
# if this is not a market minute, go to the previous session's
# close
previous_session = self.minute_to_session_label(
start_dt, direction="previous"
)
previous_close = self.open_and_close_for_session(
previous_session
)[1]
start_idx = self.all_minutes.get_loc(previous_close)
end_idx = start_idx + count
if start_idx > end_idx:
return self.all_minutes[(end_idx + 1):(start_idx + 1)]
else:
return self.all_minutes[start_idx:end_idx]
def sessions_in_range(self, start_session_label, end_session_label):
"""
Given start and end session labels, return all the sessions in that
range, inclusive.
Parameters
----------
start_session_label: pd.Timestamp (midnight UTC)
The label representing the first session of the desired range.
end_session_label: pd.Timestamp (midnight UTC)
The label representing the last session of the desired range.
Returns
-------
pd.DatetimeIndex
The desired sessions.
"""
return self.all_sessions[
self.all_sessions.slice_indexer(
start_session_label,
end_session_label
)
]
def sessions_window(self, session_label, count):
"""
Given a session label and a window size, returns a list of sessions
of size `count` + 1, that either starts with the given session
(if `count` is positive) or ends with the given session (if `count` is
negative).
Parameters
----------
session_label: pd.Timestamp
The label of the initial session.
count: int
Defines the length and the direction of the window.
Returns
-------
pd.DatetimeIndex
The desired sessions.
"""
start_idx = self.schedule.index.get_loc(session_label)
end_idx = start_idx + count
return self.all_sessions[
min(start_idx, end_idx):max(start_idx, end_idx) + 1
]
def session_distance(self, start_session_label, end_session_label):
"""
Given a start and end session label, returns the distance between
them. For example, for three consecutive sessions Mon., Tues., and
Wed, `session_distance(Mon, Wed)` would return 2.
Parameters
----------
start_session_label: pd.Timestamp
The label of the start session.
end_session_label: pd.Timestamp
The label of the ending session.
Returns
-------
int
The distance between the two sessions.
"""
start_idx = self.all_sessions.searchsorted(
self.minute_to_session_label(start_session_label)
)
end_idx = self.all_sessions.searchsorted(
self.minute_to_session_label(end_session_label)
)
return abs(end_idx - start_idx)
def minutes_in_range(self, start_minute, end_minute):
"""
Given start and end minutes, return all the calendar minutes
in that range, inclusive.
Given minutes don't need to be calendar minutes.
Parameters
----------
start_minute: pd.Timestamp
The minute representing the start of the desired range.
end_minute: pd.Timestamp
The minute representing the end of the desired range.
Returns
-------
pd.DatetimeIndex
The minutes in the desired range.
"""
start_idx = searchsorted(self._trading_minutes_nanos,
start_minute.value)
end_idx = searchsorted(self._trading_minutes_nanos,
end_minute.value)
if end_minute.value == self._trading_minutes_nanos[end_idx]:
# if the end minute is a market minute, increase by 1
end_idx += 1
return self.all_minutes[start_idx:end_idx]
def minutes_for_sessions_in_range(self, start_session_label,
end_session_label):
"""
Returns all the minutes for all the sessions from the given start
session label to the given end session label, inclusive.
Parameters
----------
start_session_label: pd.Timestamp
The label of the first session in the range.
end_session_label: pd.Timestamp
The label of the last session in the range.
Returns
-------
pd.DatetimeIndex
The minutes in the desired range.
"""
first_minute, _ = self.open_and_close_for_session(start_session_label)
_, last_minute = self.open_and_close_for_session(end_session_label)
return self.minutes_in_range(first_minute, last_minute)
def open_and_close_for_session(self, session_label):
"""
Returns a tuple of timestamps of the open and close of the session
represented by the given label.
Parameters
----------
session_label: pd.Timestamp
The session whose open and close are desired.
Returns
-------
(Timestamp, Timestamp)
The open and close for the given session.
"""
o_and_c = self.schedule.loc[session_label]
# `market_open` and `market_close` should be timezone aware, but pandas
# 0.16.1 does not appear to support this:
# http://pandas.pydata.org/pandas-docs/stable/whatsnew.html#datetime-with-tz # noqa
return (o_and_c['market_open'].tz_localize('UTC'),
o_and_c['market_close'].tz_localize('UTC'))
@property
def all_sessions(self):
return self.schedule.index
@property
def first_session(self):
return self.all_sessions[0]
@property
def last_session(self):
return self.all_sessions[-1]
@property
@remember_last
def all_minutes(self):
"""
Returns a DatetimeIndex representing all the minutes in this calendar.
"""
opens_in_ns = \
self._opens.values.astype('datetime64[ns]')
closes_in_ns = \
self._closes.values.astype('datetime64[ns]')
deltas = closes_in_ns - opens_in_ns
# + 1 because we want 390 days per standard day, not 389
daily_sizes = (deltas / NANOS_IN_MINUTE) + 1
num_minutes = np.sum(daily_sizes).astype(np.int64)
# One allocation for the entire thing. This assumes that each day
# represents a contiguous block of minutes.
all_minutes = np.empty(num_minutes, dtype='datetime64[ns]')
idx = 0
for day_idx, size in enumerate(daily_sizes):
# lots of small allocations, but it's fast enough for now.
# size is a np.timedelta64, so we need to int it
size_int = int(size)
all_minutes[idx:(idx + size_int)] = \
np.arange(
opens_in_ns[day_idx],
closes_in_ns[day_idx] + NANOS_IN_MINUTE,
NANOS_IN_MINUTE
)
idx += size_int
return DatetimeIndex(all_minutes).tz_localize("UTC")
def minute_to_session_label(self, dt, direction="next"):
"""
Given a minute, get the label of its containing session.
Parameters
----------
dt : pd.Timestamp
The dt for which to get the containing session.
direction: str
"next" (default) means that if the given dt is not part of a
session, return the label of the next session.
"previous" means that if the given dt is not part of a session,
return the label of the previous session.
"none" means that a KeyError will be raised if the given
dt is not part of a session.
Returns
-------
pd.Timestamp (midnight UTC)
The label of the containing session.
"""
idx = searchsorted(self.market_closes_nanos, dt.value)
current_or_next_session = self.schedule.index[idx]
if direction == "previous":
if not is_open(self.market_opens_nanos, self.market_closes_nanos,
dt.value):
# if the exchange is closed, use the previous session
return self.schedule.index[idx - 1]
elif direction == "none":
if not is_open(self.market_opens_nanos, self.market_closes_nanos,
dt.value):
# if the exchange is closed, blow up
raise ValueError("The given dt is not an exchange minute!")
elif direction != "next":
# invalid direction
raise ValueError("Invalid direction parameter: "
"{0}".format(direction))
return current_or_next_session
def _special_dates(self, calendars, ad_hoc_dates, start_date, end_date):
"""
Union an iterable of pairs of the form (time, calendar)
and an iterable of pairs of the form (time, [dates])
(This is shared logic for computing special opens and special closes.)
"""
_dates = DatetimeIndex([], tz='UTC').union_many(
[
holidays_at_time(calendar, start_date, end_date, time_,
self.tz)
for time_, calendar in calendars
] + [
days_at_time(datetimes, time_, self.tz)
for time_, datetimes in ad_hoc_dates
]
)
return _dates[(_dates >= start_date) & (_dates <= end_date)]
def _special_opens(self, start, end):
return self._special_dates(
self.special_opens_calendars,
self.special_opens_adhoc,
start,
end,
)
def _special_closes(self, start, end):
return self._special_dates(
self.special_closes_calendars,
self.special_closes_adhoc,
start,
end,
)
def days_at_time(days, t, tz, day_offset=0):
"""
Shift an index of days to time t, interpreted in tz.
Overwrites any existing tz info on the input.
Parameters
----------
days : DatetimeIndex
The "base" time which we want to change.
t : datetime.time
The time we want to offset @days by
tz : pytz.timezone
The timezone which these times represent
day_offset : int
The number of days we want to offset @days by
"""
days = DatetimeIndex(days).tz_localize(None).tz_localize(tz)
days_offset = days + DateOffset(day_offset)
return days_offset.shift(
1, freq=DateOffset(hour=t.hour, minute=t.minute, second=t.second)
).tz_convert('UTC')
def holidays_at_time(calendar, start, end, time, tz):
return days_at_time(
calendar.holidays(
# Workaround for https://github.com/pydata/pandas/issues/9825.
start.tz_localize(None),
end.tz_localize(None),
),
time,
tz=tz,
)
def _overwrite_special_dates(midnight_utcs,
opens_or_closes,
special_opens_or_closes):
"""
Overwrite dates in open_or_closes with corresponding dates in
special_opens_or_closes, using midnight_utcs for alignment.
"""
# Short circuit when nothing to apply.
if not len(special_opens_or_closes):
return
len_m, len_oc = len(midnight_utcs), len(opens_or_closes)
if len_m != len_oc:
raise ValueError(
"Found misaligned dates while building calendar.\n"
"Expected midnight_utcs to be the same length as open_or_closes,\n"
"but len(midnight_utcs)=%d, len(open_or_closes)=%d" % len_m, len_oc
)
# Find the array indices corresponding to each special date.
indexer = midnight_utcs.get_indexer(special_opens_or_closes.normalize())
# -1 indicates that no corresponding entry was found. If any -1s are
# present, then we have special dates that doesn't correspond to any
# trading day.
if -1 in indexer:
bad_dates = list(special_opens_or_closes[indexer == -1])
raise ValueError("Special dates %s are not trading days." % bad_dates)
# NOTE: This is a slightly dirty hack. We're in-place overwriting the
# internal data of an Index, which is conceptually immutable. Since we're
# maintaining sorting, this should be ok, but this is a good place to
# sanity check if things start going haywire with calendar computations.
opens_or_closes.values[indexer] = special_opens_or_closes.values
-416
View File
@@ -1,416 +0,0 @@
#
# Copyright 2016 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from abc import (
ABCMeta,
abstractmethod,
abstractproperty,
)
from six import with_metaclass
from .exchange_calendar import get_calendar
from .calendar_helpers import (
next_scheduled_day,
previous_scheduled_day,
next_open_and_close,
previous_open_and_close,
scheduled_day_distance,
minutes_for_day,
days_in_range,
minutes_for_days_in_range,
add_scheduled_days,
all_scheduled_minutes,
next_scheduled_minute,
previous_scheduled_minute
)
class TradingSchedule(with_metaclass(ABCMeta)):
"""
A TradingSchedule defines the execution timing of a TradingAlgorithm.
"""
def next_execution_day(self, date):
return next_scheduled_day(
date,
last_trading_day=self.last_execution_day,
is_scheduled_day_hook=self.is_executing_on_day,
)
def previous_execution_day(self, date):
return previous_scheduled_day(
date,
first_trading_day=self.first_execution_day,
is_scheduled_day_hook=self.is_executing_on_day,
)
def next_start_and_end(self, date):
return next_open_and_close(
date,
open_and_close_hook=self.start_and_end,
next_scheduled_day_hook=self.next_execution_day,
)
def previous_start_and_end(self, date):
return previous_open_and_close(
date,
open_and_close_hook=self.start_and_end,
previous_scheduled_day_hook=self.previous_execution_day,
)
def execution_day_distance(self, first_date, second_date):
return scheduled_day_distance(
first_date, second_date,
all_days=self.all_execution_days,
)
def execution_minutes_for_day(self, day):
return minutes_for_day(
day,
open_and_close_hook=self.start_and_end,
)
def execution_days_in_range(self, start, end):
return days_in_range(
start, end,
all_days=self.all_execution_days,
)
def execution_minutes_for_days_in_range(self, start, end):
return minutes_for_days_in_range(
start, end,
days_in_range_hook=self.execution_days_in_range,
minutes_for_day_hook=self.execution_minutes_for_day,
)
def add_execution_days(self, n, date):
"""
Adds n execution days to date. If this would fall outside of the
TradingSchedule, a NoFurtherDataError is raised.
Parameters
----------
n : int
The number of days to add to date, this can be positive or
negative.
date : datetime
The date to add to.
Returns
-------
datetime
n trading days added to date.
"""
return add_scheduled_days(
n, date,
next_scheduled_day_hook=self.next_execution_day,
previous_scheduled_day_hook=self.previous_execution_day,
all_trading_days=self.all_execution_days,
)
def next_execution_minute(self, start):
return next_scheduled_minute(
start,
is_scheduled_day_hook=self.is_executing_on_day,
open_and_close_hook=self.start_and_end,
next_open_and_close_hook=self.next_start_and_end,
)
def previous_execution_minute(self, start):
return previous_scheduled_minute(
start,
is_scheduled_day_hook=self.is_executing_on_day,
open_and_close_hook=self.start_and_end,
previous_open_and_close_hook=self.previous_start_and_end,
)
def execution_minute_window(self, start, count):
start_idx = self.all_execution_minutes.get_loc(start)
end_idx = start_idx + count
if start_idx > end_idx:
return self.all_execution_minutes[(end_idx + 1):(start_idx + 1)]
else:
return self.all_execution_minutes[start_idx:end_idx]
@abstractproperty
def day(self):
"""
A CustomBusinessDay defining those days on which the algorithm is
trading.
"""
raise NotImplementedError()
@abstractproperty
def tz(self):
"""
The native timezone for this TradingSchedule.
"""
raise NotImplementedError()
@abstractproperty
def first_execution_day(self):
"""
The first possible day of trading in this TradingSchedule.
"""
raise NotImplementedError()
@abstractproperty
def last_execution_day(self):
"""
The last possible day of trading in this TradingSchedule.
"""
raise NotImplementedError()
@abstractmethod
def trading_sessions(self, start, end):
"""
Calculates all of the trading sessions between the given
start and end.
Parameters
----------
start : Timestamp
end : Timestamp
Returns
-------
DataFrame
A DataFrame, with a DatetimeIndex of trading dates, containing
columns of trading starts and ends in this TradingSchedule.
"""
raise NotImplementedError()
@property
def all_execution_days(self):
return self.schedule.index
@property
def all_execution_minutes(self):
return all_scheduled_minutes(self.all_execution_days,
self.execution_minutes_for_days_in_range)
def trading_dates(self, start, end):
"""
Calculates the dates of all of the trading sessions between the given
start and end.
Parameters
----------
start : Timestamp
end : Timestamp
Returns
-------
DatetimeIndex
A DatetimeIndex containing the dates of the desired trading
sessions.
"""
return self.trading_sessions(start, end).index
@abstractmethod
def data_availability_time(self, date):
"""
Given a UTC-canonicalized date, returns a time by-which all data from
the previous date is available to the algorithm.
Parameters
----------
date : Timestamp
The UTC-canonicalized calendar date whose data availability time
is needed.
Returns
-------
Timestamp or None
The data availability time on the given date, or None if there is
no data availability time for that date.
"""
raise NotImplementedError()
@abstractmethod
def start_and_end(self, date):
"""
Given a UTC-canonicalized date, returns a tuple of timestamps of the
start and end of the algorithm trading session for that date.
Parameters
----------
date : Timestamp
The UTC-canonicalized algorithm trading session date whose start
and end are needed.
Returns
-------
(Timestamp, Timestamp)
The start and end for the given date.
"""
raise NotImplementedError()
@abstractmethod
def is_executing_on_minute(self, dt):
"""
Calculates if a TradingAlgorithm using this TradingSchedule should be
executed at time dt.
Parameters
----------
dt : Timestamp
The time being queried.
Returns
-------
bool
True if the TradingAlgorithm should be executed at dt,
otherwise False.
"""
raise NotImplementedError()
@abstractmethod
def is_executing_on_day(self, dt):
"""
Calculates if a TradingAlgorithm using this TradingSchedule would
execute on the day of dt.
Parameters
----------
dt : Timestamp
The time being queried.
Returns
-------
bool
True if the TradingAlgorithm should be executed at dt,
otherwise False.
"""
raise NotImplementedError()
@abstractmethod
def session_date(self, dt):
"""
Given a time, returns the UTC-canonicalized date of the trading
session in which the time belongs. If the time is not in a trading
session (while algorithm isn't trading), returns the date of the next
exchange session after the time.
Parameters
----------
dt : Timestamp
Returns
-------
Timestamp
The date of the exchange session in which dt belongs.
"""
raise NotImplementedError()
@abstractproperty
def early_ends(self):
"""
Returns a DatetimeIndex containing the session dates on-which there is
an early end to trading.
"""
raise NotImplementedError()
class ExchangeTradingSchedule(TradingSchedule):
"""
A TradingSchedule that functions as a wrapper around an ExchangeCalendar.
"""
def __init__(self, cal):
"""
Docstring goes here, Jimmy
Parameters
----------
cal : ExchangeCalendar
The ExchangeCalendar to be represented by this
ExchangeTradingSchedule.
"""
self._exchange_calendar = cal
super(ExchangeTradingSchedule, self).__init__()
@property
def all_execution_days(self):
return self._exchange_calendar.all_trading_days
@property
def all_execution_minutes(self):
return self._exchange_calendar.all_trading_minutes
@property
def day(self):
return self._exchange_calendar.day
@property
def tz(self):
return self._exchange_calendar.tz
@property
def schedule(self):
return self._exchange_calendar.schedule
@property
def first_execution_day(self):
return self._exchange_calendar.first_trading_day
@property
def last_execution_day(self):
return self._exchange_calendar.last_trading_day
def trading_sessions(self, start, end):
"""
See TradingSchedule definition.
"""
return self._exchange_calendar.trading_days(start, end)
def data_availability_time(self, date):
"""
See TradingSchedule definition.
"""
calendar_open, _ = self._exchange_calendar.open_and_close(date)
return calendar_open
def start_and_end(self, date):
"""
See TradingSchedule definition.
"""
return self._exchange_calendar.open_and_close(date)
def is_executing_on_minute(self, dt):
"""
See TradingSchedule definition.
"""
return self._exchange_calendar.is_open_on_minute(dt)
def is_executing_on_day(self, dt):
"""
See TradingSchedule definition.
"""
return self._exchange_calendar.is_open_on_day(dt)
def session_date(self, dt):
"""
See TradingSchedule definition.
"""
return self._exchange_calendar.session_date(dt)
@property
def early_ends(self):
return self._exchange_calendar.early_closes
default_nyse_schedule = ExchangeTradingSchedule(cal=get_calendar('NYSE'))
+147
View File
@@ -0,0 +1,147 @@
from pandas import (
Timestamp,
DateOffset,
date_range,
)
from pandas.tseries.holiday import (
Holiday,
sunday_to_monday,
nearest_workday,
)
from dateutil.relativedelta import (
MO,
TH
)
from pandas.tseries.offsets import Day
from zipline.utils.calendars.trading_calendar import (
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
)
# These have the same definition, but are used in different places because the
# NYSE closed at 2:00 PM on Christmas Eve until 1993.
from zipline.utils.pandas_utils import july_5th_holiday_observance
ChristmasEveBefore1993 = Holiday(
'Christmas Eve',
month=12,
day=24,
end_date=Timestamp('1993-01-01'),
# When Christmas is a Saturday, the 24th is a full holiday.
days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY),
)
ChristmasEveInOrAfter1993 = Holiday(
'Christmas Eve',
month=12,
day=24,
start_date=Timestamp('1993-01-01'),
# When Christmas is a Saturday, the 24th is a full holiday.
days_of_week=(MONDAY, TUESDAY, WEDNESDAY, THURSDAY),
)
USNewYearsDay = Holiday(
'New Years Day',
month=1,
day=1,
# When Jan 1 is a Sunday, US markets observe the subsequent Monday.
# When Jan 1 is a Saturday (as in 2005 and 2011), no holiday is observed.
observance=sunday_to_monday
)
USMartinLutherKingJrAfter1998 = Holiday(
'Dr. Martin Luther King Jr. Day',
month=1,
day=1,
# The US markets didn't observe MLK day as a holiday until 1998.
start_date=Timestamp('1998-01-01'),
offset=DateOffset(weekday=MO(3)),
)
USMemorialDay = Holiday(
# NOTE: The definition for Memorial Day is incorrect as of pandas 0.16.0.
# See https://github.com/pydata/pandas/issues/9760.
'Memorial Day',
month=5,
day=25,
offset=DateOffset(weekday=MO(1)),
)
USIndependenceDay = Holiday(
'July 4th',
month=7,
day=4,
observance=nearest_workday,
)
Christmas = Holiday(
'Christmas',
month=12,
day=25,
observance=nearest_workday,
)
MonTuesThursBeforeIndependenceDay = Holiday(
# When July 4th is a Tuesday, Wednesday, or Friday, the previous day is a
# half day.
'Mondays, Tuesdays, and Thursdays Before Independence Day',
month=7,
day=3,
days_of_week=(MONDAY, TUESDAY, THURSDAY),
start_date=Timestamp("1995-01-01"),
)
FridayAfterIndependenceDayExcept2013 = Holiday(
# When July 4th is a Thursday, the next day is a half day (except in 2013,
# when, for no explicable reason, Wednesday was a half day instead).
"Fridays after Independence Day that aren't in 2013",
month=7,
day=5,
days_of_week=(FRIDAY,),
observance=july_5th_holiday_observance,
start_date=Timestamp("1995-01-01"),
)
USBlackFridayBefore1993 = Holiday(
'Black Friday',
month=11,
day=1,
# Black Friday was not observed until 1992.
start_date=Timestamp('1992-01-01'),
end_date=Timestamp('1993-01-01'),
offset=[DateOffset(weekday=TH(4)), Day(1)],
)
USBlackFridayInOrAfter1993 = Holiday(
'Black Friday',
month=11,
day=1,
start_date=Timestamp('1993-01-01'),
offset=[DateOffset(weekday=TH(4)), Day(1)],
)
BattleOfGettysburg = Holiday(
# All of the floor traders in Chicago were sent to PA
'Markets were closed during the battle of Gettysburg',
month=7,
day=(1, 2, 3),
start_date=Timestamp("1863-07-01"),
end_date=Timestamp("1863-07-03")
)
# http://en.wikipedia.org/wiki/Aftermath_of_the_September_11_attacks
September11Closings = date_range('2001-09-11', '2001-09-16', tz='UTC')
# http://en.wikipedia.org/wiki/Hurricane_sandy
HurricaneSandyClosings = date_range(
'2012-10-29',
'2012-10-30',
tz='UTC'
)
# National Days of Mourning
# - President Richard Nixon - April 27, 1994
# - President Ronald W. Reagan - June 11, 2004
# - President Gerald R. Ford - Jan 2, 2007
USNationalDaysofMourning = [
Timestamp('1994-04-27', tz='UTC'),
Timestamp('2004-06-11', tz='UTC'),
Timestamp('2007-01-02', tz='UTC'),
]
+39 -138
View File
@@ -1,5 +1,5 @@
#
# Copyright 2014 Quantopian, Inc.
# Copyright 2016 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -20,10 +20,9 @@ import datetime
import pandas as pd
import pytz
from zipline.utils.memoize import lazyval
from .context_tricks import nop_context
from zipline.errors import NoFurtherDataError
from zipline.utils.calendars import normalize_date
__all__ = [
'EventManager',
@@ -50,7 +49,7 @@ __all__ = [
]
MAX_MONTH_RANGE = 26
MAX_MONTH_RANGE = 23
MAX_WEEK_RANGE = 5
@@ -320,7 +319,10 @@ class AfterOpen(StatelessRule):
def calculate_dates(self, dt):
# given a dt, find that day's open and period end (open + offset)
self._period_start, self._period_close = self.cal.open_and_close(dt)
self._period_start, self._period_close = \
self.cal.open_and_close_for_session(
self.cal.minute_to_session_label(dt, direction="none")
)
self._period_end = self._period_start + self.offset - self._one_minute
def should_trigger(self, dt):
@@ -358,13 +360,18 @@ class BeforeClose(StatelessRule):
)
self._period_start = None
self._period_close = None
self._period_end = None
self._one_minute = datetime.timedelta(minutes=1)
def calculate_dates(self, dt):
# given a dt, find that day's close and period start (close - offset)
self._period_end = self.cal.open_and_close(dt)[1]
self._period_end = \
self.cal.open_and_close_for_session(
self.cal.minute_to_session_label(dt)
)[1]
self._period_start = self._period_end - self.offset
self._period_close = self._period_end
@@ -378,10 +385,7 @@ class BeforeClose(StatelessRule):
# that we will NOT correctly recognize a new date if we go backwards
# in time(which should never happen in a simulation, or in live
# trading)
if (
self._period_start is None or
self._period_close <= dt
):
if self._period_start is None or self._period_close <= dt:
self.calculate_dates(dt)
return self._period_start == dt
@@ -392,59 +396,28 @@ class NotHalfDay(StatelessRule):
A rule that only triggers when it is not a half day.
"""
def should_trigger(self, dt):
return normalize_date(dt) not in self.cal.early_closes
return self.cal.minute_to_session_label(dt, direction="none") \
not in self.cal.early_closes
class TradingDayOfWeekRule(six.with_metaclass(ABCMeta, StatelessRule)):
def __init__(self, n, invert):
if not 0 <= n < MAX_WEEK_RANGE:
raise _out_of_range_error(MAX_WEEK_RANGE)
self.next_date_start = None
self.next_date_end = None
self.next_midnight_timestamp = None
self.td_delta = -n if invert else n
@abstractmethod
def date_func(self, dt, cal):
raise NotImplementedError
self.td_delta = (-n - 1) if invert else n
def calculate_start_and_end(self, dt):
while True:
next_trading_day = self.cal.add_trading_days(
self.td_delta,
self.date_func(dt, self.cal),
)
# If after applying the offset to the start/end day of the week, we
# get day in a different week, skip this week and go on to the next
if next_trading_day.isocalendar()[1] == dt.isocalendar()[1]:
break
else:
dt += datetime.timedelta(days=7)
next_open, next_close = self.cal.open_and_close(next_trading_day)
self.next_date_start = next_open
self.next_date_end = next_close
self.next_midnight_timestamp = next_trading_day
@lazyval
def execution_periods(self):
# calculate the list of periods that match the given criteria
return self.cal.schedule.groupby(
pd.Grouper(freq="W")
).nth(self.td_delta).index
def should_trigger(self, dt):
if self.next_date_start is None:
# First time this method has been called. Calculate the midnight,
# open, and close for the first trigger, which occurs on the week
# of the simulation start
self.calculate_start_and_end(dt)
# If we've passed the trigger, calculate the next one
if dt > self.next_date_end:
self.calculate_start_and_end(self.next_date_end +
datetime.timedelta(days=7))
# if the given dt is within the next matching day, return true.
if self.next_date_start <= dt <= self.next_date_end or \
dt == self.next_midnight_timestamp:
return True
return False
# is this market minute's period in the list of execution periods?
return self.cal.minute_to_session_label(dt, direction="none") in \
self.execution_periods
class NthTradingDayOfWeek(TradingDayOfWeekRule):
@@ -455,25 +428,6 @@ class NthTradingDayOfWeek(TradingDayOfWeekRule):
def __init__(self, n):
super(NthTradingDayOfWeek, self).__init__(n, invert=False)
@staticmethod
def get_first_trading_day_of_week(dt, cal):
prev = None
# Traverse backward until we hit a week border, then jump back to the
# previous trading day.
try:
while not prev or dt.weekday() < prev.weekday():
prev = dt
dt = cal.previous_trading_day(dt)
except NoFurtherDataError:
prev = dt
if cal.is_open_on_day(prev):
return prev
else:
return cal.next_trading_day(prev)
date_func = get_first_trading_day_of_week
class NDaysBeforeLastTradingDayOfWeek(TradingDayOfWeekRule):
"""
@@ -482,51 +436,27 @@ class NDaysBeforeLastTradingDayOfWeek(TradingDayOfWeekRule):
def __init__(self, n):
super(NDaysBeforeLastTradingDayOfWeek, self).__init__(n, invert=True)
@staticmethod
def get_last_trading_day_of_week(dt, cal):
prev = None
# Traverse forward until we hit a week border, then jump back to the
# previous trading day.
try:
while not prev or dt.weekday() > prev.weekday():
prev = dt
dt = cal.next_trading_day(dt)
except NoFurtherDataError:
prev = dt
if cal.is_open_on_day(prev):
return prev
else:
return cal.previous_trading_day(prev)
date_func = get_last_trading_day_of_week
class TradingDayOfMonthRule(six.with_metaclass(ABCMeta, StatelessRule)):
def __init__(self, n, invert):
if not 0 <= n < MAX_MONTH_RANGE:
raise _out_of_range_error(MAX_MONTH_RANGE)
self.month = None
self.date = None
self.td_delta = -n if invert else n
if invert:
self.td_delta = -n - 1
else:
self.td_delta = n
def should_trigger(self, dt):
return self.get_trigger_day_of_month(dt) == normalize_date(dt)
# is this market minute's period in the list of execution periods?
return self.cal.minute_to_session_label(dt, direction="none") in \
self.execution_periods
@abstractmethod
def date_func(self, dt):
raise NotImplementedError
def get_trigger_day_of_month(self, dt):
if self.month == dt.month:
# We already computed the day for this month.
return self.date
self.date = self.date_func(dt)
if self.td_delta:
self.date = self.cal.add_trading_days(self.td_delta, self.date)
return self.date
@lazyval
def execution_periods(self):
# calculate the list of periods that match the given criteria
return self.cal.schedule.groupby(
pd.Grouper(freq="M")
).nth(self.td_delta).index
class NthTradingDayOfMonth(TradingDayOfMonthRule):
@@ -537,16 +467,6 @@ class NthTradingDayOfMonth(TradingDayOfMonthRule):
def __init__(self, n):
super(NthTradingDayOfMonth, self).__init__(n, invert=False)
def get_first_trading_day_of_month(self, dt):
self.month = dt.month
dt = dt.replace(day=1)
first_day = (normalize_date(dt) if self.cal.is_open_on_day(dt)
else self.cal.next_trading_day(dt))
return first_day
date_func = get_first_trading_day_of_month
class NDaysBeforeLastTradingDayOfMonth(TradingDayOfMonthRule):
"""
@@ -555,25 +475,6 @@ class NDaysBeforeLastTradingDayOfMonth(TradingDayOfMonthRule):
def __init__(self, n):
super(NDaysBeforeLastTradingDayOfMonth, self).__init__(n, invert=True)
def get_last_trading_day_of_month(self, dt):
self.month = dt.month
if dt.month == 12:
# Roll the year forward and start in January.
year = dt.year + 1
month = 1
else:
# Increment the month in the same year.
year = dt.year
month = dt.month + 1
last_day = self.cal.previous_trading_day(
dt.replace(year=year, month=month, day=1)
)
return last_day
date_func = get_last_trading_day_of_month
# Stateful rules
+39 -32
View File
@@ -1,5 +1,5 @@
#
# Copyright 2013 Quantopian, Inc.
# Copyright 2016 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@@ -19,7 +19,7 @@ Factory functions to prepare useful data.
"""
import pandas as pd
import numpy as np
from datetime import timedelta
from datetime import timedelta, datetime
from zipline.protocol import Event, DATASOURCE_TYPE
from zipline.sources import SpecificEquityTrades
@@ -29,7 +29,7 @@ from zipline.data.loader import ( # For backwards compatibility
load_from_yahoo,
load_bars_from_yahoo,
)
from zipline.utils.calendars import default_nyse_schedule
from zipline.utils.calendars import get_calendar
__all__ = ['load_from_yahoo', 'load_bars_from_yahoo']
@@ -40,49 +40,56 @@ def create_simulation_parameters(year=2006, start=None, end=None,
num_days=None,
data_frequency='daily',
emission_rate='daily',
trading_schedule=default_nyse_schedule):
trading_calendar=None):
if not trading_calendar:
trading_calendar = get_calendar("NYSE")
if start is None:
start = pd.Timestamp("{0}-01-01".format(year), tz='UTC')
elif type(start) == datetime:
start = pd.Timestamp(start)
if end is None:
if num_days:
start_index = trading_schedule.all_execution_days\
.searchsorted(start)
end = trading_schedule.all_execution_days[
start_index + num_days - 1
]
start_index = trading_calendar.all_sessions.searchsorted(start)
end = trading_calendar.all_sessions[start_index + num_days - 1]
else:
end = pd.Timestamp("{0}-12-31".format(year), tz='UTC')
elif type(end) == datetime:
end = pd.Timestamp(end)
sim_params = SimulationParameters(
period_start=start,
period_end=end,
start_session=start,
end_session=end,
capital_base=capital_base,
data_frequency=data_frequency,
emission_rate=emission_rate,
trading_schedule=trading_schedule,
trading_calendar=trading_calendar,
)
return sim_params
def get_next_trading_dt(current, interval, trading_schedule):
next_dt = pd.Timestamp(current).tz_convert(trading_schedule.tz)
def get_next_trading_dt(current, interval, trading_calendar):
next_dt = pd.Timestamp(current).tz_convert(trading_calendar.tz)
while True:
# Convert timestamp to naive before adding day, otherwise the when
# stepping over EDT an hour is added.
next_dt = pd.Timestamp(next_dt.replace(tzinfo=None))
next_dt = next_dt + interval
next_dt = pd.Timestamp(next_dt, tz=trading_schedule.tz)
next_dt = pd.Timestamp(next_dt, tz=trading_calendar.tz)
next_dt_utc = next_dt.tz_convert('UTC')
if trading_schedule.is_executing_on_minute(next_dt_utc):
if trading_calendar.is_open_on_minute(next_dt_utc):
break
next_dt = next_dt_utc.tz_convert(trading_schedule.tz)
next_dt = next_dt_utc.tz_convert(trading_calendar.tz)
return next_dt_utc
def create_trade_history(sid, prices, amounts, interval, sim_params,
trading_schedule, source_id="test_factory"):
trading_calendar, source_id="test_factory"):
trades = []
current = sim_params.first_open
@@ -95,7 +102,7 @@ def create_trade_history(sid, prices, amounts, interval, sim_params,
trade_dt = current
trade = create_trade(sid, price, amount, trade_dt, source_id)
trades.append(trade)
current = get_next_trading_dt(current, interval, trading_schedule)
current = get_next_trading_dt(current, interval, trading_calendar)
assert len(trades) == len(prices)
return trades
@@ -156,12 +163,12 @@ def create_txn(sid, price, amount, datetime):
def create_txn_history(sid, priceList, amtList, interval, sim_params,
trading_schedule):
trading_calendar):
txns = []
current = sim_params.first_open
for price, amount in zip(priceList, amtList):
current = get_next_trading_dt(current, interval, trading_schedule)
current = get_next_trading_dt(current, interval, trading_calendar)
txns.append(create_txn(sid, price, amount, current))
current = current + interval
@@ -169,20 +176,20 @@ def create_txn_history(sid, priceList, amtList, interval, sim_params,
def create_returns_from_range(sim_params):
return pd.Series(index=sim_params.trading_days,
data=np.random.rand(len(sim_params.trading_days)))
return pd.Series(index=sim_params.sessions,
data=np.random.rand(len(sim_params.sessions)))
def create_returns_from_list(returns, sim_params):
return pd.Series(index=sim_params.trading_days[:len(returns)],
return pd.Series(index=sim_params.sessions[:len(returns)],
data=returns)
def create_daily_trade_source(sids, sim_params, env, trading_schedule,
def create_daily_trade_source(sids, sim_params, env, trading_calendar,
concurrent=False):
"""
creates trade_count trades for each sid in sids list.
first trade will be on sim_params.period_start, and daily
first trade will be on sim_params.start_session, and daily
thereafter for each sid. Thus, two sids should result in two trades per
day.
"""
@@ -191,19 +198,19 @@ def create_daily_trade_source(sids, sim_params, env, trading_schedule,
timedelta(days=1),
sim_params,
env=env,
trading_schedule=trading_schedule,
trading_calendar=trading_calendar,
concurrent=concurrent,
)
def create_trade_source(sids, trade_time_increment, sim_params, env,
trading_schedule, concurrent=False):
trading_calendar, concurrent=False):
# If the sim_params define an end that is during market hours, that will be
# used as the end of the data source
if trading_schedule.is_executing_on_minute(sim_params.period_end):
end = sim_params.period_end
# Otherwise, the last_close after the period_end is used as the end of the
if trading_calendar.is_open_on_minute(sim_params.end_session):
end = sim_params.end_session
# Otherwise, the last_close after the end_session is used as the end of the
# data source
else:
end = sim_params.last_close
@@ -217,7 +224,7 @@ def create_trade_source(sids, trade_time_increment, sim_params, env,
'filter': sids,
'concurrent': concurrent,
'env': env,
'trading_schedule': trading_schedule,
'trading_calendar': trading_calendar,
}
source = SpecificEquityTrades(*args, **kwargs)
+2 -2
View File
@@ -20,7 +20,7 @@ from zipline.data.data_portal import DataPortal
from zipline.finance.trading import TradingEnvironment
from zipline.pipeline.data import USEquityPricing
from zipline.pipeline.loaders import USEquityPricingLoader
from zipline.utils.calendars import default_nyse_schedule
from zipline.utils.calendars import get_calendar
import zipline.utils.paths as pth
@@ -132,7 +132,7 @@ def _run(handle_data,
first_trading_day =\
bundle_data.equity_minute_bar_reader.first_trading_day
data = DataPortal(
env.asset_finder, default_nyse_schedule,
env.asset_finder, get_calendar("NYSE"),
first_trading_day=first_trading_day,
equity_minute_reader=bundle_data.equity_minute_bar_reader,
equity_daily_reader=bundle_data.equity_daily_bar_reader,
+5 -5
View File
@@ -2,7 +2,7 @@ import zipline.utils.factory as factory
from zipline.testing.core import create_data_portal_from_trade_history
from zipline.test_algorithms import TestAlgorithm
from zipline.utils.calendars import default_nyse_schedule
from zipline.utils.calendars import get_calendar
def create_test_zipline(**config):
@@ -40,7 +40,7 @@ def create_test_zipline(**config):
concurrent_trades = config.get('concurrent_trades', False)
order_count = config.get('order_count', 100)
order_amount = config.get('order_amount', 100)
trading_schedule = config.get('trading_schedule', default_nyse_schedule)
trading_calendar = config.get('trading_calendar', get_calendar("NYSE"))
# -------------------
# Create the Algo
@@ -54,7 +54,7 @@ def create_test_zipline(**config):
order_count,
sim_params=config.get('sim_params',
factory.create_simulation_parameters()),
trading_schedule=trading_schedule,
trading_calendar=trading_calendar,
slippage=config.get('slippage'),
identifiers=sid_list
)
@@ -70,7 +70,7 @@ def create_test_zipline(**config):
sid_list,
test_algo.sim_params,
test_algo.trading_environment,
trading_schedule,
trading_calendar,
concurrent=concurrent_trades,
)
@@ -83,7 +83,7 @@ def create_test_zipline(**config):
data_portal = create_data_portal_from_trade_history(
config['env'].asset_finder,
trading_schedule,
trading_calendar,
config['tempdir'],
config['sim_params'],
trades_by_sid