Crypto Trading Bot Mobile App Development
The trading bot runs on a server: opening positions, executing orders, managing risk. The mobile application is not the bot itself, but a complete management interface: real-time position monitoring, strategy configuration, trade history, and push notifications for critical events. Developing such an application combines several complex tasks simultaneously.
System Architecture
The mobile app does not trade directly with the exchange. The architecture always goes through the backend:
Mobile Application
↕ REST API + WebSocket
Backend API (bot server)
↕ Exchange API (Binance/Bybit)
This architecture is mandatory: exchange API keys are stored only on the server, the mobile application authenticates via JWT to its own backend. Direct requests from the phone to the exchange are excluded—keys in the app would be compromised by static APK/IPA analysis.
Authentication and Security
The user logs in to the application (email/password or OAuth), receives JWT + refresh token. The JWT is stored in iOS Keychain / Android Keystore—not in SharedPreferences or UserDefaults, otherwise it's accessible from unencrypted backups.
Biometric authentication for opening the app and confirming critical operations (for example, stopping the bot with position closure):
// iOS — biometrics via LocalAuthentication
import LocalAuthentication
func authenticateWithBiometrics() async throws {
let context = LAContext()
var error: NSError?
guard context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) else {
throw AuthError.biometricsNotAvailable
}
let success = try await context.evaluatePolicy(
.deviceOwnerAuthenticationWithBiometrics,
localizedReason: "Confirm bot shutdown"
)
guard success else { throw AuthError.biometricsFailed }
}
Real-time Data
Two types of data with different latency requirements:
Bot data (positions, status, PnL)—via WebSocket to your own backend. The backend aggregates data from the exchange and distributes it to clients. A delay of 1–3 seconds is acceptable.
Market data (asset price, order book)—can come directly from Binance WebSocket streams (btcusdt@ticker, btcusdt@depth5). Latency < 100 ms. However, direct connection to the exchange from a mobile app is only for display purposes, not for trading.
On Flutter, managing multiple WebSocket connections:
@riverpod
class TradingHubNotifier extends _$TradingHubNotifier {
WebSocketChannel? _botChannel;
WebSocketChannel? _marketChannel;
@override
TradingHubState build() {
ref.onDispose(() {
_botChannel?.sink.close();
_marketChannel?.sink.close();
});
return const TradingHubState.initial();
}
void connect(String botId, String jwtToken, String symbol) {
_connectBot(botId, jwtToken);
_connectMarket(symbol);
}
void _connectBot(String botId, String token) {
_botChannel = WebSocketChannel.connect(
Uri.parse('wss://api.mybot.com/bots/$botId/ws?token=$token'),
);
_botChannel!.stream.listen(
(data) => _handleBotEvent(jsonDecode(data as String)),
onError: (_) => Future.delayed(const Duration(seconds: 3), () => _connectBot(botId, token)),
onDone: () => Future.delayed(const Duration(seconds: 3), () => _connectBot(botId, token)),
);
}
void _connectMarket(String symbol) {
_marketChannel = WebSocketChannel.connect(
Uri.parse('wss://stream.binance.com:9443/ws/${symbol.toLowerCase()}@ticker'),
);
_marketChannel!.stream.listen(
(data) => _handleMarketTick(jsonDecode(data as String)),
);
}
}
Reconnection is not optional. Mobile networks break constantly: switching from WiFi to LTE, entering tunnels. Use exponential backoff with a maximum of 30 seconds.
Main Screen: Dashboard
Three meaningful blocks without clutter:
Bot status. Color indicator + text status (Running / Stopped / Error). Start/Stop buttons. On error—brief message ("Insufficient balance to open position").
Open positions. Card list: pair, side, size, entry price, current price, unrealized PnL in USDT and %. PnL updates on every market tick without rebuilding the list—only changing data in already-created cells. On iOS use UICollectionView with UICollectionViewDiffableDataSource and NSDiffableDataSourceSnapshot for point updates.
Session metrics. Total realized PnL for today, number of trades, win rate. Recalculated on every position_closed event.
Managing Multiple Bots
The user can run multiple bots on different pairs and strategies. BotsListScreen—a list of bots with compact cards (status, current PnL, pair). Tap—navigate to the detail screen for a specific bot.
With multiple active bots, keep WebSocket connections only for the open bot. In the background—switch to push notifications instead of WebSocket (FCM high priority data message).
Push Notifications: Types and Priorities
Three importance levels:
Critical (deliver immediately, wake the device):
- Stop-loss triggered
- Bot stopped with error
- Exchange API returned 401 (invalid key)
Informational (show at convenient time):
- Take-profit reached, position closed with profit
- Daily report
Silent updates (update data in background only, no alert):
- Statistics recalculation
On Android: critical—FCM with priority: high, ttl: 60s, notification + data. Informational—priority: normal. iOS: critical—apns-priority: 10, apns-expiration: 60.
// Android — push handling in FirebaseMessagingService
override fun onMessageReceived(message: RemoteMessage) {
val eventType = message.data["event_type"] ?: return
when (eventType) {
"stop_loss_triggered" -> {
showCriticalNotification(
title = "Stop-loss triggered",
body = "${message.data["pair"]}: -${message.data["loss_usdt"]} USDT",
channelId = CRITICAL_CHANNEL_ID
)
}
"position_closed" -> {
showInfoNotification(
title = "Position closed",
body = "${message.data["pair"]}: +${message.data["pnl_usdt"]} USDT",
channelId = INFO_CHANNEL_ID
)
}
"background_sync" -> {
// Update local cache without showing notification
syncBotState(message.data["bot_id"]!!)
}
}
}
Notification channels on Android—critical in a separate channel with IMPORTANCE_HIGH and vibration. Informational—IMPORTANCE_DEFAULT. Users can manage channels in system settings without disabling all notifications at once.
Trade History and Statistics
Separate tab with full history: paginated list (Jetpack Paging 3 / SwiftUI LazyVStack with .task trigger for the next page), filters by pair/period/result.
Summary metrics over period: Total PnL, Win Rate, Profit Factor, Max Drawdown. Equity curve chart (cumulative PnL) on fl_chart LineChart. Chart updates when switching periods, not on every event.
Strategy Configuration
Separate screen for each bot strategy. Form with numeric fields and validation:
- Take Profit / Stop Loss (range 0.1–50%)
- Order size (minimum dictated by exchange)
- Trading pairs (multiselect with search, list from exchange API)
- Cooldown between trades (in minutes)
Critical rule: changing parameters while the bot is running—with warning and confirmation. Fields that cannot be changed on the fly (strategy type, exchange)—locked when status == RUNNING.
Common Development Mistakes
Storing JWT in plain text. SharedPreferences/UserDefaults are accessible in unencrypted backups. Use Keychain/Keystore only.
Rebuilding the entire list on every market tick. With 5+ open positions and a tick every second—constant rebuilding of the whole list. Update only changed cells: DiffableDataSource on iOS, key parameter in LazyColumn on Android.
Ignoring WebSocket disconnection. Connection breaks without an onError event when switching networks. Implement ping/pong: send ping every 30 seconds, if no pong within 10 seconds—consider the connection dead and reconnect.
No offline mode. Without network, the app shows a blank screen. At minimum—cached data with a timestamp ("data from 14:30") and ProgressView when connecting.
What's Included
- Authentication (JWT + biometrics)
- Dashboard with real-time positions via WebSocket
- Multiple bot management
- Trade history with pagination and filters
- Equity curve graph
- Strategy settings form with validation
- Push notifications at three priority levels
- Offline mode with caching
Timeline
| Version | Scope | Duration |
|---|---|---|
| MVP | 1 bot, monitoring, start/stop, push | 10–14 days |
| Full | Multiple bots, strategies, statistics, settings | 20–30 days |
| From scratch + design | Same + UI/UX design | +5–7 days |
Cost is calculated individually after requirements analysis.







