kkunal 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- kkunal-1.0.0/LICENSE +21 -0
- kkunal-1.0.0/PKG-INFO +410 -0
- kkunal-1.0.0/README.md +386 -0
- kkunal-1.0.0/choice_api/__init__.py +22 -0
- kkunal-1.0.0/choice_api/client.py +190 -0
- kkunal-1.0.0/choice_api/funds.py +69 -0
- kkunal-1.0.0/choice_api/historical.py +87 -0
- kkunal-1.0.0/choice_api/market.py +22 -0
- kkunal-1.0.0/choice_api/orders.py +93 -0
- kkunal-1.0.0/choice_api/portfolio.py +43 -0
- kkunal-1.0.0/choice_api/scrip_master.py +105 -0
- kkunal-1.0.0/choice_api/websockets_feed.py +106 -0
- kkunal-1.0.0/choice_api/websockets_interactive.py +94 -0
- kkunal-1.0.0/kkunal.egg-info/PKG-INFO +410 -0
- kkunal-1.0.0/kkunal.egg-info/SOURCES.txt +19 -0
- kkunal-1.0.0/kkunal.egg-info/dependency_links.txt +1 -0
- kkunal-1.0.0/kkunal.egg-info/requires.txt +4 -0
- kkunal-1.0.0/kkunal.egg-info/top_level.txt +1 -0
- kkunal-1.0.0/pyproject.toml +3 -0
- kkunal-1.0.0/setup.cfg +4 -0
- kkunal-1.0.0/setup.py +26 -0
kkunal-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Kkunal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT/TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
kkunal-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,410 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: kkunal
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Kkunal - Python library for Choice FINX Trading API
|
|
5
|
+
Author: Kkunal
|
|
6
|
+
Classifier: Programming Language :: Python :: 3
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Requires-Python: >=3.7
|
|
10
|
+
Description-Content-Type: text/markdown
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Requires-Dist: requests>=2.28.0
|
|
13
|
+
Requires-Dist: pycryptodome>=3.17.0
|
|
14
|
+
Requires-Dist: websockets>=11.0.3
|
|
15
|
+
Requires-Dist: pandas>=1.3.0
|
|
16
|
+
Dynamic: author
|
|
17
|
+
Dynamic: classifier
|
|
18
|
+
Dynamic: description
|
|
19
|
+
Dynamic: description-content-type
|
|
20
|
+
Dynamic: license-file
|
|
21
|
+
Dynamic: requires-dist
|
|
22
|
+
Dynamic: requires-python
|
|
23
|
+
Dynamic: summary
|
|
24
|
+
|
|
25
|
+
# Kkunal
|
|
26
|
+
|
|
27
|
+
A Python library for the Choice FINX Trading API. Supports REST API, Interactive WebSockets (order/trade updates), and Live Price Feed WebSockets (FIX3.0 compressed data).
|
|
28
|
+
|
|
29
|
+
## Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install kkunal
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
All dependencies (`requests`, `pycryptodome`, `websockets`, `pandas`) are installed automatically.
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## Quick Start
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from choice_api import ChoiceClient
|
|
43
|
+
|
|
44
|
+
client = ChoiceClient(
|
|
45
|
+
vendor_id="YOUR_VENDOR_ID",
|
|
46
|
+
vendor_key="YOUR_VENDOR_KEY",
|
|
47
|
+
api_key="YOUR_JWT_BEARER_TOKEN",
|
|
48
|
+
aes_key="YOUR_AES_KEY",
|
|
49
|
+
aes_iv="YOUR_AES_IV"
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
# Login (TOTP flow is handled automatically)
|
|
53
|
+
session_id = client.login(mobile_no="1234567890")
|
|
54
|
+
print(f"Session ID: {session_id}")
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Session Persistence
|
|
58
|
+
|
|
59
|
+
You can save and reload sessions to avoid logging in repeatedly during the same trading day:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
session_file = "my_session.json"
|
|
63
|
+
|
|
64
|
+
if client.load_session(session_file):
|
|
65
|
+
print("Restored today's session.")
|
|
66
|
+
else:
|
|
67
|
+
client.login(mobile_no="1234567890")
|
|
68
|
+
client.save_session(session_file)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
> **Note:** Sessions expire daily. `load_session` will return `False` if the saved session is from a previous day.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## Scrip Master
|
|
76
|
+
|
|
77
|
+
The Scrip Master CSV is automatically downloaded when you log in. It maps instrument symbols to their tokens, lot sizes, and other metadata.
|
|
78
|
+
|
|
79
|
+
### `get_token(symbol, exchange=None)`
|
|
80
|
+
|
|
81
|
+
Returns the token for a given symbol.
|
|
82
|
+
|
|
83
|
+
- For **NSE** instruments, no `exchange` parameter is needed (returns NSE by default).
|
|
84
|
+
- For **BSE** instruments, pass `exchange="BSE"` explicitly.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
# NSE (default)
|
|
88
|
+
reliance_token = client.scrip_master.get_token("RELIANCE")
|
|
89
|
+
|
|
90
|
+
# BSE (must specify exchange)
|
|
91
|
+
reliance_bse_token = client.scrip_master.get_token("RELIANCE", exchange="BSE")
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### `get_details(token)`
|
|
95
|
+
|
|
96
|
+
Returns all CSV row details for a given token as a dictionary.
|
|
97
|
+
|
|
98
|
+
```python
|
|
99
|
+
details = client.scrip_master.get_details("2885")
|
|
100
|
+
print(details)
|
|
101
|
+
# {'Exchange': 'NSE', 'Segment': '1', 'Token': '2885', 'Symbol': 'RELIANCE', ...}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### `get_lot_size(token)`
|
|
105
|
+
|
|
106
|
+
Returns the market lot size for a token.
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
lot = client.scrip_master.get_lot_size("2885")
|
|
110
|
+
print(lot) # 1 for equity, 250 for NIFTY futures, etc.
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
---
|
|
114
|
+
|
|
115
|
+
## Orders
|
|
116
|
+
|
|
117
|
+
> **Important:** Prices must be in **paisa** (multiply INR by 100). For F&O orders, `qty` must be in **total shares** (multiples of the lot size), not the number of lots.
|
|
118
|
+
|
|
119
|
+
### `client.orders.place_order(...)`
|
|
120
|
+
|
|
121
|
+
| Parameter | Type | Description |
|
|
122
|
+
|---|---|---|
|
|
123
|
+
| `segment_id` | `int` | `1` = NSE Cash, `2` = NSE F&O, `3` = BSE Cash |
|
|
124
|
+
| `token` | `int` | Instrument token from Scrip Master |
|
|
125
|
+
| `order_type` | `str` | `"RL_MKT"` = Market, `"RL_LIMIT"` = Limit |
|
|
126
|
+
| `bs` | `int` | `1` = Buy, `2` = Sell |
|
|
127
|
+
| `qty` | `int` | Total quantity in shares |
|
|
128
|
+
| `price` | `float` | Price in paisa (e.g., 1300 INR → `130000`) |
|
|
129
|
+
| `trigger_price` | `float` | Trigger price in paisa (0 for non-SL orders) |
|
|
130
|
+
| `validity` | `int` | `1` = Day |
|
|
131
|
+
| `product_type` | `str` | `"M"` = Intraday (Margin), `"D"` = Delivery/CarryForward |
|
|
132
|
+
| `disclosed_qty` | `int` | Optional. Disclosed quantity (default `0`) |
|
|
133
|
+
|
|
134
|
+
```python
|
|
135
|
+
response = client.orders.place_order(
|
|
136
|
+
segment_id=1,
|
|
137
|
+
token=2885,
|
|
138
|
+
order_type="RL_MKT",
|
|
139
|
+
bs=1,
|
|
140
|
+
qty=1,
|
|
141
|
+
price=0,
|
|
142
|
+
trigger_price=0,
|
|
143
|
+
validity=1,
|
|
144
|
+
product_type="D"
|
|
145
|
+
)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### `client.orders.modify_order(...)`
|
|
149
|
+
|
|
150
|
+
Modifies an existing order. Requires `client_order_no`, `exchange_order_no`, and `gateway_order_no` from the order book.
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
response = client.orders.modify_order(
|
|
154
|
+
client_order_no=123456,
|
|
155
|
+
exchange_order_no="1234567890",
|
|
156
|
+
gateway_order_no="1234567890",
|
|
157
|
+
segment_id=1,
|
|
158
|
+
token=2885,
|
|
159
|
+
order_type="RL_LIMIT",
|
|
160
|
+
bs=1,
|
|
161
|
+
qty=1,
|
|
162
|
+
price=130000,
|
|
163
|
+
trigger_price=0,
|
|
164
|
+
validity=1,
|
|
165
|
+
product_type="D"
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### `client.orders.cancel_order(...)`
|
|
170
|
+
|
|
171
|
+
Cancels an existing order. Same parameters as `modify_order` plus optional `exchange_order_time`.
|
|
172
|
+
|
|
173
|
+
### `client.orders.get_order_book()`
|
|
174
|
+
|
|
175
|
+
Returns all orders placed during the current session.
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
order_book = client.orders.get_order_book()
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### `client.orders.get_order_book_v2()`
|
|
182
|
+
|
|
183
|
+
Returns the order book (version 2 format).
|
|
184
|
+
|
|
185
|
+
### `client.orders.get_order_by_no(order_no)`
|
|
186
|
+
|
|
187
|
+
Returns details for a specific order number.
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
order = client.orders.get_order_by_no(123456)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### `client.orders.get_trade_book()`
|
|
194
|
+
|
|
195
|
+
Returns all executed trades.
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
trades = client.orders.get_trade_book()
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### `client.orders.get_order_messages(req_id)`
|
|
202
|
+
|
|
203
|
+
Returns order-related messages for a given request ID.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Portfolio
|
|
208
|
+
|
|
209
|
+
### `client.portfolio.get_holdings()`
|
|
210
|
+
|
|
211
|
+
Returns current holdings.
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
holdings = client.portfolio.get_holdings()
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### `client.portfolio.get_net_position()`
|
|
218
|
+
|
|
219
|
+
Returns net positions.
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
positions = client.portfolio.get_net_position()
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### `client.portfolio.position_conversion(...)`
|
|
226
|
+
|
|
227
|
+
Converts an open position from one product type to another (e.g., Intraday to Delivery).
|
|
228
|
+
|
|
229
|
+
| Parameter | Type | Description |
|
|
230
|
+
|---|---|---|
|
|
231
|
+
| `segment_id` | `int` | Exchange segment |
|
|
232
|
+
| `token` | `int` | Instrument token |
|
|
233
|
+
| `client_order_no` | `int` | Client order number |
|
|
234
|
+
| `buy_sell` | `int` | `1` = Buy, `2` = Sell |
|
|
235
|
+
| `quantity` | `int` | Quantity to convert |
|
|
236
|
+
| `product_type` | `str` | Target product type |
|
|
237
|
+
| `source_product_type` | `str` | Current product type |
|
|
238
|
+
|
|
239
|
+
### `client.portfolio.verify_dis(...)`
|
|
240
|
+
|
|
241
|
+
Verifies eDIS (Electronic Delivery Instruction Slip) for delivery sell orders.
|
|
242
|
+
|
|
243
|
+
### `client.portfolio.get_dis_status()`
|
|
244
|
+
|
|
245
|
+
Returns the current DIS verification status.
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Funds
|
|
250
|
+
|
|
251
|
+
### `client.funds.get_funds_view()`
|
|
252
|
+
|
|
253
|
+
Returns funds summary.
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
funds = client.funds.get_funds_view()
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### `client.funds.get_funds_view_new()`
|
|
260
|
+
|
|
261
|
+
Returns funds summary in the new format.
|
|
262
|
+
|
|
263
|
+
### `client.funds.process_payout(amount, bank_acc_no, product_type=0)`
|
|
264
|
+
|
|
265
|
+
Initiates a fund withdrawal.
|
|
266
|
+
|
|
267
|
+
### `client.funds.payment_via_netbanking(amount, bank_acc_no, bank_ifsc_code, return_url, segment_id, product_type=0)`
|
|
268
|
+
|
|
269
|
+
Initiates a net banking payment.
|
|
270
|
+
|
|
271
|
+
### `client.funds.payment_via_hdfc_upi(amount, bank_acc_no, user_vpa, segment_id, product_type=0)`
|
|
272
|
+
|
|
273
|
+
Initiates a HDFC UPI payment.
|
|
274
|
+
|
|
275
|
+
### `client.funds.check_vpa(user_vpa)`
|
|
276
|
+
|
|
277
|
+
Validates a UPI VPA address.
|
|
278
|
+
|
|
279
|
+
### `client.funds.payment_via_razorpay(amount, bank_acc_no, bank_ifsc_code, upi_id, segment_id, payment_type=0, product_type=0)`
|
|
280
|
+
|
|
281
|
+
Initiates a RazorPay payment.
|
|
282
|
+
|
|
283
|
+
### `client.funds.payment_ack_response(transaction_id)`
|
|
284
|
+
|
|
285
|
+
Acknowledges a payment transaction.
|
|
286
|
+
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
## Market
|
|
290
|
+
|
|
291
|
+
### `client.market.get_market_status()`
|
|
292
|
+
|
|
293
|
+
Returns current market status across all segments.
|
|
294
|
+
|
|
295
|
+
```python
|
|
296
|
+
status = client.market.get_market_status()
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### `client.market.get_user_profile()`
|
|
300
|
+
|
|
301
|
+
Returns the authenticated user's profile.
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
profile = client.market.get_user_profile()
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### `client.market.get_multiple_touchline(multiple_seg_token)`
|
|
308
|
+
|
|
309
|
+
Returns touchline data for multiple instruments.
|
|
310
|
+
|
|
311
|
+
```python
|
|
312
|
+
# Format: "SegmentId1,Token1|SegmentId2,Token2"
|
|
313
|
+
touchline = client.market.get_multiple_touchline("1,2885|1,11536")
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## Historical Data
|
|
319
|
+
|
|
320
|
+
### `client.historical.get_historical_data(segment_id, token, from_date, to_date, resolution)`
|
|
321
|
+
|
|
322
|
+
Returns historical OHLCV data as a **Pandas DataFrame**.
|
|
323
|
+
|
|
324
|
+
| Parameter | Type | Description |
|
|
325
|
+
|---|---|---|
|
|
326
|
+
| `segment_id` | `int` | Exchange segment |
|
|
327
|
+
| `token` | `int` | Instrument token |
|
|
328
|
+
| `from_date` | `str` or `int` | Start date (`"YYYY-MM-DD"` or seconds from 1980) |
|
|
329
|
+
| `to_date` | `str` or `int` | End date (`"YYYY-MM-DD"` or seconds from 1980) |
|
|
330
|
+
| `resolution` | `str` | `"1"` = 1 min, `"5"` = 5 min, `"D"` = Daily |
|
|
331
|
+
|
|
332
|
+
```python
|
|
333
|
+
df = client.historical.get_historical_data(
|
|
334
|
+
segment_id=1,
|
|
335
|
+
token=2885,
|
|
336
|
+
from_date="2024-01-01",
|
|
337
|
+
to_date="2024-12-31",
|
|
338
|
+
resolution="D"
|
|
339
|
+
)
|
|
340
|
+
print(df.head())
|
|
341
|
+
# Time Open High Low Close Volume OI
|
|
342
|
+
# 0 2024-01-01 00:00:00 2501.00 2520.50 2490.00 2515.30 1234567 0
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
The returned DataFrame has columns: `Time`, `Open`, `High`, `Low`, `Close`, `Volume`, `OI`. Prices are automatically adjusted using the `PriceDivisor` from the API response.
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Interactive WebSockets
|
|
350
|
+
|
|
351
|
+
Receives live order updates, trade confirmations, and market status events.
|
|
352
|
+
|
|
353
|
+
```python
|
|
354
|
+
import asyncio
|
|
355
|
+
from choice_api import InteractiveSocketClient
|
|
356
|
+
|
|
357
|
+
async def main():
|
|
358
|
+
ws = InteractiveSocketClient(token=client.session_id)
|
|
359
|
+
|
|
360
|
+
ws.on("ORD_NRML", lambda data: print(f"Order Update: {data}"))
|
|
361
|
+
ws.on("TRD_MSG", lambda data: print(f"Trade: {data}"))
|
|
362
|
+
ws.on("MKT_STAT", lambda data: print(f"Market Status: {data}"))
|
|
363
|
+
|
|
364
|
+
await ws.connect()
|
|
365
|
+
|
|
366
|
+
asyncio.run(main())
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Event types:** `ORD_NRML` (order updates), `TRD_MSG` (trade confirmations), `MKT_STAT` (market open/close).
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Price Feed WebSockets (FIX3.0)
|
|
374
|
+
|
|
375
|
+
Receives live Level 1 (Touchline) and Level 2 (Best Five / Depth) market data via TCP socket with Zlib compression.
|
|
376
|
+
|
|
377
|
+
```python
|
|
378
|
+
import asyncio
|
|
379
|
+
from choice_api import PriceFeedSocketClient
|
|
380
|
+
|
|
381
|
+
async def main():
|
|
382
|
+
feed = PriceFeedSocketClient(
|
|
383
|
+
host=client.bcast_ip,
|
|
384
|
+
port=client.bcast_port,
|
|
385
|
+
user_id="YOUR_USER_ID"
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
feed.on_message(lambda raw: print(f"Feed: {raw}"))
|
|
389
|
+
|
|
390
|
+
# Start connection (sends login automatically)
|
|
391
|
+
asyncio.create_task(feed.connect())
|
|
392
|
+
|
|
393
|
+
# Wait for connection, then subscribe
|
|
394
|
+
await asyncio.sleep(2)
|
|
395
|
+
feed.subscribe_touchline(client.session_id, segment_id=1, token=2885)
|
|
396
|
+
feed.subscribe_best_five(client.session_id, segment_id=1, token=2885)
|
|
397
|
+
|
|
398
|
+
# Keep running
|
|
399
|
+
await asyncio.sleep(3600)
|
|
400
|
+
|
|
401
|
+
asyncio.run(main())
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
---
|
|
405
|
+
|
|
406
|
+
## Logoff
|
|
407
|
+
|
|
408
|
+
```python
|
|
409
|
+
client.logoff()
|
|
410
|
+
```
|