Skip to content

Commit 4c32dfe

Browse files
committed
Added IEX Daily Reader (Historical)
1 parent 30e6249 commit 4c32dfe

File tree

4 files changed

+213
-0
lines changed

4 files changed

+213
-0
lines changed

docs/source/remote_data.rst

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ extract data from various Internet sources into a pandas DataFrame.
2929
Currently the following sources are supported:
3030

3131
- :ref:`Google Finance<remote_data.google>`
32+
- :ref:`IEX<remote_data.iex>`
3233
- :ref:`Enigma<remote_data.enigma>`
3334
- :ref:`Quandl<remote_data.quandl>`
3435
- :ref:`St.Louis FED (FRED)<remote_data.fred>`
@@ -64,6 +65,25 @@ Google Finance
6465
f = web.DataReader('F', 'google', start, end)
6566
f.ix['2010-01-04']
6667
68+
.. _remote_data.iex:
69+
70+
IEX
71+
===
72+
73+
Historical stock prices from `IEX <https://iextrading.com/developer/>`__,
74+
75+
.. ipython:: python
76+
77+
import pandas_datareader.data as web
78+
from datetime import datetime
79+
start = datetime(2015, 2, 9)
80+
end = datetime(2017, 5, 24)
81+
f = web.DataReader('F', 'iex', start, end)
82+
f.loc['2015-02-09']
83+
84+
85+
Prices are available up for the past 5 years.
86+
6787
.. _remote_data.enigma:
6888

6989
Enigma

pandas_datareader/data.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from pandas_datareader.google.daily import GoogleDailyReader
1616
from pandas_datareader.google.options import Options as GoogleOptions
1717
from pandas_datareader.google.quotes import GoogleQuotesReader
18+
from pandas_datareader.iex.daily import IEXDailyReader
1819
from pandas_datareader.iex.deep import Deep as IEXDeep
1920
from pandas_datareader.iex.tops import LastReader as IEXLasts
2021
from pandas_datareader.iex.tops import TopsReader as IEXTops
@@ -286,6 +287,13 @@ def DataReader(name, data_source=None, start=None, end=None,
286287
chunksize=25,
287288
retry_count=retry_count, pause=pause,
288289
session=session).read()
290+
291+
elif data_source == "iex":
292+
return IEXDailyReader(symbols=name, start=start, end=end,
293+
chunksize=25,
294+
retry_count=retry_count, pause=pause,
295+
session=session).read()
296+
289297
elif data_source == "iex-tops":
290298
return IEXTops(symbols=name, start=start, end=end,
291299
retry_count=retry_count, pause=pause,

pandas_datareader/iex/daily.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import datetime
2+
import json
3+
4+
import pandas as pd
5+
6+
from dateutil.relativedelta import relativedelta
7+
from pandas_datareader.base import _DailyBaseReader
8+
9+
# Data provided for free by IEX
10+
# Data is furnished in compliance with the guidelines promulgated in the IEX
11+
# API terms of service and manual
12+
# See https://iextrading.com/api-exhibit-a/ for additional information
13+
# and conditions of use
14+
15+
16+
class IEXDailyReader(_DailyBaseReader):
17+
18+
"""
19+
Returns DataFrame/Panel of historical stock prices from symbols, over date
20+
range, start to end. To avoid being penalized by Google Finance servers,
21+
pauses between downloading 'chunks' of symbols can be specified.
22+
23+
Parameters
24+
----------
25+
symbols : string, array-like object (list, tuple, Series), or DataFrame
26+
Single stock symbol (ticker), array-like object of symbols or
27+
DataFrame with index containing stock symbols.
28+
start : string, (defaults to '1/1/2010')
29+
Starting date, timestamp. Parses many different kind of date
30+
representations (e.g., 'JAN-01-2010', '1/1/10', 'Jan, 1, 1980')
31+
end : string, (defaults to today)
32+
Ending date, timestamp. Same format as starting date.
33+
retry_count : int, default 3
34+
Number of times to retry query request.
35+
pause : int, default 0
36+
Time, in seconds, to pause between consecutive queries of chunks. If
37+
single value given for symbol, represents the pause between retries.
38+
chunksize : int, default 25
39+
Number of symbols to download consecutively before intiating pause.
40+
session : Session, default None
41+
requests.sessions.Session instance to be used
42+
"""
43+
44+
def __init__(self, symbols=None, start=None, end=None, retry_count=3,
45+
pause=0.35, session=None, chunksize=25):
46+
super(IEXDailyReader, self).__init__(symbols=symbols, start=start, end=end,
47+
retry_count=retry_count, pause=pause, session=session, chunksize=chunksize)
48+
49+
@property
50+
def url(self):
51+
return 'https://api.iextrading.com/1.0/stock/market/batch'
52+
53+
@property
54+
def endpoint(self):
55+
return "chart"
56+
57+
def _get_params(self, symbol):
58+
chart_range = self._range_string_from_date()
59+
print(chart_range)
60+
if isinstance(symbol, list):
61+
symbolList = ','.join(symbol)
62+
else:
63+
symbolList = symbol
64+
params = {
65+
"symbols": symbolList,
66+
"types": self.endpoint,
67+
"range": chart_range,
68+
}
69+
return params
70+
71+
def _range_string_from_date(self):
72+
delta = relativedelta(self.start, datetime.datetime.now())
73+
if 2 <= (delta.years * -1) <= 5:
74+
return "5y"
75+
elif 1 <= (delta.years * -1) <= 2:
76+
return "2y"
77+
elif 0 <= (delta.years * -1) < 1:
78+
return "1y"
79+
else:
80+
raise ValueError(
81+
"Invalid date specified. Must be within past 5 years.")
82+
83+
def read(self):
84+
"""read data"""
85+
try:
86+
return self._read_one_data(self.url, self._get_params(self.symbols))
87+
finally:
88+
self.close()
89+
90+
def _read_lines(self, out):
91+
data = out.read()
92+
json_data = json.loads(data)
93+
result = {}
94+
if type(self.symbols) is str:
95+
syms = [self.symbols]
96+
else:
97+
syms = self.symbols
98+
for symbol in syms:
99+
d = json_data.pop(symbol)["chart"]
100+
df = pd.DataFrame(d)
101+
df.set_index("date", inplace=True)
102+
values = ["open", "high", "low", "close", "volume"]
103+
df = df[values]
104+
sstart = self.start.strftime('%Y-%m-%d')
105+
send = self.end.strftime('%Y-%m-%d')
106+
df= df.loc[sstart:send]
107+
result.update({symbol:df})
108+
if len(result) > 1:
109+
return result
110+
return result[self.symbols]
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
from datetime import datetime
2+
3+
import pytest
4+
5+
import pandas_datareader.data as web
6+
7+
8+
class TestIEXDaily(object):
9+
10+
@property
11+
def start(self):
12+
return datetime(2015, 2, 9)
13+
14+
@property
15+
def end(self):
16+
return datetime(2017, 5, 24)
17+
18+
def test_iex_bad_symbol(self):
19+
with pytest.raises(Exception):
20+
web.DataReader("BADTICKER", "iex,", self.start, self.end)
21+
22+
def test_iex_bad_symbol_list(self):
23+
with pytest.raises(Exception):
24+
web.DataReader(["AAPL", "BADTICKER"], "iex", self.start, self.end)
25+
26+
def test_daily_invalid_date(self):
27+
start = datetime(2010, 1, 5)
28+
end = datetime(2017, 5, 24)
29+
with pytest.raises(Exception):
30+
df = web.DataReader(["AAPL", "TSLA"], "iex", start, end)
31+
32+
def test_single_symbol(self):
33+
df = web.DataReader("AAPL", "iex", self.start, self.end)
34+
assert list(df) == ["open", "high", "low", "close", "volume"]
35+
assert len(df) == 578
36+
assert df["volume"][-1] == 19219154
37+
38+
def test_multiple_symbols(self):
39+
syms = ["AAPL", "MSFT", "TSLA"]
40+
df = web.DataReader(syms, "iex", self.start, self.end)
41+
assert sorted(list(df)) == syms
42+
for sym in syms:
43+
assert len(df[sym] == 578)
44+
45+
def test_multiple_symbols_2(self):
46+
syms = ["AAPL", "MSFT", "TSLA"]
47+
good_start = datetime(2017, 2, 9)
48+
good_end = datetime(2017, 5, 24)
49+
df = web.DataReader(syms, "iex", good_start, good_end)
50+
assert isinstance(df, dict)
51+
assert len(df) == 3
52+
assert sorted(list(df)) == syms
53+
54+
a = df["AAPL"]
55+
t = df["TSLA"]
56+
57+
assert len(a) == 73
58+
assert len(t) == 73
59+
60+
expected1 = a.loc["2017-02-09"]
61+
assert expected1["close"] == 132.42
62+
assert expected1["high"] == 132.445
63+
64+
expected2 = a.loc["2017-05-24"]
65+
assert expected2["close"] == 153.34
66+
assert expected2["high"] == 154.17
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
75+

0 commit comments

Comments
 (0)