-
Notifications
You must be signed in to change notification settings - Fork 681
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #465 from addisonlynch/dev
Added IEX Daily Reader (Historical)
- Loading branch information
Showing
4 changed files
with
219 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |