Skip to content

Commit 0cef790

Browse files
committed
Migrated IEX Daily reader to IEX Cloud
1 parent b2ee60b commit 0cef790

File tree

5 files changed

+100
-84
lines changed

5 files changed

+100
-84
lines changed

docs/source/remote_data.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,12 @@ writing).
7070
IEX
7171
===
7272

73+
.. warning:: Usage of all IEX readers now requries an API key. See
74+
below for additional information.
75+
7376
The Investors Exchange (IEX) provides a wide range of data through an
74-
`API <https://iextrading.com/developer/docs/>`__. Historical stock
75-
prices are available for up to 5 years:
77+
`API <https://iexcloud.io/api/docs/>`__. Historical stock
78+
prices are available for up to 15 years. The usage of these readers requires an API key, which can be stored in the ``IEX_API_TOKEN`` environment variable.
7679

7780
.. ipython:: python
7881

docs/source/whatsnew/v0.8.0.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ Highlights include:
88
- A new connector for Econdb was introduced. Econdb provides
99
aggregated economic data from 90+ official statistical agencies
1010
(:issue:`615`)
11+
- Migrated IEX readers to `IEX Cloud <https://iexcloud.io>`__. All
12+
readers now require an API token (``IEX_API_TOKEN``)
1113
- Removal of Google finance and Morningstar, which were deprecated in 0.7.0.
1214
- Immediate deprecation of Robinhood for quotes and historical data. Robinhood
1315
ended support for these endpoints in 1/2019
@@ -24,6 +26,7 @@ Enhancements
2426
~~~~~~~~~~~~
2527

2628
- Added Tiingo IEX Historical reader.
29+
- Up to 15 years of historical prices from IEX with new platform, IEX Cloud
2730

2831
.. _whatsnew_080.api_breaking:
2932

@@ -33,7 +36,9 @@ Backwards incompatible API changes
3336
- Immediate deprecation of Robinhood for quotes and historical data. Robinhood
3437
ended support for these endpoints in 1/2019. The Robinhood quotes and daily
3538
readers will raise an ``ImmediateDeprecationError`` when called.
36-
39+
- Usage of all IEX readers requires an IEX Cloud API token, which can
40+
be passed as a parameter or stored in the environment variable
41+
``IEX_API_TOKEN``
3742

3843
.. _whatsnew_080.bug_fixes:
3944

pandas_datareader/data.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ def DataReader(name, data_source=None, start=None, end=None,
312312

313313
elif data_source == "iex":
314314
return IEXDailyReader(symbols=name, start=start, end=end,
315-
chunksize=25,
315+
chunksize=25, api_key=access_key,
316316
retry_count=retry_count, pause=pause,
317317
session=session).read()
318318

pandas_datareader/iex/daily.py

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import datetime
22
import json
3+
import os
34

45
import pandas as pd
56

@@ -40,10 +41,19 @@ class IEXDailyReader(_DailyBaseReader):
4041
Number of symbols to download consecutively before intiating pause.
4142
session : Session, default None
4243
requests.sessions.Session instance to be used
44+
api_key: str
45+
IEX Cloud Secret Token
4346
"""
4447

4548
def __init__(self, symbols=None, start=None, end=None, retry_count=3,
46-
pause=0.1, session=None, chunksize=25):
49+
pause=0.1, session=None, chunksize=25, api_key=None):
50+
if api_key is None:
51+
api_key = os.getenv('IEX_API_KEY')
52+
if not api_key or not isinstance(api_key, str):
53+
raise ValueError('The IEX Cloud API key must be provided either '
54+
'through the api_key variable or through the '
55+
' environment variable IEX_API_KEY')
56+
self.api_key = api_key
4757
super(IEXDailyReader, self).__init__(symbols=symbols, start=start,
4858
end=end, retry_count=retry_count,
4959
pause=pause, session=session,
@@ -52,7 +62,7 @@ def __init__(self, symbols=None, start=None, end=None, retry_count=3,
5262
@property
5363
def url(self):
5464
"""API URL"""
55-
return 'https://api.iextrading.com/1.0/stock/market/batch'
65+
return 'https://sandbox.iexapis.com/stable/stock/market/batch'
5666

5767
@property
5868
def endpoint(self):
@@ -69,20 +79,24 @@ def _get_params(self, symbol):
6979
"symbols": symbolList,
7080
"types": self.endpoint,
7181
"range": chart_range,
82+
"token": self.api_key
7283
}
7384
return params
7485

7586
def _range_string_from_date(self):
7687
delta = relativedelta(self.start, datetime.datetime.now())
77-
if 2 <= (delta.years * -1) <= 5:
88+
years = (delta.years * -1)
89+
if 5 <= years <= 15:
90+
return "max"
91+
if 2 <= years < 5:
7892
return "5y"
79-
elif 1 <= (delta.years * -1) <= 2:
93+
elif 1 <= years < 2:
8094
return "2y"
81-
elif 0 <= (delta.years * -1) < 1:
95+
elif 0 <= years < 1:
8296
return "1y"
8397
else:
8498
raise ValueError(
85-
"Invalid date specified. Must be within past 5 years.")
99+
"Invalid date specified. Must be within past 15 years.")
86100

87101
def read(self):
88102
"""Read data"""
Lines changed: 68 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,68 @@
1-
from datetime import datetime
2-
3-
from pandas import DataFrame, MultiIndex
4-
5-
import pytest
6-
7-
import pandas_datareader.data as web
8-
9-
10-
class TestIEXDaily(object):
11-
12-
@classmethod
13-
def setup_class(cls):
14-
pytest.importorskip("lxml")
15-
16-
@property
17-
def start(self):
18-
return datetime(2015, 2, 9)
19-
20-
@property
21-
def end(self):
22-
return datetime(2017, 5, 24)
23-
24-
def test_iex_bad_symbol(self):
25-
with pytest.raises(Exception):
26-
web.DataReader("BADTICKER", "iex,", self.start, self.end)
27-
28-
def test_iex_bad_symbol_list(self):
29-
with pytest.raises(Exception):
30-
web.DataReader(["AAPL", "BADTICKER"], "iex",
31-
self.start, self.end)
32-
33-
def test_daily_invalid_date(self):
34-
start = datetime(2010, 1, 5)
35-
end = datetime(2017, 5, 24)
36-
with pytest.raises(Exception):
37-
web.DataReader(["AAPL", "TSLA"], "iex", start, end)
38-
39-
def test_single_symbol(self):
40-
df = web.DataReader("AAPL", "iex", self.start, self.end)
41-
assert list(df) == ["open", "high", "low", "close", "volume"]
42-
assert len(df) == 578
43-
assert df["volume"][-1] == 19219154
44-
45-
def test_multiple_symbols(self):
46-
syms = ["AAPL", "MSFT", "TSLA"]
47-
df = web.DataReader(syms, "iex", self.start, self.end)
48-
assert sorted(list(df.columns.levels[1])) == syms
49-
for sym in syms:
50-
assert len(df.xs(sym, level='Symbols', axis=1) == 578)
51-
52-
def test_multiple_symbols_2(self):
53-
syms = ["AAPL", "MSFT", "TSLA"]
54-
good_start = datetime(2017, 2, 9)
55-
good_end = datetime(2017, 5, 24)
56-
df = web.DataReader(syms, "iex", good_start, good_end)
57-
assert isinstance(df, DataFrame)
58-
assert isinstance(df.columns, MultiIndex)
59-
assert len(df.columns.levels[1]) == 3
60-
assert sorted(list(df.columns.levels[1])) == syms
61-
62-
a = df.xs("AAPL", axis=1, level='Symbols')
63-
t = df.xs("TSLA", axis=1, level='Symbols')
64-
65-
assert len(a) == 73
66-
assert len(t) == 73
67-
68-
expected3 = t.loc["2017-02-09"]
69-
assert expected3["close"] == 269.20
70-
assert expected3["high"] == 271.18
71-
72-
expected4 = t.loc["2017-05-24"]
73-
assert expected4["close"] == 310.22
74-
assert expected4["high"] == 311.0
1+
from datetime import datetime
2+
import os
3+
4+
from pandas import DataFrame, MultiIndex
5+
6+
import pytest
7+
8+
import pandas_datareader.data as web
9+
10+
11+
@pytest.mark.skipif(os.getenv("IEX_SANDBOX") != 'enable',
12+
reason='All tests must be run in sandbox mode')
13+
class TestIEXDaily(object):
14+
15+
@classmethod
16+
def setup_class(cls):
17+
pytest.importorskip("lxml")
18+
19+
@property
20+
def start(self):
21+
return datetime(2015, 2, 9)
22+
23+
@property
24+
def end(self):
25+
return datetime(2017, 5, 24)
26+
27+
def test_iex_bad_symbol(self):
28+
with pytest.raises(Exception):
29+
web.DataReader("BADTICKER", "iex,", self.start, self.end)
30+
31+
def test_iex_bad_symbol_list(self):
32+
with pytest.raises(Exception):
33+
web.DataReader(["AAPL", "BADTICKER"], "iex",
34+
self.start, self.end)
35+
36+
def test_daily_invalid_date(self):
37+
start = datetime(2000, 1, 5)
38+
end = datetime(2017, 5, 24)
39+
with pytest.raises(Exception):
40+
web.DataReader(["AAPL", "TSLA"], "iex", start, end)
41+
42+
def test_single_symbol(self):
43+
df = web.DataReader("AAPL", "iex", self.start, self.end)
44+
assert list(df) == ["open", "high", "low", "close", "volume"]
45+
assert len(df) == 578
46+
47+
def test_multiple_symbols(self):
48+
syms = ["AAPL", "MSFT", "TSLA"]
49+
df = web.DataReader(syms, "iex", self.start, self.end)
50+
assert sorted(list(df.columns.levels[1])) == syms
51+
for sym in syms:
52+
assert len(df.xs(sym, level='Symbols', axis=1) == 578)
53+
54+
def test_multiple_symbols_2(self):
55+
syms = ["AAPL", "MSFT", "TSLA"]
56+
good_start = datetime(2017, 2, 9)
57+
good_end = datetime(2017, 5, 24)
58+
df = web.DataReader(syms, "iex", good_start, good_end)
59+
assert isinstance(df, DataFrame)
60+
assert isinstance(df.columns, MultiIndex)
61+
assert len(df.columns.levels[1]) == 3
62+
assert sorted(list(df.columns.levels[1])) == syms
63+
64+
a = df.xs("AAPL", axis=1, level='Symbols')
65+
t = df.xs("TSLA", axis=1, level='Symbols')
66+
67+
assert len(a) == 73
68+
assert len(t) == 73

0 commit comments

Comments
 (0)