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/market.py
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
"""Public market data methods for the Revolut X API.
|
|
2
|
+
|
|
3
|
+
This module provides :class:`MarketMixin`, which is mixed into
|
|
4
|
+
:class:`~revolut_x.client.RevolutXClient` to add public (unauthenticated)
|
|
5
|
+
endpoints for market data: trading pairs, currencies, tickers, order book,
|
|
6
|
+
candlestick charts (OHLCV), and public trade history.
|
|
7
|
+
|
|
8
|
+
All methods in this mixin work **without** an API key.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
import logging
|
|
13
|
+
import time
|
|
14
|
+
from typing import Any, Iterator, TYPE_CHECKING
|
|
15
|
+
from revolut_x.types import (
|
|
16
|
+
Candle,
|
|
17
|
+
CurrencyConfig,
|
|
18
|
+
Interval,
|
|
19
|
+
OrderBook,
|
|
20
|
+
PairConfig,
|
|
21
|
+
Ticker,
|
|
22
|
+
Trade,
|
|
23
|
+
)
|
|
24
|
+
from revolut_x.helpers import normalize_symbol as _normalize_symbol
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from revolut_x._http import HttpClient
|
|
28
|
+
|
|
29
|
+
logger = logging.getLogger(__name__)
|
|
30
|
+
|
|
31
|
+
#: Largest page size the ``/public/trades/all`` endpoint accepts. Asking for
|
|
32
|
+
#: more is rejected with HTTP 400 ('Limit must be between 1 and 100').
|
|
33
|
+
MAX_TRADES_PER_PAGE = 100
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class MarketMixin:
|
|
37
|
+
"""Public market data endpoints — no authentication required.
|
|
38
|
+
|
|
39
|
+
This mixin is not meant to be used directly. It is mixed into
|
|
40
|
+
:class:`~revolut_x.client.RevolutXClient`.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
# Provided by RevolutXClient
|
|
44
|
+
_http: HttpClient
|
|
45
|
+
|
|
46
|
+
# ------------------------------------------------------------------
|
|
47
|
+
# Configuration
|
|
48
|
+
# ------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
def get_pairs(self) -> dict[str, PairConfig]:
|
|
51
|
+
"""Retrieve the configuration and trading limits for all active pairs.
|
|
52
|
+
|
|
53
|
+
Returns:
|
|
54
|
+
A dictionary mapping pair symbols (e.g. ``'BTC/EUR'``) to their
|
|
55
|
+
:class:`~revolut_x.types.PairConfig` configuration, including
|
|
56
|
+
minimum/maximum order sizes, price/quantity step sizes, and status.
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
ApiError: If the API returns a non-2xx response.
|
|
60
|
+
NetworkError: On connection failure.
|
|
61
|
+
|
|
62
|
+
Example::
|
|
63
|
+
|
|
64
|
+
>>> client = RevolutXClient()
|
|
65
|
+
>>> pairs = client.get_pairs()
|
|
66
|
+
>>> btc = pairs["BTC/EUR"]
|
|
67
|
+
>>> print(f"Min order: {btc['min_order_size']} BTC")
|
|
68
|
+
Min order: 0.00000001 BTC
|
|
69
|
+
"""
|
|
70
|
+
_status, data = self._http.request("GET", "/public/configuration/pairs")
|
|
71
|
+
return data
|
|
72
|
+
|
|
73
|
+
def get_pair(self, symbol: str) -> PairConfig:
|
|
74
|
+
"""Retrieve the configuration for a specific trading pair.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
symbol: Trading pair in any common format
|
|
78
|
+
(e.g. ``'BTC-EUR'``, ``'BTC/EUR'``, ``'btceur'``).
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
The :class:`~revolut_x.types.PairConfig` for the requested pair.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
ValueError: If the pair is not found or not active on the exchange.
|
|
85
|
+
ApiError: If the API returns a non-2xx response.
|
|
86
|
+
|
|
87
|
+
Example::
|
|
88
|
+
|
|
89
|
+
>>> pair = client.get_pair("ETH-EUR")
|
|
90
|
+
>>> print(f"Step: {pair['base_step']}")
|
|
91
|
+
"""
|
|
92
|
+
pairs = self.get_pairs()
|
|
93
|
+
|
|
94
|
+
# Try both slash and dash formats (API returns slash-keyed dict)
|
|
95
|
+
clean_slash = symbol.replace("-", "/").upper()
|
|
96
|
+
clean_dash = symbol.replace("/", "-").upper()
|
|
97
|
+
|
|
98
|
+
info = pairs.get(clean_slash) or pairs.get(clean_dash)
|
|
99
|
+
if info is not None:
|
|
100
|
+
return info
|
|
101
|
+
|
|
102
|
+
# Case-insensitive fallback
|
|
103
|
+
for key, val in pairs.items():
|
|
104
|
+
if key.upper() in (clean_slash, clean_dash):
|
|
105
|
+
return val
|
|
106
|
+
|
|
107
|
+
available = ", ".join(sorted(pairs.keys())[:10])
|
|
108
|
+
raise ValueError(
|
|
109
|
+
f"Trading pair '{symbol}' not found on Revolut X. "
|
|
110
|
+
f"Available pairs include: {available}… "
|
|
111
|
+
f"Use get_pairs() to see the full list."
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def get_currencies(self) -> dict[str, CurrencyConfig]:
|
|
115
|
+
"""Retrieve the configuration for all supported currencies.
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
A dictionary mapping currency symbols (e.g. ``'BTC'``, ``'EUR'``)
|
|
119
|
+
to their :class:`~revolut_x.types.CurrencyConfig`.
|
|
120
|
+
|
|
121
|
+
Raises:
|
|
122
|
+
ApiError: If the API returns a non-2xx response.
|
|
123
|
+
|
|
124
|
+
Example::
|
|
125
|
+
|
|
126
|
+
>>> currencies = client.get_currencies()
|
|
127
|
+
>>> print(currencies["BTC"]["name"])
|
|
128
|
+
Bitcoin
|
|
129
|
+
"""
|
|
130
|
+
_status, data = self._http.request("GET", "/public/configuration/currencies")
|
|
131
|
+
return data
|
|
132
|
+
|
|
133
|
+
# ------------------------------------------------------------------
|
|
134
|
+
# Tickers & Order Book
|
|
135
|
+
# ------------------------------------------------------------------
|
|
136
|
+
|
|
137
|
+
def get_tickers(self) -> list[Ticker]:
|
|
138
|
+
"""Retrieve current tickers (best bid, best ask, last price) for all pairs.
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
A list of :class:`~revolut_x.types.Ticker` dictionaries.
|
|
142
|
+
|
|
143
|
+
Raises:
|
|
144
|
+
ApiError: If the API returns a non-2xx response.
|
|
145
|
+
"""
|
|
146
|
+
_status, data = self._http.request("GET", "/public/tickers")
|
|
147
|
+
if isinstance(data, dict):
|
|
148
|
+
return data.get("data", [])
|
|
149
|
+
return data
|
|
150
|
+
|
|
151
|
+
def get_ticker(self, symbol: str) -> Ticker:
|
|
152
|
+
"""Retrieve the current ticker for a specific trading pair.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
symbol: Trading pair (e.g. ``'BTC-EUR'``).
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
A :class:`~revolut_x.types.Ticker` with best bid, best ask,
|
|
159
|
+
and last traded price.
|
|
160
|
+
|
|
161
|
+
Raises:
|
|
162
|
+
ValueError: If ticker data is not available for the given symbol.
|
|
163
|
+
ApiError: If the API returns a non-2xx response.
|
|
164
|
+
|
|
165
|
+
Example::
|
|
166
|
+
|
|
167
|
+
>>> ticker = client.get_ticker("BTC-EUR")
|
|
168
|
+
>>> print(f"BTC price: {ticker['last_price']} EUR")
|
|
169
|
+
"""
|
|
170
|
+
tickers = self.get_tickers()
|
|
171
|
+
|
|
172
|
+
target_slash = symbol.replace("-", "/").upper()
|
|
173
|
+
target_dash = symbol.replace("/", "-").upper()
|
|
174
|
+
|
|
175
|
+
if isinstance(tickers, list):
|
|
176
|
+
for t in tickers:
|
|
177
|
+
sym = t.get("symbol", "").upper()
|
|
178
|
+
if sym in (target_slash, target_dash):
|
|
179
|
+
return t
|
|
180
|
+
elif isinstance(tickers, dict):
|
|
181
|
+
result = tickers.get(target_slash) or tickers.get(target_dash)
|
|
182
|
+
if result is not None:
|
|
183
|
+
return result
|
|
184
|
+
|
|
185
|
+
raise ValueError(
|
|
186
|
+
f"Ticker data not available for '{symbol}'. "
|
|
187
|
+
f"Check that the pair exists with get_pairs()."
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
def get_order_book(self, symbol: str, depth: int = 10) -> OrderBook:
|
|
191
|
+
"""Retrieve the current order book (bids and asks) for a trading pair.
|
|
192
|
+
|
|
193
|
+
Args:
|
|
194
|
+
symbol: Trading pair (e.g. ``'BTC-EUR'``).
|
|
195
|
+
depth: Requested number of price levels. **Currently ignored by
|
|
196
|
+
the exchange** — the venue always returns 5 levels per side,
|
|
197
|
+
regardless of this value. Kept so that the SDK picks up
|
|
198
|
+
deeper books automatically if Revolut X starts honouring it.
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
An :class:`~revolut_x.types.OrderBook` with ``bids`` and ``asks``
|
|
202
|
+
lists. Each entry is an
|
|
203
|
+
:class:`~revolut_x.types.OrderBookLevel` **dictionary** — read the
|
|
204
|
+
price from ``['p']`` and the quantity from ``['q']``. Levels are
|
|
205
|
+
sorted best-first.
|
|
206
|
+
|
|
207
|
+
Raises:
|
|
208
|
+
ApiError: If the API returns a non-2xx response.
|
|
209
|
+
|
|
210
|
+
Example::
|
|
211
|
+
|
|
212
|
+
>>> book = client.get_order_book("BTC-EUR")
|
|
213
|
+
>>> best_bid = book["bids"][0]
|
|
214
|
+
>>> print(f"Best bid: {best_bid['p']} EUR x {best_bid['q']} BTC")
|
|
215
|
+
"""
|
|
216
|
+
api_symbol = _normalize_symbol(symbol)
|
|
217
|
+
_status, data = self._http.request(
|
|
218
|
+
"GET",
|
|
219
|
+
f"/public/order-book/{api_symbol}",
|
|
220
|
+
params={"limit": depth},
|
|
221
|
+
)
|
|
222
|
+
if isinstance(data, dict):
|
|
223
|
+
return data.get("data", data)
|
|
224
|
+
return data
|
|
225
|
+
|
|
226
|
+
# ------------------------------------------------------------------
|
|
227
|
+
# Candles (OHLCV)
|
|
228
|
+
# ------------------------------------------------------------------
|
|
229
|
+
|
|
230
|
+
def get_candles(
|
|
231
|
+
self,
|
|
232
|
+
symbol: str,
|
|
233
|
+
interval: Interval | int = Interval.HOUR_1,
|
|
234
|
+
*,
|
|
235
|
+
since: int | None = None,
|
|
236
|
+
until: int | None = None,
|
|
237
|
+
) -> list[Candle]:
|
|
238
|
+
"""Fetch up to 1000 OHLCV candlesticks for a trading pair.
|
|
239
|
+
|
|
240
|
+
Args:
|
|
241
|
+
symbol: Trading pair (e.g. ``'BTC-EUR'``).
|
|
242
|
+
interval: Candle interval — use the :class:`~revolut_x.types.Interval`
|
|
243
|
+
enum (e.g. ``Interval.MIN_1``, ``Interval.HOUR_1``) or an integer
|
|
244
|
+
number of minutes.
|
|
245
|
+
since: Start time as Unix timestamp in **milliseconds** (inclusive).
|
|
246
|
+
until: End time as Unix timestamp in **milliseconds** (inclusive).
|
|
247
|
+
|
|
248
|
+
Returns:
|
|
249
|
+
A list of :class:`~revolut_x.types.Candle` dictionaries with keys:
|
|
250
|
+
``start``, ``open``, ``high``, ``low``, ``close``, ``volume``.
|
|
251
|
+
|
|
252
|
+
Note:
|
|
253
|
+
A single request covers at most **1000 candles**. The exchange
|
|
254
|
+
does *not* silently truncate a wider window — it rejects it with
|
|
255
|
+
HTTP 400 (``'Lookup window interval exceeds the limit of 1000
|
|
256
|
+
candles'``). So ``until - since`` must stay within
|
|
257
|
+
``999 * interval``; to download a longer range use
|
|
258
|
+
:meth:`iter_candles`, which paginates automatically.
|
|
259
|
+
|
|
260
|
+
Raises:
|
|
261
|
+
ApiError: If the API returns a non-2xx response — including when
|
|
262
|
+
``since``/``until`` span more than 1000 candles.
|
|
263
|
+
|
|
264
|
+
Example::
|
|
265
|
+
|
|
266
|
+
>>> from revolut_x import Interval
|
|
267
|
+
>>> # last 500 hours — comfortably inside the 1000-candle limit
|
|
268
|
+
>>> now_ms = int(time.time() * 1000)
|
|
269
|
+
>>> candles = client.get_candles("BTC-EUR", Interval.HOUR_1,
|
|
270
|
+
... since=now_ms - 500 * 3_600_000)
|
|
271
|
+
>>> for c in candles[:3]:
|
|
272
|
+
... print(f"{c['start']}: O={c['open']} H={c['high']}")
|
|
273
|
+
"""
|
|
274
|
+
api_symbol = _normalize_symbol(symbol)
|
|
275
|
+
params: dict[str, Any] = {"interval": int(interval)}
|
|
276
|
+
if since is not None:
|
|
277
|
+
params["since"] = int(since)
|
|
278
|
+
if until is not None:
|
|
279
|
+
params["until"] = int(until)
|
|
280
|
+
|
|
281
|
+
_status, data = self._http.request(
|
|
282
|
+
"GET",
|
|
283
|
+
f"/public/candles/{api_symbol}",
|
|
284
|
+
params=params,
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
if isinstance(data, dict):
|
|
288
|
+
return data.get("data", [])
|
|
289
|
+
return data
|
|
290
|
+
|
|
291
|
+
def iter_candles(
|
|
292
|
+
self,
|
|
293
|
+
symbol: str,
|
|
294
|
+
interval: Interval | int = Interval.HOUR_1,
|
|
295
|
+
*,
|
|
296
|
+
since: int | None = None,
|
|
297
|
+
until: int | None = None,
|
|
298
|
+
) -> Iterator[Candle]:
|
|
299
|
+
"""Iterate over all candles in a time range, automatically paginating.
|
|
300
|
+
|
|
301
|
+
This is a generator that yields :class:`~revolut_x.types.Candle`
|
|
302
|
+
dictionaries one by one, fetching additional pages from the API as
|
|
303
|
+
needed. It handles the 1000-candle-per-request limit transparently.
|
|
304
|
+
|
|
305
|
+
Args:
|
|
306
|
+
symbol: Trading pair (e.g. ``'BTC-EUR'``).
|
|
307
|
+
interval: Candle interval (see :meth:`get_candles`).
|
|
308
|
+
since: Start time as Unix timestamp in **milliseconds**.
|
|
309
|
+
until: End time as Unix timestamp in **milliseconds**.
|
|
310
|
+
|
|
311
|
+
Yields:
|
|
312
|
+
:class:`~revolut_x.types.Candle` dictionaries in chronological
|
|
313
|
+
order (oldest first). No candle is yielded twice, and none is
|
|
314
|
+
yielded with a ``start`` later than ``until``.
|
|
315
|
+
|
|
316
|
+
Example::
|
|
317
|
+
|
|
318
|
+
>>> for candle in client.iter_candles("BTC-EUR", Interval.MIN_1,
|
|
319
|
+
... since=1700000000000):
|
|
320
|
+
... process(candle) # each candle is a dict
|
|
321
|
+
"""
|
|
322
|
+
interval_ms = int(interval) * 60 * 1000 # interval in milliseconds
|
|
323
|
+
# The API rejects a window spanning more than 1000 candles, and the
|
|
324
|
+
# window is inclusive on both ends — so 999 intervals == 1000 candles.
|
|
325
|
+
max_batch_ms = 999 * interval_ms
|
|
326
|
+
|
|
327
|
+
if since is None:
|
|
328
|
+
# Without a start there is nothing to paginate: a single request
|
|
329
|
+
# already returns the most recent batch.
|
|
330
|
+
yield from self.get_candles(symbol, interval, until=until)
|
|
331
|
+
return
|
|
332
|
+
|
|
333
|
+
current_since = since
|
|
334
|
+
|
|
335
|
+
while True:
|
|
336
|
+
# Walk fixed-size windows forward rather than chaining off the last
|
|
337
|
+
# returned candle, so that gaps in the series (illiquid pairs with
|
|
338
|
+
# no trades in an interval) cannot cut the iteration short.
|
|
339
|
+
hard_end = until if until is not None else int(time.time() * 1000)
|
|
340
|
+
batch_until = min(current_since + max_batch_ms, hard_end)
|
|
341
|
+
# The API rejects a window where 'until' equals 'since', which is
|
|
342
|
+
# what a final single-candle window would produce. Always ask for
|
|
343
|
+
# at least two candles and drop the overshoot below.
|
|
344
|
+
batch_until = max(batch_until, current_since + interval_ms)
|
|
345
|
+
|
|
346
|
+
batch = self.get_candles(
|
|
347
|
+
symbol, interval, since=current_since, until=batch_until
|
|
348
|
+
)
|
|
349
|
+
if until is not None:
|
|
350
|
+
batch = [c for c in batch if int(c.get("start", 0)) <= until]
|
|
351
|
+
|
|
352
|
+
yield from batch
|
|
353
|
+
|
|
354
|
+
if batch_until >= hard_end:
|
|
355
|
+
break
|
|
356
|
+
|
|
357
|
+
current_since = batch_until + interval_ms
|
|
358
|
+
|
|
359
|
+
# ------------------------------------------------------------------
|
|
360
|
+
# Public Trades
|
|
361
|
+
# ------------------------------------------------------------------
|
|
362
|
+
|
|
363
|
+
def get_trades(
|
|
364
|
+
self,
|
|
365
|
+
symbol: str,
|
|
366
|
+
*,
|
|
367
|
+
start_date: int | None = None,
|
|
368
|
+
end_date: int | None = None,
|
|
369
|
+
cursor: str | None = None,
|
|
370
|
+
limit: int = MAX_TRADES_PER_PAGE,
|
|
371
|
+
) -> tuple[list[Trade], str | None]:
|
|
372
|
+
"""Fetch a single page of public trades (up to 100 per request).
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
symbol: Trading pair (e.g. ``'BTC-EUR'``).
|
|
376
|
+
start_date: Start time as Unix timestamp in **milliseconds**.
|
|
377
|
+
end_date: End time as Unix timestamp in **milliseconds**.
|
|
378
|
+
cursor: Pagination cursor from a previous response's
|
|
379
|
+
``metadata.next_cursor`` field.
|
|
380
|
+
limit: Maximum number of trades to return. The exchange accepts
|
|
381
|
+
**1–100** and rejects anything larger with HTTP 400, so values
|
|
382
|
+
outside that range are clamped into it. To read more than one
|
|
383
|
+
page, follow ``next_cursor`` or use :meth:`iter_trades`.
|
|
384
|
+
|
|
385
|
+
Returns:
|
|
386
|
+
A tuple of ``(trades, next_cursor)`` where ``trades`` is a list of
|
|
387
|
+
:class:`~revolut_x.types.Trade` dicts and ``next_cursor`` is a
|
|
388
|
+
string for fetching the next page (or ``None`` if no more data).
|
|
389
|
+
|
|
390
|
+
Raises:
|
|
391
|
+
ApiError: If the API returns a non-2xx response.
|
|
392
|
+
|
|
393
|
+
Example::
|
|
394
|
+
|
|
395
|
+
>>> trades, cursor = client.get_trades("BTC-EUR", limit=100)
|
|
396
|
+
>>> print(f"Got {len(trades)} trades, next cursor: {cursor}")
|
|
397
|
+
"""
|
|
398
|
+
api_symbol = _normalize_symbol(symbol)
|
|
399
|
+
params: dict[str, Any] = {
|
|
400
|
+
"symbol": api_symbol,
|
|
401
|
+
"limit": max(1, min(limit, MAX_TRADES_PER_PAGE)),
|
|
402
|
+
}
|
|
403
|
+
if start_date is not None:
|
|
404
|
+
params["start_date"] = int(start_date)
|
|
405
|
+
if end_date is not None:
|
|
406
|
+
params["end_date"] = int(end_date)
|
|
407
|
+
if cursor:
|
|
408
|
+
params["cursor"] = cursor
|
|
409
|
+
|
|
410
|
+
_status, data = self._http.request(
|
|
411
|
+
"GET", "/public/trades/all", params=params
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
if isinstance(data, dict):
|
|
415
|
+
trades = data.get("data", [])
|
|
416
|
+
next_cursor = data.get("metadata", {}).get("next_cursor")
|
|
417
|
+
else:
|
|
418
|
+
trades = data if isinstance(data, list) else []
|
|
419
|
+
next_cursor = None
|
|
420
|
+
|
|
421
|
+
return trades, next_cursor
|
|
422
|
+
|
|
423
|
+
def iter_trades(
|
|
424
|
+
self,
|
|
425
|
+
symbol: str,
|
|
426
|
+
*,
|
|
427
|
+
start_date: int | None = None,
|
|
428
|
+
end_date: int | None = None,
|
|
429
|
+
limit: int = MAX_TRADES_PER_PAGE,
|
|
430
|
+
) -> Iterator[Trade]:
|
|
431
|
+
"""Iterate over all public trades in a time range, automatically paginating.
|
|
432
|
+
|
|
433
|
+
This generator fetches pages of trades using cursor-based pagination
|
|
434
|
+
and yields :class:`~revolut_x.types.Trade` dicts one by one.
|
|
435
|
+
|
|
436
|
+
Args:
|
|
437
|
+
symbol: Trading pair (e.g. ``'BTC-EUR'``).
|
|
438
|
+
start_date: Start time as Unix timestamp in **milliseconds**.
|
|
439
|
+
end_date: End time as Unix timestamp in **milliseconds**.
|
|
440
|
+
limit: Page size per request (1–100, clamped into that range).
|
|
441
|
+
|
|
442
|
+
Yields:
|
|
443
|
+
:class:`~revolut_x.types.Trade` dictionaries.
|
|
444
|
+
|
|
445
|
+
Example::
|
|
446
|
+
|
|
447
|
+
>>> for trade in client.iter_trades("BTC-EUR",
|
|
448
|
+
... start_date=1700000000000):
|
|
449
|
+
... print(f"{trade['price']} EUR x {trade['quantity']} BTC")
|
|
450
|
+
"""
|
|
451
|
+
cursor: str | None = None
|
|
452
|
+
|
|
453
|
+
while True:
|
|
454
|
+
trades, next_cursor = self.get_trades(
|
|
455
|
+
symbol,
|
|
456
|
+
start_date=start_date,
|
|
457
|
+
end_date=end_date,
|
|
458
|
+
cursor=cursor,
|
|
459
|
+
limit=limit,
|
|
460
|
+
)
|
|
461
|
+
if not trades:
|
|
462
|
+
break
|
|
463
|
+
|
|
464
|
+
yield from trades
|
|
465
|
+
|
|
466
|
+
if not next_cursor:
|
|
467
|
+
break
|
|
468
|
+
|
|
469
|
+
cursor = next_cursor
|