mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-02 22:45:17 +08:00
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:
@@ -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(
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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
@@ -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():
|
||||
|
||||
@@ -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
@@ -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"
|
||||
)
|
||||
)
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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 "
|
||||
|
||||
@@ -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))
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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)]
|
||||
|
||||
|
||||
@@ -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."
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -86,8 +86,10 @@ class RiskMetricsCumulative(object):
|
||||
'information',
|
||||
)
|
||||
|
||||
def __init__(self, sim_params, env, create_first_day_stats=False):
|
||||
self.treasury_curves = env.treasury_curves
|
||||
def __init__(self, sim_params, treasury_curves, trading_schedule,
|
||||
create_first_day_stats=False):
|
||||
self.treasury_curves = treasury_curves
|
||||
self.trading_schedule = trading_schedule
|
||||
self.start_date = sim_params.period_start.replace(
|
||||
hour=0, minute=0, second=0, microsecond=0
|
||||
)
|
||||
@@ -95,12 +97,14 @@ class RiskMetricsCumulative(object):
|
||||
hour=0, minute=0, second=0, microsecond=0
|
||||
)
|
||||
|
||||
self.trading_days = env.days_in_range(self.start_date, self.end_date)
|
||||
self.trading_days = trading_schedule.trading_dates(
|
||||
self.start_date, self.end_date
|
||||
)
|
||||
|
||||
# Hold on to the trading day before the start,
|
||||
# used for index of the zero return value when forcing returns
|
||||
# on the first day.
|
||||
self.day_before_start = self.start_date - env.trading_days.freq
|
||||
self.day_before_start = self.start_date - self.trading_days.freq
|
||||
|
||||
last_day = normalize_date(sim_params.period_end)
|
||||
if last_day not in self.trading_days:
|
||||
@@ -110,7 +114,6 @@ class RiskMetricsCumulative(object):
|
||||
self.trading_days = self.trading_days.append(last_day)
|
||||
|
||||
self.sim_params = sim_params
|
||||
self.env = env
|
||||
|
||||
self.create_first_day_stats = create_first_day_stats
|
||||
|
||||
@@ -268,7 +271,7 @@ algorithm_returns ({algo_count}) in range {start} : {end} on {dt}"
|
||||
self.treasury_curves,
|
||||
self.start_date,
|
||||
treasury_end,
|
||||
self.env,
|
||||
self.trading_schedule,
|
||||
)
|
||||
self.daily_treasury[treasury_end] = treasury_period_return
|
||||
self.treasury_period_return = self.daily_treasury[treasury_end]
|
||||
|
||||
@@ -41,11 +41,9 @@ choose_treasury = functools.partial(risk.choose_treasury,
|
||||
|
||||
|
||||
class RiskMetricsPeriod(object):
|
||||
def __init__(self, start_date, end_date, returns, env,
|
||||
benchmark_returns=None, algorithm_leverages=None):
|
||||
def __init__(self, start_date, end_date, returns, trading_schedule,
|
||||
treasury_curves, benchmark_returns, algorithm_leverages=None):
|
||||
|
||||
self.env = env
|
||||
treasury_curves = env.treasury_curves
|
||||
if treasury_curves.index[-1] >= start_date:
|
||||
mask = ((treasury_curves.index >= start_date) &
|
||||
(treasury_curves.index <= end_date))
|
||||
@@ -58,16 +56,20 @@ class RiskMetricsPeriod(object):
|
||||
|
||||
self.start_date = start_date
|
||||
self.end_date = end_date
|
||||
self.trading_schedule = trading_schedule
|
||||
|
||||
if benchmark_returns is None:
|
||||
br = env.benchmark_returns
|
||||
benchmark_returns = br[(br.index >= returns.index[0]) &
|
||||
(br.index <= returns.index[-1])]
|
||||
|
||||
trading_dates = trading_schedule.trading_dates(
|
||||
start=self.start_date,
|
||||
end=self.end_date,
|
||||
)
|
||||
self.algorithm_returns = self.mask_returns_to_period(returns,
|
||||
env)
|
||||
self.benchmark_returns = self.mask_returns_to_period(benchmark_returns,
|
||||
env)
|
||||
trading_dates)
|
||||
|
||||
# Benchmark needs to be masked to the same dates as the algo returns
|
||||
self.benchmark_returns = self.mask_returns_to_period(
|
||||
benchmark_returns,
|
||||
self.algorithm_returns.index
|
||||
)
|
||||
self.algorithm_leverages = algorithm_leverages
|
||||
|
||||
self.calculate_metrics()
|
||||
@@ -108,7 +110,7 @@ class RiskMetricsPeriod(object):
|
||||
self.treasury_curves,
|
||||
self.start_date,
|
||||
self.end_date,
|
||||
self.env,
|
||||
self.trading_schedule,
|
||||
)
|
||||
self.sharpe = self.calculate_sharpe()
|
||||
# The consumer currently expects a 0.0 value for sharpe in period,
|
||||
@@ -187,15 +189,14 @@ class RiskMetricsPeriod(object):
|
||||
|
||||
return '\n'.join(statements)
|
||||
|
||||
def mask_returns_to_period(self, daily_returns, env):
|
||||
def mask_returns_to_period(self, daily_returns, trading_days):
|
||||
if isinstance(daily_returns, list):
|
||||
returns = pd.Series([x.returns for x in daily_returns],
|
||||
index=[x.date for x in daily_returns])
|
||||
else: # otherwise we're receiving an index already
|
||||
returns = daily_returns
|
||||
|
||||
trade_days = env.trading_days
|
||||
trade_day_mask = returns.index.normalize().isin(trade_days)
|
||||
trade_day_mask = returns.index.normalize().isin(trading_days)
|
||||
|
||||
mask = ((returns.index >= self.start_date) &
|
||||
(returns.index <= self.end_date) & trade_day_mask)
|
||||
|
||||
@@ -67,8 +67,9 @@ log = logbook.Logger('Risk Report')
|
||||
|
||||
|
||||
class RiskReport(object):
|
||||
def __init__(self, algorithm_returns, sim_params, env,
|
||||
benchmark_returns=None, algorithm_leverages=None):
|
||||
def __init__(self, algorithm_returns, sim_params, trading_schedule,
|
||||
treasury_curves, benchmark_returns,
|
||||
algorithm_leverages=None):
|
||||
"""
|
||||
algorithm_returns needs to be a list of daily_return objects
|
||||
sorted in date ascending order
|
||||
@@ -79,7 +80,8 @@ class RiskReport(object):
|
||||
|
||||
self.algorithm_returns = algorithm_returns
|
||||
self.sim_params = sim_params
|
||||
self.env = env
|
||||
self.trading_schedule = trading_schedule
|
||||
self.treasury_curves = treasury_curves
|
||||
self.benchmark_returns = benchmark_returns
|
||||
self.algorithm_leverages = algorithm_leverages
|
||||
|
||||
@@ -140,7 +142,8 @@ class RiskReport(object):
|
||||
end_date=cur_end,
|
||||
returns=self.algorithm_returns,
|
||||
benchmark_returns=self.benchmark_returns,
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
treasury_curves=self.treasury_curves,
|
||||
algorithm_leverages=self.algorithm_leverages,
|
||||
)
|
||||
|
||||
|
||||
@@ -202,14 +202,6 @@ def get_treasury_rate(treasury_curves, treasury_duration, day):
|
||||
return rate
|
||||
|
||||
|
||||
def search_day_distance(end_date, dt, env):
|
||||
tdd = env.trading_day_distance(dt, end_date)
|
||||
if tdd is None:
|
||||
return None
|
||||
assert tdd >= 0
|
||||
return tdd
|
||||
|
||||
|
||||
def select_treasury_duration(start_date, end_date):
|
||||
td = end_date - start_date
|
||||
if td.days <= 31:
|
||||
@@ -237,7 +229,7 @@ def select_treasury_duration(start_date, end_date):
|
||||
|
||||
|
||||
def choose_treasury(select_treasury, treasury_curves, start_date, end_date,
|
||||
env, compound=True):
|
||||
trading_schedule, compound=True):
|
||||
"""
|
||||
Find the latest known interest rate for a given duration within a date
|
||||
range.
|
||||
@@ -269,7 +261,9 @@ def choose_treasury(select_treasury, treasury_curves, start_date, end_date,
|
||||
prev_day)
|
||||
if rate is not None:
|
||||
search_day = prev_day
|
||||
search_dist = search_day_distance(end_date, prev_day, env)
|
||||
search_dist = trading_schedule.execution_day_distance(
|
||||
end_date, prev_day
|
||||
)
|
||||
break
|
||||
|
||||
if search_day:
|
||||
|
||||
+37
-333
@@ -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}(
|
||||
|
||||
@@ -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]
|
||||
)
|
||||
|
||||
@@ -21,8 +21,8 @@ from datetime import timedelta
|
||||
import pandas as pd
|
||||
|
||||
from zipline.sources.data_source import DataSource
|
||||
from zipline.utils import tradingcalendar as calendar_nyse
|
||||
from zipline.gens.utils import hash_args
|
||||
from zipline.utils.calendars import default_nyse_schedule
|
||||
|
||||
|
||||
class RandomWalkSource(DataSource):
|
||||
@@ -35,7 +35,8 @@ class RandomWalkSource(DataSource):
|
||||
VALID_FREQS = frozenset(('daily', 'minute'))
|
||||
|
||||
def __init__(self, start_prices=None, freq='minute', start=None,
|
||||
end=None, drift=0.1, sd=0.1, calendar=calendar_nyse):
|
||||
end=None, drift=0.1, sd=0.1,
|
||||
trading_schedule=default_nyse_schedule):
|
||||
"""
|
||||
:Arguments:
|
||||
start_prices : dict
|
||||
@@ -52,8 +53,8 @@ class RandomWalkSource(DataSource):
|
||||
Constant drift of the price series.
|
||||
sd: float <default=0.1>
|
||||
Standard deviation of the price series.
|
||||
calendar : calendar object <default: NYSE>
|
||||
Calendar to use.
|
||||
trading_schedule : TradingSchedule object <default: NYSESchedule>
|
||||
TradingSchedule to use.
|
||||
See zipline.utils for different choices.
|
||||
|
||||
:Example:
|
||||
@@ -66,7 +67,7 @@ class RandomWalkSource(DataSource):
|
||||
"""
|
||||
# Hash_value for downstream sorting.
|
||||
self.arg_string = hash_args(start_prices, freq, start, end,
|
||||
calendar.__name__)
|
||||
trading_schedule.__name__)
|
||||
|
||||
if freq not in self.VALID_FREQS:
|
||||
raise ValueError('%s not in %s' % (freq, self.VALID_FREQS))
|
||||
@@ -78,13 +79,13 @@ class RandomWalkSource(DataSource):
|
||||
else:
|
||||
self.start_prices = start_prices
|
||||
|
||||
self.calendar = calendar
|
||||
self.trading_schedule = trading_schedule
|
||||
if start is None:
|
||||
self.start = calendar.start
|
||||
self.start = trading_schedule.first_execution_day
|
||||
else:
|
||||
self.start = start
|
||||
if end is None:
|
||||
self.end = calendar.end_base
|
||||
self.end = trading_schedule.last_execution_day
|
||||
else:
|
||||
self.end = end
|
||||
|
||||
@@ -94,7 +95,7 @@ class RandomWalkSource(DataSource):
|
||||
self.sids = self.start_prices.keys()
|
||||
|
||||
self.open_and_closes = \
|
||||
calendar.open_and_closes[self.start:self.end]
|
||||
trading_schedule.schedule[self.start:self.end]
|
||||
|
||||
self._raw_data = None
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ def create_trade(sid, price, amount, datetime, source_id="test_factory"):
|
||||
|
||||
def date_gen(start,
|
||||
end,
|
||||
env,
|
||||
trading_schedule,
|
||||
delta=timedelta(minutes=1),
|
||||
repeats=None):
|
||||
"""
|
||||
@@ -73,13 +73,13 @@ def date_gen(start,
|
||||
"""
|
||||
cur = cur + delta
|
||||
|
||||
if not (env.is_trading_day
|
||||
if not (trading_schedule.is_executing_on_day
|
||||
if daily_delta
|
||||
else env.is_market_hours)(cur):
|
||||
else trading_schedule.is_executing_on_minute)(cur):
|
||||
if daily_delta:
|
||||
return env.next_trading_day(cur)
|
||||
return trading_schedule.next_execution_day(cur)
|
||||
else:
|
||||
return env.next_open_and_close(cur)[0]
|
||||
return trading_schedule.next_start_and_end(cur)[0]
|
||||
else:
|
||||
return cur
|
||||
|
||||
@@ -109,11 +109,12 @@ class SpecificEquityTrades(object):
|
||||
delta : timedelta between internal events
|
||||
filter : filter to remove the sids
|
||||
"""
|
||||
def __init__(self, env, *args, **kwargs):
|
||||
def __init__(self, env, trading_schedule, *args, **kwargs):
|
||||
# We shouldn't get any positional arguments.
|
||||
assert len(args) == 0
|
||||
|
||||
self.env = env
|
||||
self.trading_schedule = trading_schedule
|
||||
|
||||
# Default to None for event_list and filter.
|
||||
self.event_list = kwargs.get('event_list')
|
||||
@@ -205,14 +206,14 @@ class SpecificEquityTrades(object):
|
||||
end=self.end,
|
||||
delta=self.delta,
|
||||
repeats=len(self.sids),
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
else:
|
||||
date_generator = date_gen(
|
||||
start=self.start,
|
||||
end=self.end,
|
||||
delta=self.delta,
|
||||
env=self.env,
|
||||
trading_schedule=self.trading_schedule,
|
||||
)
|
||||
|
||||
source_id = self.get_hash()
|
||||
|
||||
+35
-26
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
#
|
||||
# Copyright 2016 Quantopian, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from .exchange_calendar import (
|
||||
ExchangeCalendar, get_calendar
|
||||
)
|
||||
from .trading_schedule import (
|
||||
TradingSchedule, ExchangeTradingSchedule, default_nyse_schedule
|
||||
)
|
||||
from .calendar_helpers import normalize_date
|
||||
|
||||
__all__ = ['get_calendar', 'ExchangeCalendar', 'TradingSchedule',
|
||||
'ExchangeTradingSchedule', 'default_nyse_schedule',
|
||||
'normalize_date']
|
||||
@@ -0,0 +1,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))
|
||||
@@ -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()
|
||||
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user