Skip to content

Commit de0fc4f

Browse files
author
Shlomi Kushchi
authored
Merge pull request #355 from alpacahq/enable_raw_data_response
Enable raw data response without Entity wrapping
2 parents 4ed4820 + 3844d7f commit de0fc4f

File tree

7 files changed

+324
-99
lines changed

7 files changed

+324
-99
lines changed

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,13 @@ The steps to execute this are:
345345
* execute your algorithm. it will connect to the servers through the proxy agent allowing you to execute multiple strategies
346346

347347

348+
## Raw Data vs Entity Data
349+
By default the data returned from the api or streamed via StreamConn is wrapped with an Entity object for ease of use.<br>
350+
Some users may prefer working with raw python objects (lists, dicts, ...). <br>You have 2 options to get the raw data:
351+
* Each Entity object as a `_raw` property that extract the raw data from the object.
352+
* If you only want to work with raw data, and avoid casting to Entity (which may take more time, casting back and forth) <br>you could pass `raw_data` argument
353+
to `Rest()` object or the `StreamConn()` object.
354+
348355
## Support and Contribution
349356

350357
For technical issues particular to this module, please report the

alpaca_trade_api/polygon/rest.py

Lines changed: 81 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import dateutil.parser
55
import requests
66
from .entity import (
7-
Aggsv2, Aggsv2Set, Trade, TradesV2, Quote, QuotesV2,
7+
Entity, Aggsv2, Aggsv2Set, Trade, TradesV2, Quote, QuotesV2,
88
Exchange, SymbolTypeMap, ConditionMap, Company, Dividends, Splits,
99
Earnings, Financials, NewsList, Ticker, DailyOpenClose, Symbol
1010
)
@@ -92,9 +92,18 @@ def fix_daily_bar_date(date, timespan):
9292

9393
class REST(object):
9494

95-
def __init__(self, api_key: str, staging: bool = False):
95+
def __init__(self, api_key: str,
96+
staging: bool = False,
97+
raw_data: bool = False
98+
):
99+
"""
100+
:param staging: do we work with the staging server
101+
:param raw_data: should we return api response raw or wrap it with
102+
Entity objects.
103+
"""
96104
self._api_key: str = get_polygon_credentials(api_key)
97105
self._staging: bool = staging
106+
self._use_raw_data: bool = raw_data
98107
self._session = requests.Session()
99108

100109
def _request(self, method: str, path: str, params: dict = None,
@@ -120,11 +129,15 @@ def get(self, path: str, params: dict = None, version: str = 'v1'):
120129

121130
def exchanges(self) -> Exchanges:
122131
path = '/meta/exchanges'
123-
return [Exchange(o) for o in self.get(path)]
132+
resp = self.get(path)
133+
if self._use_raw_data:
134+
return resp
135+
else:
136+
return [self.response_wrapper(o, Exchange) for o in resp]
124137

125138
def symbol_type_map(self) -> SymbolTypeMap:
126139
path = '/meta/symbol-types'
127-
return SymbolTypeMap(self.get(path))
140+
return self.response_wrapper(self.get(path), SymbolTypeMap)
128141

129142
def historic_trades_v2(self,
130143
symbol: str,
@@ -154,9 +167,8 @@ def historic_trades_v2(self,
154167
params['reverse'] = reverse
155168
if limit is not None:
156169
params['limit'] = limit
157-
raw = self.get(path, params, 'v2')
158-
159-
return TradesV2(raw)
170+
resp = self.get(path, params, 'v2')
171+
return self.response_wrapper(resp, TradesV2)
160172

161173
def historic_quotes_v2(self,
162174
symbol: str,
@@ -186,9 +198,8 @@ def historic_quotes_v2(self,
186198
params['reverse'] = reverse
187199
if limit is not None:
188200
params['limit'] = limit
189-
raw = self.get(path, params, 'v2')
190-
191-
return QuotesV2(raw)
201+
resp = self.get(path, params, 'v2')
202+
return self.response_wrapper(resp, QuotesV2)
192203

193204
def historic_agg_v2(self,
194205
symbol: str,
@@ -226,39 +237,39 @@ def historic_agg_v2(self,
226237
params = {'unadjusted': unadjusted}
227238
if limit:
228239
params['limit'] = limit
229-
raw = self.get(path, params, version='v2')
230-
return Aggsv2(raw)
240+
resp = self.get(path, params, version='v2')
241+
return self.response_wrapper(resp, Aggsv2)
231242

232243
def grouped_daily(self, date, unadjusted: bool = False) -> Aggsv2Set:
233244
path = f'/aggs/grouped/locale/us/market/stocks/{date}'
234245
params = {'unadjusted': unadjusted}
235-
raw = self.get(path, params, version='v2')
236-
return Aggsv2Set(raw)
246+
resp = self.get(path, params, version='v2')
247+
return self.response_wrapper(resp, Aggsv2Set)
237248

238249
def daily_open_close(self, symbol: str, date) -> DailyOpenClose:
239250
path = f'/open-close/{symbol}/{date}'
240-
raw = self.get(path)
241-
return DailyOpenClose(raw)
251+
resp = self.get(path)
252+
return self.response_wrapper(resp, DailyOpenClose)
242253

243254
def last_trade(self, symbol: str) -> Trade:
244255
path = '/last/stocks/{}'.format(symbol)
245-
raw = self.get(path)
246-
return Trade(raw['last'])
256+
resp = self.get(path)['last']
257+
return self.response_wrapper(resp, Trade)
247258

248259
def last_quote(self, symbol: str) -> Quote:
249260
path = '/last_quote/stocks/{}'.format(symbol)
250-
raw = self.get(path)
261+
resp = self.get(path)['last']
251262
# TODO status check
252-
return Quote(raw['last'])
263+
return self.response_wrapper(resp, Quote)
253264

254265
def previous_day_bar(self, symbol: str) -> Aggsv2:
255266
path = '/aggs/ticker/{}/prev'.format(symbol)
256-
raw = self.get(path, version='v2')
257-
return Aggsv2(raw)
267+
resp = self.get(path, version='v2')
268+
return self.response_wrapper(resp, Aggsv2)
258269

259270
def condition_map(self, ticktype='trades') -> ConditionMap:
260271
path = '/meta/conditions/{}'.format(ticktype)
261-
return ConditionMap(self.get(path))
272+
return self.response_wrapper(self.get(path), ConditionMap)
262273

263274
def company(self, symbol: str) -> Company:
264275
return self._get_symbol(symbol, 'company', Company)
@@ -275,7 +286,10 @@ def _get_symbol(self, symbol: str, resource: str, entity):
275286
res = self.get(path, params=params)
276287
if isinstance(res, list):
277288
res = {o['symbol']: o for o in res}
278-
retmap = {sym: entity(res[sym]) for sym in symbols if sym in res}
289+
if self._use_raw_data:
290+
retmap = res
291+
else:
292+
retmap = {sym: entity(res[sym]) for sym in symbols if sym in res}
279293
if not multi:
280294
return retmap.get(symbol)
281295
return retmap
@@ -285,7 +299,8 @@ def dividends(self, symbol: str) -> Dividends:
285299

286300
def splits(self, symbol: str) -> Splits:
287301
path = f'/reference/splits/{symbol}'
288-
return Splits(self.get(path, version='v2')['results'])
302+
resp = self.get(path, version='v2')['results']
303+
return self.response_wrapper(resp, Splits)
289304

290305
def earnings(self, symbol: str) -> Earnings:
291306
return self._get_symbol(symbol, 'earnings', Earnings)
@@ -303,26 +318,28 @@ def financials_v2(self, symbol: str,
303318
"type": report_type.name,
304319
"sort": sort.value,
305320
}
306-
return Financials(self.get(path, version='v2',
307-
params=params)['results'])
321+
resp = self.get(path, version='v2', params=params)['results']
322+
return self.response_wrapper(resp, Financials)
308323

309324
def news(self, symbol: str) -> NewsList:
310325
path = '/meta/symbols/{}/news'.format(symbol)
311-
return NewsList(self.get(path))
326+
return self.response_wrapper(self.get(path), NewsList)
312327

313328
def gainers_losers(self, direction: str = "gainers") -> Tickers:
314329
path = '/snapshot/locale/us/markets/stocks/{}'.format(direction)
315-
return [
316-
Ticker(ticker) for ticker in
317-
self.get(path, version='v2')['tickers']
318-
]
330+
resp = self.get(path, version='v2')['tickers']
331+
if self._use_raw_data:
332+
return resp
333+
else:
334+
return [self.response_wrapper(o, Ticker) for o in resp]
319335

320336
def all_tickers(self) -> Tickers:
321337
path = '/snapshot/locale/us/markets/stocks/tickers'
322-
return [
323-
Ticker(ticker) for ticker in
324-
self.get(path, version='v2')['tickers']
325-
]
338+
resp = self.get(path, version='v2')['tickers']
339+
if self._use_raw_data:
340+
return resp
341+
else:
342+
return [self.response_wrapper(o, Ticker) for o in resp]
326343

327344
def symbol_list_paginated(self, page: int = 1,
328345
per_page: int = 50) -> Symbols:
@@ -334,15 +351,34 @@ def symbol_list_paginated(self, page: int = 1,
334351
:return:
335352
"""
336353
path = '/reference/tickers'
337-
return [Symbol(s) for s in self.get(path,
338-
version='v2',
339-
params={
340-
"page": page,
341-
"active": "true",
342-
"perpage": per_page,
343-
"market": "STOCKS"
344-
})['tickers']]
354+
resp = self.get(path,
355+
version='v2',
356+
params={
357+
"page": page,
358+
"active": "true",
359+
"perpage": per_page,
360+
"market": "STOCKS"
361+
})['tickers']
362+
if self._use_raw_data:
363+
return resp
364+
else:
365+
return [self.response_wrapper(o, Symbol) for o in resp]
345366

346367
def snapshot(self, symbol: str) -> Ticker:
347368
path = '/snapshot/locale/us/markets/stocks/tickers/{}'.format(symbol)
348-
return Ticker(self.get(path, version='v2'))
369+
resp = self.get(path, version='v2')
370+
return self.response_wrapper(resp, Ticker)
371+
372+
def response_wrapper(self, obj, entity: Entity):
373+
"""
374+
To allow the user to get raw response from the api, we wrap all
375+
functions with this method, checking if the user has set raw_data
376+
bool. if they didn't, we wrap the response with an Entity object.
377+
:param obj: response from server
378+
:param entity: derivative object of Entity
379+
:return:
380+
"""
381+
if self._use_raw_data:
382+
return obj
383+
else:
384+
return entity(obj)

alpaca_trade_api/polygon/streamconn.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@
1313

1414

1515
class StreamConn(object):
16-
def __init__(self, key_id: str = None):
16+
def __init__(self, key_id: str = None, raw_data: bool = False):
17+
"""
18+
:param raw_data: should we return stream data raw or wrap it with
19+
Entity objects.
20+
"""
1721
self._key_id = get_polygon_credentials(key_id)
1822
self._endpoint: URL = URL(os.environ.get(
1923
'POLYGON_WS_URL',
@@ -28,6 +32,7 @@ def __init__(self, key_id: str = None):
2832
self._retries = 0
2933
self.loop = asyncio.get_event_loop()
3034
self._consume_task = None
35+
self._use_raw_data = raw_data
3136

3237
async def connect(self):
3338
await self._dispatch({'ev': 'status',
@@ -210,8 +215,11 @@ async def _dispatch(self, msg):
210215
if pat.match(channel):
211216
handled_symbols = self._handler_symbols.get(handler)
212217
if handled_symbols is None or msg['sym'] in handled_symbols:
213-
ent = self._cast(channel, msg)
214-
await handler(self, channel, ent)
218+
if self._use_raw_data:
219+
await handler(self, channel, msg)
220+
else:
221+
ent = self._cast(channel, msg)
222+
await handler(self, channel, ent)
215223

216224
def register(self, channel_pat, func, symbols=None):
217225
if not asyncio.iscoroutinefunction(func):

0 commit comments

Comments
 (0)