forked from alpacahq/alpaca-trade-api-python
-
Notifications
You must be signed in to change notification settings - Fork 1
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 alpacahq#19 from alpacahq/polygon
Polygon initial integration
- Loading branch information
Showing
12 changed files
with
914 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,6 +57,7 @@ jobs: | |
- run: | ||
name: run tests | ||
command: | | ||
python setup.py install | ||
python setup.py test | ||
- save_cache: | ||
|
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,2 @@ | ||
from .rest import REST # noqa | ||
from .stream import Stream # noqa |
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,153 @@ | ||
import pandas as pd | ||
import pprint | ||
|
||
NY = 'America/New_York' | ||
|
||
class Entity(object): | ||
def __init__(self, raw): | ||
self._raw = raw | ||
|
||
def __getattr__(self, key): | ||
if key in self._raw: | ||
val = self._raw[key] | ||
return val | ||
return getattr(super(), key) | ||
|
||
def __repr__(self): | ||
return '{name}({raw})'.format( | ||
name=self.__class__.__name__, | ||
raw=pprint.pformat(self._raw, indent=4), | ||
) | ||
|
||
|
||
class Agg(Entity): | ||
def __getattr__(self, key): | ||
lkey = key.lower() | ||
if key in self._raw: | ||
val = self._raw[key] | ||
if key == 'day': | ||
return pd.Timestamp(val, tz=NY) | ||
elif key in ('timestamp', 'start', 'end'): | ||
return pd.Timestamp(val, tz=NY, unit='ms') | ||
return val | ||
return getattr(super(), key) | ||
|
||
|
||
class Aggs(list): | ||
def __init__(self, raw): | ||
def rename_keys(tick, map): | ||
return { | ||
map[k]: v for k, v in tick.items() | ||
} | ||
|
||
super().__init__([ | ||
Agg(rename_keys(tick, raw['map'])) | ||
for tick in raw['ticks'] | ||
]) | ||
self._raw = raw | ||
|
||
@property | ||
def df(self): | ||
if not hasattr(self, '_df'): | ||
raw = self._raw | ||
size = raw['aggType'] | ||
# polygon doesn't return in ascending order | ||
# Do not rely on df.sort_values() as this library | ||
# may be used with older pandas | ||
df = pd.DataFrame( | ||
sorted(raw['ticks'], key=lambda d: d['d']), | ||
columns=('o', 'h', 'l', 'c', 'v', 'd'), | ||
) | ||
df.columns = [raw['map'][c] for c in df.columns] | ||
if size[0] == 'm': | ||
df.set_index('timestamp', inplace=True) | ||
# astype is necessary to deal with empty result | ||
df.index = pd.to_datetime( | ||
df.index.astype('int64') * 1000000, | ||
utc=True, | ||
).tz_convert(NY) | ||
else: | ||
df.set_index('day', inplace=True) | ||
df.index = pd.to_datetime( | ||
df.index, utc=True, | ||
).tz_convert(NY) | ||
self._df = df | ||
|
||
return self._df | ||
|
||
|
||
class _TradeOrQuote(object): | ||
'''Mixin for Trade and Quote''' | ||
def __getattr__(self, key): | ||
if key in self._raw: | ||
val = self._raw[key] | ||
if key == 'timestamp': | ||
return pd.Timestamp(val, tz=NY, unit='ms') | ||
return val | ||
return getattr(super(), key) | ||
|
||
|
||
class _TradesOrQuotes(object): | ||
'''Mixin for Trades and Quotes''' | ||
|
||
def __init__(self, raw): | ||
def rename_keys(tick, map): | ||
return { | ||
map[k]: v for k, v in tick.items() | ||
} | ||
|
||
unit_class = self.__class__._unit | ||
super().__init__([ | ||
unit_class(rename_keys(tick, raw['map'])) | ||
for tick in raw['ticks'] | ||
]) | ||
self._raw = raw | ||
|
||
@property | ||
def df(self): | ||
if not hasattr(self, '_df'): | ||
raw = self._raw | ||
columns = self.__class__._columns | ||
df = pd.DataFrame( | ||
sorted(raw['ticks'], key=lambda d: d['t']), | ||
columns=columns, | ||
) | ||
df.columns = [raw['map'][c] for c in df.columns] | ||
df.set_index('timestamp', inplace=True) | ||
df.index = pd.to_datetime( | ||
df.index.astype('int64') * 1000000, | ||
utc=True, | ||
).tz_convert(NY) | ||
|
||
self._df = df | ||
return self._df | ||
|
||
|
||
|
||
class Trade(_TradeOrQuote, Entity): | ||
pass | ||
|
||
|
||
class Trades(_TradesOrQuotes, list): | ||
_columns = ('p', 's', 'e', 't', 'c1', 'c2', 'c3', 'c4') | ||
_unit = Trade | ||
|
||
|
||
class Quote(_TradeOrQuote, Entity): | ||
pass | ||
|
||
|
||
class Quotes(_TradesOrQuotes, list): | ||
_columns = ('t', 'c', 'bE', 'aE', 'aP', 'bP', 'bS', 'aS') | ||
_unit = Quote | ||
|
||
|
||
class Exchange(Entity): | ||
pass | ||
|
||
|
||
class SymbolTypeMap(Entity): | ||
pass | ||
|
||
class ConditionMap(Entity): | ||
pass |
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,84 @@ | ||
import requests | ||
from .entity import ( | ||
Aggs, | ||
Trade, Trades, | ||
Quote, Quotes, | ||
Exchange, SymbolTypeMap, ConditionMap, | ||
) | ||
|
||
|
||
class REST(object): | ||
|
||
def __init__(self, api_key): | ||
self._api_key = api_key | ||
self._session = requests.Session() | ||
|
||
def _request(self, method, path, params=None): | ||
url = 'https://api.polygon.io/v1' + path | ||
params = params or {} | ||
params['apiKey'] = self._api_key | ||
resp = self._session.request(method, url, params=params) | ||
resp.raise_for_status() | ||
return resp.json() | ||
|
||
def get(self, path, params=None): | ||
return self._request('GET', path, params=params) | ||
|
||
def exchanges(self): | ||
path = '/meta/exchanges' | ||
return [Exchange(o) for o in self.get(path)] | ||
|
||
def symbol_type_map(self): | ||
path = '/meta/symbol-types' | ||
return SymbolTypeMap(self.get(path)) | ||
|
||
def historic_trades(self, symbol, date, offset=None, limit=None): | ||
path = '/historic/trades/{}/{}'.format(symbol, date) | ||
params = {} | ||
if offset is not None: | ||
params['offset'] = offset | ||
if limit is not None: | ||
params['limit'] = limit | ||
raw = self.get(path, params) | ||
|
||
return Trades(raw) | ||
|
||
def historic_quotes(self, symbol, date, offset=None, limit=None): | ||
path = '/historic/quotes/{}/{}'.format(symbol, date) | ||
params = {} | ||
if offset is not None: | ||
params['offset'] = offset | ||
if limit is not None: | ||
params['limit'] = limit | ||
raw = self.get(path, params) | ||
|
||
return Quotes(raw) | ||
|
||
def historic_agg(self, size, symbol, | ||
_from=None, to=None, limit=None): | ||
path = '/historic/agg/{}/{}'.format(size, symbol) | ||
params = {} | ||
if _from is not None: | ||
params['from'] = _from | ||
if to is not None: | ||
params['to'] = to | ||
if limit is not None: | ||
params['limit'] = limit | ||
raw = self.get(path, params) | ||
|
||
return Aggs(raw) | ||
|
||
def last_trade(self, symbol): | ||
path = '/last/stocks/{}'.format(symbol) | ||
raw = self.get(path) | ||
return Trade(raw['last']) | ||
|
||
def last_quote(self, symbol): | ||
path = '/last_quote/stocks/{}'.format(symbol) | ||
raw = self.get(path) | ||
# TODO status check | ||
return Quote(raw['last']) | ||
|
||
def condition_map(self, ticktype='trades'): | ||
path = '/meta/conditions/{}'.format(ticktype) | ||
return ConditionMap(self.get(path)) |
Oops, something went wrong.