Запуск/Зупинення торгової стратегії в мобільному додатку
Здається, що запуск/зупинення бота — це просто одна кнопка. Насправді це послідовність дій з підтвердженнями, перевірками стану та обробкою граничних випадків. Натисніть «Зупинити», коли є відкриті позиції — що має відбутися? Закрити позиції? Чекати природного закриття? Просто зупинити нові ордери?
Стани бота та переходи
Торговий бот — це FSM. Мінімальний набір станів:
- STOPPED — бот не працює, немає позицій
- STARTING — ініціалізація в процесі (підключення до біржі, завантаження стану)
- RUNNING — активно торгує
- STOPPING — отримав команду зупинити, чекає завершення поточного циклу
- ERROR — помилка (невалідний API-ключ, недостатньо коштів, біржа недоступна)
Мобільний UI повинен відображати кожен стан і блокувати несумісні дії:
// iOS, SwiftUI
struct BotControlView: View {
@ObservedObject var viewModel: BotControlViewModel
var body: some View {
VStack(spacing: 16) {
StatusBadge(status: viewModel.bot.status)
switch viewModel.bot.status {
case .stopped:
Button("Запустити") { viewModel.start() }
.buttonStyle(.primary)
case .running:
Button("Зупинити") { viewModel.showStopDialog = true }
.buttonStyle(.destructive)
case .starting, .stopping:
HStack {
ProgressView()
Text(viewModel.bot.status == .starting ? "Запуск..." : "Зупинення...")
}
.foregroundColor(.secondary)
case .error(let message):
VStack {
Label(message, systemImage: "exclamationmark.triangle")
.foregroundColor(.red)
Button("Перезапустити") { viewModel.start() }
}
}
}
.confirmationDialog("Зупинити бота?", isPresented: $viewModel.showStopDialog) {
Button("Зупинити та закрити позиції", role: .destructive) {
viewModel.stop(closePositions: true)
}
Button("Зупинити без закриття") {
viewModel.stop(closePositions: false)
}
Button("Скасувати", role: .cancel) {}
}
}
}
confirmationDialog на iOS — нативний action sheet. Користувач явно вибирає поведінку при зупиненні.
Оптимістичний проти реального статусу
Коли користувач натисне «Запустити», кнопка повинна одразу перейти до STARTING, а не чекати відповідь сервера. Але реальний статус приходить з сервера. Два джерела істини конфліктують.
Рішення: установіть локальний pendingStatus оптимістично, одночасно надішліть запит. На відповідь сервера — замініть локальний статус статусом сервера. Якщо запит не вдається — поверніть локальний статус та покажіть помилку.
// Android, ViewModel
fun start() {
_uiState.update { it.copy(localStatus = BotStatus.STARTING) }
viewModelScope.launch {
runCatching { repository.startBot(botId) }
.onSuccess { serverBot -> _uiState.update { it.copy(bot = serverBot, localStatus = null) } }
.onFailure { err ->
_uiState.update { it.copy(localStatus = null, error = err.message) }
}
}
}
Мультистратегійний бот
Якщо бот підтримує кілька стратегій, запуск/зупинення — окремо для кожної. Список стратегій з індивідуальними toggle-перемикачами та агрегованим статусом бота зверху. Toggle для конкретної стратегії надсилає PATCH /bots/{id}/strategies/{strategyId} з {active: true/false}.
Важливо: зміна активності стратегії та зупинення бота — різні операції. Деактивація стратегії видаляє її з торгівлі, але бот продовжує роботу з решти.
Push-сповіщення при змені статусу
Коли бот переходить до ERROR або автоматично зупиняється (наприклад, досягнута добова межа збитків) — push через FCM/APNs. Користувач повинен дізнатися без відкриття додатку.
На backend: після кожної зміни статусу опублікувати подію до черги, воркер надсилає push. Мобільний додаток реєструє FCM token при логіні та оновлює при кожному запуску додатку.
Що входить до роботи
- Компонент керування ботом з відображенням всіх статусів
- Діалог підтвердження з вибором режиму зупинення
- Оптимістичне оновлення статусу
- Управління окремими стратегіями (якщо застосовується)
- Push-сповіщення при автоматичних змінах статусу
Терміни
3–5 робочих днів залежно від кількості стратегій та складності FSM. Вартість розраховується індивідуально після аналізу вимог.







