Разработка мобильного торгового терминала
Мобильный торговый терминал — это не уменьшенная копия десктопного. Другой контекст использования, другие паттерны взаимодействия, другие технические ограничения. Пользователь на мобильном торгует в другой ситуации: проверяет позиции в транспорте, реагирует на алерты, делает быстрые сделки. UX должен это учитывать.
Технологический выбор
React Native с Expo или чистый bare workflow — оптимальный выбор при наличии web-команды. Переиспользование бизнес-логики и части UI-компонентов с web-версией.
Flutter (Dart) — отличная производительность, красивый UI из коробки, но другой язык и экосистема.
Native (Swift/Kotlin) — максимальная производительность, но два отдельных кодовых базы.
Для большинства проектов — React Native. При бюджете и требованиях к производительности — Flutter.
Архитектура React Native терминала
// Навигационная структура
const TerminalApp = () => (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Markets" component={MarketsScreen} />
<Tab.Screen name="Terminal" component={TradingScreen} />
<Tab.Screen name="Portfolio" component={PortfolioScreen} />
<Tab.Screen name="Orders" component={OrdersScreen} />
</Tab.Navigator>
</NavigationContainer>
);
// TradingScreen - основной экран терминала
const TradingScreen = ({ route }) => {
const { symbol } = route.params;
return (
<SafeAreaView style={styles.container}>
{/* Свайп между вкладками */}
<ScrollView
horizontal
pagingEnabled
showsHorizontalScrollIndicator={false}
>
<ChartView symbol={symbol} />
<OrderBookView symbol={symbol} />
<TradeHistoryView symbol={symbol} />
</ScrollView>
{/* Фиксированная панель ордера внизу */}
<OrderFormBottomSheet symbol={symbol} />
</SafeAreaView>
);
};
Графики на мобильном
TradingView Lightweight Charts работает в React Native через WebView:
import { WebView } from 'react-native-webview';
const ChartWebView = ({ symbol, candles }: ChartProps) => {
const htmlContent = `
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/lightweight-charts/dist/lightweight-charts.standalone.production.js"></script>
</head>
<body style="margin:0; background:#1a1a2e">
<div id="chart"></div>
<script>
const chart = LightweightCharts.createChart(document.getElementById('chart'), {
width: window.innerWidth,
height: window.innerHeight,
layout: { background: { color: '#1a1a2e' }, textColor: '#d1d4dc' },
});
const series = chart.addCandlestickSeries();
series.setData(${JSON.stringify(candles)});
// Получаем обновления от React Native
window.addEventListener('message', (e) => {
const msg = JSON.parse(e.data);
if (msg.type === 'UPDATE_CANDLE') series.update(msg.candle);
});
</script>
</body>
</html>
`;
return (
<WebView
source={{ html: htmlContent }}
style={{ flex: 1 }}
javaScriptEnabled
onMessage={handleChartMessage}
/>
);
};
Форма ордера: Bottom Sheet
import BottomSheet, { BottomSheetView } from '@gorhom/bottom-sheet';
const OrderFormBottomSheet = ({ symbol }: { symbol: string }) => {
const snapPoints = ['15%', '50%', '90%'];
const { balance, price } = useMarketData(symbol);
return (
<BottomSheet snapPoints={snapPoints} enablePanDownToClose={false}>
<BottomSheetView>
<OrderTypeSelector />
<PriceInput
value={orderPrice}
onChange={setOrderPrice}
currentPrice={price}
/>
<AmountSlider
min={0}
max={balance}
step={0.001}
onChange={(pct) => setAmount(balance * pct)}
/>
<View style={styles.buttons}>
<TouchableOpacity
style={[styles.button, styles.buyButton]}
onPress={() => placeOrder('BUY')}
>
<Text>Buy</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.sellButton]}
onPress={() => placeOrder('SELL')}
>
<Text>Sell</Text>
</TouchableOpacity>
</View>
</BottomSheetView>
</BottomSheet>
);
};
Push Notifications
Торговые алерты через Firebase Cloud Messaging (FCM):
// Регистрация токена
import messaging from '@react-native-firebase/messaging';
async function registerPushToken(userId: string) {
const permission = await messaging().requestPermission();
if (permission !== messaging.AuthorizationStatus.AUTHORIZED) return;
const token = await messaging().getToken();
await api.registerPushToken(userId, token, 'mobile');
}
// Обработка входящего уведомления
messaging().onMessage(async (remoteMessage) => {
const { type, symbol, price } = remoteMessage.data;
if (type === 'PRICE_ALERT') {
// Показываем локальное уведомление
await notifee.displayNotification({
title: `${symbol} Alert`,
body: `Price reached ${price}`,
android: { channelId: 'trading-alerts', importance: AndroidImportance.HIGH },
});
}
});
Биометрическая аутентификация
Touch ID / Face ID для быстрого входа и подтверждения ордеров:
import ReactNativeBiometrics from 'react-native-biometrics';
const rnBiometrics = new ReactNativeBiometrics();
async function confirmOrderWithBiometrics(order: Order): Promise<boolean> {
const { available } = await rnBiometrics.isSensorAvailable();
if (!available) return await showPinCodeFallback();
const { success } = await rnBiometrics.simplePrompt({
promptMessage: `Confirm ${order.side} ${order.quantity} ${order.symbol}`,
cancelButtonText: 'Cancel',
});
return success;
}
Производительность и оптимизация
На мобильном устройстве производительность критичнее: ограниченные CPU/GPU, батарея. Ключевые оптимизации:
- Hermes JavaScript engine — быстрее V8 для React Native на Android
- Reanimated 2 для анимаций — нативный поток, не блокирует JS
- FlashList вместо FlatList — значительно быстрее для длинных списков (история ордеров, лента сделок)
- Throttle WebSocket updates — на мобильном достаточно 5–10 обновлений в секунду
- Background mode — iOS/Android ограничивают WebSocket в фоне. Переключаемся на REST polling при уходе в background







