Skip to content

Commit

Permalink
Merge 5b425d5 into 7c72eef
Browse files Browse the repository at this point in the history
  • Loading branch information
ehebert committed Oct 21, 2016
2 parents 7c72eef + 5b425d5 commit 5d56922
Show file tree
Hide file tree
Showing 6 changed files with 408 additions and 25 deletions.
189 changes: 189 additions & 0 deletions tests/test_continuous_futures.py
Expand Up @@ -483,6 +483,132 @@ def test_history_close_session(self):
135441.440,
err_msg="On session after roll, Should be FOJ16's 44th value.")

def test_history_close_session_adjusted(self):
cf = self.data_portal.asset_finder.create_continuous_future(
'FO', 0, 'calendar')
cf_mul = self.data_portal.asset_finder.create_continuous_future(
'FO', 0, 'calendar').adj('mul')
cf_add = self.data_portal.asset_finder.create_continuous_future(
'FO', 0, 'calendar').adj('add')
window = self.data_portal.get_history_window(
[cf, cf_mul, cf_add],
Timestamp('2016-03-06', tz='UTC'), 30, '1d', 'close')

# Unadjusted value is: 115011.44
# Adjustment is based on hop from 115231.44 to 122240.001
# a ratio of ~1.06
assert_almost_equal(
window.loc['2016-01-26', cf_mul],
122006.62,
err_msg="At beginning of window, should be FOG16's first value, "
"adjusted.")

# Difference of 7008.561
assert_almost_equal(
window.loc['2016-01-26', cf_add],
122020.001,
err_msg="At beginning of window, should be FOG16's first value, "
"adjusted.")

assert_almost_equal(
window.loc['2016-02-26', cf_mul],
125241.440,
err_msg="On session with roll, should be FOH16's 24th value, "
"unadjusted.")

assert_almost_equal(
window.loc['2016-02-26', cf_add],
125241.440,
err_msg="On session with roll, should be FOH16's 24th value, "
"unadjusted.")

assert_almost_equal(
window.loc['2016-02-29', cf_mul],
125251.440,
err_msg="After roll, Should be FOH16's 25th value, unadjusted.")

assert_almost_equal(
window.loc['2016-02-29', cf_add],
125251.440,
err_msg="After roll, Should be FOH16's 25th value, unadjusted.")

# Advance the window a month.
window = self.data_portal.get_history_window(
[cf, cf_mul, cf_add],
Timestamp('2016-04-06', tz='UTC'), 30, '1d', 'close')

# Unadjusted value: 115221.44
# Adjustments based on hops:
# 2016-02-25 00:00:00+00:00 115231.440
# 2016-02-26 00:00:00+00:00 122240.001
# ratio: ~1.061
# difference: 7008.561
# and
# 2016-03-23 00:00:00+00:00 125421.440
# 2016-03-24 00:00:00+00:00 132430.001
# ratio: ~1.056
# difference: 7008.56
assert_almost_equal(
window.loc['2016-02-24', cf_mul],
129059.581,
err_msg="At beginning of window, should be FOG16's 22nd value, "
"with two adjustments.")

assert_almost_equal(
window.loc['2016-02-24', cf_add],
129238.561,
err_msg="At beginning of window, should be FOG16's 22nd value, "
"with two adjustments")

# Unadjusted: 125241.44
assert_almost_equal(
window.loc['2016-02-26', cf_mul],
132239.942,
err_msg="On session with roll, should be FOH16's 24th value, "
"with one adjustment.")

assert_almost_equal(
window.loc['2016-02-26', cf_add],
132250.0,
err_msg="On session with roll, should be FOH16's 24th value, "
"with one adjustment.")

# Unadjusted: 125251.44
assert_almost_equal(
window.loc['2016-02-29', cf_mul],
132250.500,
err_msg="On session after roll, should be FOH16's 25th value, "
"with one adjustment.")

assert_almost_equal(
window.loc['2016-02-29', cf_add],
132260.000,
err_msg="On session after roll, should be FOH16's 25th value, "
"unadjusted.")

# Unadjusted: 135431.44
assert_almost_equal(
window.loc['2016-03-24', cf_mul],
135431.44,
err_msg="On session with roll, should be FOJ16's 43rd value, "
"unadjusted.")

assert_almost_equal(
window.loc['2016-03-24', cf_add],
135431.44,
err_msg="On session with roll, should be FOJ16's 43rd value.")

# Unadjusted: 135441.44
assert_almost_equal(
window.loc['2016-03-28', cf_mul],
135441.44,
err_msg="On session after roll, Should be FOJ16's 44th value.")

assert_almost_equal(
window.loc['2016-03-28', cf_add],
135441.44,
err_msg="On session after roll, Should be FOJ16's 44th value.")

def test_history_close_minute(self):
cf = self.data_portal.asset_finder.create_continuous_future(
'FO', 0, 'calendar')
Expand Down Expand Up @@ -518,6 +644,69 @@ def test_history_close_minute(self):
125250.001,
"Should remain FOH16 on next session.")

def test_history_close_minute_adjusted(self):
cf = self.data_portal.asset_finder.create_continuous_future(
'FO', 0, 'calendar')
cf_mul = self.data_portal.asset_finder.create_continuous_future(
'FO', 0, 'calendar').adj('mul')
cf_add = self.data_portal.asset_finder.create_continuous_future(
'FO', 0, 'calendar').adj('add')
window = self.data_portal.get_history_window(
[cf, cf_mul, cf_add],
Timestamp('2016-02-25 18:01', tz='US/Eastern').tz_convert('UTC'),
30, '1m', 'close')

# Unadjusted: 115231.412
# Adjustment based on roll:
# 2016-02-25 23:00:00+00:00 115231.440
# 2016-02-25 23:01:00+00:00 122240.001
# Ratio: ~1.061
# Difference: 7008.561
self.assertEqual(window.loc['2016-02-25 22:32', cf_mul],
122239.971,
"Should be FOG16 at beginning of window. A minute "
"which is in the 02-25 session, before the roll.")

self.assertEqual(window.loc['2016-02-25 22:32', cf_add],
122239.973,
"Should be FOG16 at beginning of window. A minute "
"which is in the 02-25 session, before the roll.")

# Unadjusted: 115231.44
# Should use same ratios as above.
self.assertEqual(window.loc['2016-02-25 23:00', cf_mul],
122240.001,
"Should be FOG16 on on minute before roll minute, "
"adjusted.")

self.assertEqual(window.loc['2016-02-25 23:00', cf_add],
122240.001,
"Should be FOG16 on on minute before roll minute, "
"adjusted.")

self.assertEqual(window.loc['2016-02-25 23:01', cf_mul],
125240.001,
"Should be FOH16 on minute after roll, unadjusted.")

self.assertEqual(window.loc['2016-02-25 23:01', cf_add],
125240.001,
"Should be FOH16 on minute after roll, unadjusted.")

# Advance the window a session.
window = self.data_portal.get_history_window(
[cf, cf_mul, cf_add],
Timestamp('2016-02-28 18:01', tz='US/Eastern').tz_convert('UTC'),
30, '1m', 'close')

# No adjustments in this window.
self.assertEqual(window.loc['2016-02-26 22:32', cf_mul],
125241.412,
"Should be FOH16 at beginning of window.")

self.assertEqual(window.loc['2016-02-28 23:01', cf_mul],
125250.001,
"Should remain FOH16 on next session.")


class OrderedContractsTestCase(ZiplineTestCase):

Expand Down
53 changes: 47 additions & 6 deletions zipline/assets/assets.py
Expand Up @@ -132,15 +132,25 @@ def _convert_asset_timestamp_fields(dict_):
'calendar': 0
}

CONTINUOUS_FUTURE_ADJUSTMENT_STYLE_IDS = {
None: 0,
'div': 1,
'add': 2,
}


def _encode_continuous_future_sid(root_symbol, offset, roll_style):
s = struct.Struct("B 2B B B 3B")
def _encode_continuous_future_sid(root_symbol,
offset,
roll_style,
adjustment_style):
s = struct.Struct("B 2B B B B 2B")
# 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
# B - adjustment
# 2B - 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
Expand All @@ -153,7 +163,8 @@ def _encode_continuous_future_sid(root_symbol, offset, roll_style):
rs[1],
offset,
CONTINUOUS_FUTURE_ROLL_STYLE_IDS[roll_style],
0, 0, 0,)
CONTINUOUS_FUTURE_ADJUSTMENT_STYLE_IDS[adjustment_style],
0, 0)
s.pack_into(a, 0, *values)
return int(binascii.hexlify(a), 16)

Expand Down Expand Up @@ -908,15 +919,45 @@ def create_continuous_future(self, root_symbol, offset, roll_style):
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)
sid = _encode_continuous_future_sid(root_symbol, offset,
roll_style,
None)
mul_sid = _encode_continuous_future_sid(root_symbol, offset,
roll_style,
'div')
add_sid = _encode_continuous_future_sid(root_symbol, offset,
roll_style,
'add')
mul_cf = ContinuousFuture(mul_sid,
root_symbol,
offset,
roll_style,
start_date,
end_date,
exchange,
'mul')
add_cf = ContinuousFuture(add_sid,
root_symbol,
offset,
roll_style,
start_date,
end_date,
exchange,
'add')
cf = ContinuousFuture(sid,
root_symbol,
offset,
roll_style,
start_date,
end_date,
exchange)
exchange,
adjustment_children={
'mul': mul_cf,
'add': add_cf
})
self._asset_cache[cf.sid] = cf
self._asset_cache[add_cf.sid] = add_cf
self._asset_cache[mul_cf.sid] = mul_cf
return cf

def _make_sids(tblattr):
Expand Down
23 changes: 19 additions & 4 deletions zipline/assets/continuous_futures.pyx
Expand Up @@ -65,6 +65,9 @@ cdef class ContinuousFuture:

cdef readonly object exchange

cdef readonly object adjustment
cdef readonly object _adjustment_children

_kwargnames = frozenset({
'sid',
'root_symbol',
Expand All @@ -81,7 +84,9 @@ cdef class ContinuousFuture:
object roll_style,
object start_date,
object end_date,
object exchange):
object exchange,
object adjustment=None,
dict adjustment_children=None):

self.sid = sid
self.sid_hash = hash(sid)
Expand All @@ -91,6 +96,9 @@ cdef class ContinuousFuture:
self.exchange = exchange
self.start_date = start_date
self.end_date = end_date
self.adjustment = adjustment
self._adjustment_children = adjustment_children


def __int__(self):
return self.sid
Expand Down Expand Up @@ -138,16 +146,17 @@ cdef class ContinuousFuture:
raise AssertionError('%d is not an operator' % op)

def __str__(self):
return '%s(%d [%s, %s, %s])' % (
return '%s(%d [%s, %s, %s, %s])' % (
type(self).__name__,
self.sid,
self.root_symbol,
self.offset,
self.roll_style
self.roll_style,
self.adjustment,
)

def __repr__(self):
attrs = ('root_symbol', 'offset', 'roll_style')
attrs = ('root_symbol', 'offset', 'roll_style', 'adjustment')
tuples = ((attr, repr(getattr(self, attr, None)))
for attr in attrs)
strings = ('%s=%s' % (t[0], t[1]) for t in tuples)
Expand Down Expand Up @@ -226,6 +235,12 @@ cdef class ContinuousFuture:
calendar = get_calendar(self.exchange)
return calendar.is_open_on_minute(dt_minute)

def adj(self, style):
try:
return self._adjustment_children[style]
except KeyError:
return None


cdef class OrderedContracts(object):
"""
Expand Down
2 changes: 0 additions & 2 deletions zipline/data/continuous_future_reader.py
Expand Up @@ -229,8 +229,6 @@ def load_raw_arrays(self, columns, start_date, end_date, assets):
# Get partitions
partitions_by_asset = {}
for asset in assets:
rolls_by_asset[asset] = rf.get_rolls(
asset.root_symbol, start_date, end_date, asset.offset)
partitions = []
partitions_by_asset[asset] = partitions
rolls = rolls_by_asset[asset]
Expand Down
8 changes: 6 additions & 2 deletions zipline/data/data_portal.py
Expand Up @@ -229,12 +229,16 @@ def __init__(self,
self._history_loader = DailyHistoryLoader(
self.trading_calendar,
_dispatch_session_reader,
self._adjustment_reader
self._adjustment_reader,
self.asset_finder,
self._roll_finders,
)
self._minute_history_loader = MinuteHistoryLoader(
self.trading_calendar,
_dispatch_minute_reader,
self._adjustment_reader
self._adjustment_reader,
self.asset_finder,
self._roll_finders,
)

self._first_trading_day = first_trading_day
Expand Down

0 comments on commit 5d56922

Please sign in to comment.