xtb-api-python 0.5.2__tar.gz → 0.5.4__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.
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/CHANGELOG.md +98 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/PKG-INFO +26 -3
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/README.md +25 -2
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/pyproject.toml +1 -1
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/ws/parsers.py +28 -23
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/ws/ws_client.py +8 -2
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/.gitignore +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/LICENSE +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/examples/basic_usage.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/examples/grpc_trade.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/examples/live_quotes.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/__init__.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/__main__.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/auth/__init__.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/auth/auth_manager.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/auth/browser_auth.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/auth/cas_client.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/client.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/exceptions.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/grpc/__init__.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/grpc/client.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/grpc/proto.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/grpc/types.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/instruments.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/py.typed +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/types/__init__.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/types/enums.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/types/instrument.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/types/trading.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/types/websocket.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/utils.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/src/xtb_api/ws/__init__.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/__init__.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/conftest.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/test_auth.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/test_auth_manager.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/test_browser_auth.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/test_browser_auth_chromium_missing.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/test_cas_cookies.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/test_client.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/test_client_fill_price.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/test_client_volume_validation.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/test_doctor.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/test_exceptions.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/test_grpc_client.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/test_instruments.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/test_parsers.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/test_proto.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/test_version.py +0 -0
- {xtb_api_python-0.5.2 → xtb_api_python-0.5.4}/tests/test_ws_client.py +0 -0
|
@@ -1,6 +1,104 @@
|
|
|
1
1
|
# CHANGELOG
|
|
2
2
|
|
|
3
3
|
|
|
4
|
+
## v0.5.4 (2026-04-15)
|
|
5
|
+
|
|
6
|
+
### Bug Fixes
|
|
7
|
+
|
|
8
|
+
- **ws**: Revert get_positions to reqId-based send
|
|
9
|
+
([#9](https://github.com/liskeee/xtb-api-unofficial-python/pull/9),
|
|
10
|
+
[`d197b08`](https://github.com/liskeee/xtb-api-unofficial-python/commit/d197b08350e57a46b82adee5dcb206b2d068b852))
|
|
11
|
+
|
|
12
|
+
v0.5.3's push-channel collection was based on a wrong diagnosis. Live API probing confirms the
|
|
13
|
+
xStation5 CoreAPI echoes getPositions on the NORMAL reqId response channel (status=0,
|
|
14
|
+
response=[...]), not as push events. The original implementation was correct.
|
|
15
|
+
|
|
16
|
+
The 30s timeouts observed on the original 0.5.2 bot were something else — probably container-level
|
|
17
|
+
networking / first-call timing — not a protocol mismatch. The wrong fix in 0.5.3 made the problem
|
|
18
|
+
worse because get_positions() started returning [] instantly, causing the downstream bot to
|
|
19
|
+
auto-close positions it mistakenly thought were gone from the broker.
|
|
20
|
+
|
|
21
|
+
Reverts the get_positions implementation to: res = await self.send("getPositions",
|
|
22
|
+
{"getAndSubscribeElement": {"eid": POSITIONS}}, timeout_ms=30000) return
|
|
23
|
+
parse_positions(self._extract_elements(res))
|
|
24
|
+
|
|
25
|
+
Keeps `parse_position_trade` helper in parsers.py — harmless refactor. Removes
|
|
26
|
+
tests/test_get_positions_push.py — tested wrong behavior.
|
|
27
|
+
|
|
28
|
+
Verified against real XTB account: all 5 open positions returned in ~1-2s via normal reqId response.
|
|
29
|
+
|
|
30
|
+
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
## v0.5.3 (2026-04-15)
|
|
34
|
+
|
|
35
|
+
### Bug Fixes
|
|
36
|
+
|
|
37
|
+
- **ws**: Consume POSITIONS push channel in get_positions
|
|
38
|
+
([`b0156bc`](https://github.com/liskeee/xtb-api-unofficial-python/commit/b0156bcc328630fb5e80952d87794780a8e30c73))
|
|
39
|
+
|
|
40
|
+
XTB's xStation5 CoreAPI does not echo a reqId-correlated response for the `getPositions` RPC;
|
|
41
|
+
position data arrives exclusively via status=1 push events with eid=POSITIONS. The previous
|
|
42
|
+
implementation awaited a regular reqId-matched response and timed out after 30s on every call
|
|
43
|
+
against a live account.
|
|
44
|
+
|
|
45
|
+
Fix: subscribe and consume the push burst. Register a one-shot 'position' handler, fire the
|
|
46
|
+
subscribe RPC (do not await its reply), and collect pushed events until either a quiet period (no
|
|
47
|
+
new position for 500 ms) or a max-wait ceiling (5 s) closes the window. Dedup by positionId so a
|
|
48
|
+
retriggered snapshot does not duplicate entries.
|
|
49
|
+
|
|
50
|
+
parse_positions is refactored around a new parse_position_trade(trade) helper so the push handler
|
|
51
|
+
and the (still-supported) element-list path share the single source of truth for xcfdtrade →
|
|
52
|
+
Position mapping.
|
|
53
|
+
|
|
54
|
+
Tests: 8 new tests cover parser extraction, burst collection, dedup, empty-on-timeout, listener
|
|
55
|
+
cleanup, and the not-connected guard.
|
|
56
|
+
|
|
57
|
+
Fixes the downstream xtb-investor-pro bot's "get_positions failed" timeouts observed immediately
|
|
58
|
+
after the broker abstraction merge.
|
|
59
|
+
|
|
60
|
+
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
61
|
+
|
|
62
|
+
- **ws**: Drop unused parse_positions import
|
|
63
|
+
([`63b3263`](https://github.com/liskeee/xtb-api-unofficial-python/commit/63b3263b64ef76ad7759b26a70e8d6ec26e6c9b3))
|
|
64
|
+
|
|
65
|
+
### Code Style
|
|
66
|
+
|
|
67
|
+
- Ruff format test_get_positions_push.py
|
|
68
|
+
([`6e376a8`](https://github.com/liskeee/xtb-api-unofficial-python/commit/6e376a8cc1578d388f8469da3b49b02dee021841))
|
|
69
|
+
|
|
70
|
+
### Documentation
|
|
71
|
+
|
|
72
|
+
- Add design spec for v0.5 docs refresh
|
|
73
|
+
([`c55d839`](https://github.com/liskeee/xtb-api-unofficial-python/commit/c55d839534ff39f5da55ec920fe6457eb82fb458))
|
|
74
|
+
|
|
75
|
+
Captures the README/CONTRIBUTING/SECURITY drift between v0.4.x docs and the v0.5.2 surface (XTBAuth,
|
|
76
|
+
InstrumentRegistry, fill-price polling, volume guard, inlined publish jobs, supported-versions
|
|
77
|
+
table).
|
|
78
|
+
|
|
79
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
80
|
+
|
|
81
|
+
- Add implementation plan for v0.5 docs refresh
|
|
82
|
+
([`0a4e3dd`](https://github.com/liskeee/xtb-api-unofficial-python/commit/0a4e3ddab03ad6cc87b4789cf39c772d85ca6d75))
|
|
83
|
+
|
|
84
|
+
Pairs with the design spec at docs/superpowers/specs/2026-04-15-docs-refresh-v0.5-design.md and
|
|
85
|
+
tracks the four tasks executed in 97bbada.
|
|
86
|
+
|
|
87
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
88
|
+
|
|
89
|
+
- Refresh README, CONTRIBUTING, SECURITY to v0.5 state
|
|
90
|
+
([`97bbada`](https://github.com/liskeee/xtb-api-unofficial-python/commit/97bbada41434dacbf3139af6e26b70ad8a632ba6))
|
|
91
|
+
|
|
92
|
+
- README: document XTBAuth alias, InstrumentRegistry, post-fill TradeResult.price, and the
|
|
93
|
+
volume-validation guard added in v0.5.0; drop stale 11,888+ symbol count. - CONTRIBUTING: trim
|
|
94
|
+
"before enabling on master" framing now that PSR is live; list both semantic-release.yml and
|
|
95
|
+
release.yml as required Trusted Publishers (PyPI matches the OIDC token's workflow filename
|
|
96
|
+
exactly, and v0.5.2 inlined the publish jobs into semantic-release.yml). - SECURITY: bump
|
|
97
|
+
supported-versions table from 0.3.x to 0.5.x.
|
|
98
|
+
|
|
99
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
100
|
+
|
|
101
|
+
|
|
4
102
|
## v0.5.2 (2026-04-15)
|
|
5
103
|
|
|
6
104
|
### Bug Fixes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: xtb-api-python
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
4
4
|
Summary: Python port of unofficial XTB xStation5 API client
|
|
5
5
|
Project-URL: Homepage, https://github.com/liskeee/xtb-api-python
|
|
6
6
|
Project-URL: Repository, https://github.com/liskeee/xtb-api-python
|
|
@@ -52,7 +52,9 @@ Python client for the XTB xStation5 trading platform. Dead-simple API that handl
|
|
|
52
52
|
- **2FA Support** — Automatic TOTP handling when `totp_secret` is provided
|
|
53
53
|
- **Real-time Data** — Live quotes, positions, balance via WebSocket push events
|
|
54
54
|
- **Trading** — Buy/sell market orders with SL/TP via gRPC-web
|
|
55
|
-
- **
|
|
55
|
+
- **Volume-Validated Orders** — `buy`/`sell` reject `volume < 1` before touching the wire
|
|
56
|
+
- **Persistent Instrument Cache** — `InstrumentRegistry` caches symbol → instrument-ID lookups to disk
|
|
57
|
+
- **Full Symbol Search** — Search and resolve all listed instruments with caching
|
|
56
58
|
- **Modern Python** — async/await, Pydantic models, strict typing, Python 3.12+
|
|
57
59
|
|
|
58
60
|
## Requirements
|
|
@@ -129,6 +131,12 @@ async def main():
|
|
|
129
131
|
# Search instruments
|
|
130
132
|
results = await client.search_instrument("Apple")
|
|
131
133
|
|
|
134
|
+
# Persistent instrument cache (avoids re-fetching the full symbol list)
|
|
135
|
+
from xtb_api import InstrumentRegistry
|
|
136
|
+
registry = InstrumentRegistry("~/.xtb_instruments.json")
|
|
137
|
+
matched = await registry.populate(client, ["AAPL.US", "EURUSD"])
|
|
138
|
+
instrument_id = registry.get("AAPL.US") # int | None
|
|
139
|
+
|
|
132
140
|
# Trading (USE WITH CAUTION!)
|
|
133
141
|
result = await client.buy("AAPL.US", volume=1, stop_loss=150.0, take_profit=200.0)
|
|
134
142
|
print(f"Order: {result.order_id}")
|
|
@@ -162,6 +170,8 @@ await client.sell("CIG.PL", volume=100, options=TradeOptions(
|
|
|
162
170
|
))
|
|
163
171
|
```
|
|
164
172
|
|
|
173
|
+
> `TradeResult.price` is populated by polling open positions immediately after fill. If the position cannot be located within the poll window, `price` remains `None`.
|
|
174
|
+
|
|
165
175
|
## API Reference
|
|
166
176
|
|
|
167
177
|
### `XTBClient`
|
|
@@ -194,6 +204,18 @@ await client.sell("CIG.PL", volume=100, options=TradeOptions(
|
|
|
194
204
|
| `account_server` | No | `"XS-real1"` | gRPC account server |
|
|
195
205
|
| `auto_reconnect` | No | `True` | Auto-reconnect on disconnect |
|
|
196
206
|
|
|
207
|
+
### `InstrumentRegistry`
|
|
208
|
+
|
|
209
|
+
Persistent symbol → instrument-ID cache, stored as JSON.
|
|
210
|
+
|
|
211
|
+
| Method | Returns | Description |
|
|
212
|
+
|--------|---------|-------------|
|
|
213
|
+
| `InstrumentRegistry(path)` | — | Load (or create) the JSON cache at `path` |
|
|
214
|
+
| `get(symbol)` | `int \| None` | Cached instrument ID for `symbol`, or `None` |
|
|
215
|
+
| `set(symbol, id)` | `None` | Cache one mapping and persist immediately |
|
|
216
|
+
| `populate(client, symbols)` | `dict[str, int]` | Download the full symbol list via `client`, match requested `symbols` (case-insensitive, dot-less fallback), persist, return new matches |
|
|
217
|
+
| `ids` | `dict[str, int]` | Read-only copy of the full cache |
|
|
218
|
+
|
|
197
219
|
### WebSocket URLs
|
|
198
220
|
|
|
199
221
|
| Environment | URL |
|
|
@@ -212,8 +234,9 @@ ws = client.ws
|
|
|
212
234
|
# gRPC client (available after first trade)
|
|
213
235
|
grpc = client.grpc_client
|
|
214
236
|
|
|
215
|
-
# Auth manager
|
|
237
|
+
# Auth manager (accessor, or import the public alias)
|
|
216
238
|
auth = client.auth
|
|
239
|
+
from xtb_api import XTBAuth # public alias for the AuthManager class
|
|
217
240
|
tgt = await auth.get_tgt()
|
|
218
241
|
```
|
|
219
242
|
|
|
@@ -16,7 +16,9 @@ Python client for the XTB xStation5 trading platform. Dead-simple API that handl
|
|
|
16
16
|
- **2FA Support** — Automatic TOTP handling when `totp_secret` is provided
|
|
17
17
|
- **Real-time Data** — Live quotes, positions, balance via WebSocket push events
|
|
18
18
|
- **Trading** — Buy/sell market orders with SL/TP via gRPC-web
|
|
19
|
-
- **
|
|
19
|
+
- **Volume-Validated Orders** — `buy`/`sell` reject `volume < 1` before touching the wire
|
|
20
|
+
- **Persistent Instrument Cache** — `InstrumentRegistry` caches symbol → instrument-ID lookups to disk
|
|
21
|
+
- **Full Symbol Search** — Search and resolve all listed instruments with caching
|
|
20
22
|
- **Modern Python** — async/await, Pydantic models, strict typing, Python 3.12+
|
|
21
23
|
|
|
22
24
|
## Requirements
|
|
@@ -93,6 +95,12 @@ async def main():
|
|
|
93
95
|
# Search instruments
|
|
94
96
|
results = await client.search_instrument("Apple")
|
|
95
97
|
|
|
98
|
+
# Persistent instrument cache (avoids re-fetching the full symbol list)
|
|
99
|
+
from xtb_api import InstrumentRegistry
|
|
100
|
+
registry = InstrumentRegistry("~/.xtb_instruments.json")
|
|
101
|
+
matched = await registry.populate(client, ["AAPL.US", "EURUSD"])
|
|
102
|
+
instrument_id = registry.get("AAPL.US") # int | None
|
|
103
|
+
|
|
96
104
|
# Trading (USE WITH CAUTION!)
|
|
97
105
|
result = await client.buy("AAPL.US", volume=1, stop_loss=150.0, take_profit=200.0)
|
|
98
106
|
print(f"Order: {result.order_id}")
|
|
@@ -126,6 +134,8 @@ await client.sell("CIG.PL", volume=100, options=TradeOptions(
|
|
|
126
134
|
))
|
|
127
135
|
```
|
|
128
136
|
|
|
137
|
+
> `TradeResult.price` is populated by polling open positions immediately after fill. If the position cannot be located within the poll window, `price` remains `None`.
|
|
138
|
+
|
|
129
139
|
## API Reference
|
|
130
140
|
|
|
131
141
|
### `XTBClient`
|
|
@@ -158,6 +168,18 @@ await client.sell("CIG.PL", volume=100, options=TradeOptions(
|
|
|
158
168
|
| `account_server` | No | `"XS-real1"` | gRPC account server |
|
|
159
169
|
| `auto_reconnect` | No | `True` | Auto-reconnect on disconnect |
|
|
160
170
|
|
|
171
|
+
### `InstrumentRegistry`
|
|
172
|
+
|
|
173
|
+
Persistent symbol → instrument-ID cache, stored as JSON.
|
|
174
|
+
|
|
175
|
+
| Method | Returns | Description |
|
|
176
|
+
|--------|---------|-------------|
|
|
177
|
+
| `InstrumentRegistry(path)` | — | Load (or create) the JSON cache at `path` |
|
|
178
|
+
| `get(symbol)` | `int \| None` | Cached instrument ID for `symbol`, or `None` |
|
|
179
|
+
| `set(symbol, id)` | `None` | Cache one mapping and persist immediately |
|
|
180
|
+
| `populate(client, symbols)` | `dict[str, int]` | Download the full symbol list via `client`, match requested `symbols` (case-insensitive, dot-less fallback), persist, return new matches |
|
|
181
|
+
| `ids` | `dict[str, int]` | Read-only copy of the full cache |
|
|
182
|
+
|
|
161
183
|
### WebSocket URLs
|
|
162
184
|
|
|
163
185
|
| Environment | URL |
|
|
@@ -176,8 +198,9 @@ ws = client.ws
|
|
|
176
198
|
# gRPC client (available after first trade)
|
|
177
199
|
grpc = client.grpc_client
|
|
178
200
|
|
|
179
|
-
# Auth manager
|
|
201
|
+
# Auth manager (accessor, or import the public alias)
|
|
180
202
|
auth = client.auth
|
|
203
|
+
from xtb_api import XTBAuth # public alias for the AuthManager class
|
|
181
204
|
tgt = await auth.get_tgt()
|
|
182
205
|
```
|
|
183
206
|
|
|
@@ -49,36 +49,41 @@ def parse_balance(
|
|
|
49
49
|
)
|
|
50
50
|
|
|
51
51
|
|
|
52
|
+
def parse_position_trade(trade: dict[str, Any]) -> Position:
|
|
53
|
+
"""Convert a single `xcfdtrade` dict (as pushed on the POSITIONS
|
|
54
|
+
subscription) into a `Position`.
|
|
55
|
+
|
|
56
|
+
Callers that receive the wrapped `{value: {xcfdtrade: ...}}` shape
|
|
57
|
+
should use `parse_positions()` instead.
|
|
58
|
+
"""
|
|
59
|
+
side_val = int(trade.get("side", 0))
|
|
60
|
+
return Position(
|
|
61
|
+
symbol=str(trade.get("symbol", "")),
|
|
62
|
+
instrument_id=int(trade["idQuote"]) if trade.get("idQuote") is not None else None,
|
|
63
|
+
volume=float(trade.get("volume", 0)),
|
|
64
|
+
current_price=0.0,
|
|
65
|
+
open_price=float(trade.get("openPrice", 0)),
|
|
66
|
+
stop_loss=float(trade["sl"]) if trade.get("sl") and trade["sl"] != 0 else None,
|
|
67
|
+
take_profit=float(trade["tp"]) if trade.get("tp") and trade["tp"] != 0 else None,
|
|
68
|
+
profit_percent=0.0,
|
|
69
|
+
profit_net=0.0,
|
|
70
|
+
swap=float(trade["swap"]) if trade.get("swap") is not None else None,
|
|
71
|
+
side="buy" if side_val == Xs6Side.BUY else "sell",
|
|
72
|
+
order_id=str(trade["positionId"]) if trade.get("positionId") is not None else None,
|
|
73
|
+
commission=float(trade["commission"]) if trade.get("commission") is not None else None,
|
|
74
|
+
margin=float(trade["margin"]) if trade.get("margin") is not None else None,
|
|
75
|
+
open_time=int(trade["openTime"]) if trade.get("openTime") is not None else None,
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
52
79
|
def parse_positions(elements: list[dict[str, Any]]) -> list[Position]:
|
|
53
80
|
"""Parse open trading positions from subscription elements."""
|
|
54
81
|
positions: list[Position] = []
|
|
55
|
-
|
|
56
82
|
for el in elements:
|
|
57
83
|
trade = (el or {}).get("value", {}).get("xcfdtrade")
|
|
58
84
|
if not trade:
|
|
59
85
|
continue
|
|
60
|
-
|
|
61
|
-
side_val = int(trade.get("side", 0))
|
|
62
|
-
positions.append(
|
|
63
|
-
Position(
|
|
64
|
-
symbol=str(trade.get("symbol", "")),
|
|
65
|
-
instrument_id=int(trade["idQuote"]) if trade.get("idQuote") is not None else None,
|
|
66
|
-
volume=float(trade.get("volume", 0)),
|
|
67
|
-
current_price=0.0,
|
|
68
|
-
open_price=float(trade.get("openPrice", 0)),
|
|
69
|
-
stop_loss=float(trade["sl"]) if trade.get("sl") and trade["sl"] != 0 else None,
|
|
70
|
-
take_profit=float(trade["tp"]) if trade.get("tp") and trade["tp"] != 0 else None,
|
|
71
|
-
profit_percent=0.0,
|
|
72
|
-
profit_net=0.0,
|
|
73
|
-
swap=float(trade["swap"]) if trade.get("swap") is not None else None,
|
|
74
|
-
side="buy" if side_val == Xs6Side.BUY else "sell",
|
|
75
|
-
order_id=str(trade["positionId"]) if trade.get("positionId") is not None else None,
|
|
76
|
-
commission=float(trade["commission"]) if trade.get("commission") is not None else None,
|
|
77
|
-
margin=float(trade["margin"]) if trade.get("margin") is not None else None,
|
|
78
|
-
open_time=int(trade["openTime"]) if trade.get("openTime") is not None else None,
|
|
79
|
-
)
|
|
80
|
-
)
|
|
81
|
-
|
|
86
|
+
positions.append(parse_position_trade(trade))
|
|
82
87
|
return positions
|
|
83
88
|
|
|
84
89
|
|
|
@@ -534,13 +534,19 @@ class XTBWebSocketClient:
|
|
|
534
534
|
return parse_balance(self._extract_elements(res), account.currency, account.accountNo)
|
|
535
535
|
|
|
536
536
|
async def get_positions(self) -> list[Position]:
|
|
537
|
-
"""Get all open trading positions.
|
|
537
|
+
"""Get all open trading positions.
|
|
538
|
+
|
|
539
|
+
XTB's xStation5 CoreAPI echoes the position snapshot on the normal
|
|
540
|
+
reqId-correlated response channel (not the push channel, despite the
|
|
541
|
+
`getAndSubscribeElement` keyword). The subscription it sets up also
|
|
542
|
+
emits per-position updates via `_emit("position", ...)`; those are
|
|
543
|
+
orthogonal to the snapshot returned here.
|
|
544
|
+
"""
|
|
538
545
|
res = await self.send(
|
|
539
546
|
"getPositions",
|
|
540
547
|
{"getAndSubscribeElement": {"eid": SubscriptionEid.POSITIONS}},
|
|
541
548
|
timeout_ms=30000,
|
|
542
549
|
)
|
|
543
|
-
|
|
544
550
|
return parse_positions(self._extract_elements(res))
|
|
545
551
|
|
|
546
552
|
async def get_orders(self) -> list[PendingOrder]:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|