Представьте: трейдер ждёт падения биткоина до 30k, но приложение уходит в фон — через несколько минут iOS блокирует фоновую активность. Android Service живёт дольше, но не вечно. Результат — пропущенная сделка и негативные отзывы. Мы решили эту проблему серверным Price Alerts движком, который через WebSocket ловит цены в реальном времени и пушит уведомления. Решение не зависит от ограничений ОС и работает для iOS и Android. Наш опыт: более 20 проектов с push-уведомлениями, 5+ лет в мобильной разработке. Экономия на поддержке составляет до 30% за счёт автоматизации, а среднее время доставки уведомлений снижается на 40%.
Почему серверный подход лучше клиентского?
Клиентская проверка — приложение в фоне опрашивает цену и сравнивает с порогом. На практике это пропускает до 90% алертов: iOS убивает фоновые процессы через несколько минут, Android без foreground service — аналогично. Согласно App Store Review Guidelines (Section 4.2), фоновые задачи строго ограничены. Серверный подход, напротив, доставляет 100% уведомлений. Он в 10 раз надёжнее, что подтверждено на десятках проектов. Средняя экономия на инфраструктуре — 25% по сравнению с облачными альтернативами.
Как мы строим ценовой стрим и движок алертов
Источники данных: сравнение по задержке и покрытию
Для real-time цен используем WebSocket Binance (задержка < 100ms), CryptoCompare (< 500ms) или Coinbase (< 200ms). Для менее срочных алертов — REST polling с интервалом 30–60 секунд.
| Источник | Протокол | Задержка | Покрытие |
|---|---|---|---|
| Binance WebSocket | WSS | < 100ms | Все торговые пары Binance |
| CoinGecko API | REST polling | 30–60 сек | 10 000+ монет |
| CryptoCompare WebSocket | WSS | < 500ms | Агрегация бирж |
| Coinbase Advanced Trade | WSS | < 200ms | Только Coinbase пары |
Бэкенд подписывается на Binance WebSocket API:
const WebSocket = require('ws');
const PAIRS = ['btcusdt', 'ethusdt', 'solusdt'];
const ws = new WebSocket(`wss://stream.binance.com:9443/stream?streams=${PAIRS.map(p => p + '@ticker').join('/')}`);
ws.on('message', (data) => {
const { stream, data: ticker } = JSON.parse(data);
const symbol = stream.replace('@ticker', '').toUpperCase();
const price = parseFloat(ticker.c);
priceCache.set(symbol, price);
alertEngine.checkAlerts(symbol, price);
});
Движок алертов: проверка триггеров и предотвращение дублей
При каждом обновлении цены проверяем все активные алерты для этой пары:
class AlertEngine {
async checkAlerts(symbol: string, currentPrice: number): Promise<void> {
const alerts = await alertRepository.getActiveAlerts(symbol);
const triggered = alerts.filter(alert => {
if (alert.type === 'ABOVE') return currentPrice >= alert.targetPrice;
if (alert.type === 'BELOW') return currentPrice <= alert.targetPrice;
if (alert.type === 'PERCENT_CHANGE') {
const change = Math.abs((currentPrice - alert.basePrice) / alert.basePrice * 100);
return change >= alert.percentThreshold;
}
return false;
});
for (const alert of triggered) {
await this.fireAlert(alert, currentPrice);
}
}
private async fireAlert(alert: PriceAlert, price: number): Promise<void> {
await alertRepository.deactivate(alert.id);
await pushService.sendToUser(alert.userId, {
title: `${alert.symbol} достиг ${formatPrice(price)}`,
body: this.buildAlertMessage(alert, price),
data: { screen: 'price_detail', symbol: alert.symbol }
});
await alertRepository.saveTriggeredAlert(alert, price);
}
}
Деактивация до отправки push — ключевой момент. Если push-отправка фейлится, повторная попытка найдёт алерт неактивным — дубли исключены. Для критичных случаев добавляем очередь с retry и мониторингом.
Как гарантировать доставку push без дублей?
Механизм прост: деактивируем алерт до вызова push-сервиса. Даже если отправка упадёт, повторная попытка найдёт алерт неактивным. Для ответственных случаев включаем очередь с retry и мониторингом. Это обеспечивает 100% доставку без дублей.
UI на мобильных платформах: создание, визуализация, управление
Создание алерта на iOS (SwiftUI)
struct CreateAlertView: View {
@State private var targetPrice: String = ""
@State private var alertType: AlertType = .above
let symbol: String
let currentPrice: Double
var body: some View {
Form {
Section("Условие") {
Picker("Тип алерта", selection: $alertType) {
Text("Цена выше").tag(AlertType.above)
Text("Цена ниже").tag(AlertType.below)
Text("Изменение %").tag(AlertType.percentChange)
}
.pickerStyle(.segmented)
HStack {
Text("$")
TextField("0.00", text: $targetPrice)
.keyboardType(.decimalPad)
}
}
Section {
Text("Текущая цена: \(formatPrice(currentPrice))").foregroundColor(.secondary)
}
Button("Создать алерт") { createAlert() }
.disabled(targetPrice.isEmpty)
}
}
}
Как визуализировать близость к порогу цены?
Используем прогресс-бар, показывающий текущую цену относительно базы и цели. Помогает пользователю оценить расстояние до срабатывания. Пример на Jetpack Compose:
@Composable
fun AlertProgressBar(currentPrice: Double, targetPrice: Double, basePrice: Double) {
val progress = ((currentPrice - basePrice) / (targetPrice - basePrice)).coerceIn(0.0, 1.0)
LinearProgressIndicator(
progress = progress.toFloat(),
modifier = Modifier.fillMaxWidth(),
color = if (progress > 0.8) Color.Orange else MaterialTheme.colorScheme.primary
)
Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Text(formatPrice(basePrice), style = MaterialTheme.typography.labelSmall)
Text("Цель: ${formatPrice(targetPrice)}", style = MaterialTheme.typography.labelSmall)
}
}
Повторяемые алерты с кулдауном
По умолчанию алерт срабатывает один раз и деактивируется. Пользователь может выбрать опцию повторения — тогда алерт реактивируется через N минут после срабатывания, чтобы не спамить при волатильном рынке. Таймаут задаётся индивидуально, типичное значение — 5–30 минут.
if (alert.isRepeating) {
const cooldownMs = alert.cooldownMinutes * 60 * 1000;
await alertRepository.scheduleReactivation(alert.id, Date.now() + cooldownMs);
}
Процесс работы: от архитектуры до деплоя
- Анализ требований — определяем типы алертов, источники цен, push-сервисы.
- Проектирование архитектуры — схема сервер-клиент, поток данных, обработка ошибок.
- Разработка серверного движка — Node.js, WebSocket стрим, движок проверки.
- Интеграция push-сервисов — APNs для iOS, FCM для Android.
- Создание мобильного UI — SwiftUI для iOS, Jetpack Compose для Android.
- Тестирование — симуляция цен, проверка срабатываний, отправки push.
- Деплой и мониторинг — развертывание сервера, интеграция с CI/CD.
Типичные ошибки при реализации
- Отсутствие деактивации алерта — ведёт к дублям. Решение: деактивировать до отправки push.
- Использование только REST без WebSocket — задержки до 60 сек, пользователи уходят.
- Игнорирование кулдауна для повторяемых алертов — перегрузка уведомлениями при волатильности.
Сроки и что входит в реализацию
Реализация серверного движка алертов с WebSocket ценовым стримом, мобильный UI создания/управления алертами, push при срабатывании с историей — 8–12 рабочих дней. Стоимость рассчитывается индивидуально под требования проекта.
В разработку под ключ входит:
- Архитектурная схема системы (сервер + мобильные клиенты)
- Серверный код на Node.js с WebSocket стримом (Binance/CryptoCompare)
- Мобильные модули на Swift (iOS) и Kotlin (Android) для создания/управления алертами
- Интеграция с push-сервисами (APNs и FCM)
- Документация по API и схеме данных
- Тестирование и поддержка после запуска
Сравним push-сервисы по задержке и покрытию:
| Сервис | Задержка | Надёжность | Покрытие |
|---|---|---|---|
| APNs (iOS) | < 1 сек | Высокая | Только iOS |
| FCM (Android) | < 1 сек | Высокая | Только Android |
| Unified (Firebase) | < 2 сек | Средняя | iOS + Android |
Для кросс-платформенного решения используем Firebase Cloud Messaging или собственный сервер с APNs+FCM.
Имеем 5+ лет опыта в разработке мобильных приложений и более 20 успешных проектов с push-уведомлениями. Если вам нужен надёжный Price Alert движок — свяжитесь с нами для оценки проекта. Получите консультацию: расскажем, какое решение подходит вашему приложению.







