mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-13 00:41:38 +08:00
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:
@@ -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']
|
||||
),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -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]
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
@@ -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={
|
||||
|
||||
@@ -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():
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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
@@ -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 "
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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")
|
||||
|
||||
|
||||
@@ -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))
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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(
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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))
|
||||
@@ -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
@@ -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:
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)]
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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]
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
@@ -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'))
|
||||
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user