mirror of
https://github.com/wassname/catalyst.git
synced 2026-08-13 12:00:16 +08:00
WIP: Integration of generic bundle and five minute bars
This commit is contained in:
@@ -35,6 +35,17 @@ def minute_value(ndarray[long_t, ndim=1] market_opens,
|
||||
|
||||
return market_opens[q] + r
|
||||
|
||||
@cython.cdivision(True)
|
||||
def five_minute_value(ndarray[long_t, ndim=1] market_opens,
|
||||
Py_ssize_t pos,
|
||||
short five_minutes_per_day):
|
||||
|
||||
cdef short q, r
|
||||
q = cython.cdiv(pos, five_minutes_per_day)
|
||||
r = cython.cmod(pos, five_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,
|
||||
@@ -88,6 +99,26 @@ def find_position_of_minute(ndarray[long_t, ndim=1] market_opens,
|
||||
|
||||
return (market_open_loc * minutes_per_day) + delta
|
||||
|
||||
def find_position_of_five_minute(ndarray[long_t, ndim=1] market_opens,
|
||||
ndarray[long_t, ndim=1] market_closes,
|
||||
long_t five_minute_val,
|
||||
short five_minutes_per_day,
|
||||
bool forward_fill):
|
||||
|
||||
cdef Py_ssize_t market_open_loc, market_open, delta
|
||||
|
||||
market_open_loc = \
|
||||
searchsorted(market_opens, five_minute_val, side='right') - 1
|
||||
market_open = market_opens[market_open_loc]
|
||||
market_close = market_closes[market_open_loc]
|
||||
|
||||
if not forward_fill and ((five_minute_val - market_open) >= five_minutes_per_day):
|
||||
raise ValueError("Given five minutes is not between an open and a close")
|
||||
|
||||
delta = int_min(five_minute_val - market_open, market_close - market_open)
|
||||
|
||||
return (market_open_loc * five_minutes_per_day) + delta
|
||||
|
||||
def find_last_traded_position_internal(
|
||||
ndarray[long_t, ndim=1] market_opens,
|
||||
ndarray[long_t, ndim=1] market_closes,
|
||||
@@ -157,3 +188,51 @@ def find_last_traded_position_internal(
|
||||
# we've gone to the beginning of this asset's range, and still haven't
|
||||
# found a trade event
|
||||
return -1
|
||||
|
||||
def find_last_traded_five_minute_position_internal(
|
||||
ndarray[long_t, ndim=1] market_opens,
|
||||
ndarray[long_t, ndim=1] market_closes,
|
||||
long_t end_five_minute,
|
||||
long_t start_five_minute,
|
||||
volumes,
|
||||
short five_minutes_per_day):
|
||||
cdef Py_ssize_t minute_pos, current_minute, q
|
||||
|
||||
five_minute_pos = int_min(
|
||||
find_position_of_five_minute(
|
||||
market_opens,
|
||||
market_closes,
|
||||
end_five_minute,
|
||||
five_minutes_per_day,
|
||||
True,
|
||||
),
|
||||
len(volumes) - 1,
|
||||
)
|
||||
|
||||
while five_minute_pos >= 0:
|
||||
current_five_minute = five_minute_value(
|
||||
market_opens, five_minute_pos, five_minutes_per_day
|
||||
)
|
||||
|
||||
q = cython.cdiv(five_minute_pos, five_minutes_per_day)
|
||||
if current_five_minute > market_closes[q]:
|
||||
five_minute_pos = find_position_of_five_minute(
|
||||
market_opens,
|
||||
market_closes,
|
||||
market_closes[q],
|
||||
five_minutes_per_day,
|
||||
False,
|
||||
)
|
||||
continue
|
||||
|
||||
if current_five_minute < start_five_minute:
|
||||
return -1
|
||||
|
||||
if volumes[five_minute_pos] != 0:
|
||||
return five_minute_pos
|
||||
|
||||
five_minute_pos -= 1
|
||||
|
||||
# we've gone to the beginning of this asset's range, and still haven't
|
||||
# found a trade event
|
||||
return -1
|
||||
|
||||
+148
-127
@@ -33,7 +33,17 @@ from catalyst.utils.memoize import lazyval
|
||||
logbook.StderrHandler().push_application()
|
||||
log = logbook.Logger(__name__)
|
||||
|
||||
DEFAULT_RETRIES = 5
|
||||
|
||||
class BaseBundle(object):
|
||||
def __init__(self, asset_filter=[]):
|
||||
self._asset_filter = asset_filter
|
||||
self._reset()
|
||||
|
||||
def _reset(self):
|
||||
self._splits = []
|
||||
self._dividends = []
|
||||
|
||||
@lazyval
|
||||
def name(self):
|
||||
raise NotImplementedError()
|
||||
@@ -50,6 +60,10 @@ class BaseBundle(object):
|
||||
def minutes_per_day(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@lazyval
|
||||
def five_minutes_per_day(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
@lazyval
|
||||
def frequencies(self):
|
||||
raise NotImplementedError()
|
||||
@@ -101,6 +115,7 @@ class BaseBundle(object):
|
||||
environ,
|
||||
asset_db_writer,
|
||||
minute_bar_writer,
|
||||
five_minute_bar_writer,
|
||||
daily_bar_writer,
|
||||
adjustment_writer,
|
||||
calendar,
|
||||
@@ -108,110 +123,117 @@ class BaseBundle(object):
|
||||
end_session,
|
||||
cache,
|
||||
show_progress,
|
||||
is_compile,
|
||||
output_dir):
|
||||
|
||||
api_key = environ.get('CATALYST_API_KEY')
|
||||
retries = environ.get('CATALYST_DOWNLOAD_ATTEMPTS', 5)
|
||||
use_local = environ.get('CATALYST_INGEST_LOCAL', 0) > 0
|
||||
try:
|
||||
api_key = environ.get('CATALYST_API_KEY')
|
||||
retries = environ.get('CATALYST_DOWNLOAD_ATTEMPTS', 5)
|
||||
|
||||
if use_local:
|
||||
# User has instructed local curation and ingestion of bundle.
|
||||
# Fetch raw metadata for all symbols.
|
||||
raw_metadata = self._fetch_metadata_frame(
|
||||
api_key,
|
||||
cache=cache,
|
||||
retries=retries,
|
||||
environ=environ,
|
||||
show_progress=show_progress,
|
||||
)
|
||||
|
||||
# Compile daily symbol data if bundle supports daily mode and
|
||||
# persist the dataset to disk.
|
||||
symbol_map = raw_metadata.symbol
|
||||
if 'daily' in self.frequencies:
|
||||
daily_bar_writer.write(
|
||||
self._fetch_symbol_iter(
|
||||
api_key,
|
||||
cache,
|
||||
symbol_map,
|
||||
calendar,
|
||||
start_session,
|
||||
end_session,
|
||||
'daily',
|
||||
retries,
|
||||
),
|
||||
assets=raw_metadata.index,
|
||||
if is_compile:
|
||||
# User has instructed local compilation and ingestion of bundle.
|
||||
# Fetch raw metadata for all symbols.
|
||||
raw_metadata = self._fetch_metadata_frame(
|
||||
api_key,
|
||||
cache=cache,
|
||||
retries=retries,
|
||||
environ=environ,
|
||||
show_progress=show_progress,
|
||||
)
|
||||
|
||||
# Post-process metadata using cached symbol frames, and write to
|
||||
# disk. This metadata must be written before any attempt to write
|
||||
# either minute or 5-minute data.
|
||||
metadata = self._post_process_metadata(
|
||||
raw_metadata,
|
||||
cache,
|
||||
show_progress=show_progress,
|
||||
)
|
||||
asset_db_writer.write(metadata)
|
||||
# Compile daily symbol data if bundle supports daily mode and
|
||||
# persist the dataset to disk.
|
||||
symbol_map = raw_metadata.symbol
|
||||
if 'daily' in self.frequencies:
|
||||
daily_bar_writer.write(
|
||||
self._fetch_symbol_iter(
|
||||
api_key,
|
||||
cache,
|
||||
symbol_map,
|
||||
calendar,
|
||||
start_session,
|
||||
end_session,
|
||||
'daily',
|
||||
retries,
|
||||
),
|
||||
assets=raw_metadata.index,
|
||||
show_progress=show_progress,
|
||||
)
|
||||
|
||||
# Compile 5-minute symbol data if bundle supports 5-minute mode and
|
||||
# persist the dataset to disk.
|
||||
if '5-minute' in self.frequencies:
|
||||
#TODO(cfromknecht) replace with five_minute_bar_writer
|
||||
minute_bar_writer.write(
|
||||
self._fetch_symbol_iter(
|
||||
api_key,
|
||||
cache,
|
||||
symbol_map,
|
||||
calendar,
|
||||
start_session,
|
||||
end_session,
|
||||
'5-minute',
|
||||
retries,
|
||||
),
|
||||
# Post-process metadata using cached symbol frames, and write to
|
||||
# disk. This metadata must be written before any attempt to write
|
||||
# either minute or 5-minute data.
|
||||
metadata = self._post_process_metadata(
|
||||
raw_metadata,
|
||||
cache,
|
||||
show_progress=show_progress,
|
||||
)
|
||||
asset_db_writer.write(metadata)
|
||||
|
||||
# Compile minute symbol data if bundle supports minute mode and
|
||||
# persist the dataset to disk.
|
||||
if 'minute' in self.frequencies:
|
||||
minute_bar_writer.write(
|
||||
self._fetch_symbol_iter(
|
||||
api_key,
|
||||
cache,
|
||||
symbol_map,
|
||||
calendar,
|
||||
start_session,
|
||||
end_session,
|
||||
'minute',
|
||||
retries,
|
||||
),
|
||||
show_progress=show_progress,
|
||||
)
|
||||
# Compile 5-minute symbol data if bundle supports 5-minute mode and
|
||||
# persist the dataset to disk.
|
||||
if '5-minute' in self.frequencies:
|
||||
five_minute_bar_writer.write(
|
||||
self._fetch_symbol_iter(
|
||||
api_key,
|
||||
cache,
|
||||
symbol_map,
|
||||
calendar,
|
||||
start_session,
|
||||
end_session,
|
||||
'5-minute',
|
||||
retries,
|
||||
),
|
||||
length=len(symbol_map),
|
||||
show_progress=show_progress,
|
||||
)
|
||||
|
||||
# For legacy purposes, this call is required to ensure the database
|
||||
# contains an appropriately initialized file structure. We don't
|
||||
# forsee a usecase for adjustments at this time, but may later
|
||||
# choose to expose this functionality in the future.
|
||||
if len(self.splits) > 0 or len(self.dividends) > 0:
|
||||
# Compile minute symbol data if bundle supports minute mode and
|
||||
# persist the dataset to disk.
|
||||
if 'minute' in self.frequencies:
|
||||
minute_bar_writer.write(
|
||||
self._fetch_symbol_iter(
|
||||
api_key,
|
||||
cache,
|
||||
symbol_map,
|
||||
calendar,
|
||||
start_session,
|
||||
end_session,
|
||||
'minute',
|
||||
retries,
|
||||
),
|
||||
show_progress=show_progress,
|
||||
)
|
||||
|
||||
# For legacy purposes, this call is required to ensure the database
|
||||
# contains an appropriately initialized file structure. We don't
|
||||
# forsee a usecase for adjustments at this time, but may later
|
||||
# choose to expose this functionality in the future.
|
||||
adjustment_writer.write(
|
||||
splits=(
|
||||
pd.concat(self.splits, ignore_index=True)
|
||||
#if self.splits is not None \
|
||||
#and len(self.splits) > 0 else
|
||||
#None
|
||||
if len(self.splits) > 0 else
|
||||
None
|
||||
),
|
||||
dividends=(
|
||||
pd.concat(self.dividends, ignore_index=True)
|
||||
#if self.dividends is not None \
|
||||
#and len(dividends) > 0 else
|
||||
#None
|
||||
if len(self.dividends) > 0 else
|
||||
None
|
||||
),
|
||||
)
|
||||
else:
|
||||
# Otherwise, user has instructed to download and untar bundle
|
||||
# directly from the bundles `tar_url`.
|
||||
self._download_and_untar(show_progress, output_dir)
|
||||
except Exception as e:
|
||||
log.exception(
|
||||
' Failed to ingest {name}:\n{msg}'.format(
|
||||
name=self.name,
|
||||
msg=str(e),
|
||||
)
|
||||
)
|
||||
else:
|
||||
# Otherwise, user has instructed to download and untar bundle
|
||||
# directly from the bundles `tar_url`.
|
||||
self._download_and_untar(show_progress, output_dir)
|
||||
self._reset()
|
||||
|
||||
def _download_and_untar(self, show_progress, output_dir):
|
||||
# Download bundle conditioned on whether the user would like progress
|
||||
@@ -233,7 +255,7 @@ class BaseBundle(object):
|
||||
def _fetch_metadata_frame(self,
|
||||
api_key,
|
||||
cache,
|
||||
retries=5,
|
||||
retries=DEFAULT_RETRIES,
|
||||
environ=None,
|
||||
show_progress=False):
|
||||
|
||||
@@ -257,7 +279,7 @@ class BaseBundle(object):
|
||||
for page_number in count(1):
|
||||
# Attempt to load metadata page from cache. If it does not exist,
|
||||
# poll the API upto `retries` times in order to get raw DataFrame.
|
||||
key = 'metadata-page-{pn}'.format(pn=page_number)
|
||||
key = 'metadata-page-{pn}.frame'.format(pn=page_number)
|
||||
try:
|
||||
raw = cache[key]
|
||||
except KeyError:
|
||||
@@ -289,6 +311,10 @@ class BaseBundle(object):
|
||||
# Empty DataFrame signals completion.
|
||||
break
|
||||
|
||||
# Apply selective asset filtering, useful for benchmark
|
||||
# ingestion.
|
||||
raw = raw[raw.symbol.isin(self._asset_filter)]
|
||||
|
||||
# Update cached value for key.
|
||||
cache[key] = raw
|
||||
|
||||
@@ -317,8 +343,9 @@ class BaseBundle(object):
|
||||
# Attempt to load data from disk, the cache should have an entry
|
||||
# for each symbol at this point of the execution. If one does
|
||||
# not exist, we should fail.
|
||||
key = '{sym}.daily.frame'.format(sym=symbol)
|
||||
try:
|
||||
raw_data = cache[symbol]
|
||||
raw_data = cache[key]
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
'Unable to find cached data for symbol: {0}'.format(symbol)
|
||||
@@ -398,8 +425,9 @@ class BaseBundle(object):
|
||||
retries):
|
||||
|
||||
# Attempt to load pre-existing symbol data from cache.
|
||||
key = '{sym}.{freq}.frame'.format(sym=symbol, freq=data_frequency)
|
||||
try:
|
||||
raw_data = cache[symbol]
|
||||
raw_data = cache[key]
|
||||
except KeyError:
|
||||
raw_data = None
|
||||
|
||||
@@ -418,6 +446,36 @@ class BaseBundle(object):
|
||||
# proceed to next symbol directly since no API call was required.
|
||||
return raw_data, should_sleep
|
||||
|
||||
# If we arrive here, we must have attempted an API call.
|
||||
# Setting this flag tells the iterator to pause before starting
|
||||
# the next asset, that we don't exceed the data source's rate
|
||||
# limit.
|
||||
should_sleep = True
|
||||
|
||||
raw_data = self._fetch_symbol_frame(
|
||||
api_key,
|
||||
symbol,
|
||||
calendar,
|
||||
start_session,
|
||||
end_session,
|
||||
data_frequency,
|
||||
retries=retries,
|
||||
)
|
||||
|
||||
# Cache latest symbol data.
|
||||
cache[key] = raw_data
|
||||
|
||||
return raw_data, should_sleep
|
||||
|
||||
def _fetch_symbol_frame(self,
|
||||
api_key,
|
||||
symbol,
|
||||
calendar,
|
||||
start_session,
|
||||
end_session,
|
||||
data_frequency,
|
||||
retries=DEFAULT_RETRIES):
|
||||
|
||||
# Data for symbol is old enough to attempt an update or is not
|
||||
# present in the cache. Fetch raw data for a single symbol
|
||||
# with requested intervals and frequency. Retry as necessary.
|
||||
@@ -432,6 +490,7 @@ class BaseBundle(object):
|
||||
data_frequency,
|
||||
)
|
||||
raw_data.index = pd.to_datetime(raw_data.index, utc=True)
|
||||
raw_data.index = raw_data.index.tz_localize('UTC')
|
||||
|
||||
# Filter incoming data to fit start and end sessions.
|
||||
raw_data = raw_data[
|
||||
@@ -443,15 +502,7 @@ class BaseBundle(object):
|
||||
# previous frame is probably an incomplete.
|
||||
raw_data = raw_data[~raw_data.index.duplicated(keep='last')]
|
||||
|
||||
# Cache latest symbol data.
|
||||
cache[symbol] = raw_data
|
||||
|
||||
# If we arrive here, we must have attempted an API call.
|
||||
# This flag tells the iterator to pause before starting the next
|
||||
# asset, that we don't exceed the data source's rate limit.
|
||||
should_sleep = True
|
||||
|
||||
return raw_data, should_sleep
|
||||
return raw_data
|
||||
|
||||
except Exception as e:
|
||||
log.exception(
|
||||
@@ -468,35 +519,5 @@ class BaseBundle(object):
|
||||
)
|
||||
|
||||
|
||||
def _write_symbol_for_freq(self,
|
||||
pricing_iter,
|
||||
data_frequency,
|
||||
daily_bar_writer,
|
||||
minute_bar_writer,
|
||||
assets,
|
||||
show_progress=False):
|
||||
|
||||
if data_frequency == 'daily':
|
||||
daily_bar_writer.write(
|
||||
pricing_iter,
|
||||
assets=assets,
|
||||
show_progress=show_progress,
|
||||
)
|
||||
elif data_frequency == '5-minute':
|
||||
# TODO(cfromknecht) replace with five minute writer
|
||||
minute_bar_writer.write(
|
||||
pricing_iter,
|
||||
show_progress=show_progress,
|
||||
)
|
||||
elif data_frequency == 'minute':
|
||||
minute_bar_writer.write(
|
||||
pricing_iter,
|
||||
show_progress=show_progress,
|
||||
)
|
||||
else:
|
||||
raise ValueError(
|
||||
'Unsupported data-frequency: {0}'.format(data_frequency)
|
||||
)
|
||||
|
||||
def _dtypes_to_cols(dtypes):
|
||||
return [name for name, _ in dtypes]
|
||||
|
||||
@@ -38,9 +38,6 @@ class BasePricingBundle(BaseBundle):
|
||||
]
|
||||
|
||||
class BaseCryptoPricingBundle(BasePricingBundle):
|
||||
def __init__(self):
|
||||
super(BasePricingBundle, self).__init__()
|
||||
|
||||
@lazyval
|
||||
def calendar_name(self):
|
||||
return 'OPEN'
|
||||
@@ -49,6 +46,10 @@ class BaseCryptoPricingBundle(BasePricingBundle):
|
||||
def minutes_per_day(self):
|
||||
return 1440
|
||||
|
||||
@lazyval
|
||||
def five_minutes_per_day(self):
|
||||
return 288
|
||||
|
||||
@property
|
||||
def splits(self):
|
||||
return []
|
||||
@@ -58,11 +59,6 @@ class BaseCryptoPricingBundle(BasePricingBundle):
|
||||
return []
|
||||
|
||||
class BaseEquityPricingBundle(BasePricingBundle):
|
||||
def __init__(self):
|
||||
super(BasePricingBundle, self).__init__()
|
||||
self._splits = []
|
||||
self._dividends = []
|
||||
|
||||
@lazyval
|
||||
def calendar_name(self):
|
||||
return 'NYSE'
|
||||
@@ -71,6 +67,9 @@ class BaseEquityPricingBundle(BasePricingBundle):
|
||||
def minutes_per_day(self):
|
||||
return 390
|
||||
|
||||
@lazyval
|
||||
def five_minutes_per_day(self):
|
||||
return 78
|
||||
|
||||
@property
|
||||
def splits(self):
|
||||
|
||||
@@ -17,6 +17,10 @@ from ..us_equity_pricing import (
|
||||
SQLiteAdjustmentReader,
|
||||
SQLiteAdjustmentWriter,
|
||||
)
|
||||
from ..five_minute_bars import (
|
||||
BcolzFiveMinuteBarReader,
|
||||
BcolzFiveMinuteBarWriter,
|
||||
)
|
||||
from ..minute_bars import (
|
||||
BcolzMinuteBarReader,
|
||||
BcolzMinuteBarWriter,
|
||||
@@ -44,16 +48,21 @@ def asset_db_path(bundle_name, timestr, environ=None, db_version=None):
|
||||
)
|
||||
|
||||
|
||||
def minute_equity_path(bundle_name, timestr, environ=None):
|
||||
def minute_path(bundle_name, timestr, environ=None):
|
||||
return pth.data_path(
|
||||
minute_equity_relative(bundle_name, timestr, environ),
|
||||
minute_relative(bundle_name, timestr, environ),
|
||||
environ=environ,
|
||||
)
|
||||
|
||||
|
||||
def daily_equity_path(bundle_name, timestr, environ=None):
|
||||
def five_minute_path(bundle_name, timestr, environ=None):
|
||||
return pth.data_path(
|
||||
daily_equity_relative(bundle_name, timestr, environ),
|
||||
five_minute_relative(bundle_name, timestr, environ),
|
||||
environ=environ,
|
||||
)
|
||||
|
||||
def daily_path(bundle_name, timestr, environ=None):
|
||||
return pth.data_path(
|
||||
daily_relative(bundle_name, timestr, environ),
|
||||
environ=environ,
|
||||
)
|
||||
|
||||
@@ -80,12 +89,14 @@ def cache_relative(bundle_name, timestr, environ=None):
|
||||
return bundle_name, '.cache'
|
||||
|
||||
|
||||
def daily_equity_relative(bundle_name, timestr, environ=None):
|
||||
return bundle_name, timestr, 'daily_equities.bcolz'
|
||||
def daily_relative(bundle_name, timestr, environ=None):
|
||||
return bundle_name, timestr, 'daily.bcolz'
|
||||
|
||||
def five_minute_relative(bundle_name, timestr, environ=None):
|
||||
return bundle_name, timestr, 'five_minute.bcolz'
|
||||
|
||||
def minute_equity_relative(bundle_name, timestr, environ=None):
|
||||
return bundle_name, timestr, 'minute_equities.bcolz'
|
||||
def minute_relative(bundle_name, timestr, environ=None):
|
||||
return bundle_name, timestr, 'minute.bcolz'
|
||||
|
||||
|
||||
def asset_db_relative(bundle_name, timestr, environ=None, db_version=None):
|
||||
@@ -195,13 +206,14 @@ RegisteredBundle = namedtuple(
|
||||
'start_session',
|
||||
'end_session',
|
||||
'minutes_per_day',
|
||||
'five_minutes_per_day',
|
||||
'ingest',
|
||||
'create_writers']
|
||||
)
|
||||
|
||||
BundleData = namedtuple(
|
||||
'BundleData',
|
||||
'asset_finder equity_minute_bar_reader equity_daily_bar_reader '
|
||||
'asset_finder minute_bar_reader five_minute_bar_reader daily_bar_reader '
|
||||
'adjustment_reader',
|
||||
)
|
||||
|
||||
@@ -281,15 +293,17 @@ def _make_bundle_core():
|
||||
bundles = mappingproxy(_bundles)
|
||||
|
||||
def register_bundle(bundle_cls,
|
||||
asset_filter=None,
|
||||
start_session=None,
|
||||
end_session=None,
|
||||
create_writers=True):
|
||||
bundle = bundle_cls()
|
||||
bundle = bundle_cls(asset_filter=asset_filter)
|
||||
return register(
|
||||
bundle.name,
|
||||
bundle.ingest,
|
||||
calendar_name=bundle.calendar_name,
|
||||
minutes_per_day=bundle.minutes_per_day,
|
||||
five_minutes_per_day=bundle.five_minutes_per_day,
|
||||
start_session=start_session,
|
||||
end_session=end_session,
|
||||
create_writers=create_writers,
|
||||
@@ -298,10 +312,11 @@ def _make_bundle_core():
|
||||
@curry
|
||||
def register(name,
|
||||
f,
|
||||
calendar_name='NYSE',
|
||||
calendar_name='OPEN',
|
||||
start_session=None,
|
||||
end_session=None,
|
||||
minutes_per_day=390,
|
||||
minutes_per_day=1440,
|
||||
five_minutes_per_day=288,
|
||||
create_writers=True):
|
||||
"""Register a data bundle ingest function.
|
||||
|
||||
@@ -382,6 +397,7 @@ def _make_bundle_core():
|
||||
start_session=start_session,
|
||||
end_session=end_session,
|
||||
minutes_per_day=minutes_per_day,
|
||||
five_minutes_per_day=five_minutes_per_day,
|
||||
ingest=f,
|
||||
create_writers=create_writers,
|
||||
)
|
||||
@@ -413,7 +429,8 @@ def _make_bundle_core():
|
||||
environ=os.environ,
|
||||
timestamp=None,
|
||||
assets_versions=(),
|
||||
show_progress=False):
|
||||
show_progress=False,
|
||||
is_compile=False):
|
||||
"""Ingest data for a given bundle.
|
||||
|
||||
Parameters
|
||||
@@ -463,7 +480,7 @@ def _make_bundle_core():
|
||||
pth.data_path([], environ=environ))
|
||||
)
|
||||
daily_bars_path = wd.ensure_dir(
|
||||
*daily_equity_relative(
|
||||
*daily_relative(
|
||||
name, timestr, environ=environ,
|
||||
)
|
||||
)
|
||||
@@ -477,10 +494,20 @@ def _make_bundle_core():
|
||||
# when we create the SQLiteAdjustmentWriter below. The
|
||||
# SQLiteAdjustmentWriter needs to open the daily ctables so
|
||||
# that it can compute the adjustment ratios for the dividends.
|
||||
|
||||
daily_bar_writer.write(())
|
||||
|
||||
five_minute_bar_writer = BcolzFiveMinuteBarWriter(
|
||||
wd.ensure_dir(*five_minute_relative(
|
||||
name, timestr, environ=environ)
|
||||
),
|
||||
calendar,
|
||||
start_session,
|
||||
end_session,
|
||||
five_minutes_per_day=bundle.five_minutes_per_day,
|
||||
)
|
||||
|
||||
minute_bar_writer = BcolzMinuteBarWriter(
|
||||
wd.ensure_dir(*minute_equity_relative(
|
||||
wd.ensure_dir(*minute_relative(
|
||||
name, timestr, environ=environ)
|
||||
),
|
||||
calendar,
|
||||
@@ -488,6 +515,7 @@ def _make_bundle_core():
|
||||
end_session,
|
||||
minutes_per_day=bundle.minutes_per_day,
|
||||
)
|
||||
|
||||
assets_db_path = wd.getpath(*asset_db_relative(
|
||||
name, timestr, environ=environ,
|
||||
))
|
||||
@@ -504,6 +532,7 @@ def _make_bundle_core():
|
||||
)
|
||||
else:
|
||||
daily_bar_writer = None
|
||||
five_minute_bar_writer = None
|
||||
minute_bar_writer = None
|
||||
asset_db_writer = None
|
||||
adjustment_db_writer = None
|
||||
@@ -515,6 +544,7 @@ def _make_bundle_core():
|
||||
environ,
|
||||
asset_db_writer,
|
||||
minute_bar_writer,
|
||||
five_minute_bar_writer,
|
||||
daily_bar_writer,
|
||||
adjustment_db_writer,
|
||||
calendar,
|
||||
@@ -522,6 +552,7 @@ def _make_bundle_core():
|
||||
end_session,
|
||||
cache,
|
||||
show_progress,
|
||||
is_compile,
|
||||
pth.data_path([name, timestr], environ=environ),
|
||||
)
|
||||
|
||||
@@ -597,11 +628,14 @@ def _make_bundle_core():
|
||||
asset_finder=AssetFinder(
|
||||
asset_db_path(name, timestr, environ=environ),
|
||||
),
|
||||
equity_minute_bar_reader=BcolzMinuteBarReader(
|
||||
minute_equity_path(name, timestr, environ=environ),
|
||||
minute_bar_reader=BcolzMinuteBarReader(
|
||||
minute_path(name, timestr, environ=environ),
|
||||
),
|
||||
equity_daily_bar_reader=BcolzDailyBarReader(
|
||||
daily_equity_path(name, timestr, environ=environ),
|
||||
five_minute_bar_reader=BcolzFiveMinuteBarReader(
|
||||
five_minute_path(name, timestr, environ=environ),
|
||||
),
|
||||
daily_bar_reader=BcolzDailyBarReader(
|
||||
daily_path(name, timestr, environ=environ),
|
||||
),
|
||||
adjustment_reader=SQLiteAdjustmentReader(
|
||||
adjustment_db_path(name, timestr, environ=environ),
|
||||
|
||||
@@ -36,6 +36,7 @@ class PoloniexBundle(BaseCryptoPricingBundle):
|
||||
def frequencies(self):
|
||||
return set((
|
||||
'daily',
|
||||
'5-minute',
|
||||
))
|
||||
|
||||
@lazyval
|
||||
@@ -72,8 +73,8 @@ class PoloniexBundle(BaseCryptoPricingBundle):
|
||||
return raw
|
||||
|
||||
def post_process_symbol_metadata(self, asset_id, sym_md, sym_data):
|
||||
start_date = sym_data.index[0].tz_localize(None)
|
||||
end_date = sym_data.index[-1].tz_localize(None)
|
||||
start_date = sym_data.index[0]
|
||||
end_date = sym_data.index[-1]
|
||||
ac_date = end_date + pd.Timedelta(days=1)
|
||||
|
||||
return (
|
||||
@@ -100,7 +101,6 @@ class PoloniexBundle(BaseCryptoPricingBundle):
|
||||
),
|
||||
orient='records',
|
||||
)
|
||||
|
||||
raw.set_index('date', inplace=True)
|
||||
|
||||
scale = 1000.0
|
||||
@@ -155,4 +155,4 @@ class PoloniexBundle(BaseCryptoPricingBundle):
|
||||
query=urlencode(query_params),
|
||||
)
|
||||
|
||||
register_bundle(PoloniexBundle)
|
||||
register_bundle(PoloniexBundle, ['USDT_BTC'])
|
||||
|
||||
@@ -42,6 +42,7 @@ from catalyst.assets.roll_finder import (
|
||||
)
|
||||
from catalyst.data.dispatch_bar_reader import (
|
||||
AssetDispatchMinuteBarReader,
|
||||
AssetDispatchFiveMinuteBarReader,
|
||||
AssetDispatchSessionBarReader
|
||||
)
|
||||
from catalyst.data.resample import (
|
||||
@@ -114,12 +115,16 @@ class DataPortal(object):
|
||||
The calendar instance used to provide minute->session information.
|
||||
first_trading_day : pd.Timestamp
|
||||
The first trading day for the simulation.
|
||||
equity_daily_reader : BcolzDailyBarReader, optional
|
||||
daily_reader : BcolzDailyBarReader, optional
|
||||
The daily bar reader for equities. This will be used to service
|
||||
daily data backtests or daily history calls in a minute backetest.
|
||||
If a daily bar reader is not provided but a minute bar reader is,
|
||||
the minutes will be rolled up to serve the daily requests.
|
||||
equity_minute_reader : BcolzMinuteBarReader, optional
|
||||
five_minute_reader : BcolzFiveMinuteBarReader, optional
|
||||
The five minute bar reader for equities. This will be used to service
|
||||
5-minute data backtests or five-minute history calls. This can be used
|
||||
to serve daily calls if no daily bar reader is provided.
|
||||
minute_reader : BcolzMinuteBarReader, optional
|
||||
The minute bar reader for equities. This will be used to service
|
||||
minute data backtests or minute history calls. This can be used
|
||||
to serve daily calls if no daily bar reader is provided.
|
||||
@@ -144,8 +149,9 @@ class DataPortal(object):
|
||||
asset_finder,
|
||||
trading_calendar,
|
||||
first_trading_day,
|
||||
equity_daily_reader=None,
|
||||
equity_minute_reader=None,
|
||||
daily_reader=None,
|
||||
five_minute_reader=None,
|
||||
minute_reader=None,
|
||||
future_daily_reader=None,
|
||||
future_minute_reader=None,
|
||||
adjustment_reader=None,
|
||||
@@ -180,7 +186,7 @@ class DataPortal(object):
|
||||
# Infer the last session from the provided readers.
|
||||
last_sessions = [
|
||||
reader.last_available_dt
|
||||
for reader in [equity_daily_reader, future_daily_reader]
|
||||
for reader in [daily_reader, future_daily_reader]
|
||||
if reader is not None
|
||||
]
|
||||
if last_sessions:
|
||||
@@ -194,7 +200,11 @@ class DataPortal(object):
|
||||
# Infer the last minute from the provided readers.
|
||||
last_minutes = [
|
||||
reader.last_available_dt
|
||||
for reader in [equity_minute_reader, future_minute_reader]
|
||||
for reader in [
|
||||
minute_reader,
|
||||
five_minute_reader,
|
||||
future_minute_reader,
|
||||
]
|
||||
if reader is not None
|
||||
]
|
||||
if last_minutes:
|
||||
@@ -202,10 +212,12 @@ class DataPortal(object):
|
||||
else:
|
||||
self._last_available_minute = None
|
||||
|
||||
aligned_equity_minute_reader = self._ensure_reader_aligned(
|
||||
equity_minute_reader)
|
||||
aligned_equity_session_reader = self._ensure_reader_aligned(
|
||||
equity_daily_reader)
|
||||
aligned_minute_reader = self._ensure_reader_aligned(
|
||||
minute_reader)
|
||||
aligned_five_minute_reader = self._ensure_reader_aligned(
|
||||
five_minute_reader)
|
||||
aligned_session_reader = self._ensure_reader_aligned(
|
||||
daily_reader)
|
||||
aligned_future_minute_reader = self._ensure_reader_aligned(
|
||||
future_minute_reader)
|
||||
aligned_future_session_reader = self._ensure_reader_aligned(
|
||||
@@ -217,12 +229,15 @@ class DataPortal(object):
|
||||
}
|
||||
|
||||
aligned_minute_readers = {}
|
||||
aligned_five_minute_readers = {}
|
||||
aligned_session_readers = {}
|
||||
|
||||
if aligned_equity_minute_reader is not None:
|
||||
aligned_minute_readers[Equity] = aligned_equity_minute_reader
|
||||
if aligned_equity_session_reader is not None:
|
||||
aligned_session_readers[Equity] = aligned_equity_session_reader
|
||||
if aligned_minute_reader is not None:
|
||||
aligned_minute_readers[Equity] = aligned_minute_reader
|
||||
if aligned_five_minute_reader is not None:
|
||||
aligned_five_minute_readers[Equity] = aligned_five_minute_reader
|
||||
if aligned_session_reader is not None:
|
||||
aligned_session_readers[Equity] = aligned_session_reader
|
||||
|
||||
if aligned_future_minute_reader is not None:
|
||||
aligned_minute_readers[Future] = aligned_future_minute_reader
|
||||
@@ -252,6 +267,13 @@ class DataPortal(object):
|
||||
self._last_available_minute,
|
||||
)
|
||||
|
||||
_dispatch_five_minute_reader = AssetDispatchFiveMinuteBarReader(
|
||||
self.trading_calendar,
|
||||
self.asset_finder,
|
||||
aligned_five_minute_readers,
|
||||
self._last_available_minute,
|
||||
)
|
||||
|
||||
_dispatch_session_reader = AssetDispatchSessionBarReader(
|
||||
self.trading_calendar,
|
||||
self.asset_finder,
|
||||
@@ -261,6 +283,7 @@ class DataPortal(object):
|
||||
|
||||
self._pricing_readers = {
|
||||
'minute': _dispatch_minute_reader,
|
||||
'5-minute': _dispatch_five_minute_reader,
|
||||
'daily': _dispatch_session_reader,
|
||||
}
|
||||
|
||||
@@ -514,15 +537,17 @@ class DataPortal(object):
|
||||
)
|
||||
else:
|
||||
if field == "last_traded":
|
||||
return self.get_last_traded_dt(asset, dt, 'minute')
|
||||
return self.get_last_traded_dt(asset, dt, data_frequency)
|
||||
elif field == "price":
|
||||
return self._get_minute_spot_value(
|
||||
asset, "close", dt, ffill=True,
|
||||
return self._get_minutely_spot_value(
|
||||
asset, "close", dt, data_frequency, ffill=True,
|
||||
)
|
||||
elif field == "contract":
|
||||
return self._get_current_contract(asset, dt)
|
||||
else:
|
||||
return self._get_minute_spot_value(asset, field, dt)
|
||||
return self._get_minutely_spot_value(
|
||||
asset, field, dt, data_frequency,
|
||||
)
|
||||
|
||||
if assets_is_scalar:
|
||||
return get_single_asset_value(assets)
|
||||
@@ -648,8 +673,14 @@ class DataPortal(object):
|
||||
|
||||
return spot_value
|
||||
|
||||
def _get_minute_spot_value(self, asset, column, dt, ffill=False):
|
||||
reader = self._get_pricing_reader('minute')
|
||||
def _get_minutely_spot_value(self,
|
||||
asset,
|
||||
column,
|
||||
dt,
|
||||
data_frequency,
|
||||
ffill=False):
|
||||
|
||||
reader = self._get_pricing_reader(data_frequency)
|
||||
|
||||
if ffill:
|
||||
# If forward filling, we want the last minute with values (up to
|
||||
@@ -680,8 +711,32 @@ class DataPortal(object):
|
||||
# the value we found came from a different day, so we have to adjust
|
||||
# the data if there are any adjustments on that day barrier
|
||||
return self.get_adjusted_value(
|
||||
asset, column, query_dt,
|
||||
dt, "minute", spot_value=result
|
||||
asset,
|
||||
column,
|
||||
query_dt,
|
||||
dt,
|
||||
data_frequency,
|
||||
spot_value=result
|
||||
)
|
||||
|
||||
|
||||
def _get_five_minute_spot_value(self, asset, column, dt, ffill=False):
|
||||
return self._get_minutely_spot_value(
|
||||
asset,
|
||||
column,
|
||||
dt,
|
||||
ffill,
|
||||
'5-minute',
|
||||
)
|
||||
|
||||
|
||||
def _get_minute_spot_value(self, asset, column, dt, ffill=False):
|
||||
return self._get_minutely_spot_value(
|
||||
asset,
|
||||
column,
|
||||
dt,
|
||||
ffill,
|
||||
'minute',
|
||||
)
|
||||
|
||||
def _get_daily_spot_value(self, asset, column, dt):
|
||||
|
||||
@@ -130,13 +130,17 @@ class AssetDispatchBarReader(with_metaclass(ABCMeta)):
|
||||
|
||||
return results
|
||||
|
||||
|
||||
class AssetDispatchMinuteBarReader(AssetDispatchBarReader):
|
||||
|
||||
def _dt_window_size(self, start_dt, end_dt):
|
||||
return len(self.trading_calendar.minutes_in_range(start_dt, end_dt))
|
||||
|
||||
|
||||
class AssetDispatchFiveMinuteBarReader(AssetDispatchBarReader):
|
||||
|
||||
def _dt_window_size(self, start_dt, end_dt):
|
||||
return len(self.trading_calendar.five_minutes_in_range(start_dt, end_dt))
|
||||
|
||||
class AssetDispatchSessionBarReader(AssetDispatchBarReader):
|
||||
|
||||
def _dt_window_size(self, start_dt, end_dt):
|
||||
|
||||
@@ -35,9 +35,9 @@ from six import with_metaclass
|
||||
from toolz import keymap, valmap
|
||||
|
||||
from catalyst.data._minute_bar_internal import (
|
||||
minute_value,
|
||||
find_position_of_minute,
|
||||
find_last_traded_position_internal
|
||||
five_minute_value,
|
||||
find_position_of_five_minute,
|
||||
find_last_traded_five_minute_position_internal,
|
||||
)
|
||||
|
||||
from catalyst.gens.sim_engine import NANOS_IN_MINUTE
|
||||
@@ -48,15 +48,16 @@ from catalyst.data.us_equity_pricing import (
|
||||
check_uint64_safe,
|
||||
)
|
||||
from catalyst.utils.calendars import get_calendar
|
||||
from catalyst.utils.cli import maybe_show_progress
|
||||
from catalyst.utils.cli import (
|
||||
item_show_count,
|
||||
maybe_show_progress,
|
||||
)
|
||||
from catalyst.utils.memoize import lazyval
|
||||
|
||||
logger = logbook.Logger('FiveMinuteBars')
|
||||
|
||||
OPEN_FIVE_MINUTES_PER_DAY = 288
|
||||
US_EQUITIES_MINUTES_PER_DAY = 390
|
||||
|
||||
DEFAULT_EXPECTEDLEN = US_EQUITIES_MINUTES_PER_DAY * 252 * 15
|
||||
DEFAULT_EXPECTEDLEN_CRYPTO = OPEN_FIVE_MINUTES_PER_DAY * 366 * 15
|
||||
|
||||
OHLC_RATIO = 1000000
|
||||
@@ -66,6 +67,8 @@ OHLCV = frozenset(['open', 'high', 'low', 'close', 'volume'])
|
||||
|
||||
UINT64_MAX = iinfo(uint64).max
|
||||
|
||||
NANOS_IN_FIVE_MINUTES = 5 * NANOS_IN_MINUTE
|
||||
|
||||
class BcolzFiveMinuteOverlappingData(Exception):
|
||||
pass
|
||||
|
||||
@@ -83,7 +86,7 @@ class FiveMinuteBarReader(BarReader):
|
||||
def _calc_five_minute_index(market_opens, five_minutes_per_day):
|
||||
five_minutes = np.zeros(len(market_opens) * five_minutes_per_day,
|
||||
dtype='datetime64[ns]')
|
||||
deltas = np.arange(0, five_minutes_per_day, dtype='timedelta64[m]')
|
||||
deltas = 5 * np.arange(0, five_minutes_per_day, dtype='timedelta64[m]')
|
||||
for i, market_open in enumerate(market_opens):
|
||||
start = market_open.asm8
|
||||
five_minute_values = start + deltas
|
||||
@@ -211,7 +214,7 @@ class BcolzFiveMinuteBarMetadata(object):
|
||||
"""
|
||||
FORMAT_VERSION = 3
|
||||
|
||||
METADATA_FILENAME = 'metadata.json'
|
||||
METADATA_FILENAME = 'five-minute-metadata.json'
|
||||
|
||||
@classmethod
|
||||
def metadata_path(cls, rootdir):
|
||||
@@ -268,7 +271,7 @@ class BcolzFiveMinuteBarMetadata(object):
|
||||
calendar,
|
||||
start_session,
|
||||
end_session,
|
||||
minutes_per_day,
|
||||
five_minutes_per_day,
|
||||
version=version,
|
||||
)
|
||||
|
||||
@@ -345,7 +348,7 @@ class BcolzFiveMinuteBarMetadata(object):
|
||||
'version': self.version,
|
||||
'ohlc_ratio': self.default_ohlc_ratio,
|
||||
'ohlc_ratios_per_sid': self.ohlc_ratios_per_sid,
|
||||
'minutes_per_day': self.five_minutes_per_day,
|
||||
'five_minutes_per_day': self.five_minutes_per_day,
|
||||
'calendar_name': self.calendar.name,
|
||||
'start_session': str(self.start_session.date()),
|
||||
'end_session': str(self.end_session.date()),
|
||||
@@ -461,7 +464,7 @@ class BcolzFiveMinuteBarWriter(object):
|
||||
five_minutes_per_day,
|
||||
default_ohlc_ratio=OHLC_RATIO,
|
||||
ohlc_ratios_per_sid=None,
|
||||
expectedlen=DEFAULT_EXPECTED_CRYPTO_LEN,
|
||||
expectedlen=DEFAULT_EXPECTEDLEN_CRYPTO,
|
||||
write_metadata=True):
|
||||
|
||||
self._rootdir = rootdir
|
||||
@@ -477,11 +480,11 @@ class BcolzFiveMinuteBarWriter(object):
|
||||
self._default_ohlc_ratio = default_ohlc_ratio
|
||||
self._ohlc_ratios_per_sid = ohlc_ratios_per_sid
|
||||
|
||||
self._minute_index = _calc_minute_index(
|
||||
self._schedule.market_open, self._minutes_per_day)
|
||||
self._five_minute_index = _calc_five_minute_index(
|
||||
self._schedule.market_open, self._five_minutes_per_day)
|
||||
|
||||
if write_metadata:
|
||||
metadata = BcolzMinuteBarMetadata(
|
||||
metadata = BcolzFiveMinuteBarMetadata(
|
||||
self._default_ohlc_ratio,
|
||||
self._ohlc_ratios_per_sid,
|
||||
self._calendar,
|
||||
@@ -678,7 +681,11 @@ class BcolzFiveMinuteBarWriter(object):
|
||||
for k, v in kwargs.items():
|
||||
table.attrs[k] = v
|
||||
|
||||
def write(self, data, show_progress=False, invalid_data_behavior='warn'):
|
||||
def write(self,
|
||||
data,
|
||||
length=None,
|
||||
show_progress=False,
|
||||
invalid_data_behavior='warn'):
|
||||
"""Write a stream of minute data.
|
||||
|
||||
Parameters
|
||||
@@ -698,14 +705,15 @@ class BcolzFiveMinuteBarWriter(object):
|
||||
show_progress : bool, optional
|
||||
Whether or not to show a progress bar while writing.
|
||||
"""
|
||||
ctx = maybe_show_progress(
|
||||
with maybe_show_progress(
|
||||
data,
|
||||
length=length,
|
||||
show_percent=False,
|
||||
show_progress=show_progress,
|
||||
item_show_func=lambda e: e if e is None else str(e[0]),
|
||||
label="Merging minute equity files:",
|
||||
)
|
||||
write_sid = self.write_sid
|
||||
with ctx as it:
|
||||
item_show_func=item_show_count(length),
|
||||
label='Compiling five-minute data',
|
||||
) as it:
|
||||
write_sid = self.write_sid
|
||||
for e in it:
|
||||
write_sid(*e, invalid_data_behavior=invalid_data_behavior)
|
||||
|
||||
@@ -807,10 +815,13 @@ class BcolzFiveMinuteBarWriter(object):
|
||||
# Get the number of minutes already recorded in this sid's ctable
|
||||
num_rec_mins = table.size
|
||||
|
||||
all_minutes = self._minute_index
|
||||
all_minutes = self._five_minute_index
|
||||
# Get the latest minute we wish to write to the ctable
|
||||
last_minute_to_write = pd.Timestamp(dts[-1], tz='UTC')
|
||||
|
||||
#print 'all_minutes[-1]:', all_minutes[num_rec_mins-1]
|
||||
#print 'last_minute_to_write:', last_minute_to_write
|
||||
|
||||
# In the event that we've already written some minutely data to the
|
||||
# ctable, guard against overwriting that data.
|
||||
if num_rec_mins > 0:
|
||||
@@ -864,7 +875,7 @@ class BcolzFiveMinuteBarWriter(object):
|
||||
day_ix = self._session_labels.get_loc(day)
|
||||
# Add one to the 0-indexed day_ix to get the number of days.
|
||||
num_days = day_ix + 1
|
||||
return num_days * self._minutes_per_day
|
||||
return num_days * self._five_minutes_per_day
|
||||
|
||||
def truncate(self, date):
|
||||
"""Truncate data beyond this date in all ctables."""
|
||||
@@ -1002,7 +1013,7 @@ class BcolzFiveMinuteBarReader(FiveMinuteBarReader):
|
||||
market_closes = self._market_closes.values.astype('datetime64[m]')
|
||||
minutes_per_day = (market_closes - market_opens).astype(np.int64) / 5
|
||||
early_indices = np.where(
|
||||
minutes_per_day != self._minutes_per_day - 1)[0]
|
||||
minutes_per_day != self._five_minutes_per_day - 1)[0]
|
||||
early_opens = self._market_opens[early_indices]
|
||||
early_closes = self._market_closes[early_indices]
|
||||
minutes = [(market_open, early_close)
|
||||
@@ -1030,9 +1041,9 @@ class BcolzFiveMinuteBarReader(FiveMinuteBarReader):
|
||||
"""
|
||||
itree = IntervalTree()
|
||||
for market_open, early_close in self._minutes_to_exclude():
|
||||
start_pos = self._find_position_of_minute(early_close) + 1
|
||||
start_pos = self._find_position_of_five_minute(early_close) + 1
|
||||
end_pos = (
|
||||
self._find_position_of_minute(market_open)
|
||||
self._find_position_of_five_minute(market_open)
|
||||
+
|
||||
self._five_minutes_per_day
|
||||
-
|
||||
@@ -1121,7 +1132,7 @@ class BcolzFiveMinuteBarReader(FiveMinuteBarReader):
|
||||
minute_pos = self._last_get_value_dt_position
|
||||
else:
|
||||
try:
|
||||
minute_pos = self._find_position_of_minute(dt)
|
||||
minute_pos = self._find_position_of_five_minute(dt)
|
||||
except ValueError:
|
||||
raise NoDataOnDate()
|
||||
|
||||
@@ -1143,15 +1154,15 @@ class BcolzFiveMinuteBarReader(FiveMinuteBarReader):
|
||||
return value
|
||||
|
||||
def get_last_traded_dt(self, asset, dt):
|
||||
minute_pos = self._find_last_traded_position(asset, dt)
|
||||
minute_pos = self._find_last_traded_five_minute_position(asset, dt)
|
||||
if minute_pos == -1:
|
||||
return pd.NaT
|
||||
return self._pos_to_minute(minute_pos)
|
||||
|
||||
def _find_last_traded_position(self, asset, dt):
|
||||
def _find_last_traded_five_minute_position(self, asset, dt):
|
||||
volumes = self._open_minute_file('volume', asset)
|
||||
start_date_minute = asset.start_date.value / NANOS_IN_MINUTE
|
||||
dt_minute = dt.value / NANOS_IN_MINUTE
|
||||
start_date_minute = asset.start_date.value / NANOS_IN_FIVE_MINUTE
|
||||
dt_minute = dt.value / NANOS_IN_FIVE_MINUTE
|
||||
|
||||
try:
|
||||
# if we know of a dt before which this asset has no volume,
|
||||
@@ -1163,13 +1174,13 @@ class BcolzFiveMinuteBarReader(FiveMinuteBarReader):
|
||||
if dt_minute < earliest_dt_to_search:
|
||||
return -1
|
||||
|
||||
pos = find_last_traded_position_internal(
|
||||
pos = find_last_traded_five_minute_position_internal(
|
||||
self._market_open_values,
|
||||
self._market_close_values,
|
||||
dt_minute,
|
||||
earliest_dt_to_search,
|
||||
volumes,
|
||||
self._minutes_per_day,
|
||||
self._five_minutes_per_day,
|
||||
)
|
||||
|
||||
if pos == -1:
|
||||
@@ -1186,15 +1197,15 @@ class BcolzFiveMinuteBarReader(FiveMinuteBarReader):
|
||||
return pos
|
||||
|
||||
def _pos_to_minute(self, pos):
|
||||
minute_epoch = minute_value(
|
||||
minute_epoch = five_minute_value(
|
||||
self._market_open_values,
|
||||
pos,
|
||||
self._minutes_per_day
|
||||
self._five_minutes_per_day
|
||||
)
|
||||
|
||||
return pd.Timestamp(minute_epoch, tz='UTC', unit="m")
|
||||
|
||||
def _find_position_of_minute(self, minute_dt):
|
||||
def _find_position_of_five_minute(self, minute_dt):
|
||||
"""
|
||||
Internal method that returns the position of the given minute in the
|
||||
list of every trading minute since market open of the first trading
|
||||
@@ -1213,11 +1224,11 @@ class BcolzFiveMinuteBarReader(FiveMinuteBarReader):
|
||||
int: The position of the given minute in the list of all trading
|
||||
minutes since market open on the first trading day.
|
||||
"""
|
||||
return find_position_of_minute(
|
||||
return find_position_of_five_minute(
|
||||
self._market_open_values,
|
||||
self._market_close_values,
|
||||
minute_dt.value / NANOS_IN_MINUTE,
|
||||
self._minutes_per_day,
|
||||
minute_dt.value / NANOS_IN_FIVE_MINUTE,
|
||||
self._five_minutes_per_day,
|
||||
False,
|
||||
)
|
||||
|
||||
@@ -1241,8 +1252,8 @@ class BcolzFiveMinuteBarReader(FiveMinuteBarReader):
|
||||
(minutes in range, sids) with a dtype of float64, containing the
|
||||
values for the respective field over start and end dt range.
|
||||
"""
|
||||
start_idx = self._find_position_of_minute(start_dt)
|
||||
end_idx = self._find_position_of_minute(end_dt)
|
||||
start_idx = self._find_position_of_five_minute(start_dt)
|
||||
end_idx = self._find_position_of_five_minute(end_dt)
|
||||
|
||||
num_minutes = (end_idx - start_idx + 1)
|
||||
|
||||
@@ -1261,7 +1272,7 @@ class BcolzFiveMinuteBarReader(FiveMinuteBarReader):
|
||||
if field != 'volume':
|
||||
out = np.full(shape, np.nan)
|
||||
else:
|
||||
out = np.zeros(shape, dtype=int64)
|
||||
out = np.zeros(shape, dtype=uint64)
|
||||
|
||||
for i, sid in enumerate(sids):
|
||||
carray = self._open_minute_file(field, sid)
|
||||
|
||||
+21
-87
@@ -33,7 +33,7 @@ from ..utils.paths import (
|
||||
)
|
||||
from ..utils.deprecate import deprecated
|
||||
|
||||
from catalyst.curate.poloniex import PoloniexCurator
|
||||
from catalyst.data.bundles.poloniex import PoloniexBundle
|
||||
from catalyst.utils.calendars import get_calendar
|
||||
|
||||
|
||||
@@ -232,11 +232,14 @@ def load_market_data(trading_day=None, trading_days=None, bm_symbol='SPY',
|
||||
treasury_curves = tc[tc.index.slice_indexer(first_date, last_date)]
|
||||
return benchmark_returns, treasury_curves
|
||||
|
||||
def ensure_crypto_benchmark_data(symbol, first_date, last_date, now,
|
||||
trading_day, environ=None):
|
||||
def ensure_crypto_benchmark_data(symbol,
|
||||
first_date,
|
||||
last_date,
|
||||
now,
|
||||
trading_day,
|
||||
environ=None):
|
||||
|
||||
filename = get_benchmark_filename(symbol)
|
||||
source_filename = '/var/tmp/catalyst/data/poloniex/crypto_prices-{0}.csv'.\
|
||||
format(symbol)
|
||||
|
||||
logger.info(
|
||||
('Loading benchmark data for {symbol!r} '
|
||||
@@ -269,92 +272,23 @@ def ensure_crypto_benchmark_data(symbol, first_date, last_date, now,
|
||||
last_date=last_date
|
||||
)
|
||||
|
||||
def dateparse(time_in_secs):
|
||||
return datetime.datetime.fromtimestamp(float(time_in_secs), pytz.utc)
|
||||
|
||||
def compute_daily_bars(five_min_bars, schedule):
|
||||
# filter and copy the entry at the beginning of each session
|
||||
daily_bars = five_min_bars[
|
||||
five_min_bars.index.isin(schedule)
|
||||
].copy()
|
||||
|
||||
day_offset = pd.Timedelta(days=1)
|
||||
|
||||
# iterate through session starts doing:
|
||||
# 1. filter five_min_bars to get all entries in one day
|
||||
# 2. compute daily bar entry
|
||||
# 3. record in rid-th row of daily_bars
|
||||
for rid, start_date in enumerate(daily_bars.index):
|
||||
# compute beginning of next session
|
||||
end_date = start_date + day_offset
|
||||
|
||||
# filter for entries session entries
|
||||
day_data = five_min_bars[
|
||||
(five_min_bars.index >= start_date) &
|
||||
(five_min_bars.index < end_date)
|
||||
]
|
||||
|
||||
# compute and record daily bar
|
||||
daily_bars.iloc[rid] = (
|
||||
day_data.open.iloc[0], # first open price
|
||||
day_data.high.max(), # max of high prices
|
||||
day_data.low.min(), # min of low prices
|
||||
day_data.close.iloc[-1], # last close prices
|
||||
day_data.volume.sum(), # sum of all volumes
|
||||
)
|
||||
|
||||
# scale to allow trading 10-ths of a coin
|
||||
scale = 10.0
|
||||
daily_bars.loc[:, 'open'] /= scale
|
||||
daily_bars.loc[:, 'high'] /= scale
|
||||
daily_bars.loc[:, 'low'] /= scale
|
||||
daily_bars.loc[:, 'close'] /= scale
|
||||
daily_bars.loc[:, 'volume'] *= scale
|
||||
|
||||
return daily_bars
|
||||
|
||||
|
||||
five_min_bars = None
|
||||
# Load benchmark symbol from Poloniex API
|
||||
try:
|
||||
# load five minute bars from csv cache
|
||||
five_min_bars = pd.read_csv(
|
||||
source_filename,
|
||||
names=['date', 'open', 'high', 'low', 'close', 'volume'],
|
||||
index_col=[0],
|
||||
parse_dates=True,
|
||||
date_parser=dateparse,
|
||||
bundle = PoloniexBundle()
|
||||
bench_raw = bundle._fetch_symbol_frame(
|
||||
None,
|
||||
symbol,
|
||||
get_calendar(bundle.calendar_name),
|
||||
first_date,
|
||||
last_date,
|
||||
'daily',
|
||||
)
|
||||
five_min_bars.index = pd.to_datetime(five_min_bars.index, utc=True, unit='s')
|
||||
except (OSError, IOError):
|
||||
# Otherwise load from Poloniex API
|
||||
try:
|
||||
pc = PoloniexCurator()
|
||||
pc.append_data_single_pair(symbol)
|
||||
|
||||
five_min_bars = pc.to_dataframe(
|
||||
time.mktime(first_date.timetuple()),
|
||||
time.mktime(last_date.timetuple()),
|
||||
currencyPair=symbol,
|
||||
)
|
||||
except (OSError, IOError, HTTPError):
|
||||
logger.exception('Failed to new crypto benchmark returns')
|
||||
raise
|
||||
|
||||
# compute daily bars for open calendar
|
||||
open_calendar = get_calendar('OPEN')
|
||||
daily_bars = compute_daily_bars(
|
||||
five_min_bars,
|
||||
open_calendar.all_sessions,
|
||||
)
|
||||
|
||||
# filter daily bars to include first_date and last_date
|
||||
daily_bars = daily_bars[
|
||||
(daily_bars.index >= (first_date - trading_day)) &
|
||||
(daily_bars.index <= last_date)
|
||||
]
|
||||
except (OSError, IOError, HTTPError):
|
||||
logger.exception('Failed to fetch new crypto benchmark returns')
|
||||
raise
|
||||
|
||||
# select close column and compute percent change between days
|
||||
daily_close = daily_bars[['close']]
|
||||
daily_close = bench_raw[['close']]
|
||||
daily_close = daily_close.pct_change(1).iloc[1:]
|
||||
|
||||
try:
|
||||
|
||||
@@ -229,7 +229,7 @@ class BcolzDailyBarWriter(object):
|
||||
|
||||
@property
|
||||
def progress_bar_message(self):
|
||||
return 'Compiling daily pricing data'
|
||||
return 'Compiling daily data'
|
||||
|
||||
def write(self,
|
||||
data,
|
||||
|
||||
Reference in New Issue
Block a user