wiz-trader 0.8.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.
- wiz_trader/__init__.py +1 -1
- wiz_trader/apis/client.py +176 -44
- wiz_trader/quotes/client.py +154 -29
- wiz_trader-0.10.0.dist-info/METADATA +1781 -0
- wiz_trader-0.10.0.dist-info/RECORD +9 -0
- {wiz_trader-0.8.0.dist-info → wiz_trader-0.10.0.dist-info}/WHEEL +1 -1
- wiz_trader-0.8.0.dist-info/METADATA +0 -165
- wiz_trader-0.8.0.dist-info/RECORD +0 -9
- {wiz_trader-0.8.0.dist-info → wiz_trader-0.10.0.dist-info}/top_level.txt +0 -0
@@ -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)`
|