ENH: Adds ExchangeCalendar, TradingSchedule, and implementations

Conflicts:
	tests/data/test_minute_bars.py
	tests/data/test_us_equity_pricing.py
	tests/finance/test_slippage.py
	tests/pipeline/test_engine.py
	tests/pipeline/test_us_equity_pricing_loader.py
	tests/serialization_cases.py
	tests/test_algorithm.py
	tests/test_assets.py
	tests/test_bar_data.py
	tests/test_benchmark.py
	tests/test_exception_handling.py
	tests/test_fetcher.py
	tests/test_finance.py
	tests/test_history.py
	tests/test_perf_tracking.py
	tests/test_security_list.py
	tests/utils/test_events.py
	zipline/algorithm.py
	zipline/data/data_portal.py
	zipline/data/us_equity_loader.py
	zipline/errors.py
	zipline/finance/trading.py
	zipline/testing/core.py
	zipline/utils/events.py
This commit is contained in:
jfkirk
2016-04-06 14:08:06 -04:00
committed by Jean Bredeche
parent c9b5979f45
commit c8304e8601
46 changed files with 9709 additions and 1137 deletions
+17 -16
View File
@@ -45,8 +45,7 @@ from zipline.data.minute_bars import (
US_EQUITIES_MINUTES_PER_DAY,
BcolzMinuteWriterColumnMismatch
)
from zipline.finance.trading import TradingEnvironment
from zipline.utils.calendars import get_calendar, default_nyse_schedule
# 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
@@ -59,15 +58,11 @@ class BcolzMinuteBarTestCase(TestCase):
@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
trading_days = get_calendar('NYSE').trading_days(
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]
@@ -802,10 +797,12 @@ 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 = \
default_nyse_schedule.all_execution_minutes.get_loc(minutes[0])
minute_locs = [
default_nyse_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 +821,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(default_nyse_schedule.execution_minutes_for_days_in_range(
start_day, end_day
))
self.writer.write_cols(sid, dts, cols)
self.assertEqual(
@@ -866,7 +865,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(default_nyse_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 -1
View File
@@ -38,6 +38,7 @@ from zipline.testing.fixtures import (
WithSimParams,
ZiplineTestCase,
)
from zipline.utils.calendars import default_nyse_schedule
class SlippageTestCase(WithSimParams, WithDataPortal, ZiplineTestCase):
@@ -93,7 +94,7 @@ class SlippageTestCase(WithSimParams, WithDataPortal, ZiplineTestCase):
)
with tmp_bcolz_minute_bar_reader(self.env, days, assets) as reader:
data_portal = DataPortal(
self.env,
self.env, default_nyse_schedule,
first_trading_day=reader.first_trading_day,
equity_minute_reader=reader,
)
@@ -482,8 +483,12 @@ class SlippageTestCase(WithSimParams, WithDataPortal, ZiplineTestCase):
)
with tmp_bcolz_minute_bar_reader(self.env, days, assets) as reader:
data_portal = DataPortal(
<<<<<<< HEAD
self.env,
first_trading_day=reader.first_trading_day,
=======
self.env, default_nyse_schedule,
>>>>>>> ENH: Adds ExchangeCalendar, TradingSchedule, and implementations
equity_minute_reader=reader,
)
+5 -4
View File
@@ -85,6 +85,7 @@ from zipline.testing.fixtures import (
ZiplineTestCase,
)
from zipline.utils.memoize import lazyval
from zipline.utils.calendars import default_nyse_schedule
class RollingSumDifference(CustomFactor):
@@ -826,7 +827,7 @@ class FrameInputTestCase(WithTradingEnvironment, ZiplineTestCase):
cls.dates = date_range(
cls.start,
cls.end,
freq=cls.env.trading_day,
freq=default_nyse_schedule.day,
tz='UTC',
)
cls.assets = cls.asset_finder.retrieve_all(cls.asset_ids)
@@ -985,7 +986,7 @@ class SyntheticBcolzTestCase(WithAdjustmentReader,
def test_SMA(self):
engine = SimplePipelineEngine(
lambda column: self.pipeline_loader,
self.env.trading_days,
default_nyse_schedule.all_execution_days,
self.asset_finder,
)
window_length = 5
@@ -1039,7 +1040,7 @@ class SyntheticBcolzTestCase(WithAdjustmentReader,
# valuable.
engine = SimplePipelineEngine(
lambda column: self.pipeline_loader,
self.env.trading_days,
default_nyse_schedule.all_execution_days,
self.asset_finder,
)
window_length = 5
@@ -1083,7 +1084,7 @@ class ParameterizedFactorTestCase(WithTradingEnvironment, ZiplineTestCase):
@classmethod
def init_class_fixtures(cls):
super(ParameterizedFactorTestCase, cls).init_class_fixtures()
day = cls.env.trading_day
day = default_nyse_schedule.day
cls.dates = dates = date_range(
'2015-02-01',
+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
+5 -3
View File
@@ -22,7 +22,7 @@ import zipline.finance.risk as risk
from zipline.utils import factory
from zipline.finance.trading import SimulationParameters, TradingEnvironment
from zipline.utils.calendars import default_nyse_schedule
from . import answer_key
ANSWER_KEY = answer_key.ANSWER_KEY
@@ -51,7 +51,7 @@ class TestRisk(unittest.TestCase):
self.sim_params = SimulationParameters(
period_start=start_date,
period_end=end_date,
env=self.env,
trading_schedule=default_nyse_schedule,
)
self.algo_returns_06 = factory.create_returns_from_list(
@@ -60,7 +60,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=default_nyse_schedule,
)
for dt, returns in answer_key.RETURNS_DATA.iterrows():
+39 -15
View File
@@ -26,7 +26,7 @@ import zipline.finance.risk as risk
from zipline.utils import factory
from zipline.finance.trading import SimulationParameters, TradingEnvironment
from zipline.utils.calendars import default_nyse_schedule
from . import answer_key
from . answer_key import AnswerKey
@@ -60,7 +60,7 @@ class TestRisk(unittest.TestCase):
self.sim_params = SimulationParameters(
period_start=start_date,
period_end=end_date,
env=self.env,
trading_schedule=default_nyse_schedule,
)
self.algo_returns_06 = factory.create_returns_from_list(
@@ -75,7 +75,8 @@ class TestRisk(unittest.TestCase):
self.algo_returns_06,
self.sim_params,
benchmark_returns=self.benchmark_returns_06,
env=self.env,
trading_schedule=default_nyse_schedule,
treasury_curves=self.env.treasury_curves,
)
start_08 = datetime.datetime(
@@ -95,7 +96,7 @@ class TestRisk(unittest.TestCase):
self.sim_params08 = SimulationParameters(
period_start=start_08,
period_end=end_08,
env=self.env,
trading_schedule=default_nyse_schedule,
)
def tearDown(self):
@@ -116,8 +117,9 @@ class TestRisk(unittest.TestCase):
returns.index[0],
returns.index[-1],
returns,
env=self.env,
trading_schedule=default_nyse_schedule,
benchmark_returns=self.env.benchmark_returns,
treasury_curves=self.env.treasury_curves,
)
self.assertEqual(metrics.max_drawdown, 0.505)
@@ -142,7 +144,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=default_nyse_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 +371,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=default_nyse_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 +420,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=default_nyse_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 +432,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=default_nyse_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 +483,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=default_nyse_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 +550,15 @@ class TestRisk(unittest.TestCase):
sim_params90s = SimulationParameters(
period_start=start,
period_end=end,
env=self.env,
trading_schedule=default_nyse_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=default_nyse_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 +566,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=default_nyse_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=default_nyse_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 +659,8 @@ class TestRisk(unittest.TestCase):
self.algo_returns_06,
self.sim_params,
benchmark_returns=benchmark_returns,
env=self.env,
trading_schedule=default_nyse_schedule,
treasury_curves=self.env.treasury_curves,
)
for risk_period in chain.from_iterable(itervalues(report.to_dict())):
self.assertIsNone(risk_period['beta'])
+35 -24
View File
@@ -166,7 +166,7 @@ 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
from zipline.utils.calendars import default_nyse_schedule
# Because test cases appear to reuse some resources.
@@ -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,7 +953,7 @@ def before_trading_start(context, data):
period_end=period_end,
capital_base=float("1.0e5"),
data_frequency='minute',
env=env
trading_schedule=default_nyse_schedule,
)
data_portal = create_data_portal(
@@ -961,6 +961,7 @@ def before_trading_start(context, data):
tempdir,
sim_params,
equities.index,
default_nyse_schedule,
)
algo = algo_class(sim_params=sim_params, env=env)
algo.run(data_portal)
@@ -1551,9 +1552,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, default_nyse_schedule)
data_portal = create_data_portal_from_trade_history(
self.env, tempdir, self.sim_params, {0: trades})
self.env, default_nyse_schedule, tempdir, self.sim_params,
{0: trades})
results = test_algo.run(data_portal)
all_txns = [
@@ -1640,7 +1642,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=default_nyse_schedule,
)
# order method shouldn't blow up
@@ -2719,7 +2721,6 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
sim_params = factory.create_simulation_parameters(
start=start,
num_days=4,
env=env,
data_frequency='minute',
)
@@ -2727,7 +2728,8 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
env,
tempdir,
sim_params,
[1]
[1],
default_nyse_schedule,
)
def handle_data(algo, data):
@@ -2848,7 +2850,8 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
env,
tempdir,
self.sim_params,
[0]
[0],
default_nyse_schedule,
)
algo.run(data_portal)
@@ -2862,7 +2865,8 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
env,
tempdir,
self.sim_params,
[0]
[0],
default_nyse_schedule,
)
algo = SetAssetDateBoundsAlgorithm(
sim_params=self.sim_params,
@@ -2881,7 +2885,8 @@ class TestTradingControls(WithSimParams, WithDataPortal, ZiplineTestCase):
env,
tempdir,
self.sim_params,
[0]
[0],
default_nyse_schedule,
)
algo = SetAssetDateBoundsAlgorithm(
sim_params=self.sim_params,
@@ -2907,7 +2912,7 @@ class TestAccountControls(WithDataPortal, WithSimParams, ZiplineTestCase):
[100, 100, 100, 300],
timedelta(days=1),
cls.sim_params,
cls.env,
default_nyse_schedule,
),
},
index=cls.sim_params.trading_days,
@@ -3054,7 +3059,7 @@ class TestFutureFlip(WithSimParams, WithDataPortal, ZiplineTestCase):
[1e9, 1e9, 1e9],
timedelta(days=1),
cls.sim_params,
cls.env
default_nyse_schedule,
),
},
index=cls.sim_params.trading_days,
@@ -3064,7 +3069,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': default_nyse_schedule.next_execution_day(
self.sim_params.trading_days[-1]),
'multiplier': 5}}
@@ -3206,7 +3211,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.env,
data_frequency=data_frequency,
emission_rate='minute' if minute_emission else 'daily'
)
@@ -3419,8 +3424,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 = default_nyse_schedule.schedule.market_open.loc[
self.test_days
]
market_closes = default_nyse_schedule.schedule.market_close.loc[
self.test_days
]
if frequency == 'daily':
dates = self.test_days
@@ -3441,12 +3450,12 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
)
reader = BcolzDailyBarReader(path)
data_portal = DataPortal(
env,
env, default_nyse_schedule,
first_trading_day=reader.first_trading_day,
equity_daily_reader=reader,
)
elif frequency == 'minute':
dates = env.minutes_for_days_in_range(
dates = default_nyse_schedule.execution_minutes_for_days_in_range(
self.test_days[0],
self.test_days[-1],
)
@@ -3471,7 +3480,7 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
)
reader = BcolzMinuteBarReader(self.tmpdir.path)
data_portal = DataPortal(
env,
env, default_nyse_schedule,
first_trading_day=reader.first_trading_day,
equity_minute_reader=reader,
)
@@ -3485,7 +3494,6 @@ class TestEquityAutoClose(WithTmpDir, ZiplineTestCase):
end=self.test_days[-1],
data_frequency=frequency,
emission_rate=frequency,
env=env,
capital_base=capital_base,
)
@@ -3498,7 +3506,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]
default_nyse_schedule.start_and_end(asset.end_date)[1]
].close
for asset in assets
}
@@ -3852,6 +3860,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 +3998,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=default_nyse_schedule,
data_frequency="minute"
)
)
+2 -1
View File
@@ -83,7 +83,7 @@ from zipline.testing.fixtures import (
WithAssetFinder,
ZiplineTestCase,
)
from zipline.utils.tradingcalendar import trading_day
from zipline.utils.calendars import default_nyse_schedule
@contextmanager
@@ -776,6 +776,7 @@ class AssetFinderTestCase(ZiplineTestCase):
def test_compute_lifetimes(self):
num_assets = 4
trading_day = default_nyse_schedule.day
first_start = pd.Timestamp('2015-04-01', tz='UTC')
frame = make_rotating_equity_info(
+28 -18
View File
@@ -28,6 +28,7 @@ from zipline.testing.fixtures import (
WithDataPortal,
ZiplineTestCase,
)
from zipline.utils.calendars import default_nyse_schedule
OHLC = ["open", "high", "low", "close"]
OHLCP = OHLC + ["price"]
@@ -165,8 +166,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 +195,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 +287,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 +299,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 +347,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 +362,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]):
@@ -604,7 +610,7 @@ class TestDailyBarData(WithBarDataChecks,
def make_daily_bar_data(cls):
for sid in cls.sids:
yield sid, create_daily_df_for_asset(
cls.env,
default_nyse_schedule,
cls.bcolz_daily_bar_days[0],
cls.bcolz_daily_bar_days[-1],
interval=2 - sid % 2
@@ -638,7 +644,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 +749,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)
+8 -3
View File
@@ -32,6 +32,7 @@ from zipline.testing.fixtures import (
WithSimParams,
ZiplineTestCase,
)
from zipline.utils.calendars import default_nyse_schedule
class TestBenchmark(WithDataPortal, WithSimParams, ZiplineTestCase):
@@ -85,7 +86,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, default_nyse_schedule, days_to_use, self.data_portal
)
# should be the equivalent of getting the price history, then doing
@@ -111,6 +112,7 @@ class TestBenchmark(WithDataPortal, WithSimParams, ZiplineTestCase):
BenchmarkSource(
3,
self.env,
default_nyse_schedule,
self.sim_params.trading_days[1:],
self.data_portal
)
@@ -125,6 +127,7 @@ class TestBenchmark(WithDataPortal, WithSimParams, ZiplineTestCase):
BenchmarkSource(
3,
self.env,
default_nyse_schedule,
self.sim_params.trading_days[120:],
self.data_portal
)
@@ -138,7 +141,7 @@ 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 = default_nyse_schedule.execution_minutes_for_days_in_range(
self.sim_params.trading_days[0],
self.sim_params.trading_days[5]
)
@@ -160,6 +163,7 @@ class TestBenchmark(WithDataPortal, WithSimParams, ZiplineTestCase):
source = BenchmarkSource(
2,
self.env,
default_nyse_schedule,
self.sim_params.trading_days,
data_portal
)
@@ -188,7 +192,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, default_nyse_schedule,
self.sim_params.trading_days, self.data_portal
)
self.assertEqual("4 cannot be used as the benchmark because it has a "
+301
View File
@@ -0,0 +1,301 @@
#
# 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
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.utils.calendars.nyse_exchange_calendar import NYSEExchangeCalendar
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")
# def test_minutes_for_date(self):
# for date in self.answers.index:
# mins_for_date = self.calendar.minutes_for_date(date)
def test_minute_window(self):
for open in self.answers.market_open:
open_tz = open.tz_localize('UTC')
window = self.calendar.minute_window(open_tz, 390, 1)
self.assertEqual(len(window), 390)
class NYSECalendarTestCase(ExchangeCalendarTestBase, TestCase):
answer_key_filename = 'nyse'
calendar_class = NYSEExchangeCalendar
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
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 tradingcalendar Thanksgiving dates.
"""
# November 2005
# Su Mo Tu We Th Fr Sa
# 1 2 3 4 5
# 6 7 8 9 10 11 12
# 13 14 15 16 17 18 19
# 20 21 22 23 24 25 26
# 27 28 29 30
start_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))
+5 -2
View File
@@ -28,6 +28,7 @@ from zipline.testing.fixtures import (
WithSimParams,
ZiplineTestCase,
)
from zipline.utils.calendars import default_nyse_schedule
from .resources.fetcher_inputs.fetcher_test_data import (
AAPL_CSV_DATA,
AAPL_IBM_CSV_DATA,
@@ -108,7 +109,8 @@ class FetcherTestCase(WithResponses,
data_frequency=data_frequency
)
results = test_algo.run(FetcherDataPortal(self.env))
results = test_algo.run(FetcherDataPortal(self.env,
default_nyse_schedule))
return results
@@ -141,7 +143,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,
default_nyse_schedule)
gen = test_algo.get_generator()
perf_packets = list(gen)
+31 -27
View File
@@ -50,6 +50,10 @@ from zipline.testing.fixtures import (
)
import zipline.utils.factory as factory
from zipline.utils.calendars import (
default_nyse_schedule,
get_calendar,
)
DEFAULT_TIMEOUT = 15 # seconds
EXTENDED_TIMEOUT = 90
@@ -199,7 +203,7 @@ class FinanceTestCase(WithLogger,
data_frequency="minute"
)
minutes = env.market_minute_window(
minutes = default_nyse_schedule.minute_window(
sim_params.first_open,
int((trade_interval.total_seconds() / 60) * trade_count)
+ 100)
@@ -217,8 +221,9 @@ class FinanceTestCase(WithLogger,
}
write_bcolz_minute_data(
env,
env.days_in_range(minutes[0], minutes[-1]),
default_nyse_schedule,
default_nyse_schedule.execution_days_in_range(minutes[0],
minutes[-1]),
tempdir.path,
iteritems(assets),
)
@@ -226,7 +231,7 @@ class FinanceTestCase(WithLogger,
equity_minute_reader = BcolzMinuteBarReader(tempdir.path)
data_portal = DataPortal(
env,
env, default_nyse_schedule,
first_trading_day=equity_minute_reader.first_trading_day,
equity_minute_reader=equity_minute_reader,
)
@@ -254,7 +259,7 @@ class FinanceTestCase(WithLogger,
equity_daily_reader = BcolzDailyBarReader(path)
data_portal = DataPortal(
env,
env, default_nyse_schedule,
first_trading_day=equity_daily_reader.first_trading_day,
equity_daily_reader=equity_daily_reader,
)
@@ -417,25 +422,25 @@ class TradingEnvironmentTestCase(WithLogger,
]
for holiday in holidays:
self.assertTrue(not self.env.is_trading_day(holiday))
self.assertTrue(not self.cal.is_open_on_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))
self.assertTrue(self.cal.is_open_on_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=default_nyse_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 +457,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=default_nyse_schedule,
)
expected_trading_days = (
@@ -473,7 +478,7 @@ class TradingEnvironmentTestCase(WithLogger,
params.trading_days.tolist())
@timed(DEFAULT_TIMEOUT)
def test_market_minute_window(self):
def test_minute_window(self):
# January 2008
# Su Mo Tu We Th Fr Sa
@@ -488,10 +493,10 @@ class TradingEnvironmentTestCase(WithLogger,
# 10:01 AM Eastern on January 7th..
start = us_east.localize(datetime(2008, 1, 7, 10, 1))
utc_start = start.astimezone(utc)
utc_start = pd.Timestamp(start.astimezone(utc))
# Get the next 10 minutes
minutes = self.env.market_minute_window(
minutes = self.cal.minute_window(
utc_start, 10,
)
self.assertEqual(len(minutes), 10)
@@ -499,7 +504,7 @@ class TradingEnvironmentTestCase(WithLogger,
self.assertEqual(minutes[i], utc_start + timedelta(minutes=i))
# Get the previous 10 minutes.
minutes = self.env.market_minute_window(
minutes = self.cal.minute_window(
utc_start, 10, step=-1,
)
self.assertEqual(len(minutes), 10)
@@ -512,14 +517,14 @@ class TradingEnvironmentTestCase(WithLogger,
# 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,
minutes = self.cal.minute_window(
start, 900,
)
today = self.env.market_minutes_for_day(start)[30:]
tomorrow = self.env.market_minutes_for_day(
today = self.cal.minutes_for_date(utc_start)[30:]
tomorrow = self.cal.minutes_for_date(
start + timedelta(days=1)
)
last_day = self.env.market_minutes_for_day(
last_day = self.cal.minutes_for_date(
start + timedelta(days=2))[:150]
self.assertEqual(len(minutes), 900)
@@ -534,17 +539,17 @@ class TradingEnvironmentTestCase(WithLogger,
# 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,
minutes = self.cal.minute_window(
start, 801, step=-1,
)
today = self.env.market_minutes_for_day(start)[30::-1]
today = self.cal.minutes_for_date(utc_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(
friday = self.cal.minutes_for_date(
start + timedelta(days=-3),
)[::-1]
thursday = self.env.market_minutes_for_day(
thursday = self.cal.minutes_for_date(
start + timedelta(days=-4),
)[:9:-1]
@@ -566,6 +571,5 @@ class TradingEnvironmentTestCase(WithLogger,
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)
+33 -26
View File
@@ -24,6 +24,7 @@ from zipline.testing import (
str_to_seconds,
MockDailyBarReader,
)
from zipline.utils.calendars import default_nyse_schedule
from zipline.testing.fixtures import (
WithBcolzMinuteBarReader,
WithDataPortal,
@@ -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 = default_nyse_schedule.execution_days_in_range(
start=cls.TRADING_START_DT,
end=cls.TRADING_END_DT
)
@@ -455,14 +456,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,
default_nyse_schedule,
asset.start_date,
asset.end_date,
start_val=2,
)
data[1] = create_minute_df_for_asset(
cls.env,
default_nyse_schedule,
pd.Timestamp('2014-01-03', tz='utc'),
pd.Timestamp('2016-01-30', tz='utc'),
start_val=2,
@@ -509,7 +510,7 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
))
asset3 = cls.asset_finder.retrieve_asset(3)
data[3] = create_minute_df_for_asset(
cls.env,
default_nyse_schedule,
asset3.start_date,
asset3.end_date,
start_val=2,
@@ -539,7 +540,7 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
capital_base=float('1.0e5'),
data_frequency='minute',
emission_rate='daily',
env=self.env,
trading_schedule=default_nyse_schedule,
)
test_algo = TradingAlgorithm(
@@ -678,8 +679,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 = default_nyse_schedule.execution_minutes_for_day(
default_nyse_schedule.previous_execution_day(
pd.Timestamp('2015-01-05', tz='UTC')
)
)[0:60]
for idx, minute in enumerate(minutes):
@@ -726,7 +729,7 @@ class MinuteEquityHistoryTestCase(WithHistory, ZiplineTestCase):
# 10 minutes
asset = self.env.asset_finder.retrieve_asset(sid)
minutes = self.env.market_minutes_for_day(
minutes = default_nyse_schedule.execution_minutes_for_day(
pd.Timestamp('2015-01-05', tz='UTC')
)[0:60]
@@ -737,7 +740,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 = default_nyse_schedule.start_and_end(
default_nyse_schedule.previous_execution_day(midnight)
)[1]
midnight_bar_data = \
BarData(self.data_portal, lambda: midnight, 'minute')
@@ -755,7 +760,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 = default_nyse_schedule.execution_minutes_for_day(
pd.Timestamp('2015-01-07', tz='UTC')
)[0:60]
@@ -850,7 +855,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],
default_nyse_schedule.start_and_end(jan5)[1],
10,
'1m',
'close'
@@ -1099,20 +1104,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 = default_nyse_schedule.next_execution_day(self.TRADING_START_DT)
asset1_minutes = self.env.minutes_for_days_in_range(
asset1_minutes = \
default_nyse_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]
default_nyse_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],
default_nyse_schedule.start_and_end(day)[0],
100,
'1m',
'close'
@@ -1130,7 +1136,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 = default_nyse_schedule.execution_minutes_for_day(
self.TRADING_START_DT
)
exp_msg = (
@@ -1150,7 +1156,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 = default_nyse_schedule.execution_minutes_for_day(day)
# minute data, baseline:
# Jan 5: 2 to 391
@@ -1214,7 +1220,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 = default_nyse_schedule.execution_minutes_for_day(day)
# minute data, baseline:
# Jan 5: 2 to 391
@@ -1333,7 +1339,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 = default_nyse_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 +1369,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 = default_nyse_schedule.execution_days_in_range(
start=pd.Timestamp('2014-12-15', tz='UTC'),
end=pd.Timestamp('2014-12-18', tz='UTC'),
)
@@ -1400,9 +1407,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 = default_nyse_schedule.execution_days_in_range(
start=jan5,
end=self.env.add_trading_days(30, jan5)
end=default_nyse_schedule.add_execution_days(30, jan5)
)
for idx, day in enumerate(days):
@@ -1445,7 +1452,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 = default_nyse_schedule.execution_days_in_range(
start=pd.Timestamp('2015-01-07', tz='UTC'),
end=pd.Timestamp('2015-01-08', tz='UTC')
)
@@ -1637,7 +1644,7 @@ class DailyEquityHistoryTestCase(WithHistory, ZiplineTestCase):
# 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 = default_nyse_schedule.next_execution_day(self.TRADING_START_DT)
exp_msg = (
'History window extends before 2014-01-03. To use this history '
@@ -1663,7 +1670,7 @@ class DailyEquityHistoryTestCase(WithHistory, ZiplineTestCase):
)[self.ASSET1]
# Use a minute to force minute mode.
first_minute = self.env.open_and_closes.market_open[
first_minute = default_nyse_schedule.open_and_closes.market_open[
self.TRADING_START_DT]
with self.assertRaisesRegexp(HistoryWindowStartsBeforeData, exp_msg):
@@ -1794,7 +1801,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,
default_nyse_schedule.schedule.market_open,
self.bcolz_minute_bar_reader,
)
+68 -41
View File
@@ -59,6 +59,7 @@ from zipline.testing.fixtures import (
WithTradingEnvironment,
ZiplineTestCase,
)
from zipline.utils.calendars import default_nyse_schedule
logger = logging.getLogger('Test Perf Tracking')
@@ -175,7 +176,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 +243,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 +254,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 +278,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,
default_nyse_schedule,
self.env)
asset1 = self.asset_finder.retrieve_asset(1)
asset2 = self.asset_finder.retrieve_asset(2)
@@ -300,13 +309,14 @@ class TestSplitPerformance(WithSimParams, WithTmpDir, ZiplineTestCase):
[100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_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,
default_nyse_schedule,
self.tmpdir,
self.sim_params,
{1: events},
@@ -411,7 +421,7 @@ class TestDividendPerformance(WithSimParams,
after = factory.get_next_trading_dt(
before,
timedelta(days=1),
self.env,
default_nyse_schedule,
)
self.assertEqual(after.hour, 13)
@@ -423,7 +433,7 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_schedule,
)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
@@ -431,7 +441,7 @@ class TestDividendPerformance(WithSimParams,
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.env.trading_days,
default_nyse_schedule.all_execution_days,
)
splits = mergers = create_empty_splits_mergers_frame()
dividends = pd.DataFrame({
@@ -446,6 +456,7 @@ class TestDividendPerformance(WithSimParams,
adjustment_reader = SQLiteAdjustmentReader(dbpath)
data_portal = create_data_portal_from_trade_history(
self.env,
default_nyse_schedule,
self.instance_tmpdir,
self.sim_params,
{1: events},
@@ -488,7 +499,7 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_schedule,
)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
@@ -496,7 +507,7 @@ class TestDividendPerformance(WithSimParams,
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.env.trading_days,
default_nyse_schedule.all_execution_days,
)
splits = mergers = create_empty_splits_mergers_frame()
dividends = pd.DataFrame({
@@ -522,6 +533,7 @@ class TestDividendPerformance(WithSimParams,
data_portal = create_data_portal_from_trade_history(
self.env,
default_nyse_schedule,
self.instance_tmpdir,
self.sim_params,
events,
@@ -562,7 +574,7 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_schedule,
)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
@@ -570,7 +582,7 @@ class TestDividendPerformance(WithSimParams,
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.env.trading_days,
default_nyse_schedule.all_execution_days,
)
splits = mergers = create_empty_splits_mergers_frame()
dividends = pd.DataFrame({
@@ -586,6 +598,7 @@ class TestDividendPerformance(WithSimParams,
data_portal = create_data_portal_from_trade_history(
self.env,
default_nyse_schedule,
self.instance_tmpdir,
self.sim_params,
{1: events},
@@ -623,7 +636,7 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_schedule,
)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
@@ -631,7 +644,7 @@ class TestDividendPerformance(WithSimParams,
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.env.trading_days,
default_nyse_schedule.all_execution_days,
)
splits = mergers = create_empty_splits_mergers_frame()
dividends = pd.DataFrame({
@@ -647,6 +660,7 @@ class TestDividendPerformance(WithSimParams,
data_portal = create_data_portal_from_trade_history(
self.env,
default_nyse_schedule,
self.instance_tmpdir,
self.sim_params,
{1: events},
@@ -685,14 +699,14 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_schedule,
)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.env.trading_days,
default_nyse_schedule.all_execution_days,
)
splits = mergers = create_empty_splits_mergers_frame()
@@ -709,6 +723,7 @@ class TestDividendPerformance(WithSimParams,
data_portal = create_data_portal_from_trade_history(
self.env,
default_nyse_schedule,
self.instance_tmpdir,
self.sim_params,
{1: events},
@@ -745,20 +760,21 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_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,
default_nyse_schedule)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.env.trading_days,
default_nyse_schedule.all_execution_days,
)
splits = mergers = create_empty_splits_mergers_frame()
dividends = pd.DataFrame({
@@ -774,6 +790,7 @@ class TestDividendPerformance(WithSimParams,
data_portal = create_data_portal_from_trade_history(
self.env,
default_nyse_schedule,
self.instance_tmpdir,
self.sim_params,
{1: events},
@@ -811,7 +828,7 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_schedule,
)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
@@ -819,7 +836,7 @@ class TestDividendPerformance(WithSimParams,
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.env.trading_days,
default_nyse_schedule.all_execution_days,
)
splits = mergers = create_empty_splits_mergers_frame()
dividends = pd.DataFrame({
@@ -835,6 +852,7 @@ class TestDividendPerformance(WithSimParams,
data_portal = create_data_portal_from_trade_history(
self.env,
default_nyse_schedule,
self.instance_tmpdir,
self.sim_params,
{1: events},
@@ -869,7 +887,7 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_schedule,
)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
@@ -877,7 +895,7 @@ class TestDividendPerformance(WithSimParams,
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.env.trading_days,
default_nyse_schedule.all_execution_days,
)
splits = mergers = create_empty_splits_mergers_frame()
dividends = pd.DataFrame({
@@ -893,6 +911,7 @@ class TestDividendPerformance(WithSimParams,
data_portal = create_data_portal_from_trade_history(
self.env,
default_nyse_schedule,
self.instance_tmpdir,
self.sim_params,
{1: events},
@@ -925,7 +944,7 @@ class TestDividendPerformance(WithSimParams,
[100, 100, 100, 100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_schedule,
)
dbpath = self.instance_tmpdir.getpath('adjustments.sqlite')
@@ -933,7 +952,7 @@ class TestDividendPerformance(WithSimParams,
writer = SQLiteAdjustmentWriter(
dbpath,
MockDailyBarReader(),
self.env.trading_days,
default_nyse_schedule.all_execution_days,
)
splits = mergers = create_empty_splits_mergers_frame()
dividends = pd.DataFrame({
@@ -942,8 +961,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(
[default_nyse_schedule.next_execution_day(events[-1].dt)],
dtype='datetime64[ns]'),
})
writer.write(splits, mergers, dividends)
adjustment_reader = SQLiteAdjustmentReader(dbpath)
@@ -957,10 +977,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(default_nyse_schedule)
data_portal = create_data_portal_from_trade_history(
self.env,
default_nyse_schedule,
self.instance_tmpdir,
sim_params,
{1: events},
@@ -1049,7 +1070,7 @@ class TestPositionPerformance(WithInstanceTmpDir, ZiplineTestCase):
[100, 100, 100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_schedule,
)
trades_2 = factory.create_trade_history(
@@ -1058,11 +1079,12 @@ class TestPositionPerformance(WithInstanceTmpDir, ZiplineTestCase):
[100, 100, 100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_schedule,
)
data_portal = create_data_portal_from_trade_history(
self.env,
default_nyse_schedule,
self.instance_tmpdir,
self.sim_params,
{1: trades_1, 2: trades_2}
@@ -1154,11 +1176,12 @@ class TestPositionPerformance(WithInstanceTmpDir, ZiplineTestCase):
[100, 100, 100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_schedule,
)
data_portal = create_data_portal_from_trade_history(
self.env,
default_nyse_schedule,
self.instance_tmpdir,
self.sim_params,
{1: trades})
@@ -1245,11 +1268,12 @@ class TestPositionPerformance(WithInstanceTmpDir, ZiplineTestCase):
[100, 100, 100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_schedule,
)
data_portal = create_data_portal_from_trade_history(
self.env,
default_nyse_schedule,
self.instance_tmpdir,
self.sim_params,
{1: trades})
@@ -1360,13 +1384,14 @@ single short-sale transaction"""
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_schedule,
)
trades_1 = trades[:-2]
data_portal = create_data_portal_from_trade_history(
self.env,
default_nyse_schedule,
self.instance_tmpdir,
self.sim_params,
{1: trades})
@@ -1593,11 +1618,12 @@ cost of sole txn in test"
[100, 100, 100, 100],
oneday,
sim_params,
env=self.env
trading_schedule=default_nyse_schedule,
)
data_portal = create_data_portal_from_trade_history(
self.env,
default_nyse_schedule,
self.instance_tmpdir,
self.sim_params,
{3: trades}
@@ -1712,11 +1738,12 @@ single short-sale transaction"""
[100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_schedule,
)
data_portal = create_data_portal_from_trade_history(
self.env,
default_nyse_schedule,
self.instance_tmpdir,
self.sim_params,
{3: trades}
@@ -1956,11 +1983,12 @@ trade after cover"""
[100, 100, 100, 100, 100, 100, 100, 100, 100, 100],
oneday,
self.sim_params,
env=self.env
trading_schedule=default_nyse_schedule,
)
data_portal = create_data_portal_from_trade_history(
self.env,
default_nyse_schedule,
self.instance_tmpdir,
self.sim_params,
{1: trades})
@@ -2042,13 +2070,14 @@ shares in position"
[100, 100, 100, 100, 100],
oneday,
self.sim_params,
self.env
default_nyse_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,
default_nyse_schedule,
self.instance_tmpdir,
self.sim_params,
{1: trades})
@@ -2167,7 +2196,7 @@ shares in position"
[200, -100, -100, 100, -300, 100, 500, 400],
oneday,
self.sim_params,
self.env
default_nyse_schedule,
)
cost_bases = [10, 10, 0, 8, 9, 9, 13, 13.5]
@@ -2318,9 +2347,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)
+10 -6
View File
@@ -18,6 +18,7 @@ from zipline.utils.security_list import (
SecurityListSet,
load_from_directory,
)
from zipline.utils.calendars import default_nyse_schedule
LEVERAGED_ETFS = load_from_directory('leveraged_etf_list')
@@ -87,7 +88,7 @@ class SecurityListTestCase(WithLogger, ZiplineTestCase):
cls.sim_params = factory.create_simulation_parameters(
start=start,
num_days=4,
env=cls.env
trading_schedule=default_nyse_schedule
)
cls.sim_params2 = sp2 = factory.create_simulation_parameters(
@@ -110,13 +111,15 @@ class SecurityListTestCase(WithLogger, ZiplineTestCase):
tempdir=cls.tempdir,
sim_params=cls.sim_params,
sids=range(0, 5),
trading_schedule=default_nyse_schedule,
)
cls.data_portal2 = create_data_portal(
env=cls.env2,
tempdir=cls.tempdir2,
sim_params=cls.sim_params2,
sids=range(0, 5)
sids=range(0, 5),
trading_schedule=default_nyse_schedule,
)
def test_iterate_over_restricted_list(self):
@@ -212,14 +215,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.tempdir,
sim_params=sim_params,
sids=range(0, 5)
sids=range(0, 5),
trading_schedule=default_nyse_schedule,
)
algo = RestrictedAlgoWithoutCheck(symbol='BZQ',
@@ -270,7 +273,8 @@ class SecurityListTestCase(WithLogger, ZiplineTestCase):
env,
new_tempdir,
sim_params,
range(0, 5)
range(0, 5),
trading_schedule=default_nyse_schedule,
)
algo = RestrictedAlgoWithoutCheck(
+58
View File
@@ -0,0 +1,58 @@
from unittest import TestCase
from pandas import (
Timestamp,
date_range,
)
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)
)
-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)
+29 -44
View File
@@ -28,6 +28,7 @@ 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 +166,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 +175,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 +187,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,7 +206,7 @@ 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
@@ -223,16 +222,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 +242,6 @@ 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
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,7 @@ class TestStatelessRules(RuleTestCase):
@subtest(minutes_for_days(), 'ms')
def test_NotHalfDay(self, ms):
should_trigger = partial(NotHalfDay().should_trigger, env=self.env)
should_trigger = NotHalfDay().should_trigger
self.assertTrue(should_trigger(FULL_DAY))
self.assertFalse(should_trigger(HALF_DAY))
@@ -340,14 +331,13 @@ class TestStatelessRules(RuleTestCase):
"""
self.assertTrue(
NthTradingDayOfWeek(0).should_trigger(
self.env.trading_days[0], self.env
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)
should_trigger = NthTradingDayOfWeek(n).should_trigger
prev_day = self.sept_week[0].date()
n_tdays = 0
for m in self.sept_week:
@@ -362,17 +352,15 @@ 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
)
should_trigger = NDaysBeforeLastTradingDayOfWeek(n).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)
@@ -486,10 +474,9 @@ 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)
should_trigger = NthTradingDayOfMonth(n).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 +484,9 @@ 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
)
should_trigger = NDaysBeforeLastTradingDayOfMonth(n).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 +498,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 +521,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)
+2 -1
View File
@@ -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
+21 -10
View File
@@ -94,6 +94,7 @@ 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
import zipline.utils.events
from zipline.utils.events import (
EventManager,
@@ -273,6 +274,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 +288,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 +420,7 @@ class TradingAlgorithm(object):
if get_loader is not None:
self.engine = SimplePipelineEngine(
get_loader,
self.trading_environment.trading_days,
self.trading_schedule.schedule.index,
self.asset_finder,
)
else:
@@ -484,8 +493,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 +514,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 +531,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,
)
@@ -625,6 +635,7 @@ class TradingAlgorithm(object):
)
self.data_portal = DataPortal(
self.trading_environment,
self.trading_schedule,
first_trading_day=equity_daily_reader.first_trading_day,
equity_daily_reader=equity_daily_reader,
)
@@ -2210,7 +2221,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)
+24 -16
View File
@@ -116,6 +116,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:
@@ -496,6 +497,7 @@ class DataPortal(object):
"""
def __init__(self,
env,
trading_schedule,
first_trading_day,
equity_daily_reader=None,
equity_minute_reader=None,
@@ -503,6 +505,7 @@ class DataPortal(object):
future_minute_reader=None,
adjustment_reader=None):
self.env = env
self.trading_schedule = trading_schedule
self.views = {}
@@ -535,7 +538,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
)
@@ -547,10 +550,10 @@ class DataPortal(object):
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
)
@@ -591,7 +594,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
)
@@ -1012,12 +1015,13 @@ class DataPortal(object):
@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:
start_dt = tds[start_loc]
if start_dt < self._first_trading_day:
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(),
)
@@ -1069,7 +1073,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 +1088,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"
)
@@ -1168,13 +1172,15 @@ class DataPortal(object):
of minute frequency for the given sids.
"""
# get all the minutes for this window
mm = self.env.market_minutes
mm = self.trading_schedule.all_execution_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()
suggested_start_day = \
(mm[bar_count] + self.trading_schedule.day).date()
raise HistoryWindowStartsBeforeData(
first_trading_day=self.env.first_trading_day.date(),
first_trading_day=\
self.trading_schedule.first_execution_day.date(),
bar_count=bar_count,
suggested_start_day=suggested_start_day,
)
@@ -1690,9 +1696,11 @@ 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,
)
+3 -3
View File
@@ -82,8 +82,8 @@ 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 +403,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)]
+19
View File
@@ -635,3 +635,22 @@ 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."
)
+33 -20
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
@@ -380,7 +388,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 +403,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 +450,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:
+37 -333
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,36 +73,26 @@ 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",
min_date=None,
max_date=None,
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)
self.benchmark_returns, self.treasury_curves = load(
trading_schedule.day,
trading_schedule.schedule.index,
self.bm_symbol,
)
if max_date:
tr_c = self.treasury_curves
@@ -133,10 +116,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 +127,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 +146,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 -3
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
@@ -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]
)
+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()
+35 -26
View File
@@ -47,6 +47,7 @@ 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 +426,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 +476,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(env, 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,
env, 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,
env, 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 +521,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 +558,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 +614,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(env, 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 +625,12 @@ def create_data_portal_from_trade_history(env, tempdir, sim_params,
equity_daily_reader = BcolzDailyBarReader(path)
return DataPortal(
env,
env, 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 +665,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 +677,7 @@ def create_data_portal_from_trade_history(env, tempdir, sim_params,
equity_minute_reader = BcolzMinuteBarReader(tempdir.path)
return DataPortal(
env,
env, trading_schedule,
first_trading_day=equity_minute_reader.first_trading_day,
equity_minute_reader=equity_minute_reader,
)
@@ -679,11 +685,13 @@ 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, trading_schedule,
first_trading_day)
def get_spot_value(self, asset, field, dt, data_frequency):
if field == "volume":
@@ -712,8 +720,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, env, trading_schedule, first_trading_day=None):
super(FetcherDataPortal, self).__init__(env, 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
+5 -3
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,7 @@ from zipline.pipeline.loaders.utils import (
get_values_for_date_ranges,
zip_with_dates
)
from zipline.utils.calendars import default_nyse_schedule
class ZiplineTestCase(with_metaclass(FinalMeta, TestCase)):
@@ -399,7 +400,7 @@ class WithTradingEnvironment(WithAssetFinder):
"""
TRADING_ENV_MIN_DATE = None
TRADING_ENV_MAX_DATE = None
TRADING_ENV_TRADING_CALENDAR = tradingcalendar
TRADING_ENV_TRADING_SCHEDULE = default_nyse_schedule
@classmethod
def make_load_function(cls):
@@ -412,7 +413,7 @@ class WithTradingEnvironment(WithAssetFinder):
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_ENV_TRADING_SCHEDULE,
)
@classmethod
@@ -611,6 +612,7 @@ class WithBcolzDailyBarReader(WithTradingEnvironment, WithTmpDir):
BCOLZ_DAILY_BAR_END_DATE = alias('END_DATE')
BCOLZ_DAILY_BAR_READ_ALL_THRESHOLD = None
BCOLZ_DAILY_BAR_SOURCE_FROM_MINUTE = False
BCOLZ_TRADING_SCHEDULE = default_nyse_schedule
# allows WithBcolzDailyBarReaderFromCSVs to call the `write_csvs` method
# without needing to reimplement `init_class_fixtures`
_write_method_name = 'write'
+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']
+199
View File
@@ -0,0 +1,199 @@
#
# 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 with partial hooks to those
# class' methods. These methods live in the helpers module to avoid code
# duplication.
def next_scheduled_day(date, last_trading_day, is_scheduled_day_hook):
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):
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)
mask = ((all_days >= start_date) & (all_days <= end_date))
return all_days[mask]
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.
: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 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,497 @@
#
# 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,
)
from functools import partial
import pandas as pd
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,
all_scheduled_minutes,
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)
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.
_opens = days_at_time(_all_days, self.open_time, tz, open_offset)
_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, _opens, _special_opens)
_overwrite_special_dates(_all_days, _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': _opens,
'market_close': _closes,
},
dtype='datetime64[ns]',
)
self.first_trading_day = _all_days[0]
self.last_trading_day = _all_days[-1]
self.early_closes = _special_closes.map(self.session_date)
# Assign the partial calendar helpers
self.next_trading_day = partial(
next_scheduled_day,
last_trading_day=self.last_trading_day,
is_scheduled_day_hook=self.is_open_on_day,
)
self.previous_trading_day = partial(
previous_scheduled_day,
first_trading_day=self.first_trading_day,
is_scheduled_day_hook=self.is_open_on_day,
)
self.next_open_and_close = partial(
next_open_and_close,
open_and_close_hook=self.open_and_close,
next_scheduled_day_hook=self.next_trading_day,
)
self.previous_open_and_close = partial(
previous_open_and_close,
open_and_close_hook=self.open_and_close,
previous_scheduled_day_hook=self.previous_trading_day,
)
self.trading_day_distance = partial(
scheduled_day_distance,
all_days=self.all_trading_days,
)
self.trading_minutes_for_day = partial(
minutes_for_day,
open_and_close_hook=self.open_and_close,
)
self.trading_days_in_range = partial(
days_in_range,
all_days=self.all_trading_days,
)
self.trading_minutes_for_days_in_range = partial(
minutes_for_days_in_range,
days_in_range_hook=self.trading_days_in_range,
minutes_for_day_hook=self.trading_minutes_for_day,
)
self.add_trading_days = partial(
add_scheduled_days,
next_scheduled_day_hook=self.next_trading_day,
previous_scheduled_day_hook=self.previous_trading_day,
all_trading_days=self.all_trading_days,
)
self.next_market_minute = partial(
next_scheduled_minute,
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,
)
self.previous_market_minute = partial(
previous_scheduled_minute,
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):
return all_scheduled_minutes(self.all_trading_days,
self.trading_minutes_for_days_in_range)
@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()
@abstractmethod
def minutes_for_date(self, date):
"""
Given a UTC-canonicalized date, returns a DatetimeIndex of all trading
minutes in the exchange session for that date.
SD: Sounds like @date can be an arbitrary datetime, and that we should
first map to an exchange session by calling self.session_date. Need to
check what the consumers expect.
Parameters
----------
date : Timestamp
The UTC-canonicalized date whose minutes are needed.
Returns
-------
DatetimeIndex
A DatetimeIndex populated with all of the minutes in the
given date.
"""
raise NotImplementedError()
@abstractmethod
def minute_window(self, start, count, step=1):
"""
Return a DatetimeIndex containing `count` market minutes, starting with
`start` and continuing `step` minutes at a time.
Parameters
----------
start : Timestamp
The start of the window.
count : int
The number of minutes needed.
step : int
The step size by which to increment.
Returns
-------
DatetimeIndex
A window with @count minutes, starting with @start a returning
every @step minute.
"""
raise NotImplementedError()
_static_calendars = {}
_lazy_calendar_names = ['NYSE']
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:
# The calendar is not registered, so check if it is a lazy calendar
if name not in _lazy_calendar_names:
# It's not a lazy calendar, so raise an exception
raise InvalidCalendarName(calendar_name=name)
if name is 'NYSE':
from zipline.utils.calendars.nyse_exchange_calendar \
import NYSEExchangeCalendar
nyse_cal = NYSEExchangeCalendar()
register_calendar(nyse_cal)
return _static_calendars[name]
def register_calendar(calendar):
# 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,522 @@
#
# 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
import numpy as np
import pandas as pd
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 .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 exchang 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,),
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 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.
"""
while not self.is_open_on_day(dt):
dt += Timedelta(days=1)
return normalize_date(dt)
def minutes_for_date(self, dt):
"""
Given a datetime, returns a DatetimeIndex of all trading
minutes in the exchange session for that datetime.
SD: Should @dt be an arbitrary datetime, so that we should
first map to an exchange session by calling self.session_date. Need to
check what the consumers expect. Here, I assume we need to map it to a
session.
Parameters
----------
dt : Timestamp
The datetime whose exchange session minutes are needed.
Returns
-------
DatetimeIndex
A DatetimeIndex populated with all of the minutes in the
given dt.
"""
session = self.session_date(dt)
open, close = self.open_and_close(session)
return date_range(open, close, freq='min', tz='UTC')
def minute_window(self, start, count, step=1):
"""
Returns a DatetimeIndex containing `count` market minutes, starting
with `start` and continuing `step` minutes at a time.
Parameters
----------
start : Timestamp
The start of the window.
count : int
The number of minutes needed.
step : int
The step size by which to increment.
Returns
-------
DatetimeIndex
A window with @count minutes, start with @start.
"""
if not self.is_open_on_minute(start):
raise ValueError("minute_window starting at non-market time "
"{minute}".format(minute=start))
start_utc = start.astimezone(timezone('UTC'))
session = self.session_date(start)
session_idx = self.schedule.index.get_loc(session)
mins_in_session = self.minutes_for_date(session)
start_idx = mins_in_session.searchsorted(start_utc)
# Use a list instead of a pandas DatetimeIndex, as using .append()
# with DatetimeIndex can become expensive if used several times, since
# it makes a full copy of the data. list.extend() will not typically
# copy the data unless there is not enough memory to extend into, which
# is usually not problem.
all_minutes = list(mins_in_session[start_idx::np.sign(step)])
while True:
step_minutes = all_minutes[0::np.absolute(step)]
if len(step_minutes) >= count:
step_minutes = step_minutes[:count]
return pd.DatetimeIndex(step_minutes, copy=False)
# Iterate session forward or backward
session_idx += np.sign(step)
# Get the minutes in the next exchange session
session = self.schedule.index[session_idx]
session_minutes = self.minutes_for_date(session)[::np.sign(step)]
# A these new session_minutes to the `all_minutes` candidate list
all_minutes.extend(list(session_minutes))
+367
View File
@@ -0,0 +1,367 @@
#
# 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 functools import partial
from zipline.utils.memoize import remember_last
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(object):
"""
A TradingSchedule defines the execution timing of a TradingAlgorithm.
"""
__metaclass__ = ABCMeta
def __init__(self):
# Assign the partial calendar helpers
self.next_execution_day = partial(
next_scheduled_day,
last_trading_day=self.last_execution_day,
is_scheduled_day_hook=self.is_executing_on_day,
)
self.previous_execution_day = partial(
previous_scheduled_day,
first_trading_day=self.first_execution_day,
is_scheduled_day_hook=self.is_executing_on_day,
)
self.next_start_and_end = partial(
next_open_and_close,
open_and_close_hook=self.start_and_end,
next_scheduled_day_hook=self.next_execution_day,
)
self.previous_start_and_end = partial(
previous_open_and_close,
open_and_close_hook=self.start_and_end,
previous_scheduled_day_hook=self.previous_execution_day,
)
self.execution_day_distance = partial(
scheduled_day_distance,
all_days=self.all_execution_days,
)
self.execution_minutes_for_day = partial(
minutes_for_day,
open_and_close_hook=self.start_and_end,
)
self.execution_days_in_range = partial(
days_in_range,
all_days=self.all_execution_days,
)
self.execution_minutes_for_days_in_range = partial(
minutes_for_days_in_range,
days_in_range_hook=self.execution_days_in_range,
minutes_for_day_hook=self.execution_minutes_for_day,
)
self.add_execution_days = partial(
add_scheduled_days,
next_scheduled_day_hook=self.next_execution_day,
previous_scheduled_day_hook=self.previous_execution_day,
all_trading_days=self.all_execution_days,
)
self.next_execution_minute = partial(
next_scheduled_minute,
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,
)
self.previous_execution_minute = partial(
previous_scheduled_minute,
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,
)
@abstractproperty
def day(self):
"""
A CustomBusinessDay defining those days on which the algorithm is
usually 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
@remember_last
def all_execution_days(self):
return self.schedule.index
@property
@remember_last
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 minute_window(self, start, count, step=1):
"""
Return a DatetimeIndex containing `count` market minutes, starting with
`start` and continuing `step` minutes at a time.
Parameters
----------
start : Timestamp
The start of the window.
count : int
The number of minutes needed.
step : int
The step size by which to increment.
Returns
-------
DatetimeIndex
A window with @count minutes, starting with @start a returning
every @step minute.
"""
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 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 minute_window(self, start, count, step=1):
return self._exchange_calendar.minute_window(start=start,
count=count,
step=step)
class NYSETradingSchedule(ExchangeTradingSchedule):
"""
An ExchangeTradingSchedule for NYSE. Provided for convenience.
"""
def __init__(self):
super(NYSETradingSchedule, self).__init__(cal=get_calendar('NYSE'))
default_nyse_schedule = NYSETradingSchedule()
+454
View File
@@ -0,0 +1,454 @@
#
# 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
import numpy as np
import pandas as pd
from dateutil.relativedelta import (
MO,
TH,
)
from pandas import (
date_range,
DateOffset,
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
# 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('Americas/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, Americas/Chicago
Close Time: 5:00 PM, Americas/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 = 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(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
o_and_c_df = self.open_and_close(session)
# Is @dt within the trading hours for this exchange session
for index, row in o_and_c_df.iterrows():
if row['market_open'] <= dt and dt <= row['market_close']:
return True
return False
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, session):
"""
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.
"""
# Generalised logic for the case of trading pauses.
# Note: this logic is ~3-4 times slower than that used for the NYSE
# (pass a list, to ensure we get a DataFrame returned)
return self.schedule.loc[[session]]
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.
"""
dt_utc = dt.tz_convert('UTC').tz_convert(None)
return dt_utc.replace(hour=0, minute=0, second=0)
def minutes_for_date(self, date):
"""
Given a UTC-canonicalized date, returns a DatetimeIndex of all trading
minutes in the exchange session for that date.
SD: Sounds like @date can be an arbitrary datetime, and that we should
first map to an exchange session by calling self.session_date. Need to
check what the consumers expect.
Parameters
----------
date : Timestamp
The UTC-canonicalized date whose minutes are needed.
Returns
-------
DatetimeIndex
A DatetimeIndex populated with all of the minutes in the
given date.
"""
open, close = self.open_and_close(date)
return date_range(open, close, freq='min', tz='UTC')
def minute_window(self, start, count, step=1):
"""
Return a DatetimeIndex containing `count` market minutes, starting with
`start` and continuing `step` minutes at a time.
Parameters
----------
start : Timestamp
The start of the window.
count : int
The number of minutes needed.
step : int
The step size by which to increment.
Returns
-------
DatetimeIndex
A window with @count minutes, start with @start.
"""
if not self.is_open(start):
raise ValueError("minute_window starting at non-market time "
"{minute}".format(minute=start))
start_utc = start.tz_convert('UTC')
session = self.session_date(start)
session_idx = self.schedule.index.get_loc(session)
mins_in_session = self.minutes_for_date(session)
start_idx = mins_in_session.searchsorted(start_utc)
# Use a list instead of a pandas DatetimeIndex, as using .append()
# with DatetimeIndex can become expensive if used several times, since
# it makes a full copy of the data. list.extend() will not typically
# copy the data unless there is not enough memory to extend into, which
# is usually not problem.
all_minutes = list(mins_in_session[start_idx::np.sign(step)])
while True:
step_minutes = all_minutes[0::step]
if len(step_minutes) >= count:
step_minutes = step_minutes[:count]
return pd.DatetimeIndex(step_minutes, copy=False)
# Iterate session forward or backward
session_idx += np.sign(step)
# Get the minutes in the next exchange session
session = self.schedule.index[session_idx]
session_minutes = self.minutes_for_date(session)
# A these new session_minutes to the `all_minutes` candidate list
all_minutes.extend(list(session_minutes))
+62 -55
View File
@@ -22,6 +22,10 @@ import pytz
from .context_tricks import nop_context
from zipline.utils.calendars import (
get_calendar,
normalize_date,
)
__all__ = [
'EventManager',
@@ -52,6 +56,9 @@ MAX_MONTH_RANGE = 26
MAX_WEEK_RANGE = 5
_static_nyse_cal = get_calendar('NYSE')
def naive_to_utc(ts):
"""
Converts a UTC tz-naive timestamp to a tz-aware timestamp.
@@ -204,7 +211,6 @@ class EventManager(object):
context,
data,
dt,
context.trading_environment,
)
@@ -218,17 +224,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 +280,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 +304,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 +317,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 +345,14 @@ 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_start, self._period_close = \
_static_nyse_cal.get_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 +366,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 +390,14 @@ 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_end = _static_nyse_cal.get_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 +411,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 +420,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 _static_nyse_cal.early_closes
class TradingDayOfWeekRule(six.with_metaclass(ABCMeta, StatelessRule)):
@@ -430,14 +436,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):
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(
_static_nyse_cal.add_trading_days(
self.td_delta,
self.date_func(dt, env),
self.date_func(dt),
)
)
@@ -452,23 +458,24 @@ class TradingDayOfWeekRule(six.with_metaclass(ABCMeta, StatelessRule)):
)
)
next_open, next_close = env.get_open_and_close(next_trading_day)
next_open, next_close = _static_nyse_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 +491,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):
prev = dt
dt = env.previous_trading_day(dt)
dt = _static_nyse_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,7 +504,7 @@ class NthTradingDayOfWeek(TradingDayOfWeekRule):
return prev
while dt.date().weekday() < prev.date().weekday():
prev = dt
dt = env.previous_trading_day(dt)
dt = _static_nyse_cal.previous_trading_day(dt)
if dt is None:
return prev
@@ -517,14 +524,14 @@ 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):
prev = dt
dt = env.next_trading_day(dt)
dt = _static_nyse_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 = _static_nyse_cal.next_trading_day(dt)
if env.is_trading_day(prev):
return prev.date()
@@ -546,30 +553,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 = _static_nyse_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 _static_nyse_cal.is_open_on_day(dt)
else _static_nyse_cal.next_trading_day(dt)).date()
return self.first_day
@@ -584,25 +591,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 = _static_nyse_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 +621,7 @@ class NDaysBeforeLastTradingDayOfMonth(StatelessRule):
year = dt.year
month = dt.month + 1
self.last_day = env.previous_trading_day(
self.last_day = _static_nyse_cal.previous_trading_day(
dt.replace(year=year, month=month, day=1)
).date()
return self.last_day
@@ -649,7 +656,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 +666,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
+28 -112
View File
@@ -35,6 +35,7 @@ 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 +46,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 +64,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 +101,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 +172,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 +195,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 +208,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 +226,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 +252,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
+9
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):
@@ -48,6 +49,11 @@ def create_test_zipline(**config):
else:
order_amount = 100
if 'trading_schedule' in config:
trading_schedule = config['trading_schedule']
else:
trading_schedule = default_nyse_schedule
# -------------------
# Create the Algo
# -------------------
@@ -60,6 +66,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 +82,7 @@ def create_test_zipline(**config):
sid_list,
test_algo.sim_params,
test_algo.trading_environment,
trading_schedule,
concurrent=concurrent_trades,
)
@@ -87,6 +95,7 @@ def create_test_zipline(**config):
data_portal = create_data_portal_from_trade_history(
config['env'],
trading_schedule,
config['tempdir'],
config['sim_params'],
trades_by_sid