mirror of
https://github.com/wassname/catalyst.git
synced 2026-09-12 12:12:04 +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]))
|
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
@@ -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):
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user