-
Notifications
You must be signed in to change notification settings - Fork 683
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for reading quotes and historical from Robinhood
- Loading branch information
Showing
7 changed files
with
258 additions
and
5 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,6 +15,7 @@ Data Readers | |
nasdaq-trader | ||
oecd | ||
quandl | ||
robinhood | ||
stooq | ||
tsp | ||
world-bank |
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,12 @@ | ||
Robinhood | ||
--------- | ||
|
||
.. py:module:: pandas_datareader.robinhood | ||
.. autoclass:: RobinhoodHistoricalReader | ||
:members: | ||
:inherited-members: | ||
|
||
.. autoclass:: RobinhoodQuoteReader | ||
:members: | ||
:inherited-members: |
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
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,155 @@ | ||
import pandas as pd | ||
|
||
from pandas_datareader.base import _BaseReader | ||
|
||
|
||
class RobinhoodQuoteReader(_BaseReader): | ||
""" | ||
Read quotes from Robinhood | ||
Parameters | ||
---------- | ||
symbols : {str, List[str]} | ||
String symbol of like of symbols | ||
start : None | ||
Quotes are near real-time and so this value is ignored | ||
end : None | ||
Quotes are near real-time and so this value is ignored | ||
retry_count : int, default 3 | ||
Number of times to retry query request. | ||
pause : float, default 0.1 | ||
Time, in seconds, of the pause between retries. | ||
session : Session, default None | ||
requests.sessions.Session instance to be used | ||
freq : None | ||
Quotes are near real-time and so this value is ignored | ||
""" | ||
_format = 'json' | ||
|
||
def __init__(self, symbols, start=None, end=None, retry_count=3, pause=.1, | ||
timeout=30, session=None, freq=None): | ||
super(RobinhoodQuoteReader, self).__init__(symbols, start, end, | ||
retry_count, pause, | ||
timeout, session, freq) | ||
if isinstance(self.symbols, str): | ||
self.symbols = [self.symbols] | ||
self._max_symbols = 1630 | ||
self._validate_symbols() | ||
self._json_results = [] | ||
|
||
def _validate_symbols(self): | ||
if len(self.symbols) > self._max_symbols: | ||
raise ValueError('A maximum of {0} symbols are supported ' | ||
'in a single call.'.format(self._max_symbols)) | ||
|
||
def _get_crumb(self, *args): | ||
pass | ||
|
||
@property | ||
def url(self): | ||
"""API URL""" | ||
return 'https://api.robinhood.com/quotes/' | ||
|
||
@property | ||
def params(self): | ||
"""Parameters to use in API calls""" | ||
symbols = ','.join(self.symbols) | ||
return {'symbols': symbols} | ||
|
||
def _process_json(self): | ||
res = pd.DataFrame(self._json_results) | ||
return res.set_index('symbol').T | ||
|
||
def _read_lines(self, out): | ||
if 'next' in out: | ||
self._json_results.extend(out['results']) | ||
return self._read_one_data(out['next']) | ||
self._json_results.extend(out['results']) | ||
return self._process_json() | ||
|
||
|
||
class RobinhoodHistoricalReader(RobinhoodQuoteReader): | ||
""" | ||
Read historical values from Robinhood | ||
Parameters | ||
---------- | ||
symbols : {str, List[str]} | ||
String symbol of like of symbols | ||
start : None | ||
Ignored. See span and interval. | ||
end : None | ||
Ignored. See span and interval. | ||
retry_count : int, default 3 | ||
Number of times to retry query request. | ||
pause : float, default 0.1 | ||
Time, in seconds, of the pause between retries. | ||
session : Session, default None | ||
requests.sessions.Session instance to be used | ||
freq : None | ||
Quotes are near real-time and so this value is ignored | ||
interval : {'day' ,'week', '5minute', '10minute'} | ||
Interval between historical prices | ||
span : {'day', 'week', 'year', '5year'} | ||
Time span relative to now to retrieve. The available spans are a | ||
function of interval. See notes | ||
Notes | ||
----- | ||
Only provides up to 1 year of daily data. | ||
The available spans are a function of interval. | ||
* day: year | ||
* week: 5year | ||
* 5minute: day, week | ||
* 10minute: day, week | ||
""" | ||
_format = 'json' | ||
|
||
def __init__(self, symbols, start=None, end=None, retry_count=3, pause=.1, | ||
timeout=30, session=None, freq=None, interval='day', | ||
span='year'): | ||
super(RobinhoodHistoricalReader, self).__init__(symbols, start, end, | ||
retry_count, pause, | ||
timeout, session, freq) | ||
interval_span = {'day': ['year'], | ||
'week': ['5year'], | ||
'10minute': ['day', 'week'], | ||
'5minute': ['day', 'week']} | ||
if interval not in interval_span: | ||
raise ValueError('Interval must be one of ' | ||
'{0}'.format(', '.join(interval_span.keys()))) | ||
valid_spans = interval_span[interval] | ||
if span not in valid_spans: | ||
raise ValueError('For interval {0}, span must ' | ||
'be in: {1}'.format(interval, valid_spans)) | ||
self.interval = interval | ||
self.span = span | ||
self._max_symbols = 75 | ||
self._validate_symbols() | ||
self._json_results = [] | ||
|
||
@property | ||
def url(self): | ||
"""API URL""" | ||
return 'https://api.robinhood.com/quotes/historicals/' | ||
|
||
@property | ||
def params(self): | ||
"""Parameters to use in API calls""" | ||
symbols = ','.join(self.symbols) | ||
pars = {'symbols': symbols, | ||
'interval': self.interval, | ||
'span': self.span} | ||
|
||
return pars | ||
|
||
def _process_json(self): | ||
df = [] | ||
for sym in self._json_results: | ||
vals = pd.DataFrame(sym['historicals']) | ||
vals['begins_at'] = pd.to_datetime(vals['begins_at']) | ||
vals['symbol'] = sym['symbol'] | ||
df.append(vals.set_index(['symbol', 'begins_at'])) | ||
return pd.concat(df, 0) |
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,48 @@ | ||
import numpy as np | ||
import pandas as pd | ||
import pytest | ||
|
||
from pandas_datareader.robinhood import RobinhoodQuoteReader, \ | ||
RobinhoodHistoricalReader | ||
|
||
syms = ['GOOG', ['GOOG', 'AAPL']] | ||
ids = list(map(str, syms)) | ||
|
||
|
||
@pytest.fixture(params=['GOOG', ['GOOG', 'AAPL']], ids=ids) | ||
def symbols(request): | ||
return request.param | ||
|
||
|
||
def test_robinhood_quote(symbols): | ||
df = RobinhoodQuoteReader(symbols=symbols).read() | ||
assert isinstance(df, pd.DataFrame) | ||
if isinstance(symbols, str): | ||
symbols = [symbols] | ||
assert df.shape[1] == len(symbols) | ||
|
||
|
||
def test_robinhood_quote_too_many(): | ||
syms = np.random.randint(65, 90, size=(10000, 4)).tolist() | ||
syms = list(map(lambda r: ''.join(map(chr, r)), syms)) | ||
syms = list(set(syms)) | ||
with pytest.raises(ValueError): | ||
RobinhoodQuoteReader(symbols=syms) | ||
|
||
|
||
def test_robinhood_historical_too_many(): | ||
syms = np.random.randint(65, 90, size=(10000, 4)).tolist() | ||
syms = list(map(lambda r: ''.join(map(chr, r)), syms)) | ||
syms = list(set(syms)) | ||
with pytest.raises(ValueError): | ||
RobinhoodHistoricalReader(symbols=syms) | ||
with pytest.raises(ValueError): | ||
RobinhoodHistoricalReader(symbols=syms[:76]) | ||
|
||
|
||
def test_robinhood_historical(symbols): | ||
df = RobinhoodHistoricalReader(symbols=symbols).read() | ||
assert isinstance(df, pd.DataFrame) | ||
if isinstance(symbols, str): | ||
symbols = [symbols] | ||
assert df.index.levels[0].shape[0] == len(symbols) |