mirror of
https://github.com/wassname/catalyst.git
synced 2026-07-12 15:37:45 +08:00
5624e0f391
When the following conditions occur, - a `nan` occurred after a half day (e.g. on the Monday after Thanksgiving, where the Friday would be a half day.) -data was written to the span between the early close and where the market close would have been if it were not an early close session - a `nan` also occured on the last minute of the early market session. the exisitng implementation would incorrectly return a `nan` when requesting a forward filled price. The steps that caused this error were. 1. Request for `'price'` on the market open of the day after the early close. 2. `nan` is found for that minute 3. `get_last_traded_dt` is called, and finds a volume that occurs after the early close. e.g. `18:47` when the market close was `18:00`. 4. The minute position for `18:47` is used, when calling `find_positon_of_minute`, since that value is after the `market_close` the minute is set to the position of `18:00`` due to the delta logic in 5. Since there is also no data in at `18:00`, a `nan` is returned, even though there were valid minutes earlier in the session. e.g. a non-zero volume at `16:47` should have been used, but was not. Fix by checking the current minute against the minute close when searching for the last traded minute. If the minute is greater than the market close for the corresponding day, continue the search until the minute position is within the trading session. This could also be fixed by enforcing that only zeros can be written between an early close and the minute where the close would have been, but this fix allows the reader to work with existing data.
160 lines
4.8 KiB
Cython
160 lines
4.8 KiB
Cython
from numpy cimport ndarray, long_t
|
|
from numpy import searchsorted
|
|
from cpython cimport bool
|
|
cimport cython
|
|
|
|
cdef inline int int_min(int a, int b): return a if a <= b else b
|
|
|
|
@cython.cdivision(True)
|
|
def minute_value(ndarray[long_t, ndim=1] market_opens,
|
|
Py_ssize_t pos,
|
|
short minutes_per_day):
|
|
"""
|
|
Finds the value of the minute represented by `pos` in the given array of
|
|
market opens.
|
|
|
|
Parameters
|
|
----------
|
|
market_opens: numpy array of ints
|
|
Market opens, in minute epoch values.
|
|
|
|
pos: int
|
|
The index of the desired minute.
|
|
|
|
minutes_per_day: int
|
|
The number of minutes per day (e.g. 390 for NYSE).
|
|
|
|
Returns
|
|
-------
|
|
int: The minute epoch value of the desired minute.
|
|
"""
|
|
cdef short q, r
|
|
|
|
q = cython.cdiv(pos, minutes_per_day)
|
|
r = cython.cmod(pos, minutes_per_day)
|
|
|
|
return market_opens[q] + r
|
|
|
|
def find_position_of_minute(ndarray[long_t, ndim=1] market_opens,
|
|
ndarray[long_t, ndim=1] market_closes,
|
|
long_t minute_val,
|
|
short minutes_per_day,
|
|
bool forward_fill):
|
|
"""
|
|
Finds the position of a given minute in the given array of market opens.
|
|
If not a market minute, adjusts to the last market minute.
|
|
|
|
Parameters
|
|
----------
|
|
market_opens: numpy array of ints
|
|
Market opens, in minute epoch values.
|
|
|
|
market_closes: numpy array of ints
|
|
Market closes, in minute epoch values.
|
|
|
|
minute_val: int
|
|
The desired minute, as a minute epoch.
|
|
|
|
minutes_per_day: int
|
|
The number of minutes per day (e.g. 390 for NYSE).
|
|
|
|
forward_fill: bool
|
|
Whether to use the previous market minute if the given minute does
|
|
not fall within an open/close pair.
|
|
|
|
Returns
|
|
-------
|
|
int: The position of the given minute in the market opens array.
|
|
|
|
Raises
|
|
------
|
|
ValueError
|
|
If the given minute is not between a single open/close pair AND
|
|
forward_fill is False. For example, if minute_val is 17:00 Eastern
|
|
for a given day whose normal hours are 9:30 to 16:00, and we are not
|
|
forward filling, ValueError is raised.
|
|
"""
|
|
cdef Py_ssize_t market_open_loc, market_open, delta
|
|
|
|
market_open_loc = \
|
|
searchsorted(market_opens, minute_val, side='right') - 1
|
|
market_open = market_opens[market_open_loc]
|
|
market_close = market_closes[market_open_loc]
|
|
|
|
if not forward_fill and ((minute_val - market_open) >= minutes_per_day):
|
|
raise ValueError("Given minute is not between an open and a close")
|
|
|
|
delta = int_min(minute_val - market_open, market_close - market_open)
|
|
|
|
return (market_open_loc * minutes_per_day) + delta
|
|
|
|
def find_last_traded_position_internal(
|
|
ndarray[long_t, ndim=1] market_opens,
|
|
ndarray[long_t, ndim=1] market_closes,
|
|
long_t end_minute,
|
|
long_t start_minute,
|
|
volumes,
|
|
short minutes_per_day):
|
|
|
|
"""
|
|
Finds the position of the last traded minute for the given volumes array.
|
|
|
|
Parameters
|
|
----------
|
|
market_opens: numpy array of ints
|
|
Market opens, in minute epoch values.
|
|
|
|
market_closes: numpy array of ints
|
|
Market closes, in minute epoch values.
|
|
|
|
end_minute: int
|
|
The minute from which to start looking backwards, as a minute epoch.
|
|
|
|
start_minute: int
|
|
The asset's start date, as a minute epoch. Acts as the bottom limit of
|
|
how far we can look backwards.
|
|
|
|
volumes: bcolz carray
|
|
The volume history for the given asset.
|
|
|
|
minutes_per_day: int
|
|
The number of minutes per day (e.g. 390 for NYSE).
|
|
|
|
Returns
|
|
-------
|
|
int: The position of the last traded minute, starting from `minute_val`
|
|
"""
|
|
cdef Py_ssize_t minute_pos, current_minute, q
|
|
|
|
minute_pos = int_min(
|
|
find_position_of_minute(market_opens, market_closes, end_minute,
|
|
minutes_per_day, True),
|
|
len(volumes) - 1
|
|
)
|
|
|
|
while minute_pos >= 0:
|
|
current_minute = minute_value(
|
|
market_opens, minute_pos, minutes_per_day
|
|
)
|
|
|
|
q = cython.cdiv(minute_pos, minutes_per_day)
|
|
if current_minute > market_closes[q]:
|
|
minute_pos = find_position_of_minute(market_opens,
|
|
market_closes,
|
|
market_closes[q],
|
|
minutes_per_day,
|
|
False)
|
|
continue
|
|
|
|
if current_minute < start_minute:
|
|
return -1
|
|
|
|
if volumes[minute_pos] != 0:
|
|
return minute_pos
|
|
|
|
minute_pos -= 1
|
|
|
|
# we've gone to the beginning of this asset's range, and still haven't
|
|
# found a trade event
|
|
return -1
|