tradingnexus 0.1.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.
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: tradingnexus
3
+ Version: 0.1.0
4
+ Summary: Official Python client for the Trading Nexus Personal Trade API
5
+ Author: Trading Nexus
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://tradingnexus.pro/developers
8
+ Project-URL: Documentation, https://tradingnexus.pro/developers
9
+ Project-URL: Source, https://github.com/tradingnexus
10
+ Keywords: trading,tradingnexus,algo,orders,indian-markets
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Office/Business :: Financial :: Investment
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: requests>=2.28.0
22
+ Requires-Dist: websocket-client>=1.6.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7.0; extra == "dev"
25
+ Requires-Dist: responses>=0.25; extra == "dev"
26
+
27
+ # Trading Nexus — Python client (draft)
28
+
29
+ Official-style Python helper for the **Personal Trade API**
30
+ (own-account algo / bot access).
31
+
32
+ > Status: **v0.1.0 draft** — not published to PyPI yet. Install from this folder for testing.
33
+
34
+ ## Install (local)
35
+
36
+ ```bash
37
+ cd sdks/python
38
+ pip install -e .
39
+ ```
40
+
41
+ ## Quick start
42
+
43
+ ```python
44
+ from tradingnexus import TradeClient
45
+
46
+ client = TradeClient(api_key="tk_your_secret_key_here")
47
+
48
+ # 1) Find the exact option / equity contract
49
+ found = client.search_instruments(
50
+ underlying="NIFTY",
51
+ exchange_segment="NSE_FNO",
52
+ symbol="24000",
53
+ limit=50,
54
+ )
55
+ row = next(
56
+ (
57
+ i
58
+ for i in found.get("instruments", [])
59
+ if str(i.get("option_type", "")).upper() == "PE"
60
+ and float(i.get("strike_price") or 0) == 24000
61
+ ),
62
+ None,
63
+ )
64
+ if not row:
65
+ raise SystemExit("Contract not found — refine search / expiry")
66
+
67
+ # 2) Place order using returned fields
68
+ order = client.place_order(
69
+ side="BUY",
70
+ quantity=int(row["lot_size"]),
71
+ order_type="MARKET",
72
+ product_type="MIS",
73
+ symbol=row["symbol"],
74
+ exchange_segment=row["exchange_segment"],
75
+ security_id=row.get("security_id") or row.get("instrument_token"),
76
+ idempotency_key="bot-demo-001",
77
+ )
78
+ print(order)
79
+ ```
80
+
81
+ ## Live order updates
82
+
83
+ ```python
84
+ from tradingnexus import OrderStream
85
+
86
+ def on_message(msg):
87
+ print(msg)
88
+
89
+ stream = OrderStream(api_key="tk_your_secret_key_here", on_message=on_message)
90
+ stream.start()
91
+ # ... keep process alive ...
92
+ # stream.stop()
93
+ ```
94
+
95
+ ## What this covers
96
+
97
+ | Client method | API |
98
+ |---------------|-----|
99
+ | `search_instruments` / `get_instrument` | Instruments |
100
+ | `place_order` / `modify_order` / `cancel_order` | Orders write |
101
+ | `get_order` / `list_orders` | Orders read |
102
+ | `list_trades` / `list_trades_for_order` | Fills |
103
+ | `list_positions` | Positions |
104
+ | `OrderStream` | WebSocket `/personal/ws/orders` |
105
+
106
+ ## Auth notes
107
+
108
+ - Header: `X-PERSONAL-KEY` (server also accepts `X-API-KEY`)
109
+ - IP allowlist is **optional per key** (Super Admin → API Access)
110
+ - Keys are issued only by Trading Nexus Super Admin — no self-service
111
+
112
+ ## Docs
113
+
114
+ - Web: https://tradingnexus.pro/developers
115
+ - Markdown: `docs/Personal_Trade_API_Reference.md`
116
+
117
+ ## PyPI (later)
118
+
119
+ When you approve publishing:
120
+
121
+ ```bash
122
+ pip install tradingnexus
123
+ ```
124
+
125
+ Until then, use `pip install -e .` from `sdks/python`.
@@ -0,0 +1,99 @@
1
+ # Trading Nexus — Python client (draft)
2
+
3
+ Official-style Python helper for the **Personal Trade API**
4
+ (own-account algo / bot access).
5
+
6
+ > Status: **v0.1.0 draft** — not published to PyPI yet. Install from this folder for testing.
7
+
8
+ ## Install (local)
9
+
10
+ ```bash
11
+ cd sdks/python
12
+ pip install -e .
13
+ ```
14
+
15
+ ## Quick start
16
+
17
+ ```python
18
+ from tradingnexus import TradeClient
19
+
20
+ client = TradeClient(api_key="tk_your_secret_key_here")
21
+
22
+ # 1) Find the exact option / equity contract
23
+ found = client.search_instruments(
24
+ underlying="NIFTY",
25
+ exchange_segment="NSE_FNO",
26
+ symbol="24000",
27
+ limit=50,
28
+ )
29
+ row = next(
30
+ (
31
+ i
32
+ for i in found.get("instruments", [])
33
+ if str(i.get("option_type", "")).upper() == "PE"
34
+ and float(i.get("strike_price") or 0) == 24000
35
+ ),
36
+ None,
37
+ )
38
+ if not row:
39
+ raise SystemExit("Contract not found — refine search / expiry")
40
+
41
+ # 2) Place order using returned fields
42
+ order = client.place_order(
43
+ side="BUY",
44
+ quantity=int(row["lot_size"]),
45
+ order_type="MARKET",
46
+ product_type="MIS",
47
+ symbol=row["symbol"],
48
+ exchange_segment=row["exchange_segment"],
49
+ security_id=row.get("security_id") or row.get("instrument_token"),
50
+ idempotency_key="bot-demo-001",
51
+ )
52
+ print(order)
53
+ ```
54
+
55
+ ## Live order updates
56
+
57
+ ```python
58
+ from tradingnexus import OrderStream
59
+
60
+ def on_message(msg):
61
+ print(msg)
62
+
63
+ stream = OrderStream(api_key="tk_your_secret_key_here", on_message=on_message)
64
+ stream.start()
65
+ # ... keep process alive ...
66
+ # stream.stop()
67
+ ```
68
+
69
+ ## What this covers
70
+
71
+ | Client method | API |
72
+ |---------------|-----|
73
+ | `search_instruments` / `get_instrument` | Instruments |
74
+ | `place_order` / `modify_order` / `cancel_order` | Orders write |
75
+ | `get_order` / `list_orders` | Orders read |
76
+ | `list_trades` / `list_trades_for_order` | Fills |
77
+ | `list_positions` | Positions |
78
+ | `OrderStream` | WebSocket `/personal/ws/orders` |
79
+
80
+ ## Auth notes
81
+
82
+ - Header: `X-PERSONAL-KEY` (server also accepts `X-API-KEY`)
83
+ - IP allowlist is **optional per key** (Super Admin → API Access)
84
+ - Keys are issued only by Trading Nexus Super Admin — no self-service
85
+
86
+ ## Docs
87
+
88
+ - Web: https://tradingnexus.pro/developers
89
+ - Markdown: `docs/Personal_Trade_API_Reference.md`
90
+
91
+ ## PyPI (later)
92
+
93
+ When you approve publishing:
94
+
95
+ ```bash
96
+ pip install tradingnexus
97
+ ```
98
+
99
+ Until then, use `pip install -e .` from `sdks/python`.
@@ -0,0 +1,39 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tradingnexus"
7
+ version = "0.1.0"
8
+ description = "Official Python client for the Trading Nexus Personal Trade API"
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "Proprietary" }
12
+ authors = [{ name = "Trading Nexus" }]
13
+ keywords = ["trading", "tradingnexus", "algo", "orders", "indian-markets"]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.9",
19
+ "Programming Language :: Python :: 3.10",
20
+ "Programming Language :: Python :: 3.11",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Topic :: Office/Business :: Financial :: Investment",
23
+ ]
24
+ dependencies = [
25
+ "requests>=2.28.0",
26
+ "websocket-client>=1.6.0",
27
+ ]
28
+
29
+ [project.optional-dependencies]
30
+ dev = ["pytest>=7.0", "responses>=0.25"]
31
+
32
+ [project.urls]
33
+ Homepage = "https://tradingnexus.pro/developers"
34
+ Documentation = "https://tradingnexus.pro/developers"
35
+ Source = "https://github.com/tradingnexus"
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["."]
39
+ include = ["tradingnexus*"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,36 @@
1
+ """
2
+ Trading Nexus Personal Trade API — Python client.
3
+
4
+ Install (from this folder, for local draft use):
5
+ pip install -e .
6
+
7
+ Example:
8
+ from tradingnexus import TradeClient
9
+
10
+ client = TradeClient(api_key="tk_...")
11
+ instruments = client.search_instruments(underlying="NIFTY", exchange_segment="NSE_FNO", symbol="24000")
12
+ """
13
+
14
+ from .client import TradeClient
15
+ from .errors import (
16
+ ApiError,
17
+ AuthenticationError,
18
+ NotFoundError,
19
+ RateLimitError,
20
+ TradeApiError,
21
+ ValidationError,
22
+ )
23
+ from .websocket import OrderStream
24
+
25
+ __all__ = [
26
+ "TradeClient",
27
+ "OrderStream",
28
+ "TradeApiError",
29
+ "ApiError",
30
+ "AuthenticationError",
31
+ "ValidationError",
32
+ "NotFoundError",
33
+ "RateLimitError",
34
+ ]
35
+
36
+ __version__ = "0.1.0"
@@ -0,0 +1,308 @@
1
+ """Synchronous REST client for Trading Nexus Personal Trade API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Dict, Mapping, Optional
6
+ from urllib.parse import urljoin
7
+
8
+ import requests
9
+
10
+ from .errors import (
11
+ ApiError,
12
+ AuthenticationError,
13
+ NotFoundError,
14
+ RateLimitError,
15
+ TradeApiError,
16
+ ValidationError,
17
+ )
18
+
19
+ DEFAULT_BASE_URL = "https://api.tradingnexus.pro/api/v2"
20
+ DEFAULT_TIMEOUT = 15.0
21
+
22
+
23
+ class TradeClient:
24
+ """
25
+ Personal Trade API client (own account only).
26
+
27
+ Auth:
28
+ - Header X-PERSONAL-KEY (preferred)
29
+ - X-API-KEY also accepted by the server
30
+
31
+ IP restriction is configured by Super Admin per key (ON allowlist / OFF any IP).
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ api_key: str,
37
+ *,
38
+ base_url: str = DEFAULT_BASE_URL,
39
+ timeout: float = DEFAULT_TIMEOUT,
40
+ session: Optional[requests.Session] = None,
41
+ ) -> None:
42
+ key = (api_key or "").strip()
43
+ if not key:
44
+ raise AuthenticationError("api_key is required")
45
+
46
+ self.api_key = key
47
+ self.base_url = base_url.rstrip("/") + "/"
48
+ self.timeout = timeout
49
+ self._session = session or requests.Session()
50
+ self._session.headers.update(
51
+ {
52
+ "X-PERSONAL-KEY": self.api_key,
53
+ "Accept": "application/json",
54
+ }
55
+ )
56
+
57
+ # ── Instruments ──────────────────────────────────────────────────────────
58
+
59
+ def search_instruments(
60
+ self,
61
+ *,
62
+ symbol: Optional[str] = None,
63
+ exchange_segment: Optional[str] = None,
64
+ underlying: Optional[str] = None,
65
+ offset: int = 0,
66
+ limit: int = 100,
67
+ ) -> Dict[str, Any]:
68
+ """GET /personal/instruments — search before placing F&O / equity orders."""
69
+ params: Dict[str, Any] = {"offset": offset, "limit": limit}
70
+ if symbol:
71
+ params["symbol"] = symbol
72
+ if exchange_segment:
73
+ params["exchange_segment"] = exchange_segment
74
+ if underlying:
75
+ params["underlying"] = underlying
76
+ return self._request("GET", "personal/instruments", params=params)
77
+
78
+ def get_instrument(self, symbol: str) -> Dict[str, Any]:
79
+ """GET /personal/instruments/{symbol} — exact symbol match."""
80
+ return self._request("GET", f"personal/instruments/{symbol}")
81
+
82
+ # ── Orders ───────────────────────────────────────────────────────────────
83
+
84
+ def place_order(
85
+ self,
86
+ *,
87
+ side: str,
88
+ quantity: int,
89
+ order_type: str = "MARKET",
90
+ product_type: str = "NORMAL",
91
+ symbol: Optional[str] = None,
92
+ exchange_segment: Optional[str] = None,
93
+ security_id: Optional[int] = None,
94
+ instrument_token: Optional[int] = None,
95
+ price: Optional[float] = None,
96
+ trigger_price: Optional[float] = None,
97
+ expiry_date: Optional[str] = None,
98
+ idempotency_key: Optional[str] = None,
99
+ transaction_type: Optional[str] = None,
100
+ ) -> Dict[str, Any]:
101
+ """
102
+ POST /personal/orders
103
+
104
+ Prefer security_id from search_instruments(). Always pass a unique
105
+ idempotency_key for each new order intent.
106
+ """
107
+ body: Dict[str, Any] = {
108
+ "side": side,
109
+ "quantity": quantity,
110
+ "order_type": order_type,
111
+ "product_type": product_type,
112
+ }
113
+ if transaction_type is not None:
114
+ body["transaction_type"] = transaction_type
115
+ if symbol is not None:
116
+ body["symbol"] = symbol
117
+ if exchange_segment is not None:
118
+ body["exchange_segment"] = exchange_segment
119
+ if security_id is not None:
120
+ body["security_id"] = security_id
121
+ if instrument_token is not None:
122
+ body["instrument_token"] = instrument_token
123
+ if price is not None:
124
+ body["price"] = price
125
+ if trigger_price is not None:
126
+ body["trigger_price"] = trigger_price
127
+ if expiry_date is not None:
128
+ body["expiry_date"] = expiry_date
129
+ if idempotency_key is not None:
130
+ body["idempotency_key"] = idempotency_key
131
+ return self._request("POST", "personal/orders", json=body)
132
+
133
+ def get_order(self, order_id: str) -> Dict[str, Any]:
134
+ """GET /personal/orders/{order_id}"""
135
+ return self._request("GET", f"personal/orders/{order_id}")
136
+
137
+ def list_orders(
138
+ self,
139
+ *,
140
+ from_date: Optional[str] = None,
141
+ to_date: Optional[str] = None,
142
+ status_filter: Optional[str] = None,
143
+ ) -> Dict[str, Any]:
144
+ """GET /personal/orders — max 500 rows."""
145
+ params: Dict[str, Any] = {}
146
+ if from_date:
147
+ params["from_date"] = from_date
148
+ if to_date:
149
+ params["to_date"] = to_date
150
+ if status_filter:
151
+ params["status_filter"] = status_filter
152
+ return self._request("GET", "personal/orders", params=params)
153
+
154
+ def modify_order(
155
+ self,
156
+ order_id: str,
157
+ *,
158
+ quantity: Optional[int] = None,
159
+ price: Optional[float] = None,
160
+ trigger_price: Optional[float] = None,
161
+ order_type: Optional[str] = None,
162
+ product_type: Optional[str] = None,
163
+ ) -> Dict[str, Any]:
164
+ """PATCH /personal/orders/{order_id}"""
165
+ body: Dict[str, Any] = {}
166
+ if quantity is not None:
167
+ body["quantity"] = quantity
168
+ if price is not None:
169
+ body["price"] = price
170
+ if trigger_price is not None:
171
+ body["trigger_price"] = trigger_price
172
+ if order_type is not None:
173
+ body["order_type"] = order_type
174
+ if product_type is not None:
175
+ body["product_type"] = product_type
176
+ if not body:
177
+ raise ValidationError("Provide at least one field to modify")
178
+ return self._request("PATCH", f"personal/orders/{order_id}", json=body)
179
+
180
+ def cancel_order(self, order_id: str) -> Dict[str, Any]:
181
+ """DELETE /personal/orders/{order_id}"""
182
+ return self._request("DELETE", f"personal/orders/{order_id}")
183
+
184
+ # ── Trades & positions ───────────────────────────────────────────────────
185
+
186
+ def list_trades(
187
+ self,
188
+ *,
189
+ from_date: Optional[str] = None,
190
+ to_date: Optional[str] = None,
191
+ ) -> Dict[str, Any]:
192
+ """GET /personal/trades"""
193
+ params: Dict[str, Any] = {}
194
+ if from_date:
195
+ params["from_date"] = from_date
196
+ if to_date:
197
+ params["to_date"] = to_date
198
+ return self._request("GET", "personal/trades", params=params)
199
+
200
+ def list_trades_for_order(self, order_id: str) -> Dict[str, Any]:
201
+ """GET /personal/trades/{order_id}"""
202
+ return self._request("GET", f"personal/trades/{order_id}")
203
+
204
+ def list_positions(self) -> Dict[str, Any]:
205
+ """GET /personal/positions"""
206
+ return self._request("GET", "personal/positions")
207
+
208
+ # ── Internals ────────────────────────────────────────────────────────────
209
+
210
+ def _request(
211
+ self,
212
+ method: str,
213
+ path: str,
214
+ *,
215
+ params: Optional[Mapping[str, Any]] = None,
216
+ json: Optional[Mapping[str, Any]] = None,
217
+ ) -> Dict[str, Any]:
218
+ url = urljoin(self.base_url, path.lstrip("/"))
219
+ headers = {}
220
+ if json is not None:
221
+ headers["Content-Type"] = "application/json"
222
+
223
+ try:
224
+ response = self._session.request(
225
+ method=method.upper(),
226
+ url=url,
227
+ params=params,
228
+ json=json,
229
+ headers=headers or None,
230
+ timeout=self.timeout,
231
+ )
232
+ except requests.RequestException as exc:
233
+ raise ApiError(f"Network error: {exc}") from exc
234
+
235
+ return self._parse_response(response)
236
+
237
+ def _parse_response(self, response: requests.Response) -> Dict[str, Any]:
238
+ status = response.status_code
239
+ try:
240
+ payload: Any = response.json() if response.content else {}
241
+ except ValueError:
242
+ payload = {"detail": response.text}
243
+
244
+ if 200 <= status < 300:
245
+ if isinstance(payload, dict):
246
+ return payload
247
+ return {"data": payload}
248
+
249
+ message = _extract_message(payload) or response.reason or "Request failed"
250
+ retry_after = None
251
+ if status == 429:
252
+ raw = response.headers.get("Retry-After")
253
+ if raw and str(raw).isdigit():
254
+ retry_after = int(raw)
255
+ if isinstance(payload, dict):
256
+ detail = payload.get("detail")
257
+ if isinstance(detail, dict) and detail.get("retry_after_seconds") is not None:
258
+ try:
259
+ retry_after = int(detail["retry_after_seconds"])
260
+ except (TypeError, ValueError):
261
+ pass
262
+ raise RateLimitError(
263
+ message,
264
+ status_code=status,
265
+ payload=payload,
266
+ retry_after_seconds=retry_after,
267
+ )
268
+
269
+ error_cls = _error_class_for_status(status)
270
+ raise error_cls(message, status_code=status, payload=payload)
271
+
272
+ def close(self) -> None:
273
+ self._session.close()
274
+
275
+ def __enter__(self) -> "TradeClient":
276
+ return self
277
+
278
+ def __exit__(self, exc_type, exc, tb) -> None:
279
+ self.close()
280
+
281
+
282
+ def _extract_message(payload: Any) -> str:
283
+ if payload is None:
284
+ return ""
285
+ if isinstance(payload, str):
286
+ return payload
287
+ if isinstance(payload, dict):
288
+ detail = payload.get("detail", payload.get("message", payload.get("error")))
289
+ if isinstance(detail, str):
290
+ return detail
291
+ if isinstance(detail, dict):
292
+ return str(detail.get("message") or detail.get("error") or detail)
293
+ if detail is not None:
294
+ return str(detail)
295
+ return str(payload)
296
+ return str(payload)
297
+
298
+
299
+ def _error_class_for_status(status: int) -> type[TradeApiError]:
300
+ if status in (401, 403):
301
+ return AuthenticationError
302
+ if status == 404:
303
+ return NotFoundError
304
+ if status == 400 or status == 409:
305
+ return ValidationError
306
+ if status == 429:
307
+ return RateLimitError
308
+ return ApiError
@@ -0,0 +1,57 @@
1
+ """Exceptions for the Trading Nexus Personal Trade API client."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from typing import Any, Optional
6
+
7
+
8
+ class TradeApiError(Exception):
9
+ """Base error for all Trading Nexus client failures."""
10
+
11
+ def __init__(
12
+ self,
13
+ message: str,
14
+ *,
15
+ status_code: Optional[int] = None,
16
+ payload: Any = None,
17
+ ) -> None:
18
+ super().__init__(message)
19
+ self.message = message
20
+ self.status_code = status_code
21
+ self.payload = payload
22
+
23
+ def __str__(self) -> str:
24
+ if self.status_code is None:
25
+ return self.message
26
+ return f"[{self.status_code}] {self.message}"
27
+
28
+
29
+ class AuthenticationError(TradeApiError):
30
+ """Missing / invalid / revoked API key, or IP not allowed when restriction is ON."""
31
+
32
+
33
+ class ValidationError(TradeApiError):
34
+ """Bad request input (lot size, price, missing fields, etc.)."""
35
+
36
+
37
+ class NotFoundError(TradeApiError):
38
+ """Order / instrument / resource not found for this account."""
39
+
40
+
41
+ class RateLimitError(TradeApiError):
42
+ """HTTP 429 — slow down; honour retry_after_seconds when present."""
43
+
44
+ def __init__(
45
+ self,
46
+ message: str,
47
+ *,
48
+ status_code: int = 429,
49
+ payload: Any = None,
50
+ retry_after_seconds: Optional[int] = None,
51
+ ) -> None:
52
+ super().__init__(message, status_code=status_code, payload=payload)
53
+ self.retry_after_seconds = retry_after_seconds
54
+
55
+
56
+ class ApiError(TradeApiError):
57
+ """Unexpected API / network failure."""
@@ -0,0 +1,130 @@
1
+ """WebSocket helper for live Personal Trade API order / fill updates."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import threading
7
+ from typing import Any, Callable, Dict, Optional
8
+ from urllib.parse import urlencode
9
+
10
+ from .errors import AuthenticationError, TradeApiError
11
+
12
+ try:
13
+ import websocket
14
+ except ImportError as exc: # pragma: no cover
15
+ raise ImportError(
16
+ "websocket-client is required for OrderStream. Install with: pip install websocket-client"
17
+ ) from exc
18
+
19
+ MessageHandler = Callable[[Dict[str, Any]], None]
20
+ ErrorHandler = Callable[[Exception], None]
21
+
22
+ DEFAULT_WS_BASE = "wss://api.tradingnexus.pro/api/v2/personal/ws/orders"
23
+
24
+
25
+ class OrderStream:
26
+ """
27
+ Live order + trade updates for the key owner's account.
28
+
29
+ Example:
30
+ def on_message(msg):
31
+ print(msg)
32
+
33
+ stream = OrderStream(api_key="tk_...", on_message=on_message)
34
+ stream.start()
35
+ ...
36
+ stream.stop()
37
+ """
38
+
39
+ def __init__(
40
+ self,
41
+ api_key: str,
42
+ *,
43
+ ws_url: str = DEFAULT_WS_BASE,
44
+ on_message: Optional[MessageHandler] = None,
45
+ on_error: Optional[ErrorHandler] = None,
46
+ on_open: Optional[Callable[[], None]] = None,
47
+ on_close: Optional[Callable[[], None]] = None,
48
+ ) -> None:
49
+ key = (api_key or "").strip()
50
+ if not key:
51
+ raise AuthenticationError("api_key is required")
52
+
53
+ self.api_key = key
54
+ query = urlencode({"api_key": self.api_key})
55
+ sep = "&" if "?" in ws_url else "?"
56
+ self.ws_url = f"{ws_url}{sep}{query}"
57
+ self.on_message = on_message
58
+ self.on_error = on_error
59
+ self.on_open = on_open
60
+ self.on_close = on_close
61
+
62
+ self._ws: Optional[websocket.WebSocketApp] = None
63
+ self._thread: Optional[threading.Thread] = None
64
+ self._running = False
65
+
66
+ def start(self, *, ping_interval: int = 20, ping_timeout: int = 10) -> None:
67
+ if self._running:
68
+ return
69
+
70
+ def _on_message(_ws, message: str) -> None:
71
+ try:
72
+ payload = json.loads(message)
73
+ except json.JSONDecodeError:
74
+ payload = {"type": "raw", "data": message}
75
+ if self.on_message:
76
+ self.on_message(payload)
77
+
78
+ def _on_error(_ws, error: Any) -> None:
79
+ exc = error if isinstance(error, Exception) else TradeApiError(str(error))
80
+ if self.on_error:
81
+ self.on_error(exc)
82
+
83
+ def _on_open(_ws) -> None:
84
+ if self.on_open:
85
+ self.on_open()
86
+
87
+ def _on_close(_ws, _status_code, _msg) -> None:
88
+ self._running = False
89
+ if self.on_close:
90
+ self.on_close()
91
+
92
+ self._ws = websocket.WebSocketApp(
93
+ self.ws_url,
94
+ header=[f"X-PERSONAL-KEY: {self.api_key}"],
95
+ on_message=_on_message,
96
+ on_error=_on_error,
97
+ on_open=_on_open,
98
+ on_close=_on_close,
99
+ )
100
+ self._running = True
101
+ self._thread = threading.Thread(
102
+ target=self._ws.run_forever,
103
+ kwargs={"ping_interval": ping_interval, "ping_timeout": ping_timeout},
104
+ name="tradingnexus-order-stream",
105
+ daemon=True,
106
+ )
107
+ self._thread.start()
108
+
109
+ def stop(self) -> None:
110
+ self._running = False
111
+ if self._ws is not None:
112
+ try:
113
+ self._ws.close()
114
+ except Exception:
115
+ pass
116
+ if self._thread is not None and self._thread.is_alive():
117
+ self._thread.join(timeout=5)
118
+ self._thread = None
119
+ self._ws = None
120
+
121
+ @property
122
+ def is_running(self) -> bool:
123
+ return self._running
124
+
125
+ def __enter__(self) -> "OrderStream":
126
+ self.start()
127
+ return self
128
+
129
+ def __exit__(self, exc_type, exc, tb) -> None:
130
+ self.stop()
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: tradingnexus
3
+ Version: 0.1.0
4
+ Summary: Official Python client for the Trading Nexus Personal Trade API
5
+ Author: Trading Nexus
6
+ License: Proprietary
7
+ Project-URL: Homepage, https://tradingnexus.pro/developers
8
+ Project-URL: Documentation, https://tradingnexus.pro/developers
9
+ Project-URL: Source, https://github.com/tradingnexus
10
+ Keywords: trading,tradingnexus,algo,orders,indian-markets
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Topic :: Office/Business :: Financial :: Investment
19
+ Requires-Python: >=3.9
20
+ Description-Content-Type: text/markdown
21
+ Requires-Dist: requests>=2.28.0
22
+ Requires-Dist: websocket-client>=1.6.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: pytest>=7.0; extra == "dev"
25
+ Requires-Dist: responses>=0.25; extra == "dev"
26
+
27
+ # Trading Nexus — Python client (draft)
28
+
29
+ Official-style Python helper for the **Personal Trade API**
30
+ (own-account algo / bot access).
31
+
32
+ > Status: **v0.1.0 draft** — not published to PyPI yet. Install from this folder for testing.
33
+
34
+ ## Install (local)
35
+
36
+ ```bash
37
+ cd sdks/python
38
+ pip install -e .
39
+ ```
40
+
41
+ ## Quick start
42
+
43
+ ```python
44
+ from tradingnexus import TradeClient
45
+
46
+ client = TradeClient(api_key="tk_your_secret_key_here")
47
+
48
+ # 1) Find the exact option / equity contract
49
+ found = client.search_instruments(
50
+ underlying="NIFTY",
51
+ exchange_segment="NSE_FNO",
52
+ symbol="24000",
53
+ limit=50,
54
+ )
55
+ row = next(
56
+ (
57
+ i
58
+ for i in found.get("instruments", [])
59
+ if str(i.get("option_type", "")).upper() == "PE"
60
+ and float(i.get("strike_price") or 0) == 24000
61
+ ),
62
+ None,
63
+ )
64
+ if not row:
65
+ raise SystemExit("Contract not found — refine search / expiry")
66
+
67
+ # 2) Place order using returned fields
68
+ order = client.place_order(
69
+ side="BUY",
70
+ quantity=int(row["lot_size"]),
71
+ order_type="MARKET",
72
+ product_type="MIS",
73
+ symbol=row["symbol"],
74
+ exchange_segment=row["exchange_segment"],
75
+ security_id=row.get("security_id") or row.get("instrument_token"),
76
+ idempotency_key="bot-demo-001",
77
+ )
78
+ print(order)
79
+ ```
80
+
81
+ ## Live order updates
82
+
83
+ ```python
84
+ from tradingnexus import OrderStream
85
+
86
+ def on_message(msg):
87
+ print(msg)
88
+
89
+ stream = OrderStream(api_key="tk_your_secret_key_here", on_message=on_message)
90
+ stream.start()
91
+ # ... keep process alive ...
92
+ # stream.stop()
93
+ ```
94
+
95
+ ## What this covers
96
+
97
+ | Client method | API |
98
+ |---------------|-----|
99
+ | `search_instruments` / `get_instrument` | Instruments |
100
+ | `place_order` / `modify_order` / `cancel_order` | Orders write |
101
+ | `get_order` / `list_orders` | Orders read |
102
+ | `list_trades` / `list_trades_for_order` | Fills |
103
+ | `list_positions` | Positions |
104
+ | `OrderStream` | WebSocket `/personal/ws/orders` |
105
+
106
+ ## Auth notes
107
+
108
+ - Header: `X-PERSONAL-KEY` (server also accepts `X-API-KEY`)
109
+ - IP allowlist is **optional per key** (Super Admin → API Access)
110
+ - Keys are issued only by Trading Nexus Super Admin — no self-service
111
+
112
+ ## Docs
113
+
114
+ - Web: https://tradingnexus.pro/developers
115
+ - Markdown: `docs/Personal_Trade_API_Reference.md`
116
+
117
+ ## PyPI (later)
118
+
119
+ When you approve publishing:
120
+
121
+ ```bash
122
+ pip install tradingnexus
123
+ ```
124
+
125
+ Until then, use `pip install -e .` from `sdks/python`.
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ tradingnexus/__init__.py
4
+ tradingnexus/client.py
5
+ tradingnexus/errors.py
6
+ tradingnexus/websocket.py
7
+ tradingnexus.egg-info/PKG-INFO
8
+ tradingnexus.egg-info/SOURCES.txt
9
+ tradingnexus.egg-info/dependency_links.txt
10
+ tradingnexus.egg-info/requires.txt
11
+ tradingnexus.egg-info/top_level.txt
@@ -0,0 +1,6 @@
1
+ requests>=2.28.0
2
+ websocket-client>=1.6.0
3
+
4
+ [dev]
5
+ pytest>=7.0
6
+ responses>=0.25
@@ -0,0 +1 @@
1
+ tradingnexus