revolut-x-python 1.0.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.
- revolut_x/__init__.py +103 -0
- revolut_x/_auth.py +139 -0
- revolut_x/_http.py +270 -0
- revolut_x/_version.py +10 -0
- revolut_x/account.py +203 -0
- revolut_x/client.py +170 -0
- revolut_x/exceptions.py +147 -0
- revolut_x/helpers.py +540 -0
- revolut_x/market.py +469 -0
- revolut_x/orders.py +522 -0
- revolut_x/py.typed +0 -0
- revolut_x/types.py +397 -0
- revolut_x_python-1.0.0.dist-info/METADATA +164 -0
- revolut_x_python-1.0.0.dist-info/RECORD +17 -0
- revolut_x_python-1.0.0.dist-info/WHEEL +5 -0
- revolut_x_python-1.0.0.dist-info/licenses/LICENSE +21 -0
- revolut_x_python-1.0.0.dist-info/top_level.txt +1 -0
revolut_x/orders.py
ADDED
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
"""Order management methods for the Revolut X API.
|
|
2
|
+
|
|
3
|
+
This module provides :class:`OrdersMixin`, which is mixed into
|
|
4
|
+
:class:`~revolut_x.client.RevolutXClient` to add authenticated endpoints
|
|
5
|
+
for placing, inspecting, and canceling orders.
|
|
6
|
+
|
|
7
|
+
All methods in this mixin **require** an API key and private key.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
import logging
|
|
12
|
+
from decimal import Decimal
|
|
13
|
+
from typing import Any, TYPE_CHECKING
|
|
14
|
+
from revolut_x.types import (
|
|
15
|
+
Fill,
|
|
16
|
+
OrderDetail,
|
|
17
|
+
OrderResponse,
|
|
18
|
+
OrderSide,
|
|
19
|
+
OrderType,
|
|
20
|
+
TimeInForce,
|
|
21
|
+
)
|
|
22
|
+
from revolut_x.helpers import OrderPayloadBuilder, calculate_maker_price
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
from revolut_x._http import HttpClient
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class OrdersMixin:
|
|
31
|
+
"""Authenticated order management endpoints.
|
|
32
|
+
|
|
33
|
+
This mixin is not meant to be used directly. It is mixed into
|
|
34
|
+
:class:`~revolut_x.client.RevolutXClient`.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
# Provided by RevolutXClient
|
|
38
|
+
_http: HttpClient
|
|
39
|
+
|
|
40
|
+
# ------------------------------------------------------------------
|
|
41
|
+
# Place orders
|
|
42
|
+
# ------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
def place_order(
|
|
45
|
+
self,
|
|
46
|
+
symbol_or_payload: str | dict[str, Any],
|
|
47
|
+
side: OrderSide | str | None = None,
|
|
48
|
+
*,
|
|
49
|
+
order_type: OrderType | str = OrderType.MARKET,
|
|
50
|
+
price: str | None = None,
|
|
51
|
+
base_size: str | None = None,
|
|
52
|
+
quote_size: str | None = None,
|
|
53
|
+
post_only: bool = False,
|
|
54
|
+
time_in_force: TimeInForce | str = TimeInForce.GTC,
|
|
55
|
+
client_order_id: str | None = None,
|
|
56
|
+
) -> OrderResponse:
|
|
57
|
+
"""Submit an order to the exchange.
|
|
58
|
+
|
|
59
|
+
This method supports two calling styles:
|
|
60
|
+
|
|
61
|
+
1. **Unified high-level placement** (recommended)::
|
|
62
|
+
|
|
63
|
+
>>> # Market order
|
|
64
|
+
>>> order = client.place_order("BTC-EUR", OrderSide.BUY, order_type="market", quote_size="50.00")
|
|
65
|
+
>>> # Limit order
|
|
66
|
+
>>> order = client.place_order("BTC-EUR", OrderSide.BUY, order_type="limit", price="80000.00", quote_size="50.00")
|
|
67
|
+
|
|
68
|
+
2. **Low-level raw payload dictionary** (backward compatible)::
|
|
69
|
+
|
|
70
|
+
>>> payload = OrderPayloadBuilder.build_market_order("BTC-EUR", OrderSide.BUY, quote_size="50.00")
|
|
71
|
+
>>> order = client.place_order(payload)
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
symbol_or_payload: Trading pair (e.g. ``'BTC-EUR'``) or a complete payload dict.
|
|
75
|
+
side: :attr:`OrderSide.BUY` or :attr:`OrderSide.SELL` (required if symbol string is given).
|
|
76
|
+
order_type: :attr:`OrderType.MARKET` (default) or :attr:`OrderType.LIMIT`.
|
|
77
|
+
price: Limit price in quote currency (required for limit orders, omitted for market orders).
|
|
78
|
+
base_size: Amount in the base currency (e.g. ``'0.001'`` BTC). Mutually exclusive with ``quote_size``.
|
|
79
|
+
quote_size: Amount in the quote currency (e.g. ``'50.00'`` EUR). Mutually exclusive with ``base_size``.
|
|
80
|
+
post_only: If ``True``, guarantees the order enters the book as a maker (0% fee, limit orders only).
|
|
81
|
+
time_in_force: :attr:`TimeInForce.GTC` (default) or :attr:`TimeInForce.IOC` (limit orders only).
|
|
82
|
+
client_order_id: Optional client UUID for idempotency. Auto-generated if not provided.
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
An :class:`~revolut_x.types.OrderResponse` with the assigned
|
|
86
|
+
``venue_order_id`` and initial ``state``.
|
|
87
|
+
|
|
88
|
+
Raises:
|
|
89
|
+
ValueError / OrderValidationError: If order parameters are invalid or missing.
|
|
90
|
+
AuthenticationError: If the client is not authenticated.
|
|
91
|
+
ApiError: If the exchange rejects the order.
|
|
92
|
+
|
|
93
|
+
Example::
|
|
94
|
+
|
|
95
|
+
>>> order = client.place_order(
|
|
96
|
+
... "BTC-EUR", OrderSide.BUY, order_type="market", quote_size="50.00"
|
|
97
|
+
... )
|
|
98
|
+
>>> print(f"Order ID: {order['venue_order_id']}")
|
|
99
|
+
"""
|
|
100
|
+
if isinstance(symbol_or_payload, dict):
|
|
101
|
+
payload = symbol_or_payload
|
|
102
|
+
else:
|
|
103
|
+
if side is None:
|
|
104
|
+
raise ValueError("Parameter 'side' (OrderSide.BUY / OrderSide.SELL) is required when placing an order by symbol.")
|
|
105
|
+
payload = OrderPayloadBuilder.build_order(
|
|
106
|
+
symbol=symbol_or_payload,
|
|
107
|
+
side=side,
|
|
108
|
+
order_type=order_type,
|
|
109
|
+
price=price,
|
|
110
|
+
base_size=base_size,
|
|
111
|
+
quote_size=quote_size,
|
|
112
|
+
post_only=post_only,
|
|
113
|
+
time_in_force=time_in_force,
|
|
114
|
+
client_order_id=client_order_id,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
_status, resp = self._http.request(
|
|
118
|
+
"POST", "/orders", json_body=payload, authenticated=True
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# API wraps response in {"data": [...]} or {"data": {...}}
|
|
122
|
+
data = resp.get("data", resp) if isinstance(resp, dict) else resp
|
|
123
|
+
if isinstance(data, list) and len(data) > 0:
|
|
124
|
+
return data[0]
|
|
125
|
+
return data
|
|
126
|
+
|
|
127
|
+
def place_market_order(
|
|
128
|
+
self,
|
|
129
|
+
symbol: str,
|
|
130
|
+
side: OrderSide | str,
|
|
131
|
+
*,
|
|
132
|
+
base_size: str | None = None,
|
|
133
|
+
quote_size: str | None = None,
|
|
134
|
+
client_order_id: str | None = None,
|
|
135
|
+
) -> OrderResponse:
|
|
136
|
+
"""Place a market order (immediate execution, 0.09% taker fee).
|
|
137
|
+
|
|
138
|
+
Convenience wrapper around :meth:`place_order` with ``order_type=OrderType.MARKET``.
|
|
139
|
+
|
|
140
|
+
Exactly **one** of ``base_size`` or ``quote_size`` must be provided.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
symbol: Trading pair (e.g. ``'BTC-EUR'``).
|
|
144
|
+
side: :attr:`OrderSide.BUY` or :attr:`OrderSide.SELL`.
|
|
145
|
+
base_size: Amount in the base currency (e.g. ``'0.001'`` BTC).
|
|
146
|
+
Mutually exclusive with ``quote_size``.
|
|
147
|
+
quote_size: Amount in the quote currency (e.g. ``'50.00'`` EUR).
|
|
148
|
+
Mutually exclusive with ``base_size``.
|
|
149
|
+
client_order_id: Optional UUID for idempotency. Auto-generated
|
|
150
|
+
if not provided.
|
|
151
|
+
|
|
152
|
+
Returns:
|
|
153
|
+
An :class:`~revolut_x.types.OrderResponse` with the exchange
|
|
154
|
+
order ID.
|
|
155
|
+
|
|
156
|
+
Raises:
|
|
157
|
+
ValueError: If neither or both of ``base_size``/``quote_size``
|
|
158
|
+
are provided.
|
|
159
|
+
AuthenticationError: If the client is not authenticated.
|
|
160
|
+
ApiError: If the exchange rejects the order.
|
|
161
|
+
|
|
162
|
+
Example::
|
|
163
|
+
|
|
164
|
+
>>> order = client.place_market_order(
|
|
165
|
+
... "BTC-EUR", OrderSide.BUY, quote_size="50.00"
|
|
166
|
+
... )
|
|
167
|
+
>>> print(f"Bought BTC, order ID: {order['venue_order_id']}")
|
|
168
|
+
"""
|
|
169
|
+
return self.place_order(
|
|
170
|
+
symbol_or_payload=symbol,
|
|
171
|
+
side=side,
|
|
172
|
+
order_type=OrderType.MARKET,
|
|
173
|
+
base_size=base_size,
|
|
174
|
+
quote_size=quote_size,
|
|
175
|
+
client_order_id=client_order_id,
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
def place_limit_order(
|
|
179
|
+
self,
|
|
180
|
+
symbol: str,
|
|
181
|
+
side: OrderSide | str,
|
|
182
|
+
price: str,
|
|
183
|
+
*,
|
|
184
|
+
base_size: str | None = None,
|
|
185
|
+
quote_size: str | None = None,
|
|
186
|
+
post_only: bool = False,
|
|
187
|
+
time_in_force: TimeInForce | str = TimeInForce.GTC,
|
|
188
|
+
client_order_id: str | None = None,
|
|
189
|
+
) -> OrderResponse:
|
|
190
|
+
"""Place a limit order at a specific price.
|
|
191
|
+
|
|
192
|
+
Convenience wrapper around :meth:`place_order` with ``order_type=OrderType.LIMIT``.
|
|
193
|
+
|
|
194
|
+
Exactly **one** of ``base_size`` or ``quote_size`` must be provided.
|
|
195
|
+
|
|
196
|
+
Args:
|
|
197
|
+
symbol: Trading pair (e.g. ``'BTC-EUR'``).
|
|
198
|
+
side: :attr:`OrderSide.BUY` or :attr:`OrderSide.SELL`.
|
|
199
|
+
price: Limit price in the quote currency (e.g. ``'85000.00'`` EUR
|
|
200
|
+
per 1 BTC).
|
|
201
|
+
base_size: Amount in the base currency. Mutually exclusive
|
|
202
|
+
with ``quote_size``.
|
|
203
|
+
quote_size: Amount in the quote currency. Mutually exclusive
|
|
204
|
+
with ``base_size``.
|
|
205
|
+
post_only: If ``True``, guarantees the order enters the book
|
|
206
|
+
as a **maker** (0% fee). If it would match immediately
|
|
207
|
+
(taker), the exchange rejects it.
|
|
208
|
+
time_in_force: :attr:`TimeInForce.GTC` (good 'til canceled,
|
|
209
|
+
default) or :attr:`TimeInForce.IOC` (immediate or cancel).
|
|
210
|
+
client_order_id: Optional UUID for idempotency.
|
|
211
|
+
|
|
212
|
+
Returns:
|
|
213
|
+
An :class:`~revolut_x.types.OrderResponse`.
|
|
214
|
+
|
|
215
|
+
Raises:
|
|
216
|
+
ValueError: If neither or both of ``base_size``/``quote_size``
|
|
217
|
+
are provided.
|
|
218
|
+
AuthenticationError: If the client is not authenticated.
|
|
219
|
+
ApiError: If the exchange rejects the order.
|
|
220
|
+
|
|
221
|
+
Example::
|
|
222
|
+
|
|
223
|
+
>>> order = client.place_limit_order(
|
|
224
|
+
... "BTC-EUR", OrderSide.BUY, price="80000.00",
|
|
225
|
+
... quote_size="50.00", post_only=True,
|
|
226
|
+
... )
|
|
227
|
+
>>> print(f"Limit order placed: {order['venue_order_id']}")
|
|
228
|
+
"""
|
|
229
|
+
return self.place_order(
|
|
230
|
+
symbol_or_payload=symbol,
|
|
231
|
+
side=side,
|
|
232
|
+
order_type=OrderType.LIMIT,
|
|
233
|
+
price=price,
|
|
234
|
+
base_size=base_size,
|
|
235
|
+
quote_size=quote_size,
|
|
236
|
+
post_only=post_only,
|
|
237
|
+
time_in_force=time_in_force,
|
|
238
|
+
client_order_id=client_order_id,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
def calculate_maker_price(
|
|
242
|
+
self,
|
|
243
|
+
symbol: str,
|
|
244
|
+
side: OrderSide | str,
|
|
245
|
+
*,
|
|
246
|
+
offset: Any = Decimal("0.10"),
|
|
247
|
+
tick_size: Any = Decimal("0.01"),
|
|
248
|
+
) -> Decimal:
|
|
249
|
+
"""Fetch current ticker for symbol and calculate the optimal Maker price.
|
|
250
|
+
|
|
251
|
+
Args:
|
|
252
|
+
symbol: Trading pair (e.g. ``'BTC-EUR'``).
|
|
253
|
+
side: :attr:`OrderSide.BUY` or :attr:`OrderSide.SELL`.
|
|
254
|
+
offset: Offset in quote currency (default: ``0.10``).
|
|
255
|
+
tick_size: Price tick size for quantisation (default: ``0.01``).
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Decimal: The optimal limit price for a zero-fee Maker order.
|
|
259
|
+
"""
|
|
260
|
+
get_ticker_fn = getattr(self, "get_ticker", None)
|
|
261
|
+
if get_ticker_fn is None:
|
|
262
|
+
raise RuntimeError("get_ticker is not available on this client instance.")
|
|
263
|
+
ticker = get_ticker_fn(symbol)
|
|
264
|
+
return calculate_maker_price(
|
|
265
|
+
side=side,
|
|
266
|
+
best_bid=ticker.get("bid"),
|
|
267
|
+
best_ask=ticker.get("ask"),
|
|
268
|
+
last_price=ticker.get("last_price"),
|
|
269
|
+
offset=offset,
|
|
270
|
+
tick_size=tick_size,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
def place_maker_order(
|
|
274
|
+
self,
|
|
275
|
+
symbol: str,
|
|
276
|
+
side: OrderSide | str,
|
|
277
|
+
*,
|
|
278
|
+
price: str | None = None,
|
|
279
|
+
offset: Any = Decimal("0.10"),
|
|
280
|
+
tick_size: Any = Decimal("0.01"),
|
|
281
|
+
base_size: str | None = None,
|
|
282
|
+
quote_size: str | None = None,
|
|
283
|
+
time_in_force: TimeInForce | str = TimeInForce.GTC,
|
|
284
|
+
client_order_id: str | None = None,
|
|
285
|
+
) -> OrderResponse:
|
|
286
|
+
"""Place a guaranteed zero-fee Maker limit order (0.00% fee).
|
|
287
|
+
|
|
288
|
+
If ``price`` is not explicitly provided, this method automatically fetches
|
|
289
|
+
the latest market ticker and computes an optimal Maker price with the given
|
|
290
|
+
``offset``:
|
|
291
|
+
- For BUY: ``best_bid - offset``
|
|
292
|
+
- For SELL: ``best_ask + offset``
|
|
293
|
+
|
|
294
|
+
The order is submitted with ``post_only=True``, ensuring the exchange will
|
|
295
|
+
accept it as a Maker (0% fee) or reject it if it would match immediately.
|
|
296
|
+
|
|
297
|
+
Exactly **one** of ``base_size`` or ``quote_size`` must be provided.
|
|
298
|
+
|
|
299
|
+
Args:
|
|
300
|
+
symbol: Trading pair (e.g. ``'BTC-EUR'``).
|
|
301
|
+
side: :attr:`OrderSide.BUY` or :attr:`OrderSide.SELL`.
|
|
302
|
+
price: Optional fixed limit price. If omitted, calculated dynamically.
|
|
303
|
+
offset: Offset in quote currency from best bid/ask (default: ``0.10``).
|
|
304
|
+
tick_size: Minimum price increment for quantisation (default: ``0.01``).
|
|
305
|
+
base_size: Amount in the base currency (e.g. ``'0.001'`` BTC).
|
|
306
|
+
quote_size: Amount in the quote currency (e.g. ``'50.00'`` EUR).
|
|
307
|
+
time_in_force: :attr:`TimeInForce.GTC` (default) or :attr:`TimeInForce.IOC`.
|
|
308
|
+
client_order_id: Optional UUID for idempotency.
|
|
309
|
+
|
|
310
|
+
Returns:
|
|
311
|
+
An :class:`~revolut_x.types.OrderResponse` from the exchange.
|
|
312
|
+
|
|
313
|
+
Raises:
|
|
314
|
+
ValueError / OrderValidationError: If sizes or side are invalid.
|
|
315
|
+
AuthenticationError: If the client is not authenticated.
|
|
316
|
+
ApiError: If the exchange rejects the order.
|
|
317
|
+
|
|
318
|
+
Example::
|
|
319
|
+
|
|
320
|
+
>>> # Buy BTC for 50 EUR with zero fee at optimal Maker price:
|
|
321
|
+
>>> order = client.place_maker_order("BTC-EUR", OrderSide.BUY, quote_size="50.00")
|
|
322
|
+
>>> print(f"Maker order placed: {order['venue_order_id']}")
|
|
323
|
+
"""
|
|
324
|
+
if price is None:
|
|
325
|
+
calc_price = str(self.calculate_maker_price(symbol, side, offset=offset, tick_size=tick_size))
|
|
326
|
+
else:
|
|
327
|
+
calc_price = str(price)
|
|
328
|
+
|
|
329
|
+
return self.place_limit_order(
|
|
330
|
+
symbol=symbol,
|
|
331
|
+
side=side,
|
|
332
|
+
price=calc_price,
|
|
333
|
+
base_size=base_size,
|
|
334
|
+
quote_size=quote_size,
|
|
335
|
+
post_only=True,
|
|
336
|
+
time_in_force=time_in_force,
|
|
337
|
+
client_order_id=client_order_id,
|
|
338
|
+
)
|
|
339
|
+
|
|
340
|
+
# ------------------------------------------------------------------
|
|
341
|
+
# Query orders
|
|
342
|
+
# ------------------------------------------------------------------
|
|
343
|
+
|
|
344
|
+
def get_order(self, order_id: str) -> OrderDetail:
|
|
345
|
+
"""Retrieve detailed information about a specific order.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
order_id: The exchange-assigned order ID (``venue_order_id``).
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
An :class:`~revolut_x.types.OrderDetail` with status, filled
|
|
352
|
+
quantity, average fill price, total fee, and timestamps.
|
|
353
|
+
|
|
354
|
+
Raises:
|
|
355
|
+
AuthenticationError: If the client is not authenticated.
|
|
356
|
+
ApiError: If the order is not found (HTTP 404) or other error.
|
|
357
|
+
|
|
358
|
+
Example::
|
|
359
|
+
|
|
360
|
+
>>> detail = client.get_order("7a52e92e-8639-4fe1-abaa-68d3a2d5234b")
|
|
361
|
+
>>> print(f"Status: {detail['status']}, Fee: {detail['total_fee']} EUR")
|
|
362
|
+
"""
|
|
363
|
+
_status, resp = self._http.request(
|
|
364
|
+
"GET", f"/orders/{order_id}", authenticated=True
|
|
365
|
+
)
|
|
366
|
+
if isinstance(resp, dict) and "data" in resp:
|
|
367
|
+
return resp["data"]
|
|
368
|
+
return resp
|
|
369
|
+
|
|
370
|
+
def get_order_fills(self, order_id: str) -> list[Fill]:
|
|
371
|
+
"""Retrieve individual fills (partial executions) for an order.
|
|
372
|
+
|
|
373
|
+
Each fill includes the execution price, quantity, and whether it
|
|
374
|
+
was a maker or taker trade (``im`` field).
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
order_id: The exchange-assigned order ID (``venue_order_id``).
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
A list of :class:`~revolut_x.types.Fill` dictionaries.
|
|
381
|
+
The ``im`` field is ``True`` for maker fills (0% fee) and
|
|
382
|
+
``False`` for taker fills (0.09% fee).
|
|
383
|
+
|
|
384
|
+
Raises:
|
|
385
|
+
AuthenticationError: If the client is not authenticated.
|
|
386
|
+
ApiError: If the order is not found or other error.
|
|
387
|
+
|
|
388
|
+
Example::
|
|
389
|
+
|
|
390
|
+
>>> fills = client.get_order_fills("7a52e92e-...")
|
|
391
|
+
>>> for fill in fills:
|
|
392
|
+
... fee_type = "maker (0%)" if fill["im"] else "taker (0.09%)"
|
|
393
|
+
... print(f"{fill['q']} BTC @ {fill['p']} EUR [{fee_type}]")
|
|
394
|
+
"""
|
|
395
|
+
_status, resp = self._http.request(
|
|
396
|
+
"GET", f"/orders/fills/{order_id}", authenticated=True
|
|
397
|
+
)
|
|
398
|
+
if isinstance(resp, dict) and "data" in resp:
|
|
399
|
+
return resp["data"]
|
|
400
|
+
return resp
|
|
401
|
+
|
|
402
|
+
def get_active_orders(
|
|
403
|
+
self, symbol: str | None = None
|
|
404
|
+
) -> list[OrderDetail]:
|
|
405
|
+
"""Retrieve all currently open (active) orders.
|
|
406
|
+
|
|
407
|
+
Args:
|
|
408
|
+
symbol: Optional filter by trading pair (e.g. ``'BTC-EUR'``).
|
|
409
|
+
If ``None``, returns active orders for all pairs.
|
|
410
|
+
|
|
411
|
+
Returns:
|
|
412
|
+
A list of :class:`~revolut_x.types.OrderDetail` dictionaries.
|
|
413
|
+
|
|
414
|
+
Raises:
|
|
415
|
+
AuthenticationError: If the client is not authenticated.
|
|
416
|
+
"""
|
|
417
|
+
params: dict[str, Any] | None = None
|
|
418
|
+
if symbol:
|
|
419
|
+
params = {"symbol": symbol.replace("/", "-").upper()}
|
|
420
|
+
|
|
421
|
+
_status, resp = self._http.request(
|
|
422
|
+
"GET", "/orders/active", params=params, authenticated=True
|
|
423
|
+
)
|
|
424
|
+
if isinstance(resp, dict) and "data" in resp:
|
|
425
|
+
return resp["data"]
|
|
426
|
+
return resp if isinstance(resp, list) else []
|
|
427
|
+
|
|
428
|
+
def get_historical_orders(
|
|
429
|
+
self,
|
|
430
|
+
symbol: str | None = None,
|
|
431
|
+
*,
|
|
432
|
+
limit: int = 50,
|
|
433
|
+
cursor: str | None = None,
|
|
434
|
+
) -> tuple[list[OrderDetail], str | None]:
|
|
435
|
+
"""Retrieve historical (completed/canceled) orders with pagination.
|
|
436
|
+
|
|
437
|
+
Args:
|
|
438
|
+
symbol: Optional filter by trading pair.
|
|
439
|
+
limit: Number of orders per page (default: 50).
|
|
440
|
+
cursor: Pagination cursor from a previous response.
|
|
441
|
+
|
|
442
|
+
Returns:
|
|
443
|
+
A tuple of ``(orders, next_cursor)``. ``next_cursor`` is ``None``
|
|
444
|
+
if there are no more pages.
|
|
445
|
+
|
|
446
|
+
Raises:
|
|
447
|
+
AuthenticationError: If the client is not authenticated.
|
|
448
|
+
"""
|
|
449
|
+
params: dict[str, Any] = {"limit": limit}
|
|
450
|
+
if symbol:
|
|
451
|
+
params["symbol"] = symbol.replace("/", "-").upper()
|
|
452
|
+
if cursor:
|
|
453
|
+
params["cursor"] = cursor
|
|
454
|
+
|
|
455
|
+
_status, resp = self._http.request(
|
|
456
|
+
"GET", "/orders/historical", params=params, authenticated=True
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
if isinstance(resp, dict):
|
|
460
|
+
orders = resp.get("data", [])
|
|
461
|
+
next_cursor = resp.get("metadata", {}).get("next_cursor")
|
|
462
|
+
else:
|
|
463
|
+
orders = resp if isinstance(resp, list) else []
|
|
464
|
+
next_cursor = None
|
|
465
|
+
|
|
466
|
+
return orders, next_cursor
|
|
467
|
+
|
|
468
|
+
# ------------------------------------------------------------------
|
|
469
|
+
# Cancel orders
|
|
470
|
+
# ------------------------------------------------------------------
|
|
471
|
+
|
|
472
|
+
def cancel_order(self, order_id: str) -> dict[str, Any]:
|
|
473
|
+
"""Cancel a specific open order.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
order_id: The exchange-assigned order ID (``venue_order_id``).
|
|
477
|
+
|
|
478
|
+
Returns:
|
|
479
|
+
The API response confirming cancellation.
|
|
480
|
+
|
|
481
|
+
Raises:
|
|
482
|
+
AuthenticationError: If the client is not authenticated.
|
|
483
|
+
ApiError: If the order cannot be canceled (e.g. already filled,
|
|
484
|
+
not found).
|
|
485
|
+
|
|
486
|
+
Example::
|
|
487
|
+
|
|
488
|
+
>>> client.cancel_order("7a52e92e-8639-4fe1-abaa-68d3a2d5234b")
|
|
489
|
+
"""
|
|
490
|
+
_status, resp = self._http.request(
|
|
491
|
+
"DELETE", f"/orders/{order_id}", authenticated=True
|
|
492
|
+
)
|
|
493
|
+
return resp
|
|
494
|
+
|
|
495
|
+
def cancel_all_orders(
|
|
496
|
+
self, symbol: str | None = None
|
|
497
|
+
) -> dict[str, Any]:
|
|
498
|
+
"""Cancel all open orders, optionally filtered by trading pair.
|
|
499
|
+
|
|
500
|
+
Args:
|
|
501
|
+
symbol: If provided, only cancel orders for this pair
|
|
502
|
+
(e.g. ``'BTC-EUR'``). If ``None``, cancel **all** open
|
|
503
|
+
orders across all pairs.
|
|
504
|
+
|
|
505
|
+
Returns:
|
|
506
|
+
The API response confirming cancellation.
|
|
507
|
+
|
|
508
|
+
Raises:
|
|
509
|
+
AuthenticationError: If the client is not authenticated.
|
|
510
|
+
|
|
511
|
+
Example::
|
|
512
|
+
|
|
513
|
+
>>> client.cancel_all_orders("BTC-EUR")
|
|
514
|
+
"""
|
|
515
|
+
params: dict[str, Any] | None = None
|
|
516
|
+
if symbol:
|
|
517
|
+
params = {"symbol": symbol.replace("/", "-").upper()}
|
|
518
|
+
|
|
519
|
+
_status, resp = self._http.request(
|
|
520
|
+
"DELETE", "/orders", params=params, authenticated=True
|
|
521
|
+
)
|
|
522
|
+
return resp
|
revolut_x/py.typed
ADDED
|
File without changes
|