Skip to content

Commit fd638e6

Browse files
authored
Merge pull request #465 from addisonlynch/dev
Added IEX Daily Reader (Historical)
2 parents 30e6249 + 2d8e075 commit fd638e6

File tree

4 files changed

+219
-0
lines changed

4 files changed

+219
-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: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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,
47+
end=end, retry_count=retry_count,
48+
pause=pause, session=session,
49+
chunksize=chunksize)
50+
51+
@property
52+
def url(self):
53+
return 'https://api.iextrading.com/1.0/stock/market/batch'
54+
55+
@property
56+
def endpoint(self):
57+
return "chart"
58+
59+
def _get_params(self, symbol):
60+
chart_range = self._range_string_from_date()
61+
print(chart_range)
62+
if isinstance(symbol, list):
63+
symbolList = ','.join(symbol)
64+
else:
65+
symbolList = symbol
66+
params = {
67+
"symbols": symbolList,
68+
"types": self.endpoint,
69+
"range": chart_range,
70+
}
71+
return params
72+
73+
def _range_string_from_date(self):
74+
delta = relativedelta(self.start, datetime.datetime.now())
75+
if 2 <= (delta.years * -1) <= 5:
76+
return "5y"
77+
elif 1 <= (delta.years * -1) <= 2:
78+
return "2y"
79+
elif 0 <= (delta.years * -1) < 1:
80+
return "1y"
81+
else:
82+
raise ValueError(
83+
"Invalid date specified. Must be within past 5 years.")
84+
85+
def read(self):
86+
"""read data"""
87+
try:
88+
return self._read_one_data(self.url,
89+
self._get_params(self.symbols))
90+
finally:
91+
self.close()
92+
93+
def _read_lines(self, out):
94+
data = out.read()
95+
json_data = json.loads(data)
96+
result = {}
97+
if type(self.symbols) is str:
98+
syms = [self.symbols]
99+
else:
100+
syms = self.symbols
101+
for symbol in syms:
102+
d = json_data.pop(symbol)["chart"]
103+
df = pd.DataFrame(d)
104+
df.set_index("date", inplace=True)
105+
values = ["open", "high", "low", "close", "volume"]
106+
df = df[values]
107+
sstart = self.start.strftime('%Y-%m-%d')
108+
send = self.end.strftime('%Y-%m-%d')
109+
df = df.loc[sstart:send]
110+
result.update({symbol: df})
111+
if len(result) > 1:
112+
return result
113+
return result[self.symbols]
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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+
@classmethod
11+
def setup_class(cls):
12+
pytest.importorskip("lxml")
13+
14+
@property
15+
def start(self):
16+
return datetime(2015, 2, 9)
17+
18+
@property
19+
def end(self):
20+
return datetime(2017, 5, 24)
21+
22+
def test_iex_bad_symbol(self):
23+
with pytest.raises(Exception):
24+
web.DataReader("BADTICKER", "iex,", self.start, self.end)
25+
26+
def test_iex_bad_symbol_list(self):
27+
with pytest.raises(Exception):
28+
web.DataReader(["AAPL", "BADTICKER"], "iex", self.start, self.end)
29+
30+
def test_daily_invalid_date(self):
31+
start = datetime(2010, 1, 5)
32+
end = datetime(2017, 5, 24)
33+
with pytest.raises(Exception):
34+
web.DataReader(["AAPL", "TSLA"], "iex", start, end)
35+
36+
def test_single_symbol(self):
37+
df = web.DataReader("AAPL", "iex", self.start, self.end)
38+
assert list(df) == ["open", "high", "low", "close", "volume"]
39+
assert len(df) == 578
40+
assert df["volume"][-1] == 19219154
41+
42+
def test_multiple_symbols(self):
43+
syms = ["AAPL", "MSFT", "TSLA"]
44+
df = web.DataReader(syms, "iex", self.start, self.end)
45+
assert sorted(list(df)) == syms
46+
for sym in syms:
47+
assert len(df[sym] == 578)
48+
49+
def test_multiple_symbols_2(self):
50+
syms = ["AAPL", "MSFT", "TSLA"]
51+
good_start = datetime(2017, 2, 9)
52+
good_end = datetime(2017, 5, 24)
53+
df = web.DataReader(syms, "iex", good_start, good_end)
54+
assert isinstance(df, dict)
55+
assert len(df) == 3
56+
assert sorted(list(df)) == syms
57+
58+
a = df["AAPL"]
59+
t = df["TSLA"]
60+
61+
assert len(a) == 73
62+
assert len(t) == 73
63+
64+
expected1 = a.loc["2017-02-09"]
65+
assert expected1["close"] == 132.42
66+
assert expected1["high"] == 132.445
67+
68+
expected2 = a.loc["2017-05-24"]
69+
assert expected2["close"] == 153.34
70+
assert expected2["high"] == 154.17
71+
72+
expected3 = t.loc["2017-02-09"]
73+
assert expected3["close"] == 269.20
74+
assert expected3["high"] == 271.18
75+
76+
expected4 = t.loc["2017-05-24"]
77+
assert expected4["close"] == 310.22
78+
assert expected4["high"] == 311.0

0 commit comments

Comments
 (0)