Merge pull request #1138 from quantopian/exchange-calendars-v2

ExchangeCalendars and TradingSchedules
This commit is contained in:
Jean Bredeche
2016-06-09 17:21:06 -04:00
committed by GitHub
72 changed files with 11259 additions and 2471 deletions
+1 -1
View File
@@ -70,4 +70,4 @@ after_success:
branches:
only:
- master
- lazy-mainline
- exchange-calendars-v2
+3 -3
View File
@@ -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))
+13 -9
View File
@@ -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,
+2 -1
View File
@@ -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)
+29 -27
View File
@@ -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(
+4 -5
View File
@@ -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):
+6 -4
View File
@@ -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,
)
+7 -9
View File
@@ -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."""
+10 -10
View File
@@ -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',
+2 -2
View File
@@ -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):
+2 -2
View File
@@ -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()
+4 -1
View File
@@ -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):
+4 -1
View File
@@ -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[
+9 -14
View File
@@ -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():
+43 -30
View File
@@ -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
View File
@@ -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"
)
)
+7 -7
View File
@@ -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,
+3 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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 "
+1 -1
View File
@@ -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,
+7 -5
View File
@@ -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(
+341
View File
@@ -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))
+4 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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(
+109
View File
@@ -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
)
-265
View File
@@ -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
View File
@@ -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)
+3 -2
View File
@@ -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
View File
@@ -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)
+5 -1
View File
@@ -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
View File
@@ -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
+5 -4
View File
@@ -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):
"""
+4 -3
View File
@@ -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)]
+31
View File
@@ -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
)
+43 -27
View File
@@ -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
+9 -6
View File
@@ -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]
+17 -16
View File
@@ -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)
+7 -4
View File
@@ -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,
)
+4 -10
View File
@@ -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
View File
@@ -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}(
+3 -1
View File
@@ -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
)
+11 -10
View File
@@ -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],
+13 -10
View File
@@ -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,
+10 -9
View File
@@ -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
+9 -8
View File
@@ -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
View File
@@ -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
View File
@@ -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
+26
View File
@@ -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']
+239
View File
@@ -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)
+416
View File
@@ -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
View File
@@ -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
View File
@@ -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
+11 -22
View File
@@ -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`
+2 -1
View File
@@ -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,
+8 -11
View File
@@ -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
+4
View File
@@ -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
-315
View File
@@ -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)
-186
View File
@@ -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)
-364
View File
@@ -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)