Files
trading_bot_v4/app/roadmap/page.tsx
mindesbunister 49f19b1a8c feat: Phase 7.2 Real-Time Quality Validation COMPLETE + Hot-Reload Roadmap
PHASE 7.2 COMPLETE (Nov 27, 2025):
 4 validation checks before Smart Entry execution
 ADX degradation check (drops >2 points = cancel)
 Volume collapse check (drops >40% = cancel)
 RSI reversal detection (LONG RSI <30 or SHORT RSI >70 = cancel)
 MAGAP divergence check (wrong MA structure = cancel)
 Integrated with Smart Entry Timer (waits 2-4 min pullback)
 Detailed logging shows validation results

EXPECTED IMPACT:
- Block 5-10% of degraded signals during wait period
- Save $300-800 in prevented losses over 100 trades
- Prevent entries when ADX/volume/momentum weakens

FILES CHANGED:
- app/api/roadmap/route.ts (marked Phase 7.2 complete)
- 1MIN_DATA_ENHANCEMENTS_ROADMAP.md (updated Phase 2 → Phase 7.2 complete)

HOT-RELOAD SOLUTION (Zero Downtime Updates):
 Created /api/roadmap/reload endpoint
 POST to reload roadmap without container restart
 Roadmap page has Reload button with status messages
 No more unnecessary downtime for documentation updates!

USAGE:
- Web UI: Click Reload button on roadmap page
- API: curl -X POST http://localhost:3001/api/roadmap/reload
- Updates live instantly without rebuild/redeploy

User request: "update the roadmap and documentation. also try to find a way to update the roadmap website without having to restart/rebuild/redeploy the whole container. thats unnessary downtime"

All complete 
2025-11-27 14:00:46 +01:00

425 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 RoadmapItem {
phase: string
title: string
status: 'complete' | 'in-progress' | 'planned'
description: string
impact: string
completed?: string
items?: string[]
}
export default function RoadmapPage() {
const [roadmap, setRoadmap] = useState<RoadmapItem[]>([])
const [loading, setLoading] = useState(true)
const [reloading, setReloading] = useState(false)
const [expandedItems, setExpandedItems] = useState<Set<number>>(new Set())
const [showCompleted, setShowCompleted] = useState(false)
const [reloadMessage, setReloadMessage] = useState('')
useEffect(() => {
fetchRoadmap()
}, [])
const fetchRoadmap = async () => {
try {
const response = await fetch('/api/roadmap', {
cache: 'no-store',
headers: {
'Cache-Control': 'no-cache'
}
})
const data = await response.json()
setRoadmap(data.roadmap || [])
// Auto-expand only non-complete items by default
const autoExpand = new Set<number>()
data.roadmap?.forEach((item: RoadmapItem, index: number) => {
if (item.status !== 'complete') {
autoExpand.add(index)
}
})
setExpandedItems(autoExpand)
} catch (error) {
console.error('Failed to fetch roadmap:', error)
} finally {
setLoading(false)
setReloading(false)
}
}
const handleReload = async () => {
setReloading(true)
setReloadMessage('')
try {
const response = await fetch('/api/roadmap/reload', {
method: 'POST'
})
const data = await response.json()
if (data.success) {
setReloadMessage('✅ Roadmap reloaded successfully!')
await fetchRoadmap()
setTimeout(() => setReloadMessage(''), 3000)
} else {
setReloadMessage('❌ Failed to reload roadmap')
}
} catch (error) {
console.error('Failed to reload roadmap:', error)
setReloadMessage('❌ Reload error')
setReloading(false)
}
}
const toggleItem = (index: number) => {
const newExpanded = new Set(expandedItems)
if (newExpanded.has(index)) {
newExpanded.delete(index)
} else {
newExpanded.add(index)
}
setExpandedItems(newExpanded)
}
const getStatusColor = (status: string) => {
switch (status) {
case 'complete':
return 'bg-green-500/20 text-green-400 border-green-500/30'
case 'in-progress':
return 'bg-blue-500/20 text-blue-400 border-blue-500/30'
case 'planned':
return 'bg-gray-500/20 text-gray-400 border-gray-500/30'
default:
return 'bg-gray-500/20 text-gray-400 border-gray-500/30'
}
}
const getStatusIcon = (status: string) => {
switch (status) {
case 'complete':
return '✅'
case 'in-progress':
return '🔄'
case 'planned':
return '📋'
default:
return '❓'
}
}
const stats = {
total: roadmap.length,
complete: roadmap.filter(item => item.status === 'complete').length,
inProgress: roadmap.filter(item => item.status === 'in-progress').length,
planned: roadmap.filter(item => item.status === 'planned').length,
}
const completionRate = stats.total > 0 ? Math.round((stats.complete / stats.total) * 100) : 0
if (loading) {
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 p-8">
<div className="max-w-6xl mx-auto">
<div className="text-center text-gray-400">Loading roadmap...</div>
</div>
</div>
)
}
return (
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 p-8">
<div className="max-w-6xl mx-auto space-y-8">
{/* Header */}
<div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700 rounded-xl p-6">
<div className="flex items-center justify-between mb-4">
<h1 className="text-3xl font-bold text-white">🗺 Trading Bot Roadmap</h1>
<div className="flex items-center gap-3">
<button
onClick={handleReload}
disabled={reloading}
className={`px-4 py-2 rounded-lg transition-colors flex items-center gap-2 ${
reloading
? 'bg-gray-600 cursor-not-allowed text-gray-400'
: 'bg-blue-600 hover:bg-blue-500 text-white'
}`}
title="Hot-reload roadmap without container restart"
>
<svg
className={`w-5 h-5 ${reloading ? 'animate-spin' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15"
/>
</svg>
{reloading ? 'Reloading...' : 'Reload'}
</button>
<a
href="/"
className="px-4 py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition-colors flex items-center gap-2"
>
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 19l-7-7m0 0l7-7m-7 7h18" />
</svg>
Back to Home
</a>
</div>
</div>
{reloadMessage && (
<div className={`mb-4 p-3 rounded-lg ${
reloadMessage.includes('✅')
? 'bg-green-500/20 text-green-400 border border-green-500/30'
: 'bg-red-500/20 text-red-400 border border-red-500/30'
}`}>
{reloadMessage}
</div>
)}
<p className="text-gray-400">Track development progress and upcoming features</p>
</div>
{/* Stats Overview */}
<div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700 rounded-xl p-6">
<div className="text-gray-400 text-sm mb-1">Total Items</div>
<div className="text-3xl font-bold text-white">{stats.total}</div>
</div>
<div className="bg-green-500/10 backdrop-blur-sm border border-green-500/30 rounded-xl p-6">
<div className="text-green-400 text-sm mb-1"> Completed</div>
<div className="text-3xl font-bold text-green-400">{stats.complete}</div>
</div>
<div className="bg-blue-500/10 backdrop-blur-sm border border-blue-500/30 rounded-xl p-6">
<div className="text-blue-400 text-sm mb-1">🔄 In Progress</div>
<div className="text-3xl font-bold text-blue-400">{stats.inProgress}</div>
</div>
<div className="bg-gray-500/10 backdrop-blur-sm border border-gray-500/30 rounded-xl p-6">
<div className="text-gray-400 text-sm mb-1">📋 Planned</div>
<div className="text-3xl font-bold text-gray-400">{stats.planned}</div>
</div>
</div>
{/* Progress Bar */}
<div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700 rounded-xl p-6">
<div className="flex items-center justify-between mb-2">
<div className="text-white font-semibold">Overall Completion</div>
<div className="text-green-400 font-bold">{completionRate}%</div>
</div>
<div className="w-full bg-gray-700 rounded-full h-4 overflow-hidden">
<div
className="bg-gradient-to-r from-green-500 to-emerald-500 h-full transition-all duration-500 rounded-full"
style={{ width: `${completionRate}%` }}
/>
</div>
</div>
{/* Roadmap Items */}
<div className="space-y-8">
{/* Completed Tasks Section */}
{stats.complete > 0 && (
<div className="bg-gray-800/50 backdrop-blur-sm border border-green-500/30 rounded-xl overflow-hidden">
<button
onClick={() => setShowCompleted(!showCompleted)}
className="w-full p-6 flex items-center justify-between hover:bg-gray-700/20 transition-colors"
>
<div className="flex items-center gap-3">
<span className="text-2xl"></span>
<h2 className="text-xl font-bold text-white">Completed Tasks</h2>
<span className="px-3 py-1 rounded-full text-sm font-semibold bg-green-500/20 text-green-400">
{stats.complete} items
</span>
</div>
<svg
className={`w-6 h-6 text-gray-400 transition-transform ${showCompleted ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
{showCompleted && (
<div className="p-6 pt-0 space-y-4 border-t border-gray-700">
{roadmap
.map((item, index) => ({ item, index }))
.filter(({ item }) => item.status === 'complete')
.map(({ item, index }) => {
const isExpanded = expandedItems.has(index)
return (
<div
key={index}
className={`bg-gray-800/50 backdrop-blur-sm border rounded-xl overflow-hidden transition-all ${getStatusColor(item.status)}`}
>
<div
className="p-6 cursor-pointer hover:bg-gray-700/20 transition-colors"
onClick={() => toggleItem(index)}
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<span className="text-2xl">{getStatusIcon(item.status)}</span>
<h3 className="text-xl font-bold text-white">{item.title}</h3>
<span className="px-3 py-1 rounded-full text-xs font-semibold bg-gray-700/50 text-gray-300">
{item.phase}
</span>
</div>
<p className="text-gray-300">{item.description}</p>
{item.completed && (
<div className="text-xs text-green-400 mt-2">
Completed: {item.completed}
</div>
)}
</div>
<button
className="ml-4 text-gray-400 hover:text-white transition-colors"
onClick={(e) => {
e.stopPropagation()
toggleItem(index)
}}
>
<svg
className={`w-6 h-6 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
</div>
</div>
{isExpanded && (
<div className="px-6 pb-6 border-t border-gray-700">
<div className="pt-4">
<div className="bg-gray-900/50 rounded-lg p-3 mb-3">
<div className="text-xs text-gray-400 mb-1">Expected Impact</div>
<div className="text-sm text-white font-semibold">{item.impact}</div>
</div>
{item.items && item.items.length > 0 && (
<div>
<div className="text-sm text-gray-400 mb-2">Implementation Details:</div>
<ul className="space-y-1">
{item.items.map((subItem, subIndex) => (
<li key={subIndex} className="text-sm text-gray-300 flex items-start gap-2">
<span className="text-gray-500 mt-1"></span>
<span>{subItem}</span>
</li>
))}
</ul>
</div>
)}
</div>
</div>
)}
</div>
)
})}
</div>
)}
</div>
)}
{/* In-Progress and Planned Tasks */}
<div className="space-y-4">
{roadmap
.map((item, index) => ({ item, index }))
.filter(({ item }) => item.status !== 'complete')
.map(({ item, index }) => {
const isExpanded = expandedItems.has(index)
return (
<div
key={index}
className={`bg-gray-800/50 backdrop-blur-sm border rounded-xl overflow-hidden transition-all ${getStatusColor(item.status)}`}
>
<div
className="p-6 cursor-pointer hover:bg-gray-700/20 transition-colors"
onClick={() => toggleItem(index)}
>
<div className="flex items-start justify-between">
<div className="flex-1">
<div className="flex items-center gap-3 mb-2">
<span className="text-2xl">{getStatusIcon(item.status)}</span>
<h3 className="text-xl font-bold text-white">{item.title}</h3>
<span className="px-3 py-1 rounded-full text-xs font-semibold bg-gray-700/50 text-gray-300">
{item.phase}
</span>
</div>
<p className="text-gray-300">{item.description}</p>
{item.completed && (
<div className="text-xs text-green-400 mt-2">
Completed: {item.completed}
</div>
)}
</div>
<button
className="ml-4 text-gray-400 hover:text-white transition-colors"
onClick={(e) => {
e.stopPropagation()
toggleItem(index)
}}
>
<svg
className={`w-6 h-6 transition-transform ${isExpanded ? 'rotate-180' : ''}`}
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 9l-7 7-7-7" />
</svg>
</button>
</div>
</div>
{isExpanded && (
<div className="px-6 pb-6 border-t border-gray-700">
<div className="pt-4">
<div className="bg-gray-900/50 rounded-lg p-3 mb-3">
<div className="text-xs text-gray-400 mb-1">Expected Impact</div>
<div className="text-sm text-white font-semibold">{item.impact}</div>
</div>
{item.items && item.items.length > 0 && (
<div>
<div className="text-sm text-gray-400 mb-2">Implementation Details:</div>
<ul className="space-y-1">
{item.items.map((subItem, subIndex) => (
<li key={subIndex} className="text-sm text-gray-300 flex items-start gap-2">
<span className="text-gray-500 mt-1"></span>
<span>{subItem}</span>
</li>
))}
</ul>
</div>
)}
</div>
</div>
)}
</div>
)
})}
</div>
</div>
{/* Footer */}
<div className="bg-gray-800/50 backdrop-blur-sm border border-gray-700 rounded-xl p-6 text-center">
<p className="text-gray-400 text-sm">
Last updated: {new Date().toLocaleDateString()} Trading Bot v4
</p>
</div>
</div>
</div>
)
}