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 CHANGED
@@ -3,6 +3,6 @@
3
3
  from .quotes import QuotesClient
4
4
  from .apis import WizzerClient
5
5
 
6
- __version__ = "0.7.0"
6
+ __version__ = "0.8.0"
7
7
 
8
8
  __all__ = ["QuotesClient", "WizzerClient"]
@@ -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
- subscribe_msg = {
83
- "action": "subscribe",
84
- "instruments": list(self.subscribed_instruments)
85
- }
86
- await self.ws.send(json.dumps(subscribe_msg))
87
- logger.info("Re-subscribed to instruments: %s", list(self.subscribed_instruments))
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.debug("Received non-JSON message: %s", message)
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
- message = {"action": "subscribe", "instruments": list(new_instruments)}
135
- await self.ws.send(json.dumps(message))
136
- logger.info("Sent subscription message for instruments: %s", list(new_instruments))
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
- if unsub_set & self.subscribed_instruments:
152
- self.subscribed_instruments.difference_update(unsub_set)
153
- message = {"action": "unsubscribe", "instruments": list(unsub_set)}
154
- await self.ws.send(json.dumps(message))
155
- logger.info("Sent unsubscription message for instruments: %s", list(unsub_set))
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.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wiz_trader
3
- Version: 0.7.0
3
+ Version: 0.8.0
4
4
  Summary: A Python SDK for connecting to the Wizzer.
5
5
  Home-page: https://bitbucket.org/wizzer-tech/quotes_sdk.git
6
6
  Author: Pawan Wagh
@@ -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,,