Přeskočit obsah

Helpers & Utilities Reference

Helper classes and functions for payload construction and fee calculation.

helpers

FeeCalculator

Calculator for order fees on Revolut X.

calculate classmethod

calculate(side: OrderSide, price: Any, is_maker: bool, *, base_size: Any | None = None, quote_size: Any | None = None) -> FeeEstimate

Calculates exact fees for a specified trade.

Parameters:

Name Type Description Default
side OrderSide

BUY or SELL.

required
price Any

The execution price in quote currency per 1 base currency unit.

required
is_maker bool

True if the order is executed as a maker (0.00% fee), False for taker (0.09% fee).

required
base_size Any | None

The amount of the base currency (e.g., BTC) if provided.

None
quote_size Any | None

The amount of the quote currency (e.g., EUR) if provided.

None

Returns:

Name Type Description
FeeEstimate FeeEstimate

An object containing detailed fee estimates.

Raises:

Type Description
OrderValidationError

If neither base_size nor quote_size is provided.

Example

FeeCalculator.calculate(OrderSide.BUY, '90000', is_maker=False, quote_size='1000')

Source code in src/revolut_x/helpers.py
@classmethod
def calculate(
    cls,
    side: OrderSide,
    price: Any,
    is_maker: bool,
    *,
    base_size: Any | None = None,
    quote_size: Any | None = None
) -> FeeEstimate:
    """Calculates exact fees for a specified trade.

    Args:
        side: BUY or SELL.
        price: The execution price in quote currency per 1 base currency unit.
        is_maker: True if the order is executed as a maker (0.00% fee), False for taker (0.09% fee).
        base_size: The amount of the base currency (e.g., BTC) if provided.
        quote_size: The amount of the quote currency (e.g., EUR) if provided.

    Returns:
        FeeEstimate: An object containing detailed fee estimates.

    Raises:
        OrderValidationError: If neither base_size nor quote_size is provided.

    Example:
        >>> FeeCalculator.calculate(OrderSide.BUY, '90000', is_maker=False, quote_size='1000')
    """
    price_dec = Decimal(str(price))
    rate = cls.MAKER_FEE_RATE if is_maker else cls.TAKER_FEE_RATE
    rate_pct = rate * Decimal("100")

    if quote_size is not None:
        value_eur = Decimal(str(quote_size))
        qty_btc = (value_eur / price_dec).quantize(Decimal("0.00000001"), rounding=ROUND_HALF_UP)
    elif base_size is not None:
        qty_btc = Decimal(str(base_size))
        value_eur = (qty_btc * price_dec).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
    else:
        raise OrderValidationError("You must specify either 'base_size' or 'quote_size'.")

    fee_eur = (value_eur * rate).quantize(Decimal("0.0001"), rounding=ROUND_HALF_UP)

    side_val = side.value if isinstance(side, OrderSide) else str(side).lower()

    if side_val == "buy":
        fee_currency = "EUR"
        net_received = qty_btc
        net_currency = "BTC"
        explanation = (
            f"Buy {net_received} BTC for {value_eur} EUR at price {price_dec}.\n"
            f"- Execution type: {'MAKER' if is_maker else 'TAKER'}\n"
            f"- Fee rate: {rate_pct:.2f} %\n"
            f"- Fee amount: {fee_eur:.4f} EUR\n"
            f"- Net received approx: {net_received:.8f} BTC"
        )
    else:
        fee_currency = "EUR"
        net_received = (value_eur - fee_eur).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP)
        net_currency = "EUR"
        explanation = (
            f"Sell {qty_btc} BTC at price {price_dec} (Gross value: {value_eur} EUR).\n"
            f"- Execution type: {'MAKER' if is_maker else 'TAKER'}\n"
            f"- Fee rate: {rate_pct:.2f} %\n"
            f"- Fee amount: {fee_eur:.4f} EUR\n"
            f"- Net received: {net_received:.2f} EUR"
        )

    return FeeEstimate(
        side=side_val,
        order_type="limit" if is_maker else "market/immediate-limit",
        is_maker=is_maker,
        fee_rate_percent=rate_pct,
        trade_value_eur=value_eur,
        estimated_base_qty=qty_btc,
        price=price_dec,
        fee_amount=fee_eur,
        fee_currency=fee_currency,
        net_received=net_received,
        net_received_currency=net_currency,
        explanation=explanation
    )

FeeEstimate dataclass

FeeEstimate(side: str, order_type: str, is_maker: bool, fee_rate_percent: Decimal, trade_value_eur: Decimal, estimated_base_qty: Decimal, price: Decimal, fee_amount: Decimal, fee_currency: str, net_received: Decimal, net_received_currency: str, explanation: str)

Estimated fee calculation result for an order.

OrderPayloadBuilder

Builder and validator for Revolut X API order payloads.

build_limit_order classmethod

build_limit_order(symbol: str, side: OrderSide, price: Any, *, base_size: Any | None = None, quote_size: Any | None = None, post_only: bool = False, time_in_force: TimeInForce | str = TimeInForce.GTC, client_order_id: str | None = None) -> dict[str, Any]

Builds a payload for a Limit Order.

Parameters:

Name Type Description Default
symbol str

The trading symbol (e.g., 'BTC-EUR').

required
side OrderSide

The side of the order (BUY or SELL).

required
price Any

The limit price in quote currency per 1 base currency unit.

required
base_size Any | None

The amount of the base currency (e.g., BTC) to buy/sell.

None
quote_size Any | None

The amount of the quote currency (e.g., EUR) to buy/sell.

None
post_only bool

If True, ensures the order is entered as a MAKER order.

False
time_in_force TimeInForce | str

The time in force for the order (e.g., 'gtc' or 'ioc').

GTC
client_order_id str | None

A unique client order ID. A UUID will be generated if not provided.

None

Returns:

Type Description
dict[str, Any]

dict[str, Any]: The constructed JSON payload for the Revolut X API.

Raises:

Type Description
OrderValidationError

If exactly one of base_size or quote_size is not provided.

Example

OrderPayloadBuilder.build_limit_order('BTC-EUR', OrderSide.SELL, '90000.00', base_size='0.1')

Source code in src/revolut_x/helpers.py
@classmethod
def build_limit_order(
    cls,
    symbol: str,
    side: OrderSide,
    price: Any,
    *,
    base_size: Any | None = None,
    quote_size: Any | None = None,
    post_only: bool = False,
    time_in_force: TimeInForce | str = TimeInForce.GTC,
    client_order_id: str | None = None
) -> dict[str, Any]:
    """Builds a payload for a Limit Order.

    Args:
        symbol: The trading symbol (e.g., 'BTC-EUR').
        side: The side of the order (BUY or SELL).
        price: The limit price in quote currency per 1 base currency unit.
        base_size: The amount of the base currency (e.g., BTC) to buy/sell.
        quote_size: The amount of the quote currency (e.g., EUR) to buy/sell.
        post_only: If True, ensures the order is entered as a MAKER order.
        time_in_force: The time in force for the order (e.g., 'gtc' or 'ioc').
        client_order_id: A unique client order ID. A UUID will be generated if not provided.

    Returns:
        dict[str, Any]: The constructed JSON payload for the Revolut X API.

    Raises:
        OrderValidationError: If exactly one of base_size or quote_size is not provided.

    Example:
        >>> OrderPayloadBuilder.build_limit_order('BTC-EUR', OrderSide.SELL, '90000.00', base_size='0.1')
    """
    if (base_size is None and quote_size is None) or (base_size is not None and quote_size is not None):
        raise OrderValidationError("You must specify exactly one field: either 'base_size' or 'quote_size'.")

    execution_instructions: list[str] = ["post_only"] if post_only else ["allow_taker"]

    tif_val = time_in_force.value if isinstance(time_in_force, TimeInForce) else str(time_in_force).lower()

    limit_conf: dict[str, Any] = {
        "price": cls._format_decimal(price),
        "time_in_force": tif_val,
        "execution_instructions": execution_instructions
    }

    if base_size is not None:
        limit_conf["base_size"] = cls._format_decimal(base_size)
    else:
        limit_conf["quote_size"] = cls._format_decimal(quote_size)

    return {
        "client_order_id": client_order_id or str(uuid.uuid4()),
        "symbol": cls._normalize_symbol(symbol),
        "side": side.value if isinstance(side, OrderSide) else str(side).lower(),
        "order_configuration": {
            "limit": limit_conf
        }
    }

build_maker_order classmethod

build_maker_order(symbol: str, side: OrderSide | str, price: Any, *, base_size: Any | None = None, quote_size: Any | None = None, time_in_force: TimeInForce | str = TimeInForce.GTC, client_order_id: str | None = None) -> dict[str, Any]

Builds a payload for a guaranteed zero-fee Maker Order (Limit with post_only=True).

Parameters:

Name Type Description Default
symbol str

The trading symbol (e.g. 'BTC-EUR').

required
side OrderSide | str

OrderSide.BUY or OrderSide.SELL.

required
price Any

Limit price in quote currency per 1 base unit.

required
base_size Any | None

Amount in base currency (e.g. BTC).

None
quote_size Any | None

Amount in quote currency (e.g. EUR).

None
time_in_force TimeInForce | str

Time in force (default: GTC).

GTC
client_order_id str | None

Optional client UUID.

None

Returns:

Type Description
dict[str, Any]

dict[str, Any]: JSON payload ready for the Revolut X API.

Source code in src/revolut_x/helpers.py
@classmethod
def build_maker_order(
    cls,
    symbol: str,
    side: OrderSide | str,
    price: Any,
    *,
    base_size: Any | None = None,
    quote_size: Any | None = None,
    time_in_force: TimeInForce | str = TimeInForce.GTC,
    client_order_id: str | None = None,
) -> dict[str, Any]:
    """Builds a payload for a guaranteed zero-fee Maker Order (Limit with post_only=True).

    Args:
        symbol: The trading symbol (e.g. 'BTC-EUR').
        side: OrderSide.BUY or OrderSide.SELL.
        price: Limit price in quote currency per 1 base unit.
        base_size: Amount in base currency (e.g. BTC).
        quote_size: Amount in quote currency (e.g. EUR).
        time_in_force: Time in force (default: GTC).
        client_order_id: Optional client UUID.

    Returns:
        dict[str, Any]: JSON payload ready for the Revolut X API.
    """
    return cls.build_limit_order(
        symbol=symbol,
        side=side,
        price=price,
        base_size=base_size,
        quote_size=quote_size,
        post_only=True,
        time_in_force=time_in_force,
        client_order_id=client_order_id,
    )

build_market_order classmethod

build_market_order(symbol: str, side: OrderSide, *, base_size: Any | None = None, quote_size: Any | None = None, client_order_id: str | None = None) -> dict[str, Any]

Builds a payload for a Market Order.

Parameters:

Name Type Description Default
symbol str

The trading symbol (e.g., 'BTC-EUR').

required
side OrderSide

The side of the order (BUY or SELL).

required
base_size Any | None

The amount of the base currency (e.g., BTC) to buy/sell.

None
quote_size Any | None

The amount of the quote currency (e.g., EUR) to buy/sell.

None
client_order_id str | None

A unique client order ID. A UUID will be generated if not provided.

None

Returns:

Type Description
dict[str, Any]

dict[str, Any]: The constructed JSON payload for the Revolut X API.

Raises:

Type Description
OrderValidationError

If exactly one of base_size or quote_size is not provided.

Example

OrderPayloadBuilder.build_market_order('BTC-EUR', OrderSide.BUY, quote_size='50.00')

Source code in src/revolut_x/helpers.py
@classmethod
def build_market_order(
    cls,
    symbol: str,
    side: OrderSide,
    *,
    base_size: Any | None = None,
    quote_size: Any | None = None,
    client_order_id: str | None = None
) -> dict[str, Any]:
    """Builds a payload for a Market Order.

    Args:
        symbol: The trading symbol (e.g., 'BTC-EUR').
        side: The side of the order (BUY or SELL).
        base_size: The amount of the base currency (e.g., BTC) to buy/sell.
        quote_size: The amount of the quote currency (e.g., EUR) to buy/sell.
        client_order_id: A unique client order ID. A UUID will be generated if not provided.

    Returns:
        dict[str, Any]: The constructed JSON payload for the Revolut X API.

    Raises:
        OrderValidationError: If exactly one of base_size or quote_size is not provided.

    Example:
        >>> OrderPayloadBuilder.build_market_order('BTC-EUR', OrderSide.BUY, quote_size='50.00')
    """
    if (base_size is None and quote_size is None) or (base_size is not None and quote_size is not None):
        raise OrderValidationError("You must specify exactly one field: either 'base_size' or 'quote_size'.")

    market_conf: dict[str, str] = {}
    if base_size is not None:
        market_conf["base_size"] = cls._format_decimal(base_size)
    else:
        market_conf["quote_size"] = cls._format_decimal(quote_size)

    return {
        "client_order_id": client_order_id or str(uuid.uuid4()),
        "symbol": cls._normalize_symbol(symbol),
        "side": side.value if isinstance(side, OrderSide) else str(side).lower(),
        "order_configuration": {
            "market": market_conf
        }
    }

build_order classmethod

build_order(symbol: str, side: OrderSide | str, order_type: OrderType | str = OrderType.MARKET, *, price: Any | None = None, base_size: Any | None = None, quote_size: Any | None = None, post_only: bool = False, time_in_force: TimeInForce | str = TimeInForce.GTC, client_order_id: str | None = None) -> dict[str, Any]

Builds a payload for either a Market or Limit Order based on order_type.

Parameters:

Name Type Description Default
symbol str

The trading symbol (e.g., 'BTC-EUR').

required
side OrderSide | str

The side of the order (BUY or SELL).

required
order_type OrderType | str

OrderType.MARKET ('market') or OrderType.LIMIT ('limit').

MARKET
price Any | None

Required for limit orders. Must be None for market orders.

None
base_size Any | None

The amount of the base currency (e.g., BTC) to buy/sell.

None
quote_size Any | None

The amount of the quote currency (e.g., EUR) to buy/sell.

None
post_only bool

If True, ensures the order is entered as a MAKER order (limit only).

False
time_in_force TimeInForce | str

The time in force policy (limit only).

GTC
client_order_id str | None

Optional client-side UUID for idempotency.

None

Returns:

Type Description
dict[str, Any]

dict[str, Any]: The constructed JSON payload for the Revolut X API.

Raises:

Type Description
OrderValidationError

If parameters are invalid for the given order_type.

Example

OrderPayloadBuilder.build_order('BTC-EUR', OrderSide.BUY, order_type=OrderType.MARKET, quote_size='50.00')

Source code in src/revolut_x/helpers.py
@classmethod
def build_order(
    cls,
    symbol: str,
    side: OrderSide | str,
    order_type: OrderType | str = OrderType.MARKET,
    *,
    price: Any | None = None,
    base_size: Any | None = None,
    quote_size: Any | None = None,
    post_only: bool = False,
    time_in_force: TimeInForce | str = TimeInForce.GTC,
    client_order_id: str | None = None,
) -> dict[str, Any]:
    """Builds a payload for either a Market or Limit Order based on order_type.

    Args:
        symbol: The trading symbol (e.g., 'BTC-EUR').
        side: The side of the order (BUY or SELL).
        order_type: OrderType.MARKET ('market') or OrderType.LIMIT ('limit').
        price: Required for limit orders. Must be None for market orders.
        base_size: The amount of the base currency (e.g., BTC) to buy/sell.
        quote_size: The amount of the quote currency (e.g., EUR) to buy/sell.
        post_only: If True, ensures the order is entered as a MAKER order (limit only).
        time_in_force: The time in force policy (limit only).
        client_order_id: Optional client-side UUID for idempotency.

    Returns:
        dict[str, Any]: The constructed JSON payload for the Revolut X API.

    Raises:
        OrderValidationError: If parameters are invalid for the given order_type.

    Example:
        >>> OrderPayloadBuilder.build_order('BTC-EUR', OrderSide.BUY, order_type=OrderType.MARKET, quote_size='50.00')
    """
    if isinstance(order_type, str):
        try:
            order_type = OrderType(order_type.lower())
        except ValueError:
            raise OrderValidationError(f"Invalid order_type: '{order_type}'. Expected 'market' or 'limit'.")

    if isinstance(side, str):
        try:
            side = OrderSide(side.lower())
        except ValueError:
            raise OrderValidationError(f"Invalid side: '{side}'. Expected 'buy' or 'sell'.")

    if order_type == OrderType.MARKET:
        if price is not None:
            raise OrderValidationError("Parameter 'price' is not supported for market orders.")
        if post_only:
            raise OrderValidationError("Parameter 'post_only' is not supported for market orders.")
        return cls.build_market_order(
            symbol=symbol,
            side=side,
            base_size=base_size,
            quote_size=quote_size,
            client_order_id=client_order_id,
        )
    elif order_type == OrderType.LIMIT:
        if price is None:
            raise OrderValidationError("Parameter 'price' is required for limit orders.")
        return cls.build_limit_order(
            symbol=symbol,
            side=side,
            price=price,
            base_size=base_size,
            quote_size=quote_size,
            post_only=post_only,
            time_in_force=time_in_force,
            client_order_id=client_order_id,
        )
    else:
        raise OrderValidationError(f"Unsupported order_type: {order_type}")

validate_against_pair_rules classmethod

validate_against_pair_rules(payload: dict[str, Any], pair_rules: dict[str, Any]) -> tuple[bool, list[str]]

Validates the constructed payload against the exchange's trading pair rules.

Parameters:

Name Type Description Default
payload dict[str, Any]

The order payload to validate.

required
pair_rules dict[str, Any]

The trading rules for the specific asset pair.

required

Returns:

Type Description
tuple[bool, list[str]]

tuple[bool, list[str]]: A tuple containing a boolean indicating if the payload is valid, and a list of error messages (if any).

Example

valid, errors = OrderPayloadBuilder.validate_against_pair_rules(payload, rules)

Source code in src/revolut_x/helpers.py
@classmethod
def validate_against_pair_rules(
    cls,
    payload: dict[str, Any],
    pair_rules: dict[str, Any]
) -> tuple[bool, list[str]]:
    """Validates the constructed payload against the exchange's trading pair rules.

    Args:
        payload: The order payload to validate.
        pair_rules: The trading rules for the specific asset pair.

    Returns:
        tuple[bool, list[str]]: A tuple containing a boolean indicating if the payload is valid,
            and a list of error messages (if any).

    Example:
        >>> valid, errors = OrderPayloadBuilder.validate_against_pair_rules(payload, rules)
    """
    errors: list[str] = []
    conf = payload.get("order_configuration", {})

    is_market = "market" in conf
    is_limit = "limit" in conf
    order_data = conf.get("market") if is_market else conf.get("limit", {})

    base_size = order_data.get("base_size")
    quote_size = order_data.get("quote_size")
    price = order_data.get("price")

    min_base = Decimal(pair_rules.get("min_order_size", "0.00000001"))
    max_base = Decimal(pair_rules.get("max_order_size", "1000000"))
    min_quote = Decimal(pair_rules.get("min_order_size_quote", "0.1"))
    max_quote = Decimal(pair_rules.get("max_order_size_quote", "1000000"))

    if base_size:
        b_dec = Decimal(base_size)
        if b_dec < min_base:
            errors.append(f"base_size ({b_dec}) is less than the minimum ({min_base})")
        if b_dec > max_base:
            errors.append(f"base_size ({b_dec}) is greater than the maximum ({max_base})")

    if quote_size:
        q_dec = Decimal(quote_size)
        if q_dec < min_quote:
            errors.append(f"quote_size ({q_dec}) is less than the minimum ({min_quote})")
        if q_dec > max_quote:
            errors.append(f"quote_size ({q_dec}) is greater than the maximum ({max_quote})")

    if is_limit and not price:
        errors.append("Limit order is missing the 'price' field!")

    return len(errors) == 0, errors

calculate_maker_price

calculate_maker_price(side: OrderSide | str, *, best_bid: Any | None = None, best_ask: Any | None = None, last_price: Any | None = None, offset: Any = Decimal('0.10'), tick_size: Any = Decimal('0.01')) -> Decimal

Calculates an optimal limit price to guarantee entry into the order book as a Maker (0.00% fee).

For BUY orders

price = (best_bid or last_price) - offset

For SELL orders: price = (best_ask or last_price) + offset

Parameters:

Name Type Description Default
side OrderSide | str

OrderSide.BUY / OrderSide.SELL or string ('buy' / 'sell').

required
best_bid Any | None

Current best bid price (highest buyer in book).

None
best_ask Any | None

Current best ask price (lowest seller in book).

None
last_price Any | None

Fallback price if best bid/ask is not available.

None
offset Any

Safety offset in quote currency (default: 0.10 EUR/USD).

Decimal('0.10')
tick_size Any

Minimum price increment for quantisation (default: 0.01).

Decimal('0.01')

Returns:

Name Type Description
Decimal Decimal

The quantised target price.

Raises:

Type Description
OrderValidationError

If side is invalid or no valid reference price can be determined.

Example

calculate_maker_price(OrderSide.BUY, best_bid="80000.00", offset="0.10") Decimal('79999.90') calculate_maker_price(OrderSide.SELL, best_ask="80000.50", offset="0.10") Decimal('80000.60')

Source code in src/revolut_x/helpers.py
def calculate_maker_price(
    side: OrderSide | str,
    *,
    best_bid: Any | None = None,
    best_ask: Any | None = None,
    last_price: Any | None = None,
    offset: Any = Decimal("0.10"),
    tick_size: Any = Decimal("0.01"),
) -> Decimal:
    """Calculates an optimal limit price to guarantee entry into the order book as a Maker (0.00% fee).

    For BUY orders:
        price = (best_bid or last_price) - offset
    For SELL orders:
        price = (best_ask or last_price) + offset

    Args:
        side: OrderSide.BUY / OrderSide.SELL or string ('buy' / 'sell').
        best_bid: Current best bid price (highest buyer in book).
        best_ask: Current best ask price (lowest seller in book).
        last_price: Fallback price if best bid/ask is not available.
        offset: Safety offset in quote currency (default: 0.10 EUR/USD).
        tick_size: Minimum price increment for quantisation (default: 0.01).

    Returns:
        Decimal: The quantised target price.

    Raises:
        OrderValidationError: If side is invalid or no valid reference price can be determined.

    Example:
        >>> calculate_maker_price(OrderSide.BUY, best_bid="80000.00", offset="0.10")
        Decimal('79999.90')
        >>> calculate_maker_price(OrderSide.SELL, best_ask="80000.50", offset="0.10")
        Decimal('80000.60')
    """
    if isinstance(side, str):
        try:
            side_enum = OrderSide(side.lower())
        except ValueError:
            raise OrderValidationError(f"Invalid side: '{side}'. Expected 'buy' or 'sell'.")
    elif isinstance(side, OrderSide):
        side_enum = side
    else:
        raise OrderValidationError(f"Invalid side type: {type(side)}")

    offset_dec = Decimal(str(offset))
    tick_dec = Decimal(str(tick_size))

    if side_enum == OrderSide.BUY:
        ref = best_bid if best_bid is not None else last_price
        if ref is None:
            raise OrderValidationError("Cannot calculate Maker BUY price: neither 'best_bid' nor 'last_price' provided.")
        raw_price = Decimal(str(ref)) - offset_dec
    else:
        ref = best_ask if best_ask is not None else last_price
        if ref is None:
            raise OrderValidationError("Cannot calculate Maker SELL price: neither 'best_ask' nor 'last_price' provided.")
        raw_price = Decimal(str(ref)) + offset_dec

    # Quantize to tick size
    return raw_price.quantize(tick_dec, rounding=ROUND_HALF_UP)

normalize_symbol

normalize_symbol(symbol: str) -> str

Normalizes the trading symbol for the Revolut X API.

Accepts formats such as 'BTC/EUR', 'BTC-EUR', 'btc-eur', 'btceur', 'BTCEUR'. Returns the normalized symbol with a dash separator (e.g. 'BTC-EUR').

Parameters:

Name Type Description Default
symbol str

The trading symbol to normalize.

required

Returns:

Name Type Description
str str

The normalized symbol.

Example

normalize_symbol('BTC/EUR') 'BTC-EUR' normalize_symbol('btceur') 'BTC-EUR'

Source code in src/revolut_x/helpers.py
def normalize_symbol(symbol: str) -> str:
    """Normalizes the trading symbol for the Revolut X API.

    Accepts formats such as 'BTC/EUR', 'BTC-EUR', 'btc-eur', 'btceur', 'BTCEUR'.
    Returns the normalized symbol with a dash separator (e.g. 'BTC-EUR').

    Args:
        symbol: The trading symbol to normalize.

    Returns:
        str: The normalized symbol.

    Example:
        >>> normalize_symbol('BTC/EUR')
        'BTC-EUR'
        >>> normalize_symbol('btceur')
        'BTC-EUR'
    """
    clean = symbol.replace("/", "").replace("-", "").upper()
    for quote in ("USDC", "EUR", "USD", "GBP", "CZK"):
        if clean.endswith(quote):
            base = clean[: -len(quote)]
            if base:
                return f"{base}-{quote}"
    return symbol.replace("/", "-").upper()

to_timestamp_ms

to_timestamp_ms(value: TimeLike) -> int

Converts a point in time to a Unix timestamp in milliseconds.

Used by the market data methods so that time ranges can be expressed with datetime objects instead of raw millisecond integers.

Parameters:

Name Type Description Default
value TimeLike

One of

  • int / float — already a Unix timestamp in milliseconds; returned as an int (floats are truncated). Seconds are not auto-detected — multiply by 1000 yourself.
  • :class:~datetime.datetime — timezone-aware values use their own offset, naive values are interpreted as UTC.
  • :class:~datetime.date — midnight UTC on that day.
  • str — an ISO-8601 timestamp (a trailing 'Z' is accepted); naive strings are again interpreted as UTC. A digits-only string of at least :data:MIN_MS_DIGITS digits is read as milliseconds instead, for values arriving from JSON or query parameters.
required

Returns:

Name Type Description
int int

The Unix timestamp in milliseconds.

Raises:

Type Description
TypeError

If the value is not one of the supported types.

ValueError

If a string is neither a valid ISO-8601 timestamp nor a long enough run of digits.

Example

from datetime import datetime, timezone to_timestamp_ms(datetime(2024, 1, 1, tzinfo=timezone.utc)) 1704067200000 to_timestamp_ms("2024-01-01") 1704067200000 to_timestamp_ms(1704067200000) 1704067200000 to_timestamp_ms("1704067200000") 1704067200000

Source code in src/revolut_x/helpers.py
def to_timestamp_ms(value: TimeLike) -> int:
    """Converts a point in time to a Unix timestamp in milliseconds.

    Used by the market data methods so that time ranges can be expressed with
    ``datetime`` objects instead of raw millisecond integers.

    Args:
        value: One of

            * ``int`` / ``float`` — already a Unix timestamp in
              **milliseconds**; returned as an int (floats are truncated).
              Seconds are *not* auto-detected — multiply by 1000 yourself.
            * :class:`~datetime.datetime` — timezone-aware values use their own
              offset, naive values are interpreted as **UTC**.
            * :class:`~datetime.date` — midnight UTC on that day.
            * ``str`` — an ISO-8601 timestamp (a trailing ``'Z'`` is accepted);
              naive strings are again interpreted as UTC.  A digits-only string
              of at least :data:`MIN_MS_DIGITS` digits is read as milliseconds
              instead, for values arriving from JSON or query parameters.

    Returns:
        int: The Unix timestamp in milliseconds.

    Raises:
        TypeError: If the value is not one of the supported types.
        ValueError: If a string is neither a valid ISO-8601 timestamp nor a
            long enough run of digits.

    Example:
        >>> from datetime import datetime, timezone
        >>> to_timestamp_ms(datetime(2024, 1, 1, tzinfo=timezone.utc))
        1704067200000
        >>> to_timestamp_ms("2024-01-01")
        1704067200000
        >>> to_timestamp_ms(1704067200000)
        1704067200000
        >>> to_timestamp_ms("1704067200000")
        1704067200000
    """
    if isinstance(value, bool):
        raise TypeError("Timestamp must be a number, datetime, date, or ISO-8601 string")

    if isinstance(value, (int, float)):
        return int(value)

    if isinstance(value, str):
        text = value.strip()

        digits = text[1:] if text[:1] in "+-" else text
        # str.isdigit() also accepts non-ASCII digits that int() cannot parse.
        if digits.isascii() and digits.isdigit():
            if len(digits) >= MIN_MS_DIGITS:
                return int(text)
            raise ValueError(
                f"Ambiguous numeric timestamp string: {value!r}. A digits-only string is "
                f"read as Unix milliseconds and needs at least {MIN_MS_DIGITS} digits — "
                f"anything shorter lands in 1970 and is far more likely a date that lost "
                f"its separators. Pass an int for milliseconds, or a date like "
                f"'2024-01-01'."
            )

        if text.endswith(("Z", "z")):
            text = f"{text[:-1]}+00:00"
        try:
            value = datetime.fromisoformat(text)
        except ValueError as exc:
            raise ValueError(f"Invalid ISO-8601 timestamp: {value!r}") from exc

    # datetime is a subclass of date, so it has to be checked first.
    if isinstance(value, datetime):
        if value.tzinfo is None:
            value = value.replace(tzinfo=timezone.utc)
        return int(value.timestamp() * 1000)

    if isinstance(value, date):
        return int(datetime.combine(value, time.min, tzinfo=timezone.utc).timestamp() * 1000)

    raise TypeError(
        f"Timestamp must be a number, datetime, date, or ISO-8601 string, "
        f"got {type(value).__name__}"
    )