mirror of
https://github.com/wassname/catalyst.git
synced 2026-06-28 01:44:29 +08:00
Merge pull request #1671 from quantopian/new-futures-hours
Change to a 10.5 hour futures calendar
This commit is contained in:
@@ -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
@@ -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):
|
||||
"""
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user