Skip to content

Commit db29621

Browse files
authored
Merge pull request #446 from bashtage/rgkimball-IEX
Rgkimball iex
2 parents 7a4abda + e73c0fb commit db29621

File tree

12 files changed

+669
-1
lines changed

12 files changed

+669
-1
lines changed

pandas_datareader/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
from ._version import get_versions
2+
23
from .data import (get_components_yahoo, get_data_famafrench, get_data_google,
34
get_data_yahoo, get_data_enigma, get_data_yahoo_actions,
4-
get_quote_google, get_quote_yahoo, DataReader, Options)
5+
get_quote_google, get_quote_yahoo, get_tops_iex,
6+
get_last_iex, get_markets_iex, get_summary_iex,
7+
get_records_iex, get_recent_iex, get_iex_symbols,
8+
get_iex_book, DataReader, Options)
59

610
__version__ = get_versions()['version']
711
del get_versions
812

913
__all__ = ['__version__', 'get_components_yahoo', 'get_data_enigma',
1014
'get_data_famafrench', 'get_data_google', 'get_data_yahoo',
1115
'get_data_yahoo_actions', 'get_quote_google', 'get_quote_yahoo',
16+
'get_iex_book', 'get_iex_symbols', 'get_last_iex',
17+
'get_markets_iex', 'get_recent_iex', 'get_records_iex',
18+
'get_summary_iex', 'get_tops_iex',
1219
'DataReader', 'Options']

pandas_datareader/base.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,29 @@ def _get_response(self, url, params=None, headers=None):
133133
# Get a new breadcrumb if necessary, in case ours is invalidated
134134
if isinstance(params, list) and 'crumb' in params:
135135
params['crumb'] = self._get_crumb(self.retry_count)
136+
137+
# If our output error function returns True, exit the loop.
138+
if self._output_error(response):
139+
break
140+
136141
if params is not None and len(params) > 0:
137142
url = url + "?" + urlencode(params)
143+
138144
raise RemoteDataError('Unable to read URL: {0}'.format(url))
139145

140146
def _get_crumb(self, *args):
141147
""" To be implemented by subclass """
142148
raise NotImplementedError("Subclass has not implemented method.")
143149

150+
def _output_error(self, out):
151+
"""If necessary, a service can implement an interpreter for any non-200
152+
HTTP responses.
153+
154+
:param out: raw output from an HTTP request
155+
:return: boolean
156+
"""
157+
return False
158+
144159
def _read_lines(self, out):
145160
rs = read_csv(out, index_col=0, parse_dates=True,
146161
na_values=('-', 'null'))[::-1]

pandas_datareader/data.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
from pandas_datareader.google.daily import GoogleDailyReader
1414
from pandas_datareader.google.options import Options as GoogleOptions
1515
from pandas_datareader.google.quotes import GoogleQuotesReader
16+
from pandas_datareader.iex.deep import Deep as IEXDeep
17+
from pandas_datareader.iex.tops import LastReader as IEXLasts
18+
from pandas_datareader.iex.tops import TopsReader as IEXTops
1619
from pandas_datareader.moex import MoexReader
1720
from pandas_datareader.nasdaq_trader import get_nasdaq_symbols
1821
from pandas_datareader.oecd import OECDReader
@@ -29,6 +32,9 @@
2932
'get_data_fred', 'get_data_google', 'get_data_moex',
3033
'get_data_quandl', 'get_data_yahoo', 'get_data_yahoo_actions',
3134
'get_nasdaq_symbols', 'get_quote_google', 'get_quote_yahoo',
35+
'get_tops_iex', 'get_summary_iex', 'get_records_iex',
36+
'get_recent_iex', 'get_markets_iex', 'get_last_iex',
37+
'get_iex_symbols', 'get_iex_book', 'get_dailysummary_iex',
3238
'get_data_stooq', 'DataReader']
3339

3440

@@ -76,6 +82,129 @@ def get_data_stooq(*args, **kwargs):
7682
return StooqDailyReader(*args, **kwargs).read()
7783

7884

85+
def get_tops_iex(*args, **kwargs):
86+
return IEXTops(*args, **kwargs).read()
87+
88+
89+
def get_last_iex(*args, **kwargs):
90+
return IEXLasts(*args, **kwargs).read()
91+
92+
93+
def get_markets_iex(*args, **kwargs):
94+
"""
95+
Returns near-real time volume data across markets segregated by tape
96+
and including a percentage of overall volume during the session
97+
98+
This endpoint does not accept any parameters.
99+
100+
Reference: https://www.iextrading.com/developer/docs/#markets
101+
102+
:return: DataFrame
103+
"""
104+
from pandas_datareader.iex.market import MarketReader
105+
return MarketReader(*args, **kwargs).read()
106+
107+
108+
def get_dailysummary_iex(*args, **kwargs):
109+
"""
110+
Returns a summary of daily market volume statistics. Without parameters,
111+
this will return the most recent trading session by default.
112+
113+
:param start:
114+
A datetime object - the beginning of the date range.
115+
:param end:
116+
A datetime object - the end of the date range.
117+
118+
Reference: https://www.iextrading.com/developer/docs/#historical-daily
119+
120+
:return: DataFrame
121+
"""
122+
from pandas_datareader.iex.stats import DailySummaryReader
123+
return DailySummaryReader(*args, **kwargs).read()
124+
125+
126+
def get_summary_iex(*args, **kwargs):
127+
"""
128+
Returns an aggregated monthly summary of market volume and a variety of
129+
related metrics for trades by lot size, security market cap, and venue.
130+
In the absence of parameters, this will return month-to-date statistics.
131+
For ranges spanning multiple months, this will return one row per month.
132+
133+
:param start:
134+
A datetime object - the beginning of the date range.
135+
:param end:
136+
A datetime object - the end of the date range.
137+
138+
:return: DataFrame
139+
"""
140+
from pandas_datareader.iex.stats import MonthlySummaryReader
141+
return MonthlySummaryReader(*args, **kwargs).read()
142+
143+
144+
def get_records_iex(*args, **kwargs):
145+
"""
146+
Returns the record value, record date, recent value, and 30-day average for
147+
market volume, # of symbols traded, # of routed trades and notional value.
148+
This function accepts no additional parameters.
149+
150+
Reference: https://www.iextrading.com/developer/docs/#records
151+
152+
:return: DataFrame
153+
"""
154+
from pandas_datareader.iex.stats import RecordsReader
155+
return RecordsReader(*args, **kwargs).read()
156+
157+
158+
def get_recent_iex(*args, **kwargs):
159+
"""
160+
Returns market volume and trade routing statistics for recent sessions.
161+
Also reports IEX's relative market share, lit share volume and a boolean
162+
halfday indicator.
163+
164+
Reference: https://www.iextrading.com/developer/docs/#recent
165+
166+
:return: DataFrame
167+
"""
168+
from pandas_datareader.iex.stats import RecentReader
169+
return RecentReader(*args, **kwargs).read()
170+
171+
172+
def get_iex_symbols(*args, **kwargs):
173+
"""
174+
Returns a list of all equity symbols available for trading on IEX. Accepts
175+
no additional parameters.
176+
177+
Reference: https://www.iextrading.com/developer/docs/#symbols
178+
179+
:return: DataFrame
180+
"""
181+
from pandas_datareader.iex.ref import SymbolsReader
182+
return SymbolsReader(*args, **kwargs).read()
183+
184+
185+
def get_iex_book(*args, **kwargs):
186+
"""
187+
Returns an array of dictionaries with depth of book data from IEX for up to
188+
10 securities at a time. Returns a dictionary of the bid and ask books.
189+
190+
:param symbols:
191+
A string or list of strings of valid tickers
192+
:param service:
193+
'book': Live depth of book data
194+
'op-halt-status': Checks to see if the exchange has instituted a halt
195+
'security-event': Denotes individual security related event
196+
'ssr-status': Short Sale Price Test restrictions, per reg 201 of SHO
197+
'system-event': Relays current feed status (i.e. market open)
198+
'trades': Retrieves recent executions, trade size/price and flags
199+
'trade-breaks': Lists execution breaks for the current trading session
200+
'trading-status': Returns status and cause codes for securities
201+
202+
:return: Object
203+
"""
204+
from pandas_datareader.iex.deep import Deep
205+
return Deep(*args, **kwargs).read()
206+
207+
79208
def DataReader(name, data_source=None, start=None, end=None,
80209
retry_count=3, pause=0.001, session=None, access_key=None):
81210
"""
@@ -103,6 +232,8 @@ def DataReader(name, data_source=None, start=None, end=None,
103232
single value given for symbol, represents the pause between retries.
104233
session : Session, default None
105234
requests.sessions.Session instance to be used
235+
access_key : (str, None)
236+
Optional parameter to specify an API key for certain data sources.
106237
107238
Examples
108239
----------
@@ -117,6 +248,13 @@ def DataReader(name, data_source=None, start=None, end=None,
117248
# Data from Google Finance
118249
aapl = DataReader("AAPL", "google")
119250
251+
# Price and volume data from IEX
252+
tops = DataReader(["GS", "AAPL"], "iex-tops")
253+
# Top of book executions from IEX
254+
gs = DataReader("GS", "iex-last")
255+
# Real-time depth of book data from IEX
256+
gs = DataReader("GS", "iex-book")
257+
120258
# Data from FRED
121259
vix = DataReader("VIXCLS", "fred")
122260
@@ -140,6 +278,7 @@ def DataReader(name, data_source=None, start=None, end=None,
140278
return YahooActionReader(symbols=name, start=start, end=end,
141279
retry_count=retry_count, pause=pause,
142280
session=session).read()
281+
143282
elif data_source == "yahoo-dividends":
144283
return YahooDivReader(symbols=name, start=start, end=end,
145284
adjust_price=False, chunksize=25,
@@ -151,6 +290,15 @@ def DataReader(name, data_source=None, start=None, end=None,
151290
chunksize=25,
152291
retry_count=retry_count, pause=pause,
153292
session=session).read()
293+
elif data_source == "iex-tops":
294+
return IEXTops(symbols=name, start=start, end=end,
295+
retry_count=retry_count, pause=pause,
296+
session=session).read()
297+
298+
elif data_source == "iex-last":
299+
return IEXLasts(symbols=name, start=start, end=end,
300+
retry_count=retry_count, pause=pause,
301+
session=session).read()
154302

155303
elif data_source == "bankofcanada":
156304
return BankOfCanadaReader(symbols=name, start=start, end=end,
@@ -162,6 +310,11 @@ def DataReader(name, data_source=None, start=None, end=None,
162310
retry_count=retry_count, pause=pause,
163311
session=session).read()
164312

313+
elif data_source == "iex-book":
314+
return IEXDeep(symbols=name, service="book", start=start, end=end,
315+
retry_count=retry_count, pause=pause,
316+
session=session).read()
317+
165318
elif data_source == "enigma":
166319
return EnigmaReader(dataset_id=name, api_key=access_key).read()
167320

pandas_datareader/exceptions.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""Custom warnings and exceptions"""
2+
3+
4+
class UnstableAPIWarning(Warning):
5+
pass

pandas_datareader/iex/__init__.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import json
2+
3+
import pandas as pd
4+
from pandas.io.common import urlencode
5+
from pandas_datareader.base import _BaseReader
6+
7+
8+
# Data provided for free by IEX
9+
# Data is furnished in compliance with the guidelines promulgated in the IEX
10+
# API terms of service and manual
11+
# See https://iextrading.com/api-exhibit-a/ for additional information
12+
# and conditions of use
13+
14+
15+
class IEX(_BaseReader):
16+
"""
17+
Serves as the base class for all IEX API services.
18+
"""
19+
20+
_format = 'json'
21+
22+
def __init__(self, symbols=None, start=None, end=None, retry_count=3,
23+
pause=0.001, session=None):
24+
super(IEX, self).__init__(symbols=symbols,
25+
start=start, end=end,
26+
retry_count=retry_count,
27+
pause=pause, session=session)
28+
29+
@property
30+
def service(self):
31+
# This property will be overridden by the subclass
32+
raise NotImplementedError("IEX API service not specified.")
33+
34+
@property
35+
def url(self):
36+
qstring = urlencode(self._get_params(self.symbols))
37+
return "https://api.iextrading.com/1.0/{}?{}".format(self.service,
38+
qstring)
39+
40+
def read(self):
41+
df = super(IEX, self).read()
42+
if isinstance(df, pd.DataFrame):
43+
df = df.squeeze()
44+
if not isinstance(df, pd.DataFrame):
45+
df = pd.DataFrame(df)
46+
return df
47+
48+
def _get_params(self, symbols):
49+
p = {}
50+
if isinstance(symbols, list):
51+
p['symbols'] = ','.join(symbols)
52+
elif isinstance(symbols, str):
53+
p['symbols'] = symbols
54+
return p
55+
56+
def _output_error(self, out):
57+
"""If IEX returns a non-200 status code, we need to notify the user of
58+
the error returned.
59+
60+
:param out: Raw HTTP Output
61+
"""
62+
try:
63+
content = json.loads(out.text)
64+
except Exception:
65+
raise TypeError("Failed to interpret response as JSON.")
66+
67+
for key, string in content.items():
68+
e = "IEX Output error encountered: {}".format(string)
69+
if key == 'error':
70+
raise Exception(e)
71+
72+
def _read_lines(self, out):
73+
"""IEX's output does not need anything complex, so we're overriding to
74+
use Pandas' default interpreter
75+
76+
:param out: Raw HTTP Output
77+
:return: DataFrame
78+
"""
79+
80+
# IEX will return a blank line for invalid tickers:
81+
if isinstance(out, list):
82+
out = [x for x in out if x is not None]
83+
return pd.DataFrame(out) if len(out) > 0 else pd.DataFrame()

0 commit comments

Comments
 (0)