Реализация размещения лимитного ордера в мобильном приложении биржи
Лимитный ордер — это тип заявки, которую биржа исполняет только при достижении указанной цены или лучше. Пользователь вводит цену и объём, нажимает «Купить» — и ордер висит в ордербуке до исполнения, отмены или истечения времени (GTC, IOC, FOK). На первый взгляд это форма с двумя полями. На практике — связанная логика трёх InputField, пересчёт в реальном времени, валидация баланса, подтверждение с актуальной ценой и обработка edge-case на стороне API.
Форма ордера: взаимный пересчёт полей
Три поля — Price, Amount, Total — связаны формулой Total = Price × Amount. Пользователь может менять любое из них, два оставшихся пересчитываются автоматически. Это ломает простую реактивность: если слушать onChange всех трёх полей одновременно, получишь бесконечный цикл.
Решение — источник правды: при изменении Price или Amount пересчитывается Total, при изменении Total пересчитывается Amount (Price остаётся фиксированной). Флаг isUserEditing или debounce 150мс предотвращают зацикливание.
// iOS — взаимный пересчёт полей через Combine
class LimitOrderViewModel: ObservableObject {
@Published var price: String = ""
@Published var amount: String = ""
@Published var total: String = ""
private var cancellables = Set<AnyCancellable>()
private var isUpdating = false
init() {
Publishers.CombineLatest($price, $amount)
.debounce(for: .milliseconds(100), scheduler: RunLoop.main)
.sink { [weak self] p, a in
guard let self, !self.isUpdating else { return }
guard let price = Decimal(string: p), let amount = Decimal(string: a) else { return }
self.isUpdating = true
self.total = "\(price * amount)"
self.isUpdating = false
}
.store(in: &cancellables)
}
}
На Android — TextWatcher или Flow с distinctUntilChanged() в ViewModel. MutableStateFlow для каждого поля, combine в CoroutineScope.
Слайдер процента от баланса
Стандартный UX: кнопки 25% / 50% / 75% / 100% под полем Amount. При нажатии на 50% — Amount устанавливается как доступный баланс / 2 / текущая цена. Если Price пустая — кнопки неактивны. Если баланс меньше минимального лота биржи — показываем предупреждение, не блокируем кнопку.
Валидация перед отправкой
Минимальный набор проверок на клиенте:
- Price > 0 и Price в допустимом диапазоне (биржа возвращает
minPrice,maxPrice,tickSizeвexchangeInfo) - Amount >=
minQty, Amount кратенstepSize - Total >=
minNotional(минимальная сумма сделки, например 10 USDT) - Доступный баланс >= Total (при покупке) или >= Amount (при продаже)
tickSize и stepSize — важнее, чем кажется. Binance API вернёт ошибку -1013 LOT_SIZE, если Amount не кратен stepSize. Округление через floor(amount / stepSize) * stepSize с BigDecimal (не float!) предотвращает эту ошибку.
// Android — округление с учётом stepSize
fun roundToStep(value: BigDecimal, step: BigDecimal): BigDecimal {
return (value.divide(step, 0, RoundingMode.FLOOR)).multiply(step)
.setScale(step.scale(), RoundingMode.FLOOR)
}
Подтверждение ордера
Перед отправкой на API — диалог подтверждения с итоговыми параметрами. Цена в этот момент может измениться — показывай актуальную рыночную цену рядом с лимитной, чтобы пользователь видел расстояние до рынка. Кнопка подтверждения — с haptic feedback (UIImpactFeedbackGenerator / HapticFeedback в Jetpack Compose).
После успешного ответа API — обновить список открытых ордеров. Это либо WebSocket-событие executionReport (Binance), либо polling через 1–2 секунды.
Обработка ошибок API
Типичные коды ошибок Binance REST API при размещении ордера:
| Код | Причина | Решение в UI |
|---|---|---|
| -1013 LOT_SIZE | Amount не кратен stepSize | Округлить автоматически |
| -1013 MIN_NOTIONAL | Total < minNotional | Показать минимальную сумму |
| -2010 Account has insufficient balance | Недостаточно средств | Подсветить поле Amount красным |
| -1021 Timestamp for this request | Рассинхрон времени | Синхронизировать timestamp с сервером |
Ошибку -1021 клиент не должен показывать пользователю — нужно повторить запрос с скорректированным recvWindow или синхронизировать время через /api/v3/time.
Сроки: 2–3 дня: взаимный пересчёт полей, слайдер процента, валидация по правилам биржи, диалог подтверждения, обработка ошибок API.







