wiz-trader 0.23.0__tar.gz → 0.25.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wiz_trader
3
- Version: 0.23.0
3
+ Version: 0.25.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
@@ -227,12 +227,22 @@ def on_connect(ws):
227
227
 
228
228
  ### Unsubscribing from Instruments
229
229
 
230
- Similarly, plain call to `unsubscribe`:
230
+ Use the `unsubscribe` method to stop receiving real-time data for specific instruments. This helps manage bandwidth and reduces unnecessary data processing.
231
231
 
232
232
  ```python
233
+ # Unsubscribe from specific instruments
233
234
  ws.unsubscribe(["NSE:SBIN:3045", "NSE:ICICIBANK:4963"])
235
+
236
+ # You can also unsubscribe from a single instrument
237
+ ws.unsubscribe(["NSE:RELIANCE:2885"])
234
238
  ```
235
239
 
240
+ **Key Features:**
241
+ - Immediately stops tick data for the specified instruments
242
+ - Reduces network bandwidth and processing overhead
243
+ - Can be called multiple times for different instrument sets
244
+ - No callback fired for unsubscribed instruments
245
+
236
246
  ### Complete Examples
237
247
 
238
248
  #### Blocking Example
@@ -301,8 +311,13 @@ asyncio.run(main())
301
311
  ts = tick timestamp
302
312
  lastTradedTs = timestamp of the last executed trade in this instrument
303
313
 
314
+ **New Fields Added:**
315
+ - `oi`: Current open interest (for futures and options)
316
+ - `oiDayHigh`: Day's highest open interest value
317
+ - `oiDayLow`: Day's lowest open interest value
318
+
304
319
  ```
305
- {'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}, 'lastTradedTs': '2025-04-21T10:29:33Z', 'ts': '2025-04-21T10:29:46Z'}
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'}
306
321
  ```
307
322
 
308
323
  ## Wizzer Client
@@ -200,12 +200,22 @@ def on_connect(ws):
200
200
 
201
201
  ### Unsubscribing from Instruments
202
202
 
203
- Similarly, plain call to `unsubscribe`:
203
+ Use the `unsubscribe` method to stop receiving real-time data for specific instruments. This helps manage bandwidth and reduces unnecessary data processing.
204
204
 
205
205
  ```python
206
+ # Unsubscribe from specific instruments
206
207
  ws.unsubscribe(["NSE:SBIN:3045", "NSE:ICICIBANK:4963"])
208
+
209
+ # You can also unsubscribe from a single instrument
210
+ ws.unsubscribe(["NSE:RELIANCE:2885"])
207
211
  ```
208
212
 
213
+ **Key Features:**
214
+ - Immediately stops tick data for the specified instruments
215
+ - Reduces network bandwidth and processing overhead
216
+ - Can be called multiple times for different instrument sets
217
+ - No callback fired for unsubscribed instruments
218
+
209
219
  ### Complete Examples
210
220
 
211
221
  #### Blocking Example
@@ -274,8 +284,13 @@ asyncio.run(main())
274
284
  ts = tick timestamp
275
285
  lastTradedTs = timestamp of the last executed trade in this instrument
276
286
 
287
+ **New Fields Added:**
288
+ - `oi`: Current open interest (for futures and options)
289
+ - `oiDayHigh`: Day's highest open interest value
290
+ - `oiDayLow`: Day's lowest open interest value
291
+
277
292
  ```
278
- {'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}, 'lastTradedTs': '2025-04-21T10:29:33Z', 'ts': '2025-04-21T10:29:46Z'}
293
+ {'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'}
279
294
  ```
280
295
 
281
296
  ## Wizzer Client
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "wiz_trader"
7
- version = "0.23.0"
7
+ version = "0.25.0"
8
8
  description = "A Python SDK for connecting to the Wizzer."
9
9
  readme = "README.md"
10
10
  authors = [
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='wiz_trader',
5
- version='0.23.0',
5
+ version='0.25.0',
6
6
  description='A Python SDK for connecting to the Wizzer.',
7
7
  long_description=open('README.md').read() if open('README.md') else "",
8
8
  long_description_content_type='text/markdown',
@@ -3,6 +3,6 @@
3
3
  from .quotes import QuotesClient
4
4
  from .apis import WizzerClient
5
5
 
6
- __version__ = "0.23.0"
6
+ __version__ = "0.25.0"
7
7
 
8
8
  __all__ = ["QuotesClient", "WizzerClient"]
@@ -3,16 +3,11 @@ import json
3
3
  import os
4
4
  import logging
5
5
  import random
6
- from typing import Callable, List, Optional, Any, Iterator, Dict, Set, TypeVar, Protocol, runtime_checkable
6
+ from typing import Callable, List, Optional, Any, Iterator
7
7
 
8
- T = TypeVar('T')
9
-
10
- @runtime_checkable
11
- class WebSocketClientProtocol(Protocol):
12
- async def send(self, message: str) -> None: ...
13
- async def close(self) -> None: ...
14
- @property
15
- def open(self) -> bool: ...
8
+ import websockets
9
+ from websockets.exceptions import ConnectionClosed
10
+ from websockets.protocol import State
16
11
 
17
12
  # Setup module‐level logger with a default handler if none exists.
18
13
  logger = logging.getLogger(__name__)
@@ -44,9 +39,6 @@ class QuotesClient:
44
39
  max_message_size: int = 10 * 1024 * 1024,
45
40
  batch_size: int = 20
46
41
  ):
47
- logger.debug("Initializing QuotesClient with params: base_url=%s, log_level=%s, max_message_size=%d, batch_size=%d",
48
- base_url, log_level, max_message_size, batch_size)
49
-
50
42
  valid_levels = {"error": logging.ERROR, "info": logging.INFO, "debug": logging.DEBUG}
51
43
  if log_level not in valid_levels:
52
44
  raise ValueError(f"log_level must be one of {list(valid_levels.keys())}")
@@ -64,60 +56,66 @@ class QuotesClient:
64
56
  raise ValueError("Base URL must be provided as an argument or in .env (WZ__QUOTES_BASE_URL)")
65
57
 
66
58
  self.url = f"{self.base_url}?token={self.token}"
67
- self.ws: Optional[WebSocketClientProtocol] = None
68
- self.subscribed_instruments: Set[str] = set()
59
+ self.ws: Optional[websockets.WebSocketClientProtocol] = None
60
+ self.subscribed_instruments: set = set()
69
61
  self._running = False
70
- self._background_task: Optional[asyncio.Task[None]] = None
62
+ self._background_task = None
63
+ self._loop: Optional[asyncio.AbstractEventLoop] = None
71
64
 
72
65
  self._backoff_base = 1
73
66
  self._backoff_factor = 2
74
67
  self._backoff_max = 60
75
68
 
76
69
  # Callbacks are plain synchronous functions
77
- self.on_tick: Optional[Callable[[Any, Dict[str, Any]], None]] = None
70
+ self.on_tick: Optional[Callable[[Any, dict], None]] = None
78
71
  self.on_connect: Optional[Callable[[Any], None]] = None
79
72
  self.on_close: Optional[Callable[[Any, Optional[int], Optional[str]], None]] = None
80
73
  self.on_error: Optional[Callable[[Any, Exception], None]] = None
81
74
 
82
- logger.debug("QuotesClient initialized successfully")
75
+ logger.debug("Initialized QuotesClient with URL: %s", self.url)
83
76
 
84
77
  def _chunk_list(self, data: List[Any], chunk_size: int) -> Iterator[List[Any]]:
85
- logger.debug("Chunking list of size %d with chunk_size %d", len(data), chunk_size)
86
- return (data[i:i + chunk_size] for i in range(0, len(data), chunk_size))
78
+ for i in range(0, len(data), chunk_size):
79
+ yield data[i:i + chunk_size]
87
80
 
88
81
  async def _connect_with_backoff(self) -> None:
89
82
  backoff = self._backoff_base
90
- logger.debug("Starting connection with initial backoff: %d", backoff)
91
83
 
92
84
  while self._running:
93
85
  try:
94
- logger.debug("Attempting WebSocket connection to %s", self.url)
95
- # Using string literal for import to avoid type checking issues
96
- ws = await __import__('websockets').connect(self.url, max_size=self.max_message_size)
97
- self.ws = ws
98
- logger.debug("WebSocket connection established successfully")
99
-
100
- if self.on_connect:
101
- logger.debug("Executing on_connect callback")
102
- try:
103
- self.on_connect(self)
104
- except Exception as e:
105
- logger.error("Error in on_connect callback: %s", e, exc_info=True)
106
-
107
- if self.subscribed_instruments:
108
- logger.debug("Re-subscribing to %d instruments", len(self.subscribed_instruments))
109
- for batch in self._chunk_list(list(self.subscribed_instruments), self.batch_size):
110
- msg = {"action": self.ACTION_SUBSCRIBE, "instruments": batch}
111
- if self.ws: # Check if ws is not None
86
+ logger.info("Connecting to %s ...", self.url)
87
+ async with websockets.connect(self.url, max_size=self.max_message_size) as websocket:
88
+ self.ws = websocket
89
+ logger.info("Connected to the quotes server.")
90
+
91
+ # plain sync on_connect
92
+ if self.on_connect:
93
+ try:
94
+ self.on_connect(self)
95
+ except Exception as e:
96
+ logger.error("Error in on_connect callback: %s", e, exc_info=True)
97
+
98
+ # re-subscribe on reconnect
99
+ if self.subscribed_instruments:
100
+ for batch in self._chunk_list(list(self.subscribed_instruments), self.batch_size):
101
+ msg = {"action": self.ACTION_SUBSCRIBE, "instruments": batch}
112
102
  await self.ws.send(json.dumps(msg))
113
103
  logger.info("Re-subscribed to %d instruments", len(batch))
114
104
  await asyncio.sleep(0.1)
115
105
 
116
- backoff = self._backoff_base
117
- await self._handle_messages()
106
+ backoff = self._backoff_base
107
+ await self._handle_messages()
108
+
109
+ except ConnectionClosed as e:
110
+ logger.info("Disconnected: %s", e)
111
+ if self.on_close:
112
+ try:
113
+ self.on_close(self, getattr(e, 'code', None), str(e))
114
+ except Exception as ex:
115
+ logger.error("Error in on_close callback: %s", ex, exc_info=True)
118
116
 
119
117
  except Exception as e:
120
- logger.debug("Connection error occurred: %s", str(e), exc_info=True)
118
+ logger.error("Connection error: %s", e, exc_info=True)
121
119
  if self.on_error:
122
120
  try:
123
121
  self.on_error(self, e)
@@ -125,115 +123,117 @@ class QuotesClient:
125
123
  logger.error("Error in on_error callback: %s", ex, exc_info=True)
126
124
 
127
125
  if not self._running:
128
- logger.debug("Client stopped, breaking reconnection loop")
129
126
  break
130
127
 
131
128
  sleep_time = min(backoff, self._backoff_max)
132
- logger.debug("Calculated reconnection backoff: %s seconds", sleep_time)
129
+ logger.info("Reconnecting in %s seconds...", sleep_time)
133
130
  await asyncio.sleep(sleep_time)
134
131
  backoff = backoff * self._backoff_factor + random.uniform(0, 1)
135
132
 
136
133
  async def _handle_messages(self) -> None:
137
134
  try:
138
- logger.debug("Starting message handling loop")
139
- if self.ws is None:
140
- logger.error("WebSocket connection is None")
141
- return
142
-
143
- # Using string literal for import to avoid type checking issues
144
135
  async for message in self.ws: # type: ignore
136
+ if self.log_level == "debug" and isinstance(message, str):
137
+ size = len(message.encode("utf-8"))
138
+ if size > 1024 * 1024:
139
+ logger.debug("Large message: %d bytes", size)
140
+
145
141
  if isinstance(message, str):
146
- msg_size = len(message.encode("utf-8"))
147
- logger.debug("Received message of size: %d bytes", msg_size)
148
-
149
142
  for chunk in message.strip().split("\n"):
150
143
  if not chunk:
151
144
  continue
152
145
  try:
153
146
  tick = json.loads(chunk)
154
- logger.debug("Successfully parsed JSON message for instrument: %s",
155
- tick.get('instrument', 'unknown'))
156
147
  if self.on_tick:
157
- logger.debug("Executing on_tick callback")
158
148
  self.on_tick(self, tick)
159
149
  except json.JSONDecodeError as e:
160
- logger.debug("Failed to parse JSON message: %s. Content: %s", str(e), chunk[:100])
161
150
  logger.error("JSON parse error: %s", e)
162
151
  else:
163
- logger.debug("Received non-string message of type: %s", type(message).__name__)
164
- logger.warning("Non-string message: %s", type(message).__name__)
152
+ logger.warning("Non-string message: %s", type(message))
153
+ except ConnectionClosed:
154
+ logger.info("Connection closed during message handling")
165
155
  except Exception as e:
166
- logger.debug("Error in message handling: %s", str(e), exc_info=True)
156
+ logger.error("Error processing message: %s", e, exc_info=True)
167
157
  if self.on_error:
168
158
  try:
169
159
  self.on_error(self, e)
170
160
  except Exception:
171
161
  pass
172
162
 
163
+ # -- Async core methods (for internal use) --
164
+
173
165
  async def _subscribe_async(self, instruments: List[str]) -> None:
174
- logger.debug("Processing async subscription request for %d instruments", len(instruments))
175
- if self.ws and self.ws.open:
166
+ if self.ws and self.ws.state == State.OPEN:
176
167
  new = set(instruments) - self.subscribed_instruments
177
168
  if new:
178
- logger.debug("Found %d new instruments to subscribe", len(new))
179
169
  self.subscribed_instruments |= new
180
170
  for batch in self._chunk_list(list(new), self.batch_size):
181
- logger.debug("Sending subscription request for batch of %d instruments", len(batch))
171
+ logger.info("Subscribing to %d instruments", len(batch))
182
172
  await self.ws.send(json.dumps({
183
173
  "action": self.ACTION_SUBSCRIBE,
184
174
  "instruments": batch
185
175
  }))
186
176
  await asyncio.sleep(0.1)
187
177
  else:
188
- logger.debug("WebSocket not ready, queueing %d instruments for later subscription", len(instruments))
189
178
  self.subscribed_instruments |= set(instruments)
190
179
 
191
180
  async def _unsubscribe_async(self, instruments: List[str]) -> None:
192
- logger.debug("Processing async unsubscription request for %d instruments", len(instruments))
193
- if self.ws and self.ws.open:
181
+ if self.ws and self.ws.state == State.OPEN:
194
182
  to_remove = set(instruments) & self.subscribed_instruments
195
183
  if to_remove:
196
- logger.debug("Found %d instruments to unsubscribe", len(to_remove))
184
+ self.subscribed_instruments -= to_remove
197
185
  for batch in self._chunk_list(list(to_remove), self.batch_size):
198
- logger.debug("Sending unsubscription request for batch of %d instruments", len(batch))
186
+ logger.info("Unsubscribing from %d instruments", len(batch))
199
187
  await self.ws.send(json.dumps({
200
188
  "action": self.ACTION_UNSUBSCRIBE,
201
189
  "instruments": batch
202
190
  }))
203
191
  await asyncio.sleep(0.1)
204
- logger.debug("Removed %d instruments from subscription set", len(to_remove))
205
- self.subscribed_instruments -= to_remove
206
192
  else:
207
- logger.debug("WebSocket not ready, removing %d instruments from queue", len(instruments))
208
193
  self.subscribed_instruments -= set(instruments)
209
194
 
195
+ # -- Public wrappers for plain callback users --
196
+
210
197
  def subscribe(self, instruments: List[str]) -> None:
211
- logger.debug("Scheduling subscription for %d instruments", len(instruments))
212
- try:
213
- loop = asyncio.get_event_loop()
214
- except RuntimeError:
215
- loop = asyncio.new_event_loop()
216
- asyncio.set_event_loop(loop)
217
- loop.create_task(self._subscribe_async(instruments))
198
+ """
199
+ Schedule subscribe onto the client’s event loop.
200
+ """
201
+ if self._loop:
202
+ asyncio.run_coroutine_threadsafe(
203
+ self._subscribe_async(instruments),
204
+ self._loop
205
+ )
206
+ else:
207
+ self.subscribed_instruments |= set(instruments)
218
208
 
219
209
  def unsubscribe(self, instruments: List[str]) -> None:
220
- logger.debug("Scheduling unsubscription for %d instruments", len(instruments))
221
- try:
222
- loop = asyncio.get_event_loop()
223
- except RuntimeError:
224
- loop = asyncio.new_event_loop()
225
- asyncio.set_event_loop(loop)
226
- loop.create_task(self._unsubscribe_async(instruments))
210
+ """
211
+ Schedule unsubscribe onto the client’s event loop.
212
+ """
213
+ if self._loop:
214
+ asyncio.run_coroutine_threadsafe(
215
+ self._unsubscribe_async(instruments),
216
+ self._loop
217
+ )
218
+ else:
219
+ self.subscribed_instruments -= set(instruments)
220
+
221
+ def unsubscribe_all(self) -> None:
222
+ """
223
+ Unsubscribe from all currently subscribed instruments.
224
+ """
225
+ if self.subscribed_instruments:
226
+ self.unsubscribe(list(self.subscribed_instruments))
227
227
 
228
228
  async def close(self) -> None:
229
- logger.debug("Initiating WebSocket connection closure")
229
+ """
230
+ Close the WebSocket connection.
231
+ """
230
232
  self._running = False
231
233
  if self.ws:
232
- logger.debug("Closing active WebSocket connection")
233
234
  await self.ws.close()
234
235
  logger.info("WebSocket closed.")
235
236
  if self._background_task and not self._background_task.done():
236
- logger.debug("Cancelling background connection task")
237
237
  self._background_task.cancel()
238
238
  try:
239
239
  await self._background_task
@@ -241,13 +241,16 @@ class QuotesClient:
241
241
  pass
242
242
 
243
243
  def connect(self) -> None:
244
- logger.debug("Starting blocking connect operation")
244
+ """
245
+ Blocking connect (runs the internal asyncio loop until stop()).
246
+ """
245
247
  self._running = True
246
248
  try:
247
249
  loop = asyncio.get_event_loop()
248
250
  except RuntimeError:
249
251
  loop = asyncio.new_event_loop()
250
252
  asyncio.set_event_loop(loop)
253
+ self._loop = loop
251
254
 
252
255
  try:
253
256
  loop.run_until_complete(self._connect_with_backoff())
@@ -262,15 +265,20 @@ class QuotesClient:
262
265
  pass
263
266
 
264
267
  def connect_async(self) -> None:
265
- logger.debug("Starting non-blocking async connect operation")
268
+ """
269
+ Non-blocking connect: starts the background task.
270
+ """
266
271
  if self._running:
267
272
  logger.warning("Client already running.")
268
273
  return
269
274
  self._running = True
270
275
  loop = asyncio.get_event_loop()
276
+ self._loop = loop
271
277
  self._background_task = loop.create_task(self._connect_with_backoff())
272
278
 
273
279
  def stop(self) -> None:
274
- logger.debug("Stop signal received")
280
+ """
281
+ Signal the client to stop and close.
282
+ """
275
283
  self._running = False
276
284
  logger.info("Client stopping; will close soon.")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: wiz_trader
3
- Version: 0.23.0
3
+ Version: 0.25.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
@@ -227,12 +227,22 @@ def on_connect(ws):
227
227
 
228
228
  ### Unsubscribing from Instruments
229
229
 
230
- Similarly, plain call to `unsubscribe`:
230
+ Use the `unsubscribe` method to stop receiving real-time data for specific instruments. This helps manage bandwidth and reduces unnecessary data processing.
231
231
 
232
232
  ```python
233
+ # Unsubscribe from specific instruments
233
234
  ws.unsubscribe(["NSE:SBIN:3045", "NSE:ICICIBANK:4963"])
235
+
236
+ # You can also unsubscribe from a single instrument
237
+ ws.unsubscribe(["NSE:RELIANCE:2885"])
234
238
  ```
235
239
 
240
+ **Key Features:**
241
+ - Immediately stops tick data for the specified instruments
242
+ - Reduces network bandwidth and processing overhead
243
+ - Can be called multiple times for different instrument sets
244
+ - No callback fired for unsubscribed instruments
245
+
236
246
  ### Complete Examples
237
247
 
238
248
  #### Blocking Example
@@ -301,8 +311,13 @@ asyncio.run(main())
301
311
  ts = tick timestamp
302
312
  lastTradedTs = timestamp of the last executed trade in this instrument
303
313
 
314
+ **New Fields Added:**
315
+ - `oi`: Current open interest (for futures and options)
316
+ - `oiDayHigh`: Day's highest open interest value
317
+ - `oiDayLow`: Day's lowest open interest value
318
+
304
319
  ```
305
- {'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}, 'lastTradedTs': '2025-04-21T10:29:33Z', 'ts': '2025-04-21T10:29:46Z'}
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'}
306
321
  ```
307
322
 
308
323
  ## Wizzer Client
File without changes
File without changes