BUG: Allow rolls to skip over contracts.

For futures that behave like GC, use the latest roll as the back contract when
walking backwards over the window, so that when the front contract is skipped
because it never has more volume between its auto close date and the previous
auto close date, the back contract which did have volume is still used when
making comparisons to construct the chain.
This commit is contained in:
Eddie Hebert
2016-12-05 17:10:17 -05:00
parent c166bb0e37
commit d3ff536de4
2 changed files with 78 additions and 6 deletions
+73 -5
View File
@@ -66,9 +66,9 @@ class ContinuousFuturesTestCase(WithCreateBarData,
@classmethod
def make_root_symbols_info(self):
return pd.DataFrame({
'root_symbol': ['FO', 'BA', 'BZ'],
'root_symbol_id': [1, 2, 3],
'exchange': ['CME', 'CME', 'CME']})
'root_symbol': ['FO', 'BA', 'BZ', 'MA'],
'root_symbol_id': [1, 2, 3, 4],
'exchange': ['CME', 'CME', 'CME', 'CME']})
@classmethod
def make_futures_info(self):
@@ -179,7 +179,33 @@ class ContinuousFuturesTestCase(WithCreateBarData,
'exchange': ['CME'] * 3,
})
return pd.concat([fo_frame, ba_frame, bz_frame])
# MA is set up to test a contract which is has no active volume.
ma_frame = DataFrame({
'symbol': ['MAG16', 'MAH16', 'MAJ16'],
'root_symbol': ['MA'] * 3,
'asset_name': ['Most Active'] * 3,
'sid': range(14, 17),
'start_date': [Timestamp('2005-01-01', tz='UTC'),
Timestamp('2005-01-21', tz='UTC'),
Timestamp('2005-01-21', tz='UTC')],
'end_date': [Timestamp('2016-08-19', tz='UTC'),
Timestamp('2016-11-21', tz='UTC'),
Timestamp('2016-10-19', tz='UTC')],
'notice_date': [Timestamp('2016-02-17', tz='UTC'),
Timestamp('2016-03-16', tz='UTC'),
Timestamp('2016-04-13', tz='UTC')],
'expiration_date': [Timestamp('2016-02-17', tz='UTC'),
Timestamp('2016-03-16', tz='UTC'),
Timestamp('2016-04-13', tz='UTC')],
'auto_close_date': [Timestamp('2016-02-17', tz='UTC'),
Timestamp('2016-03-16', tz='UTC'),
Timestamp('2016-04-13', tz='UTC')],
'tick_size': [0.001] * 3,
'multiplier': [1000.0] * 3,
'exchange': ['CME'] * 3,
})
return pd.concat([fo_frame, ba_frame, bz_frame, ma_frame])
@classmethod
def make_future_minute_bar_data(cls):
@@ -239,7 +265,7 @@ class ContinuousFuturesTestCase(WithCreateBarData,
3: Timestamp('2016-04-20', tz='UTC'),
6: Timestamp('2016-01-27', tz='UTC'),
}
for i in range(7):
for i in range(17):
df = base_df.copy()
df += i * 10000
if i in sid_to_vol_stop_session:
@@ -260,6 +286,8 @@ class ContinuousFuturesTestCase(WithCreateBarData,
# Add some volume before a roll, since a contract may be
# entered earlier than when it is the primary.
df.volume.values[:loc + 1] = 10
if i == 15: # No volume for MAH16
df.volume.values[:] = 0
yield i, df
def test_create_continuous_future(self):
@@ -852,6 +880,46 @@ def record_current_contract(algo, data):
135441.440,
err_msg="On session after roll, Should be FOJ16's 44th value.")
def test_history_close_session_skip_volume(self):
cf = self.data_portal.asset_finder.create_continuous_future(
'MA', 0, 'volume')
window = self.data_portal.get_history_window(
[cf.sid], Timestamp('2016-03-06', tz='UTC'), 30, '1d', 'close')
assert_almost_equal(
window.loc['2016-01-26', cf],
245011.440,
err_msg="At beginning of window, should be MAG16's first value.")
assert_almost_equal(
window.loc['2016-02-26', cf],
265241.440,
err_msg="Should have skipped MAH16 to MAJ16.")
assert_almost_equal(
window.loc['2016-02-29', cf],
265251.440,
err_msg="Should have remained MAJ16.")
# Advance the window a month.
window = self.data_portal.get_history_window(
[cf.sid], Timestamp('2016-04-06', tz='UTC'), 30, '1d', 'close')
assert_almost_equal(
window.loc['2016-02-24', cf],
265221.440,
err_msg="Should be MAJ16, having skipped MAH16.")
assert_almost_equal(
window.loc['2016-02-29', cf],
265251.440,
err_msg="Should be MAJ1 for rest of window.")
assert_almost_equal(
window.loc['2016-03-24', cf],
265431.440,
err_msg="Should be MAJ16 for rest of window.")
def test_history_close_session_adjusted(self):
cf = self.data_portal.asset_finder.create_continuous_future(
'FO', 0, 'calendar')
+5 -1
View File
@@ -98,9 +98,13 @@ class RollFinder(with_metaclass(ABCMeta, object)):
while session > start and curr is not None:
front = curr.contract.sid
back = curr.next.contract.sid
back = rolls[0][0]
prev_c = curr.prev
while session > start:
prev = session - freq
if prev_c is not None:
if prev < prev_c.contract.auto_close_date:
break
if back != self._active_contract(oc, front, back, prev):
rolls.insert(0, ((curr >> offset).contract.sid, session))
break