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])) 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): class TestTradingAlgorithm(ZiplineTestCase):
def test_analyze_called(self): def test_analyze_called(self):
self.perf_ref = None self.perf_ref = None
+13 -2
View File
@@ -531,6 +531,17 @@ class TradingAlgorithm(object):
# as the last minute of the session. # as the last minute of the session.
market_opens = market_closes 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 # FIXME generalize these values
before_trading_start_minutes = days_at_time( before_trading_start_minutes = days_at_time(
self.sim_params.sessions, self.sim_params.sessions,
@@ -540,8 +551,8 @@ class TradingAlgorithm(object):
return MinuteSimulationClock( return MinuteSimulationClock(
self.sim_params.sessions, self.sim_params.sessions,
market_opens, execution_opens,
market_closes, execution_closes,
before_trading_start_minutes, before_trading_start_minutes,
minute_emission=minutely_emission, minute_emission=minutely_emission,
) )
@@ -667,6 +667,12 @@ class TradingCalendar(with_metaclass(ABCMeta)):
def last_session(self): def last_session(self):
return self.all_sessions[-1] 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 @lazyval
def all_minutes(self): def all_minutes(self):
""" """
+13 -1
View File
@@ -1,6 +1,6 @@
from datetime import time from datetime import time
from pandas import Timestamp from pandas import Timedelta, Timestamp
from pandas.tseries.holiday import GoodFriday from pandas.tseries.holiday import GoodFriday
from pytz import timezone from pytz import timezone
@@ -13,6 +13,12 @@ from zipline.utils.calendars.us_holidays import (
Christmas 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): class QuantopianUSFuturesCalendar(TradingCalendar):
"""Synthetic calendar for trading US futures. """Synthetic calendar for trading US futures.
@@ -63,6 +69,12 @@ class QuantopianUSFuturesCalendar(TradingCalendar):
def open_offset(self): def open_offset(self):
return -1 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 @property
def regular_holidays(self): def regular_holidays(self):
return HolidayCalendar([ return HolidayCalendar([
+24 -10
View File
@@ -348,11 +348,19 @@ class AfterOpen(StatelessRule):
self._one_minute = datetime.timedelta(minutes=1) self._one_minute = datetime.timedelta(minutes=1)
def calculate_dates(self, dt): def calculate_dates(self, dt):
# given a dt, find that day's open and period end (open + offset) """
self._period_start, self._period_close = \ Given a date, find that day's open and period end (open + offset).
self.cal.open_and_close_for_session( """
self.cal.minute_to_session_label(dt) 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 self._period_end = self._period_start + self.offset - self._one_minute
def should_trigger(self, dt): def should_trigger(self, dt):
@@ -396,11 +404,17 @@ class BeforeClose(StatelessRule):
self._one_minute = datetime.timedelta(minutes=1) self._one_minute = datetime.timedelta(minutes=1)
def calculate_dates(self, dt): def calculate_dates(self, dt):
# given a dt, find that day's close and period start (close - offset) """
self._period_end = \ Given a dt, find that day's close and period start (close - offset).
self.cal.open_and_close_for_session( """
self.cal.minute_to_session_label(dt) period_end = self.cal.open_and_close_for_session(
)[1] 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_start = self._period_end - self.offset
self._period_close = self._period_end self._period_close = self._period_end