wiz-trader 0.35.0__py3-none-any.whl → 0.36.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 +112 -6
- {wiz_trader-0.35.0.dist-info → wiz_trader-0.36.0.dist-info}/METADATA +344 -1
- wiz_trader-0.36.0.dist-info/RECORD +9 -0
- wiz_trader-0.35.0.dist-info/RECORD +0 -9
- {wiz_trader-0.35.0.dist-info → wiz_trader-0.36.0.dist-info}/WHEEL +0 -0
- {wiz_trader-0.35.0.dist-info → wiz_trader-0.36.0.dist-info}/top_level.txt +0 -0
wiz_trader/__init__.py
CHANGED
wiz_trader/quotes/client.py
CHANGED
@@ -28,15 +28,38 @@ class QuotesClient:
|
|
28
28
|
log_level (str): Logging level. Options: "error", "info", "debug".
|
29
29
|
on_tick (Callable): Callback for tick messages (type='ticks' or no type field).
|
30
30
|
on_stats (Callable): Callback for stats/greeks messages (type='greeks').
|
31
|
+
on_order (Callable): Callback for order events (type='order').
|
32
|
+
on_trade (Callable): Callback for trade events (type='trade').
|
33
|
+
on_position (Callable): Callback for position events (type='position').
|
34
|
+
on_holding (Callable): Callback for holding events (type='holding').
|
31
35
|
on_connect (Callable): Callback when connection is established.
|
32
36
|
on_close (Callable): Callback when connection is closed.
|
33
37
|
on_error (Callable): Callback for errors.
|
34
38
|
|
35
39
|
Message Routing:
|
36
|
-
-
|
37
|
-
-
|
40
|
+
- Legacy messages with type='ticks' are routed to on_tick callback
|
41
|
+
- Legacy messages with type='greeks' are routed to on_stats callback
|
42
|
+
- Event messages with type='order' are routed to on_order callback
|
43
|
+
- Event messages with type='trade' are routed to on_trade callback
|
44
|
+
- Event messages with type='position' are routed to on_position callback
|
45
|
+
- Event messages with type='holding' are routed to on_holding callback
|
38
46
|
- Messages without type field are routed to on_tick for backward compatibility
|
39
47
|
- Messages are silently dropped if the appropriate handler is not registered
|
48
|
+
|
49
|
+
Message Structure Support:
|
50
|
+
- Legacy Format: {type: "ticks", ...data} or {...data} (no type)
|
51
|
+
- New Event Format: {type: "position", data: {...actualData}} - automatically normalized
|
52
|
+
- All handlers receive flattened message structure with type preserved at root level
|
53
|
+
|
54
|
+
Event Handler Signature:
|
55
|
+
All event handlers follow the same signature: handler(ws: QuotesClient, event_data: dict)
|
56
|
+
|
57
|
+
Example:
|
58
|
+
def on_order_event(ws, order):
|
59
|
+
print(f"Order {order['id']}: {order['status']}")
|
60
|
+
|
61
|
+
client.on_order = on_order_event
|
62
|
+
client.connect()
|
40
63
|
"""
|
41
64
|
|
42
65
|
ACTION_SUBSCRIBE = "subscribe"
|
@@ -89,6 +112,12 @@ class QuotesClient:
|
|
89
112
|
self.on_connect: Optional[Callable[[Any], None]] = None
|
90
113
|
self.on_close: Optional[Callable[[Any, Optional[int], Optional[str]], None]] = None
|
91
114
|
self.on_error: Optional[Callable[[Any, Exception], None]] = None
|
115
|
+
|
116
|
+
# Event callbacks for account events
|
117
|
+
self.on_order: Optional[Callable[[Any, dict], None]] = None
|
118
|
+
self.on_trade: Optional[Callable[[Any, dict], None]] = None
|
119
|
+
self.on_position: Optional[Callable[[Any, dict], None]] = None
|
120
|
+
self.on_holding: Optional[Callable[[Any, dict], None]] = None
|
92
121
|
|
93
122
|
logger.debug("Initialized QuotesClient with URL: %s", self.url)
|
94
123
|
|
@@ -96,6 +125,40 @@ class QuotesClient:
|
|
96
125
|
for i in range(0, len(data), chunk_size):
|
97
126
|
yield data[i:i + chunk_size]
|
98
127
|
|
128
|
+
def _normalize_message(self, raw_message: dict) -> dict:
|
129
|
+
"""
|
130
|
+
Normalize message structure to handle both legacy and new formats.
|
131
|
+
|
132
|
+
Legacy Format (Ticks/Greeks): {type: "ticks", ...data} or {...data} (no type)
|
133
|
+
New Format (Events): {type: "position", data: {...actualData}}
|
134
|
+
|
135
|
+
Returns: Flattened message with type preserved at root level
|
136
|
+
"""
|
137
|
+
try:
|
138
|
+
# Check if message has both 'type' and 'data' fields (new format)
|
139
|
+
if 'type' in raw_message and 'data' in raw_message:
|
140
|
+
# New format: Extract type, merge data into root level
|
141
|
+
message_type = raw_message['type']
|
142
|
+
data = raw_message['data']
|
143
|
+
if isinstance(data, dict):
|
144
|
+
# Create flattened message with type preserved
|
145
|
+
normalized = {'type': message_type}
|
146
|
+
normalized.update(data)
|
147
|
+
logger.debug("Normalized new format message: type=%s", message_type)
|
148
|
+
return normalized
|
149
|
+
else:
|
150
|
+
# Data is not a dict, keep original structure
|
151
|
+
logger.debug("Data field is not a dict, keeping original structure")
|
152
|
+
return raw_message
|
153
|
+
else:
|
154
|
+
# Legacy format: Use as-is (either has type only or no type)
|
155
|
+
logger.debug("Legacy format message detected")
|
156
|
+
return raw_message
|
157
|
+
|
158
|
+
except Exception as e:
|
159
|
+
logger.debug("Error normalizing message: %s, using original", e)
|
160
|
+
return raw_message
|
161
|
+
|
99
162
|
async def _connect_with_backoff(self) -> None:
|
100
163
|
backoff = self._backoff_base
|
101
164
|
|
@@ -174,24 +237,67 @@ class QuotesClient:
|
|
174
237
|
if not chunk:
|
175
238
|
continue
|
176
239
|
try:
|
177
|
-
|
240
|
+
raw_data = json.loads(chunk)
|
241
|
+
# Normalize message structure
|
242
|
+
data = self._normalize_message(raw_data)
|
178
243
|
# Route based on message type
|
179
244
|
message_type = data.get('type')
|
180
245
|
|
181
246
|
if message_type == 'greeks':
|
182
247
|
if self.on_stats:
|
183
|
-
|
248
|
+
try:
|
249
|
+
self.on_stats(self, data)
|
250
|
+
except Exception as e:
|
251
|
+
logger.debug("Error in on_stats handler: %s", e)
|
184
252
|
else:
|
185
253
|
logger.debug("Received greeks message but no on_stats handler registered")
|
186
254
|
elif message_type == 'ticks':
|
187
255
|
if self.on_tick:
|
188
|
-
|
256
|
+
try:
|
257
|
+
self.on_tick(self, data)
|
258
|
+
except Exception as e:
|
259
|
+
logger.debug("Error in on_tick handler: %s", e)
|
189
260
|
else:
|
190
261
|
logger.debug("Received ticks message but no on_tick handler registered")
|
262
|
+
elif message_type == 'order':
|
263
|
+
if self.on_order:
|
264
|
+
try:
|
265
|
+
self.on_order(self, data)
|
266
|
+
except Exception as e:
|
267
|
+
logger.debug("Error in on_order handler: %s", e)
|
268
|
+
else:
|
269
|
+
logger.debug("Received order event but no on_order handler registered")
|
270
|
+
elif message_type == 'trade':
|
271
|
+
if self.on_trade:
|
272
|
+
try:
|
273
|
+
self.on_trade(self, data)
|
274
|
+
except Exception as e:
|
275
|
+
logger.debug("Error in on_trade handler: %s", e)
|
276
|
+
else:
|
277
|
+
logger.debug("Received trade event but no on_trade handler registered")
|
278
|
+
elif message_type == 'position':
|
279
|
+
if self.on_position:
|
280
|
+
try:
|
281
|
+
self.on_position(self, data)
|
282
|
+
except Exception as e:
|
283
|
+
logger.debug("Error in on_position handler: %s", e)
|
284
|
+
else:
|
285
|
+
logger.debug("Received position event but no on_position handler registered")
|
286
|
+
elif message_type == 'holding':
|
287
|
+
if self.on_holding:
|
288
|
+
try:
|
289
|
+
self.on_holding(self, data)
|
290
|
+
except Exception as e:
|
291
|
+
logger.debug("Error in on_holding handler: %s", e)
|
292
|
+
else:
|
293
|
+
logger.debug("Received holding event but no on_holding handler registered")
|
191
294
|
else:
|
192
295
|
# No type field - send to on_tick for backward compatibility
|
193
296
|
if self.on_tick:
|
194
|
-
|
297
|
+
try:
|
298
|
+
self.on_tick(self, data)
|
299
|
+
except Exception as e:
|
300
|
+
logger.debug("Error in on_tick handler: %s", e)
|
195
301
|
else:
|
196
302
|
logger.debug("Received message without type field and no on_tick handler registered")
|
197
303
|
except json.JSONDecodeError as e:
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: wiz_trader
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.36.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
|
@@ -38,6 +38,7 @@ Dynamic: requires-python
|
|
38
38
|
- [Callbacks](#callbacks)
|
39
39
|
- [Subscribing to Instruments](#subscribing-to-instruments)
|
40
40
|
- [Unsubscribing from Instruments](#unsubscribing-from-instruments)
|
41
|
+
- [Account Events](#account-events)
|
41
42
|
- [Handling WebSocket Connection](#handling-websocket-connection)
|
42
43
|
- [Complete Examples](#quotes-client-examples)
|
43
44
|
5. [Wizzer Client](#wizzer-client)
|
@@ -253,6 +254,347 @@ ws.unsubscribe(["NSE:RELIANCE:2885"])
|
|
253
254
|
- Can be called multiple times for different instrument sets
|
254
255
|
- No callback fired for unsubscribed instruments
|
255
256
|
|
257
|
+
### Account Events
|
258
|
+
|
259
|
+
The QuotesClient now supports automatic reception of account-related events (orders, trades, positions, holdings) from the quotes server. These events are automatically pushed to SDK users via dedicated callback handlers based on the event type.
|
260
|
+
|
261
|
+
#### Event Handlers
|
262
|
+
|
263
|
+
Register event handlers to receive real-time updates for your account activity:
|
264
|
+
|
265
|
+
```python
|
266
|
+
def on_order(ws, order):
|
267
|
+
print(f"Order event: {order}")
|
268
|
+
|
269
|
+
def on_trade(ws, trade):
|
270
|
+
print(f"Trade event: {trade}")
|
271
|
+
|
272
|
+
def on_position(ws, position):
|
273
|
+
print(f"Position event: {position}")
|
274
|
+
|
275
|
+
def on_holding(ws, holding):
|
276
|
+
print(f"Holding event: {holding}")
|
277
|
+
|
278
|
+
# Register event handlers
|
279
|
+
client.on_order = on_order
|
280
|
+
client.on_trade = on_trade
|
281
|
+
client.on_position = on_position
|
282
|
+
client.on_holding = on_holding
|
283
|
+
```
|
284
|
+
|
285
|
+
#### Key Features
|
286
|
+
|
287
|
+
- **Automatic Event Reception**: No explicit subscription needed - events are automatically sent based on your JWT token's account ID and strategy ID
|
288
|
+
- **Optional Handlers**: Events are silently dropped if no handler is registered - you only receive what you need
|
289
|
+
- **Message Normalization**: The SDK automatically handles different message formats and delivers clean, flattened event data to your handlers
|
290
|
+
- **Error Resilience**: Handler exceptions are caught and logged without breaking the event flow
|
291
|
+
- **Backward Compatible**: Existing tick and Greeks functionality remains unchanged
|
292
|
+
|
293
|
+
#### Event Data Structure
|
294
|
+
|
295
|
+
All event handlers receive flattened event data with the event type preserved:
|
296
|
+
|
297
|
+
```python
|
298
|
+
# Example order event data
|
299
|
+
{
|
300
|
+
"_id": "order_01k10np7weemkv812nx55bppw5",
|
301
|
+
"accountEntityType": "client",
|
302
|
+
"accountId": "user_01j9brznsffphvzqn9gzyj1frw",
|
303
|
+
"avgPrice": 72.65,
|
304
|
+
"broker": "wizzer",
|
305
|
+
"brokerOrderId": "dssorder_175344314711184",
|
306
|
+
"brokerTimestamp": "2025-07-25T11:32:28.147Z",
|
307
|
+
"cancelledQty": 0,
|
308
|
+
"completedAt": "2025-07-25T11:32:28.147Z",
|
309
|
+
"createdAt": "2025-07-25T11:32:27.722Z",
|
310
|
+
"disclosedQty": 0,
|
311
|
+
"exchange": "NSE",
|
312
|
+
"exchangeOrderId": "dssx_1753443147",
|
313
|
+
"exchangeToken": 11184,
|
314
|
+
"exchangeUpdateTimestamp": "2025-07-25T11:32:28.147Z",
|
315
|
+
"executionType": "regular",
|
316
|
+
"filledQty": 1,
|
317
|
+
"guid": "t0lVueivgBgcEh2k_BP",
|
318
|
+
"instrumentType": "EQLC",
|
319
|
+
"isDealer": false,
|
320
|
+
"isOrderModified": false,
|
321
|
+
"legs": [],
|
322
|
+
"marginUsed": 0,
|
323
|
+
"meta": {
|
324
|
+
"dss": {"orderId": "order_01k10np7weemkv812nx55bppw5"},
|
325
|
+
"source": "wizzer.dss",
|
326
|
+
"wizzer": {"mode": "paper_trading"}
|
327
|
+
},
|
328
|
+
"orderType": "MARKET",
|
329
|
+
"parentOrderId": "",
|
330
|
+
"pendingQty": 1,
|
331
|
+
"placedBy": "user_01j9brznsffphvzqn9gzyj1frw",
|
332
|
+
"price": 0,
|
333
|
+
"product": "CNC",
|
334
|
+
"qty": 1,
|
335
|
+
"remarks": "",
|
336
|
+
"segment": "NSECM",
|
337
|
+
"slPrice": 0,
|
338
|
+
"slTriggerPrice": 0,
|
339
|
+
"sqOffHoldingId": "",
|
340
|
+
"sqOffPositionId": "",
|
341
|
+
"status": "COMPLETED",
|
342
|
+
"stoploss": 0,
|
343
|
+
"stoplossOrderId": "",
|
344
|
+
"strategy": {
|
345
|
+
"id": "stgy_01jtn7t3erf8ss78spqy40k8en",
|
346
|
+
"identifier": "S-608377",
|
347
|
+
"name": "Test 1122"
|
348
|
+
},
|
349
|
+
"target": 0,
|
350
|
+
"targetOrderId": "",
|
351
|
+
"tradingSymbol": "IDFCFIRSTB",
|
352
|
+
"transactionType": "BUY",
|
353
|
+
"triggerId": "",
|
354
|
+
"triggerPrice": 0,
|
355
|
+
"updatedAt": "2025-07-25T11:32:28.254Z",
|
356
|
+
"userId": "user_01j9brznsffphvzqn9gzyj1frw",
|
357
|
+
"validity": "DAY",
|
358
|
+
"variety": "REGULAR"
|
359
|
+
}
|
360
|
+
|
361
|
+
# Example trade event data
|
362
|
+
{
|
363
|
+
"_id": "trade_01k10np8rqef793zr93ctqj0wt",
|
364
|
+
"accountId": "user_01j9brznsffphvzqn9gzyj1frw",
|
365
|
+
"avgPrice": 72.65,
|
366
|
+
"broker": "wizzer",
|
367
|
+
"brokerOrderId": "dssorder_175344314711184",
|
368
|
+
"createdAt": "2025-07-25T11:32:28.567Z",
|
369
|
+
"exchange": "NSE",
|
370
|
+
"exchangeOrderId": "dssx_1753443147",
|
371
|
+
"exchangeTimestamp": "2025-07-25T11:32:28.147Z",
|
372
|
+
"exchangeToken": 11184,
|
373
|
+
"isDividend": false,
|
374
|
+
"meta": {
|
375
|
+
"dss": {"orderId": "order_01k10np7weemkv812nx55bppw5"},
|
376
|
+
"source": "wizzer.dss",
|
377
|
+
"wizzer": {"mode": "paper_trading"}
|
378
|
+
},
|
379
|
+
"orderId": "order_01k10np7weemkv812nx55bppw5",
|
380
|
+
"orderType": "MARKET",
|
381
|
+
"parentOrderId": "",
|
382
|
+
"parentTradeId": "",
|
383
|
+
"product": "CNC",
|
384
|
+
"qty": 1,
|
385
|
+
"segment": "NSECM",
|
386
|
+
"strategy": {
|
387
|
+
"id": "stgy_01jtn7t3erf8ss78spqy40k8en",
|
388
|
+
"identifier": "S-608377",
|
389
|
+
"name": "Test 1122"
|
390
|
+
},
|
391
|
+
"tradeId": "dsstrade_50PccSfWvsCg-Cr5dQE",
|
392
|
+
"tradingSymbol": "IDFCFIRSTB",
|
393
|
+
"transactionType": "BUY",
|
394
|
+
"updatedAt": "2025-07-25T11:32:28.567Z",
|
395
|
+
"userId": "user_01j9brznsffphvzqn9gzyj1frw"
|
396
|
+
}
|
397
|
+
|
398
|
+
# Example position event data
|
399
|
+
{
|
400
|
+
"_id": "pos_01k10np8t7e0fs63q52hacbx8m",
|
401
|
+
"accountEntityType": "client",
|
402
|
+
"accountId": "user_01j9brznsffphvzqn9gzyj1frw",
|
403
|
+
"bep": 0,
|
404
|
+
"broker": "wizzer",
|
405
|
+
"buyAvgPrice": 72.65,
|
406
|
+
"buyOrders": ["order_01k10np7weemkv812nx55bppw5"],
|
407
|
+
"buyQty": 1,
|
408
|
+
"buyTrades": ["trade_01k10np8rqef793zr93ctqj0wt"],
|
409
|
+
"buyValue": 72.65,
|
410
|
+
"closePrice": 0,
|
411
|
+
"createdAt": "2025-07-25T11:32:28.614Z",
|
412
|
+
"exchange": "NSE",
|
413
|
+
"exchangeToken": 11184,
|
414
|
+
"instrumentType": "EQLC",
|
415
|
+
"lotSize": 1,
|
416
|
+
"meta": {
|
417
|
+
"source": "wizzer.dss",
|
418
|
+
"wizzer": {"mode": "paper_trading"}
|
419
|
+
},
|
420
|
+
"netQty": 1,
|
421
|
+
"pnlPercentage": 0,
|
422
|
+
"product": "CNC",
|
423
|
+
"realisedPnlToday": 0,
|
424
|
+
"segment": "NSECM",
|
425
|
+
"sellAvgPrice": 0,
|
426
|
+
"sellOrders": [],
|
427
|
+
"sellQty": 0,
|
428
|
+
"sellTrades": [],
|
429
|
+
"sellValue": 0,
|
430
|
+
"slPrice": null,
|
431
|
+
"slTriggerPrice": null,
|
432
|
+
"status": "open",
|
433
|
+
"stoploss": 0,
|
434
|
+
"strategy": {
|
435
|
+
"id": "stgy_01jtn7t3erf8ss78spqy40k8en",
|
436
|
+
"identifier": "S-608377",
|
437
|
+
"name": "Test 1122"
|
438
|
+
},
|
439
|
+
"target": 0,
|
440
|
+
"tradingSymbol": "IDFCFIRSTB",
|
441
|
+
"unrealisedPnlToday": 0,
|
442
|
+
"updatedAt": "2025-07-25T11:32:28.614Z",
|
443
|
+
"userId": "user_01j9brznsffphvzqn9gzyj1frw"
|
444
|
+
}
|
445
|
+
|
446
|
+
# Example holding event data
|
447
|
+
{
|
448
|
+
"_id": "holding_01k10np8t7e0fs63q52hacbx8m",
|
449
|
+
"accountEntityType": "client",
|
450
|
+
"accountId": "user_01j9brznsffphvzqn9gzyj1frw",
|
451
|
+
"bep": 0,
|
452
|
+
"broker": "wizzer",
|
453
|
+
"buyAvgPrice": 72.65,
|
454
|
+
"buyOrders": ["order_01k10np7weemkv812nx55bppw5"],
|
455
|
+
"buyQty": 1,
|
456
|
+
"buyTrades": ["trade_01k10np8rqef793zr93ctqj0wt"],
|
457
|
+
"buyValue": 72.65,
|
458
|
+
"closePrice": 0,
|
459
|
+
"createdAt": "2025-07-25T11:32:28.614Z",
|
460
|
+
"exchange": "NSE",
|
461
|
+
"exchangeToken": 11184,
|
462
|
+
"instrumentType": "EQLC",
|
463
|
+
"lotSize": 1,
|
464
|
+
"meta": {
|
465
|
+
"source": "wizzer.dss",
|
466
|
+
"wizzer": {"mode": "paper_trading"}
|
467
|
+
},
|
468
|
+
"netQty": 1,
|
469
|
+
"pnlPercentage": 0,
|
470
|
+
"product": "CNC",
|
471
|
+
"realisedPnlToday": 0,
|
472
|
+
"segment": "NSECM",
|
473
|
+
"sellAvgPrice": 0,
|
474
|
+
"sellOrders": [],
|
475
|
+
"sellQty": 0,
|
476
|
+
"sellTrades": [],
|
477
|
+
"sellValue": 0,
|
478
|
+
"slPrice": null,
|
479
|
+
"slTriggerPrice": null,
|
480
|
+
"status": "open",
|
481
|
+
"stoploss": 0,
|
482
|
+
"strategy": {
|
483
|
+
"id": "stgy_01jtn7t3erf8ss78spqy40k8en",
|
484
|
+
"identifier": "S-608377",
|
485
|
+
"name": "Test 1122"
|
486
|
+
},
|
487
|
+
"target": 0,
|
488
|
+
"tradingSymbol": "IDFCFIRSTB",
|
489
|
+
"unrealisedPnlToday": 0,
|
490
|
+
"updatedAt": "2025-07-25T11:32:28.614Z",
|
491
|
+
"userId": "user_01j9brznsffphvzqn9gzyj1frw"
|
492
|
+
}
|
493
|
+
```
|
494
|
+
|
495
|
+
#### Complete Event Handling Example
|
496
|
+
|
497
|
+
```python
|
498
|
+
from wiz_trader import QuotesClient
|
499
|
+
|
500
|
+
# Initialize client
|
501
|
+
client = QuotesClient(
|
502
|
+
base_url="wss://websocket-url/quotes",
|
503
|
+
token="your-jwt-token",
|
504
|
+
log_level="info"
|
505
|
+
)
|
506
|
+
|
507
|
+
def on_order(ws, order):
|
508
|
+
# React to order status changes
|
509
|
+
if order['status'] == ORDER_STATUS_COMPLETED:
|
510
|
+
print(f"Order {order['_id']} completed!")
|
511
|
+
elif order['status'] == ORDER_STATUS_REJECTED:
|
512
|
+
print(f"Order {order['_id']} rejected: {order.get('remarks', 'Unknown')}")
|
513
|
+
|
514
|
+
def on_trade(ws, trade):
|
515
|
+
print(f"Trade executed: {trade['qty']} x {trade['tradingSymbol']} @ {trade['avgPrice']}")
|
516
|
+
|
517
|
+
def on_position(ws, position):
|
518
|
+
print(f"Position update: {position['tradingSymbol']} = {position['netQty']} (PnL: {position.get('unrealisedPnlToday', 0)})")
|
519
|
+
|
520
|
+
def on_holding(ws, holding):
|
521
|
+
print(f"Holding update: {holding['tradingSymbol']} = {holding['netQty']}")
|
522
|
+
|
523
|
+
def on_connect(ws):
|
524
|
+
print("Connected! Ready to receive events.")
|
525
|
+
# Also subscribe to some ticks if needed
|
526
|
+
ws.subscribe(["NSE:SBIN:3045"], mode="ticks")
|
527
|
+
|
528
|
+
# Register all handlers
|
529
|
+
client.on_order = on_order
|
530
|
+
client.on_trade = on_trade
|
531
|
+
client.on_position = on_position
|
532
|
+
client.on_holding = on_holding
|
533
|
+
client.on_connect = on_connect
|
534
|
+
|
535
|
+
# Connect and receive events
|
536
|
+
client.connect()
|
537
|
+
```
|
538
|
+
|
539
|
+
#### Event-Driven Trading Strategy Example
|
540
|
+
|
541
|
+
```python
|
542
|
+
class EventDrivenStrategy:
|
543
|
+
def __init__(self):
|
544
|
+
self.orders = {}
|
545
|
+
self.positions = {}
|
546
|
+
self.pending_orders = set()
|
547
|
+
|
548
|
+
def on_order_event(self, ws, order):
|
549
|
+
order_id = order['_id']
|
550
|
+
self.orders[order_id] = order
|
551
|
+
|
552
|
+
# Track pending orders
|
553
|
+
if order['status'] == ORDER_STATUS_OPEN:
|
554
|
+
self.pending_orders.add(order_id)
|
555
|
+
elif order_id in self.pending_orders:
|
556
|
+
self.pending_orders.remove(order_id)
|
557
|
+
|
558
|
+
# Log order lifecycle
|
559
|
+
print(f"Order {order_id}: {order['status']}")
|
560
|
+
|
561
|
+
# React to rejections
|
562
|
+
if order['status'] == ORDER_STATUS_REJECTED:
|
563
|
+
print(f"Order rejected! Reason: {order.get('remarks', 'Unknown')}")
|
564
|
+
# Implement retry logic or alternative strategy
|
565
|
+
|
566
|
+
def on_position_event(self, ws, position):
|
567
|
+
symbol = position['tradingSymbol']
|
568
|
+
self.positions[symbol] = position
|
569
|
+
|
570
|
+
# Monitor position changes
|
571
|
+
print(f"Position {symbol}: Qty={position['netQty']}, PnL={position.get('unrealisedPnlToday', 0)}")
|
572
|
+
|
573
|
+
# Implement stop-loss or take-profit logic
|
574
|
+
pnl = position.get('unrealisedPnlToday', 0)
|
575
|
+
if pnl < -100: # Stop loss at -100
|
576
|
+
print(f"Stop loss triggered for {symbol}")
|
577
|
+
# Place exit order
|
578
|
+
elif pnl > 200: # Take profit at 200
|
579
|
+
print(f"Take profit triggered for {symbol}")
|
580
|
+
# Place exit order
|
581
|
+
|
582
|
+
def on_trade_event(self, ws, trade):
|
583
|
+
print(f"Trade executed: {trade['qty']} @ {trade['avgPrice']}")
|
584
|
+
# Update internal trade log, calculate VWAP, etc.
|
585
|
+
|
586
|
+
# Initialize strategy
|
587
|
+
strategy = EventDrivenStrategy()
|
588
|
+
|
589
|
+
# Set up client with strategy handlers
|
590
|
+
client = QuotesClient(base_url="wss://...", token="...")
|
591
|
+
client.on_order = strategy.on_order_event
|
592
|
+
client.on_position = strategy.on_position_event
|
593
|
+
client.on_trade = strategy.on_trade_event
|
594
|
+
|
595
|
+
client.connect()
|
596
|
+
```
|
597
|
+
|
256
598
|
### Complete Examples
|
257
599
|
|
258
600
|
#### Blocking Example
|
@@ -425,6 +767,7 @@ Available constants:
|
|
425
767
|
- Transaction types: `TRANSACTION_TYPE_BUY`, `TRANSACTION_TYPE_SELL`
|
426
768
|
- Product types: `PRODUCT_CNC`, `PRODUCT_MIS`, `PRODUCT_NRML`
|
427
769
|
- Order types: `ORDER_TYPE_MARKET`, `ORDER_TYPE_LIMIT`, `ORDER_TYPE_SL`, `ORDER_TYPE_SLM`
|
770
|
+
- Order status: `ORDER_STATUS_OPEN`, `ORDER_STATUS_CANCELLED`, `ORDER_STATUS_REJECTED`, `ORDER_STATUS_PENDING`, `ORDER_STATUS_COMPLETED`
|
428
771
|
- Validity types: `VALIDITY_DAY`, `VALIDITY_IOC`, `VALIDITY_GTT`
|
429
772
|
- Variety types: `VARIETY_REGULAR`, `VARIETY_AMO`, `VARIETY_BO`, `VARIETY_CO`
|
430
773
|
- Exchanges: `EXCHANGE_NSE`, `EXCHANGE_BSE`, `EXCHANGE_WZR`
|
@@ -0,0 +1,9 @@
|
|
1
|
+
wiz_trader/__init__.py,sha256=hOpsoebZsI_XebFP-JQ-CenvIShBGtVDsJhl72ARJxU,183
|
2
|
+
wiz_trader/apis/__init__.py,sha256=6sUr1nzmplNdld0zryMrQSt0jHT2GhOiFYgKKVHzk8U,133
|
3
|
+
wiz_trader/apis/client.py,sha256=znwlyDIHbaiQuDqxp7pn1NPiiYB99oA74JWt4AiLkBg,80088
|
4
|
+
wiz_trader/quotes/__init__.py,sha256=RF9g9CNP6bVWlmCh_ad8krm3-EWOIuVfLp0-H9fAeEM,108
|
5
|
+
wiz_trader/quotes/client.py,sha256=cAGaysLCljZilYgX8Sf3V88F6dWlcewJf6TMOpSKb7I,20862
|
6
|
+
wiz_trader-0.36.0.dist-info/METADATA,sha256=AYUF296FcogkDFjLk5WDhVeAaGvH1PhO8Gyl1g4aH3Q,155156
|
7
|
+
wiz_trader-0.36.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
8
|
+
wiz_trader-0.36.0.dist-info/top_level.txt,sha256=lnYS_g8LlA6ryKYnvY8xIQ6K2K-xzOsd-99AWgnW6VY,11
|
9
|
+
wiz_trader-0.36.0.dist-info/RECORD,,
|
@@ -1,9 +0,0 @@
|
|
1
|
-
wiz_trader/__init__.py,sha256=mvDJsObXpYGcnLwfpMITtZeAouMb_YHNW0Tqse0V0bg,183
|
2
|
-
wiz_trader/apis/__init__.py,sha256=6sUr1nzmplNdld0zryMrQSt0jHT2GhOiFYgKKVHzk8U,133
|
3
|
-
wiz_trader/apis/client.py,sha256=znwlyDIHbaiQuDqxp7pn1NPiiYB99oA74JWt4AiLkBg,80088
|
4
|
-
wiz_trader/quotes/__init__.py,sha256=RF9g9CNP6bVWlmCh_ad8krm3-EWOIuVfLp0-H9fAeEM,108
|
5
|
-
wiz_trader/quotes/client.py,sha256=aZ5LVlrj0mKfgHgFxERmk2HDZraB6RMaormTOMlqWZc,14915
|
6
|
-
wiz_trader-0.35.0.dist-info/METADATA,sha256=0aBskcZN4xpphpCtQMO9oAYIjwgboe9nmcpcywuKrXg,144695
|
7
|
-
wiz_trader-0.35.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
8
|
-
wiz_trader-0.35.0.dist-info/top_level.txt,sha256=lnYS_g8LlA6ryKYnvY8xIQ6K2K-xzOsd-99AWgnW6VY,11
|
9
|
-
wiz_trader-0.35.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|