Add LICENSE, README, and Docs tab to Mission Control
This commit is contained in:
@@ -0,0 +1,341 @@
|
||||
'use client'
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { TradingChart } from './TradingChart'
|
||||
|
||||
type TradingTab = 'research' | 'strategies' | 'execution' | 'journal'
|
||||
|
||||
interface Trader {
|
||||
id: string
|
||||
name: string
|
||||
status: 'learning' | 'active' | 'paused'
|
||||
framesAnalyzed: number
|
||||
patterns: string[]
|
||||
entryRules: string[]
|
||||
exitRules: string[]
|
||||
indicators: string[]
|
||||
riskParams: string[]
|
||||
}
|
||||
|
||||
interface Trade {
|
||||
id: string
|
||||
trader: string
|
||||
pair: string
|
||||
direction: 'long' | 'short'
|
||||
entryPrice: number
|
||||
exitPrice?: number
|
||||
status: 'open' | 'closed' | 'cancelled'
|
||||
pnl?: number
|
||||
pnlPercent?: number
|
||||
reason: string
|
||||
setup: string
|
||||
timeframe: string
|
||||
openedAt: string
|
||||
closedAt?: string
|
||||
notes: string
|
||||
isDemo: boolean
|
||||
}
|
||||
|
||||
const defaultTraders: Trader[] = [
|
||||
{
|
||||
id: 'dopetrades',
|
||||
name: 'DopeTrades',
|
||||
status: 'learning',
|
||||
framesAnalyzed: 0,
|
||||
patterns: [],
|
||||
entryRules: [],
|
||||
exitRules: [],
|
||||
indicators: [],
|
||||
riskParams: []
|
||||
}
|
||||
]
|
||||
|
||||
export function TradingPanel() {
|
||||
const [activeTab, setActiveTab] = useState<TradingTab>('research')
|
||||
const [traders, setTraders] = useState<Trader[]>(defaultTraders)
|
||||
const [selectedTrader, setSelectedTrader] = useState<string>('dopetrades')
|
||||
const [trades, setTrades] = useState<Trade[]>([])
|
||||
const [journalFilter, setJournalFilter] = useState<'all' | 'demo' | 'real'>('all')
|
||||
|
||||
// Load data
|
||||
useEffect(() => {
|
||||
loadTraders()
|
||||
loadTrades()
|
||||
}, [])
|
||||
|
||||
const loadTraders = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/trading/traders')
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
if (data.traders?.length > 0) setTraders(data.traders)
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const loadTrades = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/trading/trades')
|
||||
if (res.ok) {
|
||||
const data = await res.json()
|
||||
if (data.trades) setTrades(data.trades)
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
const tabs = [
|
||||
{ id: 'research', label: '🔬 Deep Research', count: traders.filter(t => t.status === 'learning').length },
|
||||
{ id: 'strategies', label: '🎯 Strategies', count: traders.filter(t => t.status === 'active').length },
|
||||
{ id: 'execution', label: '⚡ Execution', count: trades.filter(t => t.status === 'open').length },
|
||||
{ id: 'journal', label: '📔 Journal', count: trades.length },
|
||||
]
|
||||
|
||||
const filteredTrades = trades.filter(t => {
|
||||
if (journalFilter === 'all') return true
|
||||
if (journalFilter === 'demo') return t.isDemo
|
||||
return !t.isDemo
|
||||
})
|
||||
|
||||
const openTrades = trades.filter(t => t.status === 'open')
|
||||
const closedDemoTrades = trades.filter(t => t.status === 'closed' && t.isDemo)
|
||||
const closedRealTrades = trades.filter(t => t.status === 'closed' && !t.isDemo)
|
||||
|
||||
const totalPnl = closedRealTrades.reduce((sum, t) => sum + (t.pnl || 0), 0)
|
||||
const winRate = closedRealTrades.length > 0
|
||||
? Math.round((closedRealTrades.filter(t => (t.pnl || 0) > 0).length / closedRealTrades.length) * 100)
|
||||
: 0
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Tab Navigation */}
|
||||
<div className="flex gap-2 flex-wrap">
|
||||
{tabs.map(tab => (
|
||||
<button
|
||||
key={tab.id}
|
||||
onClick={() => setActiveTab(tab.id as TradingTab)}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition ${
|
||||
activeTab === tab.id
|
||||
? 'bg-brand-pink text-white'
|
||||
: 'bg-white/10 text-white/70 hover:bg-white/20'
|
||||
}`}
|
||||
>
|
||||
{tab.label}
|
||||
<span className="ml-2 text-xs opacity-70">({tab.count})</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Research Tab */}
|
||||
{activeTab === 'research' && (
|
||||
<div className="border border-white/20 rounded-lg p-4 bg-white/5">
|
||||
<h3 className="text-lg font-bold mb-4">🔬 Deep Research</h3>
|
||||
<p className="text-white/60 mb-4">Learn trading strategies from experts by analyzing their content.</p>
|
||||
|
||||
<div className="space-y-3">
|
||||
{traders.map(trader => (
|
||||
<div
|
||||
key={trader.id}
|
||||
className="p-4 rounded-lg bg-white/5 border border-white/10"
|
||||
>
|
||||
<div className="flex items-center justify-between mb-2">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-2xl">👨🏫</span>
|
||||
<div>
|
||||
<p className="font-medium">{trader.name}</p>
|
||||
<p className="text-xs text-white/50">{trader.framesAnalyzed} frames analyzed</p>
|
||||
</div>
|
||||
</div>
|
||||
<span className={`px-2 py-1 rounded text-xs ${
|
||||
trader.status === 'learning' ? 'bg-yellow-500/20 text-yellow-400' :
|
||||
trader.status === 'active' ? 'bg-green-500/20 text-green-400' :
|
||||
'bg-white/10 text-white/50'
|
||||
}`}>
|
||||
{trader.status}
|
||||
</span>
|
||||
</div>
|
||||
{trader.patterns.length > 0 && (
|
||||
<div className="mt-2 flex flex-wrap gap-1">
|
||||
{trader.patterns.map(p => (
|
||||
<span key={p} className="px-2 py-0.5 bg-white/10 rounded text-xs">{p}</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<button className="mt-3 text-sm text-brand-pink hover:underline">
|
||||
View full analysis →
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<button className="mt-4 w-full py-2 border border-dashed border-white/20 rounded-lg text-white/50 hover:border-white/40 hover:text-white transition">
|
||||
+ Add new trader to research
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Strategies Tab */}
|
||||
{activeTab === 'strategies' && (
|
||||
<div className="border border-white/20 rounded-lg p-4 bg-white/5">
|
||||
<h3 className="text-lg font-bold mb-4">🎯 Trading Strategies</h3>
|
||||
<p className="text-white/60 mb-4">Select a trader style to follow for your next trade.</p>
|
||||
|
||||
<div className="flex gap-2 flex-wrap mb-4">
|
||||
{traders.filter(t => t.status === 'active').map(trader => (
|
||||
<button
|
||||
key={trader.id}
|
||||
onClick={() => setSelectedTrader(trader.id)}
|
||||
className={`px-4 py-2 rounded-lg text-sm font-medium transition ${
|
||||
selectedTrader === trader.id
|
||||
? 'bg-brand-pink text-white'
|
||||
: 'bg-white/10 text-white/70 hover:bg-white/20'
|
||||
}`}
|
||||
>
|
||||
{trader.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{traders.find(t => t.id === selectedTrader) && (
|
||||
<div className="p-4 rounded-lg bg-black/30 border border-white/10">
|
||||
<h4 className="font-medium mb-3">{traders.find(t => t.id === selectedTrader)?.name} Strategy</h4>
|
||||
|
||||
<div className="space-y-3 text-sm">
|
||||
<div>
|
||||
<p className="text-white/50 text-xs mb-1">Entry Rules</p>
|
||||
<ul className="list-disc list-inside space-y-1">
|
||||
{traders.find(t => t.id === selectedTrader)?.entryRules.map(r => (
|
||||
<li key={r}>{r}</li>
|
||||
))}
|
||||
</ul>
|
||||
{traders.find(t => t.id === selectedTrader)?.entryRules.length === 0 && (
|
||||
<p className="text-white/30 italic">No entry rules defined yet</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-white/50 text-xs mb-1">Exit Rules</p>
|
||||
<ul className="list-disc list-inside space-y-1">
|
||||
{traders.find(t => t.id === selectedTrader)?.exitRules.map(r => (
|
||||
<li key={r}>{r}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-white/50 text-xs mb-1">Indicators</p>
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{traders.find(t => t.id === selectedTrader)?.indicators.map(i => (
|
||||
<span key={i} className="px-2 py-0.5 bg-blue-500/20 text-blue-400 rounded text-xs">{i}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<p className="text-white/50 text-xs mb-1">Risk Parameters</p>
|
||||
<ul className="list-disc list-inside space-y-1">
|
||||
{traders.find(t => t.id === selectedTrader)?.riskParams.map(r => (
|
||||
<li key={r}>{r}</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button 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>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Execution Tab */}
|
||||
{activeTab === 'execution' && (
|
||||
<TradingChart />
|
||||
)}
|
||||
|
||||
{/* Journal Tab */}
|
||||
{activeTab === 'journal' && (
|
||||
<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 */}
|
||||
<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>
|
||||
<p className="text-xs text-white/50">Real P&L</p>
|
||||
</div>
|
||||
<div className="p-3 rounded bg-white/5 text-center">
|
||||
<p className="text-2xl font-bold">{winRate}%</p>
|
||||
<p className="text-xs text-white/50">Win Rate</p>
|
||||
</div>
|
||||
<div className="p-3 rounded bg-white/5 text-center">
|
||||
<p className="text-2xl font-bold">{closedRealTrades.length}</p>
|
||||
<p className="text-xs text-white/50">Real Trades</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Filter */}
|
||||
<div className="flex gap-2 mb-4">
|
||||
{(['all', 'demo', 'real'] as const).map(filter => (
|
||||
<button
|
||||
key={filter}
|
||||
onClick={() => setJournalFilter(filter)}
|
||||
className={`px-3 py-1 rounded text-xs ${
|
||||
journalFilter === filter
|
||||
? 'bg-brand-pink text-white'
|
||||
: 'bg-white/10 text-white/60'
|
||||
}`}
|
||||
>
|
||||
{filter === 'all' ? 'All' : filter === 'demo' ? 'Demo' : 'Real'}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Trades List */}
|
||||
<div className="space-y-2 max-h-[400px] overflow-y-auto">
|
||||
{filteredTrades.length === 0 ? (
|
||||
<p className="text-white/40 text-center py-4">No trades yet</p>
|
||||
) : (
|
||||
filteredTrades.map(trade => (
|
||||
<div
|
||||
key={trade.id}
|
||||
className={`p-3 rounded border ${
|
||||
trade.isDemo
|
||||
? 'bg-white/5 border-white/10'
|
||||
: trade.status === 'open'
|
||||
? 'bg-yellow-500/10 border-yellow-500/30'
|
||||
: (trade.pnl || 0) > 0
|
||||
? 'bg-green-500/10 border-green-500/30'
|
||||
: 'bg-red-500/10 border-red-500/30'
|
||||
}`}
|
||||
>
|
||||
<div className="flex justify-between items-start">
|
||||
<div>
|
||||
<span className="font-medium">{trade.pair}</span>
|
||||
<span className={`ml-2 text-xs px-1.5 py-0.5 rounded ${
|
||||
trade.direction === 'long' ? 'bg-green-500/20 text-green-400' : 'bg-red-500/20 text-red-400'
|
||||
}`}>
|
||||
{trade.direction.toUpperCase()}
|
||||
</span>
|
||||
{trade.isDemo && <span className="ml-1 text-xs text-white/40">(Demo)</span>}
|
||||
</div>
|
||||
{trade.status === 'closed' && (
|
||||
<span className={`font-bold ${(trade.pnl || 0) >= 0 ? 'text-green-400' : 'text-red-400'}`}>
|
||||
{trade.pnl >= 0 ? '+' : ''}{trade.pnl?.toFixed(2)} ({trade.pnlPercent?.toFixed(1)}%)
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-xs text-white/50 mt-1">{trade.setup}</p>
|
||||
<div className="flex gap-4 mt-2 text-xs text-white/40">
|
||||
<span>{trade.timeframe}</span>
|
||||
<span>{new Date(trade.openedAt).toLocaleDateString()}</span>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user