Files
trading_bot_v4/app/cluster/page.tsx
mindesbunister 4c36fa2bc3 docs: Major documentation reorganization + ENV variable reference
**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
2025-12-04 08:29:59 +01:00

456 lines
18 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'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>
)
}