wiz-trader 0.9.0__py3-none-any.whl → 0.10.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.
@@ -0,0 +1,1781 @@
1
+ Metadata-Version: 2.4
2
+ Name: wiz_trader
3
+ Version: 0.10.0
4
+ Summary: A Python SDK for connecting to the Wizzer.
5
+ Home-page: https://bitbucket.org/wizzer-tech/quotes_sdk.git
6
+ Author: Pawan Wagh
7
+ Author-email: Pawan Wagh <pawan@wizzer.in>
8
+ License: MIT
9
+ Project-URL: Homepage, https://bitbucket.org/wizzer-tech/quotes_sdk.git
10
+ Project-URL: Bug Tracker, https://bitbucket.org/wizzer-tech/quotes_sdk/issues
11
+ Keywords: finance,trading,sdk
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Financial and Insurance Industry
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: License :: OSI Approved :: MIT License
18
+ Classifier: Topic :: Office/Business :: Financial
19
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
20
+ Requires-Python: >=3.6
21
+ Description-Content-Type: text/markdown
22
+ Requires-Dist: websockets
23
+ Requires-Dist: requests
24
+ Dynamic: author
25
+ Dynamic: home-page
26
+ Dynamic: requires-python
27
+
28
+ # WizTrader SDK Documentation
29
+
30
+ ## Table of Contents
31
+
32
+ 1. [Introduction](#introduction)
33
+ 2. [Installation](#installation)
34
+ 3. [Authentication](#authentication)
35
+ 4. [Quotes Client](#quotes-client)
36
+ - [Initialization](#quotes-client-initialization)
37
+ - [Connection Methods](#connection-methods)
38
+ - [Callbacks](#callbacks)
39
+ - [Subscribing to Instruments](#subscribing-to-instruments)
40
+ - [Unsubscribing from Instruments](#unsubscribing-from-instruments)
41
+ - [Handling WebSocket Connection](#handling-websocket-connection)
42
+ - [Complete Examples](#quotes-client-examples)
43
+ 5. [Wizzer Client](#wizzer-client)
44
+ - [Initialization](#wizzer-client-initialization)
45
+ - [Constants](#constants)
46
+ - [Data Hub Methods](#data-hub-methods)
47
+ - [Order Management](#order-management)
48
+ - [Portfolio Management](#portfolio-management)
49
+ - [Basket Management](#basket-management)
50
+ - [Complete Examples](#wizzer-client-examples)
51
+ 6. [Common Use Cases](#common-use-cases)
52
+ 7. [Error Handling](#error-handling)
53
+ 8. [Troubleshooting](#troubleshooting)
54
+ 9. [API Reference](#api-reference)
55
+
56
+ ## Introduction
57
+
58
+ WizTrader is a Python SDK for connecting to the Wizzer trading platform. It provides two main client classes:
59
+
60
+ - **QuotesClient**: For real-time market data streaming via WebSockets
61
+ - **WizzerClient**: For REST API access to trading, order management, and market data
62
+
63
+ This documentation covers all the ways to use the SDK, with examples for each endpoint and common use cases.
64
+
65
+ ## Installation
66
+
67
+ You can install the package directly from PyPI:
68
+
69
+ ```bash
70
+ pip install wiz_trader
71
+ ```
72
+
73
+ To install the development version from GitHub:
74
+
75
+ ```bash
76
+ pip install git+https://github.com/wizzer-tech/wiz_trader.git
77
+ ```
78
+
79
+ ## Authentication
80
+
81
+ There are two ways to provide authentication credentials to the SDK:
82
+
83
+ 1. **Direct parameter passing** (Recommended for most use cases)
84
+ 2. **Environment variables** (Recommended for production deployments)
85
+
86
+ ### Direct Parameter Passing
87
+
88
+ Simply provide the required parameters directly when initializing the clients:
89
+
90
+ ```python
91
+ from wiz_trader import QuotesClient, WizzerClient
92
+
93
+ quotes_client = QuotesClient(
94
+ base_url="wss://websocket-url/quotes",
95
+ token="your-jwt-token",
96
+ log_level="info"
97
+ )
98
+
99
+ wizzer_client = WizzerClient(
100
+ base_url="https://api-url.in",
101
+ token="your-jwt-token",
102
+ strategy_id="str_01hxgwgfxgfrxb48bzchnve7gm", # Optional
103
+ log_level="info"
104
+ )
105
+ ```
106
+
107
+ ### Environment Variables
108
+
109
+ Set the following environment variables:
110
+
111
+ ```bash
112
+ export WZ__QUOTES_BASE_URL="wss://websocket-url/quotes"
113
+ export WZ__API_BASE_URL="https://api-url.in"
114
+ export WZ__TOKEN="your-jwt-token"
115
+ export WZ__STRATEGY_ID="str_01hxgwgfxgfrxb48bzchnve7gm" # Optional
116
+ ```
117
+
118
+ Then initialize the clients without parameters:
119
+
120
+ ```python
121
+ from wiz_trader import QuotesClient, WizzerClient
122
+
123
+ quotes_client = QuotesClient(log_level="info")
124
+ wizzer_client = WizzerClient(log_level="info")
125
+ ```
126
+
127
+ You can also use a hybrid approach, providing some parameters directly and letting others fall back to environment variables:
128
+
129
+ ```python
130
+ quotes_client = QuotesClient(log_level="debug") # Only override log level
131
+ ```
132
+
133
+ ## Quotes Client
134
+
135
+ The `QuotesClient` enables you to connect to Wizzer's WebSocket server for real-time market data.
136
+
137
+ ### Quotes Client Initialization
138
+
139
+ You can initialize the `QuotesClient` in several ways:
140
+
141
+ ```python
142
+ from wiz_trader import QuotesClient
143
+
144
+ # Method 1: All parameters provided directly
145
+ client = QuotesClient(
146
+ base_url="wss://websocket-url/quotes",
147
+ token="your-jwt-token",
148
+ log_level="info", # Options: "error", "info", "debug"
149
+ max_message_size=10 * 1024 * 1024, # Optional: Set max message size (default 10MB)
150
+ batch_size=20 # Optional: Max instruments per subscription batch (default 20)
151
+ )
152
+
153
+ # Method 2: Using environment variables
154
+ # (Requires WZ__QUOTES_BASE_URL and WZ__TOKEN to be set)
155
+ client = QuotesClient(log_level="info")
156
+
157
+ # Method 3: Mixed approach (some params direct, some from env vars)
158
+ client = QuotesClient(
159
+ base_url="wss://custom-websocket-url/quotes",
160
+ log_level="debug"
161
+ # token will be taken from WZ__TOKEN environment variable
162
+ )
163
+ ```
164
+
165
+ ### Connection Methods
166
+
167
+ The `QuotesClient` offers two ways to connect:
168
+
169
+ #### Blocking Connection
170
+
171
+ This approach is similar to Zerodha's KiteTicker and is recommended for simple scripts:
172
+
173
+ ```python
174
+ # This will block and run until stopped
175
+ client.connect()
176
+ ```
177
+
178
+ #### Non-Blocking Connection
179
+
180
+ Use this for more complex applications where you need to perform other tasks:
181
+
182
+ ```python
183
+ # Start the connection in the background
184
+ client.connect_async()
185
+
186
+ # Later, when you want to stop:
187
+ client.stop()
188
+ ```
189
+
190
+ ### Callbacks
191
+
192
+ Set up callbacks to handle different events:
193
+
194
+ ```python
195
+ def on_tick(ws, tick):
196
+ """Called when a tick is received"""
197
+ print(f"Received tick: {tick}")
198
+
199
+ def on_connect(ws):
200
+ """Called when the connection is established"""
201
+ print("Connected to the quotes server")
202
+ # Subscribe to instruments
203
+ ws.subscribe(["NSE:SBIN:3045"])
204
+
205
+ def on_close(ws, code, reason):
206
+ """Called when the connection is closed"""
207
+ print(f"Connection closed with code {code}: {reason}")
208
+ # Optional: Stop the client (if you want to exit)
209
+ # ws.stop()
210
+
211
+ def on_error(ws, error):
212
+ """Called when an error occurs"""
213
+ print(f"Error: {error}")
214
+
215
+ # Set the callbacks
216
+ client.on_tick = on_tick
217
+ client.on_connect = on_connect
218
+ client.on_close = on_close
219
+ client.on_error = on_error
220
+ ```
221
+
222
+ ### Subscribing to Instruments
223
+
224
+ Subscribe to receive market data for specific instruments:
225
+
226
+ ```python
227
+ # Inside an on_connect callback:
228
+ def on_connect(ws):
229
+ # Subscribe to a single instrument
230
+ ws.subscribe(["NSE:SBIN:3045"])
231
+
232
+ # Or subscribe to multiple instruments at once
233
+ ws.subscribe([
234
+ "NSE:SBIN:3045",
235
+ "NSE:ICICIBANK:4963",
236
+ "NSE:RELIANCE:2885"
237
+ ])
238
+ ```
239
+
240
+ For large lists of instruments, the client will automatically batch them to avoid message size issues.
241
+
242
+ ### Unsubscribing from Instruments
243
+
244
+ To stop receiving data for specific instruments:
245
+
246
+ ```python
247
+ # Unsubscribe from specific instruments
248
+ ws.unsubscribe(["NSE:SBIN:3045", "NSE:ICICIBANK:4963"])
249
+ ```
250
+
251
+ ### Handling WebSocket Connection
252
+
253
+ The WebSocket connection automatically attempts to reconnect with exponential backoff if disconnected. You don't need to handle this manually.
254
+
255
+ To explicitly close the connection:
256
+
257
+ ```python
258
+ # In a blocking context:
259
+ client.stop()
260
+
261
+ # In an async context:
262
+ await client.close()
263
+ ```
264
+
265
+ ### Quotes Client Examples
266
+
267
+ #### Complete Blocking Example
268
+
269
+ ```python
270
+ import logging
271
+ from wiz_trader import QuotesClient
272
+
273
+ # Configure logging
274
+ logging.basicConfig(level=logging.DEBUG)
275
+
276
+ # Initialize the client
277
+ client = QuotesClient(
278
+ base_url="wss://websocket-url/quotes",
279
+ token="your-jwt-token"
280
+ )
281
+
282
+ def on_tick(ws, tick):
283
+ """Process incoming market data"""
284
+ logging.debug("Received tick: %s", tick)
285
+
286
+ def on_connect(ws):
287
+ """Handle successful connection"""
288
+ logging.info("Connected to quotes server")
289
+ # Subscribe to instruments
290
+ ws.subscribe(["NSE:SBIN:3045", "NSE:RELIANCE:2885"])
291
+
292
+ def on_close(ws, code, reason):
293
+ """Handle connection closure"""
294
+ logging.info("Connection closed: %s", reason)
295
+
296
+ def on_error(ws, error):
297
+ """Handle errors"""
298
+ logging.error("Error occurred: %s", error)
299
+
300
+ # Set callbacks
301
+ client.on_tick = on_tick
302
+ client.on_connect = on_connect
303
+ client.on_close = on_close
304
+ client.on_error = on_error
305
+
306
+ # Connect and run (blocking call)
307
+ try:
308
+ client.connect()
309
+ except KeyboardInterrupt:
310
+ print("Interrupted by user, shutting down...")
311
+ client.stop()
312
+ ```
313
+
314
+ #### Non-Blocking Example with Other Operations
315
+
316
+ ```python
317
+ import asyncio
318
+ import logging
319
+ from wiz_trader import QuotesClient, WizzerClient
320
+
321
+ async def main():
322
+ # Configure logging
323
+ logging.basicConfig(level=logging.INFO)
324
+
325
+ # Initialize the quotes client
326
+ quotes_client = QuotesClient(
327
+ base_url="wss://websocket-url/quotes",
328
+ token="your-jwt-token"
329
+ )
330
+
331
+ # Initialize the REST API client
332
+ wizzer_client = WizzerClient(
333
+ base_url="https://api-url.in",
334
+ token="your-jwt-token"
335
+ )
336
+
337
+ # Set up quotes callbacks
338
+ quotes_client.on_tick = lambda ws, tick: logging.info(f"Tick: {tick}")
339
+ quotes_client.on_connect = lambda ws: ws.subscribe(["NSE:SBIN:3045"])
340
+
341
+ # Connect in non-blocking mode
342
+ quotes_client.connect_async()
343
+
344
+ # Perform other operations
345
+ indices = await asyncio.to_thread(wizzer_client.get_indices, exchange="NSE")
346
+ logging.info(f"Available indices: {[idx['name'] for idx in indices[:5]]}")
347
+
348
+ # Wait for some time
349
+ await asyncio.sleep(60)
350
+
351
+ # Stop the quotes client
352
+ quotes_client.stop()
353
+
354
+ if __name__ == "__main__":
355
+ asyncio.run(main())
356
+ ```
357
+
358
+ ## Wizzer Client
359
+
360
+ The `WizzerClient` provides access to Wizzer's REST APIs for trading, portfolio management, and market data.
361
+
362
+ ### Wizzer Client Initialization
363
+
364
+ Initialize the client with your authentication details:
365
+
366
+ ```python
367
+ from wiz_trader import WizzerClient
368
+
369
+ # Method 1: All parameters provided directly
370
+ client = WizzerClient(
371
+ base_url="https://api-url.in",
372
+ token="your-jwt-token",
373
+ strategy_id="str_01hxgwgfxgfrxb48bzchnve7gm", # Optional: Default strategy ID
374
+ log_level="info" # Options: "error", "info", "debug"
375
+ )
376
+
377
+ # Method 2: Using environment variables
378
+ # (Requires WZ__API_BASE_URL and WZ__TOKEN to be set)
379
+ client = WizzerClient(log_level="info")
380
+ ```
381
+
382
+ ### Constants
383
+
384
+ The `WizzerClient` provides constants for various parameter values, making your code more readable and type-safe:
385
+
386
+ ```python
387
+ from wiz_trader import WizzerClient
388
+
389
+ client = WizzerClient(...)
390
+
391
+ # Use constants for transaction types
392
+ client.place_order(
393
+ exchange=client.EXCHANGE_NSE,
394
+ trading_symbol="SBIN",
395
+ transaction_type=client.TRANSACTION_TYPE_BUY,
396
+ quantity=10,
397
+ order_type=client.ORDER_TYPE_MARKET,
398
+ product=client.PRODUCT_CNC
399
+ )
400
+ ```
401
+
402
+ Available constants:
403
+
404
+ - Transaction types: `TRANSACTION_TYPE_BUY`, `TRANSACTION_TYPE_SELL`
405
+ - Product types: `PRODUCT_CNC`, `PRODUCT_MIS`, `PRODUCT_NRML`
406
+ - Order types: `ORDER_TYPE_MARKET`, `ORDER_TYPE_LIMIT`, `ORDER_TYPE_SL`, `ORDER_TYPE_SLM`
407
+ - Validity types: `VALIDITY_DAY`, `VALIDITY_IOC`, `VALIDITY_GTT`
408
+ - Variety types: `VARIETY_REGULAR`, `VARIETY_AMO`, `VARIETY_BO`, `VARIETY_CO`
409
+ - Exchanges: `EXCHANGE_NSE`, `EXCHANGE_BSE`, `EXCHANGE_WZR`
410
+ - Segments: `SEGMENT_NSE_CM`, `SEGMENT_BSE_CM`, `SEGMENT_NSE_FO`, `SEGMENT_WZREQ`
411
+ - Instrument types: `INSTRUMENT_TYPE_EQ`, `INSTRUMENT_TYPE_EQLC`, `INSTRUMENT_TYPE_EQMCC`, `INSTRUMENT_TYPE_EQSCC`, `INSTRUMENT_TYPE_BASKET`
412
+
413
+ ### Data Hub Methods
414
+
415
+ #### Get Indices
416
+
417
+ Fetch available indices from an exchange:
418
+
419
+ ```python
420
+ # Get all indices from NSE
421
+ indices = client.get_indices(exchange="NSE")
422
+ print(f"Found {len(indices)} indices")
423
+
424
+ # Get a specific index by trading symbol
425
+ nifty_50 = client.get_indices(trading_symbol="NIFTY 50", exchange="NSE")
426
+ ```
427
+
428
+ #### Get Index Components
429
+
430
+ Fetch components (stocks) in a specific index:
431
+
432
+ ```python
433
+ # Get components of NIFTY 50
434
+ components = client.get_index_components(trading_symbol="NIFTY 50", exchange="NSE")
435
+ print(f"Found {len(components)} components in NIFTY 50")
436
+ for component in components[:5]:
437
+ print(f"{component['tradingSymbol']} - {component.get('weightage', 'N/A')}%")
438
+ ```
439
+
440
+ #### Get Historical OHLCV Data
441
+
442
+ Fetch historical price data for instruments:
443
+
444
+ ```python
445
+ # Get daily data
446
+ daily_data = client.get_historical_ohlcv(
447
+ instruments=["NSE:SBIN:3045"],
448
+ start_date="2024-01-01",
449
+ end_date="2024-01-31",
450
+ ohlcv=["open", "high", "low", "close", "volume"],
451
+ interval="1d" # Daily data (default)
452
+ )
453
+
454
+ # Get monthly data
455
+ monthly_data = client.get_historical_ohlcv(
456
+ instruments=["NSE:SBIN:3045", "NSE:ICICIBANK:4963"],
457
+ start_date="2023-01-01",
458
+ end_date="2024-01-01",
459
+ ohlcv=["close", "volume"],
460
+ interval="1M" # Monthly data
461
+ )
462
+ ```
463
+
464
+ ### Order Management
465
+
466
+ #### Place Regular Order
467
+
468
+ Place a regular order for a single instrument:
469
+
470
+ ```python
471
+ # Simple market order
472
+ order_response = client.place_order(
473
+ exchange=client.EXCHANGE_NSE,
474
+ trading_symbol="SBIN",
475
+ transaction_type=client.TRANSACTION_TYPE_BUY,
476
+ quantity=10,
477
+ order_type=client.ORDER_TYPE_MARKET,
478
+ product=client.PRODUCT_CNC
479
+ )
480
+ print(f"Order placed successfully: {order_response.get('orderId')}")
481
+
482
+ # Limit order with price
483
+ limit_order = client.place_order(
484
+ exchange=client.EXCHANGE_NSE,
485
+ trading_symbol="SBIN",
486
+ transaction_type=client.TRANSACTION_TYPE_BUY,
487
+ quantity=10,
488
+ order_type=client.ORDER_TYPE_LIMIT,
489
+ product=client.PRODUCT_CNC,
490
+ price=650.00
491
+ )
492
+
493
+ # Order with stop loss and target
494
+ sl_order = client.place_order(
495
+ exchange=client.EXCHANGE_NSE,
496
+ trading_symbol="SBIN",
497
+ transaction_type=client.TRANSACTION_TYPE_BUY,
498
+ quantity=10,
499
+ product=client.PRODUCT_CNC,
500
+ stoploss=640.00,
501
+ target=670.00
502
+ )
503
+ ```
504
+
505
+ #### Modify Order
506
+
507
+ Modify an existing order:
508
+
509
+ ```python
510
+ # Modify an order's price
511
+ modified_order = client.modify_order(
512
+ order_id="order_01jpeyxtr4fb69fx740my3115c",
513
+ price=655.00
514
+ )
515
+
516
+ # Modify an order's quantity
517
+ modified_order = client.modify_order(
518
+ order_id="order_01jpeyxtr4fb69fx740my3115c",
519
+ qty=15
520
+ )
521
+ ```
522
+
523
+ #### Cancel Order
524
+
525
+ Cancel an existing order:
526
+
527
+ ```python
528
+ # Cancel an order
529
+ cancelled_order = client.cancel_order(order_id="order_01jpeyxtr4fb69fx740my3115c")
530
+ ```
531
+
532
+ #### Get Order Details
533
+
534
+ Fetch details of a specific order:
535
+
536
+ ```python
537
+ # Get order details
538
+ order_details = client.get_order(order_id="order_01jpeyxtr4fb69fx740my3115c")
539
+ print(f"Order status: {order_details.get('status')}")
540
+ ```
541
+
542
+ ### Portfolio Management
543
+
544
+ #### Get Positions
545
+
546
+ Fetch your current positions:
547
+
548
+ ```python
549
+ # Get all positions
550
+ all_positions = client.get_positions()
551
+ print(f"Found {len(all_positions)} positions")
552
+
553
+ # Get only open positions
554
+ open_positions = client.get_open_positions()
555
+ print(f"Found {len(open_positions)} open positions")
556
+
557
+ # Get only closed positions
558
+ closed_positions = client.get_closed_positions()
559
+ print(f"Found {len(closed_positions)} closed positions")
560
+ ```
561
+
562
+ #### Get Holdings
563
+
564
+ Fetch your holdings:
565
+
566
+ ```python
567
+ # Get holdings
568
+ holdings = client.get_holdings()
569
+ print(f"Found {len(holdings)} holdings")
570
+
571
+ # Get holdings for a specific portfolio
572
+ portfolio_holdings = client.get_holdings(portfolios="my_portfolio")
573
+ ```
574
+
575
+ #### Exit Positions
576
+
577
+ Exit all positions or positions for a specific strategy:
578
+
579
+ ```python
580
+ # Exit all positions
581
+ exit_response = client.exit_all_positions()
582
+ print(f"Successfully exited {exit_response.get('success', 0)} positions")
583
+
584
+ # Exit positions for a specific strategy
585
+ strategy_exit = client.exit_strategy_positions(strategy_id="str_01jbxszcjdegz8zt3h95g8c1d9")
586
+ ```
587
+
588
+ ### Basket Management
589
+
590
+ #### Create Basket
591
+
592
+ Create a new basket of instruments:
593
+
594
+ ```python
595
+ # Create a basic basket with two stocks
596
+ basket_response = client.create_basket(
597
+ name="My Basket",
598
+ instruments=[
599
+ {
600
+ "shares": 10,
601
+ "weightage": 50,
602
+ "instrument": {
603
+ "exchange": "NSE",
604
+ "identifier": "NSE:SBIN:3045",
605
+ "orderIndex": 1,
606
+ "exchangeToken": 3045,
607
+ "tradingSymbol": "SBIN"
608
+ }
609
+ },
610
+ {
611
+ "shares": 15,
612
+ "weightage": 50,
613
+ "instrument": {
614
+ "exchange": "NSE",
615
+ "identifier": "NSE:ICICIBANK:4963",
616
+ "orderIndex": 2,
617
+ "exchangeToken": 4963,
618
+ "tradingSymbol": "ICICIBANK"
619
+ }
620
+ }
621
+ ],
622
+ weightage_scheme="equi_weighted",
623
+ capital={"minValue": 50000, "actualValue": 50000},
624
+ instrument_types=["EQLC"]
625
+ )
626
+ basket_id = basket_response.get('id')
627
+ ```
628
+
629
+ #### Get Baskets
630
+
631
+ Fetch existing baskets:
632
+
633
+ ```python
634
+ # Get all baskets
635
+ baskets = client.get_baskets()
636
+ print(f"Found {len(baskets)} baskets")
637
+
638
+ # Get a specific basket by ID
639
+ basket = client.get_basket(basket_id="bk_01jpf2d57ae96bez63me7p6g9k")
640
+
641
+ # Get instruments in a basket
642
+ basket_instruments = client.get_basket_instruments(basket_id="bk_01jpf2d57ae96bez63me7p6g9k")
643
+ ```
644
+
645
+ #### Place Basket Order
646
+
647
+ Place an order for an entire basket:
648
+
649
+ ```python
650
+ # Place a basket order
651
+ basket_order = client.place_basket_order(
652
+ trading_symbol="/MY_BASKET",
653
+ transaction_type=client.TRANSACTION_TYPE_BUY,
654
+ quantity=1,
655
+ price=50000.00,
656
+ product=client.PRODUCT_CNC
657
+ )
658
+ ```
659
+
660
+ #### Exit Basket Order
661
+
662
+ Exit a basket position:
663
+
664
+ ```python
665
+ # Exit a basket position
666
+ exit_order = client.place_basket_exit_order(
667
+ trading_symbol="/MY_BASKET",
668
+ exchange=client.EXCHANGE_WZR,
669
+ transaction_type=client.TRANSACTION_TYPE_SELL,
670
+ quantity=1,
671
+ exchange_token=1741872632
672
+ )
673
+ ```
674
+
675
+ #### Rebalance Basket
676
+
677
+ Rebalance a basket with new instruments:
678
+
679
+ ```python
680
+ # Rebalance a basket
681
+ rebalance_response = client.rebalance_basket(
682
+ trading_symbol="/MY_BASKET",
683
+ instruments=["NSE:SBIN:3045", "NSE:HDFCBANK:1333", "NSE:TCS:2953"]
684
+ )
685
+ ```
686
+
687
+ ### Wizzer Client Examples
688
+
689
+ #### Market Data and Analysis Example
690
+
691
+ ```python
692
+ from wiz_trader import WizzerClient
693
+ import pandas as pd
694
+ import matplotlib.pyplot as plt
695
+
696
+ # Initialize client
697
+ client = WizzerClient(
698
+ base_url="https://api-url.in",
699
+ token="your-jwt-token"
700
+ )
701
+
702
+ def analyze_index_performance(index_symbol, exchange, start_date, end_date):
703
+ # Get index components
704
+ components = client.get_index_components(
705
+ trading_symbol=index_symbol,
706
+ exchange=exchange
707
+ )
708
+ print(f"Found {len(components)} components in {index_symbol}")
709
+
710
+ # Get the identifiers for the top 5 weighted components
711
+ top_components = sorted(components, key=lambda x: x.get('weightage', 0), reverse=True)[:5]
712
+ instrument_ids = [comp['identifier'] for comp in top_components]
713
+
714
+ # Fetch historical data
715
+ historical_data = client.get_historical_ohlcv(
716
+ instruments=instrument_ids,
717
+ start_date=start_date,
718
+ end_date=end_date,
719
+ ohlcv=["close"],
720
+ interval="1d"
721
+ )
722
+
723
+ # Process and plot the data
724
+ for instrument_data in historical_data:
725
+ symbol = instrument_data['instrument'].split(':')[1]
726
+ df = pd.DataFrame(instrument_data['data'])
727
+ df['date'] = pd.to_datetime(df['date'])
728
+ df.set_index('date', inplace=True)
729
+
730
+ # Calculate percentage change from first day
731
+ first_close = df['close'].iloc[0]
732
+ df['pct_change'] = ((df['close'] - first_close) / first_close) * 100
733
+
734
+ plt.plot(df.index, df['pct_change'], label=symbol)
735
+
736
+ plt.title(f"Performance of Top {index_symbol} Components")
737
+ plt.xlabel("Date")
738
+ plt.ylabel("% Change")
739
+ plt.legend()
740
+ plt.grid(True)
741
+ plt.show()
742
+
743
+ # Analyze NIFTY 50 performance for Jan 2024
744
+ analyze_index_performance(
745
+ index_symbol="NIFTY 50",
746
+ exchange="NSE",
747
+ start_date="2024-01-01",
748
+ end_date="2024-01-31"
749
+ )
750
+ ```
751
+
752
+ #### Algorithmic Trading Example
753
+
754
+ ```python
755
+ from wiz_trader import QuotesClient, WizzerClient
756
+ import time
757
+ import logging
758
+
759
+ logging.basicConfig(level=logging.INFO)
760
+ logger = logging.getLogger(__name__)
761
+
762
+ class SimpleMovingAverageCrossover:
763
+ def __init__(self, symbol, exchange, token, fast_period=5, slow_period=20):
764
+ # Trading parameters
765
+ self.symbol = symbol
766
+ self.exchange = exchange
767
+ self.token = token
768
+ self.instrument_id = f"{exchange}:{symbol}:{token}"
769
+ self.fast_period = fast_period
770
+ self.slow_period = slow_period
771
+
772
+ # State variables
773
+ self.prices = []
774
+ self.position = None
775
+ self.order_id = None
776
+
777
+ # Clients
778
+ self.quotes_client = QuotesClient(
779
+ base_url="wss://websocket-url/quotes",
780
+ token="your-jwt-token"
781
+ )
782
+ self.wizzer_client = WizzerClient(
783
+ base_url="https://api-url.in",
784
+ token="your-jwt-token"
785
+ )
786
+
787
+ # Set up quotes client callbacks
788
+ self.quotes_client.on_tick = self.on_tick
789
+ self.quotes_client.on_connect = self.on_connect
790
+ self.quotes_client.on_error = lambda ws, error: logger.error(f"Error: {error}")
791
+
792
+ def on_connect(self, ws):
793
+ logger.info(f"Connected to quotes server")
794
+ ws.subscribe([self.instrument_id])
795
+
796
+ def on_tick(self, ws, tick):
797
+ if "ltp" in tick:
798
+ price = tick["ltp"]
799
+ self.prices.append(price)
800
+
801
+ # Keep only the required number of prices
802
+ if len(self.prices) > self.slow_period:
803
+ self.prices.pop(0)
804
+
805
+ # Once we have enough data, check for signals
806
+ if len(self.prices) >= self.slow_period:
807
+ self.check_for_signals()
808
+
809
+ def check_for_signals(self):
810
+ # Calculate moving averages
811
+ fast_ma = sum(self.prices[-self.fast_period:]) / self.fast_period
812
+ slow_ma = sum(self.prices) / self.slow_period
813
+
814
+ current_price = self.prices[-1]
815
+
816
+ logger.info(f"Price: {current_price}, Fast MA: {fast_ma:.2f}, Slow MA: {slow_ma:.2f}")
817
+
818
+ # Generate signals
819
+ if fast_ma > slow_ma and self.position != "long":
820
+ # Buy signal
821
+ logger.info("BUY SIGNAL")
822
+ self.enter_position("long", current_price)
823
+
824
+ elif fast_ma < slow_ma and self.position == "long":
825
+ # Sell signal
826
+ logger.info("SELL SIGNAL")
827
+ self.exit_position(current_price)
828
+
829
+ def enter_position(self, position_type, price):
830
+ if position_type == "long":
831
+ # Place a buy order
832
+ try:
833
+ order_response = self.wizzer_client.place_order(
834
+ exchange=self.exchange,
835
+ trading_symbol=self.symbol,
836
+ transaction_type=self.wizzer_client.TRANSACTION_TYPE_BUY,
837
+ quantity=10, # Fixed quantity for simplicity
838
+ order_type=self.wizzer_client.ORDER_TYPE_MARKET,
839
+ product=self.wizzer_client.PRODUCT_CNC
840
+ )
841
+ self.order_id = order_response.get("orderId")
842
+ self.position = "long"
843
+ logger.info(f"Entered LONG position at {price}, Order ID: {self.order_id}")
844
+ except Exception as e:
845
+ logger.error(f"Error entering position: {e}")
846
+
847
+ def exit_position(self, price):
848
+ if self.position == "long":
849
+ # Place a sell order
850
+ try:
851
+ order_response = self.wizzer_client.place_order(
852
+ exchange=self.exchange,
853
+ trading_symbol=self.symbol,
854
+ transaction_type=self.wizzer_client.TRANSACTION_TYPE_SELL,
855
+ quantity=10, # Fixed quantity for simplicity
856
+ order_type=self.wizzer_client.ORDER_TYPE_MARKET,
857
+ product=self.wizzer_client.PRODUCT_CNC
858
+ )
859
+ logger.info(f"Exited LONG position at {price}, Order ID: {order_response.get('orderId')}")
860
+ self.position = None
861
+ self.order_id = None
862
+ except Exception as e:
863
+ logger.error(f"Error exiting position: {e}")
864
+
865
+ def run(self):
866
+ try:
867
+ logger.info("Starting the strategy...")
868
+ self.quotes_client.connect()
869
+ except KeyboardInterrupt:
870
+ logger.info("Strategy interrupted by user")
871
+ if self.position:
872
+ logger.info("Closing position before exit")
873
+ self.exit_position(self.prices[-1] if self.prices else 0)
874
+ self.quotes_client.stop()
875
+
876
+ # Run the strategy
877
+ strategy = SimpleMovingAverageCrossover(
878
+ symbol="SBIN",
879
+ exchange="NSE",
880
+ token=3045,
881
+ fast_period=5,
882
+ slow_period=20
883
+ )
884
+ strategy.run()
885
+ ```
886
+
887
+ #### Basket Management Example
888
+
889
+ ```python
890
+ from wiz_trader import WizzerClient
891
+ import json
892
+
893
+ # Initialize client
894
+ client = WizzerClient(
895
+ base_url="https://api-url.in",
896
+ token="your-jwt-token"
897
+ )
898
+
899
+ def create_and_trade_basket():
900
+ # Create a sector-based basket
901
+ banking_basket = client.create_basket(
902
+ name="Banking Basket",
903
+ instruments=[
904
+ {
905
+ "shares": 20,
906
+ "weightage": 25,
907
+ "instrument": {
908
+ "exchange": "NSE",
909
+ "identifier": "NSE:SBIN:3045",
910
+ "orderIndex": 1,
911
+ "exchangeToken": 3045,
912
+ "tradingSymbol": "SBIN"
913
+ }
914
+ },
915
+ {
916
+ "shares": 15,
917
+ "weightage": 25,
918
+ "instrument": {
919
+ "exchange": "NSE",
920
+ "identifier": "NSE:ICICIBANK:4963",
921
+ "orderIndex": 2,
922
+ "exchangeToken": 4963,
923
+ "tradingSymbol": "ICICIBANK"
924
+ }
925
+ },
926
+ {
927
+ "shares": 15,
928
+ "weightage": 25,
929
+ "instrument": {
930
+ "exchange": "NSE",
931
+ "identifier": "NSE:HDFCBANK:1333",
932
+ "orderIndex": 3,
933
+ "exchangeToken": 1333,
934
+ "tradingSymbol": "HDFCBANK"
935
+ }
936
+ },
937
+ {
938
+ "shares": 30,
939
+ "weightage": 25,
940
+ "instrument": {
941
+ "exchange": "NSE",
942
+ "identifier": "NSE:BANKBARODA:2263",
943
+ "orderIndex": 4,
944
+ "exchangeToken": 2263,
945
+ "tradingSymbol": "BANKBARODA"
946
+ }
947
+ }
948
+ ],
949
+ weightage_scheme="equi_weighted",
950
+ capital={"minValue": 100000, "actualValue": 100000},
951
+ instrument_types=["EQLC"]
952
+ )
953
+
954
+ basket_id = banking_basket.get('id')
955
+ basket_symbol = banking_basket.get('tradingSymbol')
956
+ exchange_token = banking_basket.get('basketInstrument', {}).get('exchangeToken')
957
+
958
+ print(f"Created basket: {basket_symbol} (ID: {basket_id})")
959
+
960
+ # Place a buy order for the basket
961
+ try:
962
+ order_response = client.place_basket_order(
963
+ trading_symbol=basket_symbol,
964
+ transaction_type=client.TRANSACTION_TYPE_BUY,
965
+ quantity=1, # Buy one unit of the basket
966
+ price=100000.00,
967
+ product=client.PRODUCT_CNC
968
+ )
969
+ print(f"Placed basket buy order: {order_response.get('orderId')}")
970
+
971
+ # After some time, exit the basket
972
+ # (This would normally be based on some strategy or time delay)
973
+ exit_response = client.place_basket_exit_order(
974
+ trading_symbol=basket_symbol,
975
+ exchange=client.EXCHANGE_WZR,
976
+ transaction_type=client.TRANSACTION_TYPE_SELL,
977
+ quantity=1,
978
+ exchange_token=exchange_token
979
+ )
980
+ print(f"Placed basket exit order: {exit_response.get('orderId')}")
981
+
982
+ except Exception as e:
983
+ print(f"Error during basket trading: {e}")
984
+
985
+ # Rebalance the basket (e.g., to adjust weightages or change stocks)
986
+ try:
987
+ rebalance_response = client.rebalance_basket(
988
+ trading_symbol=basket_symbol,
989
+ instruments=[
990
+ "NSE:SBIN:3045",
991
+ "NSE:HDFCBANK:1333",
992
+ "NSE:ICICIBANK:4963",
993
+ "NSE:AXISBANK:5900" # Replacing BANKBARODA with AXISBANK
994
+ ]
995
+ )
996
+ print(f"Rebalanced basket successfully")
997
+ except Exception as e:
998
+ print(f"Error during basket rebalancing: {e}")
999
+
1000
+ # Run the basket trading example
1001
+ create_and_trade_basket()
1002
+ ```
1003
+
1004
+ ## Common Use Cases
1005
+
1006
+ ### Backtesting with Historical Data
1007
+
1008
+ ```python
1009
+ from wiz_trader import WizzerClient
1010
+ import pandas as pd
1011
+ import numpy as np
1012
+ from datetime import datetime
1013
+
1014
+ # Initialize client
1015
+ client = WizzerClient(
1016
+ base_url="https://api-url.in",
1017
+ token="your-jwt-token"
1018
+ )
1019
+
1020
+ def backtest_simple_strategy(symbol, exchange, token, start_date, end_date,
1021
+ fast_period=10, slow_period=30):
1022
+ # Get historical data
1023
+ instrument_id = f"{exchange}:{symbol}:{token}"
1024
+ data = client.get_historical_ohlcv(
1025
+ instruments=[instrument_id],
1026
+ start_date=start_date,
1027
+ end_date=end_date,
1028
+ ohlcv=["open", "high", "low", "close", "volume"],
1029
+ interval="1d"
1030
+ )
1031
+
1032
+ if not data or not data[0].get('data'):
1033
+ print("No data available for the specified period")
1034
+ return
1035
+
1036
+ # Convert to DataFrame
1037
+ df = pd.DataFrame(data[0]['data'])
1038
+ df['date'] = pd.to_datetime(df['date'])
1039
+ df.set_index('date', inplace=True)
1040
+
1041
+ # Calculate moving averages
1042
+ df['fast_ma'] = df['close'].rolling(window=fast_period).mean()
1043
+ df['slow_ma'] = df['close'].rolling(window=slow_period).mean()
1044
+
1045
+ # Generate signals
1046
+ df['signal'] = 0
1047
+ df.loc[df['fast_ma'] > df['slow_ma'], 'signal'] = 1 # Buy signal
1048
+ df.loc[df['fast_ma'] < df['slow_ma'], 'signal'] = -1 # Sell signal
1049
+
1050
+ # Calculate returns
1051
+ df['returns'] = df['close'].pct_change()
1052
+ df['strategy_returns'] = df['signal'].shift(1) * df['returns']
1053
+
1054
+ # Calculate cumulative returns
1055
+ df['cumulative_returns'] = (1 + df['returns']).cumprod() - 1
1056
+ df['strategy_cumulative_returns'] = (1 + df['strategy_returns']).cumprod() - 1
1057
+
1058
+ # Calculate statistics
1059
+ total_days = len(df)
1060
+ winning_days = len(df[df['strategy_returns'] > 0])
1061
+ win_rate = winning_days / total_days if total_days > 0 else 0
1062
+
1063
+ strategy_return = df['strategy_cumulative_returns'].iloc[-1]
1064
+ buy_hold_return = df['cumulative_returns'].iloc[-1]
1065
+
1066
+ print(f"Backtest Results for {symbol} ({start_date} to {end_date}):")
1067
+ print(f"Strategy Return: {strategy_return:.2%}")
1068
+ print(f"Buy & Hold Return: {buy_hold_return:.2%}")
1069
+ print(f"Win Rate: {win_rate:.2%}")
1070
+ print(f"Total Trades: {df['signal'].diff().abs().sum() / 2:.0f}")
1071
+
1072
+ return df
1073
+
1074
+ # Run a backtest
1075
+ result_df = backtest_simple_strategy(
1076
+ symbol="SBIN",
1077
+ exchange="NSE",
1078
+ token=3045,
1079
+ start_date="2023-01-01",
1080
+ end_date="2023-12-31",
1081
+ fast_period=10,
1082
+ slow_period=30
1083
+ )
1084
+
1085
+ # Plot the results
1086
+ import matplotlib.pyplot as plt
1087
+
1088
+ plt.figure(figsize=(12, 8))
1089
+
1090
+ # Plot prices and moving averages
1091
+ plt.subplot(2, 1, 1)
1092
+ plt.plot(result_df.index, result_df['close'], label='Close Price')
1093
+ plt.plot(result_df.index, result_df['fast_ma'], label=f'Fast MA ({10} days)')
1094
+ plt.plot(result_df.index, result_df['slow_ma'], label=f'Slow MA ({30} days)')
1095
+ plt.title('Price and Moving Averages')
1096
+ plt.legend()
1097
+ plt.grid(True)
1098
+
1099
+ # Plot cumulative returns
1100
+ plt.subplot(2, 1, 2)
1101
+ plt.plot(result_df.index, result_df['cumulative_returns'], label='Buy & Hold')
1102
+ plt.plot(result_df.index, result_df['strategy_cumulative_returns'], label='Strategy')
1103
+ plt.title('Cumulative Returns')
1104
+ plt.legend()
1105
+ plt.grid(True)
1106
+
1107
+ plt.tight_layout()
1108
+ plt.show()
1109
+ ```
1110
+
1111
+ ### Real-time Portfolio Monitoring
1112
+
1113
+ ```python
1114
+ from wiz_trader import QuotesClient, WizzerClient
1115
+ import pandas as pd
1116
+ import time
1117
+ from datetime import datetime
1118
+
1119
+ # Initialize clients
1120
+ quotes_client = QuotesClient(
1121
+ base_url="wss://websocket-url/quotes",
1122
+ token="your-jwt-token",
1123
+ log_level="info"
1124
+ )
1125
+
1126
+ wizzer_client = WizzerClient(
1127
+ base_url="https://api-url.in",
1128
+ token="your-jwt-token",
1129
+ log_level="info"
1130
+ )
1131
+
1132
+ class PortfolioMonitor:
1133
+ def __init__(self):
1134
+ self.portfolio = {}
1135
+ self.last_update_time = None
1136
+ self.update_interval = 60 # seconds
1137
+
1138
+ def on_tick(self, ws, tick):
1139
+ """Process incoming market data"""
1140
+ if 'instrument' in tick and 'ltp' in tick:
1141
+ instrument = tick['instrument']
1142
+ ltp = tick['ltp']
1143
+
1144
+ if instrument in self.portfolio:
1145
+ # Update the current price
1146
+ self.portfolio[instrument]['current_price'] = ltp
1147
+
1148
+ # Calculate P&L
1149
+ avg_price = self.portfolio[instrument]['avg_price']
1150
+ qty = self.portfolio[instrument]['qty']
1151
+
1152
+ if qty > 0: # Long position
1153
+ pnl = (ltp - avg_price) * qty
1154
+ pnl_percent = ((ltp / avg_price) - 1) * 100
1155
+ else: # Short position
1156
+ pnl = (avg_price - ltp) * abs(qty)
1157
+ pnl_percent = ((avg_price / ltp) - 1) * 100
1158
+
1159
+ self.portfolio[instrument]['pnl'] = pnl
1160
+ self.portfolio[instrument]['pnl_percent'] = pnl_percent
1161
+
1162
+ # Display portfolio if update interval has passed
1163
+ current_time = time.time()
1164
+ if (self.last_update_time is None or
1165
+ current_time - self.last_update_time > self.update_interval):
1166
+ self.display_portfolio()
1167
+ self.last_update_time = current_time
1168
+
1169
+ def on_connect(self, ws):
1170
+ """Handle connection to quotes server"""
1171
+ print(f"Connected to quotes server at {datetime.now()}")
1172
+ # Fetch holdings and subscribe to them
1173
+ self.fetch_holdings()
1174
+
1175
+ def fetch_holdings(self):
1176
+ """Fetch holdings from the API and update portfolio"""
1177
+ try:
1178
+ holdings = wizzer_client.get_holdings()
1179
+
1180
+ # Extract holding information and update portfolio
1181
+ instruments_to_subscribe = []
1182
+ for holding in holdings:
1183
+ if holding.get('qty', 0) > 0:
1184
+ instrument = holding.get('identifier', '')
1185
+ if instrument:
1186
+ self.portfolio[instrument] = {
1187
+ 'symbol': holding.get('tradingSymbol', ''),
1188
+ 'exchange': holding.get('exchange', ''),
1189
+ 'qty': holding.get('qty', 0),
1190
+ 'avg_price': holding.get('avgPrice', 0),
1191
+ 'invested_value': holding.get('investedValue', 0),
1192
+ 'current_price': 0,
1193
+ 'pnl': 0,
1194
+ 'pnl_percent': 0
1195
+ }
1196
+ instruments_to_subscribe.append(instrument)
1197
+
1198
+ # Subscribe to these instruments for real-time updates
1199
+ if instruments_to_subscribe:
1200
+ quotes_client.subscribe(instruments_to_subscribe)
1201
+ print(f"Subscribed to {len(instruments_to_subscribe)} instruments")
1202
+ else:
1203
+ print("No holdings found to monitor")
1204
+
1205
+ except Exception as e:
1206
+ print(f"Error fetching holdings: {e}")
1207
+
1208
+ def display_portfolio(self):
1209
+ """Display the current portfolio status"""
1210
+ if not self.portfolio:
1211
+ print("Portfolio is empty")
1212
+ return
1213
+
1214
+ print("\n" + "="*80)
1215
+ print(f"Portfolio Status as of {datetime.now()}")
1216
+ print("="*80)
1217
+
1218
+ # Create a pandas DataFrame for nicer display
1219
+ df = pd.DataFrame.from_dict(self.portfolio, orient='index')
1220
+ df['pnl'] = df['pnl'].round(2)
1221
+ df['pnl_percent'] = df['pnl_percent'].round(2)
1222
+
1223
+ # Sort by P&L
1224
+ df = df.sort_values('pnl', ascending=False)
1225
+
1226
+ print(df[['symbol', 'qty', 'avg_price', 'current_price', 'pnl', 'pnl_percent']])
1227
+
1228
+ # Calculate total values
1229
+ total_invested = df['invested_value'].sum()
1230
+ total_current = (df['current_price'] * df['qty']).sum()
1231
+ total_pnl = df['pnl'].sum()
1232
+ total_pnl_percent = ((total_current / total_invested) - 1) * 100 if total_invested > 0 else 0
1233
+
1234
+ print("-"*80)
1235
+ print(f"Total Invested: ₹{total_invested:.2f}")
1236
+ print(f"Total Current Value: ₹{total_current:.2f}")
1237
+ print(f"Total P&L: ₹{total_pnl:.2f} ({total_pnl_percent:.2f}%)")
1238
+ print("="*80 + "\n")
1239
+
1240
+ # Create and run the portfolio monitor
1241
+ monitor = PortfolioMonitor()
1242
+
1243
+ # Set up callbacks
1244
+ quotes_client.on_tick = monitor.on_tick
1245
+ quotes_client.on_connect = monitor.on_connect
1246
+ quotes_client.on_error = lambda ws, error: print(f"Error: {error}")
1247
+
1248
+ # Start monitoring (blocking call)
1249
+ try:
1250
+ quotes_client.connect()
1251
+ except KeyboardInterrupt:
1252
+ print("\nPortfolio monitoring stopped by user")
1253
+ quotes_client.stop()
1254
+ ```
1255
+
1256
+ ### Multi-Strategy Trading
1257
+
1258
+ ```python
1259
+ from wiz_trader import QuotesClient, WizzerClient
1260
+ import pandas as pd
1261
+ import threading
1262
+ import time
1263
+ from datetime import datetime
1264
+ import numpy as np
1265
+
1266
+ # Initialize API clients
1267
+ quotes_client = QuotesClient(
1268
+ base_url="wss://websocket-url/quotes",
1269
+ token="your-jwt-token",
1270
+ log_level="info"
1271
+ )
1272
+
1273
+ wizzer_client = WizzerClient(
1274
+ base_url="https://api-url.in",
1275
+ token="your-jwt-token",
1276
+ log_level="info"
1277
+ )
1278
+
1279
+ class TradingStrategy:
1280
+ """Base class for trading strategies"""
1281
+ def __init__(self, name, symbols):
1282
+ self.name = name
1283
+ self.symbols = symbols
1284
+ self.active = False
1285
+ self.positions = {}
1286
+ self.prices = {}
1287
+
1288
+ def on_tick(self, tick):
1289
+ """Process tick data - should be implemented by subclasses"""
1290
+ pass
1291
+
1292
+ def start(self):
1293
+ """Start the strategy"""
1294
+ self.active = True
1295
+ print(f"Strategy {self.name} started at {datetime.now()}")
1296
+
1297
+ def stop(self):
1298
+ """Stop the strategy"""
1299
+ self.active = False
1300
+ print(f"Strategy {self.name} stopped at {datetime.now()}")
1301
+
1302
+ # Close any open positions
1303
+ self.close_all_positions()
1304
+
1305
+ def close_all_positions(self):
1306
+ """Close all open positions"""
1307
+ for symbol, position in list(self.positions.items()):
1308
+ if position != 0:
1309
+ try:
1310
+ self.execute_order(
1311
+ symbol=symbol.split(':')[1],
1312
+ exchange=symbol.split(':')[0],
1313
+ transaction_type="SELL" if position > 0 else "BUY",
1314
+ quantity=abs(position)
1315
+ )
1316
+ print(f"Closed position for {symbol}: {position} units")
1317
+ self.positions[symbol] = 0
1318
+ except Exception as e:
1319
+ print(f"Error closing position for {symbol}: {e}")
1320
+
1321
+ def execute_order(self, symbol, exchange, transaction_type, quantity):
1322
+ """Execute an order through the API"""
1323
+ try:
1324
+ response = wizzer_client.place_order(
1325
+ exchange=exchange,
1326
+ trading_symbol=symbol,
1327
+ transaction_type=transaction_type,
1328
+ quantity=quantity,
1329
+ order_type=wizzer_client.ORDER_TYPE_MARKET,
1330
+ product=wizzer_client.PRODUCT_CNC
1331
+ )
1332
+ print(f"Order executed: {exchange}:{symbol} {transaction_type} {quantity} units")
1333
+ return response.get('orderId')
1334
+ except Exception as e:
1335
+ print(f"Order execution error: {e}")
1336
+ return None
1337
+
1338
+ class MovingAverageCrossover(TradingStrategy):
1339
+ """Moving Average Crossover Strategy"""
1340
+ def __init__(self, name, symbols, fast_period=10, slow_period=30):
1341
+ super().__init__(name, symbols)
1342
+ self.fast_period = fast_period
1343
+ self.slow_period = slow_period
1344
+ self.price_history = {s: [] for s in symbols}
1345
+
1346
+ def on_tick(self, tick):
1347
+ if not self.active:
1348
+ return
1349
+
1350
+ instrument = tick.get('instrument')
1351
+ ltp = tick.get('ltp')
1352
+
1353
+ if instrument in self.symbols and ltp is not None:
1354
+ # Store the current price
1355
+ self.prices[instrument] = ltp
1356
+
1357
+ # Add to price history
1358
+ self.price_history[instrument].append(ltp)
1359
+
1360
+ # Keep only enough prices for the slow MA
1361
+ if len(self.price_history[instrument]) > self.slow_period:
1362
+ self.price_history[instrument].pop(0)
1363
+
1364
+ # Check for trading signals if we have enough data
1365
+ if len(self.price_history[instrument]) >= self.slow_period:
1366
+ self.check_signals(instrument)
1367
+
1368
+ def check_signals(self, instrument):
1369
+ prices = self.price_history[instrument]
1370
+
1371
+ # Calculate moving averages
1372
+ fast_ma = sum(prices[-self.fast_period:]) / self.fast_period
1373
+ slow_ma = sum(prices) / self.slow_period
1374
+
1375
+ # Get current position and price
1376
+ current_position = self.positions.get(instrument, 0)
1377
+ current_price = self.prices[instrument]
1378
+
1379
+ # Generate signals
1380
+ if fast_ma > slow_ma and current_position <= 0:
1381
+ # Buy signal
1382
+ quantity = 10 # Fixed quantity for simplicity
1383
+
1384
+ # Close any short position first
1385
+ if current_position < 0:
1386
+ self.execute_order(
1387
+ symbol=instrument.split(':')[1],
1388
+ exchange=instrument.split(':')[0],
1389
+ transaction_type="BUY",
1390
+ quantity=abs(current_position)
1391
+ )
1392
+ self.positions[instrument] = 0
1393
+
1394
+ # Enter long position
1395
+ self.execute_order(
1396
+ symbol=instrument.split(':')[1],
1397
+ exchange=instrument.split(':')[0],
1398
+ transaction_type="BUY",
1399
+ quantity=quantity
1400
+ )
1401
+ self.positions[instrument] = quantity
1402
+
1403
+ print(f"{self.name}: BUY {quantity} units of {instrument} at {current_price}")
1404
+
1405
+ elif fast_ma < slow_ma and current_position >= 0:
1406
+ # Sell signal
1407
+
1408
+ # Close any long position
1409
+ if current_position > 0:
1410
+ self.execute_order(
1411
+ symbol=instrument.split(':')[1],
1412
+ exchange=instrument.split(':')[0],
1413
+ transaction_type="SELL",
1414
+ quantity=current_position
1415
+ )
1416
+ self.positions[instrument] = 0
1417
+
1418
+ print(f"{self.name}: SELL {current_position} units of {instrument} at {current_price}")
1419
+
1420
+ # Option: Enter short position (if allowed)
1421
+ # quantity = 10 # Fixed quantity for simplicity
1422
+ # self.execute_order(
1423
+ # symbol=instrument.split(':')[1],
1424
+ # exchange=instrument.split(':')[0],
1425
+ # transaction_type="SELL",
1426
+ # quantity=quantity
1427
+ # )
1428
+ # self.positions[instrument] = -quantity
1429
+ # print(f"{self.name}: SHORT {quantity} units of {instrument} at {current_price}")
1430
+
1431
+ class BollingerBands(TradingStrategy):
1432
+ """Bollinger Bands Strategy"""
1433
+ def __init__(self, name, symbols, period=20, std_dev=2):
1434
+ super().__init__(name, symbols)
1435
+ self.period = period
1436
+ self.std_dev = std_dev
1437
+ self.price_history = {s: [] for s in symbols}
1438
+
1439
+ def on_tick(self, tick):
1440
+ if not self.active:
1441
+ return
1442
+
1443
+ instrument = tick.get('instrument')
1444
+ ltp = tick.get('ltp')
1445
+
1446
+ if instrument in self.symbols and ltp is not None:
1447
+ # Store the current price
1448
+ self.prices[instrument] = ltp
1449
+
1450
+ # Add to price history
1451
+ self.price_history[instrument].append(ltp)
1452
+
1453
+ # Keep only enough prices for the calculation
1454
+ if len(self.price_history[instrument]) > self.period:
1455
+ self.price_history[instrument].pop(0)
1456
+
1457
+ # Check for trading signals if we have enough data
1458
+ if len(self.price_history[instrument]) >= self.period:
1459
+ self.check_signals(instrument)
1460
+
1461
+ def check_signals(self, instrument):
1462
+ prices = self.price_history[instrument]
1463
+
1464
+ # Calculate Bollinger Bands
1465
+ sma = sum(prices) / len(prices)
1466
+ std = np.std(prices)
1467
+ upper_band = sma + (std * self.std_dev)
1468
+ lower_band = sma - (std * self.std_dev)
1469
+
1470
+ # Get current position and price
1471
+ current_position = self.positions.get(instrument, 0)
1472
+ current_price = self.prices[instrument]
1473
+
1474
+ # Generate signals
1475
+ if current_price < lower_band and current_position <= 0:
1476
+ # Buy signal (price below lower band)
1477
+ quantity = 10 # Fixed quantity for simplicity
1478
+
1479
+ # Close any short position first
1480
+ if current_position < 0:
1481
+ self.execute_order(
1482
+ symbol=instrument.split(':')[1],
1483
+ exchange=instrument.split(':')[0],
1484
+ transaction_type="BUY",
1485
+ quantity=abs(current_position)
1486
+ )
1487
+ self.positions[instrument] = 0
1488
+
1489
+ # Enter long position
1490
+ self.execute_order(
1491
+ symbol=instrument.split(':')[1],
1492
+ exchange=instrument.split(':')[0],
1493
+ transaction_type="BUY",
1494
+ quantity=quantity
1495
+ )
1496
+ self.positions[instrument] = quantity
1497
+
1498
+ print(f"{self.name}: BUY {quantity} units of {instrument} at {current_price} (below lower band {lower_band:.2f})")
1499
+
1500
+ elif current_price > upper_band and current_position >= 0:
1501
+ # Sell signal (price above upper band)
1502
+
1503
+ # Close any long position
1504
+ if current_position > 0:
1505
+ self.execute_order(
1506
+ symbol=instrument.split(':')[1],
1507
+ exchange=instrument.split(':')[0],
1508
+ transaction_type="SELL",
1509
+ quantity=current_position
1510
+ )
1511
+ self.positions[instrument] = 0
1512
+
1513
+ print(f"{self.name}: SELL {current_position} units of {instrument} at {current_price} (above upper band {upper_band:.2f})")
1514
+
1515
+ class MultiStrategyManager:
1516
+ """Manages multiple trading strategies"""
1517
+ def __init__(self, quotes_client):
1518
+ self.quotes_client = quotes_client
1519
+ self.strategies = {}
1520
+
1521
+ def add_strategy(self, strategy):
1522
+ """Add a strategy to the manager"""
1523
+ self.strategies[strategy.name] = strategy
1524
+
1525
+ # Subscribe to all strategy symbols
1526
+ for symbol in strategy.symbols:
1527
+ if not symbol.startswith('NSE:') and not symbol.startswith('BSE:'):
1528
+ print(f"Warning: Symbol {symbol} does not include exchange prefix")
1529
+
1530
+ def on_tick(self, ws, tick):
1531
+ """Process ticks and distribute to strategies"""
1532
+ for strategy in self.strategies.values():
1533
+ strategy.on_tick(tick)
1534
+
1535
+ def on_connect(self, ws):
1536
+ """Handle connection to quotes server"""
1537
+ print(f"Connected to quotes server at {datetime.now()}")
1538
+
1539
+ # Collect all symbols from all strategies
1540
+ all_symbols = set()
1541
+ for strategy in self.strategies.values():
1542
+ all_symbols.update(strategy.symbols)
1543
+
1544
+ # Subscribe to all symbols
1545
+ if all_symbols:
1546
+ ws.subscribe(list(all_symbols))
1547
+ print(f"Subscribed to {len(all_symbols)} symbols")
1548
+
1549
+ def start_all(self):
1550
+ """Start all strategies"""
1551
+ for strategy in self.strategies.values():
1552
+ strategy.start()
1553
+
1554
+ def stop_all(self):
1555
+ """Stop all strategies"""
1556
+ for strategy in self.strategies.values():
1557
+ strategy.stop()
1558
+
1559
+ def start_strategy(self, name):
1560
+ """Start a specific strategy"""
1561
+ if name in self.strategies:
1562
+ self.strategies[name].start()
1563
+ else:
1564
+ print(f"Strategy {name} not found")
1565
+
1566
+ def stop_strategy(self, name):
1567
+ """Stop a specific strategy"""
1568
+ if name in self.strategies:
1569
+ self.strategies[name].stop()
1570
+ else:
1571
+ print(f"Strategy {name} not found")
1572
+
1573
+ def run(self):
1574
+ """Run the strategy manager"""
1575
+ # Set up callbacks
1576
+ self.quotes_client.on_tick = self.on_tick
1577
+ self.quotes_client.on_connect = self.on_connect
1578
+ self.quotes_client.on_error = lambda ws, error: print(f"Error: {error}")
1579
+
1580
+ # Start all strategies
1581
+ self.start_all()
1582
+
1583
+ # Start the quotes client (blocking call)
1584
+ try:
1585
+ self.quotes_client.connect()
1586
+ except KeyboardInterrupt:
1587
+ print("\nMulti-strategy manager stopped by user")
1588
+ self.stop_all()
1589
+ self.quotes_client.stop()
1590
+
1591
+ # Create strategies
1592
+ ma_strategy = MovingAverageCrossover(
1593
+ name="MA Crossover",
1594
+ symbols=["NSE:SBIN:3045", "NSE:ICICIBANK:4963"],
1595
+ fast_period=10,
1596
+ slow_period=30
1597
+ )
1598
+
1599
+ bb_strategy = BollingerBands(
1600
+ name="Bollinger Bands",
1601
+ symbols=["NSE:RELIANCE:2885", "NSE:TCS:2953"],
1602
+ period=20,
1603
+ std_dev=2
1604
+ )
1605
+
1606
+ # Create and run the multi-strategy manager
1607
+ manager = MultiStrategyManager(quotes_client)
1608
+ manager.add_strategy(ma_strategy)
1609
+ manager.add_strategy(bb_strategy)
1610
+ manager.run()
1611
+ ```
1612
+
1613
+ ## Error Handling
1614
+
1615
+ The SDK provides several mechanisms for handling errors:
1616
+
1617
+ ### Exception Handling
1618
+
1619
+ All API calls can throw exceptions, which should be caught and handled:
1620
+
1621
+ ```python
1622
+ try:
1623
+ order_response = client.place_order(
1624
+ exchange=client.EXCHANGE_NSE,
1625
+ trading_symbol="SBIN",
1626
+ transaction_type=client.TRANSACTION_TYPE_BUY,
1627
+ quantity=10,
1628
+ order_type=client.ORDER_TYPE_MARKET,
1629
+ product=client.PRODUCT_CNC
1630
+ )
1631
+ print(f"Order placed successfully: {order_response.get('orderId')}")
1632
+ except Exception as e:
1633
+ print(f"Error placing order: {e}")
1634
+ ```
1635
+
1636
+ ### Callback Error Handling
1637
+
1638
+ For the WebSocket client, errors are also sent via the `on_error` callback:
1639
+
1640
+ ```python
1641
+ def on_error(ws, error):
1642
+ """Handle WebSocket errors"""
1643
+ print(f"WebSocket error: {error}")
1644
+
1645
+ # You could implement reconnection logic or alerting here
1646
+ if isinstance(error, ConnectionRefusedError):
1647
+ print("Connection refused. Check server status.")
1648
+ elif isinstance(error, TimeoutError):
1649
+ print("Connection timed out. Check network.")
1650
+
1651
+ quotes_client.on_error = on_error
1652
+ ```
1653
+
1654
+ ### Logging
1655
+
1656
+ Both clients support different log levels:
1657
+
1658
+ ```python
1659
+ client = WizzerClient(
1660
+ base_url="https://api-url.in",
1661
+ token="your-jwt-token",
1662
+ log_level="debug" # Options: "error", "info", "debug"
1663
+ )
1664
+ ```
1665
+
1666
+ The logs can help diagnose issues during development and production.
1667
+
1668
+ ## Troubleshooting
1669
+
1670
+ ### Common Issues
1671
+
1672
+ #### Authentication Issues
1673
+
1674
+ If you're facing authentication errors:
1675
+
1676
+ 1. Check that your token is valid and not expired
1677
+ 2. Ensure you're using the correct base_url for production or development
1678
+ 3. Verify environment variables if you're using them
1679
+
1680
+ Example of token validation:
1681
+ ```python
1682
+ def is_token_valid(token):
1683
+ """Basic validation of JWT token format"""
1684
+ parts = token.split('.')
1685
+ if len(parts) != 3:
1686
+ return False
1687
+
1688
+ # Check if the token is expired
1689
+ import base64
1690
+ import json
1691
+ import time
1692
+
1693
+ try:
1694
+ # Decode the payload
1695
+ payload = parts[1]
1696
+ # Add padding if needed
1697
+ payload += '=' * (4 - len(payload) % 4) if len(payload) % 4 != 0 else ''
1698
+ decoded = base64.b64decode(payload)
1699
+ data = json.loads(decoded)
1700
+
1701
+ # Check expiration
1702
+ if 'exp' in data:
1703
+ return data['exp'] > time.time()
1704
+ return True
1705
+ except Exception:
1706
+ return False
1707
+
1708
+ # Check if token is valid
1709
+ if not is_token_valid(token):
1710
+ print("Token is invalid or expired. Please get a new token.")
1711
+ ```
1712
+
1713
+ #### WebSocket Connection Issues
1714
+
1715
+ If you're having trouble connecting to the WebSocket server:
1716
+
1717
+ 1. Check network connectivity and firewall settings
1718
+ 2. Verify the WebSocket URL is correct
1719
+ 3. Check if the server supports SSL/TLS if using 'wss://' protocol
1720
+ 4. Try with a smaller `max_message_size` to rule out size limitations
1721
+
1722
+ #### Order Placement Failures
1723
+
1724
+ If orders are not being placed successfully:
1725
+
1726
+ 1. Check the error message from the API response
1727
+ 2. Verify that you're using the correct exchange, symbol, and token
1728
+ 3. Ensure you have sufficient funds/holdings for the trade
1729
+ 4. Check that market hours are active for the segment you're trading
1730
+
1731
+ ## API Reference
1732
+
1733
+ For a complete list of all available methods and parameters, refer to the class docstrings within the SDK.
1734
+
1735
+ ```python
1736
+ # Example of getting detailed help on a method
1737
+ help(WizzerClient.place_order)
1738
+ ```
1739
+
1740
+ ### Environment Variables
1741
+
1742
+ All supported environment variables:
1743
+
1744
+ - `WZ__QUOTES_BASE_URL`: WebSocket URL for the quotes server
1745
+ - `WZ__API_BASE_URL`: Base URL for the Wizzer's REST API
1746
+ - `WZ__TOKEN`: JWT token for authentication
1747
+ - `WZ__STRATEGY_ID`: Default strategy ID to use if not provided in methods
1748
+
1749
+ ### Full Method List
1750
+
1751
+ #### QuotesClient
1752
+ - `__init__(base_url, token, log_level, max_message_size, batch_size)`
1753
+ - `connect()` - Connect in blocking mode
1754
+ - `connect_async()` - Connect in non-blocking mode
1755
+ - `stop()` - Stop the WebSocket connection
1756
+ - `subscribe(instruments)` - Subscribe to instruments
1757
+ - `unsubscribe(instruments)` - Unsubscribe from instruments
1758
+
1759
+ #### WizzerClient
1760
+ - `__init__(base_url, token, strategy_id, log_level)`
1761
+ - `get_indices(trading_symbol, exchange)`
1762
+ - `get_index_components(trading_symbol, exchange)`
1763
+ - `get_historical_ohlcv(instruments, start_date, end_date, ohlcv, interval)`
1764
+ - `place_order(exchange, trading_symbol, transaction_type, quantity, ...)`
1765
+ - `modify_order(order_id, **params)`
1766
+ - `cancel_order(order_id)`
1767
+ - `get_order(order_id)`
1768
+ - `get_positions(position_status)`
1769
+ - `get_open_positions()`
1770
+ - `get_closed_positions()`
1771
+ - `get_holdings(portfolios)`
1772
+ - `create_basket(name, instruments, weightage_scheme, capital, instrument_types)`
1773
+ - `get_baskets()`
1774
+ - `get_basket(basket_id)`
1775
+ - `get_basket_instruments(basket_id)`
1776
+ - `place_basket_order(trading_symbol, transaction_type, quantity, ...)`
1777
+ - `place_basket_exit_order(trading_symbol, exchange, transaction_type, quantity, exchange_token, **kwargs)`
1778
+ - `modify_basket_order(order_id, **params)`
1779
+ - `rebalance_basket(trading_symbol, instruments)`
1780
+ - `exit_all_positions()`
1781
+ - `exit_strategy_positions(strategy_id)`