mirror of
https://github.com/wassname/catalyst.git
synced 2026-06-27 19:30:28 +08:00
ENH: Simplified implementation of FutureChain object (not user-facing API).
No longer auto-updates its internal as-of date, instead requires an explicit as-of date from the consumer. Take a static list of contracts (instead of needing an assetfinder). Instead of the as_of method, the user-facing API now lets you pass in an offset, which is defined as an integral number of sessions.
This commit is contained in:
+44
-2
@@ -726,9 +726,51 @@ def log_nyse_close(context, data):
|
||||
with self.assertRaises(TypeError):
|
||||
algo.future_symbol({'foo': 'bar'})
|
||||
|
||||
def test_future_chain_offset(self):
|
||||
# November 2006 December 2006
|
||||
# Su Mo Tu We Th Fr Sa Su Mo Tu We Th Fr Sa
|
||||
# 1 2 3 4 1 2
|
||||
# 5 6 7 8 9 10 11 3 4 5 6 7 8 9
|
||||
# 12 13 14 15 16 17 18 10 11 12 13 14 15 16
|
||||
# 19 20 21 22 23 24 25 17 18 19 20 21 22 23
|
||||
# 26 27 28 29 30 24 25 26 27 28 29 30
|
||||
# 31
|
||||
|
||||
algo = TradingAlgorithm(env=self.env)
|
||||
algo.datetime = pd.Timestamp('2006-12-01', tz='UTC')
|
||||
|
||||
self.assertEqual(
|
||||
algo.future_chain('CL', offset=1).as_of_date,
|
||||
pd.Timestamp("2006-12-04", tz='UTC')
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
algo.future_chain("CL", offset=5).as_of_date,
|
||||
pd.Timestamp("2006-12-08", tz='UTC')
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
algo.future_chain("CL", offset=-10).as_of_date,
|
||||
pd.Timestamp("2006-11-16", tz='UTC')
|
||||
)
|
||||
|
||||
# September 2016
|
||||
# Su Mo Tu We Th Fr Sa
|
||||
# 1 2 3
|
||||
# 4 5 6 7 8 9 10
|
||||
# 11 12 13 14 15 16 17
|
||||
# 18 19 20 21 22 23 24
|
||||
# 25 26 27 28 29 30
|
||||
self.assertEqual(
|
||||
algo.future_chain(
|
||||
"CL",
|
||||
as_of_date=pd.Timestamp("2016-08-31", tz='UTC'),
|
||||
offset=10
|
||||
).as_of_date,
|
||||
pd.Timestamp("2016-09-15", tz='UTC')
|
||||
)
|
||||
|
||||
def test_future_chain(self):
|
||||
""" Tests the future_chain API function.
|
||||
"""
|
||||
algo = TradingAlgorithm(env=self.env)
|
||||
algo.datetime = pd.Timestamp('2006-12-01', tz='UTC')
|
||||
|
||||
|
||||
+34
-105
@@ -17,7 +17,7 @@
|
||||
Tests for the zipline.assets package
|
||||
"""
|
||||
from contextlib import contextmanager
|
||||
from datetime import datetime, timedelta
|
||||
from datetime import timedelta
|
||||
from functools import partial
|
||||
import pickle
|
||||
import sys
|
||||
@@ -1169,56 +1169,65 @@ class TestFutureChain(WithAssetFinder, ZiplineTestCase):
|
||||
}
|
||||
])
|
||||
|
||||
def _get_future_chain(self, date_str, symbol):
|
||||
dt = pd.Timestamp(date_str, tz='UTC')
|
||||
|
||||
return FutureChain(
|
||||
symbol,
|
||||
dt,
|
||||
self.asset_finder.lookup_future_chain(symbol, dt)
|
||||
)
|
||||
|
||||
def test_len(self):
|
||||
""" Test the __len__ method of FutureChain.
|
||||
"""
|
||||
# Sids 0, 1, & 2 have started, 3 has not yet started, but all are in
|
||||
# the chain
|
||||
cl = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL')
|
||||
cl = self._get_future_chain('2005-12-01', 'CL')
|
||||
self.assertEqual(len(cl), 4)
|
||||
|
||||
# Sid 0 is still valid on its notice date.
|
||||
cl = FutureChain(self.asset_finder, lambda: '2005-12-20', 'CL')
|
||||
cl = self._get_future_chain('2005-12-20', 'CL')
|
||||
self.assertEqual(len(cl), 4)
|
||||
|
||||
# Sid 0 is now invalid, leaving Sids 1 & 2 valid (and 3 not started).
|
||||
cl = FutureChain(self.asset_finder, lambda: '2005-12-21', 'CL')
|
||||
cl = self._get_future_chain('2005-12-21', 'CL')
|
||||
self.assertEqual(len(cl), 3)
|
||||
|
||||
# Sid 3 has started, so 1, 2, & 3 are now valid.
|
||||
cl = FutureChain(self.asset_finder, lambda: '2006-02-01', 'CL')
|
||||
cl = self._get_future_chain('2006-02-01', 'CL')
|
||||
self.assertEqual(len(cl), 3)
|
||||
|
||||
# All contracts are no longer valid.
|
||||
cl = FutureChain(self.asset_finder, lambda: '2006-09-21', 'CL')
|
||||
cl = self._get_future_chain('2006-09-21', 'CL')
|
||||
self.assertEqual(len(cl), 0)
|
||||
|
||||
def test_getitem(self):
|
||||
""" Test the __getitem__ method of FutureChain.
|
||||
"""
|
||||
cl = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL')
|
||||
cl = self._get_future_chain('2005-12-01', 'CL')
|
||||
self.assertEqual(cl[0], 0)
|
||||
self.assertEqual(cl[1], 1)
|
||||
self.assertEqual(cl[2], 2)
|
||||
|
||||
cl = FutureChain(self.asset_finder, lambda: '2005-12-20', 'CL')
|
||||
cl = self._get_future_chain('2005-12-20', 'CL')
|
||||
self.assertEqual(cl[0], 0)
|
||||
|
||||
cl = FutureChain(self.asset_finder, lambda: '2005-12-21', 'CL')
|
||||
cl = self._get_future_chain('2005-12-21', 'CL')
|
||||
self.assertEqual(cl[0], 1)
|
||||
|
||||
cl = FutureChain(self.asset_finder, lambda: '2006-02-01', 'CL')
|
||||
cl = self._get_future_chain('2006-02-01', 'CL')
|
||||
self.assertEqual(cl[-1], 3)
|
||||
|
||||
def test_iter(self):
|
||||
""" Test the __iter__ method of FutureChain.
|
||||
"""
|
||||
cl = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL')
|
||||
cl = self._get_future_chain('2005-12-01', 'CL')
|
||||
for i, contract in enumerate(cl):
|
||||
self.assertEqual(contract, i)
|
||||
|
||||
# First contract is now invalid, so sids will be offset by one
|
||||
cl = FutureChain(self.asset_finder, lambda: '2005-12-21', 'CL')
|
||||
cl = self._get_future_chain('2005-12-21', 'CL')
|
||||
for i, contract in enumerate(cl):
|
||||
self.assertEqual(contract, i + 1)
|
||||
|
||||
@@ -1227,116 +1236,36 @@ class TestFutureChain(WithAssetFinder, ZiplineTestCase):
|
||||
as expected.
|
||||
"""
|
||||
# Make sure this successfully gets the chain for CL.
|
||||
cl = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL')
|
||||
cl = self._get_future_chain('2005-12-01', 'CL')
|
||||
self.assertEqual(cl.root_symbol, 'CL')
|
||||
|
||||
# These root symbols don't exist, so RootSymbolNotFound should
|
||||
# be raised immediately.
|
||||
with self.assertRaises(RootSymbolNotFound):
|
||||
FutureChain(self.asset_finder, lambda: '2005-12-01', 'CLZ')
|
||||
self._get_future_chain('2005-12-01', 'CLZ')
|
||||
|
||||
with self.assertRaises(RootSymbolNotFound):
|
||||
FutureChain(self.asset_finder, lambda: '2005-12-01', '')
|
||||
self._get_future_chain('2005-12-01', '')
|
||||
|
||||
def test_repr(self):
|
||||
""" Test the __repr__ method of FutureChain.
|
||||
"""
|
||||
cl = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL')
|
||||
cl_feb = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL',
|
||||
as_of_date=pd.Timestamp('2006-02-01', tz='UTC'))
|
||||
cl = self._get_future_chain('2005-12-01', 'CL')
|
||||
|
||||
# The default chain should not include the as of date.
|
||||
self.assertEqual(repr(cl), "FutureChain(root_symbol='CL')")
|
||||
|
||||
# An explicit as of date should show up in the repr.
|
||||
self.assertEqual(
|
||||
repr(cl_feb),
|
||||
("FutureChain(root_symbol='CL', "
|
||||
"as_of_date='2006-02-01 00:00:00+00:00')")
|
||||
repr(cl),
|
||||
"FutureChain('CL', '2005-12-01')"
|
||||
)
|
||||
|
||||
def test_as_of(self):
|
||||
""" Test the as_of method of FutureChain.
|
||||
"""
|
||||
cl = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL')
|
||||
def test_contracts_returns_a_copy(self):
|
||||
cl = self._get_future_chain('2005-12-01', 'CL')
|
||||
self.assertEqual(len(cl), 4)
|
||||
|
||||
# Test that the as_of_date is set correctly to the future
|
||||
feb = pd.Timestamp('2006-02-01', tz='UTC')
|
||||
cl_feb = cl.as_of(feb)
|
||||
self.assertEqual(
|
||||
cl_feb.as_of_date,
|
||||
pd.Timestamp(feb, tz='UTC')
|
||||
)
|
||||
contracts = cl.contracts
|
||||
contracts.pop(0)
|
||||
|
||||
# Test that the as_of_date is set correctly to the past, with
|
||||
# args of str, datetime.datetime, and pd.Timestamp.
|
||||
feb_prev = pd.Timestamp('2005-02-01', tz='UTC')
|
||||
cl_feb_prev = cl.as_of(feb_prev)
|
||||
self.assertEqual(
|
||||
cl_feb_prev.as_of_date,
|
||||
pd.Timestamp(feb_prev, tz='UTC')
|
||||
)
|
||||
|
||||
feb_prev = pd.Timestamp(datetime(year=2005, month=2, day=1), tz='UTC')
|
||||
cl_feb_prev = cl.as_of(feb_prev)
|
||||
self.assertEqual(
|
||||
cl_feb_prev.as_of_date,
|
||||
pd.Timestamp(feb_prev, tz='UTC')
|
||||
)
|
||||
|
||||
feb_prev = pd.Timestamp('2005-02-01', tz='UTC')
|
||||
cl_feb_prev = cl.as_of(feb_prev)
|
||||
self.assertEqual(
|
||||
cl_feb_prev.as_of_date,
|
||||
pd.Timestamp(feb_prev, tz='UTC')
|
||||
)
|
||||
|
||||
# Test that the as_of() method works with str args
|
||||
feb_str = '2006-02-01'
|
||||
cl_feb = cl.as_of(feb_str)
|
||||
self.assertEqual(
|
||||
cl_feb.as_of_date,
|
||||
pd.Timestamp(feb, tz='UTC')
|
||||
)
|
||||
|
||||
# The chain as of the current dt should always be the same as
|
||||
# the defualt chain.
|
||||
self.assertEqual(cl[0], cl.as_of(pd.Timestamp('2005-12-01'))[0])
|
||||
|
||||
def test_offset(self):
|
||||
""" Test the offset method of FutureChain.
|
||||
"""
|
||||
cl = FutureChain(self.asset_finder, lambda: '2005-12-01', 'CL')
|
||||
|
||||
# Test that an offset forward sets as_of_date as expected
|
||||
self.assertEqual(
|
||||
cl.offset('3 days').as_of_date,
|
||||
cl.as_of_date + pd.Timedelta(days=3)
|
||||
)
|
||||
|
||||
# Test that an offset backward sets as_of_date as expected, with
|
||||
# time delta given as str, datetime.timedelta, and pd.Timedelta.
|
||||
self.assertEqual(
|
||||
cl.offset('-1000 days').as_of_date,
|
||||
cl.as_of_date + pd.Timedelta(days=-1000)
|
||||
)
|
||||
self.assertEqual(
|
||||
cl.offset(timedelta(days=-1000)).as_of_date,
|
||||
cl.as_of_date + pd.Timedelta(days=-1000)
|
||||
)
|
||||
self.assertEqual(
|
||||
cl.offset(pd.Timedelta('-1000 days')).as_of_date,
|
||||
cl.as_of_date + pd.Timedelta(days=-1000)
|
||||
)
|
||||
|
||||
# An offset of zero should give the original chain.
|
||||
self.assertEqual(cl[0], cl.offset(0)[0])
|
||||
self.assertEqual(cl[0], cl.offset("0 days")[0])
|
||||
|
||||
# A string that doesn't represent a time delta should raise a
|
||||
# ValueError.
|
||||
with self.assertRaises(ValueError):
|
||||
cl.offset("blah")
|
||||
self.assertEqual(len(contracts), 3)
|
||||
self.assertEqual(len(cl), 4)
|
||||
|
||||
def test_cme_code_to_month(self):
|
||||
codes = {
|
||||
|
||||
+35
-10
@@ -1242,17 +1242,20 @@ class TradingAlgorithm(object):
|
||||
|
||||
@api_method
|
||||
@preprocess(root_symbol=ensure_upper_case)
|
||||
def future_chain(self, root_symbol, as_of_date=None):
|
||||
"""Look up a future chain with the specified parameters.
|
||||
def future_chain(self, root_symbol, as_of_date=None, offset=0):
|
||||
"""
|
||||
Look up a future chain.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
root_symbol : str
|
||||
The root symbol of a future chain.
|
||||
as_of_date : datetime.datetime or pandas.Timestamp or str, optional
|
||||
Date at which the chain determination is rooted. I.e. the
|
||||
existing contract whose notice date is first after this date is
|
||||
the primary contract, etc.
|
||||
Date at which the chain determination is rooted. If this date is
|
||||
not passed in, the current simulation session (not minute) is used.
|
||||
offset: int
|
||||
Number of sessions to shift `as_of_date`. Positive values shift
|
||||
forward in time. Negative values shift backward in time.
|
||||
|
||||
Returns
|
||||
-------
|
||||
@@ -1268,13 +1271,35 @@ class TradingAlgorithm(object):
|
||||
try:
|
||||
as_of_date = pd.Timestamp(as_of_date, tz='UTC')
|
||||
except ValueError:
|
||||
raise UnsupportedDatetimeFormat(input=as_of_date,
|
||||
method='future_chain')
|
||||
raise UnsupportedDatetimeFormat(
|
||||
input=as_of_date,
|
||||
method='future_chain'
|
||||
)
|
||||
else:
|
||||
as_of_date = self.trading_calendar.minute_to_session_label(
|
||||
self.get_datetime()
|
||||
)
|
||||
|
||||
if offset != 0:
|
||||
# move as_of_date by offset sessions
|
||||
session_window = self.trading_calendar.sessions_window(
|
||||
as_of_date, offset
|
||||
)
|
||||
|
||||
if offset > 0:
|
||||
as_of_date = session_window[-1]
|
||||
else:
|
||||
as_of_date = session_window[0]
|
||||
|
||||
chain_of_contracts = self.asset_finder.lookup_future_chain(
|
||||
root_symbol,
|
||||
as_of_date
|
||||
)
|
||||
|
||||
return FutureChain(
|
||||
asset_finder=self.asset_finder,
|
||||
get_datetime=self.get_datetime,
|
||||
root_symbol=root_symbol,
|
||||
as_of_date=as_of_date
|
||||
as_of_date=as_of_date,
|
||||
contracts=chain_of_contracts
|
||||
)
|
||||
|
||||
def _calculate_order_value_amount(self, asset, value):
|
||||
|
||||
+40
-127
@@ -1,5 +1,5 @@
|
||||
#
|
||||
# Copyright 2015 Quantopian, Inc.
|
||||
# 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.
|
||||
@@ -13,173 +13,86 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from pandas import Timestamp, Timedelta
|
||||
from pandas.tseries.tools import normalize_date
|
||||
from pandas import Timestamp
|
||||
|
||||
from zipline.utils.input_validation import expect_types
|
||||
|
||||
|
||||
class FutureChain(object):
|
||||
""" Allows users to look up future contracts.
|
||||
"""
|
||||
Allows users to look up future contracts.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
asset_finder : AssetFinder
|
||||
An AssetFinder for future contract lookups, in particular the
|
||||
AssetFinder of the TradingAlgorithm instance.
|
||||
get_datetime : function
|
||||
A function that returns the simulation datetime, in particular
|
||||
the get_datetime method of the TradingAlgorithm instance.
|
||||
root_symbol : str
|
||||
The root symbol of a future chain.
|
||||
as_of_date : pandas.Timestamp, optional
|
||||
as_of_date : pandas.Timestamp
|
||||
Date at which the chain determination is rooted. I.e. the
|
||||
existing contract whose notice date is first after this date is
|
||||
the primary contract, etc. If not provided, the current
|
||||
simulation date is used as the as_of_date.
|
||||
the primary contract, etc.
|
||||
chain: list
|
||||
List of assets that represent the chain of contracts for the given
|
||||
root symbol at the given as_of_date.
|
||||
|
||||
Attributes
|
||||
----------
|
||||
root_symbol : str
|
||||
The root symbol of the future chain.
|
||||
as_of_date
|
||||
as_of_date: Timestamp
|
||||
The current as-of date of this future chain.
|
||||
|
||||
Methods
|
||||
-------
|
||||
as_of(dt)
|
||||
offset(time_delta)
|
||||
|
||||
Raises
|
||||
------
|
||||
RootSymbolNotFound
|
||||
Raised when the FutureChain is initialized with a root symbol for which
|
||||
a future chain could not be found.
|
||||
"""
|
||||
def __init__(self, asset_finder, get_datetime, root_symbol,
|
||||
as_of_date=None):
|
||||
self.root_symbol = root_symbol
|
||||
|
||||
# Reference to the algo's AssetFinder for contract lookups
|
||||
self._asset_finder = asset_finder
|
||||
# Reference to the algo's get_datetime to know the current dt
|
||||
self._algorithm_get_datetime = get_datetime
|
||||
|
||||
# If an as_of_date is provided, self._as_of_date uses that
|
||||
# value, otherwise None. This attribute backs the as_of_date property.
|
||||
if as_of_date:
|
||||
self._as_of_date = normalize_date(as_of_date)
|
||||
else:
|
||||
self._as_of_date = None
|
||||
|
||||
# Attribute to cache the most up-to-date chain, and the dt when it was
|
||||
# last updated.
|
||||
self._current_chain = []
|
||||
self._last_updated = None
|
||||
|
||||
# Get the initial chain, since self._last_updated is None.
|
||||
self._maybe_update_current_chain()
|
||||
@expect_types(root_symbol=str, as_of_date=Timestamp)
|
||||
def __init__(self, root_symbol, as_of_date, contracts):
|
||||
self._root_symbol = root_symbol
|
||||
self._as_of_date = as_of_date
|
||||
self._contracts = contracts
|
||||
|
||||
def __repr__(self):
|
||||
# NOTE: The string returned cannot be used to instantiate this
|
||||
# exact FutureChain, since we don't want to display the asset
|
||||
# finder and get_datetime function to the user.
|
||||
if self._as_of_date:
|
||||
return "FutureChain(root_symbol='%s', as_of_date='%s')" % (
|
||||
self.root_symbol, self.as_of_date)
|
||||
else:
|
||||
return "FutureChain(root_symbol='%s')" % self.root_symbol
|
||||
return "FutureChain('%s', '%s')" % (
|
||||
self.root_symbol, self.as_of_date.strftime('%Y-%m-%d'))
|
||||
|
||||
def _get_datetime(self):
|
||||
@property
|
||||
def root_symbol(self):
|
||||
"""
|
||||
Returns the normalized simulation datetime.
|
||||
The root symbol for this future chain.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pandas.Timestamp
|
||||
The normalized datetime of FutureChain's TradingAlgorithm.
|
||||
root_symbol: str
|
||||
The root symbol for this chain.
|
||||
"""
|
||||
return normalize_date(
|
||||
Timestamp(self._algorithm_get_datetime(), tz='UTC')
|
||||
)
|
||||
return self._root_symbol
|
||||
|
||||
@property
|
||||
def as_of_date(self):
|
||||
"""
|
||||
The current as-of date of this future chain.
|
||||
The as-of date of this future chain.
|
||||
|
||||
Returns
|
||||
-------
|
||||
pandas.Timestamp
|
||||
The user-provided as_of_date if given, otherwise the
|
||||
current datetime of the simulation.
|
||||
as_of_date: pd.Timestamp
|
||||
The as_of date for this chain.
|
||||
"""
|
||||
if self._as_of_date is not None:
|
||||
return self._as_of_date
|
||||
else:
|
||||
return self._get_datetime()
|
||||
return self._as_of_date
|
||||
|
||||
def _maybe_update_current_chain(self):
|
||||
""" Updates the current chain if it's out of date, then returns
|
||||
it.
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
The up-to-date current chain, a list of Future objects.
|
||||
@property
|
||||
def contracts(self):
|
||||
"""
|
||||
if (self._last_updated is None)\
|
||||
or (self._last_updated != self.as_of_date):
|
||||
self._current_chain = self._asset_finder.lookup_future_chain(
|
||||
self.root_symbol,
|
||||
self.as_of_date
|
||||
)
|
||||
self._last_updated = self.as_of_date
|
||||
|
||||
return self._current_chain
|
||||
Returns
|
||||
-------
|
||||
contracts: list
|
||||
The contracts wrapped by this chain.
|
||||
"""
|
||||
return list(self._contracts)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._maybe_update_current_chain()[key]
|
||||
return self._contracts[key]
|
||||
|
||||
def __len__(self):
|
||||
return len(self._maybe_update_current_chain())
|
||||
return len(self._contracts)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self._maybe_update_current_chain())
|
||||
|
||||
def as_of(self, dt):
|
||||
""" Get the future chain for this root symbol as of a specific date.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dt : datetime.datetime or pandas.Timestamp or str, optional
|
||||
The as_of_date for the new chain.
|
||||
|
||||
Returns
|
||||
-------
|
||||
FutureChain
|
||||
|
||||
"""
|
||||
return FutureChain(
|
||||
asset_finder=self._asset_finder,
|
||||
get_datetime=self._algorithm_get_datetime,
|
||||
root_symbol=self.root_symbol,
|
||||
as_of_date=Timestamp(dt, tz='UTC'),
|
||||
)
|
||||
|
||||
def offset(self, time_delta):
|
||||
""" Get the future chain for this root symbol with a given
|
||||
offset from the current as_of_date.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
time_delta : datetime.timedelta or pandas.Timedelta or str
|
||||
The offset from the current as_of_date for the new chain.
|
||||
|
||||
Returns
|
||||
-------
|
||||
FutureChain
|
||||
|
||||
"""
|
||||
return self.as_of(self.as_of_date + Timedelta(time_delta))
|
||||
return iter(self._contracts)
|
||||
|
||||
|
||||
# http://www.cmegroup.com/product-codes-listing/month-codes.html
|
||||
|
||||
Reference in New Issue
Block a user