BUG: Support futures which do not roll month to month.

Fix multiple errors when attempting to generate rolls for futures which do not
roll month to month, e.g. the Eurodollar.

These errors were caused by logic that always incremented from contract to
contract by delivery month, with errors when the next contract was not part of
the quarterly roll chain and thus had not yet begun trading even though the
previous contract had autoclosed. Instead, filter out these contracts and only
allow contracts that have begun trading before the previous contract's autoclose.

This is in lieu of a more explicit specification of quarterly rolls.
This commit is contained in:
Eddie Hebert
2016-11-30 10:37:03 -05:00
parent 6f33bdcfd3
commit 82bc2a6d3d
2 changed files with 67 additions and 7 deletions
+61 -6
View File
@@ -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')
+6 -1
View File
@@ -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)