wiz-trader 0.27.0__py3-none-any.whl → 0.28.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.27.0"
6
+ __version__ = "0.28.0"
7
7
 
8
8
  __all__ = ["QuotesClient", "WizzerClient"]
@@ -25,10 +25,26 @@ class QuotesClient:
25
25
  base_url (str): WebSocket URL of the quotes server.
26
26
  token (str): JWT token for authentication.
27
27
  log_level (str): Logging level. Options: "error", "info", "debug".
28
+ on_tick (Callable): Callback for tick messages (type='ticks' or no type field).
29
+ on_stats (Callable): Callback for stats/greeks messages (type='greeks').
30
+ on_connect (Callable): Callback when connection is established.
31
+ on_close (Callable): Callback when connection is closed.
32
+ on_error (Callable): Callback for errors.
33
+
34
+ Message Routing:
35
+ - Messages with type='ticks' are routed to on_tick callback
36
+ - Messages with type='greeks' are routed to on_stats callback
37
+ - Messages without type field are routed to on_tick for backward compatibility
38
+ - Messages are silently dropped if the appropriate handler is not registered
28
39
  """
29
40
 
30
41
  ACTION_SUBSCRIBE = "subscribe"
31
42
  ACTION_UNSUBSCRIBE = "unsubscribe"
43
+
44
+ # Subscription modes
45
+ MODE_GREEKS = "greeks"
46
+ MODE_TICKS = "ticks"
47
+ MODE_FULL = "full"
32
48
 
33
49
  def __init__(
34
50
  self,
@@ -57,6 +73,7 @@ class QuotesClient:
57
73
  self.url = f"{self.base_url}?token={self.token}"
58
74
  self.ws: Optional[websockets.WebSocketClientProtocol] = None
59
75
  self.subscribed_instruments: set = set()
76
+ self.subscription_modes: dict = {} # instrument -> mode mapping
60
77
  self._running = False
61
78
  self._background_task = None
62
79
  self._loop: Optional[asyncio.AbstractEventLoop] = None
@@ -67,6 +84,7 @@ class QuotesClient:
67
84
 
68
85
  # Callbacks are plain synchronous functions
69
86
  self.on_tick: Optional[Callable[[Any, dict], None]] = None
87
+ self.on_stats: Optional[Callable[[Any, dict], None]] = None
70
88
  self.on_connect: Optional[Callable[[Any], None]] = None
71
89
  self.on_close: Optional[Callable[[Any, Optional[int], Optional[str]], None]] = None
72
90
  self.on_error: Optional[Callable[[Any, Exception], None]] = None
@@ -94,13 +112,26 @@ class QuotesClient:
94
112
  except Exception as e:
95
113
  logger.error("Error in on_connect callback: %s", e, exc_info=True)
96
114
 
97
- # re-subscribe on reconnect
115
+ # re-subscribe on reconnect with modes
98
116
  if self.subscribed_instruments:
99
- for batch in self._chunk_list(list(self.subscribed_instruments), self.batch_size):
100
- msg = {"action": self.ACTION_SUBSCRIBE, "instruments": batch}
101
- await self.ws.send(json.dumps(msg))
102
- logger.info("Re-subscribed to %d instruments", len(batch))
103
- await asyncio.sleep(0.1)
117
+ # Group instruments by mode for efficient re-subscription
118
+ mode_groups = {}
119
+ for instrument in self.subscribed_instruments:
120
+ mode = self.subscription_modes.get(instrument, self.MODE_TICKS)
121
+ if mode not in mode_groups:
122
+ mode_groups[mode] = []
123
+ mode_groups[mode].append(instrument)
124
+
125
+ for mode, instruments in mode_groups.items():
126
+ for batch in self._chunk_list(instruments, self.batch_size):
127
+ msg = {
128
+ "action": self.ACTION_SUBSCRIBE,
129
+ "instruments": batch,
130
+ "mode": mode
131
+ }
132
+ await self.ws.send(json.dumps(msg))
133
+ logger.info("Re-subscribed to %d instruments with mode '%s'", len(batch), mode)
134
+ await asyncio.sleep(0.1)
104
135
 
105
136
  backoff = self._backoff_base
106
137
  await self._handle_messages()
@@ -142,9 +173,26 @@ class QuotesClient:
142
173
  if not chunk:
143
174
  continue
144
175
  try:
145
- tick = json.loads(chunk)
146
- if self.on_tick:
147
- self.on_tick(self, tick)
176
+ data = json.loads(chunk)
177
+ # Route based on message type
178
+ message_type = data.get('type')
179
+
180
+ if message_type == 'greeks':
181
+ if self.on_stats:
182
+ self.on_stats(self, data)
183
+ else:
184
+ logger.debug("Received greeks message but no on_stats handler registered")
185
+ elif message_type == 'ticks':
186
+ if self.on_tick:
187
+ self.on_tick(self, data)
188
+ else:
189
+ logger.debug("Received ticks message but no on_tick handler registered")
190
+ else:
191
+ # No type field - send to on_tick for backward compatibility
192
+ if self.on_tick:
193
+ self.on_tick(self, data)
194
+ else:
195
+ logger.debug("Received message without type field and no on_tick handler registered")
148
196
  except json.JSONDecodeError as e:
149
197
  logger.error("JSON parse error: %s", e)
150
198
  else:
@@ -161,26 +209,40 @@ class QuotesClient:
161
209
 
162
210
  # -- Async core methods (for internal use) --
163
211
 
164
- async def _subscribe_async(self, instruments: List[str]) -> None:
212
+ async def _subscribe_async(self, instruments: List[str], mode: str = MODE_TICKS) -> None:
165
213
  if self.ws and self.ws.open:
166
214
  new = set(instruments) - self.subscribed_instruments
167
215
  if new:
168
216
  self.subscribed_instruments |= new
217
+ # Track mode for each instrument
218
+ for instrument in new:
219
+ self.subscription_modes[instrument] = mode
220
+
169
221
  for batch in self._chunk_list(list(new), self.batch_size):
170
- logger.info("Subscribing to %d instruments", len(batch))
171
- await self.ws.send(json.dumps({
222
+ logger.info("Subscribing to %d instruments with mode '%s'", len(batch), mode)
223
+ message = {
172
224
  "action": self.ACTION_SUBSCRIBE,
173
- "instruments": batch
174
- }))
225
+ "instruments": batch,
226
+ "mode": mode
227
+ }
228
+ print(f"Subscribing to {batch} with mode {mode}")
229
+ await self.ws.send(json.dumps(message))
175
230
  await asyncio.sleep(0.1)
176
231
  else:
177
232
  self.subscribed_instruments |= set(instruments)
233
+ # Track mode for each instrument
234
+ for instrument in instruments:
235
+ self.subscription_modes[instrument] = mode
178
236
 
179
237
  async def _unsubscribe_async(self, instruments: List[str]) -> None:
180
238
  if self.ws and self.ws.open:
181
239
  to_remove = set(instruments) & self.subscribed_instruments
182
240
  if to_remove:
183
241
  self.subscribed_instruments -= to_remove
242
+ # Remove mode tracking for unsubscribed instruments
243
+ for instrument in to_remove:
244
+ self.subscription_modes.pop(instrument, None)
245
+
184
246
  for batch in self._chunk_list(list(to_remove), self.batch_size):
185
247
  logger.info("Unsubscribing from %d instruments", len(batch))
186
248
  await self.ws.send(json.dumps({
@@ -190,20 +252,26 @@ class QuotesClient:
190
252
  await asyncio.sleep(0.1)
191
253
  else:
192
254
  self.subscribed_instruments -= set(instruments)
255
+ # Remove mode tracking for unsubscribed instruments
256
+ for instrument in instruments:
257
+ self.subscription_modes.pop(instrument, None)
193
258
 
194
259
  # -- Public wrappers for plain callback users --
195
260
 
196
- def subscribe(self, instruments: List[str]) -> None:
261
+ def subscribe(self, instruments: List[str], mode: str = MODE_TICKS) -> None:
197
262
  """
198
263
  Schedule subscribe onto the client’s event loop.
199
264
  """
200
265
  if self._loop:
201
266
  asyncio.run_coroutine_threadsafe(
202
- self._subscribe_async(instruments),
267
+ self._subscribe_async(instruments, mode),
203
268
  self._loop
204
269
  )
205
270
  else:
206
271
  self.subscribed_instruments |= set(instruments)
272
+ # Track mode for each instrument
273
+ for instrument in instruments:
274
+ self.subscription_modes[instrument] = mode
207
275
 
208
276
  def unsubscribe(self, instruments: List[str]) -> None:
209
277
  """
@@ -216,6 +284,9 @@ class QuotesClient:
216
284
  )
217
285
  else:
218
286
  self.subscribed_instruments -= set(instruments)
287
+ # Remove mode tracking for unsubscribed instruments
288
+ for instrument in instruments:
289
+ self.subscription_modes.pop(instrument, None)
219
290
 
220
291
  def unsubscribe_all(self) -> None:
221
292
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wiz_trader
3
- Version: 0.27.0
3
+ Version: 0.28.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
@@ -186,17 +186,25 @@ client.stop()
186
186
 
187
187
  All callbacks are **plain `def`** functions. Inside them you can call `subscribe(...)`, which under the hood schedules the actual async work—so you never `await` in your callbacks.
188
188
 
189
+ The QuotesClient routes messages based on their `type` field:
190
+ - Messages with `type: 'ticks'` are sent to the `on_tick` callback
191
+ - Messages with `type: 'greeks'` are sent to the `on_stats` callback
192
+ - Messages without a `type` field are sent to `on_tick` for backward compatibility
193
+
189
194
  ```python
190
195
  def on_tick(ws, tick):
191
196
  print("Tick:", tick)
192
197
 
198
+ def on_stats(ws, stats):
199
+ print("Stats:", stats)
200
+ # Stats/Greek messages contain: {'type': 'greeks', 'identifier': '...', 'rho': 0.1, 'theta': 9.8, ...}
201
+
193
202
  def on_connect(ws):
194
203
  print("Connected!")
195
- # fire‑and‑forget subscribe—no await needed
196
- ws.subscribe([
197
- "NSE:SBIN:3045",
198
- "NSE:RELIANCE:2885"
199
- ])
204
+ # Subscribe with different modes
205
+ ws.subscribe(["NSE:SBIN:3045"], mode="ticks") # Will receive tick messages
206
+ ws.subscribe(["NSE:NIFTY25JULFUT:53216"], mode="greeks") # Will receive greek messages
207
+ ws.subscribe(["NSE:RELIANCE:2885"], mode="full") # Will receive both types
200
208
 
201
209
  def on_close(ws, code, reason):
202
210
  print(f"Connection closed [{code}]: {reason}")
@@ -206,6 +214,7 @@ def on_error(ws, error):
206
214
  print("Error:", error)
207
215
 
208
216
  client.on_tick = on_tick
217
+ client.on_stats = on_stats
209
218
  client.on_connect = on_connect
210
219
  client.on_close = on_close
211
220
  client.on_error = on_error
@@ -261,9 +270,13 @@ client = QuotesClient(
261
270
  def on_tick(ws, tick):
262
271
  logging.debug("Tick: %s", tick)
263
272
 
273
+ def on_stats(ws, stats):
274
+ logging.debug("Stats: %s", stats)
275
+
264
276
  def on_connect(ws):
265
277
  logging.info("Connected.")
266
- ws.subscribe(["NSE:SBIN:3045", "NSE:RELIANCE:2885"]) # no await
278
+ ws.subscribe(["NSE:SBIN:3045"], mode="ticks")
279
+ ws.subscribe(["NSE:NIFTY24JUN20100CE:20100"], mode="greeks")
267
280
 
268
281
  def on_close(ws, code, reason):
269
282
  logging.warning("Closed: %s", reason)
@@ -273,6 +286,7 @@ def on_error(ws, error):
273
286
  logging.error("Error: %s", error)
274
287
 
275
288
  client.on_tick = on_tick
289
+ client.on_stats = on_stats
276
290
  client.on_connect = on_connect
277
291
  client.on_close = on_close
278
292
  client.on_error = on_error
@@ -307,17 +321,58 @@ async def main():
307
321
  asyncio.run(main())
308
322
  ```
309
323
 
310
- #### Ticks structure
311
- ts = tick timestamp
312
- lastTradedTs = timestamp of the last executed trade in this instrument
324
+ #### Message Data Structures
313
325
 
314
- **New Fields Added:**
326
+ ##### Ticks structure
327
+ Messages with `type: 'ticks'` contain market data:
328
+ - `ts`: tick timestamp
329
+ - `lastTradedTs`: timestamp of the last executed trade in this instrument
315
330
  - `oi`: Current open interest (for futures and options)
316
331
  - `oiDayHigh`: Day's highest open interest value
317
332
  - `oiDayLow`: Day's lowest open interest value
318
333
 
334
+ ```json
335
+ {
336
+ "type": "ticks",
337
+ "identifier": "NSE:RELIANCE:2885",
338
+ "tradingSymbol": "RELIANCE",
339
+ "exchange": "NSE",
340
+ "segment": "NSECM",
341
+ "exchangeToken": 2885,
342
+ "bids": [{"volume": 1722, "price": 1295.5, "orders": 43}, ...],
343
+ "offers": [{"volume": 0, "price": 0, "orders": 0}, ...],
344
+ "ltp": 1295.5,
345
+ "lastTradedQty": 10,
346
+ "buyQty": 1722,
347
+ "sellQty": 0,
348
+ "volume": 10429964,
349
+ "avgPrice": 1291.46,
350
+ "netChange": 0,
351
+ "ohlc": {"open": 1270, "high": 1300.9, "low": 1267, "close": 1295.5},
352
+ "oi": 1234567,
353
+ "oiDayHigh": 1250000,
354
+ "oiDayLow": 1200000,
355
+ "lastTradedTs": "2025-04-21T10:29:33Z",
356
+ "ts": "2025-04-21T10:29:46Z"
357
+ }
319
358
  ```
320
- {'identifier': 'NSE:RELIANCE:2885', 'tradingSymbol': 'RELIANCE', 'exchange': 'NSE', 'segment': 'NSECM', 'exchangeToken': 2885, 'bids': [{'volume': 1722, 'price': 1295.5, 'orders': 43}, {'volume': 0, 'price': 0, 'orders': 0}, {'volume': 0, 'price': 0, 'orders': 0}, {'volume': 0, 'price': 0, 'orders': 0}, {'volume': 0, 'price': 0, 'orders': 0}], 'offers': [{'volume': 0, 'price': 0, 'orders': 0}, {'volume': 0, 'price': 0, 'orders': 0}, {'volume': 0, 'price': 0, 'orders': 0}, {'volume': 0, 'price': 0, 'orders': 0}, {'volume': 0, 'price': 0, 'orders': 0}], 'ltp': 1295.5, 'lastTradedQty': 10, 'buyQty': 1722, 'sellQty': 0, 'volume': 10429964, 'avgPrice': 1291.46, 'netChange': 0, 'ohlc': {'open': 1270, 'high': 1300.9, 'low': 1267, 'close': 1295.5}, 'oi': 1234567, 'oiDayHigh': 1250000, 'oiDayLow': 1200000, 'lastTradedTs': '2025-04-21T10:29:33Z', 'ts': '2025-04-21T10:29:46Z'}
359
+
360
+ ##### Greeks structure
361
+ Messages with `type: 'greeks'` contain options Greeks data:
362
+
363
+ ```json
364
+ {
365
+ "type": "greeks",
366
+ "identifier": "NSE:NIFTY25JULFUT:53216",
367
+ "tradingSymbol": "NIFTY25JULFUT",
368
+ "iv": 0.0,
369
+ "delta": 0.0,
370
+ "gamma": 0.0,
371
+ "theta": 0.0,
372
+ "vega": 0.0,
373
+ "rho": 0.0,
374
+ "ts": "2025-04-21T10:29:46Z"
375
+ }
321
376
  ```
322
377
 
323
378
  ## Wizzer Client
@@ -0,0 +1,9 @@
1
+ wiz_trader/__init__.py,sha256=cVYj3vesO084MUP2n_72WDH_YJ3pwVh7GJEuhkxHGWA,183
2
+ wiz_trader/apis/__init__.py,sha256=6sUr1nzmplNdld0zryMrQSt0jHT2GhOiFYgKKVHzk8U,133
3
+ wiz_trader/apis/client.py,sha256=VEotcYfPkjmpp2seJtTWyEdppa6q9NecX89iWxnFQf0,63154
4
+ wiz_trader/quotes/__init__.py,sha256=RF9g9CNP6bVWlmCh_ad8krm3-EWOIuVfLp0-H9fAeEM,108
5
+ wiz_trader/quotes/client.py,sha256=VZhWbXns6o46efLx2_buBP-MI_obEd_UWNTL2lDwHGI,14847
6
+ wiz_trader-0.28.0.dist-info/METADATA,sha256=nqXqELIc2_FOiMp_Iv_NUPqvX90YcgIzCw6qmxoQObY,99294
7
+ wiz_trader-0.28.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
+ wiz_trader-0.28.0.dist-info/top_level.txt,sha256=lnYS_g8LlA6ryKYnvY8xIQ6K2K-xzOsd-99AWgnW6VY,11
9
+ wiz_trader-0.28.0.dist-info/RECORD,,
@@ -1,9 +0,0 @@
1
- wiz_trader/__init__.py,sha256=UugQriOgr3WKD8FxmXN8c7SfcZiCRJjptS9X63oF3tg,183
2
- wiz_trader/apis/__init__.py,sha256=6sUr1nzmplNdld0zryMrQSt0jHT2GhOiFYgKKVHzk8U,133
3
- wiz_trader/apis/client.py,sha256=VEotcYfPkjmpp2seJtTWyEdppa6q9NecX89iWxnFQf0,63154
4
- wiz_trader/quotes/__init__.py,sha256=RF9g9CNP6bVWlmCh_ad8krm3-EWOIuVfLp0-H9fAeEM,108
5
- wiz_trader/quotes/client.py,sha256=EkRxCudEq0J6ReP5nBgDzrL1XbtgWhrzpZT27xXBMBU,10891
6
- wiz_trader-0.27.0.dist-info/METADATA,sha256=-At9ukmtGQGYHXuiDM5Yi0x-DjVXvlzNzQYJmnOt7nk,98383
7
- wiz_trader-0.27.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
8
- wiz_trader-0.27.0.dist-info/top_level.txt,sha256=lnYS_g8LlA6ryKYnvY8xIQ6K2K-xzOsd-99AWgnW6VY,11
9
- wiz_trader-0.27.0.dist-info/RECORD,,