mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-24 13:00:57 +08:00
Merge pull request #1138 from quantopian/exchange-calendars-v2
ExchangeCalendars and TradingSchedules
This commit is contained in:
+1
-1
@@ -70,4 +70,4 @@ after_success:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- lazy-mainline
|
||||
- exchange-calendars-v2
|
||||
|
||||
@@ -55,6 +55,6 @@ def main(env, do_upload):
|
||||
if __name__ == '__main__':
|
||||
env = os.environ.copy()
|
||||
main(env,
|
||||
do_upload=(env.get('ANACONDA_TOKEN') and
|
||||
env.get('APPVEYOR_REPO_BRANCH') == 'master') and
|
||||
'APPVEYOR_PULL_REQUEST_NUMBER' not in env)
|
||||
do_upload=((env.get('ANACONDA_TOKEN')
|
||||
and env.get('APPVEYOR_REPO_BRANCH') == 'master')
|
||||
and 'APPVEYOR_PULL_REQUEST_NUMBER' not in env))
|
||||
|
||||
@@ -16,7 +16,6 @@ from zipline.pipeline.loaders.synthetic import (
|
||||
from zipline.testing import (
|
||||
subtest,
|
||||
str_to_seconds,
|
||||
tmp_trading_env,
|
||||
)
|
||||
from zipline.testing.fixtures import WithInstanceTmpDir, ZiplineTestCase
|
||||
from zipline.testing.predicates import (
|
||||
@@ -31,7 +30,7 @@ from zipline.testing.predicates import (
|
||||
)
|
||||
from zipline.utils.cache import dataframe_cache
|
||||
from zipline.utils.functional import apply
|
||||
from zipline.utils.tradingcalendar import trading_days
|
||||
from zipline.utils.calendars import get_calendar
|
||||
import zipline.utils.paths as pth
|
||||
|
||||
|
||||
@@ -110,12 +109,13 @@ class BundleCoreTestCase(WithInstanceTmpDir, ZiplineTestCase):
|
||||
assert_true(called[0])
|
||||
|
||||
def test_ingest(self):
|
||||
env = self.enter_instance_context(tmp_trading_env())
|
||||
|
||||
start = pd.Timestamp('2014-01-06', tz='utc')
|
||||
end = pd.Timestamp('2014-01-10', tz='utc')
|
||||
trading_days = get_calendar('NYSE').all_trading_days
|
||||
calendar = trading_days[trading_days.slice_indexer(start, end)]
|
||||
minutes = env.minutes_for_days_in_range(calendar[0], calendar[-1])
|
||||
minutes = get_calendar('NYSE').trading_minutes_for_days_in_range(
|
||||
calendar[0], calendar[-1]
|
||||
)
|
||||
|
||||
sids = tuple(range(3))
|
||||
equities = make_simple_equity_info(
|
||||
@@ -141,10 +141,14 @@ class BundleCoreTestCase(WithInstanceTmpDir, ZiplineTestCase):
|
||||
},
|
||||
])
|
||||
|
||||
@self.register('bundle',
|
||||
calendar=calendar,
|
||||
opens=env.opens_in_range(calendar[0], calendar[-1]),
|
||||
closes=env.closes_in_range(calendar[0], calendar[-1]))
|
||||
schedule = get_calendar('NYSE').schedule
|
||||
|
||||
@self.register(
|
||||
'bundle',
|
||||
calendar=calendar,
|
||||
opens=schedule.market_open[calendar[0]:calendar[-1]],
|
||||
closes=schedule.market_close[calendar[0]: calendar[-1]],
|
||||
)
|
||||
def bundle_ingest(environ,
|
||||
asset_db_writer,
|
||||
minute_bar_writer,
|
||||
|
||||
@@ -10,7 +10,7 @@ from zipline.lib.adjustment import Float64Multiply
|
||||
from zipline.testing import test_resource_path, tmp_dir, read_compressed
|
||||
from zipline.testing.fixtures import WithResponses, ZiplineTestCase
|
||||
from zipline.testing.predicates import assert_equal
|
||||
from zipline.utils.tradingcalendar import trading_days
|
||||
from zipline.utils.calendars import get_calendar
|
||||
|
||||
|
||||
class YahooBundleTestCase(WithResponses, ZiplineTestCase):
|
||||
@@ -18,6 +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
|
||||
calendar = trading_days[
|
||||
(trading_days >= asset_start) &
|
||||
(trading_days <= asset_end)
|
||||
|
||||
@@ -15,8 +15,6 @@
|
||||
from datetime import timedelta
|
||||
import os
|
||||
|
||||
from unittest import TestCase
|
||||
|
||||
from numpy import (
|
||||
arange,
|
||||
array,
|
||||
@@ -36,7 +34,6 @@ from pandas import (
|
||||
NaT,
|
||||
date_range,
|
||||
)
|
||||
from testfixtures import TempDirectory
|
||||
|
||||
from zipline.data.minute_bars import (
|
||||
BcolzMinuteBarWriter,
|
||||
@@ -45,8 +42,12 @@ from zipline.data.minute_bars import (
|
||||
US_EQUITIES_MINUTES_PER_DAY,
|
||||
BcolzMinuteWriterColumnMismatch
|
||||
)
|
||||
from zipline.finance.trading import TradingEnvironment
|
||||
|
||||
from zipline.testing.fixtures import (
|
||||
WithInstanceTmpDir,
|
||||
WithTradingSchedule,
|
||||
ZiplineTestCase,
|
||||
)
|
||||
|
||||
# Calendar is set to cover several half days, to check a case where half
|
||||
# days would be read out of order in cases of windows which spanned over
|
||||
@@ -55,27 +56,24 @@ TEST_CALENDAR_START = Timestamp('2014-06-02', tz='UTC')
|
||||
TEST_CALENDAR_STOP = Timestamp('2015-12-31', tz='UTC')
|
||||
|
||||
|
||||
class BcolzMinuteBarTestCase(TestCase):
|
||||
class BcolzMinuteBarTestCase(WithTradingSchedule, WithInstanceTmpDir,
|
||||
ZiplineTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.env = TradingEnvironment()
|
||||
all_market_opens = cls.env.open_and_closes.market_open
|
||||
all_market_closes = cls.env.open_and_closes.market_close
|
||||
indexer = all_market_opens.index.slice_indexer(
|
||||
start=TEST_CALENDAR_START,
|
||||
end=TEST_CALENDAR_STOP
|
||||
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 = all_market_opens[indexer]
|
||||
cls.market_closes = all_market_closes[indexer]
|
||||
cls.market_opens = trading_days.market_open
|
||||
cls.market_closes = trading_days.market_close
|
||||
cls.test_calendar_start = cls.market_opens.index[0]
|
||||
cls.test_calendar_stop = cls.market_opens.index[-1]
|
||||
|
||||
def setUp(self):
|
||||
def init_instance_fixtures(self):
|
||||
super(BcolzMinuteBarTestCase, self).init_instance_fixtures()
|
||||
|
||||
self.dir_ = TempDirectory()
|
||||
self.dir_.create()
|
||||
self.dest = self.dir_.getpath('minute_bars')
|
||||
self.dest = self.instance_tmpdir.getpath('minute_bars')
|
||||
os.makedirs(self.dest)
|
||||
self.writer = BcolzMinuteBarWriter(
|
||||
TEST_CALENDAR_START,
|
||||
@@ -86,9 +84,6 @@ class BcolzMinuteBarTestCase(TestCase):
|
||||
)
|
||||
self.reader = BcolzMinuteBarReader(self.dest)
|
||||
|
||||
def tearDown(self):
|
||||
self.dir_.cleanup()
|
||||
|
||||
def test_write_one_ohlcv(self):
|
||||
minute = self.market_opens[self.test_calendar_start]
|
||||
sid = 1
|
||||
@@ -802,10 +797,13 @@ class BcolzMinuteBarTestCase(TestCase):
|
||||
|
||||
data = {sids[0]: data_1, sids[1]: data_2}
|
||||
|
||||
start_minute_loc = self.env.market_minutes.get_loc(minutes[0])
|
||||
minute_locs = [self.env.market_minutes.get_loc(minute) -
|
||||
start_minute_loc
|
||||
for minute in minutes]
|
||||
start_minute_loc = \
|
||||
self.trading_schedule.all_execution_minutes.get_loc(minutes[0])
|
||||
minute_locs = [
|
||||
self.trading_schedule.all_execution_minutes.get_loc(minute)
|
||||
- start_minute_loc
|
||||
for minute in minutes
|
||||
]
|
||||
|
||||
for i, col in enumerate(columns):
|
||||
for j, sid in enumerate(sids):
|
||||
@@ -824,7 +822,9 @@ class BcolzMinuteBarTestCase(TestCase):
|
||||
'close': arange(1, 781),
|
||||
'volume': arange(1, 781)
|
||||
}
|
||||
dts = array(self.env.minutes_for_days_in_range(start_day, end_day))
|
||||
dts = array(self.trading_schedule.execution_minutes_for_days_in_range(
|
||||
start_day, end_day
|
||||
))
|
||||
self.writer.write_cols(sid, dts, cols)
|
||||
|
||||
self.assertEqual(
|
||||
@@ -866,7 +866,9 @@ class BcolzMinuteBarTestCase(TestCase):
|
||||
'close': arange(1, 601),
|
||||
'volume': arange(1, 601)
|
||||
}
|
||||
dts = array(self.env.minutes_for_days_in_range(start_day, end_day))
|
||||
dts = array(self.trading_schedule.execution_minutes_for_days_in_range(
|
||||
start_day, end_day
|
||||
))
|
||||
self.writer.write_cols(sid, dts, cols)
|
||||
|
||||
self.assertEqual(
|
||||
|
||||
@@ -46,6 +46,7 @@ from zipline.testing.fixtures import (
|
||||
WithBcolzDailyBarReader,
|
||||
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')
|
||||
@@ -96,11 +97,9 @@ class BcolzDailyBarTestCase(WithBcolzDailyBarReader, ZiplineTestCase):
|
||||
@classmethod
|
||||
def init_class_fixtures(cls):
|
||||
super(BcolzDailyBarTestCase, cls).init_class_fixtures()
|
||||
all_trading_days = cls.env.trading_days
|
||||
cls.trading_days = all_trading_days[
|
||||
all_trading_days.get_loc(TEST_CALENDAR_START):
|
||||
all_trading_days.get_loc(TEST_CALENDAR_STOP) + 1
|
||||
]
|
||||
cls.trading_days = get_calendar('NYSE').trading_days(
|
||||
TEST_CALENDAR_START, TEST_CALENDAR_STOP
|
||||
).index
|
||||
|
||||
@property
|
||||
def assets(self):
|
||||
|
||||
@@ -91,9 +91,10 @@ class SlippageTestCase(WithSimParams, WithDataPortal, ZiplineTestCase):
|
||||
start=normalize_date(self.minutes[0]),
|
||||
end=normalize_date(self.minutes[-1])
|
||||
)
|
||||
with tmp_bcolz_minute_bar_reader(self.env, days, assets) as reader:
|
||||
with tmp_bcolz_minute_bar_reader(self.trading_schedule, days, assets) \
|
||||
as reader:
|
||||
data_portal = DataPortal(
|
||||
self.env,
|
||||
self.env.asset_finder, self.trading_schedule,
|
||||
first_trading_day=reader.first_trading_day,
|
||||
equity_minute_reader=reader,
|
||||
)
|
||||
@@ -480,9 +481,10 @@ class SlippageTestCase(WithSimParams, WithDataPortal, ZiplineTestCase):
|
||||
start=normalize_date(self.minutes[0]),
|
||||
end=normalize_date(self.minutes[-1])
|
||||
)
|
||||
with tmp_bcolz_minute_bar_reader(self.env, days, assets) as reader:
|
||||
with tmp_bcolz_minute_bar_reader(self.trading_schedule, days, assets) \
|
||||
as reader:
|
||||
data_portal = DataPortal(
|
||||
self.env,
|
||||
self.env.asset_finder, self.trading_schedule,
|
||||
first_trading_day=reader.first_trading_day,
|
||||
equity_minute_reader=reader,
|
||||
)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
Base class for Pipeline API unittests.
|
||||
"""
|
||||
from functools import wraps
|
||||
from unittest import TestCase
|
||||
|
||||
import numpy as np
|
||||
from numpy import arange, prod
|
||||
@@ -18,10 +17,10 @@ from zipline.testing import (
|
||||
ExplodingObject,
|
||||
tmp_asset_finder,
|
||||
)
|
||||
from zipline.testing.fixtures import ZiplineTestCase, WithTradingSchedule
|
||||
|
||||
from zipline.utils.functional import dzip_exact
|
||||
from zipline.utils.pandas_utils import explode
|
||||
from zipline.utils.tradingcalendar import trading_day
|
||||
|
||||
|
||||
def with_defaults(**default_funcs):
|
||||
@@ -51,11 +50,14 @@ def with_defaults(**default_funcs):
|
||||
with_default_shape = with_defaults(shape=lambda self: self.default_shape)
|
||||
|
||||
|
||||
class BasePipelineTestCase(TestCase):
|
||||
class BasePipelineTestCase(WithTradingSchedule, ZiplineTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.__calendar = date_range('2014', '2015', freq=trading_day)
|
||||
def init_class_fixtures(cls):
|
||||
super(BasePipelineTestCase, cls).init_class_fixtures()
|
||||
|
||||
cls.__calendar = date_range('2014', '2015',
|
||||
freq=cls.trading_schedule.day)
|
||||
cls.__assets = assets = Int64Index(arange(1, 20))
|
||||
cls.__tmp_finder_ctx = tmp_asset_finder(
|
||||
equities=make_simple_equity_info(
|
||||
@@ -70,10 +72,6 @@ class BasePipelineTestCase(TestCase):
|
||||
include_start_date=False,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
cls.__tmp_finder_ctx.__exit__()
|
||||
|
||||
@property
|
||||
def default_shape(self):
|
||||
"""Default shape for methods that build test data."""
|
||||
|
||||
@@ -826,7 +826,7 @@ class FrameInputTestCase(WithTradingEnvironment, ZiplineTestCase):
|
||||
cls.dates = date_range(
|
||||
cls.start,
|
||||
cls.end,
|
||||
freq=cls.env.trading_day,
|
||||
freq=cls.trading_schedule.day,
|
||||
tz='UTC',
|
||||
)
|
||||
cls.assets = cls.asset_finder.retrieve_all(cls.asset_ids)
|
||||
@@ -930,7 +930,7 @@ class SyntheticBcolzTestCase(WithAdjustmentReader,
|
||||
cls.equity_info = ret = make_rotating_equity_info(
|
||||
num_assets=6,
|
||||
first_start=cls.first_asset_start,
|
||||
frequency=cls.TRADING_ENV_TRADING_CALENDAR.trading_day,
|
||||
frequency=cls.trading_schedule.day,
|
||||
periods_between_starts=4,
|
||||
asset_lifetime=8,
|
||||
)
|
||||
@@ -985,15 +985,15 @@ class SyntheticBcolzTestCase(WithAdjustmentReader,
|
||||
def test_SMA(self):
|
||||
engine = SimplePipelineEngine(
|
||||
lambda column: self.pipeline_loader,
|
||||
self.env.trading_days,
|
||||
self.trading_schedule.all_execution_days,
|
||||
self.asset_finder,
|
||||
)
|
||||
window_length = 5
|
||||
asset_ids = self.all_asset_ids
|
||||
dates = date_range(
|
||||
self.first_asset_start + self.env.trading_day,
|
||||
self.first_asset_start + self.trading_schedule.day,
|
||||
self.last_asset_end,
|
||||
freq=self.env.trading_day,
|
||||
freq=self.trading_schedule.day,
|
||||
)
|
||||
dates_to_test = dates[window_length:]
|
||||
|
||||
@@ -1013,7 +1013,7 @@ class SyntheticBcolzTestCase(WithAdjustmentReader,
|
||||
# **previous** day's data.
|
||||
expected_raw = rolling_mean(
|
||||
expected_bar_values_2d(
|
||||
dates - self.env.trading_day,
|
||||
dates - self.trading_schedule.day,
|
||||
self.equity_info,
|
||||
'close',
|
||||
),
|
||||
@@ -1039,15 +1039,15 @@ class SyntheticBcolzTestCase(WithAdjustmentReader,
|
||||
# valuable.
|
||||
engine = SimplePipelineEngine(
|
||||
lambda column: self.pipeline_loader,
|
||||
self.env.trading_days,
|
||||
self.trading_schedule.all_execution_days,
|
||||
self.asset_finder,
|
||||
)
|
||||
window_length = 5
|
||||
asset_ids = self.all_asset_ids
|
||||
dates = date_range(
|
||||
self.first_asset_start + self.env.trading_day,
|
||||
self.first_asset_start + self.trading_schedule.day,
|
||||
self.last_asset_end,
|
||||
freq=self.env.trading_day,
|
||||
freq=self.trading_schedule.day,
|
||||
)
|
||||
dates_to_test = dates[window_length:]
|
||||
|
||||
@@ -1083,7 +1083,7 @@ class ParameterizedFactorTestCase(WithTradingEnvironment, ZiplineTestCase):
|
||||
@classmethod
|
||||
def init_class_fixtures(cls):
|
||||
super(ParameterizedFactorTestCase, cls).init_class_fixtures()
|
||||
day = cls.env.trading_day
|
||||
day = cls.trading_schedule.day
|
||||
|
||||
cls.dates = dates = date_range(
|
||||
'2015-02-01',
|
||||
|
||||
@@ -89,8 +89,8 @@ for_each_factor_dtype = parameterized.expand([
|
||||
|
||||
class FactorTestCase(BasePipelineTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(FactorTestCase, self).setUp()
|
||||
def init_instance_fixtures(self):
|
||||
super(FactorTestCase, self).init_instance_fixtures()
|
||||
self.f = F()
|
||||
|
||||
def test_bad_input(self):
|
||||
|
||||
@@ -75,8 +75,8 @@ class Mask(Filter):
|
||||
|
||||
class FilterTestCase(BasePipelineTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(FilterTestCase, self).setUp()
|
||||
def init_instance_fixtures(self):
|
||||
super(FilterTestCase, self).init_instance_fixtures()
|
||||
self.f = SomeFactor()
|
||||
self.g = SomeOtherFactor()
|
||||
|
||||
|
||||
@@ -24,7 +24,10 @@ from zipline.pipeline.data import USEquityPricing
|
||||
from zipline.pipeline.loaders.frame import (
|
||||
DataFrameLoader,
|
||||
)
|
||||
from zipline.utils.tradingcalendar import trading_day
|
||||
from zipline.utils.calendars import default_nyse_schedule
|
||||
|
||||
|
||||
trading_day = default_nyse_schedule.day
|
||||
|
||||
|
||||
class DataFrameLoaderTestCase(TestCase):
|
||||
|
||||
@@ -60,7 +60,7 @@ from zipline.testing.fixtures import (
|
||||
WithDataPortal,
|
||||
ZiplineTestCase,
|
||||
)
|
||||
from zipline.utils.tradingcalendar import trading_day
|
||||
from zipline.utils.calendars import default_nyse_schedule
|
||||
|
||||
|
||||
TEST_RESOURCE_PATH = join(
|
||||
@@ -70,6 +70,9 @@ 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
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@ import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from zipline.testing import test_resource_path, write_compressed
|
||||
from zipline.utils.tradingcalendar import trading_days
|
||||
from zipline.utils.calendars import get_calendar
|
||||
|
||||
|
||||
def zipfile_path(symbol, ext):
|
||||
@@ -27,6 +27,8 @@ 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
|
||||
|
||||
return pd.DataFrame(
|
||||
data={
|
||||
'Date': trading_days[
|
||||
|
||||
@@ -13,31 +13,24 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest
|
||||
|
||||
import datetime
|
||||
import numpy as np
|
||||
import pytz
|
||||
import zipline.finance.risk as risk
|
||||
from zipline.utils import factory
|
||||
|
||||
from zipline.finance.trading import SimulationParameters, TradingEnvironment
|
||||
from zipline.testing.fixtures import WithTradingEnvironment, ZiplineTestCase
|
||||
|
||||
from zipline.finance.trading import SimulationParameters
|
||||
from . import answer_key
|
||||
ANSWER_KEY = answer_key.ANSWER_KEY
|
||||
|
||||
|
||||
class TestRisk(unittest.TestCase):
|
||||
class TestRisk(WithTradingEnvironment, ZiplineTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.env = TradingEnvironment()
|
||||
def init_instance_fixtures(self):
|
||||
super(TestRisk, self).init_instance_fixtures()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.env
|
||||
|
||||
def setUp(self):
|
||||
start_date = datetime.datetime(
|
||||
year=2006,
|
||||
month=1,
|
||||
@@ -51,7 +44,7 @@ class TestRisk(unittest.TestCase):
|
||||
self.sim_params = SimulationParameters(
|
||||
period_start=start_date,
|
||||
period_end=end_date,
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
self.algo_returns_06 = factory.create_returns_from_list(
|
||||
@@ -60,7 +53,9 @@ class TestRisk(unittest.TestCase):
|
||||
)
|
||||
|
||||
self.cumulative_metrics_06 = risk.RiskMetricsCumulative(
|
||||
self.sim_params, env=self.env
|
||||
self.sim_params,
|
||||
treasury_curves=self.env.treasury_curves,
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
for dt, returns in answer_key.RETURNS_DATA.iterrows():
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import unittest
|
||||
import datetime
|
||||
import calendar
|
||||
import numpy as np
|
||||
@@ -25,8 +24,8 @@ from six import itervalues
|
||||
import zipline.finance.risk as risk
|
||||
from zipline.utils import factory
|
||||
|
||||
from zipline.finance.trading import SimulationParameters, TradingEnvironment
|
||||
|
||||
from zipline.finance.trading import SimulationParameters
|
||||
from zipline.testing.fixtures import WithTradingEnvironment, ZiplineTestCase
|
||||
from . import answer_key
|
||||
from . answer_key import AnswerKey
|
||||
|
||||
@@ -35,17 +34,10 @@ ANSWER_KEY = AnswerKey()
|
||||
RETURNS = ANSWER_KEY.RETURNS
|
||||
|
||||
|
||||
class TestRisk(unittest.TestCase):
|
||||
class TestRisk(WithTradingEnvironment, ZiplineTestCase):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.env = TradingEnvironment()
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.env
|
||||
|
||||
def setUp(self):
|
||||
def init_instance_fixtures(self):
|
||||
super(TestRisk, self).init_instance_fixtures()
|
||||
|
||||
start_date = datetime.datetime(
|
||||
year=2006,
|
||||
@@ -60,7 +52,7 @@ class TestRisk(unittest.TestCase):
|
||||
self.sim_params = SimulationParameters(
|
||||
period_start=start_date,
|
||||
period_end=end_date,
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
self.algo_returns_06 = factory.create_returns_from_list(
|
||||
@@ -75,7 +67,8 @@ class TestRisk(unittest.TestCase):
|
||||
self.algo_returns_06,
|
||||
self.sim_params,
|
||||
benchmark_returns=self.benchmark_returns_06,
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
treasury_curves=self.env.treasury_curves,
|
||||
)
|
||||
|
||||
start_08 = datetime.datetime(
|
||||
@@ -95,12 +88,9 @@ class TestRisk(unittest.TestCase):
|
||||
self.sim_params08 = SimulationParameters(
|
||||
period_start=start_08,
|
||||
period_end=end_08,
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
return
|
||||
|
||||
def test_factory(self):
|
||||
returns = [0.1] * 100
|
||||
r_objects = factory.create_returns_from_list(returns, self.sim_params)
|
||||
@@ -116,8 +106,9 @@ class TestRisk(unittest.TestCase):
|
||||
returns.index[0],
|
||||
returns.index[-1],
|
||||
returns,
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
benchmark_returns=self.env.benchmark_returns,
|
||||
treasury_curves=self.env.treasury_curves,
|
||||
)
|
||||
self.assertEqual(metrics.max_drawdown, 0.505)
|
||||
|
||||
@@ -142,7 +133,10 @@ class TestRisk(unittest.TestCase):
|
||||
|
||||
def test_trading_days_06(self):
|
||||
returns = factory.create_returns_from_range(self.sim_params)
|
||||
metrics = risk.RiskReport(returns, self.sim_params, env=self.env)
|
||||
metrics = risk.RiskReport(returns, self.sim_params,
|
||||
trading_schedule=self.trading_schedule,
|
||||
treasury_curves=self.env.treasury_curves,
|
||||
benchmark_returns=self.env.benchmark_returns)
|
||||
self.assertEqual([x.num_trading_days for x in metrics.year_periods],
|
||||
[251])
|
||||
self.assertEqual([x.num_trading_days for x in metrics.month_periods],
|
||||
@@ -366,7 +360,10 @@ class TestRisk(unittest.TestCase):
|
||||
|
||||
def test_benchmark_returns_08(self):
|
||||
returns = factory.create_returns_from_range(self.sim_params08)
|
||||
metrics = risk.RiskReport(returns, self.sim_params08, env=self.env)
|
||||
metrics = risk.RiskReport(returns, self.sim_params08,
|
||||
trading_schedule=self.trading_schedule,
|
||||
treasury_curves=self.env.treasury_curves,
|
||||
benchmark_returns=self.env.benchmark_returns)
|
||||
|
||||
self.assertEqual([round(x.benchmark_period_returns, 3)
|
||||
for x in metrics.month_periods],
|
||||
@@ -412,7 +409,10 @@ class TestRisk(unittest.TestCase):
|
||||
|
||||
def test_trading_days_08(self):
|
||||
returns = factory.create_returns_from_range(self.sim_params08)
|
||||
metrics = risk.RiskReport(returns, self.sim_params08, env=self.env)
|
||||
metrics = risk.RiskReport(returns, self.sim_params08,
|
||||
trading_schedule=self.trading_schedule,
|
||||
treasury_curves=self.env.treasury_curves,
|
||||
benchmark_returns=self.env.benchmark_returns)
|
||||
self.assertEqual([x.num_trading_days for x in metrics.year_periods],
|
||||
[253])
|
||||
|
||||
@@ -421,7 +421,10 @@ class TestRisk(unittest.TestCase):
|
||||
|
||||
def test_benchmark_volatility_08(self):
|
||||
returns = factory.create_returns_from_range(self.sim_params08)
|
||||
metrics = risk.RiskReport(returns, self.sim_params08, env=self.env)
|
||||
metrics = risk.RiskReport(returns, self.sim_params08,
|
||||
trading_schedule=self.trading_schedule,
|
||||
treasury_curves=self.env.treasury_curves,
|
||||
benchmark_returns=self.env.benchmark_returns)
|
||||
|
||||
self.assertEqual([round(x.benchmark_volatility, 3)
|
||||
for x in metrics.month_periods],
|
||||
@@ -469,7 +472,10 @@ class TestRisk(unittest.TestCase):
|
||||
|
||||
def test_treasury_returns_06(self):
|
||||
returns = factory.create_returns_from_range(self.sim_params)
|
||||
metrics = risk.RiskReport(returns, self.sim_params, env=self.env)
|
||||
metrics = risk.RiskReport(returns, self.sim_params,
|
||||
trading_schedule=self.trading_schedule,
|
||||
treasury_curves=self.env.treasury_curves,
|
||||
benchmark_returns=self.env.benchmark_returns)
|
||||
self.assertEqual([round(x.treasury_period_return, 4)
|
||||
for x in metrics.month_periods],
|
||||
[0.0037,
|
||||
@@ -533,12 +539,15 @@ class TestRisk(unittest.TestCase):
|
||||
sim_params90s = SimulationParameters(
|
||||
period_start=start,
|
||||
period_end=end,
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
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, env=self.env)
|
||||
metrics = risk.RiskReport(returns, sim_params90s,
|
||||
trading_schedule=self.trading_schedule,
|
||||
treasury_curves=self.env.treasury_curves,
|
||||
benchmark_returns=self.env.benchmark_returns)
|
||||
total_months = 60
|
||||
self.check_metrics(metrics, total_months, start)
|
||||
|
||||
@@ -546,10 +555,13 @@ class TestRisk(unittest.TestCase):
|
||||
sim_params = SimulationParameters(
|
||||
period_start=start_date,
|
||||
period_end=start_date.replace(year=(start_date.year + years)),
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
returns = factory.create_returns_from_range(sim_params)
|
||||
metrics = risk.RiskReport(returns, self.sim_params, env=self.env)
|
||||
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)
|
||||
|
||||
@@ -636,7 +648,8 @@ class TestRisk(unittest.TestCase):
|
||||
self.algo_returns_06,
|
||||
self.sim_params,
|
||||
benchmark_returns=benchmark_returns,
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
treasury_curves=self.env.treasury_curves,
|
||||
)
|
||||
for risk_period in chain.from_iterable(itervalues(report.to_dict())):
|
||||
self.assertIsNone(risk_period['beta'])
|
||||
|
||||
+69
-54
@@ -97,6 +97,7 @@ from zipline.testing.fixtures import (
|
||||
WithSimParams,
|
||||
WithTradingEnvironment,
|
||||
WithTmpDir,
|
||||
WithTradingSchedule,
|
||||
ZiplineTestCase,
|
||||
)
|
||||
from zipline.test_algorithms import (
|
||||
@@ -166,7 +167,6 @@ from zipline.utils.control_flow import nullctx
|
||||
import zipline.utils.events
|
||||
from zipline.utils.events import date_rules, time_rules, Always
|
||||
import zipline.utils.factory as factory
|
||||
from zipline.utils.tradingcalendar import trading_day, trading_days
|
||||
|
||||
# Because test cases appear to reuse some resources.
|
||||
|
||||
@@ -751,7 +751,7 @@ class TestTransformAlgorithm(WithLogger,
|
||||
[100, 100, 100, 300],
|
||||
timedelta(days=1),
|
||||
cls.sim_params,
|
||||
cls.env
|
||||
cls.trading_schedule,
|
||||
) for sid in cls.sids
|
||||
},
|
||||
index=cls.sim_params.trading_days,
|
||||
@@ -826,7 +826,7 @@ def before_trading_start(context, data):
|
||||
self.sim_params.data_frequency = 'daily'
|
||||
|
||||
sim_params = factory.create_simulation_parameters(
|
||||
num_days=4, env=self.env, data_frequency='daily')
|
||||
num_days=4, data_frequency='daily')
|
||||
|
||||
algo = TestRegisterTransformAlgorithm(
|
||||
sim_params=sim_params,
|
||||
@@ -835,7 +835,7 @@ def before_trading_start(context, data):
|
||||
self.assertEqual(algo.sim_params.data_frequency, 'daily')
|
||||
|
||||
sim_params = factory.create_simulation_parameters(
|
||||
num_days=4, env=self.env, data_frequency='minute')
|
||||
num_days=4, data_frequency='minute')
|
||||
|
||||
algo = TestRegisterTransformAlgorithm(
|
||||
sim_params=sim_params,
|
||||
@@ -953,14 +953,15 @@ def before_trading_start(context, data):
|
||||
period_end=period_end,
|
||||
capital_base=float("1.0e5"),
|
||||
data_frequency='minute',
|
||||
env=env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
data_portal = create_data_portal(
|
||||
env,
|
||||
env.asset_finder,
|
||||
tempdir,
|
||||
sim_params,
|
||||
equities.index,
|
||||
self.trading_schedule,
|
||||
)
|
||||
algo = algo_class(sim_params=sim_params, env=env)
|
||||
algo.run(data_portal)
|
||||
@@ -1022,10 +1023,11 @@ class TestBeforeTradingStart(WithDataPortal,
|
||||
|
||||
@classmethod
|
||||
def make_minute_bar_data(cls):
|
||||
asset_minutes = cls.env.minutes_for_days_in_range(
|
||||
cls.data_start,
|
||||
cls.END_DATE,
|
||||
)
|
||||
asset_minutes = \
|
||||
cls.trading_schedule.execution_minutes_for_days_in_range(
|
||||
cls.data_start,
|
||||
cls.END_DATE,
|
||||
)
|
||||
minutes_count = len(asset_minutes)
|
||||
minutes_arr = np.arange(minutes_count) + 1
|
||||
split_data = pd.DataFrame(
|
||||
@@ -1041,13 +1043,13 @@ 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.env,
|
||||
cls.trading_schedule,
|
||||
cls.data_start,
|
||||
cls.sim_params.period_end,
|
||||
)
|
||||
|
||||
yield 2, create_minute_df_for_asset(
|
||||
cls.env,
|
||||
cls.trading_schedule,
|
||||
cls.data_start,
|
||||
cls.sim_params.period_end,
|
||||
50,
|
||||
@@ -1068,7 +1070,7 @@ class TestBeforeTradingStart(WithDataPortal,
|
||||
def make_daily_bar_data(cls):
|
||||
for sid in cls.ASSET_FINDER_EQUITY_SIDS:
|
||||
yield sid, create_daily_df_for_asset(
|
||||
cls.env,
|
||||
cls.trading_schedule,
|
||||
cls.data_start,
|
||||
cls.sim_params.period_end,
|
||||
)
|
||||
@@ -1355,6 +1357,7 @@ class TestAlgoScript(WithLogger,
|
||||
ZiplineTestCase):
|
||||
START_DATE = pd.Timestamp('2006-01-03', tz='utc')
|
||||
END_DATE = pd.Timestamp('2006-12-31', tz='utc')
|
||||
DATA_PORTAL_USE_MINUTE_DATA = False
|
||||
BCOLZ_DAILY_BAR_LOOKBACK_DAYS = 5 # max history window length
|
||||
|
||||
ARG_TYPE_TEST_CASES = (
|
||||
@@ -1396,7 +1399,7 @@ class TestAlgoScript(WithLogger,
|
||||
|
||||
@classmethod
|
||||
def make_daily_bar_data(cls):
|
||||
days = len(cls.env.days_in_range(cls.START_DATE, cls.END_DATE))
|
||||
days = len(cls.bcolz_daily_bar_days)
|
||||
return trades_by_sid_to_dfs(
|
||||
{
|
||||
0: factory.create_trade_history(
|
||||
@@ -1405,16 +1408,16 @@ class TestAlgoScript(WithLogger,
|
||||
[100] * days,
|
||||
timedelta(days=1),
|
||||
cls.sim_params,
|
||||
cls.env),
|
||||
cls.trading_schedule),
|
||||
3: factory.create_trade_history(
|
||||
3,
|
||||
[10.0] * days,
|
||||
[100] * days,
|
||||
timedelta(days=1),
|
||||
cls.sim_params,
|
||||
cls.env)
|
||||
cls.trading_schedule)
|
||||
},
|
||||
index=cls.sim_params.trading_days,
|
||||
index=cls.bcolz_daily_bar_days,
|
||||
)
|
||||
|
||||
def test_noop(self):
|
||||
@@ -1551,9 +1554,10 @@ def handle_data(context, data):
|
||||
env=self.env,
|
||||
)
|
||||
trades = factory.create_daily_trade_source(
|
||||
[0], self.sim_params, self.env)
|
||||
[0], self.sim_params, self.env, self.trading_schedule)
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env, tempdir, self.sim_params, {0: trades})
|
||||
self.env.asset_finder, self.trading_schedule, tempdir,
|
||||
self.sim_params, {0: trades})
|
||||
results = test_algo.run(data_portal)
|
||||
|
||||
all_txns = [
|
||||
@@ -1640,7 +1644,7 @@ def handle_data(context, data):
|
||||
params = SimulationParameters(
|
||||
period_start=pd.Timestamp("2007-01-03", tz='UTC'),
|
||||
period_end=pd.Timestamp("2007-01-05", tz='UTC'),
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
# order method shouldn't blow up
|
||||
@@ -1940,7 +1944,7 @@ class TestCapitalChanges(WithLogger,
|
||||
|
||||
@classmethod
|
||||
def make_minute_bar_data(cls):
|
||||
minutes = cls.env.minutes_for_days_in_range(
|
||||
minutes = cls.trading_schedule.execution_minutes_for_days_in_range(
|
||||
pd.Timestamp('2006-01-03', tz='UTC'),
|
||||
pd.Timestamp('2006-01-09', tz='UTC')
|
||||
)
|
||||
@@ -1952,14 +1956,14 @@ class TestCapitalChanges(WithLogger,
|
||||
[10000] * len(minutes),
|
||||
timedelta(minutes=1),
|
||||
cls.sim_params,
|
||||
cls.env),
|
||||
cls.trading_schedule),
|
||||
},
|
||||
index=pd.DatetimeIndex(minutes),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def make_daily_bar_data(cls):
|
||||
days = cls.env.days_in_range(
|
||||
days = cls.trading_schedule.execution_days_in_range(
|
||||
pd.Timestamp('2006-01-03', tz='UTC'),
|
||||
pd.Timestamp('2006-01-09', tz='UTC')
|
||||
)
|
||||
@@ -1971,7 +1975,7 @@ class TestCapitalChanges(WithLogger,
|
||||
[10000] * len(days),
|
||||
timedelta(days=1),
|
||||
cls.sim_params,
|
||||
cls.env),
|
||||
cls.trading_schedule),
|
||||
},
|
||||
index=pd.DatetimeIndex(days),
|
||||
)
|
||||
@@ -2719,15 +2723,15 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
|
||||
sim_params = factory.create_simulation_parameters(
|
||||
start=start,
|
||||
num_days=4,
|
||||
env=env,
|
||||
data_frequency='minute',
|
||||
)
|
||||
|
||||
data_portal = create_data_portal(
|
||||
env,
|
||||
env.asset_finder,
|
||||
tempdir,
|
||||
sim_params,
|
||||
[1]
|
||||
[1],
|
||||
self.trading_schedule,
|
||||
)
|
||||
|
||||
def handle_data(algo, data):
|
||||
@@ -2845,10 +2849,11 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
|
||||
env=env,
|
||||
)
|
||||
data_portal = create_data_portal(
|
||||
env,
|
||||
env.asset_finder,
|
||||
tempdir,
|
||||
self.sim_params,
|
||||
[0]
|
||||
[0],
|
||||
self.trading_schedule,
|
||||
)
|
||||
algo.run(data_portal)
|
||||
|
||||
@@ -2859,10 +2864,11 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
|
||||
with TempDirectory() as tempdir, \
|
||||
tmp_trading_env(equities=metadata) as env:
|
||||
data_portal = create_data_portal(
|
||||
env,
|
||||
env.asset_finder,
|
||||
tempdir,
|
||||
self.sim_params,
|
||||
[0]
|
||||
[0],
|
||||
self.trading_schedule,
|
||||
)
|
||||
algo = SetAssetDateBoundsAlgorithm(
|
||||
sim_params=self.sim_params,
|
||||
@@ -2878,10 +2884,11 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
|
||||
with TempDirectory() as tempdir, \
|
||||
tmp_trading_env(equities=metadata) as env:
|
||||
data_portal = create_data_portal(
|
||||
env,
|
||||
env.asset_finder,
|
||||
tempdir,
|
||||
self.sim_params,
|
||||
[0]
|
||||
[0],
|
||||
self.trading_schedule,
|
||||
)
|
||||
algo = SetAssetDateBoundsAlgorithm(
|
||||
sim_params=self.sim_params,
|
||||
@@ -2907,7 +2914,7 @@ class TestAccountControls(WithDataPortal, WithSimParams, ZiplineTestCase):
|
||||
[100, 100, 100, 300],
|
||||
timedelta(days=1),
|
||||
cls.sim_params,
|
||||
cls.env,
|
||||
cls.trading_schedule,
|
||||
),
|
||||
},
|
||||
index=cls.sim_params.trading_days,
|
||||
@@ -3054,7 +3061,7 @@ class TestFutureFlip(WithSimParams, WithDataPortal, ZiplineTestCase):
|
||||
[1e9, 1e9, 1e9],
|
||||
timedelta(days=1),
|
||||
cls.sim_params,
|
||||
cls.env
|
||||
cls.trading_schedule,
|
||||
),
|
||||
},
|
||||
index=cls.sim_params.trading_days,
|
||||
@@ -3064,7 +3071,7 @@ class TestFutureFlip(WithSimParams, WithDataPortal, ZiplineTestCase):
|
||||
def test_flip_algo(self):
|
||||
metadata = {1: {'symbol': 'TEST',
|
||||
'start_date': self.sim_params.trading_days[0],
|
||||
'end_date': self.env.next_trading_day(
|
||||
'end_date': self.trading_schedule.next_execution_day(
|
||||
self.sim_params.trading_days[-1]),
|
||||
'multiplier': 5}}
|
||||
|
||||
@@ -3164,10 +3171,11 @@ class TestOrderCancelation(WithDataPortal,
|
||||
|
||||
@classmethod
|
||||
def make_minute_bar_data(cls):
|
||||
asset_minutes = cls.env.minutes_for_days_in_range(
|
||||
cls.sim_params.period_start,
|
||||
cls.sim_params.period_end,
|
||||
)
|
||||
asset_minutes = \
|
||||
cls.trading_schedule.execution_minutes_for_days_in_range(
|
||||
cls.sim_params.period_start,
|
||||
cls.sim_params.period_end,
|
||||
)
|
||||
|
||||
minutes_count = len(asset_minutes)
|
||||
minutes_arr = np.arange(1, 1 + minutes_count)
|
||||
@@ -3206,7 +3214,7 @@ class TestOrderCancelation(WithDataPortal,
|
||||
sim_params=SimulationParameters(
|
||||
period_start=self.sim_params.period_start,
|
||||
period_end=self.sim_params.period_end,
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
data_frequency=data_frequency,
|
||||
emission_rate='minute' if minute_emission else 'daily'
|
||||
)
|
||||
@@ -3388,7 +3396,7 @@ class TestRemoveData(TestCase):
|
||||
self.assertEqual(algo.data_lengths, self.live_asset_counts)
|
||||
|
||||
|
||||
class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
|
||||
class TestEquityAutoClose(WithTmpDir, WithTradingSchedule, ZiplineTestCase):
|
||||
"""
|
||||
Tests if delisted equities are properly removed from a portfolio holding
|
||||
positions in said equities.
|
||||
@@ -3396,6 +3404,7 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
|
||||
@classmethod
|
||||
def init_class_fixtures(cls):
|
||||
super(TestEquityAutoClose, cls).init_class_fixtures()
|
||||
trading_days = cls.trading_schedule.all_execution_days
|
||||
start_date = pd.Timestamp('2015-01-05', tz='UTC')
|
||||
start_date_loc = trading_days.get_loc(start_date)
|
||||
test_duration = 7
|
||||
@@ -3411,7 +3420,7 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
|
||||
num_assets=3,
|
||||
start_date=self.test_days[0],
|
||||
first_end=self.first_asset_expiration,
|
||||
frequency=trading_day,
|
||||
frequency=self.trading_schedule.day,
|
||||
periods_between_ends=2,
|
||||
auto_close_delta=auto_close_delta,
|
||||
)
|
||||
@@ -3419,8 +3428,12 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
|
||||
sids = asset_info.index
|
||||
|
||||
env = self.enter_instance_context(tmp_trading_env(equities=asset_info))
|
||||
market_opens = env.open_and_closes.market_open.loc[self.test_days]
|
||||
market_closes = env.open_and_closes.market_close.loc[self.test_days]
|
||||
market_opens = self.trading_schedule.schedule.market_open.loc[
|
||||
self.test_days
|
||||
]
|
||||
market_closes = self.trading_schedule.schedule.market_close.loc[
|
||||
self.test_days
|
||||
]
|
||||
|
||||
if frequency == 'daily':
|
||||
dates = self.test_days
|
||||
@@ -3441,12 +3454,12 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
|
||||
)
|
||||
reader = BcolzDailyBarReader(path)
|
||||
data_portal = DataPortal(
|
||||
env,
|
||||
env.asset_finder, self.trading_schedule,
|
||||
first_trading_day=reader.first_trading_day,
|
||||
equity_daily_reader=reader,
|
||||
)
|
||||
elif frequency == 'minute':
|
||||
dates = env.minutes_for_days_in_range(
|
||||
dates = self.trading_schedule.execution_minutes_for_days_in_range(
|
||||
self.test_days[0],
|
||||
self.test_days[-1],
|
||||
)
|
||||
@@ -3471,7 +3484,7 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
|
||||
)
|
||||
reader = BcolzMinuteBarReader(self.tmpdir.path)
|
||||
data_portal = DataPortal(
|
||||
env,
|
||||
env.asset_finder, self.trading_schedule,
|
||||
first_trading_day=reader.first_trading_day,
|
||||
equity_minute_reader=reader,
|
||||
)
|
||||
@@ -3485,7 +3498,6 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
|
||||
end=self.test_days[-1],
|
||||
data_frequency=frequency,
|
||||
emission_rate=frequency,
|
||||
env=env,
|
||||
capital_base=capital_base,
|
||||
)
|
||||
|
||||
@@ -3498,7 +3510,7 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
|
||||
else:
|
||||
final_prices = {
|
||||
asset.sid: trade_data_by_sid[asset.sid].loc[
|
||||
env.get_open_and_close(asset.end_date)[1]
|
||||
self.trading_schedule.start_and_end(asset.end_date)[1]
|
||||
].close
|
||||
for asset in assets
|
||||
}
|
||||
@@ -3570,7 +3582,7 @@ class TestEquityAutoClose(WithTmpDir, 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 = trading_day * auto_close_lag
|
||||
auto_close_delta = self.trading_schedule.day * auto_close_lag
|
||||
resources = self.make_data(auto_close_delta, 'daily', capital_base)
|
||||
|
||||
assets = resources.assets
|
||||
@@ -3730,7 +3742,7 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
|
||||
canceled. Unless an equity is auto closed, any open orders for that
|
||||
equity will persist indefinitely.
|
||||
"""
|
||||
auto_close_delta = trading_day
|
||||
auto_close_delta = self.trading_schedule.day
|
||||
resources = self.make_data(auto_close_delta, 'daily')
|
||||
env = resources.env
|
||||
assets = resources.assets
|
||||
@@ -3802,7 +3814,7 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
|
||||
)
|
||||
|
||||
def test_minutely_delisted_equities(self):
|
||||
resources = self.make_data(trading_day, 'minute')
|
||||
resources = self.make_data(self.trading_schedule.day, 'minute')
|
||||
|
||||
env = resources.env
|
||||
assets = resources.assets
|
||||
@@ -3852,6 +3864,9 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
|
||||
expected_cash.extend([after_second_auto_close] * (390 + 390))
|
||||
expected_position_counts.extend([1] * (390 + 390))
|
||||
|
||||
# Check list lengths first to avoid expensive comparison
|
||||
self.assertEqual(len(algo.cash), len(expected_cash))
|
||||
# TODO find more efficient way to compare these lists
|
||||
self.assertEqual(algo.cash, expected_cash)
|
||||
self.assertEqual(
|
||||
list(output['ending_cash']),
|
||||
@@ -3987,7 +4002,7 @@ class TestOrderAfterDelist(WithTradingEnvironment, ZiplineTestCase):
|
||||
sim_params=SimulationParameters(
|
||||
period_start=pd.Timestamp("2016-01-06", tz='UTC'),
|
||||
period_end=pd.Timestamp("2016-01-07", tz='UTC'),
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
data_frequency="minute"
|
||||
)
|
||||
)
|
||||
|
||||
@@ -124,7 +124,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
|
||||
def make_minute_bar_data(cls):
|
||||
for sid in cls.sids:
|
||||
yield sid, create_minute_df_for_asset(
|
||||
cls.env,
|
||||
cls.trading_schedule,
|
||||
cls.SIM_PARAMS_START,
|
||||
cls.SIM_PARAMS_END,
|
||||
)
|
||||
@@ -133,7 +133,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
|
||||
def make_daily_bar_data(cls):
|
||||
for sid in cls.sids:
|
||||
yield sid, create_daily_df_for_asset(
|
||||
cls.env,
|
||||
cls.trading_schedule,
|
||||
cls.SIM_PARAMS_START,
|
||||
cls.SIM_PARAMS_END,
|
||||
)
|
||||
@@ -179,10 +179,10 @@ 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.env.market_minutes_for_day(
|
||||
test_start_minute = self.trading_schedule.execution_minutes_for_day(
|
||||
self.sim_params.trading_days[0]
|
||||
)[1]
|
||||
test_end_minute = self.env.market_minutes_for_day(
|
||||
test_end_minute = self.trading_schedule.execution_minutes_for_day(
|
||||
self.sim_params.trading_days[0]
|
||||
)[-1]
|
||||
bar_data = BarData(
|
||||
@@ -260,7 +260,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
|
||||
period_start=test_start_minute,
|
||||
period_end=test_end_minute,
|
||||
data_frequency="minute",
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
history_algorithm = self.create_algo(
|
||||
@@ -381,7 +381,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
|
||||
capital_base=self.sim_params.capital_base,
|
||||
data_frequency=self.sim_params.data_frequency,
|
||||
emission_rate=self.sim_params.emission_rate,
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
algo = self.create_algo(history_algo,
|
||||
@@ -424,7 +424,7 @@ class TestAPIShim(WithDataPortal, WithSimParams, ZiplineTestCase):
|
||||
period_start=self.sim_params.trading_days[8],
|
||||
period_end=self.sim_params.trading_days[-1],
|
||||
data_frequency="minute",
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
algo = self.create_algo(simple_transforms_algo,
|
||||
|
||||
@@ -82,8 +82,8 @@ from zipline.testing.predicates import assert_equal
|
||||
from zipline.testing.fixtures import (
|
||||
WithAssetFinder,
|
||||
ZiplineTestCase,
|
||||
WithTradingSchedule,
|
||||
)
|
||||
from zipline.utils.tradingcalendar import trading_day
|
||||
|
||||
|
||||
@contextmanager
|
||||
@@ -396,7 +396,7 @@ class TestFuture(WithAssetFinder, ZiplineTestCase):
|
||||
TestFuture.asset_finder.lookup_future_symbol('XXX99')
|
||||
|
||||
|
||||
class AssetFinderTestCase(ZiplineTestCase):
|
||||
class AssetFinderTestCase(WithTradingSchedule, ZiplineTestCase):
|
||||
asset_finder_type = AssetFinder
|
||||
|
||||
def write_assets(self, **kwargs):
|
||||
@@ -776,6 +776,7 @@ class AssetFinderTestCase(ZiplineTestCase):
|
||||
|
||||
def test_compute_lifetimes(self):
|
||||
num_assets = 4
|
||||
trading_day = self.trading_schedule.day
|
||||
first_start = pd.Timestamp('2015-04-01', tz='UTC')
|
||||
|
||||
frame = make_rotating_equity_info(
|
||||
|
||||
+32
-23
@@ -110,21 +110,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.env,
|
||||
cls.trading_schedule,
|
||||
cls.bcolz_minute_bar_days[0],
|
||||
cls.bcolz_minute_bar_days[-1],
|
||||
)
|
||||
|
||||
for sid in (2, cls.ILLIQUID_SPLIT_ASSET_SID):
|
||||
yield sid, create_minute_df_for_asset(
|
||||
cls.env,
|
||||
cls.trading_schedule,
|
||||
cls.bcolz_minute_bar_days[0],
|
||||
cls.bcolz_minute_bar_days[-1],
|
||||
10,
|
||||
)
|
||||
|
||||
yield cls.HILARIOUSLY_ILLIQUID_ASSET_SID, create_minute_df_for_asset(
|
||||
cls.env,
|
||||
cls.trading_schedule,
|
||||
cls.bcolz_minute_bar_days[0],
|
||||
cls.bcolz_minute_bar_days[-1],
|
||||
50,
|
||||
@@ -165,8 +165,10 @@ class TestMinuteBarData(WithBarDataChecks,
|
||||
|
||||
def test_minute_before_assets_trading(self):
|
||||
# grab minutes that include the day before the asset start
|
||||
minutes = self.env.market_minutes_for_day(
|
||||
self.env.previous_trading_day(self.bcolz_minute_bar_days[0])
|
||||
minutes = self.trading_schedule.execution_minutes_for_day(
|
||||
self.trading_schedule.previous_execution_day(
|
||||
self.bcolz_minute_bar_days[0]
|
||||
)
|
||||
)
|
||||
|
||||
# this entire day is before either asset has started trading
|
||||
@@ -192,8 +194,8 @@ class TestMinuteBarData(WithBarDataChecks,
|
||||
self.assertTrue(asset_value is pd.NaT)
|
||||
|
||||
def test_regular_minute(self):
|
||||
minutes = self.env.market_minutes_for_day(
|
||||
self.bcolz_minute_bar_days[0],
|
||||
minutes = self.trading_schedule.execution_minutes_for_day(
|
||||
self.bcolz_minute_bar_days[0]
|
||||
)
|
||||
|
||||
for idx, minute in enumerate(minutes):
|
||||
@@ -284,7 +286,7 @@ class TestMinuteBarData(WithBarDataChecks,
|
||||
asset2_value)
|
||||
|
||||
def test_minute_of_last_day(self):
|
||||
minutes = self.env.market_minutes_for_day(
|
||||
minutes = self.trading_schedule.execution_minutes_for_day(
|
||||
self.bcolz_daily_bar_days[-1],
|
||||
)
|
||||
|
||||
@@ -296,12 +298,15 @@ class TestMinuteBarData(WithBarDataChecks,
|
||||
self.assertTrue(bar_data.can_trade(self.ASSET2))
|
||||
|
||||
def test_minute_after_assets_stopped(self):
|
||||
minutes = self.env.market_minutes_for_day(
|
||||
self.env.next_trading_day(self.bcolz_minute_bar_days[-1])
|
||||
minutes = self.trading_schedule.execution_minutes_for_day(
|
||||
self.trading_schedule.next_execution_day(
|
||||
self.bcolz_minute_bar_days[-1]
|
||||
)
|
||||
)
|
||||
|
||||
last_trading_minute = \
|
||||
self.env.market_minutes_for_day(self.bcolz_minute_bar_days[-1])[-1]
|
||||
last_trading_minute = self.trading_schedule.execution_minutes_for_day(
|
||||
self.bcolz_minute_bar_days[-1]
|
||||
)[-1]
|
||||
|
||||
# this entire day is after both assets have stopped trading
|
||||
for idx, minute in enumerate(minutes):
|
||||
@@ -341,9 +346,9 @@ class TestMinuteBarData(WithBarDataChecks,
|
||||
)
|
||||
|
||||
# ... but that's it's not applied when using spot value
|
||||
minutes = self.env.minutes_for_days_in_range(
|
||||
minutes = self.trading_schedule.execution_minutes_for_days_in_range(
|
||||
start=self.bcolz_minute_bar_days[0],
|
||||
end=self.bcolz_minute_bar_days[1],
|
||||
end=self.bcolz_minute_bar_days[1]
|
||||
)
|
||||
|
||||
for idx, minute in enumerate(minutes):
|
||||
@@ -356,11 +361,11 @@ 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.env.market_minutes_for_day(
|
||||
self.bcolz_minute_bar_days[0],
|
||||
day0_minutes = self.trading_schedule.execution_minutes_for_day(
|
||||
self.bcolz_minute_bar_days[0]
|
||||
)
|
||||
day1_minutes = self.env.market_minutes_for_day(
|
||||
self.bcolz_minute_bar_days[1],
|
||||
day1_minutes = self.trading_schedule.execution_minutes_for_day(
|
||||
self.bcolz_minute_bar_days[1]
|
||||
)
|
||||
|
||||
for idx, minute in enumerate(day0_minutes[-10:-1]):
|
||||
@@ -433,8 +438,8 @@ 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.env.next_trading_day(
|
||||
self.bcolz_minute_bar_days[-1],
|
||||
the_day_after = self.trading_schedule.next_execution_day(
|
||||
self.bcolz_minute_bar_days[-1]
|
||||
)
|
||||
|
||||
bar_data = BarData(self.data_portal, lambda: the_day_after, "minute")
|
||||
@@ -604,7 +609,7 @@ class TestDailyBarData(WithBarDataChecks,
|
||||
def make_daily_bar_data(cls):
|
||||
for sid in cls.sids:
|
||||
yield sid, create_daily_df_for_asset(
|
||||
cls.env,
|
||||
cls.trading_schedule,
|
||||
cls.bcolz_daily_bar_days[0],
|
||||
cls.bcolz_daily_bar_days[-1],
|
||||
interval=2 - sid % 2
|
||||
@@ -638,7 +643,9 @@ class TestDailyBarData(WithBarDataChecks,
|
||||
|
||||
def test_day_before_assets_trading(self):
|
||||
# use the day before self.bcolz_daily_bar_days[0]
|
||||
day = self.env.previous_trading_day(self.bcolz_daily_bar_days[0])
|
||||
day = self.trading_schedule.previous_execution_day(
|
||||
self.bcolz_daily_bar_days[0]
|
||||
)
|
||||
|
||||
bar_data = BarData(self.data_portal, lambda: day, "daily")
|
||||
self.check_internal_consistency(bar_data)
|
||||
@@ -741,7 +748,9 @@ 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.env.next_trading_day(self.bcolz_daily_bar_days[-1])
|
||||
next_day = self.trading_schedule.next_execution_day(
|
||||
self.bcolz_daily_bar_days[-1]
|
||||
)
|
||||
|
||||
bar_data = BarData(self.data_portal, lambda: next_day, "daily")
|
||||
self.check_internal_consistency(bar_data)
|
||||
|
||||
+13
-7
@@ -30,11 +30,13 @@ from zipline.testing import (
|
||||
from zipline.testing.fixtures import (
|
||||
WithDataPortal,
|
||||
WithSimParams,
|
||||
WithTradingSchedule,
|
||||
ZiplineTestCase,
|
||||
)
|
||||
|
||||
|
||||
class TestBenchmark(WithDataPortal, WithSimParams, ZiplineTestCase):
|
||||
class TestBenchmark(WithDataPortal, WithSimParams, WithTradingSchedule,
|
||||
ZiplineTestCase):
|
||||
START_DATE = pd.Timestamp('2006-01-03', tz='utc')
|
||||
END_DATE = pd.Timestamp('2006-12-29', tz='utc')
|
||||
|
||||
@@ -85,7 +87,7 @@ class TestBenchmark(WithDataPortal, WithSimParams, ZiplineTestCase):
|
||||
days_to_use = self.sim_params.trading_days[1:]
|
||||
|
||||
source = BenchmarkSource(
|
||||
1, self.env, days_to_use, self.data_portal
|
||||
1, self.env, self.trading_schedule, days_to_use, self.data_portal
|
||||
)
|
||||
|
||||
# should be the equivalent of getting the price history, then doing
|
||||
@@ -111,6 +113,7 @@ class TestBenchmark(WithDataPortal, WithSimParams, ZiplineTestCase):
|
||||
BenchmarkSource(
|
||||
3,
|
||||
self.env,
|
||||
self.trading_schedule,
|
||||
self.sim_params.trading_days[1:],
|
||||
self.data_portal
|
||||
)
|
||||
@@ -125,6 +128,7 @@ class TestBenchmark(WithDataPortal, WithSimParams, ZiplineTestCase):
|
||||
BenchmarkSource(
|
||||
3,
|
||||
self.env,
|
||||
self.trading_schedule,
|
||||
self.sim_params.trading_days[120:],
|
||||
self.data_portal
|
||||
)
|
||||
@@ -138,19 +142,19 @@ class TestBenchmark(WithDataPortal, WithSimParams, ZiplineTestCase):
|
||||
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.env.minutes_for_days_in_range(
|
||||
minutes = self.trading_schedule.execution_minutes_for_days_in_range(
|
||||
self.sim_params.trading_days[0],
|
||||
self.sim_params.trading_days[5]
|
||||
)
|
||||
|
||||
tmp_reader = tmp_bcolz_minute_bar_reader(
|
||||
self.env,
|
||||
self.env.trading_days,
|
||||
self.trading_schedule,
|
||||
self.trading_schedule.all_execution_days,
|
||||
create_minute_bar_data(minutes, [2]),
|
||||
)
|
||||
with tmp_reader as reader:
|
||||
data_portal = DataPortal(
|
||||
self.env,
|
||||
self.env.asset_finder, self.trading_schedule,
|
||||
first_trading_day=reader.first_trading_day,
|
||||
equity_minute_reader=reader,
|
||||
equity_daily_reader=self.bcolz_daily_bar_reader,
|
||||
@@ -160,6 +164,7 @@ class TestBenchmark(WithDataPortal, WithSimParams, ZiplineTestCase):
|
||||
source = BenchmarkSource(
|
||||
2,
|
||||
self.env,
|
||||
self.trading_schedule,
|
||||
self.sim_params.trading_days,
|
||||
data_portal
|
||||
)
|
||||
@@ -188,7 +193,8 @@ class TestBenchmark(WithDataPortal, WithSimParams, ZiplineTestCase):
|
||||
|
||||
with self.assertRaises(InvalidBenchmarkAsset) as exc:
|
||||
BenchmarkSource(
|
||||
4, self.env, self.sim_params.trading_days, self.data_portal
|
||||
4, self.env, self.trading_schedule,
|
||||
self.sim_params.trading_days, self.data_portal
|
||||
)
|
||||
|
||||
self.assertEqual("4 cannot be used as the benchmark because it has a "
|
||||
|
||||
@@ -141,7 +141,7 @@ class CommissionAlgorithmTests(WithDataPortal, WithSimParams, ZiplineTestCase):
|
||||
[100.0] * num_days,
|
||||
timedelta(days=1),
|
||||
cls.sim_params,
|
||||
cls.env,
|
||||
trading_schedule=cls.trading_schedule,
|
||||
),
|
||||
},
|
||||
index=cls.sim_params.trading_days,
|
||||
|
||||
@@ -26,7 +26,9 @@ class TestDataPortal(WithTradingEnvironment, ZiplineTestCase):
|
||||
def init_instance_fixtures(self):
|
||||
super(TestDataPortal, self).init_instance_fixtures()
|
||||
|
||||
self.data_portal = DataPortal(self.env, first_trading_day=None)
|
||||
self.data_portal = DataPortal(self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
first_trading_day=None)
|
||||
|
||||
def test_bar_count_for_simple_transforms(self):
|
||||
# July 2015
|
||||
@@ -40,8 +42,8 @@ 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.env.get_open_and_close(
|
||||
pd.Timestamp("2015-07-09")
|
||||
july_9_dt = self.trading_schedule.start_and_end(
|
||||
pd.Timestamp("2015-07-09", tz='UTC')
|
||||
)[0] + Timedelta("30 minutes")
|
||||
|
||||
self.assertEqual(
|
||||
@@ -63,8 +65,8 @@ 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.env.get_open_and_close(
|
||||
pd.Timestamp("2015-11-30")
|
||||
nov_30_dt = self.trading_schedule.start_and_end(
|
||||
pd.Timestamp("2015-11-30", tz='UTC')
|
||||
)[0] + Timedelta("30 minutes")
|
||||
|
||||
self.assertEqual(
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
#
|
||||
# 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))
|
||||
@@ -108,7 +108,8 @@ class FetcherTestCase(WithResponses,
|
||||
data_frequency=data_frequency
|
||||
)
|
||||
|
||||
results = test_algo.run(FetcherDataPortal(self.env))
|
||||
results = test_algo.run(FetcherDataPortal(self.env,
|
||||
self.trading_schedule))
|
||||
|
||||
return results
|
||||
|
||||
@@ -141,7 +142,8 @@ def handle_data(context, data):
|
||||
# manually setting data portal and getting generator because we need
|
||||
# the minutely emission packets here. TradingAlgorithm.run() only
|
||||
# returns daily packets.
|
||||
test_algo.data_portal = FetcherDataPortal(self.env)
|
||||
test_algo.data_portal = FetcherDataPortal(self.env,
|
||||
self.trading_schedule)
|
||||
gen = test_algo.get_generator()
|
||||
perf_packets = list(gen)
|
||||
|
||||
|
||||
+13
-148
@@ -30,7 +30,6 @@ from testfixtures import TempDirectory
|
||||
from zipline.assets.synthetic import make_simple_equity_info
|
||||
from zipline.finance.blotter import Blotter
|
||||
from zipline.finance.execution import MarketOrder, LimitOrder
|
||||
from zipline.finance.trading import TradingEnvironment
|
||||
from zipline.finance.performance import PerformanceTracker
|
||||
from zipline.finance.trading import SimulationParameters
|
||||
from zipline.data.us_equity_pricing import BcolzDailyBarReader
|
||||
@@ -199,7 +198,7 @@ class FinanceTestCase(WithLogger,
|
||||
data_frequency="minute"
|
||||
)
|
||||
|
||||
minutes = env.market_minute_window(
|
||||
minutes = self.trading_schedule.execution_minute_window(
|
||||
sim_params.first_open,
|
||||
int((trade_interval.total_seconds() / 60) * trade_count)
|
||||
+ 100)
|
||||
@@ -217,8 +216,9 @@ class FinanceTestCase(WithLogger,
|
||||
}
|
||||
|
||||
write_bcolz_minute_data(
|
||||
env,
|
||||
env.days_in_range(minutes[0], minutes[-1]),
|
||||
self.trading_schedule,
|
||||
self.trading_schedule.execution_days_in_range(minutes[0],
|
||||
minutes[-1]),
|
||||
tempdir.path,
|
||||
iteritems(assets),
|
||||
)
|
||||
@@ -226,7 +226,7 @@ class FinanceTestCase(WithLogger,
|
||||
equity_minute_reader = BcolzMinuteBarReader(tempdir.path)
|
||||
|
||||
data_portal = DataPortal(
|
||||
env,
|
||||
env.asset_finder, self.trading_schedule,
|
||||
first_trading_day=equity_minute_reader.first_trading_day,
|
||||
equity_minute_reader=equity_minute_reader,
|
||||
)
|
||||
@@ -254,7 +254,7 @@ class FinanceTestCase(WithLogger,
|
||||
equity_daily_reader = BcolzDailyBarReader(path)
|
||||
|
||||
data_portal = DataPortal(
|
||||
env,
|
||||
env.asset_finder, self.trading_schedule,
|
||||
first_trading_day=equity_daily_reader.first_trading_day,
|
||||
equity_daily_reader=equity_daily_reader,
|
||||
)
|
||||
@@ -275,7 +275,8 @@ class FinanceTestCase(WithLogger,
|
||||
else:
|
||||
alternator = 1
|
||||
|
||||
tracker = PerformanceTracker(sim_params, self.env)
|
||||
tracker = PerformanceTracker(sim_params, self.trading_schedule,
|
||||
self.env)
|
||||
|
||||
# replicate what tradesim does by going through every minute or day
|
||||
# of the simulation and processing open orders each time
|
||||
@@ -388,54 +389,16 @@ class TradingEnvironmentTestCase(WithLogger,
|
||||
"""
|
||||
Tests for date management utilities in zipline.finance.trading.
|
||||
"""
|
||||
@timed(DEFAULT_TIMEOUT)
|
||||
def test_is_trading_day(self):
|
||||
# holidays taken from: http://www.nyse.com/press/1191407641943.html
|
||||
new_years = datetime(2008, 1, 1, tzinfo=pytz.utc)
|
||||
mlk_day = datetime(2008, 1, 21, tzinfo=pytz.utc)
|
||||
presidents = datetime(2008, 2, 18, tzinfo=pytz.utc)
|
||||
good_friday = datetime(2008, 3, 21, tzinfo=pytz.utc)
|
||||
memorial_day = datetime(2008, 5, 26, tzinfo=pytz.utc)
|
||||
july_4th = datetime(2008, 7, 4, tzinfo=pytz.utc)
|
||||
labor_day = datetime(2008, 9, 1, tzinfo=pytz.utc)
|
||||
tgiving = datetime(2008, 11, 27, tzinfo=pytz.utc)
|
||||
christmas = datetime(2008, 5, 25, tzinfo=pytz.utc)
|
||||
a_saturday = datetime(2008, 8, 2, tzinfo=pytz.utc)
|
||||
a_sunday = datetime(2008, 10, 12, tzinfo=pytz.utc)
|
||||
holidays = [
|
||||
new_years,
|
||||
mlk_day,
|
||||
presidents,
|
||||
good_friday,
|
||||
memorial_day,
|
||||
july_4th,
|
||||
labor_day,
|
||||
tgiving,
|
||||
christmas,
|
||||
a_saturday,
|
||||
a_sunday
|
||||
]
|
||||
|
||||
for holiday in holidays:
|
||||
self.assertTrue(not self.env.is_trading_day(holiday))
|
||||
|
||||
first_trading_day = datetime(2008, 1, 2, tzinfo=pytz.utc)
|
||||
last_trading_day = datetime(2008, 12, 31, tzinfo=pytz.utc)
|
||||
workdays = [first_trading_day, last_trading_day]
|
||||
|
||||
for workday in workdays:
|
||||
self.assertTrue(self.env.is_trading_day(workday))
|
||||
|
||||
def test_simulation_parameters(self):
|
||||
env = SimulationParameters(
|
||||
sp = SimulationParameters(
|
||||
period_start=datetime(2008, 1, 1, tzinfo=pytz.utc),
|
||||
period_end=datetime(2008, 12, 31, tzinfo=pytz.utc),
|
||||
capital_base=100000,
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
self.assertTrue(env.last_close.month == 12)
|
||||
self.assertTrue(env.last_close.day == 31)
|
||||
self.assertTrue(sp.last_close.month == 12)
|
||||
self.assertTrue(sp.last_close.day == 31)
|
||||
|
||||
@timed(DEFAULT_TIMEOUT)
|
||||
def test_sim_params_days_in_period(self):
|
||||
@@ -452,7 +415,7 @@ class TradingEnvironmentTestCase(WithLogger,
|
||||
period_start=datetime(2007, 12, 31, tzinfo=pytz.utc),
|
||||
period_end=datetime(2008, 1, 7, tzinfo=pytz.utc),
|
||||
capital_base=100000,
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
expected_trading_days = (
|
||||
@@ -471,101 +434,3 @@ class TradingEnvironmentTestCase(WithLogger,
|
||||
self.assertEquals(num_expected_trading_days, params.days_in_period)
|
||||
np.testing.assert_array_equal(expected_trading_days,
|
||||
params.trading_days.tolist())
|
||||
|
||||
@timed(DEFAULT_TIMEOUT)
|
||||
def test_market_minute_window(self):
|
||||
|
||||
# January 2008
|
||||
# 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
|
||||
|
||||
us_east = pytz.timezone('US/Eastern')
|
||||
utc = pytz.utc
|
||||
|
||||
# 10:01 AM Eastern on January 7th..
|
||||
start = us_east.localize(datetime(2008, 1, 7, 10, 1))
|
||||
utc_start = start.astimezone(utc)
|
||||
|
||||
# Get the next 10 minutes
|
||||
minutes = self.env.market_minute_window(
|
||||
utc_start, 10,
|
||||
)
|
||||
self.assertEqual(len(minutes), 10)
|
||||
for i in range(10):
|
||||
self.assertEqual(minutes[i], utc_start + timedelta(minutes=i))
|
||||
|
||||
# Get the previous 10 minutes.
|
||||
minutes = self.env.market_minute_window(
|
||||
utc_start, 10, step=-1,
|
||||
)
|
||||
self.assertEqual(len(minutes), 10)
|
||||
for i in range(10):
|
||||
self.assertEqual(minutes[i], utc_start + timedelta(minutes=-i))
|
||||
|
||||
# Get the next 900 minutes, including utc_start, rolling over into the
|
||||
# next two days.
|
||||
# Should include:
|
||||
# Today: 10:01 AM -> 4:00 PM (360 minutes)
|
||||
# Tomorrow: 9:31 AM -> 4:00 PM (390 minutes, 750 total)
|
||||
# Last Day: 9:31 AM -> 12:00 PM (150 minutes, 900 total)
|
||||
minutes = self.env.market_minute_window(
|
||||
utc_start, 900,
|
||||
)
|
||||
today = self.env.market_minutes_for_day(start)[30:]
|
||||
tomorrow = self.env.market_minutes_for_day(
|
||||
start + timedelta(days=1)
|
||||
)
|
||||
last_day = self.env.market_minutes_for_day(
|
||||
start + timedelta(days=2))[:150]
|
||||
|
||||
self.assertEqual(len(minutes), 900)
|
||||
self.assertEqual(minutes[0], utc_start)
|
||||
self.assertTrue(all(today == minutes[:360]))
|
||||
self.assertTrue(all(tomorrow == minutes[360:750]))
|
||||
self.assertTrue(all(last_day == minutes[750:]))
|
||||
|
||||
# Get the previous 801 minutes, including utc_start, rolling over into
|
||||
# Friday the 4th and Thursday the 3rd.
|
||||
# Should include:
|
||||
# Today: 10:01 AM -> 9:31 AM (31 minutes)
|
||||
# Friday: 4:00 PM -> 9:31 AM (390 minutes, 421 total)
|
||||
# Thursday: 4:00 PM -> 9:41 AM (380 minutes, 801 total)
|
||||
minutes = self.env.market_minute_window(
|
||||
utc_start, 801, step=-1,
|
||||
)
|
||||
|
||||
today = self.env.market_minutes_for_day(start)[30::-1]
|
||||
# minus an extra two days from each of these to account for the two
|
||||
# weekend days we skipped
|
||||
friday = self.env.market_minutes_for_day(
|
||||
start + timedelta(days=-3),
|
||||
)[::-1]
|
||||
thursday = self.env.market_minutes_for_day(
|
||||
start + timedelta(days=-4),
|
||||
)[:9:-1]
|
||||
|
||||
self.assertEqual(len(minutes), 801)
|
||||
self.assertEqual(minutes[0], utc_start)
|
||||
self.assertTrue(all(today == minutes[:31]))
|
||||
self.assertTrue(all(friday == minutes[31:421]))
|
||||
self.assertTrue(all(thursday == minutes[421:]))
|
||||
|
||||
def test_min_date(self):
|
||||
min_date = pd.Timestamp('2016-03-04', tz='UTC')
|
||||
env = TradingEnvironment(min_date=min_date)
|
||||
|
||||
self.assertGreaterEqual(env.first_trading_day, min_date)
|
||||
self.assertGreaterEqual(env.treasury_curves.index[0],
|
||||
min_date)
|
||||
|
||||
def test_max_date(self):
|
||||
max_date = pd.Timestamp('2008-08-01', tz='UTC')
|
||||
env = TradingEnvironment(max_date=max_date)
|
||||
|
||||
self.assertLessEqual(env.last_trading_day, max_date)
|
||||
self.assertLessEqual(env.treasury_curves.index[-1],
|
||||
max_date)
|
||||
|
||||
+49
-40
@@ -27,7 +27,8 @@ from zipline.testing import (
|
||||
from zipline.testing.fixtures import (
|
||||
WithBcolzMinuteBarReader,
|
||||
WithDataPortal,
|
||||
ZiplineTestCase
|
||||
ZiplineTestCase,
|
||||
alias,
|
||||
)
|
||||
|
||||
|
||||
@@ -78,7 +79,7 @@ class WithHistory(WithDataPortal):
|
||||
@classmethod
|
||||
def init_class_fixtures(cls):
|
||||
super(WithHistory, cls).init_class_fixtures()
|
||||
cls.trading_days = cls.env.days_in_range(
|
||||
cls.trading_days = cls.trading_schedule.execution_days_in_range(
|
||||
start=cls.TRADING_START_DT,
|
||||
end=cls.TRADING_END_DT
|
||||
)
|
||||
@@ -447,6 +448,7 @@ MINUTE_FIELD_INFO = {
|
||||
class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
|
||||
|
||||
BCOLZ_DAILY_BAR_SOURCE_FROM_MINUTE = True
|
||||
DATA_PORTAL_FIRST_TRADING_DAY = alias('TRADING_START_DT')
|
||||
|
||||
@classmethod
|
||||
def make_minute_bar_data(cls):
|
||||
@@ -455,14 +457,14 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
|
||||
for sid in sids:
|
||||
asset = cls.asset_finder.retrieve_asset(sid)
|
||||
data[sid] = create_minute_df_for_asset(
|
||||
cls.env,
|
||||
cls.trading_schedule,
|
||||
asset.start_date,
|
||||
asset.end_date,
|
||||
start_val=2,
|
||||
)
|
||||
|
||||
data[1] = create_minute_df_for_asset(
|
||||
cls.env,
|
||||
cls.trading_schedule,
|
||||
pd.Timestamp('2014-01-03', tz='utc'),
|
||||
pd.Timestamp('2016-01-30', tz='utc'),
|
||||
start_val=2,
|
||||
@@ -470,9 +472,9 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
|
||||
|
||||
asset2 = cls.asset_finder.retrieve_asset(2)
|
||||
data[asset2.sid] = create_minute_df_for_asset(
|
||||
cls.env,
|
||||
cls.trading_schedule,
|
||||
asset2.start_date,
|
||||
cls.env.previous_trading_day(asset2.end_date),
|
||||
cls.trading_schedule.previous_execution_day(asset2.end_date),
|
||||
start_val=2,
|
||||
minute_blacklist=[
|
||||
pd.Timestamp('2015-01-08 14:31', tz='UTC'),
|
||||
@@ -487,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.env,
|
||||
cls.trading_schedule,
|
||||
pd.Timestamp('2015-01-05', tz='UTC'),
|
||||
pd.Timestamp('2015-01-05', tz='UTC'),
|
||||
start_val=8000),
|
||||
create_minute_df_for_asset(
|
||||
cls.env,
|
||||
cls.trading_schedule,
|
||||
pd.Timestamp('2015-01-06', tz='UTC'),
|
||||
pd.Timestamp('2015-01-06', tz='UTC'),
|
||||
start_val=2000),
|
||||
create_minute_df_for_asset(
|
||||
cls.env,
|
||||
cls.trading_schedule,
|
||||
pd.Timestamp('2015-01-07', tz='UTC'),
|
||||
pd.Timestamp('2015-01-07', tz='UTC'),
|
||||
start_val=1000),
|
||||
create_minute_df_for_asset(
|
||||
cls.env,
|
||||
cls.trading_schedule,
|
||||
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.env,
|
||||
cls.trading_schedule,
|
||||
asset3.start_date,
|
||||
asset3.end_date,
|
||||
start_val=2,
|
||||
@@ -539,7 +541,7 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
|
||||
capital_base=float('1.0e5'),
|
||||
data_frequency='minute',
|
||||
emission_rate='daily',
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
test_algo = TradingAlgorithm(
|
||||
@@ -562,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.env.get_open_and_close(jan5)[1],
|
||||
self.trading_schedule.start_and_end(jan5)[1],
|
||||
2,
|
||||
'1d',
|
||||
'close'
|
||||
@@ -623,7 +625,7 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
|
||||
# before any of the dividends
|
||||
window1 = self.data_portal.get_history_window(
|
||||
[asset],
|
||||
self.env.get_open_and_close(jan5)[1],
|
||||
self.trading_schedule.start_and_end(jan5)[1],
|
||||
2,
|
||||
'1d',
|
||||
'close'
|
||||
@@ -678,8 +680,10 @@ 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.env.market_minutes_for_day(
|
||||
self.env.previous_trading_day(pd.Timestamp('2015-01-05', tz='UTC'))
|
||||
minutes = self.trading_schedule.execution_minutes_for_day(
|
||||
self.trading_schedule.previous_execution_day(pd.Timestamp(
|
||||
'2015-01-05', tz='UTC'
|
||||
))
|
||||
)[0:60]
|
||||
|
||||
for idx, minute in enumerate(minutes):
|
||||
@@ -726,7 +730,7 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
|
||||
# 10 minutes
|
||||
asset = self.env.asset_finder.retrieve_asset(sid)
|
||||
|
||||
minutes = self.env.market_minutes_for_day(
|
||||
minutes = self.trading_schedule.execution_minutes_for_day(
|
||||
pd.Timestamp('2015-01-05', tz='UTC')
|
||||
)[0:60]
|
||||
|
||||
@@ -737,7 +741,9 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
|
||||
|
||||
def test_minute_midnight(self):
|
||||
midnight = pd.Timestamp('2015-01-06', tz='UTC')
|
||||
last_minute = self.env.previous_open_and_close(midnight)[1]
|
||||
last_minute = self.trading_schedule.start_and_end(
|
||||
self.trading_schedule.previous_execution_day(midnight)
|
||||
)[1]
|
||||
|
||||
midnight_bar_data = \
|
||||
BarData(self.data_portal, lambda: midnight, 'minute')
|
||||
@@ -755,7 +761,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.env.market_minutes_for_day(
|
||||
minutes = self.trading_schedule.execution_minutes_for_day(
|
||||
pd.Timestamp('2015-01-07', tz='UTC')
|
||||
)[0:60]
|
||||
|
||||
@@ -850,7 +856,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.env.get_open_and_close(jan5)[1],
|
||||
self.trading_schedule.start_and_end(jan5)[1],
|
||||
10,
|
||||
'1m',
|
||||
'close'
|
||||
@@ -1099,20 +1105,21 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
|
||||
|
||||
def test_minute_different_lifetimes(self):
|
||||
# at trading start, only asset1 existed
|
||||
day = self.env.next_trading_day(self.TRADING_START_DT)
|
||||
day = self.trading_schedule.next_execution_day(self.TRADING_START_DT)
|
||||
|
||||
asset1_minutes = self.env.minutes_for_days_in_range(
|
||||
start=self.ASSET1.start_date,
|
||||
end=self.ASSET1.end_date
|
||||
)
|
||||
asset1_minutes = \
|
||||
self.trading_schedule.execution_minutes_for_days_in_range(
|
||||
start=self.ASSET1.start_date,
|
||||
end=self.ASSET1.end_date
|
||||
)
|
||||
|
||||
asset1_idx = asset1_minutes.searchsorted(
|
||||
self.env.get_open_and_close(day)[0]
|
||||
self.trading_schedule.start_and_end(day)[0]
|
||||
)
|
||||
|
||||
window = self.data_portal.get_history_window(
|
||||
[self.ASSET1, self.ASSET2],
|
||||
self.env.get_open_and_close(day)[0],
|
||||
self.trading_schedule.start_and_end(day)[0],
|
||||
100,
|
||||
'1m',
|
||||
'close'
|
||||
@@ -1130,7 +1137,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.env.market_minutes_for_day(
|
||||
first_day_minutes = self.trading_schedule.execution_minutes_for_day(
|
||||
self.TRADING_START_DT
|
||||
)
|
||||
exp_msg = (
|
||||
@@ -1150,7 +1157,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.env.market_minutes_for_day(day)
|
||||
minutes = self.trading_schedule.execution_minutes_for_day(day)
|
||||
|
||||
# minute data, baseline:
|
||||
# Jan 5: 2 to 391
|
||||
@@ -1214,7 +1221,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.env.market_minutes_for_day(day)
|
||||
minutes = self.trading_schedule.execution_minutes_for_day(day)
|
||||
|
||||
# minute data, baseline:
|
||||
# Jan 5: 2 to 391
|
||||
@@ -1333,7 +1340,8 @@ class DailyEquityHistoryTestCase(WithHistory, ZiplineTestCase):
|
||||
@classmethod
|
||||
def create_df_for_asset(cls, start_day, end_day, interval=1,
|
||||
force_zeroes=False):
|
||||
days = cls.env.days_in_range(start_day, end_day)
|
||||
days = cls.trading_schedule.execution_days_in_range(start_day,
|
||||
end_day)
|
||||
days_count = len(days)
|
||||
|
||||
# default to 2 because the low array subtracts 1, and we don't
|
||||
@@ -1362,7 +1370,7 @@ class DailyEquityHistoryTestCase(WithHistory, ZiplineTestCase):
|
||||
def test_daily_before_assets_trading(self):
|
||||
# asset2 and asset3 both started trading in 2015
|
||||
|
||||
days = self.env.days_in_range(
|
||||
days = self.trading_schedule.execution_days_in_range(
|
||||
start=pd.Timestamp('2014-12-15', tz='UTC'),
|
||||
end=pd.Timestamp('2014-12-18', tz='UTC'),
|
||||
)
|
||||
@@ -1400,9 +1408,9 @@ class DailyEquityHistoryTestCase(WithHistory, ZiplineTestCase):
|
||||
# get the first 30 days of 2015
|
||||
jan5 = pd.Timestamp('2015-01-04')
|
||||
|
||||
days = self.env.days_in_range(
|
||||
days = self.trading_schedule.execution_days_in_range(
|
||||
start=jan5,
|
||||
end=self.env.add_trading_days(30, jan5)
|
||||
end=self.trading_schedule.add_execution_days(30, jan5)
|
||||
)
|
||||
|
||||
for idx, day in enumerate(days):
|
||||
@@ -1445,7 +1453,7 @@ class DailyEquityHistoryTestCase(WithHistory, ZiplineTestCase):
|
||||
def test_daily_after_asset_stopped(self):
|
||||
# SHORT_ASSET trades on 1/5, 1/6, that's it.
|
||||
|
||||
days = self.env.days_in_range(
|
||||
days = self.trading_schedule.execution_days_in_range(
|
||||
start=pd.Timestamp('2015-01-07', tz='UTC'),
|
||||
end=pd.Timestamp('2015-01-08', tz='UTC')
|
||||
)
|
||||
@@ -1636,8 +1644,9 @@ 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.env.next_trading_day(self.TRADING_START_DT)
|
||||
second_day = self.trading_schedule.next_execution_day(
|
||||
self.TRADING_START_DT
|
||||
)
|
||||
|
||||
exp_msg = (
|
||||
'History window extends before 2014-01-03. To use this history '
|
||||
@@ -1663,8 +1672,8 @@ class DailyEquityHistoryTestCase(WithHistory, ZiplineTestCase):
|
||||
)[self.ASSET1]
|
||||
|
||||
# Use a minute to force minute mode.
|
||||
first_minute = self.env.open_and_closes.market_open[
|
||||
self.TRADING_START_DT]
|
||||
first_minute = \
|
||||
self.trading_schedule.schedule.market_open[self.TRADING_START_DT]
|
||||
|
||||
with self.assertRaisesRegexp(HistoryWindowStartsBeforeData, exp_msg):
|
||||
self.data_portal.get_history_window(
|
||||
@@ -1794,7 +1803,7 @@ class MinuteToDailyAggregationTestCase(WithBcolzMinuteBarReader,
|
||||
# Set up a fresh data portal for each test, since order of calling
|
||||
# needs to be tested.
|
||||
self.equity_daily_aggregator = DailyHistoryAggregator(
|
||||
self.env.open_and_closes.market_open,
|
||||
self.trading_schedule.schedule.market_open,
|
||||
self.bcolz_minute_bar_reader,
|
||||
)
|
||||
|
||||
|
||||
+95
-64
@@ -57,8 +57,10 @@ from zipline.testing.fixtures import (
|
||||
WithSimParams,
|
||||
WithTmpDir,
|
||||
WithTradingEnvironment,
|
||||
WithTradingSchedule,
|
||||
ZiplineTestCase,
|
||||
)
|
||||
from zipline.utils.calendars import default_nyse_schedule
|
||||
|
||||
logger = logging.getLogger('Test Perf Tracking')
|
||||
|
||||
@@ -175,7 +177,9 @@ def calculate_results(sim_params,
|
||||
splits = splits or {}
|
||||
commissions = commissions or {}
|
||||
|
||||
perf_tracker = perf.PerformanceTracker(sim_params, env)
|
||||
perf_tracker = perf.PerformanceTracker(sim_params,
|
||||
default_nyse_schedule,
|
||||
env)
|
||||
|
||||
results = []
|
||||
|
||||
@@ -240,7 +244,9 @@ def setup_env_data(env, sim_params, sids, futures_sids=[]):
|
||||
for sid in sids:
|
||||
data[sid] = {
|
||||
"start_date": sim_params.trading_days[0],
|
||||
"end_date": env.next_trading_day(sim_params.trading_days[-1])
|
||||
"end_date": default_nyse_schedule.next_execution_day(
|
||||
sim_params.trading_days[-1]
|
||||
)
|
||||
}
|
||||
|
||||
env.write_data(equities_data=data)
|
||||
@@ -249,7 +255,9 @@ def setup_env_data(env, sim_params, sids, futures_sids=[]):
|
||||
for future_sid in futures_sids:
|
||||
futures_data[future_sid] = {
|
||||
"start_date": sim_params.trading_days[0],
|
||||
"end_date": env.next_trading_day(sim_params.trading_days[-1]),
|
||||
"end_date": default_nyse_schedule.next_execution_day(
|
||||
sim_params.trading_days[-1]
|
||||
),
|
||||
"multiplier": 100
|
||||
}
|
||||
|
||||
@@ -271,7 +279,9 @@ class TestSplitPerformance(WithSimParams, WithTmpDir, ZiplineTestCase):
|
||||
def test_multiple_splits(self):
|
||||
# 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.env)
|
||||
perf_tracker = perf.PerformanceTracker(self.sim_params,
|
||||
self.trading_schedule,
|
||||
self.env)
|
||||
|
||||
asset1 = self.asset_finder.retrieve_asset(1)
|
||||
asset2 = self.asset_finder.retrieve_asset(2)
|
||||
@@ -300,13 +310,14 @@ class TestSplitPerformance(WithSimParams, WithTmpDir, ZiplineTestCase):
|
||||
[100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
# 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,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.tmpdir,
|
||||
self.sim_params,
|
||||
{1: events},
|
||||
@@ -411,7 +422,7 @@ class TestDividendPerformance(WithSimParams,
|
||||
after = factory.get_next_trading_dt(
|
||||
before,
|
||||
timedelta(days=1),
|
||||
self.env,
|
||||
self.trading_schedule,
|
||||
)
|
||||
self.assertEqual(after.hour, 13)
|
||||
|
||||
@@ -423,7 +434,7 @@ class TestDividendPerformance(WithSimParams,
|
||||
[100, 100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
|
||||
@@ -431,7 +442,7 @@ class TestDividendPerformance(WithSimParams,
|
||||
writer = SQLiteAdjustmentWriter(
|
||||
dbpath,
|
||||
MockDailyBarReader(),
|
||||
self.env.trading_days,
|
||||
self.trading_schedule.all_execution_days,
|
||||
)
|
||||
splits = mergers = create_empty_splits_mergers_frame()
|
||||
dividends = pd.DataFrame({
|
||||
@@ -445,7 +456,8 @@ class TestDividendPerformance(WithSimParams,
|
||||
writer.write(splits, mergers, dividends)
|
||||
adjustment_reader = SQLiteAdjustmentReader(dbpath)
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{1: events},
|
||||
@@ -488,7 +500,7 @@ class TestDividendPerformance(WithSimParams,
|
||||
[100, 100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
|
||||
@@ -496,7 +508,7 @@ class TestDividendPerformance(WithSimParams,
|
||||
writer = SQLiteAdjustmentWriter(
|
||||
dbpath,
|
||||
MockDailyBarReader(),
|
||||
self.env.trading_days,
|
||||
self.trading_schedule.all_execution_days,
|
||||
)
|
||||
splits = mergers = create_empty_splits_mergers_frame()
|
||||
dividends = pd.DataFrame({
|
||||
@@ -521,7 +533,8 @@ class TestDividendPerformance(WithSimParams,
|
||||
adjustment_reader = SQLiteAdjustmentReader(dbpath)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
events,
|
||||
@@ -562,7 +575,7 @@ class TestDividendPerformance(WithSimParams,
|
||||
[100, 100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
|
||||
@@ -570,7 +583,7 @@ class TestDividendPerformance(WithSimParams,
|
||||
writer = SQLiteAdjustmentWriter(
|
||||
dbpath,
|
||||
MockDailyBarReader(),
|
||||
self.env.trading_days,
|
||||
self.trading_schedule.all_execution_days,
|
||||
)
|
||||
splits = mergers = create_empty_splits_mergers_frame()
|
||||
dividends = pd.DataFrame({
|
||||
@@ -585,7 +598,8 @@ class TestDividendPerformance(WithSimParams,
|
||||
adjustment_reader = SQLiteAdjustmentReader(dbpath)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{1: events},
|
||||
@@ -623,7 +637,7 @@ class TestDividendPerformance(WithSimParams,
|
||||
[100, 100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
|
||||
@@ -631,7 +645,7 @@ class TestDividendPerformance(WithSimParams,
|
||||
writer = SQLiteAdjustmentWriter(
|
||||
dbpath,
|
||||
MockDailyBarReader(),
|
||||
self.env.trading_days,
|
||||
self.trading_schedule.all_execution_days,
|
||||
)
|
||||
splits = mergers = create_empty_splits_mergers_frame()
|
||||
dividends = pd.DataFrame({
|
||||
@@ -646,7 +660,8 @@ class TestDividendPerformance(WithSimParams,
|
||||
adjustment_reader = SQLiteAdjustmentReader(dbpath)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{1: events},
|
||||
@@ -685,14 +700,14 @@ class TestDividendPerformance(WithSimParams,
|
||||
[100, 100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
|
||||
|
||||
writer = SQLiteAdjustmentWriter(
|
||||
dbpath,
|
||||
MockDailyBarReader(),
|
||||
self.env.trading_days,
|
||||
self.trading_schedule.all_execution_days,
|
||||
)
|
||||
splits = mergers = create_empty_splits_mergers_frame()
|
||||
|
||||
@@ -708,7 +723,8 @@ class TestDividendPerformance(WithSimParams,
|
||||
adjustment_reader = SQLiteAdjustmentReader(dbpath)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{1: events},
|
||||
@@ -745,20 +761,21 @@ class TestDividendPerformance(WithSimParams,
|
||||
[100, 100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
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.env)
|
||||
pay_date = factory.get_next_trading_dt(pay_date, oneday,
|
||||
self.trading_schedule)
|
||||
|
||||
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
|
||||
|
||||
writer = SQLiteAdjustmentWriter(
|
||||
dbpath,
|
||||
MockDailyBarReader(),
|
||||
self.env.trading_days,
|
||||
self.trading_schedule.all_execution_days,
|
||||
)
|
||||
splits = mergers = create_empty_splits_mergers_frame()
|
||||
dividends = pd.DataFrame({
|
||||
@@ -773,7 +790,8 @@ class TestDividendPerformance(WithSimParams,
|
||||
adjustment_reader = SQLiteAdjustmentReader(dbpath)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{1: events},
|
||||
@@ -811,7 +829,7 @@ class TestDividendPerformance(WithSimParams,
|
||||
[100, 100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
|
||||
@@ -819,7 +837,7 @@ class TestDividendPerformance(WithSimParams,
|
||||
writer = SQLiteAdjustmentWriter(
|
||||
dbpath,
|
||||
MockDailyBarReader(),
|
||||
self.env.trading_days,
|
||||
self.trading_schedule.all_execution_days,
|
||||
)
|
||||
splits = mergers = create_empty_splits_mergers_frame()
|
||||
dividends = pd.DataFrame({
|
||||
@@ -834,7 +852,8 @@ class TestDividendPerformance(WithSimParams,
|
||||
adjustment_reader = SQLiteAdjustmentReader(dbpath)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{1: events},
|
||||
@@ -869,7 +888,7 @@ class TestDividendPerformance(WithSimParams,
|
||||
[100, 100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
|
||||
@@ -877,7 +896,7 @@ class TestDividendPerformance(WithSimParams,
|
||||
writer = SQLiteAdjustmentWriter(
|
||||
dbpath,
|
||||
MockDailyBarReader(),
|
||||
self.env.trading_days,
|
||||
self.trading_schedule.all_execution_days,
|
||||
)
|
||||
splits = mergers = create_empty_splits_mergers_frame()
|
||||
dividends = pd.DataFrame({
|
||||
@@ -892,7 +911,8 @@ class TestDividendPerformance(WithSimParams,
|
||||
adjustment_reader = SQLiteAdjustmentReader(dbpath)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{1: events},
|
||||
@@ -925,7 +945,7 @@ class TestDividendPerformance(WithSimParams,
|
||||
[100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
|
||||
@@ -933,7 +953,7 @@ class TestDividendPerformance(WithSimParams,
|
||||
writer = SQLiteAdjustmentWriter(
|
||||
dbpath,
|
||||
MockDailyBarReader(),
|
||||
self.env.trading_days,
|
||||
self.trading_schedule.all_execution_days,
|
||||
)
|
||||
splits = mergers = create_empty_splits_mergers_frame()
|
||||
dividends = pd.DataFrame({
|
||||
@@ -942,8 +962,9 @@ class TestDividendPerformance(WithSimParams,
|
||||
'declared_date': np.array([events[-3].dt], dtype='datetime64[ns]'),
|
||||
'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.env.next_trading_day(events[-1].dt)],
|
||||
dtype='datetime64[ns]'),
|
||||
'pay_date': np.array(
|
||||
[self.trading_schedule.next_execution_day(events[-1].dt)],
|
||||
dtype='datetime64[ns]'),
|
||||
})
|
||||
writer.write(splits, mergers, dividends)
|
||||
adjustment_reader = SQLiteAdjustmentReader(dbpath)
|
||||
@@ -957,10 +978,11 @@ class TestDividendPerformance(WithSimParams,
|
||||
)
|
||||
|
||||
sim_params.period_end = events[-1].dt
|
||||
sim_params.update_internal_from_env(self.env)
|
||||
sim_params.update_internal_from_trading_schedule(self.trading_schedule)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
sim_params,
|
||||
{1: events},
|
||||
@@ -1000,7 +1022,8 @@ class TestDividendPerformanceHolidayStyle(TestDividendPerformance):
|
||||
END_DATE = pd.Timestamp('2003-12-08', tz='utc')
|
||||
|
||||
|
||||
class TestPositionPerformance(WithInstanceTmpDir, ZiplineTestCase):
|
||||
class TestPositionPerformance(WithInstanceTmpDir, WithTradingSchedule,
|
||||
ZiplineTestCase):
|
||||
def create_environment_stuff(self,
|
||||
num_days=4,
|
||||
sids=[1, 2],
|
||||
@@ -1049,7 +1072,7 @@ class TestPositionPerformance(WithInstanceTmpDir, ZiplineTestCase):
|
||||
[100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
trades_2 = factory.create_trade_history(
|
||||
@@ -1058,11 +1081,12 @@ class TestPositionPerformance(WithInstanceTmpDir, ZiplineTestCase):
|
||||
[100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{1: trades_1, 2: trades_2}
|
||||
@@ -1154,11 +1178,12 @@ class TestPositionPerformance(WithInstanceTmpDir, ZiplineTestCase):
|
||||
[100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{1: trades})
|
||||
@@ -1245,11 +1270,12 @@ class TestPositionPerformance(WithInstanceTmpDir, ZiplineTestCase):
|
||||
[100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{1: trades})
|
||||
@@ -1360,13 +1386,14 @@ single short-sale transaction"""
|
||||
[100, 100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
trades_1 = trades[:-2]
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{1: trades})
|
||||
@@ -1593,11 +1620,12 @@ cost of sole txn in test"
|
||||
[100, 100, 100, 100],
|
||||
oneday,
|
||||
sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{3: trades}
|
||||
@@ -1712,11 +1740,12 @@ single short-sale transaction"""
|
||||
[100, 100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{3: trades}
|
||||
@@ -1956,11 +1985,12 @@ trade after cover"""
|
||||
[100, 100, 100, 100, 100, 100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{1: trades})
|
||||
@@ -2042,13 +2072,14 @@ shares in position"
|
||||
[100, 100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
self.env
|
||||
self.trading_schedule,
|
||||
)
|
||||
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,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{1: trades})
|
||||
@@ -2167,7 +2198,7 @@ shares in position"
|
||||
[200, -100, -100, 100, -300, 100, 500, 400],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
self.env
|
||||
self.trading_schedule,
|
||||
)
|
||||
cost_bases = [10, 10, 0, 8, 9, 9, 13, 13.5]
|
||||
|
||||
@@ -2203,11 +2234,12 @@ shares in position"
|
||||
[100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{1: trades})
|
||||
@@ -2247,11 +2279,12 @@ shares in position"
|
||||
[100, 100, 100, 100],
|
||||
oneday,
|
||||
self.sim_params,
|
||||
env=self.env
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
self.instance_tmpdir,
|
||||
self.sim_params,
|
||||
{1: trades})
|
||||
@@ -2318,9 +2351,7 @@ class TestPositionTracker(WithTradingEnvironment,
|
||||
Originally this bug was due to np.dot([], []) returning
|
||||
np.bool_(False)
|
||||
"""
|
||||
sim_params = factory.create_simulation_parameters(
|
||||
num_days=4, env=self.env
|
||||
)
|
||||
sim_params = factory.create_simulation_parameters(num_days=4)
|
||||
|
||||
pt = perf.PositionTracker(self.env.asset_finder,
|
||||
sim_params.data_frequency)
|
||||
|
||||
+19
-12
@@ -12,7 +12,11 @@ from zipline.testing import (
|
||||
tmp_trading_env,
|
||||
tmp_dir,
|
||||
)
|
||||
from zipline.testing.fixtures import WithLogger, ZiplineTestCase
|
||||
from zipline.testing.fixtures import (
|
||||
WithLogger,
|
||||
WithTradingSchedule,
|
||||
ZiplineTestCase,
|
||||
)
|
||||
from zipline.utils import factory
|
||||
from zipline.utils.security_list import (
|
||||
SecurityListSet,
|
||||
@@ -63,7 +67,7 @@ class IterateRLAlgo(TradingAlgorithm):
|
||||
self.found = True
|
||||
|
||||
|
||||
class SecurityListTestCase(WithLogger, ZiplineTestCase):
|
||||
class SecurityListTestCase(WithLogger, WithTradingSchedule, ZiplineTestCase):
|
||||
|
||||
@classmethod
|
||||
def init_class_fixtures(cls):
|
||||
@@ -87,7 +91,7 @@ class SecurityListTestCase(WithLogger, ZiplineTestCase):
|
||||
cls.sim_params = factory.create_simulation_parameters(
|
||||
start=start,
|
||||
num_days=4,
|
||||
env=cls.env
|
||||
trading_schedule=cls.trading_schedule
|
||||
)
|
||||
|
||||
cls.sim_params2 = sp2 = factory.create_simulation_parameters(
|
||||
@@ -106,17 +110,19 @@ class SecurityListTestCase(WithLogger, ZiplineTestCase):
|
||||
cls.tempdir2 = cls.enter_class_context(tmp_dir())
|
||||
|
||||
cls.data_portal = create_data_portal(
|
||||
env=cls.env,
|
||||
asset_finder=cls.env.asset_finder,
|
||||
tempdir=cls.tempdir,
|
||||
sim_params=cls.sim_params,
|
||||
sids=range(0, 5),
|
||||
trading_schedule=cls.trading_schedule,
|
||||
)
|
||||
|
||||
cls.data_portal2 = create_data_portal(
|
||||
env=cls.env2,
|
||||
asset_finder=cls.env2.asset_finder,
|
||||
tempdir=cls.tempdir2,
|
||||
sim_params=cls.sim_params2,
|
||||
sids=range(0, 5)
|
||||
sids=range(0, 5),
|
||||
trading_schedule=cls.trading_schedule,
|
||||
)
|
||||
|
||||
def test_iterate_over_restricted_list(self):
|
||||
@@ -212,14 +218,14 @@ class SecurityListTestCase(WithLogger, 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,
|
||||
env=self.env)
|
||||
LEVERAGED_ETFS.keys())[0] + timedelta(days=7), num_days=5)
|
||||
|
||||
data_portal = create_data_portal(
|
||||
self.env,
|
||||
self.env.asset_finder,
|
||||
self.tempdir,
|
||||
sim_params=sim_params,
|
||||
sids=range(0, 5)
|
||||
sids=range(0, 5),
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
algo = RestrictedAlgoWithoutCheck(symbol='BZQ',
|
||||
@@ -267,10 +273,11 @@ class SecurityListTestCase(WithLogger, ZiplineTestCase):
|
||||
add_security_data([], ['BZQ'])
|
||||
|
||||
data_portal = create_data_portal(
|
||||
env,
|
||||
env.asset_finder,
|
||||
new_tempdir,
|
||||
sim_params,
|
||||
range(0, 5)
|
||||
range(0, 5),
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
algo = RestrictedAlgoWithoutCheck(
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
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
|
||||
)
|
||||
@@ -1,265 +0,0 @@
|
||||
#
|
||||
# Copyright 2013 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 unittest import TestCase
|
||||
from zipline.utils import tradingcalendar
|
||||
from zipline.utils import tradingcalendar_lse
|
||||
from zipline.utils import tradingcalendar_tse
|
||||
from zipline.utils import tradingcalendar_bmf
|
||||
import pytz
|
||||
import datetime
|
||||
from zipline.finance.trading import TradingEnvironment
|
||||
from nose.tools import nottest
|
||||
|
||||
|
||||
class TestTradingCalendar(TestCase):
|
||||
|
||||
def test_calendar_vs_environment(self):
|
||||
"""
|
||||
test_calendar_vs_environment checks whether the
|
||||
historical data from yahoo matches our rule based system.
|
||||
handy, if not canonical, reference:
|
||||
http://www.chronos-st.org/NYSE_Observed_Holidays-1885-Present.html
|
||||
"""
|
||||
|
||||
env = TradingEnvironment()
|
||||
bench_days = env.benchmark_returns[tradingcalendar.start:].index
|
||||
bounds = env.trading_days.slice_locs(start=tradingcalendar.start,
|
||||
end=bench_days[-1])
|
||||
env_days = env.trading_days[bounds[0]:bounds[1]]
|
||||
self.check_days(env_days, bench_days)
|
||||
|
||||
@nottest
|
||||
def test_lse_calendar_vs_environment(self):
|
||||
env = TradingEnvironment(
|
||||
bm_symbol='^FTSE',
|
||||
exchange_tz='Europe/London'
|
||||
)
|
||||
|
||||
env_start_index = \
|
||||
env.trading_days.searchsorted(tradingcalendar_lse.start)
|
||||
env_days = env.trading_days[env_start_index:]
|
||||
cal_days = tradingcalendar_lse.trading_days
|
||||
self.check_days(env_days, cal_days)
|
||||
|
||||
@nottest
|
||||
def test_tse_calendar_vs_environment(self):
|
||||
env = TradingEnvironment(
|
||||
bm_symbol='^GSPTSE',
|
||||
exchange_tz='US/Eastern'
|
||||
)
|
||||
|
||||
env_start_index = \
|
||||
env.trading_days.searchsorted(tradingcalendar_tse.start)
|
||||
env_days = env.trading_days[env_start_index:]
|
||||
cal_days = tradingcalendar_tse.trading_days
|
||||
self.check_days(env_days, cal_days)
|
||||
|
||||
@nottest
|
||||
def test_bmf_calendar_vs_environment(self):
|
||||
env = TradingEnvironment(
|
||||
bm_symbol='^BVSP',
|
||||
exchange_tz='America/Sao_Paulo'
|
||||
)
|
||||
|
||||
env_start_index = \
|
||||
env.trading_days.searchsorted(tradingcalendar_bmf.start)
|
||||
env_days = env.trading_days[env_start_index:]
|
||||
cal_days = tradingcalendar_bmf.trading_days
|
||||
self.check_days(env_days, cal_days)
|
||||
|
||||
def check_days(self, env_days, cal_days):
|
||||
diff = env_days.difference(cal_days)
|
||||
self.assertEqual(
|
||||
len(diff),
|
||||
0,
|
||||
"{diff} should be empty".format(diff=diff)
|
||||
)
|
||||
|
||||
diff2 = cal_days.difference(env_days)
|
||||
self.assertEqual(
|
||||
len(diff2),
|
||||
0,
|
||||
"{diff} should be empty".format(diff=diff2)
|
||||
)
|
||||
|
||||
def test_newyears(self):
|
||||
"""
|
||||
Check whether 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
|
||||
|
||||
day_after_new_years_sunday = datetime.datetime(
|
||||
2012, 1, 2, tzinfo=pytz.utc)
|
||||
|
||||
self.assertNotIn(day_after_new_years_sunday,
|
||||
tradingcalendar.trading_days,
|
||||
"""
|
||||
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.datetime(
|
||||
2012, 1, 3, tzinfo=pytz.utc)
|
||||
|
||||
self.assertIn(first_trading_day_after_new_years_sunday,
|
||||
tradingcalendar.trading_days,
|
||||
"""
|
||||
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.datetime(
|
||||
2013, 1, 1, tzinfo=pytz.utc)
|
||||
|
||||
self.assertNotIn(new_years_day,
|
||||
tradingcalendar.trading_days,
|
||||
"""
|
||||
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.datetime(
|
||||
2013, 1, 2, tzinfo=pytz.utc)
|
||||
|
||||
self.assertIn(first_trading_day_after_new_years,
|
||||
tradingcalendar.trading_days,
|
||||
"""
|
||||
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
|
||||
thanksgiving_with_four_weeks = datetime.datetime(
|
||||
2005, 11, 24, tzinfo=pytz.utc)
|
||||
|
||||
self.assertNotIn(thanksgiving_with_four_weeks,
|
||||
tradingcalendar.trading_days,
|
||||
"""
|
||||
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.datetime(
|
||||
2006, 11, 23, tzinfo=pytz.utc)
|
||||
|
||||
self.assertNotIn(thanksgiving_with_five_weeks,
|
||||
tradingcalendar.trading_days,
|
||||
"""
|
||||
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.datetime(
|
||||
2012, 1, 3, tzinfo=pytz.utc)
|
||||
|
||||
self.assertIn(first_trading_day_after_new_years_sunday,
|
||||
tradingcalendar.trading_days,
|
||||
"""
|
||||
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):
|
||||
early_closes = tradingcalendar.get_early_closes(
|
||||
tradingcalendar.start,
|
||||
tradingcalendar.end.replace(year=tradingcalendar.end.year + 1)
|
||||
)
|
||||
|
||||
# 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 = datetime.datetime(2012, 11, 23, tzinfo=pytz.utc)
|
||||
self.assertIn(fourth_friday, early_closes)
|
||||
|
||||
# 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 = datetime.datetime(2013, 11, 29, tzinfo=pytz.utc)
|
||||
self.assertIn(fifth_friday, early_closes)
|
||||
|
||||
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.
|
||||
"""
|
||||
early_closes = tradingcalendar.get_early_closes(
|
||||
tradingcalendar.start,
|
||||
tradingcalendar.end.replace(year=tradingcalendar.end.year + 1)
|
||||
)
|
||||
# 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 = datetime.datetime(2002, 7, 3, tzinfo=pytz.utc)
|
||||
friday_after = datetime.datetime(2002, 7, 5, tzinfo=pytz.utc)
|
||||
self.assertNotIn(wednesday_before, early_closes)
|
||||
self.assertIn(friday_after, early_closes)
|
||||
|
||||
# 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 = datetime.datetime(2013, 7, 3, tzinfo=pytz.utc)
|
||||
friday_after = datetime.datetime(2013, 7, 5, tzinfo=pytz.utc)
|
||||
self.assertIn(wednesday_before, early_closes)
|
||||
self.assertNotIn(friday_after, early_closes)
|
||||
+61
-58
@@ -12,9 +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 collections import namedtuple
|
||||
import datetime
|
||||
from functools import partial
|
||||
from inspect import isabstract
|
||||
import random
|
||||
from unittest import TestCase
|
||||
@@ -25,9 +23,9 @@ import pandas as pd
|
||||
from six import iteritems
|
||||
from six.moves import range, map
|
||||
|
||||
from zipline.finance.trading import TradingEnvironment
|
||||
from zipline.testing import subtest, parameter_space
|
||||
import zipline.utils.events
|
||||
from zipline.utils.calendars import get_calendar
|
||||
from zipline.utils.events import (
|
||||
EventRule,
|
||||
StatelessRule,
|
||||
@@ -165,7 +163,7 @@ class TestEventManager(TestCase):
|
||||
class CountingRule(Always):
|
||||
count = 0
|
||||
|
||||
def should_trigger(self, dt, env):
|
||||
def should_trigger(self, dt):
|
||||
CountingRule.count += 1
|
||||
return True
|
||||
|
||||
@@ -174,9 +172,7 @@ class TestEventManager(TestCase):
|
||||
Event(r(), lambda context, data: None)
|
||||
)
|
||||
|
||||
mock_algo_class = namedtuple('FakeAlgo', ['trading_environment'])
|
||||
mock_algo = mock_algo_class(trading_environment="fake_env")
|
||||
self.em.handle_data(mock_algo, None, datetime.datetime.now())
|
||||
self.em.handle_data(None, None, datetime.datetime.now())
|
||||
|
||||
self.assertEqual(CountingRule.count, 5)
|
||||
|
||||
@@ -188,7 +184,7 @@ class TestEventRule(TestCase):
|
||||
|
||||
def test_not_implemented(self):
|
||||
with self.assertRaises(NotImplementedError):
|
||||
super(Always, Always()).should_trigger('a', env=None)
|
||||
super(Always, Always()).should_trigger('a')
|
||||
|
||||
|
||||
def minutes_for_days(ordered_days=False):
|
||||
@@ -207,14 +203,14 @@ def minutes_for_days(ordered_days=False):
|
||||
Iterating over this yields a single day, iterating over the day yields
|
||||
the minutes for that day.
|
||||
"""
|
||||
env = TradingEnvironment()
|
||||
cal = get_calendar('NYSE')
|
||||
random.seed('deterministic')
|
||||
if ordered_days:
|
||||
# Get a list of 500 trading days, in order. As a performance
|
||||
# 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(env.trading_days), 500)
|
||||
ordered_day_list = random.sample(list(cal.all_trading_days), 500)
|
||||
ordered_day_list.sort()
|
||||
|
||||
def day_picker(day):
|
||||
@@ -223,16 +219,15 @@ def minutes_for_days(ordered_days=False):
|
||||
# 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(env.trading_days[:-1])
|
||||
return random.choice(cal.all_trading_days[:-1])
|
||||
|
||||
return ((env.market_minutes_for_day(day_picker(cnt)),)
|
||||
return ((cal.trading_minutes_for_day(day_picker(cnt)),)
|
||||
for cnt in range(500))
|
||||
|
||||
|
||||
class RuleTestCase(TestCase):
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
cls.env = TradingEnvironment()
|
||||
# On the AfterOpen and BeforeClose tests, we want ensure that the
|
||||
# functions are pure, and that running them with the same input will
|
||||
# provide the same output, regardless of whether the function is run 1
|
||||
@@ -244,9 +239,9 @@ class RuleTestCase(TestCase):
|
||||
cls.after_open = AfterOpen(hours=1, minutes=5)
|
||||
cls.class_ = None # Mark that this is the base class.
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
del cls.env
|
||||
cal = get_calendar('NYSE')
|
||||
cls.before_close.cal = cal
|
||||
cls.after_open.cal = cal
|
||||
|
||||
def test_completeness(self):
|
||||
"""
|
||||
@@ -280,32 +275,31 @@ class TestStatelessRules(RuleTestCase):
|
||||
|
||||
cls.class_ = StatelessRule
|
||||
|
||||
cls.sept_days = cls.env.days_in_range(
|
||||
cls.nyse_cal = get_calendar('NYSE')
|
||||
|
||||
cls.sept_days = cls.nyse_cal.trading_days_in_range(
|
||||
pd.Timestamp('2014-09-01'),
|
||||
pd.Timestamp('2014-09-30'),
|
||||
)
|
||||
|
||||
cls.sept_week = cls.env.minutes_for_days_in_range(
|
||||
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),
|
||||
)
|
||||
|
||||
@subtest(minutes_for_days(), 'ms')
|
||||
def test_Always(self, ms):
|
||||
should_trigger = partial(Always().should_trigger, env=self.env)
|
||||
should_trigger = Always().should_trigger
|
||||
self.assertTrue(all(map(should_trigger, ms)))
|
||||
|
||||
@subtest(minutes_for_days(), 'ms')
|
||||
def test_Never(self, ms):
|
||||
should_trigger = partial(Never().should_trigger, env=self.env)
|
||||
should_trigger = Never().should_trigger
|
||||
self.assertFalse(any(map(should_trigger, ms)))
|
||||
|
||||
@subtest(minutes_for_days(ordered_days=True), 'ms')
|
||||
def test_AfterOpen(self, ms):
|
||||
should_trigger = partial(
|
||||
self.after_open.should_trigger,
|
||||
env=self.env,
|
||||
)
|
||||
should_trigger = self.after_open.should_trigger
|
||||
for i, m in enumerate(ms):
|
||||
# Should only trigger at the 64th minute
|
||||
if i != 64:
|
||||
@@ -316,10 +310,7 @@ class TestStatelessRules(RuleTestCase):
|
||||
@subtest(minutes_for_days(ordered_days=True), 'ms')
|
||||
def test_BeforeClose(self, ms):
|
||||
ms = list(ms)
|
||||
should_trigger = partial(
|
||||
self.before_close.should_trigger,
|
||||
env=self.env
|
||||
)
|
||||
should_trigger = self.before_close.should_trigger
|
||||
for m in ms:
|
||||
# Should only trigger at the 65th-to-last minute
|
||||
if m != ms[-66]:
|
||||
@@ -329,7 +320,10 @@ class TestStatelessRules(RuleTestCase):
|
||||
|
||||
@subtest(minutes_for_days(), 'ms')
|
||||
def test_NotHalfDay(self, ms):
|
||||
should_trigger = partial(NotHalfDay().should_trigger, env=self.env)
|
||||
cal = get_calendar('NYSE')
|
||||
rule = NotHalfDay()
|
||||
rule.cal = cal
|
||||
should_trigger = rule.should_trigger
|
||||
self.assertTrue(should_trigger(FULL_DAY))
|
||||
self.assertFalse(should_trigger(HALF_DAY))
|
||||
|
||||
@@ -338,16 +332,19 @@ class TestStatelessRules(RuleTestCase):
|
||||
Test that we don't blow up when trying to call week_start's
|
||||
should_trigger on the first day of a trading environment.
|
||||
"""
|
||||
cal = get_calendar('NYSE')
|
||||
rule = NthTradingDayOfWeek(0)
|
||||
rule.cal = cal
|
||||
self.assertTrue(
|
||||
NthTradingDayOfWeek(0).should_trigger(
|
||||
self.env.trading_days[0], self.env
|
||||
)
|
||||
rule.should_trigger(self.nyse_cal.all_trading_days[0])
|
||||
)
|
||||
|
||||
@subtest(param_range(MAX_WEEK_RANGE), 'n')
|
||||
def test_NthTradingDayOfWeek(self, n):
|
||||
should_trigger = partial(NthTradingDayOfWeek(n).should_trigger,
|
||||
env=self.env)
|
||||
cal = get_calendar('NYSE')
|
||||
rule = NthTradingDayOfWeek(n)
|
||||
rule.cal = cal
|
||||
should_trigger = rule.should_trigger
|
||||
prev_day = self.sept_week[0].date()
|
||||
n_tdays = 0
|
||||
for m in self.sept_week:
|
||||
@@ -362,17 +359,18 @@ class TestStatelessRules(RuleTestCase):
|
||||
|
||||
@subtest(param_range(MAX_WEEK_RANGE), 'n')
|
||||
def test_NDaysBeforeLastTradingDayOfWeek(self, n):
|
||||
should_trigger = partial(
|
||||
NDaysBeforeLastTradingDayOfWeek(n).should_trigger, env=self.env
|
||||
)
|
||||
cal = get_calendar('NYSE')
|
||||
rule = NDaysBeforeLastTradingDayOfWeek(n)
|
||||
rule.cal = cal
|
||||
should_trigger = rule.should_trigger
|
||||
for m in self.sept_week:
|
||||
if should_trigger(m):
|
||||
n_tdays = 0
|
||||
date = m.to_datetime().date()
|
||||
next_date = self.env.next_trading_day(date)
|
||||
next_date = self.nyse_cal.next_trading_day(date)
|
||||
while next_date.weekday() > date.weekday():
|
||||
date = next_date
|
||||
next_date = self.env.next_trading_day(date)
|
||||
next_date = self.nyse_cal.next_trading_day(date)
|
||||
n_tdays += 1
|
||||
|
||||
self.assertEqual(n_tdays, n)
|
||||
@@ -397,7 +395,7 @@ class TestStatelessRules(RuleTestCase):
|
||||
sim_start = pd.Timestamp('01-06-2014', tz='UTC') + \
|
||||
timedelta(days=start_offset)
|
||||
|
||||
jan_minutes = self.env.minutes_for_days_in_range(
|
||||
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)
|
||||
@@ -428,9 +426,8 @@ class TestStatelessRules(RuleTestCase):
|
||||
trigger_dates = \
|
||||
[x - timedelta(days=rule_offset) for x in trigger_dates]
|
||||
|
||||
should_trigger = partial(
|
||||
rule(rule_offset).should_trigger, env=self.env
|
||||
)
|
||||
rule.cal = self.nyse_cal
|
||||
should_trigger = rule(rule_offset).should_trigger
|
||||
|
||||
# If offset is 4, there is not enough trading days in the short week,
|
||||
# and so it should not trigger
|
||||
@@ -441,9 +438,9 @@ class TestStatelessRules(RuleTestCase):
|
||||
trigger_dates = [x for x in trigger_dates if x >= sim_start]
|
||||
|
||||
# Get all the minutes on the trigger dates
|
||||
trigger_dts = self.env.market_minutes_for_day(trigger_dates[0])
|
||||
trigger_dts = self.nyse_cal.trading_minutes_for_day(trigger_dates[0])
|
||||
for dt in trigger_dates[1:]:
|
||||
trigger_dts += self.env.market_minutes_for_day(dt)
|
||||
trigger_dts += self.nyse_cal.trading_minutes_for_day(dt)
|
||||
|
||||
expected_n_triggered = len(trigger_dts)
|
||||
trigger_dts = iter(trigger_dts)
|
||||
@@ -462,11 +459,14 @@ class TestStatelessRules(RuleTestCase):
|
||||
NDaysBeforeLastTradingDayOfWeek(4)
|
||||
time_rule = AfterOpen(minutes=60)
|
||||
|
||||
week_rule.cal = self.nyse_cal
|
||||
time_rule.cal = self.nyse_cal
|
||||
|
||||
composed_rule = week_rule & time_rule
|
||||
|
||||
should_trigger = partial(composed_rule.should_trigger, env=self.env)
|
||||
should_trigger = composed_rule.should_trigger
|
||||
|
||||
week_minutes = self.env.minutes_for_days_in_range(
|
||||
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)
|
||||
)
|
||||
@@ -486,10 +486,12 @@ class TestStatelessRules(RuleTestCase):
|
||||
|
||||
@subtest(param_range(MAX_MONTH_RANGE), 'n')
|
||||
def test_NthTradingDayOfMonth(self, n):
|
||||
should_trigger = partial(NthTradingDayOfMonth(n).should_trigger,
|
||||
env=self.env)
|
||||
cal = get_calendar('NYSE')
|
||||
rule = NthTradingDayOfMonth(n)
|
||||
rule.cal = cal
|
||||
should_trigger = rule.should_trigger
|
||||
for n_tdays, d in enumerate(self.sept_days):
|
||||
for m in self.env.market_minutes_for_day(d):
|
||||
for m in self.nyse_cal.trading_minutes_for_day(d):
|
||||
if should_trigger(m):
|
||||
self.assertEqual(n_tdays, n)
|
||||
else:
|
||||
@@ -497,11 +499,12 @@ class TestStatelessRules(RuleTestCase):
|
||||
|
||||
@subtest(param_range(MAX_MONTH_RANGE), 'n')
|
||||
def test_NDaysBeforeLastTradingDayOfMonth(self, n):
|
||||
should_trigger = partial(
|
||||
NDaysBeforeLastTradingDayOfMonth(n).should_trigger, env=self.env
|
||||
)
|
||||
cal = get_calendar('NYSE')
|
||||
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.env.market_minutes_for_day(d):
|
||||
for m in self.nyse_cal.trading_minutes_for_day(d):
|
||||
if should_trigger(m):
|
||||
self.assertEqual(n_days_before, n)
|
||||
else:
|
||||
@@ -513,7 +516,7 @@ class TestStatelessRules(RuleTestCase):
|
||||
rule2 = Never()
|
||||
|
||||
composed = rule1 & rule2
|
||||
should_trigger = partial(composed.should_trigger, env=self.env)
|
||||
should_trigger = composed.should_trigger
|
||||
self.assertIsInstance(composed, ComposedRule)
|
||||
self.assertIs(composed.first, rule1)
|
||||
self.assertIs(composed.second, rule2)
|
||||
@@ -536,14 +539,14 @@ class TestStatefulRules(RuleTestCase):
|
||||
"""
|
||||
count = 0
|
||||
|
||||
def should_trigger(self, dt, env):
|
||||
st = self.rule.should_trigger(dt, env)
|
||||
def should_trigger(self, dt):
|
||||
st = self.rule.should_trigger(dt)
|
||||
if st:
|
||||
self.count += 1
|
||||
return st
|
||||
|
||||
rule = RuleCounter(OncePerDay())
|
||||
for m in ms:
|
||||
rule.should_trigger(m, env=self.env)
|
||||
rule.should_trigger(m)
|
||||
|
||||
self.assertEqual(rule.count, 1)
|
||||
|
||||
@@ -203,7 +203,7 @@ cdef class BarData:
|
||||
view = self._views[asset]
|
||||
except KeyError:
|
||||
try:
|
||||
asset = self.data_portal.env.asset_finder.retrieve_asset(asset)
|
||||
asset = self.data_portal.asset_finder.retrieve_asset(asset)
|
||||
except ValueError:
|
||||
# assume fetcher
|
||||
pass
|
||||
@@ -223,7 +223,8 @@ cdef class BarData:
|
||||
dt = self.simulation_dt_func()
|
||||
|
||||
if self._adjust_minutes:
|
||||
dt = self.data_portal.env.previous_market_minute(dt)
|
||||
dt = \
|
||||
self.data_portal.trading_schedule.previous_execution_minute(dt)
|
||||
|
||||
return dt
|
||||
|
||||
|
||||
+52
-21
@@ -53,8 +53,12 @@ from zipline.errors import (
|
||||
UnsupportedDatetimeFormat,
|
||||
UnsupportedOrderParameters,
|
||||
UnsupportedSlippageModel,
|
||||
CannotOrderDelistedAsset, UnsupportedCancelPolicy, SetCancelPolicyPostInit,
|
||||
OrderInBeforeTradingStart)
|
||||
CannotOrderDelistedAsset,
|
||||
UnsupportedCancelPolicy,
|
||||
SetCancelPolicyPostInit,
|
||||
OrderInBeforeTradingStart,
|
||||
ScheduleFunctionWithoutCalendar,
|
||||
)
|
||||
from zipline.finance.trading import TradingEnvironment
|
||||
from zipline.finance.blotter import Blotter
|
||||
from zipline.finance.commission import PerShare, CommissionModel
|
||||
@@ -94,6 +98,10 @@ 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,
|
||||
)
|
||||
import zipline.utils.events
|
||||
from zipline.utils.events import (
|
||||
EventManager,
|
||||
@@ -273,6 +281,12 @@ class TradingAlgorithm(object):
|
||||
futures=kwargs.pop('futures_metadata', None),
|
||||
)
|
||||
|
||||
# If a schedule has been provided, pop it. Otherwise, use NYSE.
|
||||
self.trading_schedule = kwargs.pop(
|
||||
'trading_schedule',
|
||||
default_nyse_schedule,
|
||||
)
|
||||
|
||||
# set the capital base
|
||||
self.capital_base = kwargs.pop('capital_base', DEFAULT_CAPITAL_BASE)
|
||||
self.sim_params = kwargs.pop('sim_params', None)
|
||||
@@ -281,10 +295,12 @@ class TradingAlgorithm(object):
|
||||
capital_base=self.capital_base,
|
||||
start=kwargs.pop('start', None),
|
||||
end=kwargs.pop('end', None),
|
||||
env=self.trading_environment,
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
else:
|
||||
self.sim_params.update_internal_from_env(self.trading_environment)
|
||||
self.sim_params.update_internal_from_trading_schedule(
|
||||
self.trading_schedule
|
||||
)
|
||||
|
||||
self.perf_tracker = None
|
||||
# Pull in the environment's new AssetFinder for quick reference
|
||||
@@ -411,7 +427,7 @@ class TradingAlgorithm(object):
|
||||
if get_loader is not None:
|
||||
self.engine = SimplePipelineEngine(
|
||||
get_loader,
|
||||
self.trading_environment.trading_days,
|
||||
self.trading_schedule.all_execution_days,
|
||||
self.asset_finder,
|
||||
)
|
||||
else:
|
||||
@@ -484,8 +500,7 @@ class TradingAlgorithm(object):
|
||||
If the clock property is not set, then create one based on frequency.
|
||||
"""
|
||||
if self.sim_params.data_frequency == 'minute':
|
||||
env = self.trading_environment
|
||||
trading_o_and_c = env.open_and_closes.ix[
|
||||
trading_o_and_c = self.trading_schedule.schedule.ix[
|
||||
self.sim_params.trading_days]
|
||||
market_opens = trading_o_and_c['market_open'].values.astype(
|
||||
'datetime64[ns]').astype(np.int64)
|
||||
@@ -506,10 +521,11 @@ class TradingAlgorithm(object):
|
||||
|
||||
def _create_benchmark_source(self):
|
||||
return BenchmarkSource(
|
||||
self.benchmark_sid,
|
||||
self.trading_environment,
|
||||
self.sim_params.trading_days,
|
||||
self.data_portal,
|
||||
benchmark_sid=self.benchmark_sid,
|
||||
env=self.trading_environment,
|
||||
trading_schedule=self.trading_schedule,
|
||||
trading_days=self.sim_params.trading_days,
|
||||
data_portal=self.data_portal,
|
||||
emission_rate=self.sim_params.emission_rate,
|
||||
)
|
||||
|
||||
@@ -522,6 +538,7 @@ 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,
|
||||
env=self.trading_environment,
|
||||
)
|
||||
|
||||
@@ -600,8 +617,8 @@ class TradingAlgorithm(object):
|
||||
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_env(
|
||||
env=self.trading_environment
|
||||
self.sim_params.update_internal_from_trading_schedule(
|
||||
trading_schedule=self.trading_schedule
|
||||
)
|
||||
|
||||
copy_panel = data.rename(
|
||||
@@ -615,16 +632,17 @@ class TradingAlgorithm(object):
|
||||
copy_panel.items, copy_panel.major_axis[0],
|
||||
)
|
||||
self._assets_from_source = (
|
||||
self.trading_environment.asset_finder.retrieve_all(
|
||||
self.asset_finder.retrieve_all(
|
||||
copy_panel.items
|
||||
)
|
||||
)
|
||||
equity_daily_reader = PanelDailyBarReader(
|
||||
self.trading_environment.trading_days,
|
||||
self.trading_schedule.all_execution_days,
|
||||
copy_panel,
|
||||
)
|
||||
self.data_portal = DataPortal(
|
||||
self.trading_environment,
|
||||
self.asset_finder,
|
||||
self.trading_schedule,
|
||||
first_trading_day=equity_daily_reader.first_trading_day,
|
||||
equity_daily_reader=equity_daily_reader,
|
||||
)
|
||||
@@ -735,7 +753,7 @@ class TradingAlgorithm(object):
|
||||
fake_sids = range(first_sid, first_sid + len(new_symbols))
|
||||
frame_to_write = make_simple_equity_info(
|
||||
sids=fake_sids,
|
||||
start_date=self.sim_params.period_start,
|
||||
start_date=as_of_date,
|
||||
end_date=self.sim_params.period_end,
|
||||
symbols=new_symbols,
|
||||
)
|
||||
@@ -895,7 +913,8 @@ class TradingAlgorithm(object):
|
||||
url,
|
||||
pre_func,
|
||||
post_func,
|
||||
self.trading_environment,
|
||||
self.asset_finder,
|
||||
self.trading_schedule.day,
|
||||
self.sim_params.period_start,
|
||||
self.sim_params.period_end,
|
||||
date_column,
|
||||
@@ -969,8 +988,18 @@ class TradingAlgorithm(object):
|
||||
# If we are in daily mode the time_rule is ignored.
|
||||
time_rules.every_minute())
|
||||
|
||||
# Check the type of the algorithm's schedule before pulling calendar
|
||||
# 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
|
||||
|
||||
self.add_event(
|
||||
make_eventrule(date_rule, time_rule, half_days),
|
||||
make_eventrule(date_rule, time_rule, cal, half_days),
|
||||
func,
|
||||
)
|
||||
|
||||
@@ -1934,7 +1963,9 @@ 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.env.previous_market_minute(self.datetime)
|
||||
self.data_portal.trading_schedule.previous_execution_minute(
|
||||
self.datetime
|
||||
)
|
||||
|
||||
window = self.data_portal.get_history_window(
|
||||
assets,
|
||||
@@ -2210,7 +2241,7 @@ class TradingAlgorithm(object):
|
||||
--------
|
||||
PipelineEngine.run_pipeline
|
||||
"""
|
||||
days = self.trading_environment.trading_days
|
||||
days = self.trading_schedule.all_execution_days
|
||||
|
||||
# Load data starting from the previous trading day...
|
||||
start_date_loc = days.get_loc(start_date)
|
||||
|
||||
@@ -29,7 +29,11 @@ from zipline.utils.compat import mappingproxy
|
||||
from zipline.utils.input_validation import ensure_timestamp, optionally
|
||||
import zipline.utils.paths as pth
|
||||
from zipline.utils.preprocess import preprocess
|
||||
from zipline.utils.tradingcalendar import trading_days, open_and_closes
|
||||
from zipline.utils.calendars import get_calendar
|
||||
|
||||
nyse_cal = get_calendar('NYSE')
|
||||
trading_days = nyse_cal.all_trading_days
|
||||
open_and_closes = nyse_cal.schedule
|
||||
|
||||
|
||||
def asset_db_path(bundle_name, timestr, environ=None):
|
||||
|
||||
+73
-35
@@ -30,7 +30,6 @@ from zipline.data.us_equity_loader import (
|
||||
USEquityMinuteHistoryLoader,
|
||||
)
|
||||
|
||||
from zipline.utils import tradingcalendar
|
||||
from zipline.utils.math_utils import (
|
||||
nansum,
|
||||
nanmean,
|
||||
@@ -116,6 +115,7 @@ class DailyHistoryAggregator(object):
|
||||
cache = self._caches[field] = (dt.date(), market_open, {})
|
||||
|
||||
_, market_open, entries = cache
|
||||
market_open = market_open.tz_localize('UTC')
|
||||
if dt != market_open:
|
||||
prev_dt = dt_value - self._one_min
|
||||
else:
|
||||
@@ -495,19 +495,20 @@ class DataPortal(object):
|
||||
other adjustment data to the raw data from the readers.
|
||||
"""
|
||||
def __init__(self,
|
||||
env,
|
||||
asset_finder,
|
||||
trading_schedule,
|
||||
first_trading_day,
|
||||
equity_daily_reader=None,
|
||||
equity_minute_reader=None,
|
||||
future_daily_reader=None,
|
||||
future_minute_reader=None,
|
||||
adjustment_reader=None):
|
||||
self.env = env
|
||||
|
||||
self.trading_schedule = trading_schedule
|
||||
self.asset_finder = asset_finder
|
||||
|
||||
self.views = {}
|
||||
|
||||
self._asset_finder = env.asset_finder
|
||||
|
||||
self._carrays = {
|
||||
'open': {},
|
||||
'high': {},
|
||||
@@ -535,7 +536,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.env,
|
||||
self.trading_schedule,
|
||||
self._equity_daily_reader,
|
||||
self._adjustment_reader
|
||||
)
|
||||
@@ -543,20 +544,40 @@ class DataPortal(object):
|
||||
self._future_daily_reader = future_daily_reader
|
||||
self._future_minute_reader = future_minute_reader
|
||||
|
||||
self._first_trading_day = first_trading_day
|
||||
|
||||
if self._equity_minute_reader is not None:
|
||||
self._equity_daily_aggregator = DailyHistoryAggregator(
|
||||
self.env.open_and_closes.market_open,
|
||||
self.trading_schedule.schedule.market_open,
|
||||
self._equity_minute_reader)
|
||||
self._equity_minute_history_loader = USEquityMinuteHistoryLoader(
|
||||
self.env,
|
||||
self.trading_schedule,
|
||||
self._equity_minute_reader,
|
||||
self._adjustment_reader
|
||||
)
|
||||
self.MINUTE_PRICE_ADJUSTMENT_FACTOR = \
|
||||
self._equity_minute_reader._ohlc_inverse
|
||||
|
||||
self._first_trading_day = first_trading_day
|
||||
|
||||
# Get the first trading minute
|
||||
self._first_trading_minute, _ = (
|
||||
self.trading_schedule.start_and_end(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)
|
||||
)
|
||||
if self._first_trading_day is not None else None
|
||||
)
|
||||
self._first_trading_minute_loc = (
|
||||
self.trading_schedule.all_execution_minutes.get_loc(
|
||||
self._first_trading_minute
|
||||
)
|
||||
if self._first_trading_minute is not None else None
|
||||
)
|
||||
|
||||
def _reindex_extra_source(self, df, source_date_index):
|
||||
return df.reindex(index=source_date_index, method='ffill')
|
||||
|
||||
@@ -591,7 +612,7 @@ 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.env.days_in_range(
|
||||
source_date_index = self.trading_schedule.execution_days_in_range(
|
||||
start=sim_params.period_start,
|
||||
end=sim_params.period_end
|
||||
)
|
||||
@@ -1006,20 +1027,22 @@ class DataPortal(object):
|
||||
spot_value=value
|
||||
)
|
||||
else:
|
||||
found_dt -= tradingcalendar.trading_day
|
||||
found_dt -= self.trading_schedule.day
|
||||
except NoDataOnDate:
|
||||
return np.nan
|
||||
|
||||
@remember_last
|
||||
def _get_days_for_window(self, end_date, bar_count):
|
||||
tds = self.env.trading_days
|
||||
end_loc = self.env.trading_days.get_loc(end_date)
|
||||
tds = self.trading_schedule.all_execution_days
|
||||
end_loc = tds.get_loc(end_date)
|
||||
start_loc = end_loc - bar_count + 1
|
||||
if start_loc < 0:
|
||||
if start_loc < self._first_trading_day_loc:
|
||||
raise HistoryWindowStartsBeforeData(
|
||||
first_trading_day=self.env.first_trading_day.date(),
|
||||
first_trading_day=self._first_trading_day.date(),
|
||||
bar_count=bar_count,
|
||||
suggested_start_day=tds[bar_count].date(),
|
||||
suggested_start_day=tds[
|
||||
self._first_trading_day_loc + bar_count
|
||||
].date(),
|
||||
)
|
||||
return tds[start_loc:end_loc + 1]
|
||||
|
||||
@@ -1069,7 +1092,7 @@ class DataPortal(object):
|
||||
|
||||
# get all the minutes for the days NOT including today
|
||||
for day in days_for_window[:-1]:
|
||||
minutes = self.env.market_minutes_for_day(day)
|
||||
minutes = self.trading_schedule.execution_minutes_for_day(day)
|
||||
|
||||
values_for_day = np.zeros(len(minutes), dtype=np.float64)
|
||||
|
||||
@@ -1084,7 +1107,7 @@ class DataPortal(object):
|
||||
|
||||
# get the minutes for today
|
||||
last_day_minutes = pd.date_range(
|
||||
start=self.env.get_open_and_close(end_dt)[0],
|
||||
start=self.trading_schedule.start_and_end(end_dt)[0],
|
||||
end=end_dt,
|
||||
freq="T"
|
||||
)
|
||||
@@ -1161,6 +1184,19 @@ class DataPortal(object):
|
||||
|
||||
return daily_data
|
||||
|
||||
def _handle_history_out_of_bounds(self, bar_count):
|
||||
suggested_start_day = (
|
||||
self.trading_schedule.all_execution_minutes[
|
||||
self._first_trading_minute_loc + bar_count
|
||||
] + self.trading_schedule.day
|
||||
).date()
|
||||
|
||||
raise HistoryWindowStartsBeforeData(
|
||||
first_trading_day=self._first_trading_day.date(),
|
||||
bar_count=bar_count,
|
||||
suggested_start_day=suggested_start_day,
|
||||
)
|
||||
|
||||
def _get_history_minute_window(self, assets, end_dt, bar_count,
|
||||
field_to_use):
|
||||
"""
|
||||
@@ -1168,17 +1204,15 @@ class DataPortal(object):
|
||||
of minute frequency for the given sids.
|
||||
"""
|
||||
# get all the minutes for this window
|
||||
mm = self.env.market_minutes
|
||||
end_loc = mm.get_loc(end_dt)
|
||||
start_loc = end_loc - bar_count + 1
|
||||
if start_loc < 0:
|
||||
suggested_start_day = (mm[bar_count] + self.env.trading_day).date()
|
||||
raise HistoryWindowStartsBeforeData(
|
||||
first_trading_day=self.env.first_trading_day.date(),
|
||||
bar_count=bar_count,
|
||||
suggested_start_day=suggested_start_day,
|
||||
try:
|
||||
minutes_for_window = self.trading_schedule.execution_minute_window(
|
||||
end_dt, -bar_count
|
||||
)
|
||||
minutes_for_window = mm[start_loc:end_loc + 1]
|
||||
except KeyError:
|
||||
self._handle_history_out_of_bounds(bar_count)
|
||||
|
||||
if minutes_for_window[0] < self._first_trading_minute:
|
||||
self._handle_history_out_of_bounds(bar_count)
|
||||
|
||||
asset_minute_data = self._get_minute_window_for_assets(
|
||||
assets,
|
||||
@@ -1690,17 +1724,21 @@ 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.env.previous_trading_day(ending_minute)
|
||||
days = self.env.days_in_range(
|
||||
self.env.add_trading_days(-days_count + 2, previous_day),
|
||||
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,
|
||||
)
|
||||
|
||||
minutes_count = \
|
||||
sum(210 if day in self.env.early_closes else 390 for day in days)
|
||||
minutes_count = sum(
|
||||
210 if day in self.trading_schedule.early_ends
|
||||
else 390 for day in days
|
||||
)
|
||||
|
||||
# add the minutes for today
|
||||
today_open = self.env.get_open_and_close(ending_minute)[0]
|
||||
today_open = self.trading_schedule.start_and_end(ending_minute)[0]
|
||||
minutes_count += \
|
||||
((ending_minute - today_open).total_seconds() // 60) + 1
|
||||
|
||||
|
||||
@@ -29,10 +29,7 @@ from ..utils.paths import (
|
||||
data_root,
|
||||
)
|
||||
from ..utils.deprecate import deprecated
|
||||
from ..utils.tradingcalendar import (
|
||||
trading_day as trading_day_nyse,
|
||||
trading_days as trading_days_nyse,
|
||||
)
|
||||
from zipline.utils.calendars import get_calendar
|
||||
|
||||
logger = logbook.Logger('Loader')
|
||||
|
||||
@@ -48,6 +45,10 @@ INDEX_MAPPING = {
|
||||
|
||||
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
|
||||
|
||||
|
||||
def last_modified_time(path):
|
||||
"""
|
||||
|
||||
@@ -82,8 +82,9 @@ class USEquityHistoryLoader(with_metaclass(ABCMeta)):
|
||||
"""
|
||||
FIELDS = ('open', 'high', 'low', 'close', 'volume')
|
||||
|
||||
def __init__(self, env, reader, adjustment_reader, sid_cache_size=1000):
|
||||
self.env = env
|
||||
def __init__(self, trading_schedule, reader, adjustment_reader,
|
||||
sid_cache_size=1000):
|
||||
self.trading_schedule = trading_schedule
|
||||
self._reader = reader
|
||||
self._adjustments_reader = adjustment_reader
|
||||
self._window_blocks = {
|
||||
@@ -403,7 +404,7 @@ class USEquityMinuteHistoryLoader(USEquityHistoryLoader):
|
||||
|
||||
@lazyval
|
||||
def _calendar(self):
|
||||
mm = self.env.market_minutes
|
||||
mm = self.trading_schedule.all_execution_minutes
|
||||
return mm[mm.slice_indexer(start=self._reader.first_trading_day,
|
||||
end=self._reader.last_available_dt)]
|
||||
|
||||
|
||||
@@ -635,3 +635,34 @@ class NonExistentAssetInTimeFrame(ZiplineError):
|
||||
"The target asset '{asset}' does not exist for the entire timeframe "
|
||||
"between {start_date} and {end_date}."
|
||||
)
|
||||
|
||||
|
||||
class InvalidCalendarName(ZiplineError):
|
||||
"""
|
||||
Raised when a calendar with an invalid name is requested.
|
||||
"""
|
||||
msg = (
|
||||
"The requested ExchangeCalendar, {calendar_name}, does not exist."
|
||||
)
|
||||
|
||||
|
||||
class CalendarNameCollision(ZiplineError):
|
||||
"""
|
||||
Raised when the static calendar registry already has a calendar with a
|
||||
given name.
|
||||
"""
|
||||
msg = (
|
||||
"A calendar with the name {calendar_name} is already registered."
|
||||
)
|
||||
|
||||
|
||||
class ScheduleFunctionWithoutCalendar(ZiplineError):
|
||||
"""
|
||||
Raised when schedule_function is called but there is not a calendar to be
|
||||
used in the construction of an event rule.
|
||||
"""
|
||||
# TODO update message when new TradingSchedules are built
|
||||
msg = (
|
||||
"To use schedule_function, the TradingAlgorithm must be running on an "
|
||||
"ExchangeTradingSchedule, rather than {schedule}."
|
||||
)
|
||||
|
||||
@@ -384,7 +384,7 @@ class PositionTracker(object):
|
||||
last_sale_price = data_portal.get_adjusted_value(
|
||||
asset,
|
||||
'price',
|
||||
data_portal.env.previous_market_minute(dt),
|
||||
data_portal.trading_schedule.previous_execution_minute(dt),
|
||||
dt,
|
||||
self.data_frequency
|
||||
)
|
||||
|
||||
@@ -78,28 +78,28 @@ class PerformanceTracker(object):
|
||||
"""
|
||||
Tracks the performance of the algorithm.
|
||||
"""
|
||||
def __init__(self, sim_params, env):
|
||||
def __init__(self, sim_params, trading_schedule, env):
|
||||
self.sim_params = sim_params
|
||||
self.env = env
|
||||
self.trading_schedule = trading_schedule
|
||||
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.last_close = self.sim_params.last_close
|
||||
first_open = self.sim_params.first_open.tz_convert(
|
||||
self.env.exchange_tz
|
||||
)
|
||||
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 = env.get_open_and_close(self.day)
|
||||
self.market_open, self.market_close = trading_schedule.start_and_end(
|
||||
self.day
|
||||
)
|
||||
self.total_days = self.sim_params.days_in_period
|
||||
self.capital_base = self.sim_params.capital_base
|
||||
self.emission_rate = sim_params.emission_rate
|
||||
|
||||
all_trading_days = env.trading_days
|
||||
mask = ((all_trading_days >= normalize_date(self.period_start)) &
|
||||
(all_trading_days <= normalize_date(self.period_end)))
|
||||
|
||||
self.trading_days = all_trading_days[mask]
|
||||
self.trading_days = trading_schedule.trading_dates(
|
||||
self.period_start, self.period_end
|
||||
)
|
||||
|
||||
self.position_tracker = PositionTracker(
|
||||
asset_finder=env.asset_finder,
|
||||
@@ -109,15 +109,23 @@ class PerformanceTracker(object):
|
||||
self.all_benchmark_returns = pd.Series(
|
||||
index=self.trading_days)
|
||||
self.cumulative_risk_metrics = \
|
||||
risk.RiskMetricsCumulative(self.sim_params, self.env)
|
||||
risk.RiskMetricsCumulative(
|
||||
self.sim_params,
|
||||
self.treasury_curves,
|
||||
self.trading_schedule
|
||||
)
|
||||
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'))
|
||||
|
||||
self.cumulative_risk_metrics = \
|
||||
risk.RiskMetricsCumulative(self.sim_params, self.env,
|
||||
create_first_day_stats=True)
|
||||
risk.RiskMetricsCumulative(
|
||||
self.sim_params,
|
||||
self.treasury_curves,
|
||||
self.trading_schedule,
|
||||
create_first_day_stats=True
|
||||
)
|
||||
|
||||
# this performance period will span the entire simulation from
|
||||
# inception.
|
||||
@@ -134,7 +142,7 @@ class PerformanceTracker(object):
|
||||
keep_orders=False,
|
||||
# don't serialize positions for cumulative period
|
||||
serialize_positions=False,
|
||||
asset_finder=self.env.asset_finder,
|
||||
asset_finder=self.asset_finder,
|
||||
name="Cumulative"
|
||||
)
|
||||
self.cumulative_performance.position_tracker = self.position_tracker
|
||||
@@ -150,7 +158,7 @@ class PerformanceTracker(object):
|
||||
keep_transactions=True,
|
||||
keep_orders=True,
|
||||
serialize_positions=True,
|
||||
asset_finder=self.env.asset_finder,
|
||||
asset_finder=self.asset_finder,
|
||||
name="Daily"
|
||||
)
|
||||
self.todays_performance.position_tracker = self.position_tracker
|
||||
@@ -291,14 +299,17 @@ class PerformanceTracker(object):
|
||||
# date comes.
|
||||
|
||||
if held_sids:
|
||||
asset_finder = self.env.asset_finder
|
||||
|
||||
cash_dividends = adjustment_reader.\
|
||||
get_dividends_with_ex_date(held_sids, next_trading_day,
|
||||
asset_finder)
|
||||
cash_dividends = adjustment_reader.get_dividends_with_ex_date(
|
||||
held_sids,
|
||||
next_trading_day,
|
||||
self.asset_finder
|
||||
)
|
||||
stock_dividends = adjustment_reader.\
|
||||
get_stock_dividends_with_ex_date(held_sids, next_trading_day,
|
||||
asset_finder)
|
||||
get_stock_dividends_with_ex_date(
|
||||
held_sids,
|
||||
next_trading_day,
|
||||
self.asset_finder
|
||||
)
|
||||
|
||||
position_tracker.earn_dividends(
|
||||
cash_dividends,
|
||||
@@ -380,7 +391,9 @@ class PerformanceTracker(object):
|
||||
|
||||
# Get the next trading day and, if it is past the bounds of this
|
||||
# simulation, return the daily perf packet
|
||||
next_trading_day = self.env.next_trading_day(completed_date)
|
||||
next_trading_day = self.trading_schedule.next_execution_day(
|
||||
completed_date
|
||||
)
|
||||
|
||||
# Take a snapshot of our current performance to return to the
|
||||
# browser.
|
||||
@@ -393,9 +406,10 @@ class PerformanceTracker(object):
|
||||
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.market_open, self.market_close = \
|
||||
self.env.next_open_and_close(self.day)
|
||||
self.day = self.env.next_trading_day(self.day)
|
||||
self.trading_schedule.start_and_end(self.day)
|
||||
|
||||
# Roll over positions to current day.
|
||||
self.todays_performance.rollover()
|
||||
@@ -439,7 +453,9 @@ class PerformanceTracker(object):
|
||||
self.sim_params,
|
||||
benchmark_returns=bms,
|
||||
algorithm_leverages=acl,
|
||||
env=self.env)
|
||||
trading_schedule=self.trading_schedule,
|
||||
treasury_curves=self.treasury_curves,
|
||||
)
|
||||
|
||||
risk_dict = self.risk_report.to_dict()
|
||||
return risk_dict
|
||||
|
||||
@@ -86,8 +86,10 @@ class RiskMetricsCumulative(object):
|
||||
'information',
|
||||
)
|
||||
|
||||
def __init__(self, sim_params, env, create_first_day_stats=False):
|
||||
self.treasury_curves = env.treasury_curves
|
||||
def __init__(self, sim_params, treasury_curves, trading_schedule,
|
||||
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
|
||||
)
|
||||
@@ -95,12 +97,14 @@ class RiskMetricsCumulative(object):
|
||||
hour=0, minute=0, second=0, microsecond=0
|
||||
)
|
||||
|
||||
self.trading_days = env.days_in_range(self.start_date, self.end_date)
|
||||
self.trading_days = trading_schedule.trading_dates(
|
||||
self.start_date, self.end_date
|
||||
)
|
||||
|
||||
# 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 - env.trading_days.freq
|
||||
self.day_before_start = self.start_date - self.trading_days.freq
|
||||
|
||||
last_day = normalize_date(sim_params.period_end)
|
||||
if last_day not in self.trading_days:
|
||||
@@ -110,7 +114,6 @@ class RiskMetricsCumulative(object):
|
||||
self.trading_days = self.trading_days.append(last_day)
|
||||
|
||||
self.sim_params = sim_params
|
||||
self.env = env
|
||||
|
||||
self.create_first_day_stats = create_first_day_stats
|
||||
|
||||
@@ -268,7 +271,7 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
|
||||
self.treasury_curves,
|
||||
self.start_date,
|
||||
treasury_end,
|
||||
self.env,
|
||||
self.trading_schedule,
|
||||
)
|
||||
self.daily_treasury[treasury_end] = treasury_period_return
|
||||
self.treasury_period_return = self.daily_treasury[treasury_end]
|
||||
|
||||
@@ -41,11 +41,9 @@ choose_treasury = functools.partial(risk.choose_treasury,
|
||||
|
||||
|
||||
class RiskMetricsPeriod(object):
|
||||
def __init__(self, start_date, end_date, returns, env,
|
||||
benchmark_returns=None, algorithm_leverages=None):
|
||||
def __init__(self, start_date, end_date, returns, trading_schedule,
|
||||
treasury_curves, benchmark_returns, algorithm_leverages=None):
|
||||
|
||||
self.env = env
|
||||
treasury_curves = env.treasury_curves
|
||||
if treasury_curves.index[-1] >= start_date:
|
||||
mask = ((treasury_curves.index >= start_date) &
|
||||
(treasury_curves.index <= end_date))
|
||||
@@ -58,16 +56,20 @@ class RiskMetricsPeriod(object):
|
||||
|
||||
self.start_date = start_date
|
||||
self.end_date = end_date
|
||||
self.trading_schedule = trading_schedule
|
||||
|
||||
if benchmark_returns is None:
|
||||
br = env.benchmark_returns
|
||||
benchmark_returns = br[(br.index >= returns.index[0]) &
|
||||
(br.index <= returns.index[-1])]
|
||||
|
||||
trading_dates = trading_schedule.trading_dates(
|
||||
start=self.start_date,
|
||||
end=self.end_date,
|
||||
)
|
||||
self.algorithm_returns = self.mask_returns_to_period(returns,
|
||||
env)
|
||||
self.benchmark_returns = self.mask_returns_to_period(benchmark_returns,
|
||||
env)
|
||||
trading_dates)
|
||||
|
||||
# Benchmark needs to be masked to the same dates as the algo returns
|
||||
self.benchmark_returns = self.mask_returns_to_period(
|
||||
benchmark_returns,
|
||||
self.algorithm_returns.index
|
||||
)
|
||||
self.algorithm_leverages = algorithm_leverages
|
||||
|
||||
self.calculate_metrics()
|
||||
@@ -108,7 +110,7 @@ class RiskMetricsPeriod(object):
|
||||
self.treasury_curves,
|
||||
self.start_date,
|
||||
self.end_date,
|
||||
self.env,
|
||||
self.trading_schedule,
|
||||
)
|
||||
self.sharpe = self.calculate_sharpe()
|
||||
# The consumer currently expects a 0.0 value for sharpe in period,
|
||||
@@ -187,15 +189,14 @@ class RiskMetricsPeriod(object):
|
||||
|
||||
return '\n'.join(statements)
|
||||
|
||||
def mask_returns_to_period(self, daily_returns, env):
|
||||
def mask_returns_to_period(self, daily_returns, trading_days):
|
||||
if isinstance(daily_returns, list):
|
||||
returns = pd.Series([x.returns for x in daily_returns],
|
||||
index=[x.date for x in daily_returns])
|
||||
else: # otherwise we're receiving an index already
|
||||
returns = daily_returns
|
||||
|
||||
trade_days = env.trading_days
|
||||
trade_day_mask = returns.index.normalize().isin(trade_days)
|
||||
trade_day_mask = returns.index.normalize().isin(trading_days)
|
||||
|
||||
mask = ((returns.index >= self.start_date) &
|
||||
(returns.index <= self.end_date) & trade_day_mask)
|
||||
|
||||
@@ -67,8 +67,9 @@ log = logbook.Logger('Risk Report')
|
||||
|
||||
|
||||
class RiskReport(object):
|
||||
def __init__(self, algorithm_returns, sim_params, env,
|
||||
benchmark_returns=None, algorithm_leverages=None):
|
||||
def __init__(self, algorithm_returns, sim_params, trading_schedule,
|
||||
treasury_curves, benchmark_returns,
|
||||
algorithm_leverages=None):
|
||||
"""
|
||||
algorithm_returns needs to be a list of daily_return objects
|
||||
sorted in date ascending order
|
||||
@@ -79,7 +80,8 @@ class RiskReport(object):
|
||||
|
||||
self.algorithm_returns = algorithm_returns
|
||||
self.sim_params = sim_params
|
||||
self.env = env
|
||||
self.trading_schedule = trading_schedule
|
||||
self.treasury_curves = treasury_curves
|
||||
self.benchmark_returns = benchmark_returns
|
||||
self.algorithm_leverages = algorithm_leverages
|
||||
|
||||
@@ -140,7 +142,8 @@ class RiskReport(object):
|
||||
end_date=cur_end,
|
||||
returns=self.algorithm_returns,
|
||||
benchmark_returns=self.benchmark_returns,
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
treasury_curves=self.treasury_curves,
|
||||
algorithm_leverages=self.algorithm_leverages,
|
||||
)
|
||||
|
||||
|
||||
@@ -202,14 +202,6 @@ def get_treasury_rate(treasury_curves, treasury_duration, day):
|
||||
return rate
|
||||
|
||||
|
||||
def search_day_distance(end_date, dt, env):
|
||||
tdd = env.trading_day_distance(dt, end_date)
|
||||
if tdd is None:
|
||||
return None
|
||||
assert tdd >= 0
|
||||
return tdd
|
||||
|
||||
|
||||
def select_treasury_duration(start_date, end_date):
|
||||
td = end_date - start_date
|
||||
if td.days <= 31:
|
||||
@@ -237,7 +229,7 @@ def select_treasury_duration(start_date, end_date):
|
||||
|
||||
|
||||
def choose_treasury(select_treasury, treasury_curves, start_date, end_date,
|
||||
env, compound=True):
|
||||
trading_schedule, compound=True):
|
||||
"""
|
||||
Find the latest known interest rate for a given duration within a date
|
||||
range.
|
||||
@@ -269,7 +261,9 @@ def choose_treasury(select_treasury, treasury_curves, start_date, end_date,
|
||||
prev_day)
|
||||
if rate is not None:
|
||||
search_day = prev_day
|
||||
search_dist = search_day_distance(end_date, prev_day, env)
|
||||
search_dist = trading_schedule.execution_day_distance(
|
||||
end_date, prev_day
|
||||
)
|
||||
break
|
||||
|
||||
if search_day:
|
||||
|
||||
+35
-342
@@ -13,22 +13,15 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import bisect
|
||||
import logbook
|
||||
import datetime
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
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 import tradingcalendar
|
||||
from zipline.errors import (
|
||||
NoFurtherDataError
|
||||
)
|
||||
from zipline.utils.memoize import remember_last, lazyval
|
||||
from zipline.utils.calendars import default_nyse_schedule
|
||||
|
||||
log = logbook.Logger('Trading')
|
||||
|
||||
@@ -80,44 +73,24 @@ class TradingEnvironment(object):
|
||||
# reference to a TradingEnvironment
|
||||
PERSISTENT_TOKEN = "<TradingEnvironment>"
|
||||
|
||||
def __init__(self,
|
||||
load=None,
|
||||
bm_symbol='^GSPC',
|
||||
exchange_tz="US/Eastern",
|
||||
min_date=None,
|
||||
max_date=None,
|
||||
env_trading_calendar=tradingcalendar,
|
||||
asset_db_path=':memory:'):
|
||||
self.trading_day = env_trading_calendar.trading_day.copy()
|
||||
|
||||
# `tc_td` is short for "trading calendar trading days"
|
||||
tc_td = env_trading_calendar.trading_days
|
||||
|
||||
self.trading_days = tc_td[tc_td.slice_indexer(min_date, max_date)]
|
||||
|
||||
self.first_trading_day = self.trading_days[0]
|
||||
self.last_trading_day = self.trading_days[-1]
|
||||
|
||||
self.early_closes = env_trading_calendar.get_early_closes(
|
||||
self.first_trading_day, self.last_trading_day)
|
||||
|
||||
self.open_and_closes = env_trading_calendar.open_and_closes.loc[
|
||||
self.trading_days]
|
||||
def __init__(
|
||||
self,
|
||||
load=None,
|
||||
bm_symbol='^GSPC',
|
||||
exchange_tz="US/Eastern",
|
||||
trading_schedule=default_nyse_schedule,
|
||||
asset_db_path=':memory:'
|
||||
):
|
||||
|
||||
self.bm_symbol = bm_symbol
|
||||
if not load:
|
||||
load = load_market_data
|
||||
|
||||
self.benchmark_returns, self.treasury_curves = \
|
||||
load(self.trading_day, self.trading_days, self.bm_symbol)
|
||||
|
||||
if max_date:
|
||||
tr_c = self.treasury_curves
|
||||
# Mask the treasury curves down to the current date.
|
||||
# In the case of live trading, the last date in the treasury
|
||||
# curves would be the day before the date considered to be
|
||||
# 'today'.
|
||||
self.treasury_curves = tr_c[tr_c.index <= max_date]
|
||||
self.benchmark_returns, self.treasury_curves = load(
|
||||
trading_schedule.day,
|
||||
trading_schedule.schedule.index,
|
||||
self.bm_symbol,
|
||||
)
|
||||
|
||||
self.exchange_tz = exchange_tz
|
||||
|
||||
@@ -133,11 +106,6 @@ class TradingEnvironment(object):
|
||||
else:
|
||||
self.asset_finder = None
|
||||
|
||||
@lazyval
|
||||
def market_minutes(self):
|
||||
return self.minutes_for_days_in_range(self.first_trading_day,
|
||||
self.last_trading_day)
|
||||
|
||||
def write_data(self, **kwargs):
|
||||
"""Write data into the asset_db.
|
||||
|
||||
@@ -148,284 +116,13 @@ class TradingEnvironment(object):
|
||||
"""
|
||||
AssetDBWriter(self.engine).write(**kwargs)
|
||||
|
||||
def normalize_date(self, test_date):
|
||||
test_date = pd.Timestamp(test_date, tz='UTC')
|
||||
return pd.tseries.tools.normalize_date(test_date)
|
||||
|
||||
def utc_dt_in_exchange(self, dt):
|
||||
return pd.Timestamp(dt).tz_convert(self.exchange_tz)
|
||||
|
||||
def exchange_dt_in_utc(self, dt):
|
||||
return pd.Timestamp(dt, tz=self.exchange_tz).tz_convert('UTC')
|
||||
|
||||
def is_market_hours(self, test_date):
|
||||
if not self.is_trading_day(test_date):
|
||||
return False
|
||||
|
||||
mkt_open, mkt_close = self.get_open_and_close(test_date)
|
||||
return test_date >= mkt_open and test_date <= mkt_close
|
||||
|
||||
def is_trading_day(self, test_date):
|
||||
dt = self.normalize_date(test_date)
|
||||
return (dt in self.trading_days)
|
||||
|
||||
def next_trading_day(self, test_date):
|
||||
dt = self.normalize_date(test_date)
|
||||
delta = datetime.timedelta(days=1)
|
||||
|
||||
while dt <= self.last_trading_day:
|
||||
dt += delta
|
||||
if dt in self.trading_days:
|
||||
return dt
|
||||
|
||||
return None
|
||||
|
||||
def previous_trading_day(self, test_date):
|
||||
dt = self.normalize_date(test_date)
|
||||
delta = datetime.timedelta(days=-1)
|
||||
|
||||
while self.first_trading_day < dt:
|
||||
dt += delta
|
||||
if dt in self.trading_days:
|
||||
return dt
|
||||
|
||||
return None
|
||||
|
||||
def add_trading_days(self, n, date):
|
||||
"""
|
||||
Adds n trading days to date. If this would fall outside of the
|
||||
trading calendar, a NoFurtherDataError is raised.
|
||||
|
||||
:Arguments:
|
||||
n : int
|
||||
The number of days to add to date, this can be positive or
|
||||
negative.
|
||||
date : datetime
|
||||
The date to add to.
|
||||
|
||||
:Returns:
|
||||
new_date : datetime
|
||||
n trading days added to date.
|
||||
"""
|
||||
if n == 1:
|
||||
return self.next_trading_day(date)
|
||||
if n == -1:
|
||||
return self.previous_trading_day(date)
|
||||
|
||||
idx = self.get_index(date) + n
|
||||
if idx < 0 or idx >= len(self.trading_days):
|
||||
raise NoFurtherDataError(
|
||||
msg='Cannot add %d days to %s' % (n, date)
|
||||
)
|
||||
|
||||
return self.trading_days[idx]
|
||||
|
||||
def days_in_range(self, start, end):
|
||||
start_date = self.normalize_date(start)
|
||||
end_date = self.normalize_date(end)
|
||||
|
||||
mask = ((self.trading_days >= start_date) &
|
||||
(self.trading_days <= end_date))
|
||||
return self.trading_days[mask]
|
||||
|
||||
def opens_in_range(self, start, end):
|
||||
return self.open_and_closes.market_open.loc[start:end]
|
||||
|
||||
def closes_in_range(self, start, end):
|
||||
return self.open_and_closes.market_close.loc[start:end]
|
||||
|
||||
def minutes_for_days_in_range(self, start, end):
|
||||
"""
|
||||
Get all market minutes for the days between start and end, inclusive.
|
||||
"""
|
||||
start_date = self.normalize_date(start)
|
||||
end_date = self.normalize_date(end)
|
||||
|
||||
o_and_c = self.open_and_closes[
|
||||
self.open_and_closes.index.slice_indexer(start_date, end_date)]
|
||||
|
||||
opens = o_and_c.market_open
|
||||
closes = o_and_c.market_close
|
||||
|
||||
one_min = pd.Timedelta(1, unit='m')
|
||||
|
||||
all_minutes = []
|
||||
for i in range(0, len(o_and_c.index)):
|
||||
market_open = opens[i]
|
||||
market_close = closes[i]
|
||||
day_minutes = np.arange(market_open, market_close + one_min,
|
||||
dtype='datetime64[m]')
|
||||
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 next_open_and_close(self, start_date):
|
||||
"""
|
||||
Given the start_date, returns the next open and close of
|
||||
the market.
|
||||
"""
|
||||
next_open = self.next_trading_day(start_date)
|
||||
|
||||
if next_open is None:
|
||||
raise NoFurtherDataError(
|
||||
msg=("Attempt to backtest beyond available history. "
|
||||
"Last known date: %s" % self.last_trading_day)
|
||||
)
|
||||
|
||||
return self.get_open_and_close(next_open)
|
||||
|
||||
def previous_open_and_close(self, start_date):
|
||||
"""
|
||||
Given the start_date, returns the previous open and close of the
|
||||
market.
|
||||
"""
|
||||
previous = self.previous_trading_day(start_date)
|
||||
|
||||
if previous is None:
|
||||
raise NoFurtherDataError(
|
||||
msg=("Attempt to backtest beyond available history. "
|
||||
"First known date: %s" % self.first_trading_day)
|
||||
)
|
||||
return self.get_open_and_close(previous)
|
||||
|
||||
def next_market_minute(self, start):
|
||||
"""
|
||||
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 self.is_trading_day(start):
|
||||
market_open, market_close = self.get_open_and_close(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 + datetime.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 self.next_open_and_close(start)[0]
|
||||
|
||||
@remember_last
|
||||
def previous_market_minute(self, start):
|
||||
"""
|
||||
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 self.is_trading_day(start):
|
||||
market_open, market_close = self.get_open_and_close(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 - datetime.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 self.previous_open_and_close(start)[1]
|
||||
|
||||
def get_open_and_close(self, day):
|
||||
index = self.open_and_closes.index.get_loc(day.date())
|
||||
todays_minutes = self.open_and_closes.iloc[index]
|
||||
return todays_minutes[0], todays_minutes[1]
|
||||
|
||||
def market_minutes_for_day(self, stamp):
|
||||
market_open, market_close = self.get_open_and_close(stamp)
|
||||
return pd.date_range(market_open, market_close, freq='T')
|
||||
|
||||
def open_close_window(self, start, count, offset=0, step=1):
|
||||
"""
|
||||
Return a DataFrame containing `count` market opens and closes,
|
||||
beginning with `start` + `offset` days and continuing `step` minutes at
|
||||
a time.
|
||||
"""
|
||||
# TODO: Correctly handle end of data.
|
||||
start_idx = self.get_index(start) + offset
|
||||
stop_idx = start_idx + (count * step)
|
||||
|
||||
index = np.arange(start_idx, stop_idx, step)
|
||||
|
||||
return self.open_and_closes.iloc[index]
|
||||
|
||||
def market_minute_window(self, start, count, step=1):
|
||||
"""
|
||||
Return a DatetimeIndex containing `count` market minutes, starting with
|
||||
`start` and continuing `step` minutes at a time.
|
||||
"""
|
||||
if not self.is_market_hours(start):
|
||||
raise ValueError("market_minute_window starting at "
|
||||
"non-market time {minute}".format(minute=start))
|
||||
|
||||
all_minutes = []
|
||||
|
||||
current_day_minutes = self.market_minutes_for_day(start)
|
||||
first_minute_idx = current_day_minutes.searchsorted(start)
|
||||
minutes_in_range = current_day_minutes[first_minute_idx::step]
|
||||
|
||||
# Build up list of lists of days' market minutes until we have count
|
||||
# minutes stored altogether.
|
||||
while True:
|
||||
|
||||
if len(minutes_in_range) >= count:
|
||||
# Truncate off extra minutes
|
||||
minutes_in_range = minutes_in_range[:count]
|
||||
|
||||
all_minutes.append(minutes_in_range)
|
||||
count -= len(minutes_in_range)
|
||||
if count <= 0:
|
||||
break
|
||||
|
||||
if step > 0:
|
||||
start, _ = self.next_open_and_close(start)
|
||||
current_day_minutes = self.market_minutes_for_day(start)
|
||||
else:
|
||||
_, start = self.previous_open_and_close(start)
|
||||
current_day_minutes = self.market_minutes_for_day(start)
|
||||
|
||||
minutes_in_range = current_day_minutes[::step]
|
||||
|
||||
# Concatenate all the accumulated minutes.
|
||||
return pd.DatetimeIndex(
|
||||
np.concatenate(all_minutes), copy=False, tz='UTC',
|
||||
)
|
||||
|
||||
def trading_day_distance(self, first_date, second_date):
|
||||
first_date = self.normalize_date(first_date)
|
||||
second_date = self.normalize_date(second_date)
|
||||
|
||||
# TODO: May be able to replace the following with searchsorted.
|
||||
# Find leftmost item greater than or equal to day
|
||||
i = bisect.bisect_left(self.trading_days, first_date)
|
||||
if i == len(self.trading_days): # nothing found
|
||||
return None
|
||||
j = bisect.bisect_left(self.trading_days, second_date)
|
||||
if j == len(self.trading_days):
|
||||
return None
|
||||
|
||||
return j - i
|
||||
|
||||
def get_index(self, dt):
|
||||
"""
|
||||
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 = self.normalize_date(dt)
|
||||
if ndt in self.trading_days:
|
||||
return self.trading_days.searchsorted(ndt)
|
||||
else:
|
||||
return self.trading_days.searchsorted(ndt) - 1
|
||||
|
||||
|
||||
class SimulationParameters(object):
|
||||
def __init__(self, period_start, period_end,
|
||||
capital_base=10e3,
|
||||
emission_rate='daily',
|
||||
data_frequency='daily',
|
||||
env=None,
|
||||
trading_schedule=None,
|
||||
arena='backtest'):
|
||||
|
||||
self.period_start = period_start
|
||||
@@ -438,60 +135,56 @@ class SimulationParameters(object):
|
||||
# copied to algorithm's environment for runtime access
|
||||
self.arena = arena
|
||||
|
||||
if env is not None:
|
||||
self.update_internal_from_env(env=env)
|
||||
if trading_schedule is not None:
|
||||
self.update_internal_from_trading_schedule(
|
||||
trading_schedule=trading_schedule
|
||||
)
|
||||
|
||||
def update_internal_from_env(self, env):
|
||||
def update_internal_from_trading_schedule(self, trading_schedule):
|
||||
|
||||
assert self.period_start <= self.period_end, \
|
||||
"Period start falls after period end."
|
||||
|
||||
assert self.period_start <= env.last_trading_day, \
|
||||
assert self.period_start <= trading_schedule.last_execution_day, \
|
||||
"Period start falls after the last known trading day."
|
||||
assert self.period_end >= env.first_trading_day, \
|
||||
assert self.period_end >= trading_schedule.first_execution_day, \
|
||||
"Period end falls before the first known trading day."
|
||||
|
||||
self.first_open = self._calculate_first_open(env)
|
||||
self.last_close = self._calculate_last_close(env)
|
||||
self.first_open = self._calculate_first_open(trading_schedule)
|
||||
self.last_close = self._calculate_last_close(trading_schedule)
|
||||
|
||||
start_index = env.get_index(self.first_open)
|
||||
end_index = env.get_index(self.last_close)
|
||||
# Take the length of an inclusive slice of trading dates
|
||||
self.trading_days = trading_schedule.trading_dates(
|
||||
self.first_open, self.last_close
|
||||
)
|
||||
self.days_in_period = len(self.trading_days)
|
||||
|
||||
# take an inclusive slice of the environment's
|
||||
# trading_days.
|
||||
self.trading_days = env.trading_days[start_index:end_index + 1]
|
||||
|
||||
def _calculate_first_open(self, env):
|
||||
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 env.is_trading_day(first_open):
|
||||
while not trading_schedule.is_executing_on_day(first_open):
|
||||
first_open = first_open + one_day
|
||||
|
||||
mkt_open, _ = env.get_open_and_close(first_open)
|
||||
mkt_open, _ = trading_schedule.start_and_end(first_open)
|
||||
return mkt_open
|
||||
|
||||
def _calculate_last_close(self, env):
|
||||
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 env.is_trading_day(last_close):
|
||||
while not trading_schedule.is_executing_on_day(last_close):
|
||||
last_close = last_close - one_day
|
||||
|
||||
_, mkt_close = env.get_open_and_close(last_close)
|
||||
_, mkt_close = trading_schedule.start_and_end(last_close)
|
||||
return mkt_close
|
||||
|
||||
@property
|
||||
def days_in_period(self):
|
||||
"""return the number of trading days within the period [start, end)"""
|
||||
return len(self.trading_days)
|
||||
|
||||
def __repr__(self):
|
||||
return """
|
||||
{class_name}(
|
||||
|
||||
@@ -215,7 +215,9 @@ 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.env.previous_market_minute(dt),
|
||||
self.algo.trading_schedule.previous_execution_minute(
|
||||
dt
|
||||
),
|
||||
False,
|
||||
self.data_portal
|
||||
)
|
||||
|
||||
@@ -23,8 +23,8 @@ from zipline.errors import (
|
||||
|
||||
|
||||
class BenchmarkSource(object):
|
||||
def __init__(self, benchmark_sid, env, trading_days, data_portal,
|
||||
emission_rate="daily"):
|
||||
def __init__(self, benchmark_sid, env, trading_schedule, trading_days,
|
||||
data_portal, emission_rate="daily"):
|
||||
self.benchmark_sid = benchmark_sid
|
||||
self.env = env
|
||||
self.trading_days = trading_days
|
||||
@@ -42,7 +42,7 @@ class BenchmarkSource(object):
|
||||
self._precalculated_series = \
|
||||
self._initialize_precalculated_series(
|
||||
benchmark_asset,
|
||||
self.env,
|
||||
trading_schedule,
|
||||
self.trading_days,
|
||||
self.data_portal
|
||||
)
|
||||
@@ -55,7 +55,7 @@ class BenchmarkSource(object):
|
||||
if self.emission_rate == "minute":
|
||||
# we need to take the env's benchmark returns, which are daily,
|
||||
# and resample them to minute
|
||||
minutes = env.minutes_for_days_in_range(
|
||||
minutes = trading_schedule.execution_minutes_for_days_in_range(
|
||||
start=trading_days[0],
|
||||
end=trading_days[-1]
|
||||
)
|
||||
@@ -102,17 +102,17 @@ class BenchmarkSource(object):
|
||||
end_dt=benchmark_asset.end_date
|
||||
)
|
||||
|
||||
def _initialize_precalculated_series(self, asset, env, trading_days,
|
||||
data_portal):
|
||||
def _initialize_precalculated_series(self, asset, trading_schedule,
|
||||
trading_days, data_portal):
|
||||
"""
|
||||
Internal method that precalculates the benchmark return series for
|
||||
Internal method that pre-calculates the benchmark return series for
|
||||
use in the simulation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
asset: Asset to use
|
||||
|
||||
env: TradingEnvironment
|
||||
trading_schedule: TradingSchedule
|
||||
|
||||
trading_days: pd.DateTimeIndex
|
||||
|
||||
@@ -137,8 +137,9 @@ class BenchmarkSource(object):
|
||||
change from close to close.
|
||||
"""
|
||||
if self.emission_rate == "minute":
|
||||
minutes = env.minutes_for_days_in_range(self.trading_days[0],
|
||||
self.trading_days[-1])
|
||||
minutes = trading_schedule.execution_minutes_for_days_in_range(
|
||||
self.trading_days[0], self.trading_days[-1]
|
||||
)
|
||||
benchmark_series = data_portal.get_history_window(
|
||||
[asset],
|
||||
minutes[-1],
|
||||
|
||||
@@ -26,14 +26,14 @@ from zipline.assets import Equity
|
||||
logger = Logger('Requests Source Logger')
|
||||
|
||||
|
||||
def roll_dts_to_midnight(dts, env):
|
||||
def roll_dts_to_midnight(dts, trading_day):
|
||||
if len(dts) == 0:
|
||||
return dts
|
||||
|
||||
return pd.DatetimeIndex(
|
||||
(dts.tz_convert('US/Eastern') - pd.Timedelta(hours=16)).date,
|
||||
tz='UTC',
|
||||
) + env.trading_day
|
||||
) + trading_day
|
||||
|
||||
|
||||
class FetcherEvent(Event):
|
||||
@@ -144,7 +144,8 @@ class PandasCSV(with_metaclass(ABCMeta, object)):
|
||||
def __init__(self,
|
||||
pre_func,
|
||||
post_func,
|
||||
env,
|
||||
asset_finder,
|
||||
trading_day,
|
||||
start_date,
|
||||
end_date,
|
||||
date_column,
|
||||
@@ -175,8 +176,8 @@ class PandasCSV(with_metaclass(ABCMeta, object)):
|
||||
|
||||
self.symbol = symbol
|
||||
|
||||
self.env = env
|
||||
self.finder = env.asset_finder
|
||||
self.finder = asset_finder
|
||||
self.trading_day = trading_day
|
||||
|
||||
self.pre_func = pre_func
|
||||
self.post_func = post_func
|
||||
@@ -194,7 +195,7 @@ class PandasCSV(with_metaclass(ABCMeta, object)):
|
||||
|
||||
@staticmethod
|
||||
def parse_date_str_series(format_str, tz, date_str_series, data_frequency,
|
||||
env):
|
||||
trading_day):
|
||||
"""
|
||||
Efficient parsing for a 1d Pandas/numpy object containing string
|
||||
representations of dates.
|
||||
@@ -233,7 +234,7 @@ class PandasCSV(with_metaclass(ABCMeta, object)):
|
||||
).tz_localize(tz_str).tz_convert('UTC')
|
||||
|
||||
if data_frequency == 'daily':
|
||||
parsed = roll_dts_to_midnight(parsed, env)
|
||||
parsed = roll_dts_to_midnight(parsed, trading_day)
|
||||
return parsed
|
||||
|
||||
def mask_pandas_args(self, kwargs):
|
||||
@@ -290,7 +291,7 @@ class PandasCSV(with_metaclass(ABCMeta, object)):
|
||||
self.timezone,
|
||||
df[self.date_column],
|
||||
self.data_frequency,
|
||||
self.env
|
||||
self.trading_day,
|
||||
).values
|
||||
|
||||
# ignore rows whose dates we couldn't parse
|
||||
@@ -456,7 +457,8 @@ class PandasRequestsCSV(PandasCSV):
|
||||
url,
|
||||
pre_func,
|
||||
post_func,
|
||||
env,
|
||||
asset_finder,
|
||||
trading_day,
|
||||
start_date,
|
||||
end_date,
|
||||
date_column,
|
||||
@@ -488,7 +490,8 @@ class PandasRequestsCSV(PandasCSV):
|
||||
super(PandasRequestsCSV, self).__init__(
|
||||
pre_func,
|
||||
post_func,
|
||||
env,
|
||||
asset_finder,
|
||||
trading_day,
|
||||
start_date,
|
||||
end_date,
|
||||
date_column,
|
||||
|
||||
@@ -21,8 +21,8 @@ from datetime import timedelta
|
||||
import pandas as pd
|
||||
|
||||
from zipline.sources.data_source import DataSource
|
||||
from zipline.utils import tradingcalendar as calendar_nyse
|
||||
from zipline.gens.utils import hash_args
|
||||
from zipline.utils.calendars import default_nyse_schedule
|
||||
|
||||
|
||||
class RandomWalkSource(DataSource):
|
||||
@@ -35,7 +35,8 @@ class RandomWalkSource(DataSource):
|
||||
VALID_FREQS = frozenset(('daily', 'minute'))
|
||||
|
||||
def __init__(self, start_prices=None, freq='minute', start=None,
|
||||
end=None, drift=0.1, sd=0.1, calendar=calendar_nyse):
|
||||
end=None, drift=0.1, sd=0.1,
|
||||
trading_schedule=default_nyse_schedule):
|
||||
"""
|
||||
:Arguments:
|
||||
start_prices : dict
|
||||
@@ -52,8 +53,8 @@ class RandomWalkSource(DataSource):
|
||||
Constant drift of the price series.
|
||||
sd: float <default=0.1>
|
||||
Standard deviation of the price series.
|
||||
calendar : calendar object <default: NYSE>
|
||||
Calendar to use.
|
||||
trading_schedule : TradingSchedule object <default: NYSESchedule>
|
||||
TradingSchedule to use.
|
||||
See zipline.utils for different choices.
|
||||
|
||||
:Example:
|
||||
@@ -66,7 +67,7 @@ class RandomWalkSource(DataSource):
|
||||
"""
|
||||
# Hash_value for downstream sorting.
|
||||
self.arg_string = hash_args(start_prices, freq, start, end,
|
||||
calendar.__name__)
|
||||
trading_schedule.__name__)
|
||||
|
||||
if freq not in self.VALID_FREQS:
|
||||
raise ValueError('%s not in %s' % (freq, self.VALID_FREQS))
|
||||
@@ -78,13 +79,13 @@ class RandomWalkSource(DataSource):
|
||||
else:
|
||||
self.start_prices = start_prices
|
||||
|
||||
self.calendar = calendar
|
||||
self.trading_schedule = trading_schedule
|
||||
if start is None:
|
||||
self.start = calendar.start
|
||||
self.start = trading_schedule.first_execution_day
|
||||
else:
|
||||
self.start = start
|
||||
if end is None:
|
||||
self.end = calendar.end_base
|
||||
self.end = trading_schedule.last_execution_day
|
||||
else:
|
||||
self.end = end
|
||||
|
||||
@@ -94,7 +95,7 @@ class RandomWalkSource(DataSource):
|
||||
self.sids = self.start_prices.keys()
|
||||
|
||||
self.open_and_closes = \
|
||||
calendar.open_and_closes[self.start:self.end]
|
||||
trading_schedule.schedule[self.start:self.end]
|
||||
|
||||
self._raw_data = None
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ def create_trade(sid, price, amount, datetime, source_id="test_factory"):
|
||||
|
||||
def date_gen(start,
|
||||
end,
|
||||
env,
|
||||
trading_schedule,
|
||||
delta=timedelta(minutes=1),
|
||||
repeats=None):
|
||||
"""
|
||||
@@ -73,13 +73,13 @@ def date_gen(start,
|
||||
"""
|
||||
cur = cur + delta
|
||||
|
||||
if not (env.is_trading_day
|
||||
if not (trading_schedule.is_executing_on_day
|
||||
if daily_delta
|
||||
else env.is_market_hours)(cur):
|
||||
else trading_schedule.is_executing_on_minute)(cur):
|
||||
if daily_delta:
|
||||
return env.next_trading_day(cur)
|
||||
return trading_schedule.next_execution_day(cur)
|
||||
else:
|
||||
return env.next_open_and_close(cur)[0]
|
||||
return trading_schedule.next_start_and_end(cur)[0]
|
||||
else:
|
||||
return cur
|
||||
|
||||
@@ -109,11 +109,12 @@ class SpecificEquityTrades(object):
|
||||
delta : timedelta between internal events
|
||||
filter : filter to remove the sids
|
||||
"""
|
||||
def __init__(self, env, *args, **kwargs):
|
||||
def __init__(self, env, trading_schedule, *args, **kwargs):
|
||||
# We shouldn't get any positional arguments.
|
||||
assert len(args) == 0
|
||||
|
||||
self.env = env
|
||||
self.trading_schedule = trading_schedule
|
||||
|
||||
# Default to None for event_list and filter.
|
||||
self.event_list = kwargs.get('event_list')
|
||||
@@ -205,14 +206,14 @@ class SpecificEquityTrades(object):
|
||||
end=self.end,
|
||||
delta=self.delta,
|
||||
repeats=len(self.sids),
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
else:
|
||||
date_generator = date_gen(
|
||||
start=self.start,
|
||||
end=self.end,
|
||||
delta=self.delta,
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
source_id = self.get_hash()
|
||||
|
||||
+42
-30
@@ -46,7 +46,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.tradingcalendar import trading_days
|
||||
from zipline.utils.calendars import default_nyse_schedule
|
||||
import numpy as np
|
||||
from numpy import float64
|
||||
|
||||
@@ -425,10 +425,10 @@ class ExplodingObject(object):
|
||||
raise UnexpectedAttributeAccess(name)
|
||||
|
||||
|
||||
def write_minute_data(env, tempdir, minutes, sids):
|
||||
def write_minute_data(trading_schedule, tempdir, minutes, sids):
|
||||
write_bcolz_minute_data(
|
||||
env,
|
||||
env.days_in_range(minutes[0], minutes[-1]),
|
||||
trading_schedule,
|
||||
trading_schedule.execution_days_in_range(minutes[0], minutes[-1]),
|
||||
tempdir.path,
|
||||
create_minute_bar_data(minutes, sids),
|
||||
)
|
||||
@@ -475,39 +475,41 @@ def write_daily_data(tempdir, sim_params, sids):
|
||||
return path
|
||||
|
||||
|
||||
def create_data_portal(env, tempdir, sim_params, sids, adjustment_reader=None):
|
||||
def create_data_portal(asset_finder, tempdir, sim_params, sids,
|
||||
trading_schedule, adjustment_reader=None):
|
||||
if sim_params.data_frequency == "daily":
|
||||
daily_path = write_daily_data(tempdir, sim_params, sids)
|
||||
|
||||
equity_daily_reader = BcolzDailyBarReader(daily_path)
|
||||
|
||||
return DataPortal(
|
||||
env,
|
||||
asset_finder, trading_schedule,
|
||||
first_trading_day=equity_daily_reader.first_trading_day,
|
||||
equity_daily_reader=equity_daily_reader,
|
||||
adjustment_reader=adjustment_reader
|
||||
)
|
||||
else:
|
||||
minutes = env.minutes_for_days_in_range(
|
||||
minutes = trading_schedule.execution_minutes_for_days_in_range(
|
||||
sim_params.first_open,
|
||||
sim_params.last_close
|
||||
)
|
||||
|
||||
minute_path = write_minute_data(env, tempdir, minutes, sids)
|
||||
minute_path = write_minute_data(trading_schedule, tempdir, minutes,
|
||||
sids)
|
||||
|
||||
equity_minute_reader = BcolzMinuteBarReader(minute_path)
|
||||
|
||||
return DataPortal(
|
||||
env,
|
||||
asset_finder, trading_schedule,
|
||||
first_trading_day=equity_minute_reader.first_trading_day,
|
||||
equity_minute_reader=equity_minute_reader,
|
||||
adjustment_reader=adjustment_reader
|
||||
)
|
||||
|
||||
|
||||
def write_bcolz_minute_data(env, days, path, data):
|
||||
market_opens = env.open_and_closes.market_open.loc[days]
|
||||
market_closes = env.open_and_closes.market_close.loc[days]
|
||||
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
|
||||
|
||||
BcolzMinuteBarWriter(
|
||||
days[0],
|
||||
@@ -518,14 +520,16 @@ def write_bcolz_minute_data(env, days, path, data):
|
||||
).write(data)
|
||||
|
||||
|
||||
def create_minute_df_for_asset(env,
|
||||
def create_minute_df_for_asset(trading_schedule,
|
||||
start_dt,
|
||||
end_dt,
|
||||
interval=1,
|
||||
start_val=1,
|
||||
minute_blacklist=None):
|
||||
|
||||
asset_minutes = env.minutes_for_days_in_range(start_dt, end_dt)
|
||||
asset_minutes = trading_schedule.execution_minutes_for_days_in_range(
|
||||
start_dt, end_dt
|
||||
)
|
||||
minutes_count = len(asset_minutes)
|
||||
minutes_arr = np.array(range(start_val, start_val + minutes_count))
|
||||
|
||||
@@ -553,8 +557,9 @@ def create_minute_df_for_asset(env,
|
||||
return df
|
||||
|
||||
|
||||
def create_daily_df_for_asset(env, start_day, end_day, interval=1):
|
||||
days = env.days_in_range(start_day, end_day)
|
||||
def create_daily_df_for_asset(trading_schedule, start_day, end_day,
|
||||
interval=1):
|
||||
days = trading_schedule.execution_days_in_range(start_day, end_day)
|
||||
days_count = len(days)
|
||||
days_arr = np.arange(days_count) + 2
|
||||
|
||||
@@ -608,8 +613,8 @@ def trades_by_sid_to_dfs(trades_by_sid, index):
|
||||
)
|
||||
|
||||
|
||||
def create_data_portal_from_trade_history(env, tempdir, sim_params,
|
||||
trades_by_sid):
|
||||
def create_data_portal_from_trade_history(asset_finder, trading_schedule,
|
||||
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(
|
||||
@@ -619,12 +624,12 @@ def create_data_portal_from_trade_history(env, tempdir, sim_params,
|
||||
equity_daily_reader = BcolzDailyBarReader(path)
|
||||
|
||||
return DataPortal(
|
||||
env,
|
||||
asset_finder, trading_schedule,
|
||||
first_trading_day=equity_daily_reader.first_trading_day,
|
||||
equity_daily_reader=equity_daily_reader,
|
||||
)
|
||||
else:
|
||||
minutes = env.minutes_for_days_in_range(
|
||||
minutes = trading_schedule.execution_minutes_for_days_in_range(
|
||||
sim_params.first_open,
|
||||
sim_params.last_close
|
||||
)
|
||||
@@ -659,8 +664,8 @@ def create_data_portal_from_trade_history(env, tempdir, sim_params,
|
||||
}).set_index("dt")
|
||||
|
||||
write_bcolz_minute_data(
|
||||
env,
|
||||
env.days_in_range(
|
||||
trading_schedule,
|
||||
trading_schedule.execution_days_in_range(
|
||||
sim_params.first_open,
|
||||
sim_params.last_close
|
||||
),
|
||||
@@ -671,7 +676,7 @@ def create_data_portal_from_trade_history(env, tempdir, sim_params,
|
||||
equity_minute_reader = BcolzMinuteBarReader(tempdir.path)
|
||||
|
||||
return DataPortal(
|
||||
env,
|
||||
asset_finder, trading_schedule,
|
||||
first_trading_day=equity_minute_reader.first_trading_day,
|
||||
equity_minute_reader=equity_minute_reader,
|
||||
)
|
||||
@@ -679,11 +684,14 @@ def create_data_portal_from_trade_history(env, tempdir, sim_params,
|
||||
|
||||
class FakeDataPortal(DataPortal):
|
||||
|
||||
def __init__(self, env=None, first_trading_day=None):
|
||||
def __init__(self, env=None, trading_schedule=default_nyse_schedule,
|
||||
first_trading_day=None):
|
||||
if env is None:
|
||||
env = TradingEnvironment()
|
||||
|
||||
super(FakeDataPortal, self).__init__(env, first_trading_day)
|
||||
super(FakeDataPortal, self).__init__(env.asset_finder,
|
||||
trading_schedule,
|
||||
first_trading_day)
|
||||
|
||||
def get_spot_value(self, asset, field, dt, data_frequency):
|
||||
if field == "volume":
|
||||
@@ -694,9 +702,11 @@ class FakeDataPortal(DataPortal):
|
||||
def get_history_window(self, assets, end_dt, bar_count, frequency, field,
|
||||
ffill=True):
|
||||
if frequency == "1d":
|
||||
end_idx = self.env.trading_days.searchsorted(end_dt)
|
||||
days = \
|
||||
self.env.trading_days[(end_idx - bar_count + 1):(end_idx + 1)]
|
||||
end_idx = \
|
||||
self.trading_schedule.all_execution_days.searchsorted(end_dt)
|
||||
days = self.trading_schedule.all_execution_days[
|
||||
(end_idx - bar_count + 1):(end_idx + 1)
|
||||
]
|
||||
|
||||
df = pd.DataFrame(
|
||||
np.full((bar_count, len(assets)), 100),
|
||||
@@ -712,8 +722,9 @@ class FetcherDataPortal(DataPortal):
|
||||
Mock dataportal that returns fake data for history and non-fetcher
|
||||
spot value.
|
||||
"""
|
||||
def __init__(self, env, first_trading_day=None):
|
||||
super(FetcherDataPortal, self).__init__(env, first_trading_day)
|
||||
def __init__(self, asset_finder, trading_schedule, first_trading_day=None):
|
||||
super(FetcherDataPortal, self).__init__(asset_finder, trading_schedule,
|
||||
first_trading_day)
|
||||
|
||||
def get_spot_value(self, asset, field, dt, data_frequency):
|
||||
# if this is a fetcher field, exercise the regular code path
|
||||
@@ -1015,6 +1026,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
|
||||
yield (trading_days[trading_days.slice_indexer(start, stop)],)
|
||||
|
||||
|
||||
|
||||
+74
-37
@@ -33,7 +33,7 @@ from ..data.minute_bars import (
|
||||
)
|
||||
|
||||
from ..finance.trading import TradingEnvironment
|
||||
from ..utils import tradingcalendar, factory
|
||||
from ..utils import factory
|
||||
from ..utils.classproperty import classproperty
|
||||
from ..utils.final import FinalMeta, final
|
||||
from ..utils.metautils import with_metaclasses
|
||||
@@ -47,6 +47,10 @@ from zipline.pipeline.loaders.utils import (
|
||||
get_values_for_date_ranges,
|
||||
zip_with_dates
|
||||
)
|
||||
from zipline.utils.calendars import (
|
||||
get_calendar,
|
||||
ExchangeTradingSchedule,
|
||||
)
|
||||
|
||||
|
||||
class ZiplineTestCase(with_metaclass(FinalMeta, TestCase)):
|
||||
@@ -364,7 +368,41 @@ class WithAssetFinder(WithDefaultDateBounds):
|
||||
cls.asset_finder = cls.make_asset_finder()
|
||||
|
||||
|
||||
class WithTradingEnvironment(WithAssetFinder):
|
||||
class WithTradingSchedule(object):
|
||||
"""
|
||||
ZiplineTestCase mixing providing cls.trading_schedule as a class-level
|
||||
fixture.
|
||||
|
||||
After ``init_class_fixtures`` has been called, `cls.trading_schedule` is
|
||||
populated with a trading schedule.
|
||||
|
||||
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_SCHEDULE_CALENDAR = get_calendar('NYSE')
|
||||
|
||||
@classmethod
|
||||
def make_trading_schedule(cls):
|
||||
return ExchangeTradingSchedule(cls.TRADING_SCHEDULE_CALENDAR)
|
||||
|
||||
@classmethod
|
||||
def init_class_fixtures(cls):
|
||||
super(WithTradingSchedule, cls).init_class_fixtures()
|
||||
cls.trading_schedule = cls.make_trading_schedule()
|
||||
|
||||
|
||||
class WithTradingEnvironment(WithAssetFinder, WithTradingSchedule):
|
||||
"""
|
||||
ZiplineTestCase mixin providing cls.env as a class-level fixture.
|
||||
|
||||
@@ -397,9 +435,6 @@ class WithTradingEnvironment(WithAssetFinder):
|
||||
--------
|
||||
:class:`zipline.finance.trading.TradingEnvironment`
|
||||
"""
|
||||
TRADING_ENV_MIN_DATE = None
|
||||
TRADING_ENV_MAX_DATE = None
|
||||
TRADING_ENV_TRADING_CALENDAR = tradingcalendar
|
||||
|
||||
@classmethod
|
||||
def make_load_function(cls):
|
||||
@@ -410,9 +445,7 @@ class WithTradingEnvironment(WithAssetFinder):
|
||||
return TradingEnvironment(
|
||||
load=cls.make_load_function(),
|
||||
asset_db_path=cls.asset_finder.engine,
|
||||
min_date=cls.TRADING_ENV_MIN_DATE,
|
||||
max_date=cls.TRADING_ENV_MAX_DATE,
|
||||
env_trading_calendar=cls.TRADING_ENV_TRADING_CALENDAR,
|
||||
trading_schedule=cls.trading_schedule,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -467,7 +500,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,
|
||||
env=cls.env,
|
||||
trading_schedule=cls.trading_schedule,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -476,7 +509,7 @@ class WithSimParams(WithTradingEnvironment):
|
||||
cls.sim_params = cls.make_simparams()
|
||||
|
||||
|
||||
class WithNYSETradingDays(object):
|
||||
class WithNYSETradingDays(WithTradingSchedule):
|
||||
"""
|
||||
ZiplineTestCase mixin providing cls.trading_days as a class-level fixture.
|
||||
|
||||
@@ -501,7 +534,7 @@ class WithNYSETradingDays(object):
|
||||
def init_class_fixtures(cls):
|
||||
super(WithNYSETradingDays, cls).init_class_fixtures()
|
||||
|
||||
all_days = tradingcalendar.trading_days
|
||||
all_days = cls.trading_schedule.all_execution_days
|
||||
start_loc = all_days.get_loc(cls.DATA_MIN_DAY, 'bfill')
|
||||
end_loc = all_days.get_loc(cls.DATA_MAX_DAY, 'ffill')
|
||||
|
||||
@@ -630,9 +663,9 @@ class WithBcolzDailyBarReader(WithTradingEnvironment, WithTmpDir):
|
||||
# source from minute logic.
|
||||
'volume': 'last'
|
||||
}
|
||||
mm = cls.env.market_minutes
|
||||
m_opens = cls.env.open_and_closes.market_open
|
||||
m_closes = cls.env.open_and_closes.market_close
|
||||
mm = cls.trading_schedule.all_execution_minutes
|
||||
m_opens = cls.trading_schedule.schedule.market_open
|
||||
m_closes = cls.trading_schedule.schedule.market_close
|
||||
|
||||
for asset in assets:
|
||||
first_minute = m_opens.loc[asset.start_date]
|
||||
@@ -679,13 +712,13 @@ class WithBcolzDailyBarReader(WithTradingEnvironment, WithTmpDir):
|
||||
cls.BCOLZ_DAILY_BAR_PATH,
|
||||
)
|
||||
if cls.BCOLZ_DAILY_BAR_USE_FULL_CALENDAR:
|
||||
days = cls.env.trading_days
|
||||
days = cls.trading_schedule.all_execution_days
|
||||
else:
|
||||
days = cls.env.days_in_range(
|
||||
cls.env.trading_days[
|
||||
cls.env.get_index(cls.BCOLZ_DAILY_BAR_START_DATE) -
|
||||
cls.BCOLZ_DAILY_BAR_LOOKBACK_DAYS
|
||||
],
|
||||
days = cls.trading_schedule.execution_days_in_range(
|
||||
cls.trading_schedule.add_execution_days(
|
||||
-1 * cls.BCOLZ_DAILY_BAR_LOOKBACK_DAYS,
|
||||
cls.BCOLZ_DAILY_BAR_START_DATE,
|
||||
),
|
||||
cls.BCOLZ_DAILY_BAR_END_DATE,
|
||||
)
|
||||
cls.bcolz_daily_bar_days = days
|
||||
@@ -761,7 +794,7 @@ class WithBcolzMinuteBarReader(WithTradingEnvironment, WithTmpDir):
|
||||
@classmethod
|
||||
def make_minute_bar_data(cls):
|
||||
return create_minute_bar_data(
|
||||
cls.env.minutes_for_days_in_range(
|
||||
cls.trading_schedule.execution_minutes_for_days_in_range(
|
||||
cls.bcolz_minute_bar_days[0],
|
||||
cls.bcolz_minute_bar_days[-1],
|
||||
),
|
||||
@@ -775,21 +808,21 @@ class WithBcolzMinuteBarReader(WithTradingEnvironment, WithTmpDir):
|
||||
cls.BCOLZ_MINUTE_BAR_PATH,
|
||||
)
|
||||
if cls.BCOLZ_MINUTE_BAR_USE_FULL_CALENDAR:
|
||||
days = cls.env.trading_days
|
||||
days = cls.trading_schedule.all_execution_days
|
||||
else:
|
||||
days = cls.env.days_in_range(
|
||||
cls.env.trading_days[
|
||||
cls.env.get_index(cls.BCOLZ_MINUTE_BAR_START_DATE) -
|
||||
cls.BCOLZ_MINUTE_BAR_LOOKBACK_DAYS
|
||||
],
|
||||
days = cls.trading_schedule.execution_days_in_range(
|
||||
cls.trading_schedule.add_execution_days(
|
||||
-1 * cls.BCOLZ_MINUTE_BAR_LOOKBACK_DAYS,
|
||||
cls.BCOLZ_MINUTE_BAR_START_DATE,
|
||||
),
|
||||
cls.BCOLZ_MINUTE_BAR_END_DATE,
|
||||
)
|
||||
cls.bcolz_minute_bar_days = days
|
||||
writer = BcolzMinuteBarWriter(
|
||||
days[0],
|
||||
p,
|
||||
cls.env.open_and_closes.market_open.loc[days],
|
||||
cls.env.open_and_closes.market_close.loc[days],
|
||||
cls.trading_schedule.schedule.market_open.loc[days],
|
||||
cls.trading_schedule.schedule.market_close.loc[days],
|
||||
US_EQUITIES_MINUTES_PER_DAY
|
||||
)
|
||||
writer.write(cls.make_minute_bar_data())
|
||||
@@ -1155,17 +1188,21 @@ class WithDataPortal(WithAdjustmentReader,
|
||||
DATA_PORTAL_USE_MINUTE_DATA = True
|
||||
DATA_PORTAL_USE_ADJUSTMENTS = True
|
||||
|
||||
DATA_PORTAL_FIRST_TRADING_DAY = None
|
||||
|
||||
def make_data_portal(self):
|
||||
if self.DATA_PORTAL_USE_MINUTE_DATA:
|
||||
first_trading_day = self.bcolz_minute_bar_reader.first_trading_day
|
||||
elif self.DATA_PORTAL_USE_DAILY_DATA:
|
||||
first_trading_day = self.bcolz_daily_bar_reader.first_trading_day
|
||||
else:
|
||||
first_trading_day = None
|
||||
if self.DATA_PORTAL_FIRST_TRADING_DAY is None:
|
||||
if self.DATA_PORTAL_USE_MINUTE_DATA:
|
||||
self.DATA_PORTAL_FIRST_TRADING_DAY = (
|
||||
self.bcolz_minute_bar_reader.first_trading_day)
|
||||
elif self.DATA_PORTAL_USE_DAILY_DATA:
|
||||
self.DATA_PORTAL_FIRST_TRADING_DAY = (
|
||||
self.bcolz_daily_bar_reader.first_trading_day)
|
||||
|
||||
return DataPortal(
|
||||
self.env,
|
||||
first_trading_day=first_trading_day,
|
||||
self.env.asset_finder,
|
||||
self.trading_schedule,
|
||||
first_trading_day=self.DATA_PORTAL_FIRST_TRADING_DAY,
|
||||
equity_daily_reader=(
|
||||
self.bcolz_daily_bar_reader
|
||||
if self.DATA_PORTAL_USE_DAILY_DATA else
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#
|
||||
# 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 .exchange_calendar import (
|
||||
ExchangeCalendar, get_calendar
|
||||
)
|
||||
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']
|
||||
@@ -0,0 +1,239 @@
|
||||
#
|
||||
# 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
|
||||
return None
|
||||
|
||||
|
||||
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
|
||||
return None
|
||||
|
||||
|
||||
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,588 @@
|
||||
#
|
||||
# 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
|
||||
@@ -0,0 +1,376 @@
|
||||
from datetime import time
|
||||
from pandas import Timedelta
|
||||
from pandas.tseries.holiday import(
|
||||
AbstractHolidayCalendar,
|
||||
Holiday,
|
||||
Easter,
|
||||
Day,
|
||||
GoodFriday,
|
||||
)
|
||||
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)
|
||||
|
||||
# Universal Confraternization (new years day)
|
||||
ConfUniversal = Holiday(
|
||||
'Dia da Confraternizacao Universal',
|
||||
month=1,
|
||||
day=1,
|
||||
)
|
||||
# Sao Paulo city birthday
|
||||
AniversarioSaoPaulo = Holiday(
|
||||
'Aniversario de Sao Paulo',
|
||||
month=1,
|
||||
day=25,
|
||||
)
|
||||
# Carnival Monday
|
||||
CarnavalSegunda = Holiday(
|
||||
'Carnaval Segunda',
|
||||
month=1,
|
||||
day=1,
|
||||
offset=[Easter(), Day(-48)]
|
||||
)
|
||||
# Carnival Tuesday
|
||||
CarnavalTerca = Holiday(
|
||||
'Carnaval Terca',
|
||||
month=1,
|
||||
day=1,
|
||||
offset=[Easter(), Day(-47)]
|
||||
)
|
||||
# Ash Wednesday (short day)
|
||||
QuartaCinzas = Holiday(
|
||||
'Quarta Cinzas',
|
||||
month=1,
|
||||
day=1,
|
||||
offset=[Easter(), Day(-46)]
|
||||
)
|
||||
# Good Friday
|
||||
SextaPaixao = GoodFriday
|
||||
# Feast of the Most Holy Body of Christ
|
||||
CorpusChristi = Holiday(
|
||||
'Corpus Christi',
|
||||
month=1,
|
||||
day=1,
|
||||
offset=[Easter(), Day(60)]
|
||||
)
|
||||
# Tiradentes Memorial
|
||||
Tiradentes = Holiday(
|
||||
'Tiradentes',
|
||||
month=4,
|
||||
day=21,
|
||||
)
|
||||
# Labor Day
|
||||
DiaTrabalho = Holiday(
|
||||
'Dia Trabalho',
|
||||
month=5,
|
||||
day=1,
|
||||
)
|
||||
# Constitutionalist Revolution
|
||||
Constitucionalista = Holiday(
|
||||
'Constitucionalista',
|
||||
month=7,
|
||||
day=9,
|
||||
start_date='1997-01-01'
|
||||
)
|
||||
# Independence Day
|
||||
Independencia = Holiday(
|
||||
'Independencia',
|
||||
month=9,
|
||||
day=7,
|
||||
)
|
||||
# Our Lady of Aparecida
|
||||
Aparecida = Holiday(
|
||||
'Nossa Senhora de Aparecida',
|
||||
month=10,
|
||||
day=12,
|
||||
)
|
||||
# All Souls' Day
|
||||
Finados = Holiday(
|
||||
'Dia dos Finados',
|
||||
month=11,
|
||||
day=2,
|
||||
)
|
||||
# Proclamation of the Republic
|
||||
ProclamacaoRepublica = Holiday(
|
||||
'Proclamacao da Republica',
|
||||
month=11,
|
||||
day=15,
|
||||
)
|
||||
# Day of Black Awareness
|
||||
ConscienciaNegra = Holiday(
|
||||
'Dia da Consciencia Negra',
|
||||
month=11,
|
||||
day=20,
|
||||
start_date='2004-01-01'
|
||||
)
|
||||
# Christmas Eve
|
||||
VesperaNatal = Holiday(
|
||||
'Vespera Natal',
|
||||
month=12,
|
||||
day=24,
|
||||
)
|
||||
# Christmas
|
||||
Natal = Holiday(
|
||||
'Natal',
|
||||
month=12,
|
||||
day=25,
|
||||
)
|
||||
# New Year's Eve
|
||||
AnoNovo = Holiday(
|
||||
'Ano Novo',
|
||||
month=12,
|
||||
day=31,
|
||||
)
|
||||
# New Year's Eve falls on Saturday
|
||||
AnoNovoSabado = Holiday(
|
||||
'Ano Novo Sabado',
|
||||
month=12,
|
||||
day=30,
|
||||
days_of_week=(FRIDAY,),
|
||||
)
|
||||
|
||||
|
||||
class BMFHolidayCalendar(AbstractHolidayCalendar):
|
||||
"""
|
||||
Non-trading days for the BM&F.
|
||||
|
||||
See NYSEExchangeCalendar for full description.
|
||||
"""
|
||||
rules = [
|
||||
ConfUniversal,
|
||||
AniversarioSaoPaulo,
|
||||
CarnavalSegunda,
|
||||
CarnavalTerca,
|
||||
SextaPaixao,
|
||||
CorpusChristi,
|
||||
Tiradentes,
|
||||
DiaTrabalho,
|
||||
Constitucionalista,
|
||||
Independencia,
|
||||
Aparecida,
|
||||
Finados,
|
||||
ProclamacaoRepublica,
|
||||
ConscienciaNegra,
|
||||
VesperaNatal,
|
||||
Natal,
|
||||
AnoNovo,
|
||||
AnoNovoSabado,
|
||||
]
|
||||
|
||||
|
||||
class BMFLateOpenCalendar(AbstractHolidayCalendar):
|
||||
"""
|
||||
Regular early close calendar for NYSE
|
||||
"""
|
||||
rules = [
|
||||
QuartaCinzas,
|
||||
]
|
||||
|
||||
|
||||
class BMFExchangeCalendar(ExchangeCalendar):
|
||||
"""
|
||||
Exchange calendar for BM&F BOVESPA
|
||||
|
||||
Open Time: 10:00 AM, Brazil/Sao Paulo
|
||||
Close Time: 4:00 PM, Brazil/Sao Paulo
|
||||
|
||||
Regularly-Observed Holidays:
|
||||
- Universal Confraternization (New year's day, Jan 1)
|
||||
- Sao Paulo City Anniversary (Jan 25)
|
||||
- Carnaval Monday (48 days before Easter)
|
||||
- Carnaval Tuesday (47 days before Easter)
|
||||
- Passion of the Christ (Good Friday, 2 days before Easter)
|
||||
- Corpus Christi (60 days after Easter)
|
||||
- Tiradentes (April 21)
|
||||
- Labor day (May 1)
|
||||
- Constitutionalist Revolution (July 9 after 1997)
|
||||
- Independence Day (September 7)
|
||||
- Our Lady of Aparecida Feast (October 12)
|
||||
- All Souls' Day (November 2)
|
||||
- Proclamation of the Republic (November 15)
|
||||
- Day of Black Awareness (November 20 after 2004)
|
||||
- Christmas (December 24 and 25)
|
||||
- Day before New Year's Eve (December 30 if NYE falls on a Saturday)
|
||||
- New Year's Eve (December 31)
|
||||
"""
|
||||
|
||||
exchange_name = 'BMF'
|
||||
native_timezone = timezone('America/Sao_Paulo')
|
||||
open_time = time(10, 1)
|
||||
close_time = time(17)
|
||||
|
||||
# Does the market open or close on a different calendar day, compared to
|
||||
# the calendar day assigned by the exchange to this session?
|
||||
open_offset = 0
|
||||
close_offset = 0
|
||||
|
||||
holidays_calendar = BMFHolidayCalendar()
|
||||
special_opens_calendars = [
|
||||
(time(13, 1), BMFLateOpenCalendar()),
|
||||
]
|
||||
special_closes_calendars = ()
|
||||
|
||||
holidays_adhoc = ()
|
||||
|
||||
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,426 @@
|
||||
#
|
||||
# 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 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 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
|
||||
|
||||
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = range(7)
|
||||
|
||||
US_CENTRAL = timezone('America/Chicago')
|
||||
CME_OPEN = time(17)
|
||||
CME_CLOSE = time(16)
|
||||
# CME_STANDARD_EARLY_CLOSE = time(13)
|
||||
# Does the market open or close on a different calendar day, compared to the
|
||||
# calendar day assigned by the exchang 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')
|
||||
|
||||
# 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 CMEHolidayCalendar(AbstractHolidayCalendar):
|
||||
"""
|
||||
Non-trading days for the CME.
|
||||
|
||||
See CMEExchangeCalendar for full description.
|
||||
"""
|
||||
rules = [
|
||||
USNewYearsDay,
|
||||
USMartinLutherKingJrAfter1998,
|
||||
USPresidentsDay,
|
||||
GoodFriday,
|
||||
USMemorialDay,
|
||||
USIndependenceDay,
|
||||
USLaborDay,
|
||||
USThanksgivingDay,
|
||||
USIndependenceDay,
|
||||
Christmas,
|
||||
]
|
||||
|
||||
|
||||
class CMEEarlyCloseCalendar(AbstractHolidayCalendar):
|
||||
"""
|
||||
Regular early close calendar for NYSE
|
||||
"""
|
||||
rules = [
|
||||
MonTuesThursBeforeIndependenceDay,
|
||||
FridayAfterIndependenceDayExcept2013,
|
||||
USBlackFridayInOrAfter1993,
|
||||
ChristmasEveInOrAfter1993,
|
||||
]
|
||||
|
||||
|
||||
class CMEExchangeCalendar(ExchangeCalendar):
|
||||
"""
|
||||
Exchange calendar for CME
|
||||
|
||||
Open Time: 5:00 AM, America/Chicago
|
||||
Close Time: 5:00 PM, America/Chicago
|
||||
|
||||
Regularly-Observed Holidays:
|
||||
- New Years Day (observed on monday when Jan 1 is a Sunday)
|
||||
- Martin Luther King Jr. Day (3rd Monday in January, only after 1998)
|
||||
- Washington's Birthday (aka President's Day, 3rd Monday in February)
|
||||
- Good Friday (two days before Easter Sunday)
|
||||
- Memorial Day (last Monday in May)
|
||||
- Independence Day (observed on the nearest weekday to July 4th)
|
||||
- Labor Day (first Monday in September)
|
||||
- Thanksgiving (fourth Thursday in November)
|
||||
- Christmas (observed on nearest weekday to December 25)
|
||||
|
||||
NOTE: The CME does not observe the following US Federal 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.
|
||||
- Closed on 4/27/1994 due to Richard Nixon's death.
|
||||
- Closed on 6/11/2004 due to Ronald Reagan's death.
|
||||
- Closed on 1/2/2007 due to Gerald Ford's death.
|
||||
- Closed at 1:00 PM on Wednesday, July 3rd, 2013
|
||||
- Closed at 1:00 PM on Friday, December 31, 1999
|
||||
- Closed at 1:00 PM on Friday, December 26, 1997
|
||||
- Closed at 1:00 PM on Friday, December 26, 2003
|
||||
|
||||
NOTE: The exchange was **not** closed early on Friday December 26, 2008,
|
||||
nor was it closed on Friday December 26, 2014. The next Thursday Christmas
|
||||
will be in 2025. If someone is still maintaining this code in 2025, then
|
||||
we've done alright...and we should check if it's a half day.
|
||||
"""
|
||||
|
||||
native_timezone = US_CENTRAL
|
||||
open_time = CME_OPEN
|
||||
close_time = CME_CLOSE
|
||||
open_offset = CME_OPEN_OFFSET
|
||||
close_offset = CME_CLOSE_OFFSET
|
||||
|
||||
holidays_calendar = CMEHolidayCalendar()
|
||||
special_opens_calendars = ()
|
||||
special_closes_calendars = []
|
||||
|
||||
holidays_adhoc = list(chain(
|
||||
September11Closings,
|
||||
HurricaneSandyClosings,
|
||||
USNationalDaysofMourning,
|
||||
))
|
||||
|
||||
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)
|
||||
@@ -0,0 +1,287 @@
|
||||
from datetime import time
|
||||
from pandas import Timedelta
|
||||
from pandas.tseries.holiday import(
|
||||
AbstractHolidayCalendar,
|
||||
Holiday,
|
||||
DateOffset,
|
||||
MO,
|
||||
weekend_to_monday,
|
||||
GoodFriday,
|
||||
EasterMonday,
|
||||
)
|
||||
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)
|
||||
|
||||
# New Year's Day
|
||||
LSENewYearsDay = Holiday(
|
||||
"New Year's Day",
|
||||
month=1,
|
||||
day=1,
|
||||
observance=weekend_to_monday,
|
||||
)
|
||||
# Early May bank holiday
|
||||
MayBank = Holiday(
|
||||
"Early May Bank Holiday",
|
||||
month=5,
|
||||
offset=DateOffset(weekday=MO(1)),
|
||||
)
|
||||
# Spring bank holiday
|
||||
SpringBank = Holiday(
|
||||
"Spring Bank Holiday",
|
||||
month=5,
|
||||
day=31,
|
||||
offset=DateOffset(weekday=MO(-1)),
|
||||
)
|
||||
# Summer bank holiday
|
||||
SummerBank = Holiday(
|
||||
"Summer Bank Holiday",
|
||||
month=8,
|
||||
day=31,
|
||||
offset=DateOffset(weekday=MO(-1)),
|
||||
)
|
||||
# Christmas
|
||||
Christmas = Holiday(
|
||||
"Christmas",
|
||||
month=12,
|
||||
day=25,
|
||||
)
|
||||
# If christmas day is Saturday Monday 27th is a holiday
|
||||
# If christmas day is sunday the Tuesday 27th is a holiday
|
||||
WeekendChristmas = Holiday(
|
||||
"Weekend Christmas",
|
||||
month=12,
|
||||
day=27,
|
||||
days_of_week=(MONDAY, TUESDAY),
|
||||
)
|
||||
# Boxing day
|
||||
BoxingDay = Holiday(
|
||||
"Boxing Day",
|
||||
month=12,
|
||||
day=26,
|
||||
)
|
||||
# If boxing day is saturday then Monday 28th is a holiday
|
||||
# If boxing day is sunday then Tuesday 28th is a holiday
|
||||
WeekendBoxingDay = Holiday(
|
||||
"Weekend Boxing Day",
|
||||
month=12,
|
||||
day=28,
|
||||
days_of_week=(MONDAY, TUESDAY),
|
||||
)
|
||||
|
||||
|
||||
class LSEHolidayCalendar(AbstractHolidayCalendar):
|
||||
"""
|
||||
Non-trading days for the LSE.
|
||||
|
||||
See NYSEExchangeCalendar for full description.
|
||||
"""
|
||||
rules = [
|
||||
LSENewYearsDay,
|
||||
GoodFriday,
|
||||
EasterMonday,
|
||||
MayBank,
|
||||
SpringBank,
|
||||
SummerBank,
|
||||
Christmas,
|
||||
WeekendChristmas,
|
||||
BoxingDay,
|
||||
WeekendBoxingDay,
|
||||
]
|
||||
|
||||
|
||||
class LSEExchangeCalendar(ExchangeCalendar):
|
||||
"""
|
||||
Exchange calendar for the London Stock Exchange
|
||||
|
||||
Open Time: 8:00 AM, GMT
|
||||
Close Time: 4:30 PM, GMT
|
||||
|
||||
Regularly-Observed Holidays:
|
||||
- New Years Day (observed on first business day on/after)
|
||||
- Good Friday
|
||||
- Easter Monday
|
||||
- Early May Bank Holiday (first Monday in May)
|
||||
- Spring Bank Holiday (last Monday in May)
|
||||
- Summer Bank Holiday (last Monday in May)
|
||||
- Christmas Day
|
||||
- Dec. 27th (if Christmas is on a weekend)
|
||||
- Boxing Day
|
||||
- Dec. 28th (if Boxing Day is on a weekend)
|
||||
"""
|
||||
|
||||
exchange_name = 'LSE'
|
||||
native_timezone = timezone('Europe/London')
|
||||
open_time = time(8, 1)
|
||||
close_time = time(16, 30)
|
||||
open_offset = 0
|
||||
close_offset = 0
|
||||
|
||||
holidays_calendar = LSEHolidayCalendar()
|
||||
special_opens_calendars = ()
|
||||
special_closes_calendars = ()
|
||||
|
||||
holidays_adhoc = ()
|
||||
|
||||
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,450 @@
|
||||
#
|
||||
# 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 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
|
||||
|
||||
# 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?
|
||||
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):
|
||||
"""
|
||||
Non-trading days for the NYSE.
|
||||
|
||||
See NYSEExchangeCalendar for full description.
|
||||
"""
|
||||
rules = [
|
||||
USNewYearsDay,
|
||||
USMartinLutherKingJrAfter1998,
|
||||
USPresidentsDay,
|
||||
GoodFriday,
|
||||
USMemorialDay,
|
||||
USIndependenceDay,
|
||||
USLaborDay,
|
||||
USThanksgivingDay,
|
||||
USIndependenceDay,
|
||||
Christmas,
|
||||
]
|
||||
|
||||
|
||||
class NYSE2PMCloseCalendar(AbstractHolidayCalendar):
|
||||
"""
|
||||
Holiday Calendar for 2PM closes for NYSE
|
||||
"""
|
||||
rules = [
|
||||
ChristmasEveBefore1993,
|
||||
USBlackFridayBefore1993,
|
||||
]
|
||||
|
||||
|
||||
class NYSEEarlyCloseCalendar(AbstractHolidayCalendar):
|
||||
"""
|
||||
Regular early close calendar for NYSE
|
||||
"""
|
||||
rules = [
|
||||
MonTuesThursBeforeIndependenceDay,
|
||||
FridayAfterIndependenceDayExcept2013,
|
||||
USBlackFridayInOrAfter1993,
|
||||
ChristmasEveInOrAfter1993,
|
||||
]
|
||||
|
||||
|
||||
class NYSEExchangeCalendar(ExchangeCalendar):
|
||||
"""
|
||||
Exchange calendar for NYSE
|
||||
|
||||
Open Time: 9:31 AM, US/Eastern
|
||||
Close Time: 4:00 PM, US/Eastern
|
||||
|
||||
Regularly-Observed Holidays:
|
||||
- New Years Day (observed on monday when Jan 1 is a Sunday)
|
||||
- Martin Luther King Jr. Day (3rd Monday in January, only after 1998)
|
||||
- Washington's Birthday (aka President's Day, 3rd Monday in February)
|
||||
- Good Friday (two days before Easter Sunday)
|
||||
- Memorial Day (last Monday in May)
|
||||
- Independence Day (observed on the nearest weekday to July 4th)
|
||||
- Labor Day (first Monday in September)
|
||||
- Thanksgiving (fourth Thursday in November)
|
||||
- Christmas (observed on nearest weekday to December 25)
|
||||
|
||||
NOTE: The NYSE does not observe the following US Federal 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.
|
||||
- Closed on 4/27/1994 due to Richard Nixon's death.
|
||||
- Closed on 6/11/2004 due to Ronald Reagan's death.
|
||||
- Closed on 1/2/2007 due to Gerald Ford's death.
|
||||
- Closed at 1:00 PM on Wednesday, July 3rd, 2013
|
||||
- Closed at 1:00 PM on Friday, December 31, 1999
|
||||
- Closed at 1:00 PM on Friday, December 26, 1997
|
||||
- Closed at 1:00 PM on Friday, December 26, 2003
|
||||
|
||||
NOTE: The exchange was **not** closed early on Friday December 26, 2008,
|
||||
nor was it closed on Friday December 26, 2014. The next Thursday Christmas
|
||||
will be in 2025. If someone is still maintaining this code in 2025, then
|
||||
we've done alright...and we should check if it's a half day.
|
||||
"""
|
||||
|
||||
exchange_name = 'NYSE'
|
||||
native_timezone = US_EASTERN
|
||||
open_time = NYSE_OPEN
|
||||
close_time = NYSE_CLOSE
|
||||
open_offset = NYSE_OPEN_OFFSET
|
||||
close_offset = NYSE_CLOSE_OFFSET
|
||||
|
||||
holidays_calendar = NYSEHolidayCalendar()
|
||||
special_opens_calendars = ()
|
||||
special_closes_calendars = [
|
||||
(NYSE_STANDARD_EARLY_CLOSE, NYSEEarlyCloseCalendar()),
|
||||
(time(14), NYSE2PMCloseCalendar()),
|
||||
]
|
||||
|
||||
holidays_adhoc = list(chain(
|
||||
September11Closings,
|
||||
HurricaneSandyClosings,
|
||||
USNationalDaysofMourning,
|
||||
))
|
||||
|
||||
special_opens_adhoc = ()
|
||||
special_closes_adhoc = [
|
||||
(NYSE_STANDARD_EARLY_CLOSE, ('1997-12-26',
|
||||
'1999-12-31',
|
||||
'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)
|
||||
@@ -0,0 +1,291 @@
|
||||
from datetime import time
|
||||
from pandas import Timedelta
|
||||
from pandas.tseries.holiday import(
|
||||
AbstractHolidayCalendar,
|
||||
Holiday,
|
||||
DateOffset,
|
||||
MO,
|
||||
weekend_to_monday,
|
||||
GoodFriday,
|
||||
)
|
||||
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.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",
|
||||
month=1,
|
||||
day=1,
|
||||
observance=weekend_to_monday,
|
||||
)
|
||||
# Ontario Family Day
|
||||
FamilyDay = Holiday(
|
||||
"Family Day",
|
||||
month=2,
|
||||
day=1,
|
||||
offset=DateOffset(weekday=MO(3)),
|
||||
start_date='2008-01-01',
|
||||
)
|
||||
# Victoria Day
|
||||
VictoriaDay = Holiday(
|
||||
'Victoria Day',
|
||||
month=5,
|
||||
day=25,
|
||||
offset=DateOffset(weekday=MO(-1)),
|
||||
)
|
||||
# Canada Day
|
||||
CanadaDay = Holiday(
|
||||
'Canada Day',
|
||||
month=7,
|
||||
day=1,
|
||||
observance=weekend_to_monday,
|
||||
)
|
||||
# Civic Holiday
|
||||
CivicHoliday = Holiday(
|
||||
'Civic Holiday',
|
||||
month=8,
|
||||
day=1,
|
||||
offset=DateOffset(weekday=MO(1)),
|
||||
)
|
||||
# Labor Day
|
||||
LaborDay = Holiday(
|
||||
'Labor Day',
|
||||
month=9,
|
||||
day=1,
|
||||
offset=DateOffset(weekday=MO(1)),
|
||||
)
|
||||
# Thanksgiving
|
||||
Thanksgiving = Holiday(
|
||||
'Thanksgiving',
|
||||
month=10,
|
||||
day=1,
|
||||
offset=DateOffset(weekday=MO(2)),
|
||||
)
|
||||
|
||||
|
||||
class TSXHolidayCalendar(AbstractHolidayCalendar):
|
||||
"""
|
||||
Non-trading days for the TSX.
|
||||
|
||||
See NYSEExchangeCalendar for full description.
|
||||
"""
|
||||
rules = [
|
||||
TSXNewYearsDay,
|
||||
FamilyDay,
|
||||
GoodFriday,
|
||||
VictoriaDay,
|
||||
CanadaDay,
|
||||
CivicHoliday,
|
||||
LaborDay,
|
||||
Thanksgiving,
|
||||
Christmas,
|
||||
WeekendChristmas,
|
||||
BoxingDay,
|
||||
WeekendBoxingDay,
|
||||
]
|
||||
|
||||
|
||||
class TSXExchangeCalendar(ExchangeCalendar):
|
||||
"""
|
||||
Exchange calendar for the Toronto Stock Exchange
|
||||
|
||||
Open Time: 9:30 AM, EST
|
||||
Close Time: 4:00 PM, EST
|
||||
|
||||
Regularly-Observed Holidays:
|
||||
- New Years Day (observed on first business day on/after)
|
||||
- Family Day (Third Monday in February after 2008)
|
||||
- Good Friday
|
||||
- Victoria Day (Monday before May 25th)
|
||||
- Canada Day (July 1st, observed first business day after)
|
||||
- Civic Holiday (First Monday in August)
|
||||
- Labor Day (First Monday in September)
|
||||
- Thanksgiving (Second Monday in October)
|
||||
- Christmas Day
|
||||
- Dec. 27th (if Christmas is on a weekend)
|
||||
- Boxing Day
|
||||
- Dec. 28th (if Boxing Day is on a weekend)
|
||||
"""
|
||||
|
||||
exchange_name = 'TSX'
|
||||
native_timezone = timezone('Canada/Atlantic')
|
||||
open_time = time(9, 31)
|
||||
close_time = time(16)
|
||||
open_offset = 0
|
||||
close_offset = 0
|
||||
|
||||
holidays_calendar = TSXHolidayCalendar()
|
||||
special_opens_calendars = ()
|
||||
special_closes_calendars = ()
|
||||
|
||||
holidays_adhoc = ()
|
||||
|
||||
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,416 @@
|
||||
#
|
||||
# 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'))
|
||||
+70
-67
@@ -22,6 +22,7 @@ import pytz
|
||||
|
||||
from .context_tricks import nop_context
|
||||
|
||||
from zipline.utils.calendars import normalize_date
|
||||
|
||||
__all__ = [
|
||||
'EventManager',
|
||||
@@ -204,7 +205,6 @@ class EventManager(object):
|
||||
context,
|
||||
data,
|
||||
dt,
|
||||
context.trading_environment,
|
||||
)
|
||||
|
||||
|
||||
@@ -218,17 +218,17 @@ class Event(namedtuple('Event', ['rule', 'callback'])):
|
||||
callback = callback or (lambda *args, **kwargs: None)
|
||||
return super(cls, cls).__new__(cls, rule=rule, callback=callback)
|
||||
|
||||
def handle_data(self, context, data, dt, env):
|
||||
def handle_data(self, context, data, dt):
|
||||
"""
|
||||
Calls the callable only when the rule is triggered.
|
||||
"""
|
||||
if self.rule.should_trigger(dt, env):
|
||||
if self.rule.should_trigger(dt):
|
||||
self.callback(context, data)
|
||||
|
||||
|
||||
class EventRule(six.with_metaclass(ABCMeta)):
|
||||
@abstractmethod
|
||||
def should_trigger(self, dt, env):
|
||||
def should_trigger(self, dt):
|
||||
"""
|
||||
Checks if the rule should trigger with its current state.
|
||||
This method should be pure and NOT mutate any state on the object.
|
||||
@@ -274,24 +274,23 @@ class ComposedRule(StatelessRule):
|
||||
self.second = second
|
||||
self.composer = composer
|
||||
|
||||
def should_trigger(self, dt, env):
|
||||
def should_trigger(self, dt):
|
||||
"""
|
||||
Composes the two rules with a lazy composer.
|
||||
"""
|
||||
return self.composer(
|
||||
self.first.should_trigger,
|
||||
self.second.should_trigger,
|
||||
dt,
|
||||
env
|
||||
dt
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def lazy_and(first_should_trigger, second_should_trigger, dt, env):
|
||||
def lazy_and(first_should_trigger, second_should_trigger, dt):
|
||||
"""
|
||||
Lazily ands the two rules. This will NOT call the should_trigger of the
|
||||
second rule if the first one returns False.
|
||||
"""
|
||||
return first_should_trigger(dt, env) and second_should_trigger(dt, env)
|
||||
return first_should_trigger(dt) and second_should_trigger(dt)
|
||||
|
||||
|
||||
class Always(StatelessRule):
|
||||
@@ -299,7 +298,7 @@ class Always(StatelessRule):
|
||||
A rule that always triggers.
|
||||
"""
|
||||
@staticmethod
|
||||
def always_trigger(dt, env):
|
||||
def always_trigger(dt):
|
||||
"""
|
||||
A should_trigger implementation that will always trigger.
|
||||
"""
|
||||
@@ -312,7 +311,7 @@ class Never(StatelessRule):
|
||||
A rule that never triggers.
|
||||
"""
|
||||
@staticmethod
|
||||
def never_trigger(dt, env):
|
||||
def never_trigger(dt):
|
||||
"""
|
||||
A should_trigger implementation that will never trigger.
|
||||
"""
|
||||
@@ -340,13 +339,12 @@ class AfterOpen(StatelessRule):
|
||||
|
||||
self._one_minute = datetime.timedelta(minutes=1)
|
||||
|
||||
def calculate_dates(self, dt, env):
|
||||
def calculate_dates(self, dt):
|
||||
# given a dt, find that day's open and period end (open + offset)
|
||||
self._period_start, self._period_close = env.get_open_and_close(dt)
|
||||
self._period_end = \
|
||||
self._period_start + self.offset - self._one_minute
|
||||
self._period_start, self._period_close = self.cal.open_and_close(dt)
|
||||
self._period_end = self._period_start + self.offset - self._one_minute
|
||||
|
||||
def should_trigger(self, dt, env):
|
||||
def should_trigger(self, dt):
|
||||
# There are two reasons why we might want to recalculate the dates.
|
||||
# One is the first time we ever call should_trigger, when
|
||||
# self._period_start is none. The second is when we're on a new day,
|
||||
@@ -360,7 +358,7 @@ class AfterOpen(StatelessRule):
|
||||
self._period_start is None or
|
||||
self._period_close <= dt
|
||||
):
|
||||
self.calculate_dates(dt, env)
|
||||
self.calculate_dates(dt)
|
||||
|
||||
return dt == self._period_end
|
||||
|
||||
@@ -384,14 +382,13 @@ class BeforeClose(StatelessRule):
|
||||
|
||||
self._one_minute = datetime.timedelta(minutes=1)
|
||||
|
||||
def calculate_dates(self, dt, env):
|
||||
def calculate_dates(self, dt):
|
||||
# given a dt, find that day's close and period start (close - offset)
|
||||
self._period_end = env.get_open_and_close(dt)[1]
|
||||
self._period_start = \
|
||||
self._period_end - self.offset
|
||||
self._period_end = self.cal.open_and_close(dt)[1]
|
||||
self._period_start = self._period_end - self.offset
|
||||
self._period_close = self._period_end
|
||||
|
||||
def should_trigger(self, dt, env):
|
||||
def should_trigger(self, dt):
|
||||
# There are two reasons why we might want to recalculate the dates.
|
||||
# One is the first time we ever call should_trigger, when
|
||||
# self._period_start is none. The second is when we're on a new day,
|
||||
@@ -405,7 +402,7 @@ class BeforeClose(StatelessRule):
|
||||
self._period_start is None or
|
||||
self._period_close <= dt
|
||||
):
|
||||
self.calculate_dates(dt, env)
|
||||
self.calculate_dates(dt)
|
||||
|
||||
return self._period_start == dt
|
||||
|
||||
@@ -414,8 +411,8 @@ class NotHalfDay(StatelessRule):
|
||||
"""
|
||||
A rule that only triggers when it is not a half day.
|
||||
"""
|
||||
def should_trigger(self, dt, env):
|
||||
return dt.date() not in env.early_closes
|
||||
def should_trigger(self, dt):
|
||||
return normalize_date(dt) not in self.cal.early_closes
|
||||
|
||||
|
||||
class TradingDayOfWeekRule(six.with_metaclass(ABCMeta, StatelessRule)):
|
||||
@@ -430,14 +427,14 @@ class TradingDayOfWeekRule(six.with_metaclass(ABCMeta, StatelessRule)):
|
||||
self.next_midnight_timestamp = None
|
||||
|
||||
@abstractmethod
|
||||
def date_func(self, dt, env):
|
||||
def date_func(self, dt, cal):
|
||||
raise NotImplementedError
|
||||
|
||||
def calculate_start_and_end(self, dt, env):
|
||||
def calculate_start_and_end(self, dt):
|
||||
next_trading_day = _coerce_datetime(
|
||||
env.add_trading_days(
|
||||
self.cal.add_trading_days(
|
||||
self.td_delta,
|
||||
self.date_func(dt, env),
|
||||
self.date_func(dt, self.cal),
|
||||
)
|
||||
)
|
||||
|
||||
@@ -446,29 +443,28 @@ class TradingDayOfWeekRule(six.with_metaclass(ABCMeta, StatelessRule)):
|
||||
while next_trading_day.isocalendar()[1] != dt.isocalendar()[1]:
|
||||
dt += datetime.timedelta(days=7)
|
||||
next_trading_day = _coerce_datetime(
|
||||
env.add_trading_days(
|
||||
self.cal.add_trading_days(
|
||||
self.td_delta,
|
||||
self.date_func(dt, env),
|
||||
self.date_func(dt, self.cal),
|
||||
)
|
||||
)
|
||||
|
||||
next_open, next_close = env.get_open_and_close(next_trading_day)
|
||||
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
|
||||
|
||||
def should_trigger(self, dt, env):
|
||||
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, env)
|
||||
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),
|
||||
env)
|
||||
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 \
|
||||
@@ -484,9 +480,9 @@ class NthTradingDayOfWeek(TradingDayOfWeekRule):
|
||||
This is zero-indexed, n=0 is the first trading day of the week.
|
||||
"""
|
||||
@staticmethod
|
||||
def get_first_trading_day_of_week(dt, env):
|
||||
def get_first_trading_day_of_week(dt, cal):
|
||||
prev = dt
|
||||
dt = env.previous_trading_day(dt)
|
||||
dt = cal.previous_trading_day(dt)
|
||||
# If we're on the first trading day of the TradingEnvironment,
|
||||
# calling previous_trading_day on it will return None, which
|
||||
# will blow up when we try and call .date() on it. The first
|
||||
@@ -497,14 +493,14 @@ class NthTradingDayOfWeek(TradingDayOfWeekRule):
|
||||
return prev
|
||||
while dt.date().weekday() < prev.date().weekday():
|
||||
prev = dt
|
||||
dt = env.previous_trading_day(dt)
|
||||
dt = cal.previous_trading_day(dt)
|
||||
if dt is None:
|
||||
return prev
|
||||
|
||||
if env.is_trading_day(prev):
|
||||
if cal.is_open_on_day(prev):
|
||||
return prev.date()
|
||||
else:
|
||||
return env.next_trading_day(prev).date()
|
||||
return cal.next_trading_day(prev).date()
|
||||
|
||||
date_func = get_first_trading_day_of_week
|
||||
|
||||
@@ -517,19 +513,19 @@ class NDaysBeforeLastTradingDayOfWeek(TradingDayOfWeekRule):
|
||||
super(NDaysBeforeLastTradingDayOfWeek, self).__init__(-n)
|
||||
|
||||
@staticmethod
|
||||
def get_last_trading_day_of_week(dt, env):
|
||||
def get_last_trading_day_of_week(dt, cal):
|
||||
prev = dt
|
||||
dt = env.next_trading_day(dt)
|
||||
dt = cal.next_trading_day(dt)
|
||||
# Traverse forward until we hit a week border, then jump back to the
|
||||
# previous trading day.
|
||||
while dt.date().weekday() > prev.date().weekday():
|
||||
prev = dt
|
||||
dt = env.next_trading_day(dt)
|
||||
dt = cal.next_trading_day(dt)
|
||||
|
||||
if env.is_trading_day(prev):
|
||||
if cal.is_open_on_day(prev):
|
||||
return prev.date()
|
||||
else:
|
||||
return env.previous_trading_day(prev).date()
|
||||
return cal.previous_trading_day(prev).date()
|
||||
|
||||
date_func = get_last_trading_day_of_week
|
||||
|
||||
@@ -546,30 +542,30 @@ class NthTradingDayOfMonth(StatelessRule):
|
||||
self.month = None
|
||||
self.day = None
|
||||
|
||||
def should_trigger(self, dt, env):
|
||||
return self.get_nth_trading_day_of_month(dt, env) == dt.date()
|
||||
def should_trigger(self, dt):
|
||||
return self.get_nth_trading_day_of_month(dt) == dt.date()
|
||||
|
||||
def get_nth_trading_day_of_month(self, dt, env):
|
||||
def get_nth_trading_day_of_month(self, dt):
|
||||
if self.month == dt.month:
|
||||
# We already computed the day for this month.
|
||||
return self.day
|
||||
|
||||
if not self.td_delta:
|
||||
self.day = self.get_first_trading_day_of_month(dt, env)
|
||||
self.day = self.get_first_trading_day_of_month(dt)
|
||||
else:
|
||||
self.day = env.add_trading_days(
|
||||
self.day = self.cal.add_trading_days(
|
||||
self.td_delta,
|
||||
self.get_first_trading_day_of_month(dt, env),
|
||||
self.get_first_trading_day_of_month(dt),
|
||||
).date()
|
||||
|
||||
return self.day
|
||||
|
||||
def get_first_trading_day_of_month(self, dt, env):
|
||||
def get_first_trading_day_of_month(self, dt):
|
||||
self.month = dt.month
|
||||
|
||||
dt = dt.replace(day=1)
|
||||
self.first_day = (dt if env.is_trading_day(dt)
|
||||
else env.next_trading_day(dt)).date()
|
||||
self.first_day = (dt if self.cal.is_open_on_day(dt)
|
||||
else self.cal.next_trading_day(dt)).date()
|
||||
return self.first_day
|
||||
|
||||
|
||||
@@ -584,25 +580,25 @@ class NDaysBeforeLastTradingDayOfMonth(StatelessRule):
|
||||
self.month = None
|
||||
self.day = None
|
||||
|
||||
def should_trigger(self, dt, env):
|
||||
return self.get_nth_to_last_trading_day_of_month(dt, env) == dt.date()
|
||||
def should_trigger(self, dt):
|
||||
return self.get_nth_to_last_trading_day_of_month(dt) == dt.date()
|
||||
|
||||
def get_nth_to_last_trading_day_of_month(self, dt, env):
|
||||
def get_nth_to_last_trading_day_of_month(self, dt):
|
||||
if self.month == dt.month:
|
||||
# We already computed the last day for this month.
|
||||
return self.day
|
||||
|
||||
if not self.td_delta:
|
||||
self.day = self.get_last_trading_day_of_month(dt, env)
|
||||
self.day = self.get_last_trading_day_of_month(dt)
|
||||
else:
|
||||
self.day = env.add_trading_days(
|
||||
self.day = self.cal.add_trading_days(
|
||||
self.td_delta,
|
||||
self.get_last_trading_day_of_month(dt, env),
|
||||
self.get_last_trading_day_of_month(dt),
|
||||
).date()
|
||||
|
||||
return self.day
|
||||
|
||||
def get_last_trading_day_of_month(self, dt, env):
|
||||
def get_last_trading_day_of_month(self, dt):
|
||||
self.month = dt.month
|
||||
|
||||
if dt.month == 12:
|
||||
@@ -614,7 +610,7 @@ class NDaysBeforeLastTradingDayOfMonth(StatelessRule):
|
||||
year = dt.year
|
||||
month = dt.month + 1
|
||||
|
||||
self.last_day = env.previous_trading_day(
|
||||
self.last_day = self.cal.previous_trading_day(
|
||||
dt.replace(year=year, month=month, day=1)
|
||||
).date()
|
||||
return self.last_day
|
||||
@@ -649,7 +645,7 @@ class OncePerDay(StatefulRule):
|
||||
|
||||
super(OncePerDay, self).__init__(rule)
|
||||
|
||||
def should_trigger(self, dt, env):
|
||||
def should_trigger(self, dt):
|
||||
if self.date is None or dt >= self.next_date:
|
||||
# initialize or reset for new date
|
||||
self.triggered = False
|
||||
@@ -659,7 +655,7 @@ class OncePerDay(StatefulRule):
|
||||
# to know if we've moved to the next day
|
||||
self.next_date = dt + pd.Timedelta(1, unit="d")
|
||||
|
||||
if not self.triggered and self.rule.should_trigger(dt, env):
|
||||
if not self.triggered and self.rule.should_trigger(dt):
|
||||
self.triggered = True
|
||||
return True
|
||||
|
||||
@@ -692,13 +688,20 @@ class time_rules(object):
|
||||
every_minute = Always
|
||||
|
||||
|
||||
def make_eventrule(date_rule, time_rule, half_days=True):
|
||||
def make_eventrule(date_rule, time_rule, cal, half_days=True):
|
||||
"""
|
||||
Constructs an event rule from the factory api.
|
||||
"""
|
||||
|
||||
# Insert the calendar in to the individual rules
|
||||
date_rule.cal = cal
|
||||
time_rule.cal = cal
|
||||
|
||||
if half_days:
|
||||
inner_rule = date_rule & time_rule
|
||||
else:
|
||||
inner_rule = date_rule & time_rule & NotHalfDay()
|
||||
nhd_rule = NotHalfDay()
|
||||
nhd_rule.cal = cal
|
||||
inner_rule = date_rule & time_rule & nhd_rule
|
||||
|
||||
return OncePerDay(rule=inner_rule)
|
||||
|
||||
+30
-120
@@ -17,24 +17,19 @@
|
||||
"""
|
||||
Factory functions to prepare useful data.
|
||||
"""
|
||||
import pytz
|
||||
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
from datetime import timedelta
|
||||
|
||||
from zipline.protocol import Event, DATASOURCE_TYPE
|
||||
from zipline.sources import (SpecificEquityTrades,
|
||||
DataFrameSource,
|
||||
DataPanelSource)
|
||||
from zipline.finance.trading import (
|
||||
SimulationParameters, TradingEnvironment, noop_load
|
||||
)
|
||||
from zipline.sources import SpecificEquityTrades
|
||||
from zipline.finance.trading import SimulationParameters
|
||||
from zipline.sources.test_source import create_trade
|
||||
from zipline.data.loader import ( # For backwards compatibility
|
||||
load_from_yahoo,
|
||||
load_bars_from_yahoo,
|
||||
)
|
||||
from zipline.utils.calendars import default_nyse_schedule
|
||||
|
||||
|
||||
__all__ = ['load_from_yahoo', 'load_bars_from_yahoo']
|
||||
@@ -45,16 +40,16 @@ def create_simulation_parameters(year=2006, start=None, end=None,
|
||||
num_days=None,
|
||||
data_frequency='daily',
|
||||
emission_rate='daily',
|
||||
env=None):
|
||||
if env is None:
|
||||
# Construct a complete environment with reasonable defaults
|
||||
env = TradingEnvironment(load=noop_load)
|
||||
trading_schedule=default_nyse_schedule):
|
||||
if start is None:
|
||||
start = pd.Timestamp("{0}-01-01".format(year), tz='UTC')
|
||||
if end is None:
|
||||
if num_days:
|
||||
start_index = env.trading_days.searchsorted(start)
|
||||
end = env.trading_days[start_index + num_days - 1]
|
||||
start_index = trading_schedule.all_execution_days\
|
||||
.searchsorted(start)
|
||||
end = trading_schedule.all_execution_days[
|
||||
start_index + num_days - 1
|
||||
]
|
||||
else:
|
||||
end = pd.Timestamp("{0}-12-31".format(year), tz='UTC')
|
||||
sim_params = SimulationParameters(
|
||||
@@ -63,31 +58,31 @@ def create_simulation_parameters(year=2006, start=None, end=None,
|
||||
capital_base=capital_base,
|
||||
data_frequency=data_frequency,
|
||||
emission_rate=emission_rate,
|
||||
env=env,
|
||||
trading_schedule=trading_schedule,
|
||||
)
|
||||
|
||||
return sim_params
|
||||
|
||||
|
||||
def get_next_trading_dt(current, interval, env):
|
||||
next_dt = pd.Timestamp(current).tz_convert(env.exchange_tz)
|
||||
def get_next_trading_dt(current, interval, trading_schedule):
|
||||
next_dt = pd.Timestamp(current).tz_convert(trading_schedule.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=env.exchange_tz)
|
||||
next_dt = pd.Timestamp(next_dt, tz=trading_schedule.tz)
|
||||
next_dt_utc = next_dt.tz_convert('UTC')
|
||||
if env.is_market_hours(next_dt_utc):
|
||||
if trading_schedule.is_executing_on_minute(next_dt_utc):
|
||||
break
|
||||
next_dt = next_dt_utc.tz_convert(env.exchange_tz)
|
||||
next_dt = next_dt_utc.tz_convert(trading_schedule.tz)
|
||||
|
||||
return next_dt_utc
|
||||
|
||||
|
||||
def create_trade_history(sid, prices, amounts, interval, sim_params, env,
|
||||
source_id="test_factory"):
|
||||
def create_trade_history(sid, prices, amounts, interval, sim_params,
|
||||
trading_schedule, source_id="test_factory"):
|
||||
trades = []
|
||||
current = sim_params.first_open
|
||||
|
||||
@@ -100,7 +95,7 @@ def create_trade_history(sid, prices, amounts, interval, sim_params, env,
|
||||
trade_dt = current
|
||||
trade = create_trade(sid, price, amount, trade_dt, source_id)
|
||||
trades.append(trade)
|
||||
current = get_next_trading_dt(current, interval, env)
|
||||
current = get_next_trading_dt(current, interval, trading_schedule)
|
||||
|
||||
assert len(trades) == len(prices)
|
||||
return trades
|
||||
@@ -171,12 +166,13 @@ def create_commission(sid, value, datetime):
|
||||
return txn
|
||||
|
||||
|
||||
def create_txn_history(sid, priceList, amtList, interval, sim_params, env):
|
||||
def create_txn_history(sid, priceList, amtList, interval, sim_params,
|
||||
trading_schedule):
|
||||
txns = []
|
||||
current = sim_params.first_open
|
||||
|
||||
for price, amount in zip(priceList, amtList):
|
||||
current = get_next_trading_dt(current, interval, env)
|
||||
current = get_next_trading_dt(current, interval, trading_schedule)
|
||||
|
||||
txns.append(create_txn(sid, price, amount, current))
|
||||
current = current + interval
|
||||
@@ -193,7 +189,8 @@ def create_returns_from_list(returns, sim_params):
|
||||
data=returns)
|
||||
|
||||
|
||||
def create_daily_trade_source(sids, sim_params, env, concurrent=False):
|
||||
def create_daily_trade_source(sids, sim_params, env, trading_schedule,
|
||||
concurrent=False):
|
||||
"""
|
||||
creates trade_count trades for each sid in sids list.
|
||||
first trade will be on sim_params.period_start, and daily
|
||||
@@ -205,11 +202,13 @@ def create_daily_trade_source(sids, sim_params, env, concurrent=False):
|
||||
timedelta(days=1),
|
||||
sim_params,
|
||||
env=env,
|
||||
trading_schedule=trading_schedule,
|
||||
concurrent=concurrent,
|
||||
)
|
||||
|
||||
|
||||
def create_minutely_trade_source(sids, sim_params, env, concurrent=False):
|
||||
def create_minutely_trade_source(sids, sim_params, env, trading_schedule,
|
||||
concurrent=False):
|
||||
"""
|
||||
creates trade_count trades for each sid in sids list.
|
||||
first trade will be on sim_params.period_start, and every minute
|
||||
@@ -221,16 +220,17 @@ def create_minutely_trade_source(sids, sim_params, env, concurrent=False):
|
||||
timedelta(minutes=1),
|
||||
sim_params,
|
||||
env=env,
|
||||
trading_schedule=trading_schedule,
|
||||
concurrent=concurrent,
|
||||
)
|
||||
|
||||
|
||||
def create_trade_source(sids, trade_time_increment, sim_params, env,
|
||||
concurrent=False):
|
||||
trading_schedule, 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 env.is_market_hours(sim_params.period_end):
|
||||
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
|
||||
# data source
|
||||
@@ -246,98 +246,8 @@ def create_trade_source(sids, trade_time_increment, sim_params, env,
|
||||
'filter': sids,
|
||||
'concurrent': concurrent,
|
||||
'env': env,
|
||||
'trading_schedule': trading_schedule,
|
||||
}
|
||||
source = SpecificEquityTrades(*args, **kwargs)
|
||||
|
||||
return source
|
||||
|
||||
|
||||
def create_test_df_source(sim_params=None, env=None, bars='daily'):
|
||||
if bars == 'daily':
|
||||
freq = pd.datetools.BDay()
|
||||
elif bars == 'minute':
|
||||
freq = pd.datetools.Minute()
|
||||
else:
|
||||
raise ValueError('%s bars not understood.' % bars)
|
||||
|
||||
if sim_params and bars == 'daily':
|
||||
index = sim_params.trading_days
|
||||
else:
|
||||
if env is None:
|
||||
env = TradingEnvironment(load=noop_load)
|
||||
|
||||
start = pd.datetime(1990, 1, 3, 0, 0, 0, 0, pytz.utc)
|
||||
end = pd.datetime(1990, 1, 8, 0, 0, 0, 0, pytz.utc)
|
||||
|
||||
days = env.days_in_range(start, end)
|
||||
|
||||
if bars == 'daily':
|
||||
index = days
|
||||
if bars == 'minute':
|
||||
index = pd.DatetimeIndex([], freq=freq)
|
||||
|
||||
for day in days:
|
||||
day_index = env.market_minutes_for_day(day)
|
||||
index = index.append(day_index)
|
||||
|
||||
x = np.arange(1, len(index) + 1)
|
||||
|
||||
df = pd.DataFrame(x, index=index, columns=[0])
|
||||
|
||||
return DataFrameSource(df), df
|
||||
|
||||
|
||||
def create_test_panel_source(sim_params=None, env=None, source_type=None):
|
||||
start = sim_params.first_open \
|
||||
if sim_params else pd.datetime(1990, 1, 3, 0, 0, 0, 0, pytz.utc)
|
||||
|
||||
end = sim_params.last_close \
|
||||
if sim_params else pd.datetime(1990, 1, 8, 0, 0, 0, 0, pytz.utc)
|
||||
|
||||
if env is None:
|
||||
env = TradingEnvironment(load=noop_load)
|
||||
|
||||
index = env.days_in_range(start, end)
|
||||
|
||||
price = np.arange(0, len(index))
|
||||
volume = np.ones(len(index)) * 1000
|
||||
|
||||
arbitrary = np.ones(len(index))
|
||||
|
||||
df = pd.DataFrame({'price': price,
|
||||
'volume': volume,
|
||||
'arbitrary': arbitrary},
|
||||
index=index)
|
||||
if source_type:
|
||||
df['type'] = source_type
|
||||
|
||||
panel = pd.Panel.from_dict({0: df})
|
||||
|
||||
return DataPanelSource(panel), panel
|
||||
|
||||
|
||||
def create_test_panel_ohlc_source(sim_params, env):
|
||||
start = sim_params.first_open \
|
||||
if sim_params else pd.datetime(1990, 1, 3, 0, 0, 0, 0, pytz.utc)
|
||||
|
||||
end = sim_params.last_close \
|
||||
if sim_params else pd.datetime(1990, 1, 8, 0, 0, 0, 0, pytz.utc)
|
||||
|
||||
index = env.days_in_range(start, end)
|
||||
price = np.arange(0, len(index)) + 100
|
||||
high = price * 1.05
|
||||
low = price * 0.95
|
||||
open_ = price + .1 * (price % 2 - .5)
|
||||
volume = np.ones(len(index)) * 1000
|
||||
arbitrary = np.ones(len(index))
|
||||
|
||||
df = pd.DataFrame({'price': price,
|
||||
'high': high,
|
||||
'low': low,
|
||||
'open': open_,
|
||||
'volume': volume,
|
||||
'arbitrary': arbitrary},
|
||||
index=index)
|
||||
panel = pd.Panel.from_dict({0: df})
|
||||
|
||||
return DataPanelSource(panel), panel
|
||||
|
||||
@@ -5,6 +5,9 @@ from itertools import product
|
||||
import operator as op
|
||||
|
||||
import pandas as pd
|
||||
from distutils.version import StrictVersion
|
||||
|
||||
pandas_version = StrictVersion(pd.__version__)
|
||||
|
||||
|
||||
def explode(df):
|
||||
@@ -17,39 +20,28 @@ def explode(df):
|
||||
|
||||
|
||||
try:
|
||||
# pandas 0.16 compat
|
||||
_df_sort_values = pd.DataFrame.sort_values
|
||||
_series_sort_values = pd.Series.sort_values
|
||||
# This branch is hit in pandas 17
|
||||
sort_values = pd.DataFrame.sort_values
|
||||
except AttributeError:
|
||||
_df_sort_values = pd.DataFrame.sort
|
||||
_series_sort_values = pd.Series.sort
|
||||
# This branch is hit in pandas 16
|
||||
sort_values = pd.DataFrame.sort
|
||||
|
||||
|
||||
def sort_values(ob, *args, **kwargs):
|
||||
if isinstance(ob, pd.DataFrame):
|
||||
return _df_sort_values(ob, *args, **kwargs)
|
||||
elif isinstance(ob, pd.Series):
|
||||
return _series_sort_values(ob, *args, **kwargs)
|
||||
raise ValueError(
|
||||
'sort_values expected a dataframe or series, not %s: %r' % (
|
||||
type(ob).__name__, ob,
|
||||
),
|
||||
)
|
||||
if pandas_version >= StrictVersion('0.17.1'):
|
||||
july_5th_holiday_observance = lambda dtix: dtix[dtix.year != 2013]
|
||||
else:
|
||||
july_5th_holiday_observance = lambda dt: None if dt.year == 2013 else dt
|
||||
|
||||
|
||||
def _time_to_micros(time):
|
||||
"""Convert a time into microseconds since midnight.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
time : datetime.time
|
||||
The time to convert.
|
||||
|
||||
Returns
|
||||
-------
|
||||
us : int
|
||||
The number of microseconds since midnight.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This does not account for leap seconds or daylight savings.
|
||||
@@ -67,7 +59,6 @@ _opmap = dict(zip(
|
||||
def mask_between_time(dts, start, end, include_start=True, include_end=True):
|
||||
"""Return a mask of all of the datetimes in ``dts`` that are between
|
||||
``start`` and ``end``.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dts : pd.DatetimeIndex
|
||||
@@ -80,12 +71,10 @@ def mask_between_time(dts, start, end, include_start=True, include_end=True):
|
||||
Inclusive on ``start``.
|
||||
include_end : bool, optional
|
||||
Inclusive on ``end``.
|
||||
|
||||
Returns
|
||||
-------
|
||||
mask : np.ndarray[bool]
|
||||
A bool array masking ``dts``.
|
||||
|
||||
See Also
|
||||
--------
|
||||
:meth:`pandas.DatetimeIndex.indexer_between_time`
|
||||
|
||||
@@ -20,6 +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
|
||||
import zipline.utils.paths as pth
|
||||
|
||||
|
||||
@@ -129,7 +130,7 @@ def _run(handle_data,
|
||||
)
|
||||
env = TradingEnvironment(asset_db_path=connstr)
|
||||
data = DataPortal(
|
||||
env,
|
||||
env.asset_finder, default_nyse_schedule,
|
||||
first_trading_day=bundle_data.minute_bar_reader.first_trading_day,
|
||||
equity_minute_reader=bundle_data.minute_bar_reader,
|
||||
equity_daily_reader=bundle_data.daily_bar_reader,
|
||||
|
||||
@@ -2,6 +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
|
||||
|
||||
|
||||
def create_test_zipline(**config):
|
||||
@@ -37,16 +38,9 @@ def create_test_zipline(**config):
|
||||
"argument 'sid_list' or 'sid'")
|
||||
|
||||
concurrent_trades = config.get('concurrent_trades', False)
|
||||
|
||||
if 'order_count' in config:
|
||||
order_count = config['order_count']
|
||||
else:
|
||||
order_count = 100
|
||||
|
||||
if 'order_amount' in config:
|
||||
order_amount = config['order_amount']
|
||||
else:
|
||||
order_amount = 100
|
||||
order_count = config.get('order_count', 100)
|
||||
order_amount = config.get('order_amount', 100)
|
||||
trading_schedule = config.get('trading_schedule', default_nyse_schedule)
|
||||
|
||||
# -------------------
|
||||
# Create the Algo
|
||||
@@ -60,6 +54,7 @@ def create_test_zipline(**config):
|
||||
order_count,
|
||||
sim_params=config.get('sim_params',
|
||||
factory.create_simulation_parameters()),
|
||||
trading_schedule=trading_schedule,
|
||||
slippage=config.get('slippage'),
|
||||
identifiers=sid_list
|
||||
)
|
||||
@@ -75,6 +70,7 @@ def create_test_zipline(**config):
|
||||
sid_list,
|
||||
test_algo.sim_params,
|
||||
test_algo.trading_environment,
|
||||
trading_schedule,
|
||||
concurrent=concurrent_trades,
|
||||
)
|
||||
|
||||
@@ -86,7 +82,8 @@ def create_test_zipline(**config):
|
||||
trades_by_sid[trade.sid].append(trade)
|
||||
|
||||
data_portal = create_data_portal_from_trade_history(
|
||||
config['env'],
|
||||
config['env'].asset_finder,
|
||||
trading_schedule,
|
||||
config['tempdir'],
|
||||
config['sim_params'],
|
||||
trades_by_sid
|
||||
|
||||
@@ -19,6 +19,10 @@ from datetime import datetime
|
||||
from dateutil import rrule
|
||||
from functools import partial
|
||||
|
||||
# IMPORTANT: This module is deprecated and is only here for temporary backwards
|
||||
# compatibility. Look at the `zipline.utils.calendars.trading_schedule`
|
||||
# module, as well as the calendar definitions in `zipline.utils.calendars`.
|
||||
|
||||
start = 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
|
||||
|
||||
@@ -1,315 +0,0 @@
|
||||
#
|
||||
# Copyright 2014 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 pytz
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil import rrule
|
||||
from zipline.utils.tradingcalendar import end, canonicalize_datetime, \
|
||||
get_open_and_closes
|
||||
|
||||
start = pd.Timestamp('1994-01-01', tz='UTC')
|
||||
|
||||
|
||||
def get_non_trading_days(start, end):
|
||||
non_trading_rules = []
|
||||
|
||||
start = canonicalize_datetime(start)
|
||||
end = canonicalize_datetime(end)
|
||||
|
||||
weekends = rrule.rrule(
|
||||
rrule.YEARLY,
|
||||
byweekday=(rrule.SA, rrule.SU),
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(weekends)
|
||||
|
||||
# Universal confraternization
|
||||
conf_universal = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
byyearday=1,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(conf_universal)
|
||||
|
||||
# Sao Paulo city birthday
|
||||
aniversario_sao_paulo = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=1,
|
||||
bymonthday=25,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(aniversario_sao_paulo)
|
||||
|
||||
# Carnival Monday
|
||||
carnaval_segunda = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
byeaster=-48,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(carnaval_segunda)
|
||||
|
||||
# Carnival Tuesday
|
||||
carnaval_terca = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
byeaster=-47,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(carnaval_terca)
|
||||
|
||||
# Passion of the Christ
|
||||
sexta_paixao = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
byeaster=-2,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(sexta_paixao)
|
||||
|
||||
# Corpus Christi
|
||||
corpus_christi = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
byeaster=60,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(corpus_christi)
|
||||
|
||||
tiradentes = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=4,
|
||||
bymonthday=21,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(tiradentes)
|
||||
|
||||
# Labor day
|
||||
dia_trabalho = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=5,
|
||||
bymonthday=1,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(dia_trabalho)
|
||||
|
||||
# Constitutionalist Revolution
|
||||
constitucionalista = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=7,
|
||||
bymonthday=9,
|
||||
cache=True,
|
||||
dtstart=datetime(1997, 1, 1, tzinfo=pytz.utc),
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(constitucionalista)
|
||||
|
||||
# Independency day
|
||||
independencia = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=9,
|
||||
bymonthday=7,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(independencia)
|
||||
|
||||
# Our Lady of Aparecida
|
||||
aparecida = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=10,
|
||||
bymonthday=12,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(aparecida)
|
||||
|
||||
# All Souls' day
|
||||
finados = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=11,
|
||||
bymonthday=2,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(finados)
|
||||
|
||||
# Proclamation of the Republic
|
||||
proclamacao_republica = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=11,
|
||||
bymonthday=15,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(proclamacao_republica)
|
||||
|
||||
# Day of Black Awareness
|
||||
consciencia_negra = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=11,
|
||||
bymonthday=20,
|
||||
cache=True,
|
||||
dtstart=datetime(2004, 1, 1, tzinfo=pytz.utc),
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(consciencia_negra)
|
||||
|
||||
# Christmas Eve
|
||||
vespera_natal = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=12,
|
||||
bymonthday=24,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(vespera_natal)
|
||||
|
||||
# Christmas
|
||||
natal = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=12,
|
||||
bymonthday=25,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(natal)
|
||||
|
||||
# New Year Eve
|
||||
ano_novo = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=12,
|
||||
bymonthday=31,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(ano_novo)
|
||||
|
||||
# New Year Eve on saturday
|
||||
ano_novo_sab = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=12,
|
||||
bymonthday=30,
|
||||
byweekday=rrule.FR,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(ano_novo_sab)
|
||||
|
||||
non_trading_ruleset = rrule.rruleset()
|
||||
|
||||
for rule in non_trading_rules:
|
||||
non_trading_ruleset.rrule(rule)
|
||||
|
||||
non_trading_days = non_trading_ruleset.between(start, end, inc=True)
|
||||
|
||||
# World Cup 2014 Opening
|
||||
non_trading_days.append(datetime(2014, 6, 12, tzinfo=pytz.utc))
|
||||
|
||||
non_trading_days.sort()
|
||||
return pd.DatetimeIndex(non_trading_days)
|
||||
|
||||
non_trading_days = get_non_trading_days(start, end)
|
||||
trading_day = pd.tseries.offsets.CDay(holidays=non_trading_days)
|
||||
|
||||
|
||||
def get_trading_days(start, end, trading_day=trading_day):
|
||||
return pd.date_range(start=start.date(),
|
||||
end=end.date(),
|
||||
freq=trading_day).tz_localize('UTC')
|
||||
|
||||
trading_days = get_trading_days(start, end)
|
||||
|
||||
|
||||
# Ash Wednesday
|
||||
quarta_cinzas = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
byeaster=-46,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
|
||||
|
||||
def get_early_closes(start, end):
|
||||
# TSX closed at 1:00 PM on december 24th.
|
||||
|
||||
start = canonicalize_datetime(start)
|
||||
end = canonicalize_datetime(end)
|
||||
|
||||
early_close_rules = []
|
||||
|
||||
early_close_rules.append(quarta_cinzas)
|
||||
|
||||
early_close_ruleset = rrule.rruleset()
|
||||
|
||||
for rule in early_close_rules:
|
||||
early_close_ruleset.rrule(rule)
|
||||
early_closes = early_close_ruleset.between(start, end, inc=True)
|
||||
|
||||
early_closes.sort()
|
||||
return pd.DatetimeIndex(early_closes)
|
||||
|
||||
early_closes = get_early_closes(start, end)
|
||||
|
||||
|
||||
def get_open_and_close(day, early_closes):
|
||||
# only "early close" event in Bovespa actually is a late start
|
||||
# as the market only opens at 1pm
|
||||
open_hour = 13 if day in quarta_cinzas else 10
|
||||
market_open = pd.Timestamp(
|
||||
datetime(
|
||||
year=day.year,
|
||||
month=day.month,
|
||||
day=day.day,
|
||||
hour=open_hour,
|
||||
minute=00),
|
||||
tz='America/Sao_Paulo').tz_convert('UTC')
|
||||
market_close = pd.Timestamp(
|
||||
datetime(
|
||||
year=day.year,
|
||||
month=day.month,
|
||||
day=day.day,
|
||||
hour=16),
|
||||
tz='America/Sao_Paulo').tz_convert('UTC')
|
||||
|
||||
return market_open, market_close
|
||||
|
||||
open_and_closes = get_open_and_closes(trading_days, early_closes,
|
||||
get_open_and_close)
|
||||
@@ -1,186 +0,0 @@
|
||||
#
|
||||
# Copyright 2013 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.
|
||||
|
||||
|
||||
# References:
|
||||
# http://www.londonstockexchange.com
|
||||
# /about-the-exchange/company-overview/business-days/business-days.htm
|
||||
# http://en.wikipedia.org/wiki/Bank_holiday
|
||||
# http://www.adviceguide.org.uk/england/work_e/work_time_off_work_e/
|
||||
# bank_and_public_holidays.htm
|
||||
|
||||
import pytz
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil import rrule
|
||||
from zipline.utils.tradingcalendar import end
|
||||
|
||||
start = datetime(2002, 1, 1, tzinfo=pytz.utc)
|
||||
|
||||
non_trading_rules = []
|
||||
# Weekends
|
||||
weekends = rrule.rrule(
|
||||
rrule.YEARLY,
|
||||
byweekday=(rrule.SA, rrule.SU),
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(weekends)
|
||||
# New Year's Day
|
||||
new_year = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
byyearday=1,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
# If new years day is on Saturday then Monday 3rd is a holiday
|
||||
# If new years day is on Sunday then Monday 2nd is a holiday
|
||||
weekend_new_year = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=1,
|
||||
bymonthday=[2, 3],
|
||||
byweekday=(rrule.MO),
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(new_year)
|
||||
non_trading_rules.append(weekend_new_year)
|
||||
# Good Friday
|
||||
good_friday = rrule.rrule(
|
||||
rrule.DAILY,
|
||||
byeaster=-2,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(good_friday)
|
||||
# Easter Monday
|
||||
easter_monday = rrule.rrule(
|
||||
rrule.DAILY,
|
||||
byeaster=1,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(easter_monday)
|
||||
# Early May Bank Holiday (1st Monday in May)
|
||||
may_bank = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=5,
|
||||
byweekday=(rrule.MO(1)),
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(may_bank)
|
||||
# Spring Bank Holiday (Last Monday in May)
|
||||
spring_bank = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=5,
|
||||
byweekday=(rrule.MO(-1)),
|
||||
cache=True,
|
||||
dtstart=datetime(2003, 1, 1, tzinfo=pytz.utc),
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(spring_bank)
|
||||
# Summer Bank Holiday (Last Monday in August)
|
||||
summer_bank = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=8,
|
||||
byweekday=(rrule.MO(-1)),
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(summer_bank)
|
||||
# Christmas Day
|
||||
christmas = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=12,
|
||||
bymonthday=25,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
# If christmas day is Saturday Monday 27th is a holiday
|
||||
# If christmas day is sunday the Tuesday 27th is a holiday
|
||||
weekend_christmas = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=12,
|
||||
bymonthday=27,
|
||||
byweekday=(rrule.MO, rrule.TU),
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
|
||||
non_trading_rules.append(christmas)
|
||||
non_trading_rules.append(weekend_christmas)
|
||||
# Boxing Day
|
||||
boxing_day = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=12,
|
||||
bymonthday=26,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
# If boxing day is saturday then Monday 28th is a holiday
|
||||
# If boxing day is sunday then Tuesday 28th is a holiday
|
||||
weekend_boxing_day = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=12,
|
||||
bymonthday=28,
|
||||
byweekday=(rrule.MO, rrule.TU),
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
|
||||
non_trading_rules.append(boxing_day)
|
||||
non_trading_rules.append(weekend_boxing_day)
|
||||
|
||||
non_trading_ruleset = rrule.rruleset()
|
||||
|
||||
# In 2002 May bank holiday was moved to 4th June to follow the Queens
|
||||
# Golden Jubilee
|
||||
non_trading_ruleset.exdate(datetime(2002, 9, 27, tzinfo=pytz.utc))
|
||||
non_trading_ruleset.rdate(datetime(2002, 6, 3, tzinfo=pytz.utc))
|
||||
non_trading_ruleset.rdate(datetime(2002, 6, 4, tzinfo=pytz.utc))
|
||||
# TODO: not sure why Feb 18 2008 is not available in the yahoo data
|
||||
non_trading_ruleset.rdate(datetime(2008, 2, 18, tzinfo=pytz.utc))
|
||||
# In 2011 The Friday before Mayday was the Royal Wedding
|
||||
non_trading_ruleset.rdate(datetime(2011, 4, 29, tzinfo=pytz.utc))
|
||||
# In 2012 May bank holiday was moved to 4th June to preceed the Queens
|
||||
# Diamond Jubilee
|
||||
non_trading_ruleset.exdate(datetime(2012, 5, 28, tzinfo=pytz.utc))
|
||||
non_trading_ruleset.rdate(datetime(2012, 6, 4, tzinfo=pytz.utc))
|
||||
non_trading_ruleset.rdate(datetime(2012, 6, 5, tzinfo=pytz.utc))
|
||||
|
||||
for rule in non_trading_rules:
|
||||
non_trading_ruleset.rrule(rule)
|
||||
|
||||
non_trading_days = non_trading_ruleset.between(start, end, inc=True)
|
||||
non_trading_day_index = pd.DatetimeIndex(sorted(non_trading_days))
|
||||
|
||||
business_days = pd.DatetimeIndex(start=start, end=end,
|
||||
freq=pd.datetools.BDay())
|
||||
|
||||
trading_days = business_days.difference(non_trading_day_index)
|
||||
@@ -1,364 +0,0 @@
|
||||
#
|
||||
# Copyright 2014 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 pytz
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil import rrule
|
||||
from zipline.utils.tradingcalendar import end, canonicalize_datetime, \
|
||||
get_open_and_closes
|
||||
|
||||
start = pd.Timestamp('1994-01-01', tz='UTC')
|
||||
|
||||
|
||||
def get_non_trading_days(start, end):
|
||||
non_trading_rules = []
|
||||
|
||||
start = canonicalize_datetime(start)
|
||||
end = canonicalize_datetime(end)
|
||||
|
||||
weekends = rrule.rrule(
|
||||
rrule.YEARLY,
|
||||
byweekday=(rrule.SA, rrule.SU),
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(weekends)
|
||||
|
||||
new_years = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
byyearday=1,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(new_years)
|
||||
|
||||
new_years_sunday = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
byyearday=2,
|
||||
byweekday=rrule.MO,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(new_years_sunday)
|
||||
|
||||
new_years_saturday = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
byyearday=3,
|
||||
byweekday=rrule.MO,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(new_years_saturday)
|
||||
|
||||
# Family day in Ontario, starting in 2008, third monday of February
|
||||
family_day = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=2,
|
||||
byweekday=(rrule.MO(3)),
|
||||
cache=True,
|
||||
dtstart=datetime(2008, 1, 1, tzinfo=pytz.utc),
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(family_day)
|
||||
|
||||
good_friday = rrule.rrule(
|
||||
rrule.DAILY,
|
||||
byeaster=-2,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(good_friday)
|
||||
|
||||
# Monday prior to May 25th.
|
||||
victoria_day = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=5,
|
||||
byweekday=rrule.MO,
|
||||
bymonthday=[24, 23, 22, 21, 20, 19, 18],
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(victoria_day)
|
||||
|
||||
july_1st = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=7,
|
||||
bymonthday=1,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(july_1st)
|
||||
|
||||
july_1st_sunday = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=7,
|
||||
bymonthday=2,
|
||||
byweekday=rrule.MO,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(july_1st_sunday)
|
||||
|
||||
july_1st_saturday = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=7,
|
||||
bymonthday=3,
|
||||
byweekday=rrule.MO,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(july_1st_saturday)
|
||||
|
||||
civic_holiday = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=8,
|
||||
byweekday=rrule.MO(1),
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(civic_holiday)
|
||||
|
||||
labor_day = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=9,
|
||||
byweekday=(rrule.MO(1)),
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(labor_day)
|
||||
|
||||
thanksgiving = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=10,
|
||||
byweekday=(rrule.MO(2)),
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(thanksgiving)
|
||||
|
||||
christmas = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=12,
|
||||
bymonthday=25,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(christmas)
|
||||
|
||||
# If Christmas is a Sunday then the 26th, a Monday is observed.
|
||||
# (but that would be boxing day), so the 27th is also observed.
|
||||
christmas_sunday = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=12,
|
||||
bymonthday=27,
|
||||
byweekday=rrule.TU,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(christmas_sunday)
|
||||
|
||||
# If Christmas is a Saturday then the 27th, a monday is observed.
|
||||
christmas_saturday = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=12,
|
||||
bymonthday=27,
|
||||
byweekday=rrule.MO,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(christmas_saturday)
|
||||
|
||||
boxing_day = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=12,
|
||||
bymonthday=26,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(boxing_day)
|
||||
|
||||
# if boxing day is a sunday, the Christmas was saturday.
|
||||
# Christmas is observed on the 27th, a month and boxing day is observed
|
||||
# on the 28th, a tuesday.
|
||||
boxing_day_sunday = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=12,
|
||||
bymonthday=28,
|
||||
byweekday=rrule.TU,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(boxing_day_sunday)
|
||||
|
||||
# If boxing day is a Saturday then the 28th, a monday is observed.
|
||||
boxing_day_saturday = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=12,
|
||||
bymonthday=28,
|
||||
byweekday=rrule.MO,
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
non_trading_rules.append(boxing_day_saturday)
|
||||
|
||||
non_trading_ruleset = rrule.rruleset()
|
||||
|
||||
for rule in non_trading_rules:
|
||||
non_trading_ruleset.rrule(rule)
|
||||
|
||||
non_trading_days = non_trading_ruleset.between(start, end, inc=True)
|
||||
|
||||
# Add September 11th closings
|
||||
# The TSX was open for 71 minutes on September 11, 2011.
|
||||
# It was closed on the 12th and reopened on the 13th.
|
||||
# http://www.cbc.ca/news2/interactives/map-tsx/
|
||||
#
|
||||
# September 2001
|
||||
# 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
|
||||
|
||||
non_trading_days.append(
|
||||
datetime(2001, 9, 12, tzinfo=pytz.utc))
|
||||
|
||||
non_trading_days.sort()
|
||||
return pd.DatetimeIndex(non_trading_days)
|
||||
|
||||
non_trading_days = get_non_trading_days(start, end)
|
||||
trading_day = pd.tseries.offsets.CDay(holidays=non_trading_days)
|
||||
|
||||
|
||||
def get_trading_days(start, end, trading_day=trading_day):
|
||||
return pd.date_range(start=start.date(),
|
||||
end=end.date(),
|
||||
freq=trading_day).tz_localize('UTC')
|
||||
|
||||
trading_days = get_trading_days(start, end)
|
||||
|
||||
# Days in Environment but not in Calendar (using ^GSPTSE as bm_symbol):
|
||||
# --------------------------------------------------------------------
|
||||
# Used http://web.tmxmoney.com/pricehistory.php?qm_page=61468&qm_symbol=^TSX
|
||||
# to check whether exchange was open on these days.
|
||||
# 1994-07-01 - July 1st, Yahoo Finance has Volume = 0
|
||||
# 1996-07-01 - July 1st, Yahoo Finance has Volume = 0
|
||||
# 1996-08-05 - Civic Holiday, Yahoo Finance has Volume = 0
|
||||
# 1997-07-01 - July 1st, Yahoo Finance has Volume = 0
|
||||
# 1997-08-04 - Civic Holiday, Yahoo Finance has Volume = 0
|
||||
# 2001-05-21 - Victoria day, Yahoo Finance has Volume = 0
|
||||
# 2004-10-11 - Closed, Thanksgiving - Confirmed closed
|
||||
# 2004-12-28 - Closed, Boxing Day - Confirmed closed
|
||||
# 2012-10-08 - Closed, Thanksgiving - Confirmed closed
|
||||
|
||||
# Days in Calendar but not in Environment using ^GSPTSE as bm_symbol:
|
||||
# --------------------------------------------------------------------
|
||||
# Used http://web.tmxmoney.com/pricehistory.php?qm_page=61468&qm_symbol=^TSX
|
||||
# to check whether exchange was open on these days.
|
||||
# 2000-06-28 - No data this far back, can't confirm
|
||||
# 2000-08-28 - No data this far back, can't confirm
|
||||
# 2000-08-29 - No data this far back, can't confirm
|
||||
# 2001-09-11 - TSE Open for 71 min.
|
||||
# 2002-02-01 - Confirm TSE Open
|
||||
# 2002-06-14 - Confirm TSE Open
|
||||
# 2002-07-02 - Confirm TSE Open
|
||||
# 2002-11-11 - TSX website has no data for 2 weeks in 2002
|
||||
# 2003-07-07 - Confirm TSE Open
|
||||
# 2003-12-16 - Confirm TSE Open
|
||||
|
||||
|
||||
def get_early_closes(start, end):
|
||||
# TSX closed at 1:00 PM on december 24th.
|
||||
|
||||
start = canonicalize_datetime(start)
|
||||
end = canonicalize_datetime(end)
|
||||
|
||||
start = max(start, datetime(1993, 1, 1, tzinfo=pytz.utc))
|
||||
end = max(end, datetime(1993, 1, 1, tzinfo=pytz.utc))
|
||||
|
||||
# Not included here are early closes prior to 1993
|
||||
# or unplanned early closes
|
||||
|
||||
early_close_rules = []
|
||||
|
||||
christmas_eve = rrule.rrule(
|
||||
rrule.MONTHLY,
|
||||
bymonth=12,
|
||||
bymonthday=24,
|
||||
byweekday=(rrule.MO, rrule.TU, rrule.WE, rrule.TH, rrule.FR),
|
||||
cache=True,
|
||||
dtstart=start,
|
||||
until=end
|
||||
)
|
||||
early_close_rules.append(christmas_eve)
|
||||
|
||||
early_close_ruleset = rrule.rruleset()
|
||||
|
||||
for rule in early_close_rules:
|
||||
early_close_ruleset.rrule(rule)
|
||||
early_closes = early_close_ruleset.between(start, end, inc=True)
|
||||
|
||||
early_closes.sort()
|
||||
return pd.DatetimeIndex(early_closes)
|
||||
|
||||
early_closes = get_early_closes(start, end)
|
||||
|
||||
|
||||
def get_open_and_close(day, early_closes):
|
||||
market_open = pd.Timestamp(
|
||||
datetime(
|
||||
year=day.year,
|
||||
month=day.month,
|
||||
day=day.day,
|
||||
hour=9,
|
||||
minute=31),
|
||||
tz='US/Eastern').tz_convert('UTC')
|
||||
# 1 PM if early close, 4 PM otherwise
|
||||
close_hour = 13 if day in early_closes else 16
|
||||
market_close = pd.Timestamp(
|
||||
datetime(
|
||||
year=day.year,
|
||||
month=day.month,
|
||||
day=day.day,
|
||||
hour=close_hour),
|
||||
tz='US/Eastern').tz_convert('UTC')
|
||||
|
||||
return market_open, market_close
|
||||
|
||||
open_and_closes = get_open_and_closes(trading_days, early_closes,
|
||||
get_open_and_close)
|
||||
Reference in New Issue
Block a user