alpaca-py-nopandas 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- alpaca/__init__.py +2 -0
- alpaca/broker/__init__.py +8 -0
- alpaca/broker/client.py +2360 -0
- alpaca/broker/enums.py +528 -0
- alpaca/broker/models/__init__.py +7 -0
- alpaca/broker/models/accounts.py +347 -0
- alpaca/broker/models/cip.py +265 -0
- alpaca/broker/models/documents.py +159 -0
- alpaca/broker/models/funding.py +114 -0
- alpaca/broker/models/journals.py +71 -0
- alpaca/broker/models/rebalancing.py +80 -0
- alpaca/broker/models/trading.py +13 -0
- alpaca/broker/requests.py +1135 -0
- alpaca/common/__init__.py +6 -0
- alpaca/common/constants.py +13 -0
- alpaca/common/enums.py +64 -0
- alpaca/common/exceptions.py +47 -0
- alpaca/common/models.py +21 -0
- alpaca/common/requests.py +82 -0
- alpaca/common/rest.py +438 -0
- alpaca/common/types.py +7 -0
- alpaca/common/utils.py +89 -0
- alpaca/data/__init__.py +5 -0
- alpaca/data/enums.py +184 -0
- alpaca/data/historical/__init__.py +13 -0
- alpaca/data/historical/corporate_actions.py +76 -0
- alpaca/data/historical/crypto.py +299 -0
- alpaca/data/historical/news.py +63 -0
- alpaca/data/historical/option.py +230 -0
- alpaca/data/historical/screener.py +72 -0
- alpaca/data/historical/stock.py +226 -0
- alpaca/data/historical/utils.py +30 -0
- alpaca/data/live/__init__.py +11 -0
- alpaca/data/live/crypto.py +168 -0
- alpaca/data/live/news.py +62 -0
- alpaca/data/live/option.py +88 -0
- alpaca/data/live/stock.py +199 -0
- alpaca/data/live/websocket.py +390 -0
- alpaca/data/mappings.py +84 -0
- alpaca/data/models/__init__.py +7 -0
- alpaca/data/models/bars.py +83 -0
- alpaca/data/models/base.py +45 -0
- alpaca/data/models/corporate_actions.py +309 -0
- alpaca/data/models/news.py +90 -0
- alpaca/data/models/orderbooks.py +59 -0
- alpaca/data/models/quotes.py +78 -0
- alpaca/data/models/screener.py +68 -0
- alpaca/data/models/snapshots.py +132 -0
- alpaca/data/models/trades.py +204 -0
- alpaca/data/requests.py +580 -0
- alpaca/data/timeframe.py +148 -0
- alpaca/py.typed +0 -0
- alpaca/trading/__init__.py +5 -0
- alpaca/trading/client.py +784 -0
- alpaca/trading/enums.py +412 -0
- alpaca/trading/models.py +697 -0
- alpaca/trading/requests.py +604 -0
- alpaca/trading/stream.py +225 -0
- alpaca_py_nopandas-0.1.0.dist-info/LICENSE +201 -0
- alpaca_py_nopandas-0.1.0.dist-info/METADATA +299 -0
- alpaca_py_nopandas-0.1.0.dist-info/RECORD +62 -0
- alpaca_py_nopandas-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,390 @@
|
|
1
|
+
import asyncio
|
2
|
+
import logging
|
3
|
+
import queue
|
4
|
+
from collections import defaultdict
|
5
|
+
from typing import Callable, Dict, List, Optional, Tuple, Union
|
6
|
+
|
7
|
+
import msgpack
|
8
|
+
import websockets
|
9
|
+
from pydantic import BaseModel
|
10
|
+
from websockets.legacy import client as websockets_legacy
|
11
|
+
|
12
|
+
from alpaca import __version__
|
13
|
+
from alpaca.common.types import RawData
|
14
|
+
from alpaca.data.models import (
|
15
|
+
Bar,
|
16
|
+
News,
|
17
|
+
Orderbook,
|
18
|
+
Quote,
|
19
|
+
Trade,
|
20
|
+
TradeCancel,
|
21
|
+
TradeCorrection,
|
22
|
+
TradingStatus,
|
23
|
+
)
|
24
|
+
|
25
|
+
log = logging.getLogger(__name__)
|
26
|
+
|
27
|
+
|
28
|
+
class DataStream:
|
29
|
+
"""
|
30
|
+
A base class for extracting out common functionality for data websockets
|
31
|
+
"""
|
32
|
+
|
33
|
+
def __init__(
|
34
|
+
self,
|
35
|
+
endpoint: str,
|
36
|
+
api_key: str,
|
37
|
+
secret_key: str,
|
38
|
+
raw_data: bool = False,
|
39
|
+
websocket_params: Optional[Dict] = None,
|
40
|
+
) -> None:
|
41
|
+
"""Creates a new DataStream instance.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
endpoint (str): The websocket endpoint to connect to
|
45
|
+
api_key (str): Alpaca API key.
|
46
|
+
secret_key (str): Alpaca API secret key.
|
47
|
+
raw_data (bool, optional): Whether to return raw API data or parsed data. Defaults to False.
|
48
|
+
websocket_params (Optional[Dict], optional): Any websocket connection configuration parameters. Defaults to None.
|
49
|
+
"""
|
50
|
+
self._endpoint = endpoint
|
51
|
+
self._api_key = api_key
|
52
|
+
self._secret_key = secret_key
|
53
|
+
self._ws = None
|
54
|
+
self._running = False
|
55
|
+
self._loop = None
|
56
|
+
self._raw_data = raw_data
|
57
|
+
self._stop_stream_queue = queue.Queue()
|
58
|
+
self._handlers = {
|
59
|
+
"trades": {},
|
60
|
+
"quotes": {},
|
61
|
+
"orderbooks": {},
|
62
|
+
"bars": {},
|
63
|
+
"updatedBars": {},
|
64
|
+
"dailyBars": {},
|
65
|
+
"statuses": {},
|
66
|
+
"lulds": {},
|
67
|
+
"news": {},
|
68
|
+
"corrections": {},
|
69
|
+
"cancelErrors": {},
|
70
|
+
}
|
71
|
+
self._name = "data"
|
72
|
+
self._should_run = True
|
73
|
+
self._max_frame_size = 32768
|
74
|
+
|
75
|
+
self._websocket_params = {
|
76
|
+
"ping_interval": 10,
|
77
|
+
"ping_timeout": 180,
|
78
|
+
"max_queue": 1024,
|
79
|
+
}
|
80
|
+
|
81
|
+
if websocket_params:
|
82
|
+
self._websocket_params = websocket_params
|
83
|
+
|
84
|
+
async def _connect(self) -> None:
|
85
|
+
"""Attempts to connect to the websocket endpoint.
|
86
|
+
If the connection attempt fails a value error is thrown.
|
87
|
+
|
88
|
+
Raises:
|
89
|
+
ValueError: Raised if there is an unsuccessful connection
|
90
|
+
"""
|
91
|
+
|
92
|
+
extra_headers = {
|
93
|
+
"Content-Type": "application/msgpack",
|
94
|
+
"User-Agent": "APCA-PY/" + __version__,
|
95
|
+
}
|
96
|
+
|
97
|
+
log.info(f"connecting to {self._endpoint}")
|
98
|
+
self._ws = await websockets_legacy.connect(
|
99
|
+
self._endpoint,
|
100
|
+
extra_headers=extra_headers,
|
101
|
+
**self._websocket_params,
|
102
|
+
)
|
103
|
+
r = await self._ws.recv()
|
104
|
+
msg = msgpack.unpackb(r)
|
105
|
+
if msg[0]["T"] != "success" or msg[0]["msg"] != "connected":
|
106
|
+
raise ValueError("connected message not received")
|
107
|
+
|
108
|
+
async def _auth(self) -> None:
|
109
|
+
"""Authenticates with API keys after a successful connection is established.
|
110
|
+
|
111
|
+
Raises:
|
112
|
+
ValueError: Raised if authentication is unsuccessful
|
113
|
+
"""
|
114
|
+
await self._ws.send(
|
115
|
+
msgpack.packb(
|
116
|
+
{
|
117
|
+
"action": "auth",
|
118
|
+
"key": self._api_key,
|
119
|
+
"secret": self._secret_key,
|
120
|
+
}
|
121
|
+
)
|
122
|
+
)
|
123
|
+
r = await self._ws.recv()
|
124
|
+
msg = msgpack.unpackb(r)
|
125
|
+
if msg[0]["T"] == "error":
|
126
|
+
raise ValueError(msg[0].get("msg", "auth failed"))
|
127
|
+
if msg[0]["T"] != "success" or msg[0]["msg"] != "authenticated":
|
128
|
+
raise ValueError("failed to authenticate")
|
129
|
+
|
130
|
+
async def _start_ws(self) -> None:
|
131
|
+
"""Starts up the websocket connection. Attempts to connect to wss endpoint
|
132
|
+
and then authenticates with API keys.
|
133
|
+
"""
|
134
|
+
await self._connect()
|
135
|
+
await self._auth()
|
136
|
+
log.info(f"connected to {self._endpoint}")
|
137
|
+
|
138
|
+
async def close(self) -> None:
|
139
|
+
"""Closes the websocket connection."""
|
140
|
+
if self._ws:
|
141
|
+
await self._ws.close()
|
142
|
+
self._ws = None
|
143
|
+
self._running = False
|
144
|
+
|
145
|
+
async def stop_ws(self) -> None:
|
146
|
+
"""Signals websocket connection should close by adding a closing message to the stop_stream_queue"""
|
147
|
+
self._should_run = False
|
148
|
+
if self._stop_stream_queue.empty():
|
149
|
+
self._stop_stream_queue.put_nowait({"should_stop": True})
|
150
|
+
await asyncio.sleep(0)
|
151
|
+
|
152
|
+
async def _consume(self) -> None:
|
153
|
+
"""Distributes data from websocket connection to appropriate callbacks"""
|
154
|
+
while True:
|
155
|
+
if not self._stop_stream_queue.empty():
|
156
|
+
self._stop_stream_queue.get(timeout=1)
|
157
|
+
await self.close()
|
158
|
+
break
|
159
|
+
else:
|
160
|
+
try:
|
161
|
+
r = await asyncio.wait_for(self._ws.recv(), 5)
|
162
|
+
msgs = msgpack.unpackb(r)
|
163
|
+
for msg in msgs:
|
164
|
+
await self._dispatch(msg)
|
165
|
+
except asyncio.TimeoutError:
|
166
|
+
# ws.recv is hanging when no data is received. by using
|
167
|
+
# wait_for we break when no data is received, allowing us
|
168
|
+
# to break the loop when needed
|
169
|
+
pass
|
170
|
+
|
171
|
+
def _cast(self, msg: Dict) -> Union[BaseModel, RawData]:
|
172
|
+
"""Parses data from websocket message if raw_data is False, otherwise
|
173
|
+
returns the raw websocket message.
|
174
|
+
|
175
|
+
Args:
|
176
|
+
msg (Dict): The message containing market data
|
177
|
+
|
178
|
+
Returns:
|
179
|
+
Union[BaseModel, RawData]: The raw or parsed message
|
180
|
+
"""
|
181
|
+
if self._raw_data:
|
182
|
+
return msg
|
183
|
+
msg_type = msg.get("T")
|
184
|
+
if "t" in msg:
|
185
|
+
msg["t"] = msg["t"].to_datetime()
|
186
|
+
if msg_type == "n":
|
187
|
+
msg["created_at"] = msg["created_at"].to_datetime()
|
188
|
+
msg["updated_at"] = msg["updated_at"].to_datetime()
|
189
|
+
return News(msg)
|
190
|
+
if "S" not in msg:
|
191
|
+
return msg
|
192
|
+
if msg_type == "t":
|
193
|
+
return Trade(msg["S"], msg)
|
194
|
+
if msg_type == "q":
|
195
|
+
return Quote(msg["S"], msg)
|
196
|
+
if msg_type == "o":
|
197
|
+
return Orderbook(msg["S"], msg)
|
198
|
+
if msg_type in ("b", "u", "d"):
|
199
|
+
return Bar(msg["S"], msg)
|
200
|
+
if msg_type == "s":
|
201
|
+
return TradingStatus(msg["S"], msg)
|
202
|
+
if msg_type == "c":
|
203
|
+
return TradeCorrection(msg["S"], msg)
|
204
|
+
if msg_type == "x":
|
205
|
+
return TradeCancel(msg["S"], msg)
|
206
|
+
return msg
|
207
|
+
|
208
|
+
async def _dispatch(self, msg: Dict) -> None:
|
209
|
+
"""Distributes the message from websocket connection to the appropriate handler.
|
210
|
+
|
211
|
+
Args:
|
212
|
+
msg (Dict): The message from the websocket connection
|
213
|
+
"""
|
214
|
+
msg_type = msg.get("T")
|
215
|
+
if msg_type == "subscription":
|
216
|
+
sub = [f"{k}: {msg.get(k, [])}" for k in self._handlers if msg.get(k)]
|
217
|
+
log.info(f'subscribed to {", ".join(sub)}')
|
218
|
+
return
|
219
|
+
|
220
|
+
if msg_type == "error":
|
221
|
+
log.error(f'error: {msg.get("msg")} ({msg.get("code")})')
|
222
|
+
return
|
223
|
+
|
224
|
+
if msg_type == "n":
|
225
|
+
symbols = msg.get("symbols", "*")
|
226
|
+
star_handler_called = False
|
227
|
+
handlers_to_call = []
|
228
|
+
news = self._cast(msg)
|
229
|
+
for symbol in set(symbols):
|
230
|
+
if symbol in self._handlers["news"]:
|
231
|
+
handler = self._handlers["news"].get(symbol)
|
232
|
+
elif not star_handler_called:
|
233
|
+
handler = self._handlers["news"].get("*")
|
234
|
+
star_handler_called = True
|
235
|
+
else:
|
236
|
+
handler = None
|
237
|
+
if handler:
|
238
|
+
handlers_to_call.append(handler(news))
|
239
|
+
if handlers_to_call:
|
240
|
+
await asyncio.gather(*handlers_to_call)
|
241
|
+
return
|
242
|
+
|
243
|
+
channel_types = {
|
244
|
+
"t": "trades",
|
245
|
+
"q": "quotes",
|
246
|
+
"o": "orderbooks",
|
247
|
+
"b": "bars",
|
248
|
+
"u": "updatedBars",
|
249
|
+
"d": "dailyBars",
|
250
|
+
"s": "statuses",
|
251
|
+
"l": "lulds",
|
252
|
+
"n": "news",
|
253
|
+
"c": "corrections",
|
254
|
+
"x": "cancelErrors",
|
255
|
+
}
|
256
|
+
channel = channel_types.get(msg_type)
|
257
|
+
if not channel:
|
258
|
+
return
|
259
|
+
symbol = msg.get("S")
|
260
|
+
handler = self._handlers[channel].get(symbol, self._handlers[channel].get("*"))
|
261
|
+
if handler:
|
262
|
+
await handler(self._cast(msg))
|
263
|
+
|
264
|
+
def _subscribe(
|
265
|
+
self, handler: Callable, symbols: Tuple[str], handlers: Dict
|
266
|
+
) -> None:
|
267
|
+
"""Subscribes a coroutine callback function to receive data for a tuple of symbols
|
268
|
+
|
269
|
+
Args:
|
270
|
+
handler (Callable): The coroutine callback function to receive data
|
271
|
+
symbols (Tuple[str]): The tuple containing the symbols to be subscribed to
|
272
|
+
handlers (Dict): The dictionary of coroutine callback functions keyed by symbol
|
273
|
+
"""
|
274
|
+
self._ensure_coroutine(handler)
|
275
|
+
for symbol in symbols:
|
276
|
+
handlers[symbol] = handler
|
277
|
+
if self._running:
|
278
|
+
asyncio.run_coroutine_threadsafe(
|
279
|
+
self._send_subscribe_msg(), self._loop
|
280
|
+
).result()
|
281
|
+
|
282
|
+
async def _send_subscribe_msg(self) -> None:
|
283
|
+
msg = defaultdict(list)
|
284
|
+
for k, v in self._handlers.items():
|
285
|
+
if k not in ("cancelErrors", "corrections") and v:
|
286
|
+
for s in v.keys():
|
287
|
+
msg[k].append(s)
|
288
|
+
msg["action"] = "subscribe"
|
289
|
+
bs = msgpack.packb(msg)
|
290
|
+
frames = (
|
291
|
+
bs[i : i + self._max_frame_size]
|
292
|
+
for i in range(0, len(bs), self._max_frame_size)
|
293
|
+
)
|
294
|
+
await self._ws.send(frames)
|
295
|
+
|
296
|
+
def _unsubscribe(self, channel: str, symbols: List[str]) -> None:
|
297
|
+
if self._running:
|
298
|
+
asyncio.run_coroutine_threadsafe(
|
299
|
+
self._send_unsubscribe_msg(channel, symbols), self._loop
|
300
|
+
).result()
|
301
|
+
for symbol in symbols:
|
302
|
+
del self._handlers[channel][symbol]
|
303
|
+
|
304
|
+
async def _send_unsubscribe_msg(self, channel: str, symbols: List[str]) -> None:
|
305
|
+
if symbols:
|
306
|
+
await self._ws.send(
|
307
|
+
msgpack.packb(
|
308
|
+
{
|
309
|
+
"action": "unsubscribe",
|
310
|
+
channel: symbols,
|
311
|
+
}
|
312
|
+
)
|
313
|
+
)
|
314
|
+
|
315
|
+
async def _run_forever(self) -> None:
|
316
|
+
"""Starts event loop for receiving data from websocket connection and handles
|
317
|
+
distributing messages
|
318
|
+
"""
|
319
|
+
self._loop = asyncio.get_running_loop()
|
320
|
+
# do not start the websocket connection until we subscribe to something
|
321
|
+
while not any(
|
322
|
+
v
|
323
|
+
for k, v in self._handlers.items()
|
324
|
+
if k not in ("cancelErrors", "corrections")
|
325
|
+
):
|
326
|
+
if not self._stop_stream_queue.empty():
|
327
|
+
# the ws was signaled to stop before starting the loop so
|
328
|
+
# we break
|
329
|
+
self._stop_stream_queue.get(timeout=1)
|
330
|
+
return
|
331
|
+
await asyncio.sleep(0)
|
332
|
+
log.info(f"started {self._name} stream")
|
333
|
+
self._should_run = True
|
334
|
+
self._running = False
|
335
|
+
while True:
|
336
|
+
try:
|
337
|
+
if not self._should_run:
|
338
|
+
# when signaling to stop, this is how we break run_forever
|
339
|
+
log.info("{} stream stopped".format(self._name))
|
340
|
+
return
|
341
|
+
if not self._running:
|
342
|
+
log.info("starting {} websocket connection".format(self._name))
|
343
|
+
await self._start_ws()
|
344
|
+
await self._send_subscribe_msg()
|
345
|
+
self._running = True
|
346
|
+
await self._consume()
|
347
|
+
except websockets.WebSocketException as wse:
|
348
|
+
await self.close()
|
349
|
+
self._running = False
|
350
|
+
log.warning("data websocket error, restarting connection: " + str(wse))
|
351
|
+
except ValueError as ve:
|
352
|
+
if "insufficient subscription" in str(ve):
|
353
|
+
await self.close()
|
354
|
+
self._running = False
|
355
|
+
log.exception(f"error during websocket communication: {str(ve)}")
|
356
|
+
return
|
357
|
+
log.exception(f"error during websocket communication: {str(ve)}")
|
358
|
+
except Exception as e:
|
359
|
+
log.exception(f"error during websocket communication: {str(e)}")
|
360
|
+
finally:
|
361
|
+
await asyncio.sleep(0)
|
362
|
+
|
363
|
+
def run(self) -> None:
|
364
|
+
"""Starts up the websocket connection's event loop"""
|
365
|
+
try:
|
366
|
+
asyncio.run(self._run_forever())
|
367
|
+
except KeyboardInterrupt:
|
368
|
+
print("keyboard interrupt, bye")
|
369
|
+
pass
|
370
|
+
finally:
|
371
|
+
self.stop()
|
372
|
+
|
373
|
+
def stop(self) -> None:
|
374
|
+
"""Stops the websocket connection."""
|
375
|
+
if self._loop.is_running():
|
376
|
+
asyncio.run_coroutine_threadsafe(self.stop_ws(), self._loop).result(
|
377
|
+
timeout=5
|
378
|
+
)
|
379
|
+
|
380
|
+
def _ensure_coroutine(self, handler: Callable) -> None:
|
381
|
+
"""Checks if a method is an asyncio coroutine method
|
382
|
+
|
383
|
+
Args:
|
384
|
+
handler (Callable): A method to be checked for coroutineness
|
385
|
+
|
386
|
+
Raises:
|
387
|
+
ValueError: Raised if the input method is not a coroutine
|
388
|
+
"""
|
389
|
+
if not asyncio.iscoroutinefunction(handler):
|
390
|
+
raise ValueError("handler must be a coroutine function")
|
alpaca/data/mappings.py
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
from typing import Dict
|
2
|
+
|
3
|
+
BAR_MAPPING: Dict[str, str] = {
|
4
|
+
"t": "timestamp",
|
5
|
+
"o": "open",
|
6
|
+
"h": "high",
|
7
|
+
"l": "low",
|
8
|
+
"c": "close",
|
9
|
+
"v": "volume",
|
10
|
+
"n": "trade_count",
|
11
|
+
"vw": "vwap",
|
12
|
+
}
|
13
|
+
|
14
|
+
QUOTE_MAPPING: Dict[str, str] = {
|
15
|
+
"t": "timestamp",
|
16
|
+
"ax": "ask_exchange",
|
17
|
+
"ap": "ask_price",
|
18
|
+
"as": "ask_size",
|
19
|
+
"bx": "bid_exchange",
|
20
|
+
"bp": "bid_price",
|
21
|
+
"bs": "bid_size",
|
22
|
+
"c": "conditions",
|
23
|
+
"z": "tape",
|
24
|
+
}
|
25
|
+
|
26
|
+
TRADE_MAPPING: Dict[str, str] = {
|
27
|
+
"t": "timestamp",
|
28
|
+
"p": "price",
|
29
|
+
"s": "size",
|
30
|
+
"x": "exchange",
|
31
|
+
"i": "id",
|
32
|
+
"c": "conditions",
|
33
|
+
"z": "tape",
|
34
|
+
}
|
35
|
+
|
36
|
+
SNAPSHOT_MAPPING: Dict[str, str] = {
|
37
|
+
"latestTrade": "latest_trade",
|
38
|
+
"latestQuote": "latest_quote",
|
39
|
+
"minuteBar": "minute_bar",
|
40
|
+
"dailyBar": "daily_bar",
|
41
|
+
"prevDailyBar": "previous_daily_bar",
|
42
|
+
"impliedVolatility": "implied_volatility",
|
43
|
+
"greeks": "greeks",
|
44
|
+
}
|
45
|
+
|
46
|
+
ORDERBOOK_MAPPING: Dict[str, str] = {
|
47
|
+
"t": "timestamp",
|
48
|
+
"b": "bids",
|
49
|
+
"a": "asks",
|
50
|
+
"r": "reset",
|
51
|
+
}
|
52
|
+
|
53
|
+
TRADING_STATUS_MAPPING: Dict[str, str] = {
|
54
|
+
"t": "timestamp",
|
55
|
+
"sc": "status_code",
|
56
|
+
"sm": "status_message",
|
57
|
+
"rc": "reason_code",
|
58
|
+
"rm": "reason_message",
|
59
|
+
"z": "tape",
|
60
|
+
}
|
61
|
+
|
62
|
+
TRADE_CANCEL_MAPPING: Dict[str, str] = {
|
63
|
+
"t": "timestamp",
|
64
|
+
"p": "price",
|
65
|
+
"s": "size",
|
66
|
+
"x": "exchange",
|
67
|
+
"i": "id",
|
68
|
+
"a": "action",
|
69
|
+
"z": "tape",
|
70
|
+
}
|
71
|
+
|
72
|
+
TRADE_CORRECTION_MAPPING: Dict[str, str] = {
|
73
|
+
"t": "timestamp",
|
74
|
+
"x": "exchange",
|
75
|
+
"oi": "original_id",
|
76
|
+
"op": "original_price",
|
77
|
+
"os": "original_size",
|
78
|
+
"oc": "original_conditions",
|
79
|
+
"ci": "corrected_id",
|
80
|
+
"cp": "corrected_price",
|
81
|
+
"cs": "corrected_size",
|
82
|
+
"cc": "corrected_conditions",
|
83
|
+
"z": "tape",
|
84
|
+
}
|
@@ -0,0 +1,7 @@
|
|
1
|
+
from alpaca.data.models.bars import *
|
2
|
+
from alpaca.data.models.orderbooks import *
|
3
|
+
from alpaca.data.models.news import *
|
4
|
+
from alpaca.data.models.quotes import *
|
5
|
+
from alpaca.data.models.trades import *
|
6
|
+
from alpaca.data.models.snapshots import *
|
7
|
+
from alpaca.data.models.orderbooks import *
|
@@ -0,0 +1,83 @@
|
|
1
|
+
from datetime import datetime
|
2
|
+
from typing import Dict, List, Optional
|
3
|
+
|
4
|
+
from alpaca.common.models import ValidateBaseModel as BaseModel
|
5
|
+
from alpaca.common.types import RawData
|
6
|
+
from alpaca.data.mappings import BAR_MAPPING
|
7
|
+
from alpaca.data.models.base import BaseDataSet, TimeSeriesMixin
|
8
|
+
|
9
|
+
|
10
|
+
class Bar(BaseModel):
|
11
|
+
"""Represents one bar/candlestick of aggregated trade data over a specified interval.
|
12
|
+
|
13
|
+
Attributes:
|
14
|
+
symbol (str): The ticker identifier for the security whose data forms the bar.
|
15
|
+
timestamp (datetime): The opening timestamp of the bar.
|
16
|
+
open (float): The opening price of the interval.
|
17
|
+
high (float): The high price during the interval.
|
18
|
+
low (float): The low price during the interval.
|
19
|
+
close (float): The closing price of the interval.
|
20
|
+
volume (float): The volume traded over the interval.
|
21
|
+
trade_count (Optional[float]): The number of trades that occurred.
|
22
|
+
vwap (Optional[float]): The volume weighted average price.
|
23
|
+
exchange (Optional[float]): The exchange the bar was formed on.
|
24
|
+
"""
|
25
|
+
|
26
|
+
symbol: str
|
27
|
+
timestamp: datetime
|
28
|
+
open: float
|
29
|
+
high: float
|
30
|
+
low: float
|
31
|
+
close: float
|
32
|
+
volume: float
|
33
|
+
trade_count: Optional[float]
|
34
|
+
vwap: Optional[float]
|
35
|
+
|
36
|
+
def __init__(self, symbol: str, raw_data: RawData) -> None:
|
37
|
+
"""Instantiates a bar
|
38
|
+
|
39
|
+
Args:
|
40
|
+
raw_data (RawData): Raw unparsed bar data from API, contains ohlc and other fields.
|
41
|
+
"""
|
42
|
+
mapped_bar = {}
|
43
|
+
|
44
|
+
if raw_data is not None:
|
45
|
+
mapped_bar = {
|
46
|
+
BAR_MAPPING[key]: val
|
47
|
+
for key, val in raw_data.items()
|
48
|
+
if key in BAR_MAPPING
|
49
|
+
}
|
50
|
+
|
51
|
+
super().__init__(symbol=symbol, **mapped_bar)
|
52
|
+
|
53
|
+
|
54
|
+
class BarSet(BaseDataSet, TimeSeriesMixin):
|
55
|
+
"""A collection of Bars.
|
56
|
+
|
57
|
+
Attributes:
|
58
|
+
data (Dict[str, List[Bar]]): The collection of Bars keyed by symbol.
|
59
|
+
"""
|
60
|
+
|
61
|
+
data: Dict[str, List[Bar]] = {}
|
62
|
+
|
63
|
+
def __init__(
|
64
|
+
self,
|
65
|
+
raw_data: RawData,
|
66
|
+
) -> None:
|
67
|
+
"""A collection of Bars.
|
68
|
+
|
69
|
+
Args:
|
70
|
+
raw_data (RawData): The collection of raw bar data from API keyed by Symbol.
|
71
|
+
"""
|
72
|
+
|
73
|
+
parsed_bars = {}
|
74
|
+
|
75
|
+
raw_bars = raw_data
|
76
|
+
|
77
|
+
if raw_bars is not None:
|
78
|
+
for symbol, bars in raw_bars.items():
|
79
|
+
parsed_bars[symbol] = [
|
80
|
+
Bar(symbol, bar) for bar in bars if bar is not None
|
81
|
+
]
|
82
|
+
|
83
|
+
super().__init__(data=parsed_bars)
|
@@ -0,0 +1,45 @@
|
|
1
|
+
from typing import Any, Dict, List
|
2
|
+
|
3
|
+
from alpaca.common.models import ValidateBaseModel as BaseModel
|
4
|
+
|
5
|
+
|
6
|
+
class TimeSeriesMixin:
|
7
|
+
pass
|
8
|
+
|
9
|
+
|
10
|
+
class BaseDataSet(BaseModel):
|
11
|
+
"""
|
12
|
+
Base class to process data models for trades, bars quotes, and news.
|
13
|
+
"""
|
14
|
+
|
15
|
+
data: Dict[str, List[BaseModel]] = {}
|
16
|
+
|
17
|
+
def __getitem__(self, symbol: str) -> Any:
|
18
|
+
"""Gives dictionary-like access to multi-symbol data
|
19
|
+
|
20
|
+
Args:
|
21
|
+
symbol (str): The ticker identifier for the desired data
|
22
|
+
|
23
|
+
Raises:
|
24
|
+
KeyError: Symbol does not exist for data
|
25
|
+
|
26
|
+
Returns:
|
27
|
+
List[Bar]: The data for the given symbol
|
28
|
+
"""
|
29
|
+
if symbol not in self.data:
|
30
|
+
raise KeyError(f"No key {symbol} was found.")
|
31
|
+
|
32
|
+
return self.data[symbol]
|
33
|
+
|
34
|
+
def dict(self, **kwargs) -> dict:
|
35
|
+
"""
|
36
|
+
Gives dictionary representation of data.
|
37
|
+
|
38
|
+
Returns:
|
39
|
+
dict: The data in dictionary form.
|
40
|
+
"""
|
41
|
+
# converts each data (Bar, Quote, etc) in the symbol specific lists to its dictionary format
|
42
|
+
return {
|
43
|
+
symbol: list(map(lambda d: d.model_dump(), data_list))
|
44
|
+
for symbol, data_list in self.data.items()
|
45
|
+
}
|