ENH: Add continuous future current contract.

Add the ability for an algorithm to request the current contract for a
future chain via `data.current`.

e.g.:
```
data.current(ContinuousFuture('CL', offset=0, roll='calendar'),
'contract')
```
This commit is contained in:
Eddie Hebert
2016-10-06 13:39:44 -04:00
parent 03d77b34b4
commit ec6f298972
9 changed files with 697 additions and 5 deletions
+2
View File
@@ -80,6 +80,8 @@ class LazyBuildExtCommandClass(dict):
ext_modules = [
Extension('zipline.assets._assets', ['zipline/assets/_assets.pyx']),
Extension('zipline.assets.continuous_futures',
['zipline/assets/continuous_futures.pyx']),
Extension('zipline.lib.adjustment', ['zipline/lib/adjustment.pyx']),
Extension('zipline.lib._factorize', ['zipline/lib/_factorize.pyx']),
Extension(
+9 -2
View File
@@ -38,6 +38,7 @@ from zipline import run_algorithm
from zipline import TradingAlgorithm
from zipline.api import FixedSlippage
from zipline.assets import Equity, Future, Asset
from zipline.assets.continuous_futures import ContinuousFuture
from zipline.assets.synthetic import (
make_jagged_equity_info,
make_simple_equity_info,
@@ -1431,8 +1432,12 @@ class TestAlgoScript(WithLogger,
STRING_TYPE_NAMES = [s.__name__ for s in string_types]
STRING_TYPE_NAMES_STRING = ', '.join(STRING_TYPE_NAMES)
ASSET_TYPE_NAME = Asset.__name__
CONTINUOUS_FUTURE_NAME = ContinuousFuture.__name__
ASSET_OR_STRING_TYPE_NAMES = ', '.join([ASSET_TYPE_NAME] +
STRING_TYPE_NAMES)
ASSET_OR_STRING_OR_CF_TYPE_NAMES = ', '.join([ASSET_TYPE_NAME,
CONTINUOUS_FUTURE_NAME] +
STRING_TYPE_NAMES)
ARG_TYPE_TEST_CASES = (
('history__assets', (bad_type_history_assets,
ASSET_OR_STRING_TYPE_NAMES,
@@ -1445,7 +1450,7 @@ class TestAlgoScript(WithLogger,
STRING_TYPE_NAMES_STRING,
False)),
('current__assets', (bad_type_current_assets,
ASSET_OR_STRING_TYPE_NAMES,
ASSET_OR_STRING_OR_CF_TYPE_NAMES,
True)),
('current__fields', (bad_type_current_fields,
STRING_TYPE_NAMES_STRING,
@@ -1465,7 +1470,9 @@ class TestAlgoScript(WithLogger,
('history_kwarg__frequency',
(bad_type_history_frequency_kwarg, STRING_TYPE_NAMES_STRING, False)),
('current_kwarg__assets',
(bad_type_current_assets_kwarg, ASSET_OR_STRING_TYPE_NAMES, True)),
(bad_type_current_assets_kwarg,
ASSET_OR_STRING_OR_CF_TYPE_NAMES,
True)),
('current_kwarg__fields',
(bad_type_current_fields_kwarg, STRING_TYPE_NAMES_STRING, True)),
)
+172
View File
@@ -0,0 +1,172 @@
#
# 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 textwrap import dedent
import pandas as pd
from pandas import Timestamp, DataFrame
from zipline import TradingAlgorithm
from zipline.testing.fixtures import (
WithCreateBarData,
WithSimParams,
ZiplineTestCase,
)
class ContinuousFuturesTestCase(WithCreateBarData,
WithSimParams,
ZiplineTestCase):
START_DATE = pd.Timestamp('2015-01-05', tz='UTC')
END_DATE = pd.Timestamp('2016-10-19', tz='UTC')
SIM_PARAMS_START = pd.Timestamp('2016-01-25', tz='UTC')
SIM_PARAMS_END = pd.Timestamp('2016-01-27', tz='UTC')
SIM_PARAMS_DATA_FREQUENCY = 'minute'
TRADING_CALENDAR_STRS = ('us_futures',)
TRADING_CALENDAR_PRIMARY_CAL = 'us_futures'
@classmethod
def make_root_symbols_info(self):
return pd.DataFrame({
'root_symbol': ['FO'],
'root_symbol_id': [1],
'exchange': ['CME']})
@classmethod
def make_futures_info(self):
return DataFrame({
'symbol': ['FOF', 'FOG', 'FOH'],
'root_symbol': ['FO', 'FO', 'FO'],
'asset_name': ['Foo'] * 3,
'start_date': [Timestamp('2015-01-05', tz='UTC'),
Timestamp('2015-02-05', tz='UTC'),
Timestamp('2015-03-05', tz='UTC')],
'end_date': [Timestamp('2016-08-19', tz='UTC'),
Timestamp('2016-09-19', tz='UTC'),
Timestamp('2016-10-19', tz='UTC')],
'notice_date': [Timestamp('2016-01-26', tz='UTC'),
Timestamp('2016-02-26', tz='UTC'),
Timestamp('2016-03-26', tz='UTC')],
'expiration_date': [Timestamp('2016-01-26', tz='UTC'),
Timestamp('2016-02-26', tz='UTC'),
Timestamp('2016-03-26', tz='UTC')],
'auto_close_date': [Timestamp('2016-01-26', tz='UTC'),
Timestamp('2016-02-26', tz='UTC'),
Timestamp('2016-03-26', tz='UTC')],
'tick_size': [0.001] * 3,
'multiplier': [1000.0] * 3,
'exchange': ['CME'] * 3,
})
def test_create_continuous_future(self):
cf_primary = self.asset_finder.create_continuous_future(
'FO', 0, 'calendar')
self.assertEqual(cf_primary.root_symbol, 'FO')
self.assertEqual(cf_primary.offset, 0)
self.assertEqual(cf_primary.roll_style, 'calendar')
retrieved_primary = self.asset_finder.retrieve_asset(
cf_primary.sid)
self.assertEqual(retrieved_primary, cf_primary)
cf_secondary = self.asset_finder.create_continuous_future(
'FO', 1, 'calendar')
self.assertEqual(cf_secondary.root_symbol, 'FO')
self.assertEqual(cf_secondary.offset, 1)
self.assertEqual(cf_secondary.roll_style, 'calendar')
retrieved = self.asset_finder.retrieve_asset(
cf_secondary.sid)
self.assertEqual(retrieved, cf_secondary)
self.assertNotEqual(cf_primary, cf_secondary)
def test_current_contract(self):
cf_primary = self.asset_finder.create_continuous_future(
'FO', 0, 'calendar')
bar_data = self.create_bardata(
lambda: pd.Timestamp('2016-01-25', tz='UTC'))
contract = bar_data.current(cf_primary, 'contract')
self.assertEqual(contract.symbol, 'FOF')
bar_data = self.create_bardata(
lambda: pd.Timestamp('2016-01-26', tz='UTC'))
contract = bar_data.current(cf_primary, 'contract')
self.assertEqual(contract.symbol, 'FOG',
'Auto close at beginning of session so FOG is now '
'the current contract.')
bar_data = self.create_bardata(
lambda: pd.Timestamp('2016-01-27', tz='UTC'))
contract = bar_data.current(cf_primary, 'contract')
self.assertEqual(contract.symbol, 'FOG')
def test_current_contract_in_algo(self):
code = dedent("""
from zipline.api import (
record,
continuous_future,
schedule_function,
get_datetime,
)
def initialize(algo):
algo.primary_cl = continuous_future('FO', 0, 'calendar')
algo.secondary_cl = continuous_future('FO', 1, 'calendar')
schedule_function(record_current_contract)
def record_current_contract(algo, data):
record(datetime=get_datetime())
record(primary=data.current(algo.primary_cl, 'contract'))
record(secondary=data.current(algo.secondary_cl, 'contract'))
""")
algo = TradingAlgorithm(script=code,
sim_params=self.sim_params,
trading_calendar=self.trading_calendar,
env=self.env)
results = algo.run(self.data_portal)
self.assertEqual(results.iloc[0].primary.symbol,
'FOF',
'Primary should be FOF on first session.')
self.assertEqual(results.iloc[0].secondary.symbol,
'FOG',
'Secondary should be FOG on first session.')
# Second day, primary should switch to FOG
self.assertEqual(results.iloc[1].primary.symbol,
'FOG',
'Primary should be FOG on second session, auto close '
'is at beginning of the session.')
self.assertEqual(results.iloc[1].secondary.symbol,
'FOH',
'Secondary should be FOH on second session, auto '
'close is at beginning of the session.')
# Second day, primary should switch to FOG
self.assertEqual(results.iloc[2].primary.symbol,
'FOG',
'Primary should remain as FOG on third session.')
self.assertEqual(results.iloc[2].secondary.symbol,
'FOH',
'Secondary should remain as FOG on third session.')
+2 -1
View File
@@ -25,6 +25,7 @@ from cpython cimport bool
from collections import Iterable
from zipline.assets import Asset, Future
from zipline.assets.continuous_futures import ContinuousFuture
from zipline.zipline_warnings import ZiplineDeprecationWarning
@@ -254,7 +255,7 @@ cdef class BarData:
return dt
@check_parameters(('assets', 'fields'),
((Asset,) + string_types, string_types))
((Asset, ContinuousFuture) + string_types, string_types))
def current(self, assets, fields):
"""
Returns the current value of the given assets for the given fields
+27
View File
@@ -1185,6 +1185,33 @@ class TradingAlgorithm(object):
as_of_date=_lookup_date,
)
@api_method
@preprocess(root_symbol_str=ensure_upper_case)
def continuous_future(self, root_symbol_str, offset, roll):
"""Create a specifier for a continuous contract.
Parameters
----------
root_symbol_str : str
The root symbol for the future chain.
offset : int
The distance from the primary contract.
roll_style : str
How rolls are determined.
Returns
-------
continuous_future : ContinuousFuture
The continuous future specifier.
"""
return self.asset_finder.create_continuous_future(
root_symbol_str,
offset,
roll,
)
@api_method
def symbols(self, *args):
"""Lookup multuple Equities as a list.
+98
View File
@@ -13,9 +13,12 @@
# limitations under the License.
from abc import ABCMeta
import array
import binascii
from collections import namedtuple
from numbers import Integral
from operator import itemgetter, attrgetter
import struct
from logbook import Logger
import numpy as np
@@ -46,6 +49,7 @@ from zipline.errors import (
from . import (
Asset, Equity, Future,
)
from . continuous_futures import OrderedContracts, ContinuousFuture
from .asset_writer import (
check_version_info,
split_delimited_symbol,
@@ -119,6 +123,41 @@ def _convert_asset_timestamp_fields(dict_):
return dict_
SID_TYPE_IDS = {
# Asset would be 0,
ContinuousFuture: 1,
}
CONTINUOUS_FUTURE_ROLL_STYLE_IDS = {
'calendar': 0
}
def _encode_continuous_future_sid(root_symbol, offset, roll_style):
s = struct.Struct("B 2B B B 3B")
# B - sid type
# 2B - root symbol
# B - offset (could be packed smaller since offsets of greater than 12 are
# probably unneeded.)
# B - roll type
# 3B - empty space left for parameterized roll types
# The root symbol currently supports 2 characters. If 3 char root symbols
# are needed, the size of the root symbol does not need to change, however
# writing the string directly will need to change to a scheme of writing
# the A-Z values in 5-bit chunks.
a = array.array('B', [0] * s.size)
rs = bytearray(root_symbol, 'ascii')
values = (SID_TYPE_IDS[ContinuousFuture],
rs[0],
rs[1],
offset,
CONTINUOUS_FUTURE_ROLL_STYLE_IDS[roll_style],
0, 0, 0,)
s.pack_into(a, 0, *values)
return int(binascii.hexlify(a), 16)
class AssetFinder(object):
"""
An AssetFinder is an interface to a database of Asset metadata written by
@@ -162,6 +201,8 @@ class AssetFinder(object):
# retrieve_asset will populate the cache on first retrieval.
self._caches = (self._asset_cache, self._asset_type_cache) = {}, {}
self._ordered_contracts = {}
# Populated on first call to `lifetimes`.
self._asset_lifetimes = None
@@ -821,6 +862,63 @@ class AssetFinder(object):
return sids
def _get_contract_info(self, root_symbol):
fc_cols = self.futures_contracts.c
fields = (fc_cols.sid, fc_cols.start_date, fc_cols.auto_close_date)
return list(sa.select(fields).where(
(fc_cols.root_symbol == root_symbol) &
(fc_cols.start_date != pd.NaT.value))
.order_by(fc_cols.auto_close_date).execute().fetchall())
def _get_root_symbol_exchange(self, root_symbol):
fc_cols = self.futures_root_symbols.c
fields = (fc_cols.exchange,)
return sa.select(fields).where(
fc_cols.root_symbol == root_symbol).execute().fetchone()[0]
def get_ordered_contracts(self, root_symbol):
try:
return self._ordered_contracts[root_symbol]
except KeyError:
contract_info = self._get_contract_info(root_symbol)
size = len(contract_info)
sids = np.full(size, 0, dtype=np.int64)
start_dates = np.full(size, 0, dtype=np.int64)
auto_close_dates = np.full(size, 0, dtype=np.int64)
self._size = size
for i, info in enumerate(contract_info):
sid, start_date, auto_close_date = info
sids[i] = sid
start_dates[i] = start_date
auto_close_dates[i] = auto_close_date
oc = OrderedContracts(root_symbol,
sids,
start_dates,
auto_close_dates)
self._ordered_contracts[root_symbol] = oc
return oc
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
exchange = self._get_root_symbol_exchange(root_symbol)
sid = _encode_continuous_future_sid(root_symbol, offset, roll_style)
cf = ContinuousFuture(sid,
root_symbol,
offset,
roll_style,
start_date,
end_date,
exchange)
self._asset_cache[cf.sid] = cf
return cf
def _make_sids(tblattr):
def _(self):
return tuple(map(
+296
View File
@@ -0,0 +1,296 @@
# cython: embedsignature=True
#
# 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.
"""
Cythonized ContinuousFutures object.
"""
cimport cython
from cpython.number cimport PyNumber_Index
from cpython.object cimport (
Py_EQ,
Py_NE,
Py_GE,
Py_LE,
Py_GT,
Py_LT,
)
from cpython cimport bool
import numpy as np
from numpy cimport long_t, int64_t
import warnings
cimport numpy as np
from zipline.utils.calendars import get_calendar
cdef class ContinuousFuture:
"""
Represents a specifier for a chain of future contracts, where the
coordinates for the chain are:
root_symbol : str
The root symbol of the contracts.
offset : int
The distance from the primary chain.
e.g. 0 specifies the primary chain, 1 the secondary, etc.
roll_style : str
How rolls from contract to contract should be calculated.
Currently supports 'calendar'.
Instances of this class are exposed to the algorithm.
"""
cdef readonly long_t sid
# Cached hash of self.sid
cdef long_t sid_hash
cdef readonly object root_symbol
cdef readonly int offset
cdef readonly object roll_style
cdef readonly object start_date
cdef readonly object end_date
cdef readonly object exchange
_kwargnames = frozenset({
'sid',
'root_symbol',
'offset',
'start_date',
'end_date',
'exchange',
})
def __init__(self,
long_t sid, # sid is required
object root_symbol,
int offset,
object roll_style,
object start_date,
object end_date,
object exchange):
self.sid = sid
self.sid_hash = hash(sid)
self.root_symbol = root_symbol
self.roll_style = roll_style
self.offset = offset
self.exchange = exchange
self.start_date = start_date
self.end_date = end_date
def __int__(self):
return self.sid
def __index__(self):
return self.sid
def __hash__(self):
return self.sid_hash
def __richcmp__(x, y, int op):
"""
Cython rich comparison method. This is used in place of various
equality checkers in pure python.
"""
cdef int x_as_int, y_as_int
try:
x_as_int = PyNumber_Index(x)
except (TypeError, OverflowError):
return NotImplemented
try:
y_as_int = PyNumber_Index(y)
except (TypeError, OverflowError):
return NotImplemented
compared = x_as_int - y_as_int
# Handle == and != first because they're significantly more common
# operations.
if op == Py_EQ:
return compared == 0
elif op == Py_NE:
return compared != 0
elif op == Py_LT:
return compared < 0
elif op == Py_LE:
return compared <= 0
elif op == Py_GT:
return compared > 0
elif op == Py_GE:
return compared >= 0
else:
raise AssertionError('%d is not an operator' % op)
def __str__(self):
return '%s(%d [%s, %s, %s])' % (
type(self).__name__,
self.sid,
self.root_symbol,
self.offset,
self.roll_style
)
def __repr__(self):
attrs = ('root_symbol', 'offset', 'roll_style')
tuples = ((attr, repr(getattr(self, attr, None)))
for attr in attrs)
strings = ('%s=%s' % (t[0], t[1]) for t in tuples)
params = ', '.join(strings)
return 'ContinuousFuture(%d, %s)' % (self.sid, params)
cpdef __reduce__(self):
"""
Function used by pickle to determine how to serialize/deserialize this
class. Should return a tuple whose first element is self.__class__,
and whose second element is a tuple of all the attributes that should
be serialized/deserialized during pickling.
"""
return (self.__class__, (self.sid,
self.root_symbol,
self.start_date,
self.end_date,
self.offset,
self.roll_style,
self.exchange))
cpdef to_dict(self):
"""
Convert to a python dict.
"""
return {
'sid': self.sid,
'root_symbol': self.root_symbol,
'start_date': self.start_date,
'end_date': self.end_date,
'offset': self.offset,
'roll_style': self.roll_style,
'exchange': self.exchange,
}
@classmethod
def from_dict(cls, dict_):
"""
Build an ContinuousFuture instance from a dict.
"""
return cls(**dict_)
def is_alive_for_session(self, session_label):
"""
Returns whether the continuous future is alive at the given dt.
Parameters
----------
session_label: pd.Timestamp
The desired session label to check. (midnight UTC)
Returns
-------
boolean: whether the continuous is alive at the given dt.
"""
cdef int64_t ref_start
cdef int64_t ref_end
ref_start = self.start_date.value
ref_end = self.end_date.value
return ref_start <= session_label.value <= ref_end
def is_exchange_open(self, dt_minute):
"""
Parameters
----------
dt_minute: pd.Timestamp (UTC, tz-aware)
The minute to check.
Returns
-------
boolean: whether the continuous futures's exchange is open at the
given minute.
"""
calendar = get_calendar(self.exchange)
return calendar.is_open_on_minute(dt_minute)
cdef class OrderedContracts(object):
"""
A container for aligned values of a future contract chain, in sorted order
of their occurrence.
Used to get answers about contracts in relation to their auto close
dates and start dates.
The number of contracts for a given root symbol is ~250,
which is why search by comparison over the range of contracts is
used. At this size, this is faster than using an index or np.searchsorted.
Members
-------
root_symbol : str
The root symbol of the future contract chain.
contract_sids : long[:]
The contract sids in sorted order of occurrence.
start_dates : long[:]
The start dates of the contracts in the chain.
Corresponds by index with contract_sids.
auto_close_dates : long[:]
The auto close dates of the contracts in the chain.
Corresponds by index with contract_sids.
Instances of this class are used by the simulation engine, but not
exposed to the algorithm.
"""
cdef readonly object root_symbol
cdef int _size
cdef readonly long_t[:] contract_sids
cdef readonly long_t[:] start_dates
cdef readonly long_t[:] auto_close_dates
def __init__(self,
object root_symbol,
long_t[:] contract_sids,
long_t[:] start_dates,
long_t[:] auto_close_dates):
self._size = len(contract_sids)
self.root_symbol = root_symbol
self.contract_sids = contract_sids
self.start_dates = start_dates
self.auto_close_dates = auto_close_dates
cpdef long contract_before_auto_close(self, long_t dt_value):
"""
Get the contract with next upcoming auto close date.
"""
cdef Py_ssize_t i, auto_close_date
for i, auto_close_date in enumerate(self.auto_close_dates):
if auto_close_date > dt_value:
break
return self.contract_sids[i]
cpdef long contract_at_offset(self, long_t sid, Py_ssize_t offset):
"""
Get the sid which is the given sid plus the offset distance.
An offset of 0 should be reflexive.
"""
cdef Py_ssize_t i
cdef long_t[:] sids
sids = self.contract_sids
for i in range(self._size):
if sid == sids[i]:
return sids[i + offset]
+63
View File
@@ -0,0 +1,63 @@
#
# 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
class RollFinder(with_metaclass(ABCMeta, object)):
"""
Abstract base class for calculating when futures contracts are the active
contract.
"""
@abstractmethod
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.
"""
raise NotImplemented
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 get_contract_center(self, root_symbol, dt, offset):
oc = self.asset_finder.get_ordered_contracts(root_symbol)
session = self.trading_calendar.minute_to_session_label(dt)
primary_candidate = oc.contract_before_auto_close(session.value)
# Here is where a volume check would be.
primary = primary_candidate
return oc.contract_at_offset(primary, offset)
+28 -2
View File
@@ -23,6 +23,8 @@ from six import iteritems
from six.moves import reduce
from zipline.assets import Asset, Future, Equity
from zipline.assets.continuous_futures import ContinuousFuture
from zipline.assets.roll_finder import CalendarRollFinder
from zipline.data.dispatch_bar_reader import (
AssetDispatchMinuteBarReader,
AssetDispatchSessionBarReader
@@ -54,7 +56,14 @@ from zipline.errors import (
log = Logger('DataPortal')
BASE_FIELDS = frozenset([
"open", "high", "low", "close", "volume", "price", "last_traded"
"open",
"high",
"low",
"close",
"volume",
"price",
"contract",
"last_traded",
])
OHLCV_FIELDS = frozenset([
@@ -143,6 +152,11 @@ class DataPortal(object):
else:
self._last_trading_session = None
self._roll_finders = {
'calendar': CalendarRollFinder(self.trading_calendar,
self.asset_finder)
}
aligned_equity_minute_reader = self._ensure_reader_aligned(
equity_minute_reader)
aligned_equity_session_reader = self._ensure_reader_aligned(
@@ -342,7 +356,8 @@ class DataPortal(object):
# at it if it's on something like palladium and not AAPL (since our
# own price data always wins when dealing with assets).
return not (field in BASE_FIELDS and isinstance(asset, Asset))
return not (field in BASE_FIELDS and
(isinstance(asset, (Asset, ContinuousFuture))))
def _get_fetcher_value(self, asset, field, dt):
day = normalize_date(dt)
@@ -397,6 +412,8 @@ class DataPortal(object):
return 0
elif field != "last_traded":
return np.NaN
elif field == "contract":
return None
if data_frequency == "daily":
return self._get_daily_data(asset, field, session_label)
@@ -406,6 +423,8 @@ class DataPortal(object):
elif field == "price":
return self._get_minute_spot_value(asset, "close", dt,
ffill=True)
elif field == "contract":
return self._get_current_contract(asset, dt)
else:
return self._get_minute_spot_value(asset, field, dt)
@@ -1211,3 +1230,10 @@ class DataPortal(object):
ret = np.nan
return ret
def _get_current_contract(self, continuous_future, dt):
rf = self._roll_finders[continuous_future.roll_style]
return self.asset_finder.retrieve_asset(
rf.get_contract_center(continuous_future.root_symbol,
dt,
continuous_future.offset))