Реализация агрегации балансов с нескольких бирж в мобильном приложении
Пользователь держит активы на Binance, Bybit и OKX одновременно. Каждая биржа — отдельное приложение, отдельный логин, отдельный интерфейс. Агрегация балансов решает простую задачу: один экран с суммарной позицией по каждому активу и разбивкой по биржам.
Как устроена архитектура под несколько бирж
Каждая биржа — отдельный адаптер, реализующий единый протокол. Это позволяет добавлять новые биржи без изменения бизнес-логики:
// Android, Kotlin
interface ExchangeAdapter {
suspend fun fetchSpotBalances(): Result<List<Balance>>
suspend fun fetchFuturesBalances(): Result<List<Balance>>
val exchangeId: String
}
data class Balance(
val ticker: String,
val available: BigDecimal,
val locked: BigDecimal,
val exchangeId: String
)
class BinanceAdapter(private val apiKey: String, private val secret: String) : ExchangeAdapter {
override val exchangeId = "binance"
override suspend fun fetchSpotBalances(): Result<List<Balance>> = runCatching {
val timestamp = System.currentTimeMillis()
val queryString = "timestamp=$timestamp&recvWindow=5000"
val signature = HmacSHA256.sign(queryString, secret)
val response = apiClient.get("/api/v3/account?$queryString&signature=$signature")
response.balances
.filter { it.free.toBigDecimal() > BigDecimal.ZERO || it.locked.toBigDecimal() > BigDecimal.ZERO }
.map { Balance(it.asset, it.free.toBigDecimal(), it.locked.toBigDecimal(), exchangeId) }
}
}
Агрегатор запускает все адаптеры параллельно через async/await (Kotlin coroutines или Swift TaskGroup), собирает результаты и схлопывает позиции по тикеру:
class BalanceAggregator(private val adapters: List<ExchangeAdapter>) {
suspend fun aggregate(): AggregatedPortfolio = coroutineScope {
val results = adapters.map { adapter ->
async { adapter.fetchSpotBalances() }
}.awaitAll()
val allBalances = results.flatMap { it.getOrElse { emptyList() } }
// Группируем по тикеру, суммируем
val byTicker = allBalances.groupBy { it.ticker }
val aggregated = byTicker.map { (ticker, positions) ->
AggregatedPosition(
ticker = ticker,
totalAvailable = positions.sumOf { it.available },
byExchange = positions.associateBy { it.exchangeId }
)
}
AggregatedPortfolio(positions = aggregated, fetchedAt = Instant.now())
}
}
Что усложняет задачу
Разные форматы тикеров. Binance называет Tether USDT, некоторые биржи — USDT, другие — USDt. OKX для TON использует TON-USDT как торговую пару, а базовый актив — TON. Нужна нормализация: таблица алиасов и canonical ticker для каждого актива.
Ошибки одной биржи не должны ронять всё. Если Bybit вернул 503, пользователь должен видеть данные Binance и OKX с пометкой «Bybit: данные недоступны». Поэтому Result<> — не опция, а обязательный тип возврата. На UI: каждая биржа-источник показывает статус (зелёный/красный/серый).
API-ключи и безопасность. Пользователь добавляет несколько пар ключей. Каждая пара шифруется отдельно через Android Keystore / iOS Keychain с привязкой к биометрии. При запросе к бирже ключ расшифровывается в памяти, используется, не сохраняется в heap дольше необходимого.
Clock drift. Binance и Bybit требуют, чтобы timestamp был близко к серверному времени (±5 секунд). При первом запросе к каждой бирже синхронизируем время через их /time endpoint и держим offset.
UI: что показывать
Основной экран — список активов с суммарной позицией. Тап на актив — раскрытие по биржам. Дополнительный экран — breakdown по биржам: сколько на каждой в USD. Обновление по pull-to-refresh и автоматически каждые 5 минут, пока приложение активно.
Небольшая таблица для понимания объёма:
| Биржа | Эндпоинт | Авторизация | Особенности |
|---|---|---|---|
| Binance | /api/v3/account |
HMAC-SHA256 | recvWindow, clock sync |
| Bybit V5 | /v5/account/wallet-balance |
HMAC-SHA256 | accountType: UNIFIED |
| OKX | /api/v5/account/balance |
HMAC + passphrase | 3 заголовка авторизации |
| Gate.io | /api/v4/spot/accounts |
HMAC-SHA512 | — |
Сроки
Интеграция трёх бирж с агрегацией и UI: 5–8 рабочих дней. Каждая дополнительная биржа — 1 день. Стоимость рассчитывается индивидуально после анализа требований.







