Add paper trading with per-trader style tracking and stats
This commit is contained in:
@@ -34,6 +34,7 @@ interface Trade {
|
|||||||
closedAt?: string
|
closedAt?: string
|
||||||
notes: string
|
notes: string
|
||||||
isDemo: boolean
|
isDemo: boolean
|
||||||
|
traderStyle?: string // NEW: tracks which trader style was used
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultTraders: Trader[] = [
|
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() {
|
export function TradingPanel() {
|
||||||
const [activeTab, setActiveTab] = useState<TradingTab>('research')
|
const [activeTab, setActiveTab] = useState<TradingTab>('research')
|
||||||
const [traders, setTraders] = useState<Trader[]>(defaultTraders)
|
const [traders, setTraders] = useState<Trader[]>(defaultTraders)
|
||||||
const [selectedTrader, setSelectedTrader] = useState<string>('dopetrades')
|
const [selectedTrader, setSelectedTrader] = useState<string>('dopetrades')
|
||||||
const [showFullAnalysis, setShowFullAnalysis] = useState<string | null>(null)
|
const [showFullAnalysis, setShowFullAnalysis] = useState<string | null>(null)
|
||||||
|
const [showExecuteTrade, setShowExecuteTrade] = useState(false)
|
||||||
const [trades, setTrades] = useState<Trade[]>([])
|
const [trades, setTrades] = useState<Trade[]>([])
|
||||||
const [journalFilter, setJournalFilter] = useState<'all' | 'demo' | 'real'>('all')
|
const [journalFilter, setJournalFilter] = useState<'all' | 'demo' | 'real'>('all')
|
||||||
|
|
||||||
@@ -254,7 +427,9 @@ export function TradingPanel() {
|
|||||||
</div>
|
</div>
|
||||||
</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 →
|
Execute Trade in {traders.find(t => t.id === selectedTrader)?.name} Style →
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -272,7 +447,38 @@ export function TradingPanel() {
|
|||||||
<div className="border border-white/20 rounded-lg p-4 bg-white/5">
|
<div className="border border-white/20 rounded-lg p-4 bg-white/5">
|
||||||
<h3 className="text-lg font-bold mb-4">📔 Trading Journal</h3>
|
<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="grid grid-cols-3 gap-3 mb-4">
|
||||||
<div className="p-3 rounded bg-white/5 text-center">
|
<div className="p-3 rounded bg-white/5 text-center">
|
||||||
<p className="text-2xl font-bold text-green-400">${totalPnl.toFixed(2)}</p>
|
<p className="text-2xl font-bold text-green-400">${totalPnl.toFixed(2)}</p>
|
||||||
@@ -472,6 +678,21 @@ export function TradingPanel() {
|
|||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user