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.
Files changed (62) hide show
  1. alpaca/__init__.py +2 -0
  2. alpaca/broker/__init__.py +8 -0
  3. alpaca/broker/client.py +2360 -0
  4. alpaca/broker/enums.py +528 -0
  5. alpaca/broker/models/__init__.py +7 -0
  6. alpaca/broker/models/accounts.py +347 -0
  7. alpaca/broker/models/cip.py +265 -0
  8. alpaca/broker/models/documents.py +159 -0
  9. alpaca/broker/models/funding.py +114 -0
  10. alpaca/broker/models/journals.py +71 -0
  11. alpaca/broker/models/rebalancing.py +80 -0
  12. alpaca/broker/models/trading.py +13 -0
  13. alpaca/broker/requests.py +1135 -0
  14. alpaca/common/__init__.py +6 -0
  15. alpaca/common/constants.py +13 -0
  16. alpaca/common/enums.py +64 -0
  17. alpaca/common/exceptions.py +47 -0
  18. alpaca/common/models.py +21 -0
  19. alpaca/common/requests.py +82 -0
  20. alpaca/common/rest.py +438 -0
  21. alpaca/common/types.py +7 -0
  22. alpaca/common/utils.py +89 -0
  23. alpaca/data/__init__.py +5 -0
  24. alpaca/data/enums.py +184 -0
  25. alpaca/data/historical/__init__.py +13 -0
  26. alpaca/data/historical/corporate_actions.py +76 -0
  27. alpaca/data/historical/crypto.py +299 -0
  28. alpaca/data/historical/news.py +63 -0
  29. alpaca/data/historical/option.py +230 -0
  30. alpaca/data/historical/screener.py +72 -0
  31. alpaca/data/historical/stock.py +226 -0
  32. alpaca/data/historical/utils.py +30 -0
  33. alpaca/data/live/__init__.py +11 -0
  34. alpaca/data/live/crypto.py +168 -0
  35. alpaca/data/live/news.py +62 -0
  36. alpaca/data/live/option.py +88 -0
  37. alpaca/data/live/stock.py +199 -0
  38. alpaca/data/live/websocket.py +390 -0
  39. alpaca/data/mappings.py +84 -0
  40. alpaca/data/models/__init__.py +7 -0
  41. alpaca/data/models/bars.py +83 -0
  42. alpaca/data/models/base.py +45 -0
  43. alpaca/data/models/corporate_actions.py +309 -0
  44. alpaca/data/models/news.py +90 -0
  45. alpaca/data/models/orderbooks.py +59 -0
  46. alpaca/data/models/quotes.py +78 -0
  47. alpaca/data/models/screener.py +68 -0
  48. alpaca/data/models/snapshots.py +132 -0
  49. alpaca/data/models/trades.py +204 -0
  50. alpaca/data/requests.py +580 -0
  51. alpaca/data/timeframe.py +148 -0
  52. alpaca/py.typed +0 -0
  53. alpaca/trading/__init__.py +5 -0
  54. alpaca/trading/client.py +784 -0
  55. alpaca/trading/enums.py +412 -0
  56. alpaca/trading/models.py +697 -0
  57. alpaca/trading/requests.py +604 -0
  58. alpaca/trading/stream.py +225 -0
  59. alpaca_py_nopandas-0.1.0.dist-info/LICENSE +201 -0
  60. alpaca_py_nopandas-0.1.0.dist-info/METADATA +299 -0
  61. alpaca_py_nopandas-0.1.0.dist-info/RECORD +62 -0
  62. 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")
@@ -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
+ }