Add paper trading with per-trader style tracking and stats
This commit is contained in:
@@ -34,6 +34,7 @@ interface Trade {
|
||||
closedAt?: string
|
||||
notes: string
|
||||
isDemo: boolean
|
||||
traderStyle?: string // NEW: tracks which trader style was used
|
||||
}
|
||||
|
||||
const defaultTraders: Trader[] = [
|
||||
@@ -61,11 +62,183 @@ const defaultTraders: Trader[] = [
|
||||
}
|
||||
]
|
||||
|
||||
// Execute Trade Modal Component
|
||||
function ExecuteTradeModal({
|
||||
traders,
|
||||
selectedTrader,
|
||||
onClose,
|
||||
onTradeExecuted
|
||||
}: {
|
||||
traders: Trader[]
|
||||
selectedTrader: string
|
||||
onClose: () => void
|
||||
onTradeExecuted: (trade: Trade) => void
|
||||
}) {
|
||||
const [form, setForm] = useState({
|
||||
pair: 'BTC/USD',
|
||||
direction: 'long' as 'long' | 'short',
|
||||
isDemo: true,
|
||||
entryPrice: '',
|
||||
timeframe: '4H',
|
||||
setup: ''
|
||||
})
|
||||
|
||||
const submitTrade = () => {
|
||||
const trader = traders.find(t => t.id === selectedTrader)
|
||||
const trade: Trade = {
|
||||
id: Date.now().toString(),
|
||||
trader: trader?.name || 'Unknown',
|
||||
pair: form.pair,
|
||||
direction: form.direction,
|
||||
entryPrice: parseFloat(form.entryPrice) || 0,
|
||||
status: 'open',
|
||||
isDemo: form.isDemo,
|
||||
openedAt: new Date().toISOString(),
|
||||
setup: form.setup || `${trader?.name} setup`,
|
||||
timeframe: form.timeframe,
|
||||
reason: '',
|
||||
notes: '',
|
||||
traderStyle: selectedTrader
|
||||
}
|
||||
|
||||
// Save to API
|
||||
fetch('/api/trading/trades', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(trade)
|
||||
}).then(() => {
|
||||
onTradeExecuted(trade)
|
||||
})
|
||||
}
|
||||
|
||||
const trader = traders.find(t => t.id === selectedTrader)
|
||||
|
||||
return (
|
||||
<div className="bg-[#1a1625] border border-white/20 rounded-xl max-w-lg w-full">
|
||||
<div className="p-4 border-b border-white/10 flex justify-between items-center">
|
||||
<h2 className="text-xl font-bold text-white">Execute Trade</h2>
|
||||
<button onClick={onClose} className="text-white/50 hover:text-white text-2xl">×</button>
|
||||
</div>
|
||||
|
||||
<div className="p-4 space-y-4">
|
||||
{/* Trader Style Badge */}
|
||||
<div className="p-3 bg-brand-pink/20 border border-brand-pink/30 rounded-lg">
|
||||
<p className="text-xs text-white/50">Trading Style</p>
|
||||
<p className="font-bold text-white">{trader?.name}</p>
|
||||
</div>
|
||||
|
||||
{/* Pair */}
|
||||
<div>
|
||||
<label className="block text-sm text-white/70 mb-1">Trading Pair</label>
|
||||
<select
|
||||
value={form.pair}
|
||||
onChange={e => setForm({...form, pair: e.target.value})}
|
||||
className="w-full bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white"
|
||||
>
|
||||
<option value="BTC/USD">BTC/USD</option>
|
||||
<option value="ETH/USD">ETH/USD</option>
|
||||
<option value="SOL/USD">SOL/USD</option>
|
||||
<option value="EUR/USD">EUR/USD</option>
|
||||
<option value="GBP/USD">GBP/USD</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Direction */}
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setForm({...form, direction: 'long'})}
|
||||
className={`flex-1 py-2 rounded-lg font-medium ${
|
||||
form.direction === 'long' ? 'bg-green-500 text-white' : 'bg-white/10 text-white/70'
|
||||
}`}
|
||||
>
|
||||
📈 LONG
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setForm({...form, direction: 'short'})}
|
||||
className={`flex-1 py-2 rounded-lg font-medium ${
|
||||
form.direction === 'short' ? 'bg-red-500 text-white' : 'bg-white/10 text-white/70'
|
||||
}`}
|
||||
>
|
||||
📉 SHORT
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Entry Price */}
|
||||
<div>
|
||||
<label className="block text-sm text-white/70 mb-1">Entry Price</label>
|
||||
<input
|
||||
type="number"
|
||||
value={form.entryPrice}
|
||||
onChange={e => setForm({...form, entryPrice: e.target.value})}
|
||||
placeholder="0.00"
|
||||
className="w-full bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Timeframe */}
|
||||
<div>
|
||||
<label className="block text-sm text-white/70 mb-1">Timeframe</label>
|
||||
<select
|
||||
value={form.timeframe}
|
||||
onChange={e => setForm({...form, timeframe: e.target.value})}
|
||||
className="w-full bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white"
|
||||
>
|
||||
<option value="15m">15 min</option>
|
||||
<option value="1H">1 Hour</option>
|
||||
<option value="4H">4 Hour</option>
|
||||
<option value="Daily">Daily</option>
|
||||
<option value="Weekly">Weekly</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
{/* Demo/Real */}
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setForm({...form, isDemo: true})}
|
||||
className={`flex-1 py-2 rounded-lg font-medium ${
|
||||
form.isDemo ? 'bg-blue-500 text-white' : 'bg-white/10 text-white/70'
|
||||
}`}
|
||||
>
|
||||
🎯 Demo
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setForm({...form, isDemo: false})}
|
||||
className={`flex-1 py-2 rounded-lg font-medium ${
|
||||
!form.isDemo ? 'bg-yellow-500 text-white' : 'bg-white/10 text-white/70'
|
||||
}`}
|
||||
>
|
||||
💰 Real
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Setup Note */}
|
||||
<div>
|
||||
<label className="block text-sm text-white/70 mb-1">Setup/Notes</label>
|
||||
<input
|
||||
value={form.setup}
|
||||
onChange={e => setForm({...form, setup: e.target.value})}
|
||||
placeholder="What setup is this?"
|
||||
className="w-full bg-white/10 border border-white/20 rounded-lg px-3 py-2 text-white"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={submitTrade}
|
||||
className="w-full py-3 bg-brand-pink text-white rounded-lg font-bold hover:bg-[#ff7bc0] transition"
|
||||
>
|
||||
🚀 Execute Trade
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function TradingPanel() {
|
||||
const [activeTab, setActiveTab] = useState<TradingTab>('research')
|
||||
const [traders, setTraders] = useState<Trader[]>(defaultTraders)
|
||||
const [selectedTrader, setSelectedTrader] = useState<string>('dopetrades')
|
||||
const [showFullAnalysis, setShowFullAnalysis] = useState<string | null>(null)
|
||||
const [showExecuteTrade, setShowExecuteTrade] = useState(false)
|
||||
const [trades, setTrades] = useState<Trade[]>([])
|
||||
const [journalFilter, setJournalFilter] = useState<'all' | 'demo' | 'real'>('all')
|
||||
|
||||
@@ -254,7 +427,9 @@ export function TradingPanel() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button className="mt-4 w-full py-2 bg-brand-pink rounded-lg font-medium hover:bg-[#ff7bc0] transition">
|
||||
<button
|
||||
onClick={() => setShowExecuteTrade(true)}
|
||||
className="mt-4 w-full py-2 bg-brand-pink rounded-lg font-medium hover:bg-[#ff7bc0] transition">
|
||||
Execute Trade in {traders.find(t => t.id === selectedTrader)?.name} Style →
|
||||
</button>
|
||||
</div>
|
||||
@@ -272,7 +447,38 @@ export function TradingPanel() {
|
||||
<div className="border border-white/20 rounded-lg p-4 bg-white/5">
|
||||
<h3 className="text-lg font-bold mb-4">📔 Trading Journal</h3>
|
||||
|
||||
{/* Stats */}
|
||||
{/* Per-Trader Stats */}
|
||||
<div className="mb-4">
|
||||
<h4 className="text-sm font-medium text-white/70 mb-2">📊 Performance by Trader Style</h4>
|
||||
<div className="grid grid-cols-1 gap-2">
|
||||
{traders.filter(t => t.status !== 'paused').map(trader => {
|
||||
const traderTrades = trades.filter(t => t.traderStyle === trader.id)
|
||||
const closedTrades = traderTrades.filter(t => t.status === 'closed' && !t.isDemo)
|
||||
const wins = closedTrades.filter(t => (t.pnl || 0) > 0).length
|
||||
const totalPnl = closedTrades.reduce((sum, t) => sum + (t.pnl || 0), 0)
|
||||
const winRate = closedTrades.length > 0 ? Math.round((wins / closedTrades.length) * 100) : 0
|
||||
|
||||
return (
|
||||
<div key={trader.id} className="p-3 bg-white/5 rounded-lg border border-white/10">
|
||||
<div className="flex justify-between items-center">
|
||||
<div>
|
||||
<span className="font-medium text-white">{trader.name}</span>
|
||||
<span className="ml-2 text-xs text-white/50">({traderTrades.length} trades)</span>
|
||||
</div>
|
||||
<div className="text-right">
|
||||
<span className={`font-bold ${totalPnl >= 0 ? 'text-green-400' : 'text-red-400'}`}>
|
||||
${totalPnl.toFixed(2)}
|
||||
</span>
|
||||
<span className="ml-2 text-xs text-white/50">| {winRate}% WR</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Overall Stats */}
|
||||
<div className="grid grid-cols-3 gap-3 mb-4">
|
||||
<div className="p-3 rounded bg-white/5 text-center">
|
||||
<p className="text-2xl font-bold text-green-400">${totalPnl.toFixed(2)}</p>
|
||||
@@ -472,6 +678,21 @@ export function TradingPanel() {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Execute Trade Modal */}
|
||||
{showExecuteTrade && (
|
||||
<div className="fixed inset-0 bg-black/80 backdrop-blur-sm z-50 flex items-center justify-center p-4">
|
||||
<ExecuteTradeModal
|
||||
traders={traders}
|
||||
selectedTrader={selectedTrader}
|
||||
onClose={() => setShowExecuteTrade(false)}
|
||||
onTradeExecuted={(trade) => {
|
||||
setTrades([trade, ...trades])
|
||||
setShowExecuteTrade(false)
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user