Реализация подписки на торговые сигналы в мобильном приложении
Торговые сигналы — время критично. Пользователь заплатил за подписку, но получил push-уведомление о сигнале через 40 секунд после его генерации. К этому моменту цена уже ушла. Вся техническая задача здесь — минимальная задержка от генерации сигнала на сервере до отображения на экране.
Архитектура доставки сигналов
FCM / APNS для доставки сигналов — ненадёжное решение в одиночку. Apple и Google не гарантируют latency push-уведомлений: в периоды нагрузки задержка может достигать 1–5 минут. Правильная схема — два канала параллельно:
WebSocket (primary). Пока приложение активно (foreground) — подключено к серверу через WebSocket (URLSessionWebSocketTask на iOS, OkHttp WebSocket на Android). Сигнал приходит в течение 100–500 мс от генерации. При потере соединения — exponential backoff reconnect (1s → 2s → 4s → 8s → max 30s).
Push (fallback). Когда приложение в background или closed — FCM/APNS Data Message (silent push на iOS) будит приложение через BGProcessingTask или UNNotificationServiceExtension. NSE запускается на каждый push и может показать уведомление с расшифрованным содержимым — туда добавляем тикер, направление (BUY/SELL), цену входа.
Важный нюанс iOS: UNNotificationServiceExtension имеет ~30 секунд на обработку. Если за это время не вызван contentHandler — система показывает исходный push. NSE не должен делать тяжёлые сетевые запросы.
Подписка и доступ к сигналам
Модель: платный доступ через Auto-Renewable Subscription (StoreKit 2 / Play Billing Library 6+). Пользователь без активной подписки видит сигналы с задержкой (демо-режим) или не видит вовсе.
Валидация entitlement на сервере через App Store Server Notifications V2 (APNS-подписанные JWT события от Apple) и Google Play Real-time Developer Notifications (Pub/Sub). Не полагаемся на клиентскую валидацию — сервер WebSocket при установке соединения проверяет subscription_status из собственной БД, обновляемой по серверным webhook'ам от сторов.
Grace period: если подписка не продлилась из-за биллинга — не рубим доступ мгновенно. StoreKit 2 Transaction.currentEntitlements возвращает транзакцию со статусом inGracePeriod — показываем баннер «Проблема с оплатой, обновите карту» и даём 3–6 дней. Это снижает involuntary churn.
UI: real-time лента сигналов
Лента сигналов — UICollectionView с DiffableDataSource (iOS) или LazyColumn (Compose, Android). Новый сигнал вставляется в начало списка с анимацией без перерисовки всего списка. Для сигналов с ценовыми данными — форматирование через NumberFormatter с локалью пользователя (разные рынки — разные форматы цен).
Каждый сигнал: тикер, тип (BUY/SELL/CLOSE), цена входа, стоп-лосс, тейк-профит, временная метка (UTC → локальная timezone через TimeZone.current). Цвета — зелёный/красный — не только цветом, но и иконкой (accessibility: color blind users).
Архив сигналов с фильтрацией — пагинация через курсор (cursor-based pagination, не offset), бесконечный scroll через Paging 3 (Android) или собственный PaginatedRepository (iOS).
Процесс работы
Проектирование схемы подписок (тарифные планы, trial) → разработка WebSocket + Push delivery → реализация StoreKit 2 / Play Billing подписки → серверная валидация entitlements → UI ленты сигналов → тестирование задержки доставки → публикация.
Ориентиры по срокам
Реализация подписки + WebSocket лента сигналов + push fallback — 3–5 рабочих дней при готовом API. Если включает проектирование серверной части (WebSocket сервер, Pub/Sub интеграция со стором) — 2–3 недели.







