**Documentation Structure:** - Created docs/ subdirectory organization (analysis/, architecture/, bugs/, cluster/, deployments/, roadmaps/, setup/, archived/) - Moved 68 root markdown files to appropriate categories - Root directory now clean (only README.md remains) - Total: 83 markdown files now organized by purpose **New Content:** - Added comprehensive Environment Variable Reference to copilot-instructions.md - 100+ ENV variables documented with types, defaults, purpose, notes - Organized by category: Required (Drift/RPC/Pyth), Trading Config (quality/ leverage/sizing), ATR System, Runner System, Risk Limits, Notifications, etc. - Includes usage examples (correct vs wrong patterns) **File Distribution:** - docs/analysis/ - Performance analyses, blocked signals, profit projections - docs/architecture/ - Adaptive leverage, ATR trailing, indicator tracking - docs/bugs/ - CRITICAL_*.md, FIXES_*.md bug reports (7 files) - docs/cluster/ - EPYC setup, distributed computing docs (3 files) - docs/deployments/ - *_COMPLETE.md, DEPLOYMENT_*.md status (12 files) - docs/roadmaps/ - All *ROADMAP*.md strategic planning files (7 files) - docs/setup/ - TradingView guides, signal quality, n8n setup (8 files) - docs/archived/2025_pre_nov/ - Obsolete verification checklist (1 file) **Key Improvements:** - ENV variable reference: Single source of truth for all configuration - Common Pitfalls #68-71: Already complete, verified during audit - Better findability: Category-based navigation vs 68 files in root - Preserves history: All files git mv (rename), not copy/delete - Zero broken functionality: Only documentation moved, no code changes **Verification:** - 83 markdown files now in docs/ subdirectories - Root directory cleaned: 68 files → 0 files (except README.md) - Git history preserved for all moved files - Container running: trading-bot-v4 (no restart needed) **Next Steps:** - Create README.md files in each docs subdirectory - Add navigation index - Update main README.md with new structure - Consolidate duplicate deployment docs - Archive truly obsolete files (old SQL backups) See: docs/analysis/CLEANUP_PLAN.md for complete reorganization strategy
456 lines
18 KiB
TypeScript
456 lines
18 KiB
TypeScript
'use client'
|
||
|
||
import { useEffect, useState } from 'react'
|
||
|
||
interface ClusterStatus {
|
||
cluster: {
|
||
totalCores: number
|
||
activeCores: number
|
||
cpuUsage: number
|
||
activeWorkers: number
|
||
totalWorkers: number
|
||
workerProcesses: number
|
||
status: string
|
||
}
|
||
workers: Array<{
|
||
name: string
|
||
host: string
|
||
cpuUsage: number
|
||
loadAverage: string
|
||
activeProcesses: number
|
||
status: string
|
||
}>
|
||
exploration: {
|
||
totalCombinations: number
|
||
testedCombinations: number
|
||
progress: number
|
||
chunks: {
|
||
total: number
|
||
completed: number
|
||
running: number
|
||
pending: number
|
||
}
|
||
}
|
||
topStrategies: Array<{
|
||
rank: number
|
||
pnl_per_1k: number
|
||
win_rate: number
|
||
trades: number
|
||
profit_factor: number
|
||
max_drawdown: number
|
||
params: {
|
||
flip_threshold: number
|
||
ma_gap: number
|
||
adx_min: number
|
||
long_pos_max: number
|
||
short_pos_min: number
|
||
}
|
||
}>
|
||
recommendation: string
|
||
lastUpdate: string
|
||
}
|
||
|
||
export default function ClusterPage() {
|
||
const [status, setStatus] = useState<ClusterStatus | null>(null)
|
||
const [loading, setLoading] = useState(true)
|
||
const [error, setError] = useState<string | null>(null)
|
||
const [controlLoading, setControlLoading] = useState(false)
|
||
const [controlMessage, setControlMessage] = useState<string | null>(null)
|
||
const [coordinatorLog, setCoordinatorLog] = useState<string>('')
|
||
const [logLoading, setLogLoading] = useState(false)
|
||
|
||
const fetchStatus = async () => {
|
||
try {
|
||
const res = await fetch('/api/cluster/status')
|
||
if (!res.ok) throw new Error('Failed to fetch')
|
||
const data = await res.json()
|
||
setStatus(data)
|
||
setError(null)
|
||
} catch (err: any) {
|
||
setError(err.message)
|
||
} finally {
|
||
setLoading(false)
|
||
}
|
||
}
|
||
|
||
const fetchLog = async () => {
|
||
try {
|
||
setLogLoading(true)
|
||
const response = await fetch('/api/cluster/logs')
|
||
const data = await response.json()
|
||
if (data.success) {
|
||
setCoordinatorLog(data.log)
|
||
}
|
||
} catch (error) {
|
||
console.error('Failed to fetch coordinator log:', error)
|
||
} finally {
|
||
setLogLoading(false)
|
||
}
|
||
}
|
||
|
||
const handleControl = async (action: 'start' | 'stop') => {
|
||
setControlLoading(true)
|
||
setControlMessage(null)
|
||
try {
|
||
const res = await fetch('/api/cluster/control', {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ action })
|
||
})
|
||
const data = await res.json()
|
||
setControlMessage(data.message || (data.success ? `Cluster ${action}ed` : 'Operation failed'))
|
||
|
||
// Refresh status after control action
|
||
setTimeout(() => fetchStatus(), 2000)
|
||
} catch (err: any) {
|
||
setControlMessage(`Error: ${err.message}`)
|
||
} finally {
|
||
setControlLoading(false)
|
||
}
|
||
}
|
||
|
||
useEffect(() => {
|
||
fetchStatus()
|
||
fetchLog()
|
||
const statusInterval = setInterval(fetchStatus, 30000) // Refresh status every 30s
|
||
const logInterval = setInterval(fetchLog, 3000) // Refresh log every 3s
|
||
return () => {
|
||
clearInterval(statusInterval)
|
||
clearInterval(logInterval)
|
||
}
|
||
}, [])
|
||
|
||
if (loading) {
|
||
return (
|
||
<div className="min-h-screen bg-gray-900 text-white p-8">
|
||
<div className="max-w-7xl mx-auto">
|
||
<h1 className="text-3xl font-bold mb-8">🖥️ EPYC Cluster Status</h1>
|
||
<div className="text-gray-400">Loading cluster status...</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
if (error) {
|
||
return (
|
||
<div className="min-h-screen bg-gray-900 text-white p-8">
|
||
<div className="max-w-7xl mx-auto">
|
||
<h1 className="text-3xl font-bold mb-8">🖥️ EPYC Cluster Status</h1>
|
||
<div className="bg-red-900/20 border border-red-500 rounded p-4">
|
||
<p className="text-red-400">Error: {error}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|
||
|
||
if (!status) return null
|
||
|
||
const getStatusColor = (statusStr: string) => {
|
||
if (statusStr === 'active') return 'text-green-400'
|
||
if (statusStr === 'idle') return 'text-yellow-400'
|
||
return 'text-red-400'
|
||
}
|
||
|
||
const getStatusBg = (statusStr: string) => {
|
||
if (statusStr === 'active') return 'bg-green-900/20 border-green-500'
|
||
if (statusStr === 'idle') return 'bg-yellow-900/20 border-yellow-500'
|
||
return 'bg-red-900/20 border-red-500'
|
||
}
|
||
|
||
return (
|
||
<div className="min-h-screen bg-gray-900 text-white p-8">
|
||
<div className="max-w-7xl mx-auto">
|
||
{/* Back Button */}
|
||
<a
|
||
href="/"
|
||
className="inline-flex items-center text-blue-400 hover:text-blue-300 mb-6 transition-colors group"
|
||
>
|
||
<svg className="w-5 h-5 mr-2 group-hover:-translate-x-1 transition-transform" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 19l-7-7 7-7" />
|
||
</svg>
|
||
<span className="font-medium">Back to Dashboard</span>
|
||
</a>
|
||
|
||
<div className="flex justify-between items-center mb-8">
|
||
<h1 className="text-3xl font-bold">🖥️ EPYC Cluster Status</h1>
|
||
<div className="flex gap-3">
|
||
{status.cluster.status === 'idle' ? (
|
||
<button
|
||
onClick={() => handleControl('start')}
|
||
disabled={controlLoading}
|
||
className="px-6 py-2 bg-green-600 hover:bg-green-700 disabled:bg-gray-600 rounded text-sm font-semibold transition-colors"
|
||
>
|
||
{controlLoading ? '⏳ Starting...' : '▶️ Start Cluster'}
|
||
</button>
|
||
) : (
|
||
<button
|
||
onClick={() => handleControl('stop')}
|
||
disabled={controlLoading}
|
||
className="px-6 py-2 bg-red-600 hover:bg-red-700 disabled:bg-gray-600 rounded text-sm font-semibold transition-colors"
|
||
>
|
||
{controlLoading ? '⏳ Stopping...' : '⏹️ Stop Cluster'}
|
||
</button>
|
||
)}
|
||
<button
|
||
onClick={fetchStatus}
|
||
className="px-4 py-2 bg-blue-600 hover:bg-blue-700 rounded text-sm"
|
||
>
|
||
🔄 Refresh
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Control Message */}
|
||
{controlMessage && (
|
||
<div className="mb-4 p-4 bg-blue-900/20 border border-blue-500 rounded">
|
||
<p className="text-blue-300">{controlMessage}</p>
|
||
</div>
|
||
)}
|
||
|
||
{/* Cluster Overview */}
|
||
<div className={`border rounded-lg p-6 mb-6 ${getStatusBg(status.cluster.status)}`}>
|
||
<h2 className="text-xl font-semibold mb-4">Cluster Overview</h2>
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||
<div>
|
||
<div className="text-gray-400 text-sm">Status</div>
|
||
<div className={`text-2xl font-bold ${getStatusColor(status.cluster.status)}`}>
|
||
{status.cluster.status.toUpperCase()}
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<div className="text-gray-400 text-sm">CPU Usage</div>
|
||
<div className="text-2xl font-bold">{status.cluster.cpuUsage.toFixed(1)}%</div>
|
||
</div>
|
||
<div>
|
||
<div className="text-gray-400 text-sm">Active Cores</div>
|
||
<div className="text-2xl font-bold">{status.cluster.activeCores} / {status.cluster.totalCores}</div>
|
||
</div>
|
||
<div>
|
||
<div className="text-gray-400 text-sm">Workers</div>
|
||
<div className="text-2xl font-bold">{status.cluster.activeWorkers} / {status.cluster.totalWorkers}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Coordinator Log */}
|
||
<div className="bg-gray-900 rounded-lg p-6 border border-gray-800">
|
||
<div className="flex items-center justify-between mb-4">
|
||
<h3 className="text-lg font-semibold">Coordinator Log</h3>
|
||
<button
|
||
onClick={fetchLog}
|
||
disabled={logLoading}
|
||
className="px-3 py-1 bg-gray-800 hover:bg-gray-700 rounded text-sm disabled:opacity-50"
|
||
>
|
||
{logLoading ? '⏳ Loading...' : '🔄 Refresh'}
|
||
</button>
|
||
</div>
|
||
<div className="bg-black rounded-lg p-4 overflow-auto max-h-96">
|
||
<pre className="text-xs text-green-400 font-mono whitespace-pre-wrap">
|
||
{coordinatorLog || 'No log output available'}
|
||
</pre>
|
||
</div>
|
||
<div className="mt-2 text-xs text-gray-500">
|
||
Updates automatically every 3 seconds
|
||
</div>
|
||
</div>
|
||
|
||
{/* Worker Details */}
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
|
||
{status.workers.map((worker) => (
|
||
<div key={worker.name} className={`border rounded-lg p-4 ${getStatusBg(worker.status)}`}>
|
||
<h3 className="font-semibold mb-2">{worker.name}</h3>
|
||
<div className="text-sm text-gray-400 mb-3">{worker.host}</div>
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between">
|
||
<span className="text-gray-400">CPU:</span>
|
||
<span className="font-mono">{worker.cpuUsage.toFixed(1)}%</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span className="text-gray-400">Load:</span>
|
||
<span className="font-mono">{worker.loadAverage}</span>
|
||
</div>
|
||
<div className="flex justify-between">
|
||
<span className="text-gray-400">Processes:</span>
|
||
<span className="font-mono">{worker.activeProcesses}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
{/* Exploration Progress */}
|
||
<div className="border border-blue-500 bg-blue-900/20 rounded-lg p-6 mb-6">
|
||
<h2 className="text-xl font-semibold mb-4">📊 Parameter Exploration</h2>
|
||
|
||
{/* Main Stats Grid */}
|
||
<div className="grid grid-cols-1 md:grid-cols-4 gap-6 mb-6">
|
||
<div className="bg-gray-800/50 rounded-lg p-4 border border-gray-700">
|
||
<div className="text-gray-400 text-sm mb-1">Total Combinations</div>
|
||
<div className="text-3xl font-bold text-blue-400">
|
||
{status.exploration.totalCombinations.toLocaleString()}
|
||
</div>
|
||
<div className="text-xs text-gray-500 mt-1">v9 comprehensive sweep</div>
|
||
</div>
|
||
|
||
<div className="bg-gray-800/50 rounded-lg p-4 border border-gray-700">
|
||
<div className="text-gray-400 text-sm mb-1">Tested</div>
|
||
<div className="text-3xl font-bold text-purple-400">
|
||
{status.exploration.testedCombinations.toLocaleString()}
|
||
</div>
|
||
<div className="text-xs text-gray-500 mt-1">strategies evaluated</div>
|
||
</div>
|
||
|
||
<div className="bg-gray-800/50 rounded-lg p-4 border border-gray-700">
|
||
<div className="text-gray-400 text-sm mb-1">Chunks Progress</div>
|
||
<div className="text-3xl font-bold text-green-400">
|
||
{status.exploration.chunks.completed}
|
||
</div>
|
||
<div className="text-xs text-gray-500 mt-1">
|
||
of {status.exploration.chunks.total.toLocaleString()} completed
|
||
</div>
|
||
</div>
|
||
|
||
<div className="bg-gray-800/50 rounded-lg p-4 border border-gray-700">
|
||
<div className="text-gray-400 text-sm mb-1">Status</div>
|
||
<div className="text-2xl font-bold">
|
||
{status.exploration.chunks.running > 0 ? (
|
||
<span className="text-yellow-400">⚡ Processing</span>
|
||
) : status.exploration.chunks.pending > 0 ? (
|
||
<span className="text-blue-400">⏳ Pending</span>
|
||
) : status.exploration.chunks.completed === status.exploration.chunks.total && status.exploration.chunks.total > 0 ? (
|
||
<span className="text-green-400">✅ Complete</span>
|
||
) : (
|
||
<span className="text-gray-400">⏸️ Idle</span>
|
||
)}
|
||
</div>
|
||
<div className="text-xs text-gray-500 mt-1">
|
||
{status.exploration.chunks.running > 0 && `${status.exploration.chunks.running} chunks running`}
|
||
{status.exploration.chunks.running === 0 && status.exploration.chunks.pending > 0 && `${status.exploration.chunks.pending.toLocaleString()} chunks pending`}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Chunk Status Breakdown */}
|
||
<div className="grid grid-cols-3 gap-4 mb-6">
|
||
<div className="bg-green-900/30 border border-green-500/30 rounded-lg p-4 text-center">
|
||
<div className="text-green-400 text-2xl font-bold">
|
||
{status.exploration.chunks.completed}
|
||
</div>
|
||
<div className="text-green-300 text-sm mt-1">✓ Completed</div>
|
||
</div>
|
||
<div className="bg-yellow-900/30 border border-yellow-500/30 rounded-lg p-4 text-center">
|
||
<div className="text-yellow-400 text-2xl font-bold">
|
||
{status.exploration.chunks.running}
|
||
</div>
|
||
<div className="text-yellow-300 text-sm mt-1">⏳ Running</div>
|
||
</div>
|
||
<div className="bg-gray-800 border border-gray-600 rounded-lg p-4 text-center">
|
||
<div className="text-gray-400 text-2xl font-bold">
|
||
{status.exploration.chunks.pending.toLocaleString()}
|
||
</div>
|
||
<div className="text-gray-500 text-sm mt-1">⏸️ Pending</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Progress Bar */}
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between text-sm">
|
||
<span className="text-gray-400">Overall Progress</span>
|
||
<span className="text-gray-300 font-semibold">
|
||
{status.exploration.progress > 0 ? status.exploration.progress.toFixed(2) : '0.00'}%
|
||
</span>
|
||
</div>
|
||
<div className="w-full bg-gray-700 rounded-full h-5 overflow-hidden">
|
||
<div
|
||
className="bg-gradient-to-r from-blue-500 to-purple-500 h-5 rounded-full transition-all duration-500 flex items-center justify-end pr-2"
|
||
style={{ width: `${Math.max(status.exploration.progress, 0.5)}%` }}
|
||
>
|
||
{status.exploration.progress > 2 && (
|
||
<span className="text-xs text-white font-semibold">
|
||
{status.exploration.progress.toFixed(1)}%
|
||
</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
<div className="text-xs text-gray-500 text-right">
|
||
{status.exploration.chunks.completed.toLocaleString()} / {status.exploration.chunks.total.toLocaleString()} chunks processed
|
||
{status.exploration.testedCombinations > 0 && (
|
||
<span className="ml-3">• {status.exploration.testedCombinations.toLocaleString()} strategies evaluated</span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
{/* Recommendation */}
|
||
{status.recommendation && (
|
||
<div className="border border-purple-500 bg-purple-900/20 rounded-lg p-6 mb-6">
|
||
<h2 className="text-xl font-semibold mb-4">🎯 AI Recommendation</h2>
|
||
<div className="whitespace-pre-line text-gray-300 leading-relaxed">
|
||
{status.recommendation}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{/* Top Strategies */}
|
||
{status.topStrategies.length > 0 && (
|
||
<div className="border border-gray-700 rounded-lg p-6">
|
||
<h2 className="text-xl font-semibold mb-4">🏆 Top Strategies</h2>
|
||
<div className="space-y-3">
|
||
{status.topStrategies.map((strategy) => (
|
||
<div key={strategy.rank} className="bg-gray-800 rounded p-4">
|
||
<div className="flex justify-between items-start mb-2">
|
||
<div className="text-lg font-semibold">#{strategy.rank}</div>
|
||
<div className="text-right">
|
||
<div className="text-2xl font-bold text-green-400">
|
||
${strategy.pnl_per_1k.toFixed(2)}
|
||
</div>
|
||
<div className="text-sm text-gray-400">per $1k</div>
|
||
</div>
|
||
</div>
|
||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3 text-sm">
|
||
<div>
|
||
<span className="text-gray-400">Win Rate:</span>{' '}
|
||
<span className="font-semibold">{(strategy.win_rate * 100).toFixed(1)}%</span>
|
||
</div>
|
||
<div>
|
||
<span className="text-gray-400">Trades:</span>{' '}
|
||
<span className="font-semibold">{strategy.trades}</span>
|
||
</div>
|
||
<div>
|
||
<span className="text-gray-400">PF:</span>{' '}
|
||
<span className="font-semibold">{strategy.profit_factor.toFixed(2)}x</span>
|
||
</div>
|
||
<div>
|
||
<span className="text-gray-400">Max DD:</span>{' '}
|
||
<span className="font-semibold text-red-400">
|
||
${Math.abs(strategy.max_drawdown).toFixed(0)}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<details className="mt-3">
|
||
<summary className="cursor-pointer text-blue-400 text-sm hover:text-blue-300">
|
||
Show Parameters
|
||
</summary>
|
||
<div className="mt-2 grid grid-cols-2 md:grid-cols-3 gap-2 text-xs font-mono bg-gray-900 p-3 rounded">
|
||
<div>flip: {strategy.params.flip_threshold}</div>
|
||
<div>ma_gap: {strategy.params.ma_gap}</div>
|
||
<div>adx: {strategy.params.adx_min}</div>
|
||
<div>long_pos: {strategy.params.long_pos_max}</div>
|
||
<div>short_pos: {strategy.params.short_pos_min}</div>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
))}
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
<div className="mt-6 text-center text-sm text-gray-500">
|
||
Last updated: {new Date(status.lastUpdate).toLocaleString()}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
)
|
||
}
|