Skip to content

Commit

Permalink
BUG: Fix continuous future end dates.
Browse files Browse the repository at this point in the history
The end date of the last contract with a sufficient start date was being
used for the continuous future overall end date; however the end date of
that contract (which is the last day for which there is data for the
contract) is not necessarily the greatest end date out of all contracts.
It is possible for the furthest out contract to have some, but very
few, trades before it is more actively traded. Which would give it a
start date within in the range of the simulation, but an end date is
earlier than the other contracts which are active during the simulation.

This bug would result in `nan`s when getting the current price because
of the `end_date` check in `get_spot_value`. When the current simulation
time was greater than the `end_date` of the last contract the condition
which guards against attempting to get data for an instrument past its
end date would return a `nan`, even when the current underlying contract
did have data for that date.

Use max end date of all contracts instead of the last one, to ensure
that the continuous future last date is always great enough to allow
access to all contracts with in the chain.

Also, use min start date to accurately mirror the end date behavior.
  • Loading branch information
Eddie Hebert committed Nov 9, 2016
1 parent fa6e4fe commit e415c0f
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 13 deletions.
56 changes: 45 additions & 11 deletions tests/test_continuous_futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,42 +64,54 @@ def make_root_symbols_info(self):
@classmethod
def make_futures_info(self):
return DataFrame({
'symbol': ['FOF16', 'FOG16', 'FOH16', 'FOJ16', 'FOK16', 'FOF22'],
'root_symbol': ['FO'] * 6,
'asset_name': ['Foo'] * 6,
'symbol': ['FOF16', 'FOG16', 'FOH16', 'FOJ16', 'FOK16', 'FOF22',
'FOG22'],
'root_symbol': ['FO'] * 7,
'asset_name': ['Foo'] * 7,
'start_date': [Timestamp('2015-01-05', tz='UTC'),
Timestamp('2015-02-05', tz='UTC'),
Timestamp('2015-03-05', tz='UTC'),
Timestamp('2015-04-05', tz='UTC'),
Timestamp('2015-05-05', tz='UTC'),
Timestamp('2021-01-05', tz='UTC')],
Timestamp('2021-01-05', tz='UTC'),
Timestamp('2015-01-05', tz='UTC')],
'end_date': [Timestamp('2016-08-19', tz='UTC'),
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-08-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
# models real life scenarios where the end date of the
# furthest out contract is not necessarily the
# greatest end date all contracts in the chain.
Timestamp('2015-02-05', tz='UTC')],
'notice_date': [Timestamp('2016-01-27', tz='UTC'),
Timestamp('2016-02-26', tz='UTC'),
Timestamp('2016-03-24', tz='UTC'),
Timestamp('2016-04-26', tz='UTC'),
Timestamp('2016-05-26', tz='UTC'),
Timestamp('2022-01-26', tz='UTC')],
Timestamp('2022-01-26', tz='UTC'),
Timestamp('2022-02-26', tz='UTC')],
'expiration_date': [Timestamp('2016-01-27', tz='UTC'),
Timestamp('2016-02-26', tz='UTC'),
Timestamp('2016-03-24', tz='UTC'),
Timestamp('2016-04-26', tz='UTC'),
Timestamp('2016-05-26', tz='UTC'),
Timestamp('2022-01-26', tz='UTC')],
Timestamp('2022-01-26', tz='UTC'),
Timestamp('2022-02-26', tz='UTC')],
'auto_close_date': [Timestamp('2016-01-27', tz='UTC'),
Timestamp('2016-02-26', tz='UTC'),
Timestamp('2016-03-24', tz='UTC'),
Timestamp('2016-04-26', tz='UTC'),
Timestamp('2016-05-26', tz='UTC'),
Timestamp('2022-01-26', tz='UTC')],
'tick_size': [0.001] * 6,
'multiplier': [1000.0] * 6,
'exchange': ['CME'] * 6,
Timestamp('2022-01-26', tz='UTC'),
Timestamp('2022-02-26', tz='UTC')],
'tick_size': [0.001] * 7,
'multiplier': [1000.0] * 7,
'exchange': ['CME'] * 7,
})

@classmethod
Expand Down Expand Up @@ -185,6 +197,10 @@ def test_create_continuous_future(self):
self.assertEqual(cf_primary.root_symbol, 'FO')
self.assertEqual(cf_primary.offset, 0)
self.assertEqual(cf_primary.roll_style, 'calendar')
self.assertEqual(cf_primary.start_date,
Timestamp('2015-01-05', tz='UTC'))
self.assertEqual(cf_primary.end_date,
Timestamp('2022-08-19', tz='UTC'))

retrieved_primary = self.asset_finder.retrieve_asset(
cf_primary.sid)
Expand All @@ -197,6 +213,10 @@ def test_create_continuous_future(self):
self.assertEqual(cf_secondary.root_symbol, 'FO')
self.assertEqual(cf_secondary.offset, 1)
self.assertEqual(cf_secondary.roll_style, 'calendar')
self.assertEqual(cf_primary.start_date,
Timestamp('2015-01-05', tz='UTC'))
self.assertEqual(cf_primary.end_date,
Timestamp('2022-08-19', tz='UTC'))

retrieved = self.asset_finder.retrieve_asset(
cf_secondary.sid)
Expand Down Expand Up @@ -270,6 +290,20 @@ def test_get_value_close_daily(self):
'Auto close at beginning of session so FOG16 is now '
'the current contract.')

# Check a value which occurs after the end date of the last known
# contract, to prevent a regression where the end date of the last
# contract was used instead of the max date of all contracts.
value = self.data_portal.get_spot_value(
cf_primary,
'close',
pd.Timestamp('2016-03-26', tz='UTC'),
'daily',
)

self.assertEqual(value, 135441.44,
'Value should be for FOJ16, even though last '
'contract ends before query date.')

def test_current_contract_volume_roll(self):
cf_primary = self.asset_finder.create_continuous_future(
'FO', 0, 'volume')
Expand Down
5 changes: 3 additions & 2 deletions zipline/assets/assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -922,8 +922,9 @@ def get_ordered_contracts(self, root_symbol):

def create_continuous_future(self, root_symbol, offset, roll_style):
oc = self.get_ordered_contracts(root_symbol)
start_date = self.retrieve_asset(oc.contract_sids[0]).start_date
end_date = self.retrieve_asset(oc.contract_sids[-1]).end_date
contracts = self.retrieve_all(oc.contract_sids)
start_date = min(c.start_date for c in contracts)
end_date = max(c.end_date for c in contracts)
exchange = self._get_root_symbol_exchange(root_symbol)

sid = _encode_continuous_future_sid(root_symbol, offset,
Expand Down

0 comments on commit e415c0f

Please sign in to comment.