Files
catalyst/catalyst/data/bundles/quandl.py
T

234 lines
7.0 KiB
Python

#
# Copyright 2017 Enigma MPC, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from datetime import datetime
import pandas as pd
from six.moves.urllib.parse import urlencode
from catalyst.data.bundles.core import register_bundle
from catalyst.data.bundles.base_pricing import BaseEquityPricingBundle
from catalyst.utils.memoize import lazyval
"""
Module for building a complete daily dataset from Quandl's WIKI dataset.
"""
from itertools import count
import tarfile
from time import time, sleep
from datetime import datetime
from logbook import Logger
import pandas as pd
from six.moves.urllib.parse import urlencode
from catalyst.utils.calendars import register_calendar_alias
from catalyst.utils.cli import maybe_show_progress
from . import core as bundles
from catalyst.constants import LOG_LEVEL
log = Logger(__name__, level=LOG_LEVEL)
seconds_per_call = (pd.Timedelta('10 minutes') / 2000).total_seconds()
class QuandlBundle(BaseEquityPricingBundle):
@lazyval
def name(self):
return 'quandl'
@lazyval
def exchange(self):
return 'QUANDL'
@lazyval
def frequencies(self):
return set(('daily',))
@lazyval
def tar_url(self):
return 'https://s3.amazonaws.com/quantopian-public-zipline-data/quandl'
@lazyval
def wait_time(self):
return pd.Timedelta(milliseconds=300)
@lazyval
def _excluded_symbols(self):
"""
Invalid symbols that quandl has had in its metadata:
"""
return frozenset({'TEST123456789'})
def fetch_raw_metadata_frame(self, api_key, page_number):
raw = pd.read_csv(
self._format_metadata_url(api_key, page_number),
date_parser=pd.tseries.tools.to_datetime,
parse_dates=[
'oldest_available_date',
'newest_available_date',
],
dtype={
'dataset_code': 'str',
'name': 'str',
'oldest_available_date': 'str',
'newest_available_date': 'str',
},
usecols=[
'dataset_code',
'name',
'oldest_available_date',
'newest_available_date',
],
).rename(
columns={
'dataset_code': 'symbol',
'name': 'asset_name',
'oldest_available_date': 'start_date',
'newest_available_date': 'end_date',
},
)
raw['start_date'] = raw['start_date'].astype(datetime)
raw['end_date'] = raw['end_date'].astype(datetime)
raw['ac_date'] = raw['end_date'] + pd.Timedelta(days=1)
# Filter out invalid symbols
raw = raw[~raw.symbol.isin(self._excluded_symbols)]
# cut out all the other stuff in the name column
# we need to escape the paren because it is actually splitting on a regex
raw.asset_name = raw.asset_name.str.split(r' \(', 1).str.get(0)
return raw
def fetch_raw_symbol_frame(self,
api_key,
symbol,
calendar,
start_session,
end_session,
data_frequency):
raw_data = pd.read_csv(
self._format_wiki_url(
api_key,
symbol,
start_session,
end_session,
data_frequency,
),
parse_dates=['Date'],
index_col='Date',
usecols=[
'Open',
'High',
'Low',
'Close',
'Volume',
'Date',
'Ex-Dividend',
'Split Ratio',
],
na_values=['NA'],
).rename(columns={
'Open': 'open',
'High': 'high',
'Low': 'low',
'Close': 'close',
'Volume': 'volume',
'Date': 'date',
'Ex-Dividend': 'ex_dividend',
'Split Ratio': 'split_ratio',
})
sessions = calendar.sessions_in_range(start_session, end_session)
return raw_data.reindex(
sessions.tz_localize(None),
copy=False,
).fillna(0.0)
def post_process_symbol_metadata(self, asset_id, sym_md, sym_data):
self._update_splits(asset_id, sym_data)
self._update_dividends(asset_id, sym_data)
return sym_md
def _update_splits(self, asset_id, raw_data):
split_ratios = raw_data.split_ratio
df = pd.DataFrame({'ratio': 1 / split_ratios[split_ratios != 1]})
df.index.name = 'effective_date'
df.reset_index(inplace=True)
df['sid'] = asset_id
self.splits.append(df)
def _update_dividends(self, asset_id, raw_data):
divs = raw_data.ex_dividend
df = pd.DataFrame({'amount': divs[divs != 0]})
df.index.name = 'ex_date'
df.reset_index(inplace=True)
df['sid'] = asset_id
# we do not have this data in the WIKI dataset
df['record_date'] = df['declared_date'] = df['pay_date'] = pd.NaT
self.dividends.append(df)
def _format_metadata_url(self, api_key, page_number):
"""Build the query RL for the quandl WIKI metadata.
"""
query_params = [
('per_page', '100'),
('sort_by', 'id'),
('page', str(page_number)),
('database_code', 'WIKI'),
]
if api_key is not None:
query_params = [('api_key', api_key)] + query_params
return (
'https://www.quandl.com/api/v3/datasets.csv?' + urlencode(query_params)
)
def _format_wiki_url(self,
api_key,
symbol,
start_date,
end_date,
data_frequency):
"""
Build a query URL for a quandl WIKI dataset.
"""
query_params = [
('start_date', start_date.strftime('%Y-%m-%d')),
('end_date', end_date.strftime('%Y-%m-%d')),
('order', 'asc'),
]
if api_key is not None:
query_params = [('api_key', api_key)] + query_params
return (
"https://www.quandl.com/api/v3/datasets/WIKI/"
"{symbol}.csv?{query}".format(
symbol=symbol,
query=urlencode(query_params),
)
)
register_calendar_alias('QUANDL', 'NYSE')
register_bundle(QuandlBundle)