wiz-trader 0.7.0__py3-none-any.whl → 0.8.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.
- wiz_trader/__init__.py +1 -1
- wiz_trader/quotes/client.py +76 -20
- {wiz_trader-0.7.0.dist-info → wiz_trader-0.8.0.dist-info}/METADATA +1 -1
- wiz_trader-0.8.0.dist-info/RECORD +9 -0
- wiz_trader-0.7.0.dist-info/RECORD +0 -9
- {wiz_trader-0.7.0.dist-info → wiz_trader-0.8.0.dist-info}/WHEEL +0 -0
- {wiz_trader-0.7.0.dist-info → wiz_trader-0.8.0.dist-info}/top_level.txt +0 -0
wiz_trader/__init__.py
CHANGED
wiz_trader/quotes/client.py
CHANGED
@@ -3,7 +3,7 @@ import json
|
|
3
3
|
import os
|
4
4
|
import logging
|
5
5
|
import random
|
6
|
-
from typing import Callable, List, Optional
|
6
|
+
from typing import Callable, List, Optional, Any, Iterator
|
7
7
|
|
8
8
|
import websockets
|
9
9
|
from websockets.exceptions import ConnectionClosed
|
@@ -33,7 +33,9 @@ class QuotesClient:
|
|
33
33
|
self,
|
34
34
|
base_url: Optional[str] = None,
|
35
35
|
token: Optional[str] = None,
|
36
|
-
log_level: str = "error" # default only errors
|
36
|
+
log_level: str = "error", # default only errors
|
37
|
+
max_message_size: int = 10 * 1024 * 1024, # 10MB default max size
|
38
|
+
batch_size: int = 20 # Max number of instruments to subscribe to at once
|
37
39
|
):
|
38
40
|
# Configure logger based on log_level.
|
39
41
|
valid_levels = {"error": logging.ERROR, "info": logging.INFO, "debug": logging.DEBUG}
|
@@ -42,6 +44,8 @@ class QuotesClient:
|
|
42
44
|
logger.setLevel(valid_levels[log_level])
|
43
45
|
|
44
46
|
self.log_level = log_level
|
47
|
+
self.max_message_size = max_message_size
|
48
|
+
self.batch_size = batch_size
|
45
49
|
# System env vars take precedence over .env
|
46
50
|
self.base_url = base_url or os.environ.get("WZ__QUOTES_BASE_URL")
|
47
51
|
self.token = token or os.environ.get("WZ__TOKEN")
|
@@ -63,6 +67,20 @@ class QuotesClient:
|
|
63
67
|
|
64
68
|
logger.debug("Initialized QuotesClient with URL: %s", self.url)
|
65
69
|
|
70
|
+
def _chunk_list(self, data: List[Any], chunk_size: int) -> Iterator[List[Any]]:
|
71
|
+
"""
|
72
|
+
Split a list into smaller chunks.
|
73
|
+
|
74
|
+
Args:
|
75
|
+
data (List[Any]): The list to split.
|
76
|
+
chunk_size (int): Maximum size of each chunk.
|
77
|
+
|
78
|
+
Returns:
|
79
|
+
Iterator[List[Any]]: Iterator of list chunks.
|
80
|
+
"""
|
81
|
+
for i in range(0, len(data), chunk_size):
|
82
|
+
yield data[i:i + chunk_size]
|
83
|
+
|
66
84
|
async def connect(self) -> None:
|
67
85
|
"""
|
68
86
|
Continuously connect to the quotes server and process incoming messages.
|
@@ -73,18 +91,23 @@ class QuotesClient:
|
|
73
91
|
while True:
|
74
92
|
try:
|
75
93
|
logger.info("Connecting to %s ...", self.url)
|
76
|
-
async with websockets.connect(self.url) as websocket:
|
94
|
+
async with websockets.connect(self.url, max_size=self.max_message_size) as websocket:
|
77
95
|
self.ws = websocket
|
78
96
|
logger.info("Connected to the quotes server.")
|
79
97
|
|
80
98
|
# On reconnection, re-subscribe if needed.
|
81
99
|
if self.subscribed_instruments:
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
100
|
+
# Split into batches to avoid message size issues
|
101
|
+
instruments_list = list(self.subscribed_instruments)
|
102
|
+
for batch in self._chunk_list(instruments_list, self.batch_size):
|
103
|
+
subscribe_msg = {
|
104
|
+
"action": "subscribe",
|
105
|
+
"instruments": batch
|
106
|
+
}
|
107
|
+
await self.ws.send(json.dumps(subscribe_msg))
|
108
|
+
logger.info("Re-subscribed to batch of %d instruments", len(batch))
|
109
|
+
# Small delay between batches to avoid overwhelming the server
|
110
|
+
await asyncio.sleep(0.1)
|
88
111
|
|
89
112
|
# Reset backoff after a successful connection.
|
90
113
|
backoff = self._backoff_base
|
@@ -110,19 +133,29 @@ class QuotesClient:
|
|
110
133
|
try:
|
111
134
|
async for message in self.ws: # type: ignore
|
112
135
|
try:
|
136
|
+
# Log message size for debugging large message issues
|
137
|
+
if self.log_level == "debug" and isinstance(message, str):
|
138
|
+
message_size = len(message.encode("utf-8"))
|
139
|
+
if message_size > 1024 * 1024: # Over 1MB
|
140
|
+
logger.debug("Received large message: %d bytes", message_size)
|
141
|
+
|
113
142
|
tick = json.loads(message)
|
114
143
|
if self.on_tick:
|
115
144
|
self.on_tick(tick)
|
116
145
|
else:
|
117
146
|
logger.debug("Received tick (no on_tick callback set): %s", tick)
|
118
|
-
except json.JSONDecodeError:
|
119
|
-
logger.
|
147
|
+
except json.JSONDecodeError as e:
|
148
|
+
logger.error("Received invalid JSON message: %s...", message[:100] if isinstance(message, str) else str(message)[:100])
|
149
|
+
logger.error("JSON decode error: %s", str(e))
|
120
150
|
except ConnectionClosed as e:
|
121
151
|
logger.info("Connection closed during message handling: %s", e)
|
152
|
+
except Exception as e:
|
153
|
+
logger.error("Error processing message: %s", str(e), exc_info=True)
|
122
154
|
|
123
155
|
async def subscribe(self, instruments: List[str]) -> None:
|
124
156
|
"""
|
125
157
|
Subscribe to a list of instruments and update the subscription list.
|
158
|
+
Splits large subscription requests into batches to avoid message size issues.
|
126
159
|
|
127
160
|
Args:
|
128
161
|
instruments (List[str]): List of instrument identifiers.
|
@@ -131,32 +164,55 @@ class QuotesClient:
|
|
131
164
|
new_instruments = set(instruments) - self.subscribed_instruments
|
132
165
|
if new_instruments:
|
133
166
|
self.subscribed_instruments.update(new_instruments)
|
134
|
-
|
135
|
-
|
136
|
-
|
167
|
+
|
168
|
+
# Split into batches to avoid message size issues
|
169
|
+
new_instruments_list = list(new_instruments)
|
170
|
+
for batch in self._chunk_list(new_instruments_list, self.batch_size):
|
171
|
+
logger.info("Subscribing to batch of %d instruments", len(batch))
|
172
|
+
message = {"action": "subscribe", "instruments": batch}
|
173
|
+
await self.ws.send(json.dumps(message))
|
174
|
+
# Small delay between batches to avoid overwhelming the server
|
175
|
+
await asyncio.sleep(0.1)
|
176
|
+
|
177
|
+
logger.info("Completed subscription for %d new instruments", len(new_instruments))
|
137
178
|
else:
|
138
179
|
logger.info("Instruments already subscribed: %s", instruments)
|
139
180
|
else:
|
140
181
|
logger.info("Cannot subscribe: WebSocket is not connected.")
|
182
|
+
# Still update the subscription list so we can subscribe when connected
|
183
|
+
self.subscribed_instruments.update(set(instruments))
|
141
184
|
|
142
185
|
async def unsubscribe(self, instruments: List[str]) -> None:
|
143
186
|
"""
|
144
187
|
Unsubscribe from a list of instruments and update the subscription list.
|
188
|
+
Splits large unsubscription requests into batches to avoid message size issues.
|
145
189
|
|
146
190
|
Args:
|
147
191
|
instruments (List[str]): List of instrument identifiers.
|
148
192
|
"""
|
149
193
|
if self.ws and self.ws.state == State.OPEN:
|
150
194
|
unsub_set = set(instruments)
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
195
|
+
to_unsubscribe = unsub_set & self.subscribed_instruments
|
196
|
+
|
197
|
+
if to_unsubscribe:
|
198
|
+
self.subscribed_instruments.difference_update(to_unsubscribe)
|
199
|
+
|
200
|
+
# Split into batches to avoid message size issues
|
201
|
+
unsub_list = list(to_unsubscribe)
|
202
|
+
for batch in self._chunk_list(unsub_list, self.batch_size):
|
203
|
+
logger.info("Unsubscribing from batch of %d instruments", len(batch))
|
204
|
+
message = {"action": "unsubscribe", "instruments": batch}
|
205
|
+
await self.ws.send(json.dumps(message))
|
206
|
+
# Small delay between batches to avoid overwhelming the server
|
207
|
+
await asyncio.sleep(0.1)
|
208
|
+
|
209
|
+
logger.info("Completed unsubscription for %d instruments", len(to_unsubscribe))
|
156
210
|
else:
|
157
211
|
logger.info("No matching instruments found in current subscription.")
|
158
212
|
else:
|
159
213
|
logger.info("Cannot unsubscribe: WebSocket is not connected.")
|
214
|
+
# Still update the subscription list
|
215
|
+
self.subscribed_instruments.difference_update(set(instruments))
|
160
216
|
|
161
217
|
async def close(self) -> None:
|
162
218
|
"""
|
@@ -164,4 +220,4 @@ class QuotesClient:
|
|
164
220
|
"""
|
165
221
|
if self.ws:
|
166
222
|
await self.ws.close()
|
167
|
-
logger.info("WebSocket connection closed.")
|
223
|
+
logger.info("WebSocket connection closed.")
|
@@ -0,0 +1,9 @@
|
|
1
|
+
wiz_trader/__init__.py,sha256=eQVmMb93DelACJ7AZWh6NeaqKEgI8WpfXVpRn7ssrRs,181
|
2
|
+
wiz_trader/apis/__init__.py,sha256=ItWKMOl4omiW0g2f-M7WRW3v-dss_ULd9vYnFyIIT9o,132
|
3
|
+
wiz_trader/apis/client.py,sha256=rq6FEA5DDCGXc4axSSzOdVvet1NWlW8L843GMp0qXQA,20562
|
4
|
+
wiz_trader/quotes/__init__.py,sha256=RF9g9CNP6bVWlmCh_ad8krm3-EWOIuVfLp0-H9fAeEM,108
|
5
|
+
wiz_trader/quotes/client.py,sha256=jDUbFMadDfAOOVXKbNWGuEgEZWzaOFY-aXRzeNRr1Gc,8954
|
6
|
+
wiz_trader-0.8.0.dist-info/METADATA,sha256=CwTLDUVCzNGJvegr9xwXzLZTWO8I1JDAGZjkh5X3Wso,4281
|
7
|
+
wiz_trader-0.8.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
8
|
+
wiz_trader-0.8.0.dist-info/top_level.txt,sha256=lnYS_g8LlA6ryKYnvY8xIQ6K2K-xzOsd-99AWgnW6VY,11
|
9
|
+
wiz_trader-0.8.0.dist-info/RECORD,,
|
@@ -1,9 +0,0 @@
|
|
1
|
-
wiz_trader/__init__.py,sha256=V4DgwVGNtcnqYZhVe7pww5-IHtYqge5CQXqkaS81mfo,181
|
2
|
-
wiz_trader/apis/__init__.py,sha256=ItWKMOl4omiW0g2f-M7WRW3v-dss_ULd9vYnFyIIT9o,132
|
3
|
-
wiz_trader/apis/client.py,sha256=rq6FEA5DDCGXc4axSSzOdVvet1NWlW8L843GMp0qXQA,20562
|
4
|
-
wiz_trader/quotes/__init__.py,sha256=RF9g9CNP6bVWlmCh_ad8krm3-EWOIuVfLp0-H9fAeEM,108
|
5
|
-
wiz_trader/quotes/client.py,sha256=fUHTMGDauGF9cjsFsVAzoOwqSgD555_TLoNqmnFhLdQ,6203
|
6
|
-
wiz_trader-0.7.0.dist-info/METADATA,sha256=kqDpwsUfiECcbxhVKPUEuSedAu50L-OOPX2M4JAW12o,4281
|
7
|
-
wiz_trader-0.7.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
8
|
-
wiz_trader-0.7.0.dist-info/top_level.txt,sha256=lnYS_g8LlA6ryKYnvY8xIQ6K2K-xzOsd-99AWgnW6VY,11
|
9
|
-
wiz_trader-0.7.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|