298 lines
8.2 KiB
TypeScript
298 lines
8.2 KiB
TypeScript
import React, { useState } from 'react';
|
|
import { View, Text, ScrollView, Switch, StyleSheet } from 'react-native';
|
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
import { useRouter } from 'expo-router';
|
|
import { LinearGradient } from 'expo-linear-gradient';
|
|
import { Colors, Fonts, FontSize, BorderRadius, Shadows, Spacing } from '@/constants/theme';
|
|
import { strategies } from '@/constants/mockData';
|
|
import { formatCurrency } from '@/utils/format';
|
|
import AnimatedCard from '@/components/AnimatedCard';
|
|
import PillBadge from '@/components/PillBadge';
|
|
import PressableScale from '@/components/PressableScale';
|
|
|
|
const RISK_COLORS: Record<string, { color: string; label: string; icon: string }> = {
|
|
aggressive: { color: Colors.accent.orange, label: 'Aggressive', icon: '\u{1F525}' },
|
|
balanced: { color: Colors.accent.green, label: 'Balanced', icon: '\u{2696}\u{FE0F}' },
|
|
safe: { color: Colors.accent.blue, label: 'Conservative', icon: '\u{1F6E1}\u{FE0F}' },
|
|
};
|
|
|
|
function StrategyCard({ strategy, index }: { strategy: typeof strategies[0]; index: number }) {
|
|
const [isActive, setIsActive] = useState(strategy.isActive);
|
|
const risk = RISK_COLORS[strategy.riskStyle];
|
|
const isPositive = strategy.netPnl >= 0;
|
|
|
|
return (
|
|
<AnimatedCard index={index} style={[Shadows.card, { borderRadius: BorderRadius.large }]}>
|
|
<LinearGradient
|
|
colors={['rgba(20,21,26,0.9)', 'rgba(14,15,18,0.95)']}
|
|
start={{ x: 0, y: 0 }}
|
|
end={{ x: 1, y: 1 }}
|
|
style={styles.card}
|
|
>
|
|
<View style={[styles.accentLine, { backgroundColor: risk.color }]} />
|
|
|
|
<View style={styles.cardHeader}>
|
|
<Text style={styles.stratName}>{strategy.name}</Text>
|
|
<Switch
|
|
value={isActive}
|
|
onValueChange={setIsActive}
|
|
trackColor={{ false: Colors.background.elevated, true: 'rgba(0,255,136,0.3)' }}
|
|
thumbColor={isActive ? Colors.accent.green : '#666'}
|
|
/>
|
|
</View>
|
|
|
|
<PillBadge
|
|
label={`${risk.icon} ${risk.label}`}
|
|
color={risk.color}
|
|
bgColor={`${risk.color}20`}
|
|
/>
|
|
|
|
<View style={styles.statsGrid}>
|
|
<StatCell label="TRADES" value={strategy.tradeCount.toString()} />
|
|
<StatCell label="WINS" value={strategy.wins.toString()} />
|
|
<StatCell label="WIN RATE" value={`${strategy.winRate}%`} color={Colors.accent.green} />
|
|
<StatCell label="NET P&L" value={formatCurrency(strategy.netPnl)} color={isPositive ? Colors.accent.green : Colors.accent.red} mono />
|
|
</View>
|
|
|
|
<View style={styles.assetPills}>
|
|
{strategy.symbols.map(s => (
|
|
<View key={s} style={styles.assetPill}>
|
|
<Text style={styles.assetText}>{s}</Text>
|
|
</View>
|
|
))}
|
|
</View>
|
|
|
|
<View style={styles.capitalRow}>
|
|
<Text style={styles.capitalLabel}>
|
|
${strategy.allocatedCapital.toLocaleString()} allocated
|
|
</Text>
|
|
</View>
|
|
|
|
<View style={styles.progressSection}>
|
|
<View style={styles.progressHeader}>
|
|
<Text style={styles.progressLabel}>Daily Target</Text>
|
|
<Text style={styles.progressValue}>
|
|
${strategy.dailyProgress} / ${strategy.dailyTarget}
|
|
</Text>
|
|
</View>
|
|
<View style={styles.progressTrack}>
|
|
<LinearGradient
|
|
colors={['#00ff88', '#00cc6a']}
|
|
start={{ x: 0, y: 0 }}
|
|
end={{ x: 1, y: 0 }}
|
|
style={[styles.progressFill, { width: `${(strategy.dailyProgress / strategy.dailyTarget) * 100}%` as any }]}
|
|
/>
|
|
</View>
|
|
</View>
|
|
</LinearGradient>
|
|
</AnimatedCard>
|
|
);
|
|
}
|
|
|
|
function StatCell({ label, value, color, mono }: { label: string; value: string; color?: string; mono?: boolean }) {
|
|
return (
|
|
<View style={styles.statCell}>
|
|
<Text style={styles.statLabel}>{label}</Text>
|
|
<Text style={[
|
|
styles.statValue,
|
|
color ? { color } : null,
|
|
mono ? { fontFamily: Fonts.mono.extraBold } : null,
|
|
]}>
|
|
{value}
|
|
</Text>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
export default function StrategiesScreen() {
|
|
const insets = useSafeAreaInsets();
|
|
const router = useRouter();
|
|
|
|
return (
|
|
<View style={[styles.container, { paddingTop: insets.top }]}>
|
|
<View style={styles.headerSection}>
|
|
<Text style={styles.sectionLabel}>STRATEGIES</Text>
|
|
<Text style={styles.pageTitle}>My Strategies</Text>
|
|
</View>
|
|
|
|
<ScrollView
|
|
style={styles.scroll}
|
|
contentContainerStyle={styles.content}
|
|
showsVerticalScrollIndicator={false}
|
|
>
|
|
{strategies.map((s, i) => (
|
|
<StrategyCard key={s.id} strategy={s} index={i} />
|
|
))}
|
|
|
|
<PressableScale
|
|
haptic="medium"
|
|
onPress={() => router.push('/marketplace')}
|
|
style={styles.ctaWrapper}
|
|
>
|
|
<LinearGradient
|
|
colors={['#00ff88', '#00cc6a']}
|
|
start={{ x: 0, y: 0 }}
|
|
end={{ x: 1, y: 1 }}
|
|
style={styles.ctaButton}
|
|
>
|
|
<Text style={styles.ctaText}>EXPLORE MARKETPLACE</Text>
|
|
</LinearGradient>
|
|
</PressableScale>
|
|
</ScrollView>
|
|
</View>
|
|
);
|
|
}
|
|
|
|
const styles = StyleSheet.create({
|
|
container: {
|
|
flex: 1,
|
|
backgroundColor: Colors.background.primary,
|
|
},
|
|
headerSection: {
|
|
padding: Spacing.screenPadding,
|
|
paddingBottom: 0,
|
|
},
|
|
sectionLabel: {
|
|
fontFamily: Fonts.inter.black,
|
|
fontSize: FontSize.micro,
|
|
color: Colors.accent.green,
|
|
letterSpacing: 4,
|
|
marginBottom: 8,
|
|
},
|
|
pageTitle: {
|
|
fontFamily: Fonts.inter.black,
|
|
fontSize: FontSize.hero,
|
|
color: Colors.text.primary,
|
|
letterSpacing: -0.5,
|
|
marginBottom: 16,
|
|
},
|
|
scroll: {
|
|
flex: 1,
|
|
},
|
|
content: {
|
|
padding: Spacing.screenPadding,
|
|
gap: 16,
|
|
paddingBottom: 120,
|
|
},
|
|
card: {
|
|
borderRadius: BorderRadius.large,
|
|
padding: Spacing.cardPaddingLarge,
|
|
borderWidth: 1,
|
|
borderColor: Colors.border.default,
|
|
overflow: 'hidden',
|
|
gap: 12,
|
|
},
|
|
accentLine: {
|
|
position: 'absolute',
|
|
top: 0,
|
|
left: 0,
|
|
right: 0,
|
|
height: 2,
|
|
opacity: 0.6,
|
|
},
|
|
cardHeader: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
},
|
|
stratName: {
|
|
fontFamily: Fonts.inter.black,
|
|
fontSize: 18,
|
|
color: Colors.text.primary,
|
|
},
|
|
statsGrid: {
|
|
flexDirection: 'row',
|
|
flexWrap: 'wrap',
|
|
gap: 10,
|
|
marginTop: 4,
|
|
},
|
|
statCell: {
|
|
width: '46%',
|
|
},
|
|
statLabel: {
|
|
fontFamily: Fonts.inter.black,
|
|
fontSize: FontSize.micro,
|
|
color: Colors.text.secondary,
|
|
letterSpacing: 0.8,
|
|
marginBottom: 3,
|
|
},
|
|
statValue: {
|
|
fontFamily: Fonts.inter.extraBold,
|
|
fontSize: FontSize.bodyLarge,
|
|
color: Colors.text.primary,
|
|
},
|
|
assetPills: {
|
|
flexDirection: 'row',
|
|
gap: 6,
|
|
flexWrap: 'wrap',
|
|
},
|
|
assetPill: {
|
|
backgroundColor: 'rgba(255,255,255,0.05)',
|
|
paddingHorizontal: 10,
|
|
paddingVertical: 4,
|
|
borderRadius: BorderRadius.xs,
|
|
},
|
|
assetText: {
|
|
fontFamily: Fonts.mono.medium,
|
|
fontSize: FontSize.micro,
|
|
color: Colors.text.secondary,
|
|
},
|
|
capitalRow: {
|
|
marginTop: 2,
|
|
},
|
|
capitalLabel: {
|
|
fontFamily: Fonts.mono.medium,
|
|
fontSize: FontSize.bodySmall,
|
|
color: Colors.text.secondary,
|
|
},
|
|
progressSection: {
|
|
gap: 6,
|
|
},
|
|
progressHeader: {
|
|
flexDirection: 'row',
|
|
justifyContent: 'space-between',
|
|
alignItems: 'center',
|
|
},
|
|
progressLabel: {
|
|
fontFamily: Fonts.inter.semiBold,
|
|
fontSize: FontSize.bodySmall,
|
|
color: Colors.text.secondary,
|
|
},
|
|
progressValue: {
|
|
fontFamily: Fonts.mono.bold,
|
|
fontSize: FontSize.bodySmall,
|
|
color: Colors.text.primary,
|
|
},
|
|
progressTrack: {
|
|
height: 4,
|
|
borderRadius: 2,
|
|
backgroundColor: Colors.background.elevated,
|
|
overflow: 'hidden',
|
|
},
|
|
progressFill: {
|
|
height: 4,
|
|
borderRadius: 2,
|
|
},
|
|
ctaWrapper: {
|
|
marginTop: 8,
|
|
borderRadius: 18,
|
|
shadowColor: 'rgba(0,255,136,0.4)',
|
|
shadowOffset: { width: 0, height: 12 },
|
|
shadowOpacity: 1,
|
|
shadowRadius: 36,
|
|
elevation: 8,
|
|
},
|
|
ctaButton: {
|
|
height: 56,
|
|
borderRadius: 18,
|
|
alignItems: 'center',
|
|
justifyContent: 'center',
|
|
},
|
|
ctaText: {
|
|
fontFamily: Fonts.inter.black,
|
|
fontSize: FontSize.bodySmall,
|
|
color: '#000',
|
|
letterSpacing: 2,
|
|
},
|
|
});
|