mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-07-10 22:36:58 +08:00
Dev backtest (#1134)
This commit is contained in:
1273
web/src/components/BacktestPage.tsx
Normal file
1273
web/src/components/BacktestPage.tsx
Normal file
File diff suppressed because it is too large
Load Diff
177
web/src/components/DecisionCard.tsx
Normal file
177
web/src/components/DecisionCard.tsx
Normal file
@@ -0,0 +1,177 @@
|
||||
import { useState } from 'react'
|
||||
import type { DecisionRecord } from '../types'
|
||||
import { t, type Language } from '../i18n/translations'
|
||||
|
||||
interface DecisionCardProps {
|
||||
decision: DecisionRecord
|
||||
language: Language
|
||||
}
|
||||
|
||||
export function DecisionCard({ decision, language }: DecisionCardProps) {
|
||||
const [showInputPrompt, setShowInputPrompt] = useState(false)
|
||||
const [showCoT, setShowCoT] = useState(false)
|
||||
|
||||
return (
|
||||
<div
|
||||
className="rounded p-5 transition-all duration-300 hover:translate-y-[-2px]"
|
||||
style={{
|
||||
border: '1px solid #2B3139',
|
||||
background: '#1E2329',
|
||||
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.3)',
|
||||
}}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div>
|
||||
<div className="font-semibold" style={{ color: '#EAECEF' }}>
|
||||
{t('cycle', language)} #{decision.cycle_number}
|
||||
</div>
|
||||
<div className="text-xs" style={{ color: '#848E9C' }}>
|
||||
{new Date(decision.timestamp).toLocaleString()}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="px-3 py-1 rounded text-xs font-bold"
|
||||
style={
|
||||
decision.success
|
||||
? { background: 'rgba(14, 203, 129, 0.1)', color: '#0ECB81' }
|
||||
: { background: 'rgba(246, 70, 93, 0.1)', color: '#F6465D' }
|
||||
}
|
||||
>
|
||||
{t(decision.success ? 'success' : 'failed', language)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{decision.input_prompt && (
|
||||
<div className="mb-3">
|
||||
<button
|
||||
onClick={() => setShowInputPrompt(!showInputPrompt)}
|
||||
className="flex items-center gap-2 text-sm transition-colors"
|
||||
style={{ color: '#60a5fa' }}
|
||||
>
|
||||
<span className="font-semibold">
|
||||
📥 {t('inputPrompt', language)}
|
||||
</span>
|
||||
<span className="text-xs">
|
||||
{showInputPrompt ? t('collapse', language) : t('expand', language)}
|
||||
</span>
|
||||
</button>
|
||||
{showInputPrompt && (
|
||||
<div
|
||||
className="mt-2 rounded p-4 text-sm font-mono whitespace-pre-wrap max-h-96 overflow-y-auto"
|
||||
style={{
|
||||
background: '#0B0E11',
|
||||
border: '1px solid #2B3139',
|
||||
color: '#EAECEF',
|
||||
}}
|
||||
>
|
||||
{decision.input_prompt}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{decision.cot_trace && (
|
||||
<div className="mb-3">
|
||||
<button
|
||||
onClick={() => setShowCoT(!showCoT)}
|
||||
className="flex items-center gap-2 text-sm transition-colors"
|
||||
style={{ color: '#F0B90B' }}
|
||||
>
|
||||
<span className="font-semibold">
|
||||
📤 {t('aiThinking', language)}
|
||||
</span>
|
||||
<span className="text-xs">
|
||||
{showCoT ? t('collapse', language) : t('expand', language)}
|
||||
</span>
|
||||
</button>
|
||||
{showCoT && (
|
||||
<div
|
||||
className="mt-2 rounded p-4 text-sm font-mono whitespace-pre-wrap max-h-96 overflow-y-auto"
|
||||
style={{
|
||||
background: '#0B0E11',
|
||||
border: '1px solid #2B3139',
|
||||
color: '#EAECEF',
|
||||
}}
|
||||
>
|
||||
{decision.cot_trace}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{decision.decisions && decision.decisions.length > 0 && (
|
||||
<div className="space-y-2 mb-3">
|
||||
{decision.decisions.map((action, index) => (
|
||||
<div
|
||||
key={`${action.symbol}-${index}`}
|
||||
className="flex items-center gap-2 text-sm rounded px-3 py-2"
|
||||
style={{ background: '#0B0E11' }}
|
||||
>
|
||||
<span
|
||||
className="font-mono font-bold"
|
||||
style={{ color: '#EAECEF' }}
|
||||
>
|
||||
{action.symbol}
|
||||
</span>
|
||||
<span
|
||||
className="px-2 py-0.5 rounded text-xs font-bold"
|
||||
style={
|
||||
action.action.includes('open')
|
||||
? {
|
||||
background: 'rgba(96, 165, 250, 0.1)',
|
||||
color: '#60a5fa',
|
||||
}
|
||||
: action.action.includes('close')
|
||||
? {
|
||||
background: 'rgba(14, 203, 129, 0.1)',
|
||||
color: '#0ECB81',
|
||||
}
|
||||
: {
|
||||
background: 'rgba(248, 113, 113, 0.1)',
|
||||
color: '#F87171',
|
||||
}
|
||||
}
|
||||
>
|
||||
{action.action}
|
||||
</span>
|
||||
{action.reasoning && (
|
||||
<span
|
||||
className="text-xs"
|
||||
style={{ color: '#848E9C', flex: 1 }}
|
||||
>
|
||||
{action.reasoning}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{decision.execution_log && decision.execution_log.length > 0 && (
|
||||
<div
|
||||
className="rounded p-3 text-xs font-mono space-y-1"
|
||||
style={{ background: '#0B0E11', border: '1px solid #2B3139' }}
|
||||
>
|
||||
{decision.execution_log.map((log, index) => (
|
||||
<div key={`${log}-${index}`} style={{ color: '#EAECEF' }}>
|
||||
{log}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{decision.error_message && (
|
||||
<div
|
||||
className="rounded p-3 mt-3 text-sm"
|
||||
style={{
|
||||
background: 'rgba(246, 70, 93, 0.1)',
|
||||
border: '1px solid rgba(246, 70, 93, 0.4)',
|
||||
color: '#F6465D',
|
||||
}}
|
||||
>
|
||||
❌ {decision.error_message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -6,16 +6,25 @@ import { t, type Language } from '../i18n/translations'
|
||||
import { Container } from './Container'
|
||||
import { useSystemConfig } from '../hooks/useSystemConfig'
|
||||
|
||||
type Page =
|
||||
| 'competition'
|
||||
| 'traders'
|
||||
| 'trader'
|
||||
| 'backtest'
|
||||
| 'faq'
|
||||
| 'login'
|
||||
| 'register'
|
||||
|
||||
interface HeaderBarProps {
|
||||
onLoginClick?: () => void
|
||||
isLoggedIn?: boolean
|
||||
isHomePage?: boolean
|
||||
currentPage?: string
|
||||
currentPage?: Page
|
||||
language?: Language
|
||||
onLanguageChange?: (lang: Language) => void
|
||||
user?: { email: string } | null
|
||||
onLogout?: () => void
|
||||
onPageChange?: (page: string) => void
|
||||
onPageChange?: (page: Page) => void
|
||||
}
|
||||
|
||||
export default function HeaderBar({
|
||||
@@ -207,6 +216,47 @@ export default function HeaderBar({
|
||||
{t('dashboardNav', language)}
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
if (onPageChange) {
|
||||
onPageChange('backtest')
|
||||
}
|
||||
navigate('/backtest')
|
||||
}}
|
||||
className="text-sm font-bold transition-all duration-300 relative focus:outline-2 focus:outline-yellow-500"
|
||||
style={{
|
||||
color:
|
||||
currentPage === 'backtest'
|
||||
? 'var(--brand-yellow)'
|
||||
: 'var(--brand-light-gray)',
|
||||
padding: '8px 16px',
|
||||
borderRadius: '8px',
|
||||
position: 'relative',
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (currentPage !== 'backtest') {
|
||||
e.currentTarget.style.color = 'var(--brand-yellow)'
|
||||
}
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (currentPage !== 'backtest') {
|
||||
e.currentTarget.style.color = 'var(--brand-light-gray)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{currentPage === 'backtest' && (
|
||||
<span
|
||||
className="absolute inset-0 rounded-lg"
|
||||
style={{
|
||||
background: 'rgba(240, 185, 11, 0.15)',
|
||||
zIndex: -1,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
Backtest
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => {
|
||||
if (onPageChange) {
|
||||
|
||||
Reference in New Issue
Block a user