Skip to content

Commit

Permalink
Merge pull request #465 from addisonlynch/dev
Browse files Browse the repository at this point in the history
Added IEX Daily Reader (Historical)
  • Loading branch information
bashtage committed Jan 22, 2018
2 parents 30e6249 + 2d8e075 commit fd638e6
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 0 deletions.
20 changes: 20 additions & 0 deletions docs/source/remote_data.rst
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ extract data from various Internet sources into a pandas DataFrame.
Currently the following sources are supported:

- :ref:`Google Finance<remote_data.google>`
- :ref:`IEX<remote_data.iex>`
- :ref:`Enigma<remote_data.enigma>`
- :ref:`Quandl<remote_data.quandl>`
- :ref:`St.Louis FED (FRED)<remote_data.fred>`
Expand Down Expand Up @@ -64,6 +65,25 @@ Google Finance
f = web.DataReader('F', 'google', start, end)
f.ix['2010-01-04']
.. _remote_data.iex:

IEX
===

Historical stock prices from `IEX <https://iextrading.com/developer/>`__,

.. ipython:: python
import pandas_datareader.data as web
from datetime import datetime
start = datetime(2015, 2, 9)
end = datetime(2017, 5, 24)
f = web.DataReader('F', 'iex', start, end)
f.loc['2015-02-09']
Prices are available up for the past 5 years.

.. _remote_data.enigma:

Enigma
Expand Down
8 changes: 8 additions & 0 deletions pandas_datareader/data.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from pandas_datareader.google.daily import GoogleDailyReader
from pandas_datareader.google.options import Options as GoogleOptions
from pandas_datareader.google.quotes import GoogleQuotesReader
from pandas_datareader.iex.daily import IEXDailyReader
from pandas_datareader.iex.deep import Deep as IEXDeep
from pandas_datareader.iex.tops import LastReader as IEXLasts
from pandas_datareader.iex.tops import TopsReader as IEXTops
Expand Down Expand Up @@ -286,6 +287,13 @@ def DataReader(name, data_source=None, start=None, end=None,
chunksize=25,
retry_count=retry_count, pause=pause,
session=session).read()

elif data_source == "iex":
return IEXDailyReader(symbols=name, start=start, end=end,
chunksize=25,
retry_count=retry_count, pause=pause,
session=session).read()

elif data_source == "iex-tops":
return IEXTops(symbols=name, start=start, end=end,
retry_count=retry_count, pause=pause,
Expand Down
113 changes: 113 additions & 0 deletions pandas_datareader/iex/daily.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import datetime
import json

import pandas as pd

from dateutil.relativedelta import relativedelta
from pandas_datareader.base import _DailyBaseReader

# Data provided for free by IEX
# Data is furnished in compliance with the guidelines promulgated in the IEX
# API terms of service and manual
# See https://iextrading.com/api-exhibit-a/ for additional information
# and conditions of use


class IEXDailyReader(_DailyBaseReader):

"""
Returns DataFrame/Panel of historical stock prices from symbols, over date
range, start to end. To avoid being penalized by Google Finance servers,
pauses between downloading 'chunks' of symbols can be specified.
Parameters
----------
symbols : string, array-like object (list, tuple, Series), or DataFrame
Single stock symbol (ticker), array-like object of symbols or
DataFrame with index containing stock symbols.
start : string, (defaults to '1/1/2010')
Starting date, timestamp. Parses many different kind of date
representations (e.g., 'JAN-01-2010', '1/1/10', 'Jan, 1, 1980')
end : string, (defaults to today)
Ending date, timestamp. Same format as starting date.
retry_count : int, default 3
Number of times to retry query request.
pause : int, default 0
Time, in seconds, to pause between consecutive queries of chunks. If
single value given for symbol, represents the pause between retries.
chunksize : int, default 25
Number of symbols to download consecutively before intiating pause.
session : Session, default None
requests.sessions.Session instance to be used
"""

def __init__(self, symbols=None, start=None, end=None, retry_count=3,
pause=0.35, session=None, chunksize=25):
super(IEXDailyReader, self).__init__(symbols=symbols, start=start,
end=end, retry_count=retry_count,
pause=pause, session=session,
chunksize=chunksize)

@property
def url(self):
return 'https://api.iextrading.com/1.0/stock/market/batch'

@property
def endpoint(self):
return "chart"

def _get_params(self, symbol):
chart_range = self._range_string_from_date()
print(chart_range)
if isinstance(symbol, list):
symbolList = ','.join(symbol)
else:
symbolList = symbol
params = {
"symbols": symbolList,
"types": self.endpoint,
"range": chart_range,
}
return params

def _range_string_from_date(self):
delta = relativedelta(self.start, datetime.datetime.now())
if 2 <= (delta.years * -1) <= 5:
return "5y"
elif 1 <= (delta.years * -1) <= 2:
return "2y"
elif 0 <= (delta.years * -1) < 1:
return "1y"
else:
raise ValueError(
"Invalid date specified. Must be within past 5 years.")

def read(self):
"""read data"""
try:
return self._read_one_data(self.url,
self._get_params(self.symbols))
finally:
self.close()

def _read_lines(self, out):
data = out.read()
json_data = json.loads(data)
result = {}
if type(self.symbols) is str:
syms = [self.symbols]
else:
syms = self.symbols
for symbol in syms:
d = json_data.pop(symbol)["chart"]
df = pd.DataFrame(d)
df.set_index("date", inplace=True)
values = ["open", "high", "low", "close", "volume"]
df = df[values]
sstart = self.start.strftime('%Y-%m-%d')
send = self.end.strftime('%Y-%m-%d')
df = df.loc[sstart:send]
result.update({symbol: df})
if len(result) > 1:
return result
return result[self.symbols]
78 changes: 78 additions & 0 deletions pandas_datareader/tests/test_iex_daily.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from datetime import datetime

import pytest

import pandas_datareader.data as web


class TestIEXDaily(object):

@classmethod
def setup_class(cls):
pytest.importorskip("lxml")

@property
def start(self):
return datetime(2015, 2, 9)

@property
def end(self):
return datetime(2017, 5, 24)

def test_iex_bad_symbol(self):
with pytest.raises(Exception):
web.DataReader("BADTICKER", "iex,", self.start, self.end)

def test_iex_bad_symbol_list(self):
with pytest.raises(Exception):
web.DataReader(["AAPL", "BADTICKER"], "iex", self.start, self.end)

def test_daily_invalid_date(self):
start = datetime(2010, 1, 5)
end = datetime(2017, 5, 24)
with pytest.raises(Exception):
web.DataReader(["AAPL", "TSLA"], "iex", start, end)

def test_single_symbol(self):
df = web.DataReader("AAPL", "iex", self.start, self.end)
assert list(df) == ["open", "high", "low", "close", "volume"]
assert len(df) == 578
assert df["volume"][-1] == 19219154

def test_multiple_symbols(self):
syms = ["AAPL", "MSFT", "TSLA"]
df = web.DataReader(syms, "iex", self.start, self.end)
assert sorted(list(df)) == syms
for sym in syms:
assert len(df[sym] == 578)

def test_multiple_symbols_2(self):
syms = ["AAPL", "MSFT", "TSLA"]
good_start = datetime(2017, 2, 9)
good_end = datetime(2017, 5, 24)
df = web.DataReader(syms, "iex", good_start, good_end)
assert isinstance(df, dict)
assert len(df) == 3
assert sorted(list(df)) == syms

a = df["AAPL"]
t = df["TSLA"]

assert len(a) == 73
assert len(t) == 73

expected1 = a.loc["2017-02-09"]
assert expected1["close"] == 132.42
assert expected1["high"] == 132.445

expected2 = a.loc["2017-05-24"]
assert expected2["close"] == 153.34
assert expected2["high"] == 154.17

expected3 = t.loc["2017-02-09"]
assert expected3["close"] == 269.20
assert expected3["high"] == 271.18

expected4 = t.loc["2017-05-24"]
assert expected4["close"] == 310.22
assert expected4["high"] == 311.0

0 comments on commit fd638e6

Please sign in to comment.