Files
catalyst/zipline/assets/roll_finder.py
T
Eddie Hebert a3df1e3cef ENH: Allow configurable history prefetch length.
To support using a `DataPortal` and `HistoryLoader` in a notebook, allow
the prefetch length to be configurable, so that it can be set to 0.
Unlike backtesting where the prefetch is useful for repeated history
windows viewed from datetimes which are monotonically increasing by a
small amount, the notebook usage of history windows needs only to
retrieve the exact data needed for the window specified.

This patch also fixes some boundary conditions related to rolls and
adjustments which were uncovered by querying for the adjustments with an
end date near the end of the window.
2016-11-04 13:30:30 -04:00

156 lines
5.6 KiB
Python

#
# Copyright 2016 Quantopian, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from abc import ABCMeta, abstractmethod
from six import with_metaclass
from pandas import Timestamp
class RollFinder(with_metaclass(ABCMeta, object)):
"""
Abstract base class for calculating when futures contracts are the active
contract.
"""
@abstractmethod
def _active_contract(self, oc, front, back, dt):
raise NotImplementedError
def get_contract_center(self, root_symbol, dt, offset):
"""
Parameters
----------
root_symbol : str
The root symbol for the contract chain.
dt : Timestamp
The datetime for which to retrieve the current contract.
offset : int
The offset from the primary contract.
0 is the primary, 1 is the secondary, etc.
Returns
-------
Future
The active future contract at the given dt.
"""
oc = self.asset_finder.get_ordered_contracts(root_symbol)
session = self.trading_calendar.minute_to_session_label(dt)
front = oc.contract_before_auto_close(session.value)
back = oc.contract_at_offset(front, 1, dt.value)
if back is None:
return front
session = self.trading_calendar.minute_to_session_label(dt)
primary = self._active_contract(oc, front, back, session)
return oc.contract_at_offset(primary, offset, session.value)
def get_rolls(self, root_symbol, start, end, offset):
"""
Get the rolls, i.e. the session at which to hop from contract to
contract in the chain.
Parameters
----------
root_symbol : str
The root symbol for which to calculate rolls.
start : Timestamp
Start of the date range.
end : Timestamp
End of the date range.
offset : int
Offset from the primary.
Returns
-------
rolls - list[tuple(sid, roll_date)]
A list of rolls, where first value is the first active `sid`,
and the `roll_date` on which to hop to the next contract.
The last pair in the chain has a value of `None` since the roll
is after the range.
"""
oc = self.asset_finder.get_ordered_contracts(root_symbol)
front = self.get_contract_center(root_symbol, end, 0)
back = oc.contract_at_offset(front, 1, end.value)
if back is not None:
first = self._active_contract(oc, front, back, end)
else:
first = front
for i, sid in enumerate(oc.contract_sids):
if sid == first:
break
rolls = [(first + offset, None)]
tc = self.trading_calendar
sessions = tc.sessions_in_range(tc.minute_to_session_label(start),
tc.minute_to_session_label(end))
if first == front:
i -= 1
else:
i -= 2
auto_close_date = Timestamp(oc.auto_close_dates[i], tz='UTC')
while auto_close_date > start and i > -1:
session_loc = sessions.searchsorted(auto_close_date)
front = oc.contract_sids[i]
back = oc.contract_sids[i + 1]
while session_loc > -1:
session = sessions[session_loc]
if back != self._active_contract(oc, front, back, session):
break
session_loc -= 1
roll_session = sessions[session_loc + 1]
if roll_session > start:
rolls.insert(0, (oc.contract_sids[i + offset],
roll_session))
i -= 1
auto_close_date = Timestamp(oc.auto_close_dates[i],
tz='UTC')
return rolls
class CalendarRollFinder(RollFinder):
"""
The CalendarRollFinder calculates contract rolls based purely on the
contract's auto close date.
"""
def __init__(self, trading_calendar, asset_finder):
self.trading_calendar = trading_calendar
self.asset_finder = asset_finder
def _active_contract(self, oc, front, back, dt):
for i, sid in enumerate(oc.contract_sids):
if sid == front:
break
auto_close_date = Timestamp(oc.auto_close_dates[i], tz='UTC')
before_auto_close = dt < auto_close_date
return front if before_auto_close else back
class VolumeRollFinder(RollFinder):
"""
The CalendarRollFinder calculates contract rolls based on when
volume activity transfers from one contract to another.
"""
THRESHOLD = 0.10
def __init__(self, trading_calendar, asset_finder, session_reader):
self.trading_calendar = trading_calendar
self.asset_finder = asset_finder
self.session_reader = session_reader
def _active_contract(self, oc, front, back, dt):
# FIXME: Possible vector for look ahead bias.
front_vol = self.session_reader.get_value(front, dt, 'volume')
back_vol = self.session_reader.get_value(back, dt, 'volume')
return back if back_vol > front_vol else front