Merge pull request #1671 from quantopian/new-futures-hours

Change to a 10.5 hour futures calendar
This commit is contained in:
David Michalowicz
2017-02-06 12:53:23 -05:00
committed by GitHub
5 changed files with 165 additions and 13 deletions
+109
View File
@@ -3502,6 +3502,115 @@ class TestFutureFlip(WithDataPortal, WithSimParams, ZiplineTestCase):
format(i, actual_position, expected_positions[i]))
class TestFuturesAlgo(WithDataPortal, WithSimParams, ZiplineTestCase):
START_DATE = pd.Timestamp('2016-01-06', tz='utc')
END_DATE = pd.Timestamp('2016-01-07', tz='utc')
FUTURE_MINUTE_BAR_START_DATE = pd.Timestamp('2016-01-05', tz='UTC')
SIM_PARAMS_DATA_FREQUENCY = 'minute'
TRADING_CALENDAR_STRS = ('us_futures',)
TRADING_CALENDAR_PRIMARY_CAL = 'us_futures'
@classmethod
def make_futures_info(cls):
return pd.DataFrame.from_dict(
{
1: {
'symbol': 'CLG16',
'root_symbol': 'CL',
'start_date': pd.Timestamp('2015-12-01', tz='UTC'),
'notice_date': pd.Timestamp('2016-01-20', tz='UTC'),
'expiration_date': pd.Timestamp('2016-02-19', tz='UTC'),
'auto_close_date': pd.Timestamp('2016-01-18', tz='UTC'),
'exchange': 'TEST',
},
},
orient='index',
)
def test_futures_history(self):
algo_code = dedent(
"""
from datetime import time
from zipline.api import (
date_rules,
get_datetime,
schedule_function,
sid,
time_rules,
)
def initialize(context):
context.history_values = []
schedule_function(
make_history_call,
date_rules.every_day(),
time_rules.market_open(),
)
schedule_function(
check_market_close_time,
date_rules.every_day(),
time_rules.market_close(),
)
def make_history_call(context, data):
# Ensure that the market open is 6:31am US/Eastern.
open_time = get_datetime().tz_convert('US/Eastern').time()
assert open_time == time(6, 31)
context.history_values.append(
data.history(sid(1), 'close', 5, '1m'),
)
def check_market_close_time(context, data):
# Ensure that this function is called at 4:59pm US/Eastern.
# By default, `market_close()` uses an offset of 1 minute.
close_time = get_datetime().tz_convert('US/Eastern').time()
assert close_time == time(16, 59)
"""
)
algo = TradingAlgorithm(
script=algo_code,
sim_params=self.sim_params,
env=self.env,
trading_calendar=get_calendar('us_futures'),
)
algo.run(self.data_portal)
# Assert that we were able to retrieve history data for minutes outside
# of the 6:31am US/Eastern to 5:00pm US/Eastern futures open times.
np.testing.assert_array_equal(
algo.history_values[0].index,
pd.date_range(
'2016-01-06 6:27',
'2016-01-06 6:31',
freq='min',
tz='US/Eastern',
),
)
np.testing.assert_array_equal(
algo.history_values[1].index,
pd.date_range(
'2016-01-07 6:27',
'2016-01-07 6:31',
freq='min',
tz='US/Eastern',
),
)
# Expected prices here are given by the range values created by the
# default `make_future_minute_bar_data` method.
np.testing.assert_array_equal(
algo.history_values[0].values, list(map(float, range(2196, 2201))),
)
np.testing.assert_array_equal(
algo.history_values[1].values, list(map(float, range(3636, 3641))),
)
class TestTradingAlgorithm(ZiplineTestCase):
def test_analyze_called(self):
self.perf_ref = None
+13 -2
View File
@@ -531,6 +531,17 @@ class TradingAlgorithm(object):
# as the last minute of the session.
market_opens = market_closes
# The calendar's execution times are the minutes over which we actually
# want to run the clock. Typically the execution times simply adhere to
# the market open and close times. In the case of the futures calendar,
# for example, we only want to simulate over a subset of the full 24
# hour calendar, so the execution times dictate a market open time of
# 6:31am US/Eastern and a close of 5:00pm US/Eastern.
execution_opens = \
self.trading_calendar.execution_time_from_open(market_opens)
execution_closes = \
self.trading_calendar.execution_time_from_close(market_closes)
# FIXME generalize these values
before_trading_start_minutes = days_at_time(
self.sim_params.sessions,
@@ -540,8 +551,8 @@ class TradingAlgorithm(object):
return MinuteSimulationClock(
self.sim_params.sessions,
market_opens,
market_closes,
execution_opens,
execution_closes,
before_trading_start_minutes,
minute_emission=minutely_emission,
)
@@ -667,6 +667,12 @@ class TradingCalendar(with_metaclass(ABCMeta)):
def last_session(self):
return self.all_sessions[-1]
def execution_time_from_open(self, open_dates):
return open_dates
def execution_time_from_close(self, close_dates):
return close_dates
@lazyval
def all_minutes(self):
"""
+13 -1
View File
@@ -1,6 +1,6 @@
from datetime import time
from pandas import Timestamp
from pandas import Timedelta, Timestamp
from pandas.tseries.holiday import GoodFriday
from pytz import timezone
@@ -13,6 +13,12 @@ from zipline.utils.calendars.us_holidays import (
Christmas
)
# Number of hours of offset between the open and close times dictated by this
# calendar versus the 6:31am to 5:00pm times over which we want to simulate
# futures algos.
FUTURES_OPEN_TIME_OFFSET = 12.5
FUTURES_CLOSE_TIME_OFFSET = -1
class QuantopianUSFuturesCalendar(TradingCalendar):
"""Synthetic calendar for trading US futures.
@@ -63,6 +69,12 @@ class QuantopianUSFuturesCalendar(TradingCalendar):
def open_offset(self):
return -1
def execution_time_from_open(self, open_dates):
return open_dates + Timedelta(hours=FUTURES_OPEN_TIME_OFFSET)
def execution_time_from_close(self, close_dates):
return close_dates + Timedelta(hours=FUTURES_CLOSE_TIME_OFFSET)
@property
def regular_holidays(self):
return HolidayCalendar([
+24 -10
View File
@@ -348,11 +348,19 @@ class AfterOpen(StatelessRule):
self._one_minute = datetime.timedelta(minutes=1)
def calculate_dates(self, dt):
# given a dt, find that day's open and period end (open + offset)
self._period_start, self._period_close = \
self.cal.open_and_close_for_session(
self.cal.minute_to_session_label(dt)
)
"""
Given a date, find that day's open and period end (open + offset).
"""
period_start, period_close = self.cal.open_and_close_for_session(
self.cal.minute_to_session_label(dt),
)
# Align the market open and close times here with the execution times
# used by the simulation clock. This ensures that scheduled functions
# trigger at the correct times.
self._period_start = self.cal.execution_time_from_open(period_start)
self._period_close = self.cal.execution_time_from_close(period_close)
self._period_end = self._period_start + self.offset - self._one_minute
def should_trigger(self, dt):
@@ -396,11 +404,17 @@ class BeforeClose(StatelessRule):
self._one_minute = datetime.timedelta(minutes=1)
def calculate_dates(self, dt):
# given a dt, find that day's close and period start (close - offset)
self._period_end = \
self.cal.open_and_close_for_session(
self.cal.minute_to_session_label(dt)
)[1]
"""
Given a dt, find that day's close and period start (close - offset).
"""
period_end = self.cal.open_and_close_for_session(
self.cal.minute_to_session_label(dt),
)[1]
# Align the market close time here with the execution time used by the
# simulation clock. This ensures that scheduled functions trigger at
# the correct times.
self._period_end = self.cal.execution_time_from_close(period_end)
self._period_start = self._period_end - self.offset
self._period_close = self._period_end