Skip to content

Commit

Permalink
Merge pull request alpacahq#19 from alpacahq/polygon
Browse files Browse the repository at this point in the history
Polygon initial integration
  • Loading branch information
umitanuki authored Jun 22, 2018
2 parents e99ffce + 7b132e8 commit 4791830
Show file tree
Hide file tree
Showing 12 changed files with 914 additions and 0 deletions.
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ jobs:
- run:
name: run tests
command: |
python setup.py install
python setup.py test
- save_cache:
Expand Down
78 changes: 78 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,30 +134,44 @@ Calls `GET /clock` and returns a `Clock` entity.
Calls `GET /calendar` and returns a `Calendar` entity.

### REST.list_quotes(symbols)
\*** The method is being deprecated. Use Polygon API

Calls `GET /quotes` with symbols and returns a list of `Quote` entities. If `symbols` is not a string, it is concatenated with commas.

### REST.get_quote(symbol)
\*** The method is being deprecated. Use Polygon API

Calls `GET /assets/{symbol}/quote` and returns a `Quote` entity.

### REST.list_fundamentals(symbols)
\*** The method is being deprecated. Use Polygon API

Calls `GET /fundamentals` with symbols and returns a list of `Fundamental` entities.
If `symbols` is not a string, it is concatenated with commas.

### REST.get_fundamental(symbol)
\*** The method is being deprecated. Use Polygon API

Calls `GET /assets/{symbol}/fundamental` and returns a `Fundamental` entity.

### REST.list_bars(symbols, timeframe, start_dt=None, end_dt=None, limit=None)
\*** The method is being deprecated. Use Polygon API

Calls `GET /bars` and returns a list of `AssetBars` entities. If `symbols` is
not a string, it is concatenated with commas. `start_dt` and `end_dt` should be
in the ISO8601 string format.

### REST.get_bars(symbol, timeframe, start_dt=None, end_dt=None, limit=None)
\*** The method is being deprecated. Use Polygon API

Calls `GET /assets/{symbol}/bars` with parameters and returns an `AssetBars`
entity. `start_dt` and `end_dt` should be in the ISO8601 string format.

### AssetBars.df
Returns a DataFrame constructed from the Bars response. The property is cached.

---

## StreamConn

The `StreamConn` class provides WebSocket-based event-driven
Expand Down Expand Up @@ -224,3 +238,67 @@ Alpaca's customer support.

New features, as well as bug fixes, by sending pull request is always
welcomed.


---
# Polygon API Service

Alpaca's API key ID can be used to access Polygon API whose document is found [here](https://polygon.io/docs).
This python SDK wraps their API service and seamlessly integrates with Alpaca API.
`alpaca_trade_api.REST.polygon` will be the `REST` object for Polygon.

## polygon/REST
It is initialized through alpaca `REST` object.

### polygon/REST.exchanges()
Returns a list of `Exchange` entity.

### polygon/REST.symbol_type_map()
Returns a `SymbolTypeMap` object.

### polygon/REST.historic_trades(symbol, date, offset=None, limit=None)
Returns a `Trades` which is a list of `Trade` entities.

- `date` is a date string such as '2018-2-2'. The returned quotes are from this day onyl.
- `offset` is an integer in Unix Epoch millisecond as the lower bound filter, inclusive.
- `limit` is an integer for the number of ticks to return. Default and max is 30000.

### polygon/Trades.df
Returns a pandas DataFrame object with the ticks returned by the `historic_trades`.

### polygon/REST.historic_quotes(symbol, date, offset=None, limit=None)
Returns a `Quotes` which is a list of `Quote` entities.

- `date` is a date string such as '2018-2-2'. The returned quotes are from this day only.
- `offset` is an integer in Unix Epoch millisecond as the lower bound filter, inclusive.
- `limit` is an integer for the number of ticks to return. Default and max is 30000.

### polygon/Quotes.df
Returns a pandas DataFrame object with the ticks returned by the `historic_quotes`.

### polygon/REST.historic_agg(size, symbol, _from=None, to=None, limit=None)
Returns an `Aggs` which is a list of `Agg` entities. `Aggs.df` gives you the DataFrame
object.

- `_from` is an Eastern Time timestamp string that filters the result for the lower bound, inclusive.
- `to` is an Eastern Time timestamp string that filters the result for the upper bound, inclusive.
- `limit` is an integer to limit the number of results. 3000 is the default and max value.

Specify the `_from` parameter if you specify the `to` parameter since when `to` is
specified `_from` is assumed to be the beginning of history. Otherwise, when you
use only the `limit` or no parameters, the result is returned from the latest point.

The returned entities have fields relabeled with the longer name instead of shorter ones.
For example, the `o` field is renamed to `open`.

### polygon/Aggs.df
Returns a pandas DataFrame object with the ticks returned by the `hitoric_agg`.

### poylgon/REST.last_trade(symbol)
Returns a `Trade` entity representing the last trade for the symbol.

### polygon/REST.last_quote(symbol)
Returns a `Quote` entity representing the last quote for the symbol.

### polygon/REST.condition_map(ticktype='trades')
Returns a `ConditionMap` entity.
2 changes: 2 additions & 0 deletions alpaca_trade_api/polygon/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .rest import REST # noqa
from .stream import Stream # noqa
153 changes: 153 additions & 0 deletions alpaca_trade_api/polygon/entity.py
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
84 changes: 84 additions & 0 deletions alpaca_trade_api/polygon/rest.py
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))
Loading

0 comments on commit 4791830

Please sign in to comment.