$ sudo teach IT
Модуль 2 · CryptoPay Client

Урок 2.3 — Курсы валют и конвертация

Освоим методы get_exchange_rates(), get_currencies(), exchange() и get_rates_image() для работы с курсами криптовалют и фиатных валют.

Курсы валют — сердце любой платёжной системы. Crypto Pay API предоставляет не только курсы криптовалют к фиатным деньгам, но и удобные инструменты для конвертации. В этом уроке мы научимся получать актуальные курсы, список поддерживаемых валют, конвертировать суммы и даже генерировать изображения с курсами.

💱

Метод get_exchange_rates() — все курсы валют

Метод get_exchange_rates() возвращает список всех текущих курсов обмена, поддерживаемых Crypto Pay API. Это базовый метод для любой работы с конвертацией.

Сигнатура метода

async def get_exchange_rates(self) -> list[ExchangeRate]:

Не требует параметров. Возвращает список ExchangeRate.

Python · Получение всех курсов
import asyncio
from aiosend import CryptoPay

async def main():
    cp = CryptoPay(token="1234:TOKEN")
    rates = await cp.get_exchange_rates()
    print(f"Всего курсов: {len(rates)}")
    for r in rates[:10]:  # первые 10
        print(f"{r.source} -> {r.target}: {r.rate}")

asyncio.run(main())

# Пример вывода:
# USDT -> USD: 1.0001
# USDT -> EUR: 0.9250
# USDT -> RUB: 92.5000
# TON -> USD: 3.4512
# TON -> EUR: 3.1924
# BTC -> USD: 67500.00
# BTC -> EUR: 62437.50
# ETH -> USD: 3450.00
# ETH -> RUB: 319125.00
# LTC -> USD: 78.50

Обратите внимание: курсов обычно около 200+ (количество криптовалют × количество фиатных валют).

Объект ExchangeRate и его поля

Поле Тип Описание
source Asset | Fiat | str Исходная валюта (крипто или фиат). Например: "USDT", "TON", "BTC".
target Fiat | str Целевая валюта (всегда фиат). Например: "USD", "EUR", "RUB".
rate float Текущий курс: сколько единиц target стоит 1 единица source.
is_valid bool True, если курс актуален. Устаревшие курсы помечаются False.
is_crypto bool True, если source — криптовалюта.
is_fiat bool True, если source — фиатная валюта.
Python · Детальный анализ курса
import asyncio
from aiosend import CryptoPay

async def main():
    cp = CryptoPay(token="1234:TOKEN")
    rates = await cp.get_exchange_rates()

    for r in rates:
        if r.source == "USDT" and r.target == "USD":
            print(f"Курс USDT → USD: {r.rate}")
            print(f"  Актуален: {r.is_valid}")
            print(f"  Source — криптовалюта: {r.is_crypto}")
            print(f"  Source — фиат: {r.is_fiat}")
            break

asyncio.run(main())

# Вывод:
# Курс USDT → USD: 1.0001
#   Актуален: True
#   Source — криптовалюта: True
#   Source — фиат: False

Объект ExchangeRate также имеет метод update():

Python · Обновление курса
import asyncio
from aiosend import CryptoPay

async def main():
    cp = CryptoPay(token="1234:TOKEN")
    rates = await cp.get_exchange_rates()

    # Находим нужный курс
    ton_usd = None
    for r in rates:
        if r.source == "TON" and r.target == "USD":
            ton_usd = r
            break

    if ton_usd:
        print(f"Курс TON/USD: {ton_usd.rate}")
        await ton_usd.update()
        print(f"Обновлённый курс: {ton_usd.rate}")

asyncio.run(main())

🔍 Исходный код ExchangeRate

class ExchangeRate(CryptoPayObject):
    is_valid: bool                    # курс актуален?
    is_crypto: bool                   # source — крипта?
    is_fiat: bool                     # source — фиат?
    source: Asset | Fiat | str        # исходная валюта
    target: Fiat | str                # целевая (всегда фиат)
    rate: float                       # курс

    async def update(self) -> None:
        exchange_rates = await self._client.get_exchange_rates()
        for ex_rate in exchange_rates:
            if ex_rate.source == self.source \
               and ex_rate.target == self.target:
                self.__dict__ = ex_rate.__dict__
                break
💵

Метод get_currencies() — список валют

Метод get_currencies() возвращает список всех валют (криптовалют и фиатных), поддерживаемых Crypto Pay API. В отличие от курсов, этот список статичен и меняется редко (только когда добавляются новые валюты).

Сигнатура метода

async def get_currencies(self) -> list[Currency]:

Не требует параметров. Возвращает список Currency.

Объект Currency и его поля

Поле Тип Описание
code Fiat | Asset | str Код валюты. Например: "USDT", "TON", "USD", "EUR".
name str Полное название валюты. Например: "Tether", "Toncoin", "United States Dollar".
decimals int Количество знаков после запятой. Например: USDT = 2, BTC = 8, TON = 9.
is_blockchain bool True, если это нативная монета блокчейна (TON, TRX).
is_stablecoin bool True, если это стейблкоин (USDT, USDC).
is_fiat bool True, если это фиатная валюта (USD, EUR, RUB).
url str | None Ссылка на страницу валюты (обычно CoinMarketCap или CoinGecko). Может быть None.
Python · Получение списка валют
import asyncio
from aiosend import CryptoPay

async def main():
    cp = CryptoPay(token="1234:TOKEN")
    currencies = await cp.get_currencies()

    print("Криптовалюты:")
    for c in currencies:
        if not c.is_fiat:
            info = []
            if c.is_blockchain:
                info.append("нативная монета")
            if c.is_stablecoin:
                info.append("стейблкоин")
            if c.url:
                info.append(f"url: {c.url}")
            tag = f" ({', '.join(info)})" if info else ""
            print(f"  {c.code:<8} | {c.name:<30} | decimals={c.decimals}{tag}")

    print("\nФиатные валюты:")
    for c in currencies:
        if c.is_fiat:
            print(f"  {c.code:<8} | {c.name:<30} | decimals={c.decimals}")

asyncio.run(main())

# Пример вывода:
# Криптовалюты:
#   USDT     | Tether                         | decimals=2 (стейблкоин)
#   TON      | Toncoin                        | decimals=9 (нативная монета)
#   BTC      | Bitcoin                        | decimals=8
#   ETH      | Ethereum                       | decimals=18
#
# Фиатные валюты:
#   USD      | United States Dollar           | decimals=2
#   EUR      | Euro                           | decimals=2
#   RUB      | Russian Ruble                  | decimals=2

Поле decimals особенно важно для правильного форматирования сумм. Например, если вы показываете пользователю баланс в BTC, нужно округлять до 8 знаков, а не до 2:

Python · Форматирование с decimals
import asyncio
from aiosend import CryptoPay

async def format_balance(cp, asset_code: str, amount: float) -> str:
    """Форматирует сумму с правильным количеством знаков."""
    currencies = await cp.get_currencies()
    for c in currencies:
        if c.code == asset_code:
            return f"{amount:.{c.decimals}f} {c.code}"
    return f"{amount} {asset_code}"

async def main():
    cp = CryptoPay(token="1234:TOKEN")
    print(await format_balance(cp, "USDT", 150.5))   # "150.50 USDT"
    print(await format_balance(cp, "BTC", 0.12345678))  # "0.12345678 BTC"
    print(await format_balance(cp, "TON", 100.0))    # "100.000000000 TON"

asyncio.run(main())

💡 Для чего нужен decimals?

Разные криптовалюты имеют разную точность. BTC делится до 8 знаков (сатоши), USDT до 2 (центы), TON до 9 (нанотоны). Используйте decimals из объекта Currency, чтобы всегда показывать правильное количество знаков.

🔄

Метод exchange() — конвертация сумм

Метод exchange() — это удобная обёртка над get_exchange_rates(). Он конвертирует указанную сумму из одной валюты в другую по текущему курсу.

Сигнатура метода

async def exchange(
    self,
    amount: float,
    source: Asset | Fiat | str,
    target: Asset | Fiat | str,
) -> float:

amount — сумма в исходной валюте. source — код исходной валюты. target — код целевой валюты. Возвращает float. Выбрасывает CryptoPayError, если курс не найден.

Параметр Тип Описание
amount float Сумма для конвертации. Например: 100.0.
source Asset | Fiat | str Код исходной валюты. Например: "USDT", "USD".
target Asset | Fiat | str Код целевой валюты. Например: "USD", "RUB".
Python · Конвертация 10 USDT в USD
import asyncio
from aiosend import CryptoPay

async def main():
    cp = CryptoPay(token="1234:TOKEN")

    # 10 USDT → USD
    result = await cp.exchange(10, "USDT", "USD")
    print(f"10 USDT = {result:.2f} USD")  # 10.00 USD

    # 1000 RUB → USDT
    result = await cp.exchange(1000, "RUB", "USDT")
    print(f"1000 RUB = {result:.2f} USDT")  # ~10.81 USDT

    # 1 BTC → EUR
    result = await cp.exchange(1, "BTC", "EUR")
    print(f"1 BTC = {result:.2f} EUR")  # ~62437.50 EUR

    # 500 TON → USD
    result = await cp.exchange(500, "TON", "USD")
    print(f"500 TON = {result:.2f} USD")  # ~1725.60 USD

asyncio.run(main())
Python · Конвертация с использованием Enum
import asyncio
from aiosend import CryptoPay
from aiosend.enums import Asset, Fiat

async def main():
    cp = CryptoPay(token="1234:TOKEN")

    # Используем Enum вместо строк (безопаснее)
    result = await cp.exchange(100, Asset.USDT, Fiat.USD)
    print(f"100 USDT = {result:.2f} USD")

    result = await cp.exchange(50, Asset.TON, Fiat.RUB)
    print(f"50 TON = {result:.2f} RUB")

    result = await cp.exchange(0.5, Asset.BTC, Fiat.EUR)
    print(f"0.5 BTC = {result:.2f} EUR")

asyncio.run(main())
Python · Как exchange() работает внутри
async def exchange(self, amount, source, target):
    # Получаем все курсы
    ex_rates = await self.get_exchange_rates()

    for ex_rate in ex_rates:
        # Прямой курс: 1 source = rate target
        if ex_rate.source == source and ex_rate.target == target:
            return ex_rate.rate * amount

        # Обратный курс: 1 target = rate source
        if ex_rate.source == target and ex_rate.target == source:
            return amount / ex_rate.rate

    # Курс не найден
    msg = f"Exchange rate for {source} => {target} not found"
    raise CryptoPayError(msg)

Как видите, метод умеет работать в обе стороны: если прямого курса нет, он попробует обратный (разделив сумму на курс). Это очень удобно.

⚠️ Важно: точность конвертации

Курсы берутся из Crypto Pay API и могут незначительно отличаться от рыночных. API добавляет небольшую маржу. Для точных расчётов используйте официальный курс Crypto Pay.

Python · Обработка ошибки при конвертации
import asyncio
from aiosend import CryptoPay
from aiosend.exceptions import CryptoPayError

async def main():
    cp = CryptoPay(token="1234:TOKEN")

    try:
        result = await cp.exchange(100, "SOL", "USD")
        print(f"100 SOL = {result:.2f} USD")
    except CryptoPayError as e:
        print(f"Не удалось конвертировать: {e}")
        # Exchange rate for SOL => USD not found

asyncio.run(main())
🖼️

Метод get_rates_image() — изображение с курсами

Метод get_rates_image() генерирует URL изображения с курсом валюты. Это полезно для создания красивых карточек курсов для публикации в соцсетях или мессенджерах.

Сигнатура метода

def get_rates_image(
    self,
    base: Asset | Fiat | str,
    quote: Asset | Fiat | str,
    rate: float,
    percent: float,
) -> str:

Возвращает str — URL изображения. Внимание: метод синхронный (не async), так как не делает HTTP-запрос, а только формирует URL.

Параметр Тип Описание
base Asset | Fiat | str Базовая валюта (слева). Например: "USDT", "TON".
quote Asset | Fiat | str Котируемая валюта (справа). Например: "USD", "EUR".
rate float Текущий курс.
percent float Процент изменения курса (положительное = рост, отрицательное = падение).
Python · Генерация изображения с курсом
import asyncio
from aiosend import CryptoPay

async def main():
    cp = CryptoPay(token="1234:TOKEN")

    # Получаем курс TON → USD
    rates = await cp.get_exchange_rates()
    ton_usd = None
    for r in rates:
        if r.source == "TON" and r.target == "USD":
            ton_usd = r
            break

    # Генерируем изображение (метод синхронный, без await)
    image_url = cp.get_rates_image(
        base="TON",
        quote="USD",
        rate=ton_usd.rate,
        percent=5.2,  # процент изменения
    )
    print(f"Изображение: {image_url}")
    # https://imggen.send.tg/rates/image?base=TON"e=USD&rate=3.45&percent=5.2

asyncio.run(main())
Python · Автоматическое получение процента изменения
import asyncio
from aiosend import CryptoPay

async def create_rate_card(
    cp: CryptoPay,
    base: str,
    quote: str,
    percent: float = 0.0,
) -> str:
    """Создаёт карточку курса и возвращает URL изображения."""
    rates = await cp.get_exchange_rates()
    for r in rates:
        if r.source == base and r.target == quote:
            return cp.get_rates_image(
                base=base,
                quote=quote,
                rate=r.rate,
                percent=percent,
            )
    msg = f"Курс {base} → {quote} не найден"
    raise ValueError(msg)

async def main():
    cp = CryptoPay(token="1234:TOKEN")

    # Карточка TON/USD
    url = await create_rate_card(cp, "TON", "USD", percent=3.5)
    print(f"TON/USD: {url}")

    # Карточка BTC/USD
    url = await create_rate_card(cp, "BTC", "USD", percent=-1.2)
    print(f"BTC/USD: {url}")

asyncio.run(main())

🔍 Как это работает?

get_rates_image() не генерирует изображение на вашем сервере. Он возвращает URL сервиса imggen.send.tg, который динамически создаёт PNG-картинку с курсом. Вы можете использовать этот URL в Telegram, вебе или где угодно.

🧩

Практический пример: конвертер валют

Давайте создадим полноценный конвертер валют, который конвертирует любую поддерживаемую валюту и показывает красивый отчёт.

Python · Конвертер валют
import asyncio
from aiosend import CryptoPay
from aiosend.exceptions import CryptoPayError

class CurrencyConverter:
    """Конвертер криптовалют и фиатных валют."""

    def __init__(self, token: str):
        self.cp = CryptoPay(token=token)

    async def get_supported_pairs(self) -> list[str]:
        """Возвращает список доступных валютных пар."""
        rates = await self.cp.get_exchange_rates()
        pairs = []
        for r in rates:
            pairs.append(f"{r.source} → {r.target} (курс: {r.rate})")
        return pairs

    async def convert(
        self,
        amount: float,
        source: str,
        target: str,
    ) -> dict:
        """
        Конвертирует сумму и возвращает детальную информацию.

        Возвращает словарь с полями:
        - amount: исходная сумма
        - source: исходная валюта
        - target: целевая валюта
        - result: результат конвертации
        - rate: использованный курс
        """
        rate = None
        rates = await self.cp.get_exchange_rates()
        for r in rates:
            if r.source == source and r.target == target:
                result = r.rate * amount
                rate = r.rate
                break
            if r.source == target and r.target == source:
                result = amount / r.rate
                rate = r.rate
                break

        if rate is None:
            msg = f"Курс {source} → {target} не найден"
            raise CryptoPayError(msg)

        return {
            "amount": amount,
            "source": source,
            "target": target,
            "result": result,
            "rate": rate,
        }

    async def show_all_rates(self, base: str) -> None:
        """Показывает курсы базовой валюты ко всем доступным."""
        rates = await self.cp.get_exchange_rates()
        print(f"\nКурсы для {base}:")
        print("-" * 50)
        for r in rates:
            if r.source == base:
                print(f"  → {r.target:<6} {r.rate:<15.6f} {'✅' if r.is_valid else '❌'}")
        print("-" * 50)

async def main():
    # Создаём конвертер
    converter = CurrencyConverter("1234:TOKEN")

    # Пример 1: простая конвертация
    result = await converter.convert(100, "USDT", "RUB")
    print(f"{result['amount']} {result['source']} = "
          f"{result['result']:.2f} {result['target']} "
          f"(курс: {result['rate']:.4f})")

    # Пример 2: конвертация с деталями
    result = await converter.convert(1, "BTC", "USD")
    print(f"\nBTC → USD:")
    print(f"  Сумма: {result['amount']} {result['source']}")
    print(f"  Результат: ${result['result']:,.2f} {result['target']}")
    print(f"  Курс: {result['rate']:,.2f}")

    # Пример 3: показать все курсы для TON
    await converter.show_all_rates("TON")

    # Пример 4: обратная конвертация (USD → USDT)
    result = await converter.convert(100, "USD", "USDT")
    print(f"\n100 USD = {result['result']:.4f} USDT")

asyncio.run(main())

# Пример вывода:
# 100 USDT = 9250.00 RUB (курс: 92.5000)
#
# BTC → USD:
#   Сумма: 1 BTC
#   Результат: $67,500.00 USD
#   Курс: 67,500.00
#
# Курсы для TON:
# --------------------------------------------------
#   → USD    3.451200        ✅
#   → EUR    3.192360        ✅
#   → RUB    319.236000      ✅
# --------------------------------------------------
#
# 100 USD = 99.99 USDT
⚖️

Сравнение методов работы с курсами

Метод Возвращает Async? Когда использовать
get_exchange_rates() list[ExchangeRate] ✅ Когда нужны все курсы или конкретная пара source→target
get_currencies() list[Currency] ✅ Когда нужен список поддерживаемых валют и их свойства
exchange() float ✅ Когда нужно сконвертировать сумму (удобная обёртка)
get_rates_image() str (URL) ❌ (синхронный) Когда нужно получить URL изображения с курсом

Как выбрать нужный метод?

  • Нужно узнать курс TON к USD? → get_exchange_rates() + фильтр
  • Нужно сконвертировать 10 TON в USD? → exchange(10, "TON", "USD")
  • Нужно узнать, стейблкоин ли USDT? → get_currencies() + поле is_stablecoin
  • Нужно показать картинку курса в чате? → get_rates_image()
📌

Что важно запомнить

1️⃣
get_exchange_rates() — все курсы. Возвращает ~200+ объектов ExchangeRate. Поля: source, target, rate, is_valid, is_crypto, is_fiat.
2️⃣
get_currencies() — все валюты. Статичный список. Поля: code, name, decimals, is_blockchain, is_stablecoin, is_fiat, url. Используйте decimals для форматирования.
3️⃣
exchange() — конвертация. Удобная обёртка: amount, source, target. Работает с прямым и обратным курсом. Выбрасывает CryptoPayError, если курс не найден.
4️⃣
get_rates_image() — URL картинки. Синхронный метод, не делает HTTP-запросов. Просто формирует URL для сервиса imggen.send.tg.
5️⃣
Курсы от Crypto Pay могут отличаться от биржевых. API добавляет небольшую маржу. Для создания инвойсов используйте именно эти курсы, а не внешние.
💻

Практическая задача

Задача: Крипто-дашборд с курсами

Напишите асинхронную функцию print_crypto_dashboard, которая:

  1. Принимает cp (клиент CryptoPay).
  2. Получает список всех курсов через get_exchange_rates().
  3. Получает список всех валют через get_currencies().
  4. Выводит таблицу: Криптовалюта | Курс к USD | Курс к EUR | Курс к RUB | Тип (стейблкоин/нативная/обычная)
  5. Для каждой криптовалюты находит курс к USD, EUR и RUB.
  6. Определяет тип криптовалюты из Currency (is_stablecoin, is_blockchain).

Пример вывода:

Крипто-дашборд:
═══════════════════════════════════════════════════════════════════
  Актив    USD        EUR        RUB        Тип
───────────────────────────────────────────────────────────────────
  USDT     1.0001     0.9250     92.5000    стейблкоин
  TON      3.4512     3.1924     319.2360   нативная монета
  BTC      67500.00   62437.50   6250000.0  —
  ETH      3450.00    3191.25    319125.0   —
═══════════════════════════════════════════════════════════════════

Урок 2.3: Курсы валют и конвертация

8 вопросов