Розробка мобільного торговельного терміналу
Мобільний торговельний терміналу — це не зменшена копія десктопного. Інший контекст використання, інші паттерни взаємодії, інші технічні обмеження. Користувач на мобільному торгує в іншій ситуації: перевіряє позиції в транспорті, реагує на алерти, робить швидкі угоди. UX повинен це враховувати.
Вибір технології
React Native з Expo або чистий bare workflow — оптимальний вибір при наявності веб-команди. Повторне використання бізнес-логіки та деяких UI-компонентів з веб-версією.
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-сповіщення
Торговельні алерти через 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 — значно швидше для довгих списків (історія ордерів, стрічка угод)
- Регулювання оновлень WebSocket — на мобільному достатньо 5–10 оновлень на секунду
- Фоновий режим — iOS/Android обмежують WebSocket у фоні. Перемикаємось на REST polling при переході у фон







