mirror of
https://github.com/wassname/catalyst.git
synced 2026-06-28 11:51:47 +08:00
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:
@@ -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(
|
||||
|
||||
@@ -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)),
|
||||
)
|
||||
|
||||
@@ -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.')
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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]
|
||||
@@ -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)
|
||||
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user