Реалізація агрегації балансів з кількох бирж у мобільному приложенні
Користувач тримає активи на 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 хвилин, поки приложение активне.
Невеликої таблиці для розуміння обсягу:
| Біржа | Endpoint | Авторизація | Особливості |
|---|---|---|---|
| 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 день. Вартість розраховується індивідуально після аналізу вимог.







