import React, { useState } from 'react'; import { View, Text, ScrollView, StyleSheet } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { LinearGradient } from 'expo-linear-gradient'; import { Colors, Fonts, FontSize, BorderRadius, Shadows, Spacing } from '@/constants/theme'; import { formatPrice, formatPercent, formatCurrency } from '@/utils/format'; import SegmentedControl from '@/components/SegmentedControl'; import AnimatedCard from '@/components/AnimatedCard'; import Sparkline from '@/components/Sparkline'; import PillBadge from '@/components/PillBadge'; import { useTradingData } from '@/providers/TradingDataProvider'; import { toPositionCards } from '@/lib/tradingViewModels'; type MobileOrder = { id: string; symbol: string; type: string; side: string; qty: number; price: number; status: string; action?: 'ENTRY' | 'EXIT'; source?: 'BOT' | 'MANUAL'; timestamp: number; }; function PositionCard({ pos, index }: { pos: ReturnType[number]; index: number }) { const isPositive = pos.unrealizedPnl >= 0; const sideColor = pos.side === 'BUY' ? Colors.accent.green : Colors.accent.red; return ( {pos.symbol} {pos.profileName} SL {formatPrice(pos.stopLoss)} TP {formatPrice(pos.takeProfit)} {formatCurrency(pos.unrealizedPnl)} ({formatPercent(pos.unrealizedPnlPercent)}) ); } function OrderCard({ order, index }: { order: MobileOrder; index: number }) { const actionColors = { ENTRY: { bg: 'rgba(59,130,246,0.1)', color: '#3b82f6', border: 'rgba(59,130,246,0.2)' }, EXIT: { bg: 'rgba(245,158,11,0.1)', color: '#f59e0b', border: 'rgba(245,158,11,0.2)' }, }; const statusColors: Record = { filled: { bg: 'rgba(0,255,136,0.15)', color: Colors.accent.green }, pending_new: { bg: 'rgba(250,204,21,0.15)', color: Colors.accent.amber }, cancelled: { bg: 'rgba(255,255,255,0.05)', color: Colors.text.secondary }, }; const actionKey = order.action || 'ENTRY'; const ac = actionColors[actionKey]; const sc = statusColors[order.status] || statusColors.cancelled; const source = order.source || 'BOT'; return ( {order.symbol} {order.type} {order.side} {order.qty} @ {formatPrice(order.price)} {new Date(order.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} ); } function MetricCell({ label, value }: { label: string; value: string }) { return ( {label} {value} ); } export default function PositionsScreen() { const insets = useSafeAreaInsets(); const [activeTab, setActiveTab] = useState(0); const { botState } = useTradingData(); const positions = toPositionCards(botState?.positions || [], botState?.symbols); const orders: MobileOrder[] = botState?.orders || []; return ( PORTFOLIO Positions {activeTab === 0 ? ( positions.length > 0 ? positions.map((pos, i) => ) : No open positionsActive positions will appear here once the bot enters a trade. ) : ( orders.length > 0 ? orders.map((ord, i) => ) : No pending ordersOrders placed by the bot or manually will appear here. )} ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: Colors.background.primary, }, headerSection: { padding: Spacing.screenPadding, gap: 12, }, sectionLabel: { fontFamily: Fonts.inter.black, fontSize: FontSize.micro, color: Colors.accent.green, letterSpacing: 4, }, pageTitle: { fontFamily: Fonts.inter.black, fontSize: FontSize.hero, color: Colors.text.primary, letterSpacing: -0.5, }, scroll: { flex: 1, }, listContent: { padding: Spacing.screenPadding, paddingTop: 0, gap: 16, paddingBottom: 120, }, posCard: { borderRadius: BorderRadius.large, padding: Spacing.cardPadding, borderWidth: 1, borderColor: Colors.border.default, overflow: 'hidden', gap: 10, }, accentLine: { position: 'absolute', top: 0, left: 0, right: 0, height: 2, opacity: 0.6, }, row: { flexDirection: 'row', alignItems: 'center', gap: 8, }, symbol: { fontFamily: Fonts.inter.black, fontSize: FontSize.subheading, color: Colors.text.primary, letterSpacing: -0.3, }, profileText: { fontFamily: Fonts.inter.medium, fontSize: FontSize.badge, color: Colors.text.secondary, }, metricsGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 12, marginTop: 4, }, metricCell: { width: '46%', }, metricLabel: { fontFamily: Fonts.inter.black, fontSize: FontSize.micro, color: Colors.text.secondary, letterSpacing: 0.8, marginBottom: 3, }, metricValue: { fontFamily: Fonts.mono.extraBold, fontSize: FontSize.bodyLarge, color: Colors.text.primary, }, slTpRow: { flexDirection: 'row', gap: 16, marginTop: 4, }, slTp: { fontFamily: Fonts.mono.medium, fontSize: FontSize.badge, }, pnlHero: { fontFamily: Fonts.mono.extraBold, fontSize: FontSize.heading, marginTop: 4, }, orderCard: { backgroundColor: Colors.background.card, borderRadius: BorderRadius.medium, padding: Spacing.cardPadding, borderWidth: 1, borderColor: Colors.border.default, gap: 10, }, orderDetails: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, orderType: { fontFamily: Fonts.inter.bold, fontSize: FontSize.body, color: Colors.text.primary, }, orderQty: { fontFamily: Fonts.mono.bold, fontSize: FontSize.body, color: Colors.text.primary, }, orderFooter: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', }, orderTime: { fontFamily: Fonts.mono.regular, fontSize: FontSize.micro, color: Colors.text.secondary, }, emptyState: { alignItems: 'center' as const, justifyContent: 'center' as const, paddingVertical: 60, gap: 10, }, emptyText: { fontFamily: Fonts.inter.bold, fontSize: FontSize.subheading, color: Colors.text.secondary, }, emptyHint: { fontFamily: Fonts.inter.medium, fontSize: FontSize.body, color: Colors.text.muted, textAlign: 'center' as const, maxWidth: 260, lineHeight: 20, }, });