mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-07 13:19:50 +08:00
Merge pull request #1605 from quantopian/support-non-month-to-month-rolls
BUG: Support futures which do not roll month to month.
This commit is contained in:
@@ -58,15 +58,16 @@ class ContinuousFuturesTestCase(WithCreateBarData,
|
||||
@classmethod
|
||||
def make_root_symbols_info(self):
|
||||
return pd.DataFrame({
|
||||
'root_symbol': ['FO'],
|
||||
'root_symbol_id': [1],
|
||||
'exchange': ['CME']})
|
||||
'root_symbol': ['FO', 'BA'],
|
||||
'root_symbol_id': [1, 2],
|
||||
'exchange': ['CME', 'CME']})
|
||||
|
||||
@classmethod
|
||||
def make_futures_info(self):
|
||||
return DataFrame({
|
||||
fo_frame = DataFrame({
|
||||
'symbol': ['FOF16', 'FOG16', 'FOH16', 'FOJ16', 'FOK16', 'FOF22',
|
||||
'FOG22'],
|
||||
'sid': range(0, 7),
|
||||
'root_symbol': ['FO'] * 7,
|
||||
'asset_name': ['Foo'] * 7,
|
||||
'start_date': [Timestamp('2015-01-05', tz='UTC'),
|
||||
@@ -80,8 +81,8 @@ class ContinuousFuturesTestCase(WithCreateBarData,
|
||||
Timestamp('2016-09-19', tz='UTC'),
|
||||
Timestamp('2016-10-19', tz='UTC'),
|
||||
Timestamp('2016-11-19', tz='UTC'),
|
||||
Timestamp('2016-12-19', tz='UTC'),
|
||||
Timestamp('2022-08-19', tz='UTC'),
|
||||
Timestamp('2022-09-19', tz='UTC'),
|
||||
# Set the last contract's end date (which is the last
|
||||
# date for which there is data to a value that is
|
||||
# within the range of the dates being tested. This
|
||||
@@ -115,6 +116,36 @@ class ContinuousFuturesTestCase(WithCreateBarData,
|
||||
'exchange': ['CME'] * 7,
|
||||
})
|
||||
|
||||
# BA is set up to test a quarterly roll, to test Eurodollar-like
|
||||
# behavior
|
||||
# The roll should go from BAH16 -> BAM16
|
||||
ba_frame = DataFrame({
|
||||
'symbol': ['BAH16', 'BAK16', 'BAM16'],
|
||||
'root_symbol': ['BA'] * 3,
|
||||
'asset_name': ['Bar'] * 3,
|
||||
'sid': range(7, 10),
|
||||
'start_date': [Timestamp('2005-04-01', tz='UTC'),
|
||||
Timestamp('2016-04-21', tz='UTC'),
|
||||
Timestamp('2005-06-21', tz='UTC')],
|
||||
'end_date': [Timestamp('2016-08-19', tz='UTC'),
|
||||
Timestamp('2016-04-21', tz='UTC'),
|
||||
Timestamp('2016-10-19', tz='UTC')],
|
||||
'notice_date': [Timestamp('2016-03-11', tz='UTC'),
|
||||
Timestamp('2016-05-13', tz='UTC'),
|
||||
Timestamp('2016-06-10', tz='UTC')],
|
||||
'expiration_date': [Timestamp('2016-03-11', tz='UTC'),
|
||||
Timestamp('2016-05-13', tz='UTC'),
|
||||
Timestamp('2016-06-10', tz='UTC')],
|
||||
'auto_close_date': [Timestamp('2016-03-11', tz='UTC'),
|
||||
Timestamp('2016-05-13', tz='UTC'),
|
||||
Timestamp('2016-06-10', tz='UTC')],
|
||||
'tick_size': [0.001] * 3,
|
||||
'multiplier': [1000.0] * 3,
|
||||
'exchange': ['CME'] * 3,
|
||||
})
|
||||
|
||||
return pd.concat([fo_frame, ba_frame])
|
||||
|
||||
@classmethod
|
||||
def make_future_minute_bar_data(cls):
|
||||
tc = cls.trading_calendar
|
||||
@@ -163,14 +194,17 @@ class ContinuousFuturesTestCase(WithCreateBarData,
|
||||
# FOG16 cuts out on autoclose
|
||||
# FOH16 cuts out 4 days before autoclose
|
||||
# FOJ16 cuts out 3 days before autoclose
|
||||
# Make FOG22 have a blip of trading, but not be the actively trading,
|
||||
# so that it does not particpate in volume rolls.
|
||||
|
||||
sid_to_vol_stop_session = {
|
||||
0: Timestamp('2016-01-26', tz='UTC'),
|
||||
1: Timestamp('2016-02-26', tz='UTC'),
|
||||
2: Timestamp('2016-03-18', tz='UTC'),
|
||||
3: Timestamp('2016-04-20', tz='UTC'),
|
||||
6: Timestamp('2016-01-27', tz='UTC'),
|
||||
}
|
||||
for i in range(6):
|
||||
for i in range(7):
|
||||
df = base_df.copy()
|
||||
df += i * 10000
|
||||
if i in sid_to_vol_stop_session:
|
||||
@@ -538,6 +572,27 @@ def record_current_contract(algo, data):
|
||||
3,
|
||||
"Should be FOJ16 on session after roll.")
|
||||
|
||||
def test_history_sid_session_quarter_rolls(self):
|
||||
cf = self.data_portal.asset_finder.create_continuous_future(
|
||||
'BA', 0, 'calendar')
|
||||
window = self.data_portal.get_history_window(
|
||||
[cf],
|
||||
Timestamp('2016-03-13 18:01', tz='US/Eastern').tz_convert('UTC'),
|
||||
3, '1d', 'sid')
|
||||
|
||||
self.assertEqual(window.loc['2016-03-10', cf],
|
||||
7,
|
||||
"Should be BAH16 at beginning of window.")
|
||||
|
||||
self.assertEqual(window.loc['2016-03-11', cf],
|
||||
9,
|
||||
"Should be BAM16 after first roll, having skipped "
|
||||
"over BAK16.")
|
||||
|
||||
self.assertEqual(window.loc['2016-03-14', cf],
|
||||
9,
|
||||
"Should have remained BAM16")
|
||||
|
||||
def test_history_sid_session_secondary(self):
|
||||
cf = self.data_portal.asset_finder.create_continuous_future(
|
||||
'FO', 1, 'calendar')
|
||||
|
||||
@@ -318,7 +318,12 @@ cdef class OrderedContracts(object):
|
||||
while contracts:
|
||||
contract = contracts.popleft()
|
||||
|
||||
# Here is where a predicate would go to ensure continuity of the chain.
|
||||
# Prevent contract chains with gaps between auto close and start of
|
||||
# next contract.
|
||||
# This is in lieu of more explicit support for
|
||||
# contracts with quarterly rolls. e.g. Eurodollar
|
||||
if contract.start_date > prev.contract.auto_close_date:
|
||||
continue
|
||||
|
||||
self._start_date = min(contract.start_date.value, self._start_date)
|
||||
self._end_date = max(contract.end_date.value, self._end_date)
|
||||
|
||||
Reference in New Issue
Block a user