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