Skip to content

Commit

Permalink
Merge 2c4cc4c into 05220cd
Browse files Browse the repository at this point in the history
  • Loading branch information
bashtage committed Feb 7, 2018
2 parents 05220cd + 2c4cc4c commit 4f5fce0
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 49 deletions.
30 changes: 30 additions & 0 deletions docs/source/whatsnew/v0.7.0.txt
@@ -0,0 +1,30 @@
.. _whatsnew_070:

v0.7.0 (Xxxxxxx YY, 20ZZ)
---------------------------


Highlights include:


.. contents:: What's new in v0.7.0
:local:
:backlinks: none

.. _whatsnew_070.enhancements:

Enhancements
~~~~~~~~~~~~

.. _whatsnew_070.api_breaking:

Backwards incompatible API changes
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

.. _whatsnew_070.bug_fixes:

Bug Fixes
~~~~~~~~~

- Added support for passing the API KEY to QuandlReader either directly or by
setting the environmental variable QUANDL_API_KEY (:issue:`485`).
2 changes: 2 additions & 0 deletions pandas_datareader/compat/__init__.py
Expand Up @@ -17,8 +17,10 @@

if PANDAS_0190:
from pandas.api.types import is_number
from pandas.util.testing import assert_frame_equal
else:
from pandas.core.common import is_number
from pandas.testing import assert_frame_equal

if PANDAS_0200:
from pandas.util.testing import assert_raises_regex
Expand Down
75 changes: 46 additions & 29 deletions pandas_datareader/quandl.py
@@ -1,10 +1,10 @@
import os
import re

from pandas_datareader.base import _DailyBaseReader


class QuandlReader(_DailyBaseReader):

"""
Returns DataFrame of historical stock prices from symbol, over date
range, start to end.
Expand Down Expand Up @@ -37,10 +37,25 @@ class QuandlReader(_DailyBaseReader):
Number of symbols to download consecutively before intiating pause.
session : Session, default None
requests.sessions.Session instance to be used
api_key : str, optional
Quandl API key . If not provided the environmental variable
QUANDL_API_KEY is read. The API key is *required*.
"""

_BASE_URL = "https://www.quandl.com/api/v3/datasets/"

def __init__(self, symbols, start=None, end=None, retry_count=3, pause=0.1,
session=None, chunksize=25, api_key=None):
super(QuandlReader, self).__init__(symbols, start, end, retry_count,
pause, session, chunksize)
if api_key is None:
api_key = os.getenv('QUANDL_API_KEY')
if not api_key or not isinstance(api_key, str):
raise ValueError('The Quandl API key must be provided either '
'through the api_key variable or through the '
'environmental variable QUANDL_API_KEY.')
self.api_key = api_key

@property
def url(self):
"""API URL"""
Expand All @@ -65,37 +80,39 @@ def url(self):
'start_date': self.start.strftime('%Y-%m-%d'),
'end_date': self.end.strftime('%Y-%m-%d'),
'order': "asc",
'api_key': self.api_key
}
paramstring = '&'.join(['%s=%s' % (k, v) for k, v in params.items()])
return '%s%s/%s.csv?%s' % (self._BASE_URL, datasetname,
symbol, paramstring)
url = '{url}{dataset}/{symbol}.csv?{params}'
return url.format(url=self._BASE_URL, dataset=datasetname,
symbol=symbol, params=paramstring)

def _fullmatch(self, regex, string, flags=0):
"""Emulate python-3.4 re.fullmatch()."""
return re.match("(?:" + regex + r")\Z", string, flags=flags)

_COUNTRYCODE_TO_DATASET = dict(
# https://www.quandl.com/data/EURONEXT-Euronext-Stock-Exchange
BE='EURONEXT',
# https://www.quandl.com/data/HKEX-Hong-Kong-Exchange
CN='HKEX',
# https://www.quandl.com/data/SSE-Boerse-Stuttgart
DE='SSE',
FR='EURONEXT',
# https://www.quandl.com/data/NSE-National-Stock-Exchange-of-India
IN='NSE',
# https://www.quandl.com/data/TSE-Tokyo-Stock-Exchange
JP='TSE',
NL='EURONEXT',
PT='EURONEXT',
# https://www.quandl.com/data/LSE-London-Stock-Exchange
UK='LSE',
# https://www.quandl.com/data/WIKI-Wiki-EOD-Stock-Prices
US='WIKI',
)
# https://www.quandl.com/data/EURONEXT-Euronext-Stock-Exchange
BE='EURONEXT',
# https://www.quandl.com/data/HKEX-Hong-Kong-Exchange
CN='HKEX',
# https://www.quandl.com/data/SSE-Boerse-Stuttgart
DE='SSE',
FR='EURONEXT',
# https://www.quandl.com/data/NSE-National-Stock-Exchange-of-India
IN='NSE',
# https://www.quandl.com/data/TSE-Tokyo-Stock-Exchange
JP='TSE',
NL='EURONEXT',
PT='EURONEXT',
# https://www.quandl.com/data/LSE-London-Stock-Exchange
UK='LSE',
# https://www.quandl.com/data/WIKI-Wiki-EOD-Stock-Prices
US='WIKI',
)

def _db_from_countrycode(self, code):
assert code in self._COUNTRYCODE_TO_DATASET,\
assert code in self._COUNTRYCODE_TO_DATASET, \
"No Quandl dataset known for country code '%s'" % code
return self._COUNTRYCODE_TO_DATASET[code]

Expand All @@ -106,12 +123,12 @@ def read(self):
"""Read data"""
df = super(QuandlReader, self).read()
df.rename(columns=lambda n: n.replace(' ', '')
.replace('.', '')
.replace('/', '')
.replace('%', '')
.replace('(', '')
.replace(')', '')
.replace("'", '')
.replace('-', ''),
.replace('.', '')
.replace('/', '')
.replace('%', '')
.replace('(', '')
.replace(')', '')
.replace("'", '')
.replace('-', ''),
inplace=True)
return df
59 changes: 39 additions & 20 deletions pandas_datareader/tests/test_quandl.py
@@ -1,6 +1,13 @@
import os

import pytest

import pandas_datareader.data as web
from pandas_datareader._utils import RemoteDataError
from pandas_datareader._testing import skip_on_exception
from pandas_datareader.compat import assert_frame_equal

TEST_API_KEY = os.getenv('QUANDL_API_KEY')
# Ensure blank TEST_API_KEY not used in pull request
TEST_API_KEY = None if not TEST_API_KEY else TEST_API_KEY


class TestQuandl(object):
Expand All @@ -17,76 +24,88 @@ def check_headers(self, df, expected_cols):
act_cols = frozenset(df.columns.tolist())
assert expected_cols == act_cols, "unexpected cols: " + str(act_cols)

@skip_on_exception(RemoteDataError)
@pytest.mark.skipif(TEST_API_KEY is None, reason="QUANDL_API_KEY not set")
def test_db_wiki_us(self):
df = web.DataReader('F', 'quandl', self.start10, self.end10)
df = web.DataReader('F', 'quandl', self.start10, self.end10,
access_key=TEST_API_KEY)
self.check_headers(df, ['Open', 'High', 'Low', 'Close', 'Volume',
'ExDividend', 'SplitRatio', 'AdjOpen',
'AdjHigh', 'AdjLow', 'AdjClose', 'AdjVolume'])
assert df.Close.at[self.day10] == 7.70

@skip_on_exception(RemoteDataError)
@pytest.mark.skipif(TEST_API_KEY is None, reason="QUANDL_API_KEY not set")
def test_db_fse_frankfurt(self):
# ALV_X: Allianz SE
df = web.DataReader('FSE/ALV_X', 'quandl', self.start10, self.end10)
df = web.DataReader('FSE/ALV_X', 'quandl', self.start10, self.end10,
access_key=TEST_API_KEY)
self.check_headers(df, ['Open', 'High', 'Low', 'Close', 'Change',
'TradedVolume', 'Turnover',
'LastPriceoftheDay', 'DailyTradedUnits',
'DailyTurnover'])
assert df.Close.at[self.day10] == 159.45

@skip_on_exception(RemoteDataError)
@pytest.mark.skipif(TEST_API_KEY is None, reason="QUANDL_API_KEY not set")
def test_db_sse_de_stuttgart(self):
# ALV: Allianz SE
df = web.DataReader('SSE/ALV', 'quandl', self.start2, self.end2)
df = web.DataReader('SSE/ALV', 'quandl', self.start2, self.end2,
access_key=TEST_API_KEY)
self.check_headers(df, [
"High", "Low", "Last", "PreviousDayPrice", "Volume"])
"High", "Low", "Last", "PreviousDayPrice", "Volume"])
# as of 2017-06-11: PreviousDayPrice can be outside Low/High range;
# Volume can be NaN
assert df.Last.at[self.day2] == 136.47
df2 = web.DataReader('ALV.DE', 'quandl', self.start2, self.end2)
assert (df.Last == df2.Last).all()

@skip_on_exception(RemoteDataError)
@pytest.mark.skipif(TEST_API_KEY is None, reason="QUANDL_API_KEY not set")
def test_db_euronext_be_fr_nl_pt(self):
# FP: Total SA
# as of 2017-06-11, some datasets end a few months after their start,
# e.g. ALVD, BASD
df = web.DataReader('EURONEXT/FP', 'quandl', self.start2, self.end2)
df = web.DataReader('EURONEXT/FP', 'quandl', self.start2, self.end2,
access_key=TEST_API_KEY)
self.check_headers(df, [
"Open", "High", "Low", "Last", "Turnover", "Volume"])
"Open", "High", "Low", "Last", "Turnover", "Volume"])
assert df.Last.at[self.day2] == 42.525
df2 = web.DataReader('FP.FR', 'quandl', self.start2, self.end2)
assert (df.Last == df2.Last).all()

@skip_on_exception(RemoteDataError)
@pytest.mark.skipif(TEST_API_KEY is None, reason="QUANDL_API_KEY not set")
def test_db_lse_uk(self):
# RBS: Royal Bank of Scotland
df = web.DataReader('LSE/RBS', 'quandl', self.start10, self.end10)
df = web.DataReader('LSE/RBS', 'quandl', self.start10, self.end10,
access_key=TEST_API_KEY)
self.check_headers(df, ["High", "Low", "LastClose", "Price",
"Volume", "Change", "Var"])
# as of 2017-06-11, Price == LastClose, all others are NaN
assert df.Price.at[self.day10] == 5950.983

@skip_on_exception(RemoteDataError)
@pytest.mark.skipif(TEST_API_KEY is None, reason="QUANDL_API_KEY not set")
def test_db_nse_in(self):
# TCS: Tata Consutancy Services
df = web.DataReader('NSE/TCS', 'quandl', self.start10, self.end10)
df = web.DataReader('NSE/TCS', 'quandl', self.start10, self.end10,
access_key=TEST_API_KEY)
self.check_headers(df, ['Open', 'High', 'Low', 'Last', 'Close',
'TotalTradeQuantity', 'TurnoverLacs'])
assert df.Close.at[self.day10] == 1259.05

@skip_on_exception(RemoteDataError)
@pytest.mark.skipif(TEST_API_KEY is None, reason="QUANDL_API_KEY not set")
def test_db_tse_jp(self):
# TSE/6758: Sony Corp.
df = web.DataReader('TSE/6758', 'quandl', self.start10, self.end10)
df = web.DataReader('TSE/6758', 'quandl', self.start10, self.end10,
access_key=TEST_API_KEY)
self.check_headers(df, ['Open', 'High', 'Low', 'Close', 'Volume'])
assert df.Close.at[self.day10] == 5190.0

@skip_on_exception(RemoteDataError)
df2 = web.get_data_quandl('TSE/6758', self.start10, self.end10,
api_key=TEST_API_KEY)
assert_frame_equal(df, df2)

@pytest.mark.skipif(TEST_API_KEY is None, reason="QUANDL_API_KEY not set")
def test_db_hkex_cn(self):
# HKEX/00941: China Mobile
df = web.DataReader('HKEX/00941', 'quandl', self.start2, self.end2)
df = web.DataReader('HKEX/00941', 'quandl', self.start2, self.end2,
access_key=TEST_API_KEY)
self.check_headers(df,
['NominalPrice', 'NetChange', 'Change', 'Bid',
'Ask', 'PEx', 'High', 'Low', 'PreviousClose',
Expand Down

0 comments on commit 4f5fce0

Please sign in to comment.