Files
catalyst/zipline/lib/adjustment.pyx
T
2015-10-19 16:35:03 -04:00

262 lines
7.1 KiB
Cython

from cpython cimport Py_EQ
from pandas import isnull
from numpy cimport float64_t, uint8_t
# Purely for readability. There aren't C-level declarations for these types.
ctypedef object Int64Index_t
ctypedef object DatetimeIndex_t
ctypedef object Timestamp_t
cpdef tuple get_adjustment_locs(DatetimeIndex_t dates_index,
Int64Index_t assets_index,
Timestamp_t start_date,
Timestamp_t end_date,
int asset_id):
"""
Compute indices suitable for passing to an Adjustment constructor.
If the specified dates aren't in dates_index, we return the index of the
first date **BEFORE** the supplied date.
Example:
>>> from pandas import date_range, Int64Index, Timestamp
>>> dates = date_range('2014-01-01', '2014-01-07')
>>> assets = Int64Index(range(10))
>>> get_adjustment_locs(
... dates,
... assets,
... Timestamp('2014-01-03'),
... Timestamp('2014-01-05'),
... 3,
... )
(2, 4, 3)
"""
cdef int start_date_loc
# None or NaT signifies "All values before the end_date".
if isnull(start_date):
start_date_loc = 0
else:
# Location of earliest date on or after start_date.
start_date_loc = dates_index.get_loc(start_date, method='bfill')
return (
start_date_loc,
# Location of latest date on or before start_date.
dates_index.get_loc(end_date, method='ffill'),
assets_index.get_loc(asset_id), # Must be exact match.
)
cpdef _from_assets_and_dates(cls,
DatetimeIndex_t dates_index,
Int64Index_t assets_index,
Timestamp_t start_date,
Timestamp_t end_date,
int asset_id,
object value):
"""
Helper for constructing an Adjustment instance from coordinates in
assets/dates indices.
Example
-------
>>> from pandas import date_range, Int64Index, Timestamp
>>> dates = date_range('2014-01-01', '2014-01-07')
>>> assets = Int64Index(range(10))
>>> Float64Multiply.from_assets_and_dates(
... dates,
... assets,
... Timestamp('2014-01-03'),
... Timestamp('2014-01-05'),
... 3,
... 0.5,
... )
Float64Multiply(first_row=2, last_row=4, first_col=3, last_col=3, value=0.500000)
"""
cdef:
Py_ssize_t first_row, last_row, col
first_row, last_row, col = get_adjustment_locs(
dates_index,
assets_index,
start_date,
end_date,
asset_id,
)
return cls(first_row, last_row, col, col, value)
cdef class Float64Adjustment:
"""
Base class for adjustments that operate on Float64 buffers.
"""
cdef:
readonly Py_ssize_t first_col, last_col, first_row, last_row
readonly float64_t value
def __cinit__(self,
Py_ssize_t first_row,
Py_ssize_t last_row,
Py_ssize_t first_col,
Py_ssize_t last_col,
object value):
assert 0 <= first_row <= last_row
assert 0 <= first_col <= last_col
self.first_row = first_row
self.last_row = last_row
self.first_col = first_col
self.last_col = last_col
self.value = float(value)
from_assets_and_dates = classmethod(_from_assets_and_dates)
def __repr__(self):
return (
"%s(first_row=%d, last_row=%d,"
" first_col=%d, last_col=%d, value=%f)" % (
type(self).__name__,
self.first_row,
self.last_row,
self.first_col,
self.last_col,
self.value,
)
)
def __richcmp__(self, object other, int op):
"""
Rich comparison method. Only Equality is defined.
"""
if op != Py_EQ or type(self) != type(other):
return NotImplemented
return self._key() == other._key()
cpdef _key(self):
return (
self.first_row,
self.last_row,
self.first_col,
self.last_col,
self.value,
)
cdef class Float64Multiply(Float64Adjustment):
"""
An adjustment that multiplies by a scalar.
Example
-------
>>> import numpy as np
>>> arr = np.arange(9, dtype=float).reshape(3, 3)
>>> arr
array([[ 0., 1., 2.],
[ 3., 4., 5.],
[ 6., 7., 8.]])
>>> adj = Float64Multiply(
... first_row=1,
... last_row=2,
... first_col=1,
... last_col=2,
... value=4.0,
... )
>>> adj.mutate(arr)
>>> arr
array([[ 0., 1., 2.],
[ 3., 16., 20.],
[ 6., 28., 32.]])
"""
cpdef mutate(self, float64_t[:, :] data):
cdef Py_ssize_t row, col
# last_col + 1 because last_col should also be affected.
for col in range(self.first_col, self.last_col + 1):
# last_row + 1 because last_row should also be affected.
for row in range(self.first_row, self.last_row + 1):
data[row, col] *= self.value
cdef class Float64Overwrite(Float64Adjustment):
"""
An adjustment that overwrites with a scalar.
Example
-------
>>> import numpy as np
>>> arr = np.arange(9, dtype=float).reshape(3, 3)
>>> arr
array([[ 0., 1., 2.],
[ 3., 4., 5.],
[ 6., 7., 8.]])
>>> adj = Float64Overwrite(
... first_row=1,
... last_row=2,
... first_col=1,
... last_col=2,
... value=0.0,
... )
>>> adj.mutate(arr)
>>> arr
array([[ 0., 1., 2.],
[ 3., 0., 0.],
[ 6., 0., 0.]])
"""
cpdef mutate(self, float64_t[:, :] data):
cdef Py_ssize_t row, col
# last_col + 1 because last_col should also be affected.
for col in range(self.first_col, self.last_col + 1):
# last_row + 1 because last_row should also be affected.
for row in range(self.first_row, self.last_row + 1):
data[row, col] = self.value
cdef class Float64Add(Float64Adjustment):
"""
An adjustment that adds a scalar.
Example
-------
>>> import numpy as np
>>> arr = np.arange(9, dtype=float).reshape(3, 3)
>>> arr
array([[ 0., 1., 2.],
[ 3., 4., 5.],
[ 6., 7., 8.]])
>>> adj = Float64Add(
... first_row=1,
... last_row=2,
... first_col=1,
... last_col=2,
... value=1.0,
... )
>>> adj.mutate(arr)
>>> arr
array([[ 0., 1., 2.],
[ 3., 5., 6.],
[ 6., 8., 9.]])
"""
cpdef mutate(self, float64_t[:, :] data):
cdef Py_ssize_t row, col
# last_col + 1 because last_col should also be affected.
for col in range(self.first_col, self.last_col + 1):
# last_row + 1 because last_row should also be affected.
for row in range(self.first_row, self.last_row + 1):
data[row, col] += self.value