mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-02 02:57:53 +08:00
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:
@@ -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