Skip to content

Commit 9c1da14

Browse files
authored
Adds a param to the stream classes to allow overriding of websocket defaults (#596)
* Update stream classes and README * Fix flake8 warnings * Update Readme wording a little * Fix not none check
1 parent 6700281 commit 9c1da14

File tree

2 files changed

+84
-22
lines changed

2 files changed

+84
-22
lines changed

README.md

Lines changed: 39 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -223,12 +223,18 @@ It provides a much faster way to retrieve the historic data for multiple symbols
223223
Under the hood we use the [aiohttp](https://docs.aiohttp.org/en/stable/) library.<br>
224224
We provide a code sample to get you started with this new approach and it is located [here](examples/historic_async.py).<br>
225225
Follow along the example code to learn more, and to utilize it to your own needs.<br>
226-
### Live Stream Data
227-
There are 2 streams available as described [here](https://alpaca.markets/docs/api-documentation/api-v2/market-data/alpaca-data-api-v2/real-time/).<br>
228-
The free plan is using the `iex` stream, while the paid subscription is using the `sip` stream.<br>
229-
You could subscribe to bars, trades or quotes and trade updates as well.<br>
230-
Under the example folder you could find different code [samples](https://github.com/alpacahq/alpaca-trade-api-python/tree/master/examples/websockets) to achieve different goals. Let's see the basic example<br>
231-
We present a new Streamer class under `alpaca_trade_api.stream` for API V2.
226+
227+
### Live Stream Market Data
228+
There are 2 streams available as described [here](https://alpaca.markets/docs/market-data/#subscription-plans).
229+
230+
The free plan is using the `iex` stream, while the paid subscription is using the `sip` stream.
231+
232+
You can subscribe to bars, trades, quotes, and trade updates for your account as well.
233+
Under the example folder you can find different [code samples](https://github.com/alpacahq/alpaca-trade-api-python/tree/master/examples/websockets)
234+
to achieve different goals.
235+
236+
Here in this basic example, We use the Stream class under `alpaca_trade_api.stream` for API V2 to subscribe to trade
237+
updates for AAPL and quote updates for IBM.
232238
```py
233239
from alpaca_trade_api.stream import Stream
234240

@@ -251,9 +257,36 @@ stream.subscribe_trades(trade_callback, 'AAPL')
251257
stream.subscribe_quotes(quote_callback, 'IBM')
252258

253259
stream.run()
260+
```
261+
262+
#### Websockets Config For Live Data
263+
Under the hood our SDK uses the [Websockets library](https://websockets.readthedocs.io/en/stable/index.html) to handle
264+
our websocket connections. Since different environments can have wildly differing requirements for resources we allow you
265+
to pass your own config options to the websockets lib via the `websocket_params` kwarg found on the Stream class.
254266

267+
ie:
268+
```python
269+
# Initiate Class Instance
270+
stream = Stream(<ALPACA_API_KEY>,
271+
<ALPACA_SECRET_KEY>,
272+
base_url=URL('https://paper-api.alpaca.markets'),
273+
data_feed='iex',
274+
websocket_params = {'ping_interval': 5}, #here we set ping_interval to 5 seconds
275+
)
255276
```
256277

278+
If you're curious [this link to their docs](https://websockets.readthedocs.io/en/stable/reference/client.html#opening-a-connection)
279+
shows the values that websockets uses by default as well as any parameters they allow changing. Additionally, if you
280+
don't specify any we set the following defaults on top of the ones the websockets library uses:
281+
```python
282+
{
283+
"ping_interval": 10,
284+
"ping_timeout": 180,
285+
"max_queue": 1024,
286+
}
287+
```
288+
289+
257290
## Account & Portfolio Management
258291

259292
The HTTP API document is located at https://docs.alpaca.markets/

alpaca_trade_api/stream.py

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from collections import defaultdict
33
import logging
44
import json
5-
from typing import List, Optional
5+
from typing import Dict, List, Optional
66
import msgpack
77
import re
88
import websockets
@@ -30,18 +30,26 @@
3030

3131
log = logging.getLogger(__name__)
3232

33+
# Default Params we pass to the websocket constructors
34+
WEBSOCKET_DEFAULTS = {
35+
"ping_interval": 10,
36+
"ping_timeout": 180,
37+
"max_queue": 1024,
38+
}
39+
3340

3441
def _ensure_coroutine(handler):
3542
if not asyncio.iscoroutinefunction(handler):
3643
raise ValueError('handler must be a coroutine function')
3744

3845

39-
class _DataStream():
46+
class _DataStream:
4047
def __init__(self,
4148
endpoint: str,
4249
key_id: str,
4350
secret_key: str,
44-
raw_data: bool = False) -> None:
51+
raw_data: bool = False,
52+
websocket_params: Optional[Dict] = None) -> None:
4553
self._endpoint = endpoint
4654
self._key_id = key_id
4755
self._secret_key = secret_key
@@ -60,14 +68,16 @@ def __init__(self,
6068
self._name = 'data'
6169
self._should_run = True
6270
self._max_frame_size = 32768
71+
self._websocket_params = websocket_params
72+
73+
if self._websocket_params is None:
74+
self._websocket_params = WEBSOCKET_DEFAULTS
6375

6476
async def _connect(self):
6577
self._ws = await websockets.connect(
6678
self._endpoint,
6779
extra_headers={'Content-Type': 'application/msgpack'},
68-
ping_interval=10,
69-
ping_timeout=180,
70-
max_queue=1024,
80+
**self._websocket_params
7181
)
7282
r = await self._ws.recv()
7383
msg = msgpack.unpackb(r)
@@ -327,12 +337,14 @@ def __init__(self,
327337
secret_key: str,
328338
base_url: URL,
329339
raw_data: bool,
330-
feed: str = 'iex'):
340+
feed: str = 'iex',
341+
websocket_params: Optional[Dict] = None):
331342
base_url = re.sub(r'^http', 'ws', base_url)
332343
super().__init__(endpoint=base_url + '/v2/' + feed,
333344
key_id=key_id,
334345
secret_key=secret_key,
335346
raw_data=raw_data,
347+
websocket_params=websocket_params
336348
)
337349
self._handlers['statuses'] = {}
338350
self._handlers['lulds'] = {}
@@ -453,7 +465,8 @@ def __init__(self,
453465
secret_key: str,
454466
base_url: URL,
455467
raw_data: bool,
456-
exchanges: Optional[List[str]] = None):
468+
exchanges: Optional[List[str]] = None,
469+
websocket_params: Optional[Dict] = None):
457470
self._key_id = key_id
458471
self._secret_key = secret_key
459472
base_url = re.sub(r'^http', 'ws', base_url)
@@ -467,6 +480,7 @@ def __init__(self,
467480
key_id=key_id,
468481
secret_key=secret_key,
469482
raw_data=raw_data,
483+
websocket_params=websocket_params,
470484
)
471485
self._name = 'crypto data'
472486

@@ -476,7 +490,8 @@ def __init__(self,
476490
key_id: str,
477491
secret_key: str,
478492
base_url: URL,
479-
raw_data: bool):
493+
raw_data: bool,
494+
websocket_params: Optional[Dict] = None):
480495
self._key_id = key_id
481496
self._secret_key = secret_key
482497
base_url = re.sub(r'^http', 'ws', base_url)
@@ -485,6 +500,7 @@ def __init__(self,
485500
key_id=key_id,
486501
secret_key=secret_key,
487502
raw_data=raw_data,
503+
websocket_params=websocket_params
488504
)
489505
self._handlers = {
490506
'news': {},
@@ -534,7 +550,8 @@ def __init__(self,
534550
key_id: str,
535551
secret_key: str,
536552
base_url: URL,
537-
raw_data: bool = False):
553+
raw_data: bool = False,
554+
websocket_params: Optional[Dict] = None):
538555
self._key_id = key_id
539556
self._secret_key = secret_key
540557
base_url = re.sub(r'^http', 'ws', base_url)
@@ -546,9 +563,16 @@ def __init__(self,
546563
self._raw_data = raw_data
547564
self._stop_stream_queue = queue.Queue()
548565
self._should_run = True
566+
self._websocket_params = websocket_params
567+
568+
if self._websocket_params is None:
569+
self._websocket_params = WEBSOCKET_DEFAULTS
549570

550571
async def _connect(self):
551-
self._ws = await websockets.connect(self._endpoint)
572+
self._ws = await websockets.connect(
573+
self._endpoint,
574+
**self._websocket_params
575+
)
552576

553577
async def _auth(self):
554578
await self._ws.send(
@@ -675,29 +699,34 @@ def __init__(self,
675699
data_stream_url: URL = None,
676700
data_feed: str = 'iex',
677701
raw_data: bool = False,
678-
crypto_exchanges: Optional[List[str]] = None):
702+
crypto_exchanges: Optional[List[str]] = None,
703+
websocket_params: Optional[Dict] = None):
679704
self._key_id, self._secret_key, _ = get_credentials(key_id, secret_key)
680705
self._base_url = base_url or get_base_url()
681706
self._data_stream_url = data_stream_url or get_data_stream_url()
682707

683708
self._trading_ws = TradingStream(self._key_id,
684709
self._secret_key,
685710
self._base_url,
686-
raw_data)
711+
raw_data,
712+
websocket_params=websocket_params)
687713
self._data_ws = DataStream(self._key_id,
688714
self._secret_key,
689715
self._data_stream_url,
690716
raw_data,
691-
data_feed.lower())
717+
data_feed.lower(),
718+
websocket_params=websocket_params)
692719
self._crypto_ws = CryptoDataStream(self._key_id,
693720
self._secret_key,
694721
self._data_stream_url,
695722
raw_data,
696-
crypto_exchanges)
723+
crypto_exchanges,
724+
websocket_params=websocket_params)
697725
self._news_ws = NewsDataStream(self._key_id,
698726
self._secret_key,
699727
self._data_stream_url,
700-
raw_data)
728+
raw_data,
729+
websocket_params=websocket_params)
701730

702731
def subscribe_trade_updates(self, handler):
703732
self._trading_ws.subscribe_trade_updates(handler)

0 commit comments

Comments
 (0)