-
- {[1, 2, 3].map((step) => (
-
-
- {step < 3 && (
-
step ? '#F0B90B' : '#2B3139' }}
- />
- )}
-
- ))}
-
- {wizardStep === 1 ? globalT('backtestConfigForm.selectModel', lang)
- : wizardStep === 2 ? globalT('backtestConfigForm.configure', lang)
- : globalT('backtestConfigForm.confirmStart', lang)}
-
-
-
-
-
- )
-}
-
-export type { WizardStep }
diff --git a/web/src/components/backtest/BacktestDecisionsTab.tsx b/web/src/components/backtest/BacktestDecisionsTab.tsx
deleted file mode 100644
index 71a10355..00000000
--- a/web/src/components/backtest/BacktestDecisionsTab.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { motion } from 'framer-motion'
-import { DecisionCard } from '../trader/DecisionCard'
-import type { Language } from '../../i18n/translations'
-import type { DecisionRecord } from '../../types'
-
-interface BacktestDecisionsTabProps {
- decisions: DecisionRecord[] | undefined
- language: Language
- tr: (key: string) => string
-}
-
-export function BacktestDecisionsTab({ decisions, language, tr }: BacktestDecisionsTabProps) {
- return (
-
- {decisions && decisions.length > 0 ? (
- decisions.map((d) => (
-
- ))
- ) : (
-
- {tr('decisionTrail.emptyHint')}
-
- )}
-
- )
-}
diff --git a/web/src/components/backtest/BacktestOverviewTab.tsx b/web/src/components/backtest/BacktestOverviewTab.tsx
deleted file mode 100644
index c05b19a6..00000000
--- a/web/src/components/backtest/BacktestOverviewTab.tsx
+++ /dev/null
@@ -1,325 +0,0 @@
-import { motion } from 'framer-motion'
-import {
- TrendingUp,
- TrendingDown,
- Activity,
- ArrowUpRight,
- ArrowDownRight,
-} from 'lucide-react'
-import { MetricTooltip } from '../common/MetricTooltip'
-import { t, type Language } from '../../i18n/translations'
-import { EquityChart } from './BacktestChartTab'
-import type {
- BacktestEquityPoint,
- BacktestTradeEvent,
- BacktestMetrics,
- BacktestPositionStatus,
-} from '../../types'
-
-// ============ Stat Card ============
-
-interface StatCardProps {
- icon: typeof TrendingUp
- label: string
- value: string | number
- suffix?: string
- trend?: 'up' | 'down' | 'neutral'
- color?: string
- metricKey?: string
- language?: string
-}
-
-export function StatCard({
- icon: Icon,
- label,
- value,
- suffix,
- trend,
- color = '#EAECEF',
- metricKey,
- language = 'en',
-}: StatCardProps) {
- const trendColors = {
- up: '#0ECB81',
- down: '#F6465D',
- neutral: '#848E9C',
- }
-
- return (
-
-
-
-
- {label}
-
- {metricKey && (
-
- )}
-
-
-
- {value}
-
- {suffix && (
-
- {suffix}
-
- )}
- {trend && trend !== 'neutral' && (
-
- {trend === 'up' ? : }
-
- )}
-
-
- )
-}
-
-// ============ Progress Ring ============
-
-interface ProgressRingProps {
- progress: number
- size?: number
-}
-
-export function ProgressRing({ progress, size = 120 }: ProgressRingProps) {
- const strokeWidth = 8
- const radius = (size - strokeWidth) / 2
- const circumference = radius * 2 * Math.PI
- const offset = circumference - (progress / 100) * circumference
-
- return (
-
-
-
-
- {progress.toFixed(0)}%
-
-
- Complete
-
-
-
- )
-}
-
-// ============ Positions Display ============
-
-interface PositionsDisplayProps {
- positions: BacktestPositionStatus[]
- language: Language
-}
-
-export function PositionsDisplay({ positions, language }: PositionsDisplayProps) {
- if (!positions || positions.length === 0) {
- return null
- }
-
- const totalUnrealizedPnL = positions.reduce((sum, p) => sum + p.unrealized_pnl, 0)
- const totalMargin = positions.reduce((sum, p) => sum + p.margin_used, 0)
-
- return (
-
-
-
-
-
- {t('backtestOverview.activePositions', language)}
-
-
- {positions.length}
-
-
-
-
- {t('backtestOverview.margin', language)}: ${totalMargin.toFixed(2)}
-
- = 0 ? '#0ECB81' : '#F6465D' }}
- >
- {t('backtestOverview.unrealized', language)}: {totalUnrealizedPnL >= 0 ? '+' : ''}
- ${totalUnrealizedPnL.toFixed(2)}
-
-
-
-
-
- {positions.map((pos) => {
- const isLong = pos.side === 'long'
- const pnlColor = pos.unrealized_pnl >= 0 ? '#0ECB81' : '#F6465D'
-
- return (
-
-
-
- {isLong ? (
-
- ) : (
-
- )}
-
-
-
-
- {pos.symbol.replace('USDT', '')}
-
-
- {isLong ? 'LONG' : 'SHORT'} {pos.leverage}x
-
-
-
- {t('backtestOverview.qty', language)}: {pos.quantity.toFixed(4)} ·{' '}
- {t('backtestOverview.margin', language)}: ${pos.margin_used.toFixed(2)}
-
-
-
-
-
-
-
- {t('backtestOverview.entry', language)}: ${pos.entry_price.toFixed(2)}
-
-
- {t('backtestOverview.mark', language)}: ${pos.mark_price.toFixed(2)}
-
-
-
-
- {pos.unrealized_pnl >= 0 ? '+' : ''}${pos.unrealized_pnl.toFixed(2)}
-
-
- {pos.unrealized_pnl_pct >= 0 ? '+' : ''}{pos.unrealized_pnl_pct.toFixed(2)}%
-
-
-
-
- )
- })}
-
-
- )
-}
-
-// ============ Overview Tab Content ============
-
-interface BacktestOverviewTabProps {
- equity: BacktestEquityPoint[] | undefined
- trades: BacktestTradeEvent[] | undefined
- metrics: BacktestMetrics | undefined
- language: Language
- tr: (key: string) => string
-}
-
-export function BacktestOverviewTab({
- equity,
- trades,
- metrics,
- language,
- tr,
-}: BacktestOverviewTabProps) {
- return (
-
- {equity && equity.length > 0 ? (
-
- ) : (
-
- {tr('charts.equityEmpty')}
-
- )}
-
- {metrics && (
-
-
-
- {t('backtestOverview.winRate', language)}
-
-
-
- {(metrics.win_rate ?? 0).toFixed(1)}%
-
-
-
-
- {t('backtestOverview.profitFactor', language)}
-
-
-
- {(metrics.profit_factor ?? 0).toFixed(2)}
-
-
-
-
- {t('backtestOverview.totalTrades', language)}
-
-
- {metrics.trades ?? 0}
-
-
-
-
- {t('backtestOverview.bestSymbol', language)}
-
-
- {metrics.best_symbol?.replace('USDT', '') || '-'}
-
-
-
- )}
-
- )
-}
diff --git a/web/src/components/backtest/BacktestPage.tsx b/web/src/components/backtest/BacktestPage.tsx
deleted file mode 100644
index c1a031cd..00000000
--- a/web/src/components/backtest/BacktestPage.tsx
+++ /dev/null
@@ -1,579 +0,0 @@
-import { useEffect, useMemo, useState, useCallback, type FormEvent } from 'react'
-import useSWR from 'swr'
-import { motion, AnimatePresence } from 'framer-motion'
-import {
- Play,
- Pause,
- Square,
- Download,
- Trash2,
- TrendingUp,
- BarChart3,
- Brain,
- Target,
- AlertTriangle,
-} from 'lucide-react'
-import { DeepVoidBackground } from '../common/DeepVoidBackground'
-import { api } from '../../lib/api'
-import { useLanguage } from '../../contexts/LanguageContext'
-import { t } from '../../i18n/translations'
-import { confirmToast } from '../../lib/notify'
-import type {
- BacktestStatusPayload,
- BacktestEquityPoint,
- BacktestTradeEvent,
- BacktestMetrics,
- DecisionRecord,
- AIModel,
- Strategy,
-} from '../../types'
-import {
- BacktestConfigForm,
- type WizardStep,
- type BacktestFormState,
-} from './BacktestConfigForm'
-import { BacktestRunList, getStateColor, getStateIcon } from './BacktestRunList'
-import { StatCard, ProgressRing, PositionsDisplay } from './BacktestOverviewTab'
-import { BacktestOverviewTab } from './BacktestOverviewTab'
-import { BacktestChartTab } from './BacktestChartTab'
-import { BacktestTradesTab } from './BacktestTradesTab'
-import { BacktestDecisionsTab } from './BacktestDecisionsTab'
-
-// ============ Types ============
-type ViewTab = 'overview' | 'chart' | 'trades' | 'decisions' | 'compare'
-
-const toLocalInput = (date: Date) => {
- const local = new Date(date.getTime() - date.getTimezoneOffset() * 60000)
- return local.toISOString().slice(0, 16)
-}
-
-// ============ Main Component ============
-export function BacktestPage() {
- const { language } = useLanguage()
- const tr = useCallback(
- (key: string, params?: Record
) => t(`backtestPage.${key}`, language, params),
- [language]
- )
-
- // State
- const now = new Date()
- const [wizardStep, setWizardStep] = useState(1)
- const [viewTab, setViewTab] = useState('overview')
- const [selectedRunId, setSelectedRunId] = useState()
- const [compareRunIds, setCompareRunIds] = useState([])
- const [isStarting, setIsStarting] = useState(false)
- const [toast, setToast] = useState<{ text: string; tone: 'info' | 'error' | 'success' } | null>(null)
-
- // Form state
- const [formState, setFormState] = useState({
- runId: '',
- symbols: 'BTCUSDT,ETHUSDT,SOLUSDT',
- timeframes: ['3m', '15m', '4h'],
- decisionTf: '3m',
- cadence: 20,
- start: toLocalInput(new Date(now.getTime() - 3 * 24 * 3600 * 1000)),
- end: toLocalInput(now),
- balance: 1000,
- fee: 5,
- slippage: 2,
- btcEthLeverage: 5,
- altcoinLeverage: 5,
- fill: 'next_open',
- prompt: 'baseline',
- promptTemplate: 'default',
- customPrompt: '',
- overridePrompt: false,
- cacheAI: true,
- replayOnly: false,
- aiModelId: '',
- strategyId: '',
- })
-
- // Data fetching
- const { data: runsResp, mutate: refreshRuns } = useSWR(['backtest-runs'], () =>
- api.getBacktestRuns({ limit: 100, offset: 0 })
- , { refreshInterval: 5000 })
- const runs = runsResp?.items ?? []
-
- const { data: aiModels } = useSWR('ai-models', api.getModelConfigs, { refreshInterval: 30000 })
- const { data: strategies } = useSWR('strategies', api.getStrategies, { refreshInterval: 30000 })
-
- const { data: status } = useSWR(
- selectedRunId ? ['bt-status', selectedRunId] : null,
- () => api.getBacktestStatus(selectedRunId!),
- { refreshInterval: 2000 }
- )
-
- const { data: equity } = useSWR(
- selectedRunId ? ['bt-equity', selectedRunId] : null,
- () => api.getBacktestEquity(selectedRunId!, '1m', 2000),
- { refreshInterval: 5000 }
- )
-
- const { data: trades } = useSWR(
- selectedRunId ? ['bt-trades', selectedRunId] : null,
- () => api.getBacktestTrades(selectedRunId!, 500),
- { refreshInterval: 5000 }
- )
-
- const { data: metrics } = useSWR(
- selectedRunId ? ['bt-metrics', selectedRunId] : null,
- () => api.getBacktestMetrics(selectedRunId!),
- { refreshInterval: 10000 }
- )
-
- const { data: decisions } = useSWR(
- selectedRunId ? ['bt-decisions', selectedRunId] : null,
- () => api.getBacktestDecisions(selectedRunId!, 30),
- { refreshInterval: 5000 }
- )
-
- const selectedRun = runs.find((r) => r.run_id === selectedRunId)
- const selectedModel = aiModels?.find((m) => m.id === formState.aiModelId)
- const selectedStrategy = strategies?.find((s) => s.id === formState.strategyId)
-
- // Check if selected strategy has dynamic coin source (needed for handleStart)
- const strategyHasDynamicCoins = useMemo(() => {
- if (!selectedStrategy) return false
- const coinSource = selectedStrategy.config?.coin_source
- if (!coinSource) return false
-
- if (coinSource.source_type === 'ai500' || coinSource.source_type === 'oi_top') {
- return true
- }
- if (coinSource.source_type === 'mixed' && (coinSource.use_ai500 || coinSource.use_oi_top)) {
- return true
- }
-
- const srcType = coinSource.source_type as string
- if (!srcType && (coinSource.use_ai500 || coinSource.use_oi_top)) {
- return true
- }
-
- return false
- }, [selectedStrategy])
-
- // Auto-select first model
- useEffect(() => {
- if (!formState.aiModelId && aiModels?.length) {
- const enabled = aiModels.find((m) => m.enabled)
- if (enabled) setFormState((s) => ({ ...s, aiModelId: enabled.id }))
- }
- }, [aiModels, formState.aiModelId])
-
- // Auto-select first run
- useEffect(() => {
- if (!selectedRunId && runs.length > 0) {
- setSelectedRunId(runs[0].run_id)
- }
- }, [runs, selectedRunId])
-
- // Handlers
- const handleFormChange = (key: string, value: string | number | boolean | string[]) => {
- setFormState((prev) => ({ ...prev, [key]: value }))
- }
-
- const handleStart = async (event: FormEvent) => {
- event.preventDefault()
- if (!selectedModel?.enabled) {
- setToast({ text: tr('toasts.selectModel'), tone: 'error' })
- return
- }
-
- try {
- setIsStarting(true)
- const start = new Date(formState.start).getTime()
- const end = new Date(formState.end).getTime()
- if (end <= start) throw new Error(tr('toasts.invalidRange'))
-
- const userSymbols = formState.symbols.split(',').map((s) => s.trim()).filter(Boolean)
- const symbolsToSend = (userSymbols.length === 0 && strategyHasDynamicCoins) ? [] : userSymbols
-
- const payload = await api.startBacktest({
- run_id: formState.runId.trim() || undefined,
- strategy_id: formState.strategyId || undefined,
- symbols: symbolsToSend,
- timeframes: formState.timeframes,
- decision_timeframe: formState.decisionTf,
- decision_cadence_nbars: formState.cadence,
- start_ts: Math.floor(start / 1000),
- end_ts: Math.floor(end / 1000),
- initial_balance: formState.balance,
- fee_bps: formState.fee,
- slippage_bps: formState.slippage,
- fill_policy: formState.fill,
- prompt_variant: formState.prompt,
- prompt_template: formState.promptTemplate,
- custom_prompt: formState.customPrompt.trim() || undefined,
- override_prompt: formState.overridePrompt,
- cache_ai: formState.cacheAI,
- replay_only: formState.replayOnly,
- ai_model_id: formState.aiModelId,
- leverage: {
- btc_eth_leverage: formState.btcEthLeverage,
- altcoin_leverage: formState.altcoinLeverage,
- },
- })
-
- setToast({ text: tr('toasts.startSuccess', { id: payload.run_id }), tone: 'success' })
- setSelectedRunId(payload.run_id)
- setWizardStep(1)
- await refreshRuns()
- } catch (error: unknown) {
- const errMsg = error instanceof Error ? error.message : tr('toasts.startFailed')
- setToast({ text: errMsg, tone: 'error' })
- } finally {
- setIsStarting(false)
- }
- }
-
- const handleControl = async (action: 'pause' | 'resume' | 'stop') => {
- if (!selectedRunId) return
- try {
- if (action === 'pause') await api.pauseBacktest(selectedRunId)
- if (action === 'resume') await api.resumeBacktest(selectedRunId)
- if (action === 'stop') await api.stopBacktest(selectedRunId)
- setToast({ text: tr('toasts.actionSuccess', { action, id: selectedRunId }), tone: 'success' })
- await refreshRuns()
- } catch (error: unknown) {
- const errMsg = error instanceof Error ? error.message : tr('toasts.actionFailed')
- setToast({ text: errMsg, tone: 'error' })
- }
- }
-
- const handleDelete = async () => {
- if (!selectedRunId) return
- const confirmed = await confirmToast(tr('toasts.confirmDelete', { id: selectedRunId }), {
- title: t('backtestPageExtra.confirmDelete', language),
- okText: t('backtestPageExtra.delete', language),
- cancelText: t('backtestPageExtra.cancel', language),
- })
- if (!confirmed) return
- try {
- await api.deleteBacktestRun(selectedRunId)
- setToast({ text: tr('toasts.deleteSuccess'), tone: 'success' })
- setSelectedRunId(undefined)
- await refreshRuns()
- } catch (error: unknown) {
- const errMsg = error instanceof Error ? error.message : tr('toasts.deleteFailed')
- setToast({ text: errMsg, tone: 'error' })
- }
- }
-
- const handleExport = async () => {
- if (!selectedRunId) return
- try {
- const blob = await api.exportBacktest(selectedRunId)
- const url = URL.createObjectURL(blob)
- const link = document.createElement('a')
- link.href = url
- link.download = `${selectedRunId}_export.zip`
- link.click()
- URL.revokeObjectURL(url)
- setToast({ text: tr('toasts.exportSuccess', { id: selectedRunId }), tone: 'success' })
- } catch (error: unknown) {
- const errMsg = error instanceof Error ? error.message : tr('toasts.exportFailed')
- setToast({ text: errMsg, tone: 'error' })
- }
- }
-
- const toggleCompare = (runId: string) => {
- setCompareRunIds((prev) =>
- prev.includes(runId) ? prev.filter((id) => id !== runId) : [...prev, runId].slice(-3)
- )
- }
-
- // Render
- return (
-
-
- {/* Toast */}
-
- {toast && (
-
- {toast.text}
-
- )}
-
-
- {/* Header */}
-
-
-
-
- {tr('title')}
-
-
- {tr('subtitle')}
-
-
-
-
-
-
- {/* Left Panel - Config / History */}
-
-
-
-
-
-
- {/* Right Panel - Results */}
-
- {!selectedRunId ? (
-
-
-
{tr('emptyStates.selectRun')}
-
- ) : (
- <>
- {/* Status Bar */}
-
-
-
-
-
-
- {selectedRunId}
-
-
-
- {getStateIcon(status?.state ?? selectedRun?.state ?? '')}
- {tr(`states.${status?.state ?? selectedRun?.state}`)}
-
- {selectedRun?.summary.decision_tf && (
-
- {selectedRun.summary.decision_tf} · {selectedRun.summary.symbol_count} symbols
-
- )}
-
-
-
-
-
- {(status?.state === 'running' || selectedRun?.state === 'running') && (
- <>
-
-
- >
- )}
- {status?.state === 'paused' && (
-
- )}
-
-
-
-
-
- {(status?.note || status?.last_error) && (
-
-
- {status?.note || status?.last_error}
-
- )}
-
- {/* Real-time Positions Display */}
- {status?.positions && status.positions.length > 0 && (
-
- )}
-
-
- {/* Stats Grid */}
-
-
- = 0 ? 'up' : 'down'}
- color={(metrics?.total_return_pct ?? 0) >= 0 ? '#0ECB81' : '#F6465D'}
- metricKey="total_return"
- language={language}
- />
-
-
-
-
- {/* Tabs */}
-
-
- {(['overview', 'chart', 'trades', 'decisions'] as ViewTab[]).map((tab) => (
-
- ))}
-
-
-
-
- {viewTab === 'overview' && (
-
- )}
-
- {viewTab === 'chart' && (
-
- )}
-
- {viewTab === 'trades' && (
-
- )}
-
- {viewTab === 'decisions' && (
-
- )}
-
-
-
- >
- )}
-
-
-
-
- )
-}
diff --git a/web/src/components/backtest/BacktestRunList.tsx b/web/src/components/backtest/BacktestRunList.tsx
deleted file mode 100644
index f1f7bb2e..00000000
--- a/web/src/components/backtest/BacktestRunList.tsx
+++ /dev/null
@@ -1,151 +0,0 @@
-import {
- Activity,
- CheckCircle2,
- XCircle,
- Pause,
- Clock,
- Layers,
- Eye,
-} from 'lucide-react'
-import { t, type Language } from '../../i18n/translations'
-
-// ============ Types ============
-
-export interface BacktestRunItem {
- run_id: string
- state: string
- summary: {
- progress_pct: number
- equity_last: number
- decision_tf?: string
- symbol_count?: number
- }
-}
-
-// ============ State Helpers ============
-
-export function getStateColor(state: string) {
- switch (state) {
- case 'running':
- return '#F0B90B'
- case 'completed':
- return '#0ECB81'
- case 'failed':
- case 'liquidated':
- return '#F6465D'
- case 'paused':
- return '#848E9C'
- default:
- return '#848E9C'
- }
-}
-
-export function getStateIcon(state: string) {
- switch (state) {
- case 'running':
- return
- case 'completed':
- return
- case 'failed':
- case 'liquidated':
- return
- case 'paused':
- return
- default:
- return
- }
-}
-
-// ============ Run History List ============
-
-interface BacktestRunListProps {
- runs: BacktestRunItem[]
- selectedRunId: string | undefined
- compareRunIds: string[]
- language: Language
- tr: (key: string, params?: Record) => string
- onSelectRun: (runId: string) => void
- onToggleCompare: (runId: string) => void
-}
-
-export function BacktestRunList({
- runs,
- selectedRunId,
- compareRunIds,
- language,
- tr,
- onSelectRun,
- onToggleCompare,
-}: BacktestRunListProps) {
- return (
-
-
-
-
- {tr('runList.title')}
-
-
- {runs.length} {t('backtestPageExtra.runs', language)}
-
-
-
-
- {runs.length === 0 ? (
-
- {tr('emptyStates.noRuns')}
-
- ) : (
- runs.map((run) => (
-
- ))
- )}
-
-
- )
-}
diff --git a/web/src/components/backtest/BacktestTradesTab.tsx b/web/src/components/backtest/BacktestTradesTab.tsx
deleted file mode 100644
index c34ae96f..00000000
--- a/web/src/components/backtest/BacktestTradesTab.tsx
+++ /dev/null
@@ -1,104 +0,0 @@
-import { useMemo } from 'react'
-import { motion } from 'framer-motion'
-import { TrendingUp, TrendingDown } from 'lucide-react'
-import type { BacktestTradeEvent } from '../../types'
-
-// ============ Trade Timeline ============
-
-function TradeTimeline({ trades }: { trades: BacktestTradeEvent[] }) {
- const recentTrades = useMemo(() => [...trades].slice(-20).reverse(), [trades])
-
- if (recentTrades.length === 0) {
- return (
-
- No trades yet
-
- )
- }
-
- return (
-
- {recentTrades.map((trade, idx) => {
- const isOpen = trade.action.includes('open')
- const isLong = trade.action.includes('long')
- const bgColor = isOpen ? 'rgba(14, 203, 129, 0.1)' : 'rgba(246, 70, 93, 0.1)'
- const borderColor = isOpen ? 'rgba(14, 203, 129, 0.3)' : 'rgba(246, 70, 93, 0.3)'
- const iconColor = isOpen ? '#0ECB81' : '#F6465D'
-
- return (
-
-
- {isLong ? (
-
- ) : (
-
- )}
-
-
-
-
- {trade.symbol.replace('USDT', '')}
-
-
- {trade.action.replace('_', ' ').toUpperCase()}
-
- {trade.leverage && (
-
- {trade.leverage}x
-
- )}
-
-
- {new Date(trade.ts).toLocaleString()} · Qty: {trade.qty.toFixed(4)} · ${trade.price.toFixed(2)}
-
-
-
-
= 0 ? '#0ECB81' : '#F6465D' }}
- >
- {trade.realized_pnl >= 0 ? '+' : ''}
- {trade.realized_pnl.toFixed(2)}
-
-
- USDT
-
-
-
- )
- })}
-
- )
-}
-
-// ============ Trades Tab Content ============
-
-interface BacktestTradesTabProps {
- trades: BacktestTradeEvent[] | undefined
-}
-
-export function BacktestTradesTab({ trades }: BacktestTradesTabProps) {
- return (
-
-
-
- )
-}
diff --git a/web/src/components/common/HeaderBar.tsx b/web/src/components/common/HeaderBar.tsx
index 4427fec9..1b580b53 100644
--- a/web/src/components/common/HeaderBar.tsx
+++ b/web/src/components/common/HeaderBar.tsx
@@ -9,7 +9,6 @@ type Page =
| 'competition'
| 'traders'
| 'trader'
- | 'backtest'
| 'strategy'
| 'strategy-market'
| 'data'
@@ -100,7 +99,6 @@ export default function HeaderBar({
{ page: 'trader', path: '/dashboard', label: t('dashboardNav', language), requiresAuth: true },
{ page: 'strategy', path: '/strategy', label: t('strategyNav', language), requiresAuth: true },
{ page: 'competition', path: '/competition', label: t('realtimeNav', language), requiresAuth: true },
- { page: 'backtest', path: '/backtest', label: 'Backtest', requiresAuth: true },
{ page: 'faq', path: '/faq', label: t('faqNav', language), requiresAuth: false },
]
@@ -342,7 +340,6 @@ export default function HeaderBar({
{ page: 'trader', path: '/dashboard', label: t('dashboardNav', language), requiresAuth: true },
{ page: 'strategy', path: '/strategy', label: t('strategyNav', language), requiresAuth: true },
{ page: 'competition', path: '/competition', label: t('realtimeNav', language), requiresAuth: true },
- { page: 'backtest', path: '/backtest', label: 'Backtest', requiresAuth: true },
{ page: 'faq', path: '/faq', label: t('faqNav', language), requiresAuth: false },
]
diff --git a/web/src/i18n/translations.ts b/web/src/i18n/translations.ts
index c4c2bdf5..e85fc70f 100644
--- a/web/src/i18n/translations.ts
+++ b/web/src/i18n/translations.ts
@@ -9,7 +9,6 @@ export const translations = {
details: 'Details',
tradingPanel: 'Trading Panel',
competition: 'Competition',
- backtest: 'Backtest',
running: 'RUNNING',
stopped: 'STOPPED',
adminMode: 'Admin Mode',
@@ -95,170 +94,6 @@ export const translations = {
fullscreen: 'Fullscreen',
exitFullscreen: 'Exit Fullscreen',
- // Backtest Page
- backtestPage: {
- title: 'Backtest Lab',
- subtitle:
- 'Pick a model + time range to replay the full AI decision loop.',
- start: 'Start Backtest',
- starting: 'Starting...',
- quickRanges: {
- h24: '24h',
- d3: '3d',
- d7: '7d',
- },
- actions: {
- pause: 'Pause',
- resume: 'Resume',
- stop: 'Stop',
- },
- states: {
- running: 'Running',
- paused: 'Paused',
- completed: 'Completed',
- failed: 'Failed',
- liquidated: 'Liquidated',
- },
- form: {
- aiModelLabel: 'AI Model',
- selectAiModel: 'Select AI model',
- providerLabel: 'Provider',
- statusLabel: 'Status',
- enabled: 'Enabled',
- disabled: 'Disabled',
- noModelWarning:
- 'Please add and enable an AI model on the Model Config page first.',
- runIdLabel: 'Run ID',
- runIdPlaceholder: 'Leave blank to auto-generate',
- decisionTfLabel: 'Decision TF',
- cadenceLabel: 'Decision cadence (bars)',
- timeRangeLabel: 'Time range',
- symbolsLabel: 'Symbols (comma-separated)',
- customTfPlaceholder: 'Custom TFs (comma separated, e.g. 2h,6h)',
- initialBalanceLabel: 'Initial balance (USDT)',
- feeLabel: 'Fee (bps)',
- slippageLabel: 'Slippage (bps)',
- btcEthLeverageLabel: 'BTC/ETH leverage (x)',
- altcoinLeverageLabel: 'Altcoin leverage (x)',
- fillPolicies: {
- nextOpen: 'Next open',
- barVwap: 'Bar VWAP',
- midPrice: 'Mid price',
- },
- promptPresets: {
- baseline: 'Baseline',
- aggressive: 'Aggressive',
- conservative: 'Conservative',
- scalping: 'Scalping',
- },
- cacheAiLabel: 'Reuse AI cache',
- replayOnlyLabel: 'Replay only',
- overridePromptLabel: 'Use only custom prompt',
- customPromptLabel: 'Custom prompt (optional)',
- customPromptPlaceholder:
- 'Append or fully customize the strategy prompt',
- },
- runList: {
- title: 'Runs',
- count: 'Total {count} records',
- },
- filters: {
- allStates: 'All states',
- searchPlaceholder: 'Run ID / label',
- },
- tableHeaders: {
- runId: 'Run ID',
- label: 'Label',
- state: 'State',
- progress: 'Progress',
- equity: 'Equity',
- lastError: 'Last Error',
- updated: 'Updated',
- },
- emptyStates: {
- noRuns: 'No runs yet',
- selectRun: 'Select a run to view details',
- },
- detail: {
- tfAndSymbols: 'TF: {tf} · Symbols {count}',
- labelPlaceholder: 'Label note',
- saveLabel: 'Save',
- deleteLabel: 'Delete',
- exportLabel: 'Export',
- errorLabel: 'Error',
- },
- toasts: {
- selectModel: 'Please select an AI model first.',
- modelDisabled: 'AI model {name} is disabled.',
- invalidRange: 'End time must be later than start time.',
- startSuccess: 'Backtest {id} started.',
- startFailed: 'Failed to start. Please try again later.',
- actionSuccess: '{action} {id} succeeded.',
- actionFailed: 'Operation failed. Please try again later.',
- labelSaved: 'Label updated.',
- labelFailed: 'Failed to update label.',
- confirmDelete: 'Delete backtest {id}? This action cannot be undone.',
- deleteSuccess: 'Backtest record deleted.',
- deleteFailed: 'Failed to delete. Please try again later.',
- traceFailed: 'Failed to fetch AI trace.',
- exportSuccess: 'Exported data for {id}.',
- exportFailed: 'Failed to export.',
- },
- aiTrace: {
- title: 'AI Trace',
- clear: 'Clear',
- cyclePlaceholder: 'Cycle',
- fetch: 'Fetch',
- prompt: 'Prompt',
- cot: 'Chain of thought',
- output: 'Output',
- cycleTag: 'Cycle #{cycle}',
- },
- decisionTrail: {
- title: 'AI Decision Trail',
- subtitle: 'Showing last {count} cycles',
- empty: 'No records yet',
- emptyHint:
- 'The AI thought & execution log will appear once the run starts.',
- },
- charts: {
- equityTitle: 'Equity Curve',
- equityEmpty: 'No data yet',
- },
- metrics: {
- title: 'Metrics',
- totalReturn: 'Total Return %',
- maxDrawdown: 'Max Drawdown %',
- sharpe: 'Sharpe',
- profitFactor: 'Profit Factor',
- pending: 'Calculating...',
- realized: 'Realized PnL',
- unrealized: 'Unrealized PnL',
- },
- trades: {
- title: 'Trade Events',
- headers: {
- time: 'Time',
- symbol: 'Symbol',
- action: 'Action',
- qty: 'Qty',
- leverage: 'Leverage',
- pnl: 'PnL',
- },
- empty: 'No trades yet',
- },
- metadata: {
- title: 'Metadata',
- created: 'Created',
- updated: 'Updated',
- processedBars: 'Processed Bars',
- maxDrawdown: 'Max DD',
- liquidated: 'Liquidated',
- yes: 'Yes',
- no: 'No',
- },
- },
-
// Competition Page
aiCompetition: 'AI Competition',
traders: 'traders',
@@ -838,7 +673,7 @@ export const translations = {
// ===== GETTING STARTED =====
faqWhatIsNOFX: 'What is NOFX?',
faqWhatIsNOFXAnswer:
- 'NOFX is an open-source AI-powered trading operating system for cryptocurrency and US stock markets. It uses large language models (LLMs) like DeepSeek, GPT, Claude, Gemini to analyze market data and make autonomous trading decisions. Key features include: multi-AI model support, multi-exchange trading, visual strategy builder, and backtesting.',
+ 'NOFX is an open-source AI-powered trading operating system for cryptocurrency and US stock markets. It uses large language models (LLMs) like DeepSeek, GPT, Claude, Gemini to analyze market data and make autonomous trading decisions. Key features include: multi-AI model support, multi-exchange trading, and visual strategy builder.',
faqHowDoesItWork: 'How does NOFX work?',
faqHowDoesItWorkAnswer:
@@ -846,7 +681,7 @@ export const translations = {
faqIsProfitable: 'Is NOFX profitable?',
faqIsProfitableAnswer:
- 'AI trading is experimental and NOT guaranteed to be profitable. Cryptocurrency futures are highly volatile and risky. NOFX is designed for educational and research purposes. We strongly recommend: starting with small amounts (10-50 USDT), never investing more than you can afford to lose, thoroughly testing with backtests before live trading, and understanding that past performance does not guarantee future results.',
+ 'AI trading is experimental and NOT guaranteed to be profitable. Cryptocurrency futures are highly volatile and risky. NOFX is designed for educational and research purposes. We strongly recommend: starting with small amounts (10-50 USDT), never investing more than you can afford to lose, thoroughly testing before live trading, and understanding that past performance does not guarantee future results.',
faqSupportedExchanges: 'Which exchanges are supported?',
faqSupportedExchangesAnswer:
@@ -998,10 +833,6 @@ export const translations = {
faqStrategyStudioAnswer:
'Strategy Studio is a visual strategy builder where you configure: 1) Coin Sources - which cryptocurrencies to trade (static list, AI500 top coins, OI ranking); 2) Technical Indicators - EMA, MACD, RSI, ATR, Volume, Open Interest, Funding Rate; 3) Risk Controls - leverage limits, position sizing, margin caps; 4) Custom Prompts - specific instructions for AI. No coding required.',
- faqBacktestLab: 'What is Backtest Lab?',
- faqBacktestLabAnswer:
- 'Backtest Lab tests your strategy against historical data without risking real funds. Features: 1) Configure AI model, date range, initial balance; 2) Watch real-time progress with equity curve; 3) View metrics: Return %, Max Drawdown, Sharpe Ratio, Win Rate; 4) Analyze individual trades and AI reasoning. Essential for validating strategies before live trading.',
-
faqCompetitionMode: 'What is Competition Mode?',
faqCompetitionModeAnswer:
'Competition page shows a real-time leaderboard of all your traders. Compare: ROI, P&L, Sharpe ratio, win rate, number of trades. Use this to A/B test different AI models, strategies, or configurations. Traders can be marked as "Show in Competition" to appear on the leaderboard.',
@@ -1025,7 +856,7 @@ export const translations = {
faqCompareAIModels: 'How do I compare different AI models?',
faqCompareAIModelsAnswer:
- 'Create multiple traders with different AI models but same strategy/exchange. Run them simultaneously and compare on Competition page. Metrics to watch: ROI, win rate, Sharpe ratio, max drawdown. Alternatively, use Backtest Lab to test models against same historical data.',
+ 'Create multiple traders with different AI models but same strategy/exchange. Run them simultaneously and compare on Competition page. Metrics to watch: ROI, win rate, Sharpe ratio, max drawdown.',
// ===== CONTRIBUTING =====
faqHowToContribute: 'How can I contribute to NOFX?',
@@ -1256,53 +1087,6 @@ export const translations = {
runAiTestHint: 'Click to run AI test',
},
- // Backtest Page (additional keys)
- backtestPageExtra: {
- newBacktest: 'New Backtest',
- confirmDelete: 'Confirm Delete',
- delete: 'Delete',
- cancel: 'Cancel',
- equity: 'Equity',
- totalReturn: 'Return',
- maxDD: 'Max DD',
- sharpe: 'Sharpe',
- tabOverview: 'Overview',
- tabChart: 'Chart',
- tabTrades: 'Trades',
- tabDecisions: 'Decisions',
- addToCompare: 'Add to compare',
- runs: 'runs',
- runCount: 'runs',
- },
-
- // Backtest Overview Tab
- backtestOverview: {
- activePositions: 'Active Positions',
- margin: 'Margin',
- unrealized: 'Unrealized',
- qty: 'Qty',
- entry: 'Entry',
- mark: 'Mark',
- winRate: 'Win Rate',
- profitFactor: 'Profit Factor',
- totalTrades: 'Total Trades',
- bestSymbol: 'Best Symbol',
- },
-
- // Backtest Chart Tab
- backtestChart: {
- noTrades: 'No trades to display',
- symbol: 'Symbol',
- interval: 'Interval',
- trades: 'trades',
- loadingKline: 'Loading kline data...',
- openProfit: 'Open/Profit',
- lossClose: 'Loss Close',
- close: 'Close',
- equityCurve: 'Equity Curve',
- candlestickTradeMarkers: 'Candlestick & Trade Markers',
- },
-
// Metric Tooltip
metricTooltip: {
formula: 'Formula',
@@ -1314,10 +1098,9 @@ export const translations = {
accessDenied: 'ACCESS DENIED',
subtitleWithFeature: 'Module "{featureName}" requires elevated privileges',
subtitleDefault: 'Authorization required for this module',
- description: 'Initialize authentication protocol to unlock full system capabilities: AI Trader configuration, Strategy Market data streams, and Backtest Simulation core.',
+ description: 'Initialize authentication protocol to unlock full system capabilities: AI Trader configuration and Strategy Market data streams.',
benefit1: 'AI Trader Control',
benefit2: 'HFT Strategy Market',
- benefit3: 'Historical Backtest Engine',
benefit4: 'Full System Visualization',
loginButton: 'EXECUTE LOGIN',
registerButton: 'REGISTER NEW ID',
@@ -1552,28 +1335,6 @@ export const translations = {
no: 'No',
},
- // BacktestConfigForm
- backtestConfigForm: {
- selectModel: 'Select Model',
- configure: 'Configure',
- confirmStart: 'Confirm',
- strategyOptional: 'Strategy (Optional)',
- noSavedStrategy: 'No saved strategy',
- coinSource: 'Coin Source:',
- clearDynamicCoins: 'Clear the symbols field below to use strategy\'s dynamic coins',
- optionalCoinSource: 'Optional - strategy has coin source',
- leavEmptyForStrategy: 'Leave empty to use strategy coin source',
- clearToUseStrategy: 'Clear to use strategy',
- next: 'Next',
- timeframes: 'Timeframes',
- back: 'Back',
- strategyStyle: 'Strategy Style',
- quickRange24h: '24h',
- quickRange3d: '3d',
- quickRange7d: '7d',
- quickRange30d: '30d',
- },
-
},
zh: {
// Header
@@ -1583,7 +1344,6 @@ export const translations = {
details: '详情',
tradingPanel: '交易面板',
competition: '竞赛',
- backtest: '回测',
running: '运行中',
stopped: '已停止',
adminMode: '管理员模式',
@@ -1669,166 +1429,6 @@ export const translations = {
fullscreen: '全屏',
exitFullscreen: '退出全屏',
- // Backtest Page
- backtestPage: {
- title: '回测实验室',
- subtitle: '选择模型与时间范围,快速复盘 AI 决策链路。',
- start: '启动回测',
- starting: '启动中...',
- quickRanges: {
- h24: '24小时',
- d3: '3天',
- d7: '7天',
- },
- actions: {
- pause: '暂停',
- resume: '恢复',
- stop: '停止',
- },
- states: {
- running: '运行中',
- paused: '已暂停',
- completed: '已完成',
- failed: '失败',
- liquidated: '已爆仓',
- },
- form: {
- aiModelLabel: 'AI 模型',
- selectAiModel: '选择AI模型',
- providerLabel: 'Provider',
- statusLabel: '状态',
- enabled: '已启用',
- disabled: '未启用',
- noModelWarning: '请先在「模型配置」页面添加并启用AI模型。',
- runIdLabel: 'Run ID',
- runIdPlaceholder: '留空则自动生成',
- decisionTfLabel: '决策周期',
- cadenceLabel: '决策节奏(根数)',
- timeRangeLabel: '时间范围',
- symbolsLabel: '交易标的(逗号分隔)',
- customTfPlaceholder: '自定义周期(逗号分隔,例如 2h,6h)',
- initialBalanceLabel: '初始资金 (USDT)',
- feeLabel: '手续费 (bps)',
- slippageLabel: '滑点 (bps)',
- btcEthLeverageLabel: 'BTC/ETH 杠杆 (倍)',
- altcoinLeverageLabel: '山寨币杠杆 (倍)',
- fillPolicies: {
- nextOpen: '下一根开盘价',
- barVwap: 'K线 VWAP',
- midPrice: '中间价',
- },
- promptPresets: {
- baseline: '基础版',
- aggressive: '激进版',
- conservative: '稳健版',
- scalping: '剥头皮',
- },
- cacheAiLabel: '复用AI缓存',
- replayOnlyLabel: '仅回放记录',
- overridePromptLabel: '仅使用自定义提示词',
- customPromptLabel: '自定义提示词(可选)',
- customPromptPlaceholder: '追加或完全自定义策略提示词',
- },
- runList: {
- title: '运行列表',
- count: '共 {count} 条记录',
- },
- filters: {
- allStates: '全部状态',
- searchPlaceholder: 'Run ID / 标签',
- },
- tableHeaders: {
- runId: 'Run ID',
- label: '标签',
- state: '状态',
- progress: '进度',
- equity: '净值',
- lastError: '最后错误',
- updated: '更新时间',
- },
- emptyStates: {
- noRuns: '暂无记录',
- selectRun: '请选择一个运行查看详情',
- },
- detail: {
- tfAndSymbols: '周期: {tf} · 币种 {count}',
- labelPlaceholder: '备注标签',
- saveLabel: '保存',
- deleteLabel: '删除',
- exportLabel: '导出',
- errorLabel: '错误',
- },
- toasts: {
- selectModel: '请先选择一个AI模型。',
- modelDisabled: 'AI模型 {name} 尚未启用。',
- invalidRange: '结束时间必须晚于开始时间。',
- startSuccess: '回测 {id} 已启动。',
- startFailed: '启动失败,请稍后再试。',
- actionSuccess: '{action} {id} 成功。',
- actionFailed: '操作失败,请稍后再试。',
- labelSaved: '标签已更新。',
- labelFailed: '更新标签失败。',
- confirmDelete: '确认删除回测 {id} 吗?该操作不可恢复。',
- deleteSuccess: '回测记录已删除。',
- deleteFailed: '删除失败,请稍后再试。',
- traceFailed: '获取AI思维链失败。',
- exportSuccess: '已导出 {id} 的数据。',
- exportFailed: '导出失败。',
- },
- aiTrace: {
- title: 'AI 思维链',
- clear: '清除',
- cyclePlaceholder: '循环编号',
- fetch: '获取',
- prompt: '提示词',
- cot: '思考链',
- output: '输出',
- cycleTag: '周期 #{cycle}',
- },
- decisionTrail: {
- title: 'AI 决策轨迹',
- subtitle: '展示最近 {count} 次循环',
- empty: '暂无记录',
- emptyHint: '回测运行后将自动记录每次 AI 思考与执行',
- },
- charts: {
- equityTitle: '净值曲线',
- equityEmpty: '暂无数据',
- },
- metrics: {
- title: '指标',
- totalReturn: '总收益率 %',
- maxDrawdown: '最大回撤 %',
- sharpe: '夏普比率',
- profitFactor: '盈亏因子',
- pending: '计算中...',
- realized: '已实现盈亏',
- unrealized: '未实现盈亏',
- },
- trades: {
- title: '交易事件',
- headers: {
- time: '时间',
- symbol: '币种',
- action: '操作',
- qty: '数量',
- leverage: '杠杆',
- pnl: '盈亏',
- },
- empty: '暂无交易',
- },
- metadata: {
- title: '元信息',
- created: '创建时间',
- updated: '更新时间',
- processedBars: '已处理K线',
- maxDrawdown: '最大回撤',
- liquidated: '是否爆仓',
- yes: '是',
- no: '否',
- },
- },
-
// Competition Page
aiCompetition: 'AI竞赛',
traders: '交易员',
@@ -2521,10 +2121,6 @@ export const translations = {
faqStrategyStudioAnswer:
'策略工作室是可视化策略构建器,您可以配置:1)币种来源 - 交易哪些加密货币(静态列表、AI500 热门币、OI 排行);2)技术指标 - EMA、MACD、RSI、ATR、成交量、持仓量、资金费率;3)风控 - 杠杆限制、仓位大小、保证金上限;4)自定义提示词 - AI 的特定指令。无需编程。',
- faqBacktestLab: '什么是回测实验室?',
- faqBacktestLabAnswer:
- '回测实验室用历史数据测试您的策略,无需冒真金风险。功能:1)配置 AI 模型、日期范围、初始余额;2)实时观看进度和权益曲线;3)查看指标:收益率、最大回撤、夏普比率、胜率;4)分析单笔交易和 AI 推理。实盘交易前验证策略的必备工具。',
-
faqCompetitionMode: '什么是竞赛模式?',
faqCompetitionModeAnswer:
'竞赛页面显示所有交易员的实时排行榜。比较:ROI、盈亏、夏普比率、胜率、交易次数。用于 A/B 测试不同 AI 模型、策略或配置。交易员可标记为"在竞赛中显示"以出现在排行榜上。',
@@ -2548,7 +2144,7 @@ export const translations = {
faqCompareAIModels: '如何比较不同 AI 模型?',
faqCompareAIModelsAnswer:
- '创建多个交易员,使用不同 AI 模型但相同策略/交易所。同时运行并在竞赛页面比较。关注指标:ROI、胜率、夏普比率、最大回撤。或者使用回测实验室用相同历史数据测试模型。',
+ '创建多个交易员,使用不同 AI 模型但相同策略/交易所。同时运行并在竞赛页面比较。关注指标:ROI、胜率、夏普比率、最大回撤。',
// ===== 参与贡献 =====
faqHowToContribute: '如何为 NOFX 做贡献?',
@@ -2772,53 +2368,6 @@ export const translations = {
runAiTestHint: '点击运行 AI 测试',
},
- // Backtest Page (additional keys)
- backtestPageExtra: {
- newBacktest: '新建回测',
- confirmDelete: '确认删除',
- delete: '删除',
- cancel: '取消',
- equity: '当前净值',
- totalReturn: '总收益率',
- maxDD: '最大回撤',
- sharpe: '夏普比率',
- tabOverview: '概览',
- tabChart: '图表',
- tabTrades: '交易',
- tabDecisions: 'AI决策',
- addToCompare: '添加到对比',
- runs: '条',
- runCount: '条',
- },
-
- // Backtest Overview Tab
- backtestOverview: {
- activePositions: '当前持仓',
- margin: '保证金',
- unrealized: '浮盈',
- qty: '数量',
- entry: '开仓',
- mark: '现价',
- winRate: '胜率',
- profitFactor: '盈亏因子',
- totalTrades: '总交易数',
- bestSymbol: '最佳币种',
- },
-
- // Backtest Chart Tab
- backtestChart: {
- noTrades: '没有交易记录',
- symbol: '币种',
- interval: '周期',
- trades: '笔交易',
- loadingKline: '加载K线数据...',
- openProfit: '开仓/盈利',
- lossClose: '亏损平仓',
- close: '平仓',
- equityCurve: '资金曲线',
- candlestickTradeMarkers: 'K线图 & 交易标记',
- },
-
// Metric Tooltip
metricTooltip: {
formula: '计算公式',
@@ -2830,10 +2379,9 @@ export const translations = {
accessDenied: '访问被拒绝',
subtitleWithFeature: '访问「{featureName}」需要更高权限',
subtitleDefault: '此模块需要授权访问',
- description: '初始化身份验证协议以解锁完整系统功能:AI 交易员配置、策略市场数据流、回测模拟核心。',
+ description: '初始化身份验证协议以解锁完整系统功能:AI 交易员配置、策略市场数据流。',
benefit1: 'AI 交易员控制权',
benefit2: '高频策略核心市场',
- benefit3: '历史数据回测引擎',
benefit4: '全系统数据可视化',
loginButton: '执行登录指令',
registerButton: '注册新用户 ID',
@@ -3062,27 +2610,6 @@ export const translations = {
no: '否',
},
- backtestConfigForm: {
- selectModel: '选择模型',
- configure: '配置参数',
- confirmStart: '确认启动',
- strategyOptional: '策略配置(可选)',
- noSavedStrategy: '不使用保存的策略',
- coinSource: '币种来源:',
- clearDynamicCoins: '⚡ 清空下方币种输入框即可使用策略的动态币种',
- optionalCoinSource: '可选 - 策略已配置币种来源',
- leavEmptyForStrategy: '留空将使用策略配置的币种来源',
- clearToUseStrategy: '清空使用策略币种',
- next: '下一步',
- timeframes: '时间周期',
- back: '上一步',
- strategyStyle: '策略风格',
- quickRange24h: '24小时',
- quickRange3d: '3天',
- quickRange7d: '7天',
- quickRange30d: '30天',
- },
-
},
id: {
// Header
@@ -3092,7 +2619,6 @@ export const translations = {
details: 'Detail',
tradingPanel: 'Panel Trading',
competition: 'Kompetisi',
- backtest: 'Backtest',
running: 'BERJALAN',
stopped: 'BERHENTI',
adminMode: 'Mode Admin',
@@ -3178,166 +2704,6 @@ export const translations = {
fullscreen: 'Layar Penuh',
exitFullscreen: 'Keluar Layar Penuh',
- // Backtest Page
- backtestPage: {
- title: 'Lab Backtest',
- subtitle: 'Pilih model + rentang waktu untuk memutar ulang alur keputusan AI.',
- start: 'Mulai Backtest',
- starting: 'Memulai...',
- quickRanges: {
- h24: '24j',
- d3: '3h',
- d7: '7h',
- },
- actions: {
- pause: 'Jeda',
- resume: 'Lanjutkan',
- stop: 'Berhenti',
- },
- states: {
- running: 'Berjalan',
- paused: 'Dijeda',
- completed: 'Selesai',
- failed: 'Gagal',
- liquidated: 'Terlikuidasi',
- },
- form: {
- aiModelLabel: 'Model AI',
- selectAiModel: 'Pilih model AI',
- providerLabel: 'Penyedia',
- statusLabel: 'Status',
- enabled: 'Aktif',
- disabled: 'Nonaktif',
- noModelWarning: 'Silakan tambahkan dan aktifkan model AI di halaman Konfigurasi Model terlebih dahulu.',
- runIdLabel: 'Run ID',
- runIdPlaceholder: 'Kosongkan untuk otomatis',
- decisionTfLabel: 'TF Keputusan',
- cadenceLabel: 'Irama keputusan (bar)',
- timeRangeLabel: 'Rentang waktu',
- symbolsLabel: 'Simbol (pisahkan dengan koma)',
- customTfPlaceholder: 'TF kustom (pisahkan dengan koma, misal 2h,6h)',
- initialBalanceLabel: 'Saldo awal (USDT)',
- feeLabel: 'Biaya (bps)',
- slippageLabel: 'Selisih harga (bps)',
- btcEthLeverageLabel: 'Leverage BTC/ETH (x)',
- altcoinLeverageLabel: 'Leverage Altcoin (x)',
- fillPolicies: {
- nextOpen: 'Harga buka berikutnya',
- barVwap: 'VWAP Bar',
- midPrice: 'Harga tengah',
- },
- promptPresets: {
- baseline: 'Dasar',
- aggressive: 'Agresif',
- conservative: 'Konservatif',
- scalping: 'Scalping',
- },
- cacheAiLabel: 'Gunakan cache AI',
- replayOnlyLabel: 'Hanya putar ulang',
- overridePromptLabel: 'Gunakan hanya prompt kustom',
- customPromptLabel: 'Prompt kustom (opsional)',
- customPromptPlaceholder: 'Tambahkan atau kustomisasi prompt strategi sepenuhnya',
- },
- runList: {
- title: 'Daftar Run',
- count: 'Total {count} catatan',
- },
- filters: {
- allStates: 'Semua status',
- searchPlaceholder: 'Run ID / label',
- },
- tableHeaders: {
- runId: 'Run ID',
- label: 'Label',
- state: 'Status',
- progress: 'Progres',
- equity: 'Ekuitas',
- lastError: 'Error Terakhir',
- updated: 'Diperbarui',
- },
- emptyStates: {
- noRuns: 'Belum ada run',
- selectRun: 'Pilih run untuk melihat detail',
- },
- detail: {
- tfAndSymbols: 'TF: {tf} · Simbol {count}',
- labelPlaceholder: 'Catatan label',
- saveLabel: 'Simpan',
- deleteLabel: 'Hapus',
- exportLabel: 'Ekspor',
- errorLabel: 'Error',
- },
- toasts: {
- selectModel: 'Silakan pilih model AI terlebih dahulu.',
- modelDisabled: 'Model AI {name} tidak aktif.',
- invalidRange: 'Waktu akhir harus lebih lambat dari waktu mulai.',
- startSuccess: 'Backtest {id} dimulai.',
- startFailed: 'Gagal memulai. Silakan coba lagi nanti.',
- actionSuccess: '{action} {id} berhasil.',
- actionFailed: 'Operasi gagal. Silakan coba lagi nanti.',
- labelSaved: 'Label diperbarui.',
- labelFailed: 'Gagal memperbarui label.',
- confirmDelete: 'Hapus backtest {id}? Tindakan ini tidak dapat dibatalkan.',
- deleteSuccess: 'Catatan backtest dihapus.',
- deleteFailed: 'Gagal menghapus. Silakan coba lagi nanti.',
- traceFailed: 'Gagal mengambil jejak AI.',
- exportSuccess: 'Data untuk {id} diekspor.',
- exportFailed: 'Gagal mengekspor.',
- },
- aiTrace: {
- title: 'Jejak AI',
- clear: 'Hapus',
- cyclePlaceholder: 'Siklus',
- fetch: 'Ambil',
- prompt: 'Prompt',
- cot: 'Rantai pemikiran',
- output: 'Output',
- cycleTag: 'Siklus #{cycle}',
- },
- decisionTrail: {
- title: 'Jejak Keputusan AI',
- subtitle: 'Menampilkan {count} siklus terakhir',
- empty: 'Belum ada catatan',
- emptyHint: 'Log pemikiran & eksekusi AI akan muncul setelah run dimulai.',
- },
- charts: {
- equityTitle: 'Kurva Ekuitas',
- equityEmpty: 'Belum ada data',
- },
- metrics: {
- title: 'Metrik',
- totalReturn: 'Total Return %',
- maxDrawdown: 'Drawdown Maks %',
- sharpe: 'Sharpe',
- profitFactor: 'Profit Factor',
- pending: 'Menghitung...',
- realized: 'L/R Terealisasi',
- unrealized: 'L/R Belum Terealisasi',
- },
- trades: {
- title: 'Riwayat Trading',
- headers: {
- time: 'Waktu',
- symbol: 'Simbol',
- action: 'Aksi',
- qty: 'Jml',
- leverage: 'Leverage',
- pnl: 'L/R',
- },
- empty: 'Belum ada trading',
- },
- metadata: {
- title: 'Metadata',
- created: 'Dibuat',
- updated: 'Diperbarui',
- processedBars: 'Bar Diproses',
- maxDrawdown: 'DD Maks',
- liquidated: 'Terlikuidasi',
- yes: 'Ya',
- no: 'Tidak',
- },
- },
-
// Competition Page
aiCompetition: 'Kompetisi AI',
traders: 'trader',
@@ -3799,11 +3165,11 @@ export const translations = {
faqCategoryAIModels: 'Model AI',
faqCategoryContributing: 'Kontribusi',
faqWhatIsNOFX: 'Apa itu NOFX?',
- faqWhatIsNOFXAnswer: 'NOFX adalah sistem operasi trading bertenaga AI open-source untuk pasar kripto dan saham AS. Ia menggunakan model bahasa besar (LLM) seperti DeepSeek, GPT, Claude, Gemini untuk menganalisis data pasar dan membuat keputusan trading secara otonom. Fitur utama: dukungan multi-model AI, trading multi-bursa, pembangun strategi visual, dan backtesting.',
+ faqWhatIsNOFXAnswer: 'NOFX adalah sistem operasi trading bertenaga AI open-source untuk pasar kripto dan saham AS. Ia menggunakan model bahasa besar (LLM) seperti DeepSeek, GPT, Claude, Gemini untuk menganalisis data pasar dan membuat keputusan trading secara otonom. Fitur utama: dukungan multi-model AI, trading multi-bursa, dan pembangun strategi visual.',
faqHowDoesItWork: 'Bagaimana cara kerja NOFX?',
faqHowDoesItWorkAnswer: 'NOFX bekerja dalam 5 langkah: 1) Konfigurasi model AI dan kredensial API bursa; 2) Buat strategi trading (pemilihan koin, indikator, kontrol risiko); 3) Buat "Trader" menggabungkan Model AI + Bursa + Strategi; 4) Mulai trader - dia akan menganalisis data pasar secara berkala dan membuat keputusan beli/jual/tahan; 5) Pantau performa di dasbor.',
faqIsProfitable: 'Apakah NOFX menguntungkan?',
- faqIsProfitableAnswer: 'Trading AI bersifat eksperimental dan TIDAK dijamin menguntungkan. Futures kripto sangat volatil dan berisiko. NOFX dirancang untuk tujuan edukasi dan riset. Kami sangat menyarankan: mulai dengan jumlah kecil (10-50 USDT), jangan investasi melebihi yang sanggup Anda rugi, uji dengan backtest sebelum trading nyata.',
+ faqIsProfitableAnswer: 'Trading AI bersifat eksperimental dan TIDAK dijamin menguntungkan. Futures kripto sangat volatil dan berisiko. NOFX dirancang untuk tujuan edukasi dan riset. Kami sangat menyarankan: mulai dengan jumlah kecil (10-50 USDT), jangan investasi melebihi yang sanggup Anda rugi, uji sebelum trading nyata.',
faqSupportedExchanges: 'Bursa mana yang didukung?',
faqSupportedExchangesAnswer: 'CEX (Tersentralisasi): Binance Futures, Bybit, OKX, Bitget. DEX (Terdesentralisasi): Hyperliquid, Aster DEX, Lighter. Setiap bursa memiliki fitur berbeda - Binance memiliki likuiditas terbesar, Hyperliquid sepenuhnya on-chain tanpa KYC.',
faqSupportedAIModels: 'Model AI mana yang didukung?',
@@ -3876,8 +3242,6 @@ export const translations = {
faqCanNOFXStealFundsAnswer: 'NOFX open-source (lisensi AGPL-3.0) - Anda bisa audit semua kode. API key disimpan lokal di mesin ANDA, tidak pernah dikirim ke server eksternal.',
faqStrategyStudio: 'Apa itu Strategy Studio?',
faqStrategyStudioAnswer: 'Strategy Studio adalah pembangun strategi visual untuk konfigurasi: Sumber Koin, Indikator Teknikal, Kontrol Risiko, dan Prompt Kustom. Tanpa coding.',
- faqBacktestLab: 'Apa itu Lab Backtest?',
- faqBacktestLabAnswer: 'Lab Backtest menguji strategi Anda terhadap data historis tanpa risiko dana nyata.',
faqCompetitionMode: 'Apa itu Mode Kompetisi?',
faqCompetitionModeAnswer: 'Halaman kompetisi menampilkan papan peringkat realtime semua trader Anda. Bandingkan ROI, L/R, rasio Sharpe, win rate.',
faqChainOfThought: 'Apa itu Chain of Thought (CoT)?',
@@ -4087,53 +3451,6 @@ export const translations = {
runAiTestHint: 'Klik untuk menjalankan uji AI',
},
- // Backtest Page (additional keys)
- backtestPageExtra: {
- newBacktest: 'Backtest Baru',
- confirmDelete: 'Konfirmasi Hapus',
- delete: 'Hapus',
- cancel: 'Batal',
- equity: 'Ekuitas',
- totalReturn: 'Return',
- maxDD: 'Max DD',
- sharpe: 'Sharpe',
- tabOverview: 'Ringkasan',
- tabChart: 'Grafik',
- tabTrades: 'Trade',
- tabDecisions: 'Keputusan AI',
- addToCompare: 'Tambah ke perbandingan',
- runs: 'berjalan',
- runCount: 'berjalan',
- },
-
- // Backtest Overview Tab
- backtestOverview: {
- activePositions: 'Posisi Aktif',
- margin: 'Margin',
- unrealized: 'Belum Terealisasi',
- qty: 'Kuantitas',
- entry: 'Masuk',
- mark: 'Harga Saat Ini',
- winRate: 'Win Rate',
- profitFactor: 'Profit Factor',
- totalTrades: 'Total Trade',
- bestSymbol: 'Simbol Terbaik',
- },
-
- // Backtest Chart Tab
- backtestChart: {
- noTrades: 'Tidak ada trade untuk ditampilkan',
- symbol: 'Simbol',
- interval: 'Interval',
- trades: 'trade',
- loadingKline: 'Memuat data kline...',
- openProfit: 'Buka/Profit',
- lossClose: 'Tutup Rugi',
- close: 'Tutup',
- equityCurve: 'Kurva Ekuitas',
- candlestickTradeMarkers: 'Candlestick & Penanda Trade',
- },
-
// Metric Tooltip
metricTooltip: {
formula: 'Formula',
@@ -4145,10 +3462,9 @@ export const translations = {
accessDenied: 'AKSES DITOLAK',
subtitleWithFeature: 'Modul "{featureName}" memerlukan hak akses lebih tinggi',
subtitleDefault: 'Otorisasi diperlukan untuk modul ini',
- description: 'Inisialisasi protokol autentikasi untuk membuka kemampuan sistem penuh: konfigurasi Trader AI, aliran data Pasar Strategi, dan inti Simulasi Backtest.',
+ description: 'Inisialisasi protokol autentikasi untuk membuka kemampuan sistem penuh: konfigurasi Trader AI dan aliran data Pasar Strategi.',
benefit1: 'Kontrol Trader AI',
benefit2: 'Pasar Strategi HFT',
- benefit3: 'Mesin Backtest Historis',
benefit4: 'Visualisasi Sistem Penuh',
loginButton: 'JALANKAN LOGIN',
registerButton: 'DAFTAR ID BARU',
@@ -4377,27 +3693,6 @@ export const translations = {
no: 'Tidak',
},
- backtestConfigForm: {
- selectModel: 'Pilih Model',
- configure: 'Konfigurasi',
- confirmStart: 'Konfirmasi',
- strategyOptional: 'Strategi (Opsional)',
- noSavedStrategy: 'Tanpa strategi tersimpan',
- coinSource: 'Sumber Koin:',
- clearDynamicCoins: 'Kosongkan kolom simbol di bawah untuk menggunakan koin dinamis strategi',
- optionalCoinSource: 'Opsional - strategi sudah memiliki sumber koin',
- leavEmptyForStrategy: 'Kosongkan untuk menggunakan sumber koin strategi',
- clearToUseStrategy: 'Kosongkan untuk strategi',
- next: 'Lanjut',
- timeframes: 'Timeframe',
- back: 'Kembali',
- strategyStyle: 'Gaya Strategi',
- quickRange24h: '24j',
- quickRange3d: '3h',
- quickRange7d: '7h',
- quickRange30d: '30h',
- },
-
},
}
diff --git a/web/src/lib/api/backtest.ts b/web/src/lib/api/backtest.ts
deleted file mode 100644
index a5d28a60..00000000
--- a/web/src/lib/api/backtest.ts
+++ /dev/null
@@ -1,197 +0,0 @@
-import type {
- DecisionRecord,
- BacktestRunsResponse,
- BacktestStartConfig,
- BacktestStatusPayload,
- BacktestEquityPoint,
- BacktestTradeEvent,
- BacktestMetrics,
- BacktestRunMetadata,
- BacktestKlinesResponse,
-} from '../../types'
-import { API_BASE, getAuthHeaders, handleJSONResponse } from './helpers'
-
-export const backtestApi = {
- async getBacktestRuns(params?: {
- state?: string
- search?: string
- limit?: number
- offset?: number
- }): Promise {
- const query = new URLSearchParams()
- if (params?.state) query.set('state', params.state)
- if (params?.search) query.set('search', params.search)
- if (params?.limit) query.set('limit', String(params.limit))
- if (params?.offset) query.set('offset', String(params.offset))
- const res = await fetch(
- `${API_BASE}/backtest/runs${query.toString() ? `?${query}` : ''}`,
- {
- headers: getAuthHeaders(),
- }
- )
- return handleJSONResponse(res)
- },
-
- async startBacktest(config: BacktestStartConfig): Promise {
- const res = await fetch(`${API_BASE}/backtest/start`, {
- method: 'POST',
- headers: getAuthHeaders(),
- body: JSON.stringify({ config }),
- })
- return handleJSONResponse(res)
- },
-
- async pauseBacktest(runId: string): Promise {
- const res = await fetch(`${API_BASE}/backtest/pause`, {
- method: 'POST',
- headers: getAuthHeaders(),
- body: JSON.stringify({ run_id: runId }),
- })
- return handleJSONResponse(res)
- },
-
- async resumeBacktest(runId: string): Promise {
- const res = await fetch(`${API_BASE}/backtest/resume`, {
- method: 'POST',
- headers: getAuthHeaders(),
- body: JSON.stringify({ run_id: runId }),
- })
- return handleJSONResponse(res)
- },
-
- async stopBacktest(runId: string): Promise {
- const res = await fetch(`${API_BASE}/backtest/stop`, {
- method: 'POST',
- headers: getAuthHeaders(),
- body: JSON.stringify({ run_id: runId }),
- })
- return handleJSONResponse(res)
- },
-
- async updateBacktestLabel(
- runId: string,
- label: string
- ): Promise {
- const res = await fetch(`${API_BASE}/backtest/label`, {
- method: 'POST',
- headers: getAuthHeaders(),
- body: JSON.stringify({ run_id: runId, label }),
- })
- return handleJSONResponse(res)
- },
-
- async deleteBacktestRun(runId: string): Promise {
- const res = await fetch(`${API_BASE}/backtest/delete`, {
- method: 'POST',
- headers: getAuthHeaders(),
- body: JSON.stringify({ run_id: runId }),
- })
- if (!res.ok) {
- throw new Error(await res.text())
- }
- },
-
- async getBacktestStatus(runId: string): Promise {
- const res = await fetch(`${API_BASE}/backtest/status?run_id=${runId}`, {
- headers: getAuthHeaders(),
- })
- return handleJSONResponse(res)
- },
-
- async getBacktestEquity(
- runId: string,
- timeframe?: string,
- limit?: number
- ): Promise {
- const query = new URLSearchParams({ run_id: runId })
- if (timeframe) query.set('tf', timeframe)
- if (limit) query.set('limit', String(limit))
- const res = await fetch(`${API_BASE}/backtest/equity?${query}`, {
- headers: getAuthHeaders(),
- })
- return handleJSONResponse(res)
- },
-
- async getBacktestTrades(
- runId: string,
- limit = 200
- ): Promise {
- const query = new URLSearchParams({
- run_id: runId,
- limit: String(limit),
- })
- const res = await fetch(`${API_BASE}/backtest/trades?${query}`, {
- headers: getAuthHeaders(),
- })
- return handleJSONResponse(res)
- },
-
- async getBacktestMetrics(runId: string): Promise {
- const res = await fetch(`${API_BASE}/backtest/metrics?run_id=${runId}`, {
- headers: getAuthHeaders(),
- })
- return handleJSONResponse(res)
- },
-
- async getBacktestKlines(
- runId: string,
- symbol: string,
- timeframe?: string
- ): Promise {
- const query = new URLSearchParams({ run_id: runId, symbol })
- if (timeframe) query.set('timeframe', timeframe)
- const res = await fetch(`${API_BASE}/backtest/klines?${query}`, {
- headers: getAuthHeaders(),
- })
- return handleJSONResponse(res)
- },
-
- async getBacktestTrace(
- runId: string,
- cycle?: number
- ): Promise {
- const query = new URLSearchParams({ run_id: runId })
- if (cycle) query.set('cycle', String(cycle))
- const res = await fetch(`${API_BASE}/backtest/trace?${query}`, {
- headers: getAuthHeaders(),
- })
- return handleJSONResponse(res)
- },
-
- async getBacktestDecisions(
- runId: string,
- limit = 20,
- offset = 0
- ): Promise {
- const query = new URLSearchParams({
- run_id: runId,
- limit: String(limit),
- offset: String(offset),
- })
- const res = await fetch(`${API_BASE}/backtest/decisions?${query}`, {
- headers: getAuthHeaders(),
- })
- return handleJSONResponse(res)
- },
-
- async exportBacktest(runId: string): Promise {
- const res = await fetch(`${API_BASE}/backtest/export?run_id=${runId}`, {
- headers: getAuthHeaders(),
- })
- if (!res.ok) {
- const text = await res.text()
- try {
- const data = text ? JSON.parse(text) : null
- throw new Error(
- data?.error || data?.message || text || 'Export failed, please try again later'
- )
- } catch (err) {
- if (err instanceof Error && err.message) {
- throw err
- }
- throw new Error(text || 'Export failed, please try again later')
- }
- }
- return res.blob()
- },
-}
diff --git a/web/src/lib/api/index.ts b/web/src/lib/api/index.ts
index 615a56e2..5405b149 100644
--- a/web/src/lib/api/index.ts
+++ b/web/src/lib/api/index.ts
@@ -1,5 +1,4 @@
import { traderApi } from './traders'
-import { backtestApi } from './backtest'
import { strategyApi } from './strategies'
import { configApi } from './config'
import { dataApi } from './data'
@@ -7,7 +6,6 @@ import { telegramApi } from './telegram'
export const api = {
...traderApi,
- ...backtestApi,
...strategyApi,
...configApi,
...dataApi,
diff --git a/web/src/pages/LandingPage.tsx b/web/src/pages/LandingPage.tsx
index 634d8dbc..9e36e26c 100644
--- a/web/src/pages/LandingPage.tsx
+++ b/web/src/pages/LandingPage.tsx
@@ -41,7 +41,6 @@ export function LandingPage() {
'strategy-market': '/strategy-market',
'traders': '/traders',
'trader': '/dashboard',
- 'backtest': '/backtest',
'strategy': '/strategy',
'faq': '/faq',
}
diff --git a/web/src/types/backtest.ts b/web/src/types/backtest.ts
deleted file mode 100644
index b7164a12..00000000
--- a/web/src/types/backtest.ts
+++ /dev/null
@@ -1,167 +0,0 @@
-// Backtest types
-export interface BacktestRunSummary {
- symbol_count: number;
- decision_tf: string;
- processed_bars: number;
- progress_pct: number;
- equity_last: number;
- max_drawdown_pct: number;
- liquidated: boolean;
- liquidation_note?: string;
-}
-
-export interface BacktestRunMetadata {
- run_id: string;
- label?: string;
- user_id?: string;
- last_error?: string;
- version: number;
- state: string;
- created_at: string;
- updated_at: string;
- summary: BacktestRunSummary;
-}
-
-export interface BacktestRunsResponse {
- total: number;
- items: BacktestRunMetadata[];
-}
-
-// Position status for real-time display during backtest
-export interface BacktestPositionStatus {
- symbol: string;
- side: string;
- quantity: number;
- entry_price: number;
- mark_price: number;
- leverage: number;
- unrealized_pnl: number;
- unrealized_pnl_pct: number;
- margin_used: number;
-}
-
-export interface BacktestStatusPayload {
- run_id: string;
- state: string;
- progress_pct: number;
- processed_bars: number;
- current_time: number;
- decision_cycle: number;
- equity: number;
- unrealized_pnl: number;
- realized_pnl: number;
- positions?: BacktestPositionStatus[];
- note?: string;
- last_error?: string;
- last_updated_iso: string;
-}
-
-export interface BacktestEquityPoint {
- ts: number;
- equity: number;
- available: number;
- pnl: number;
- pnl_pct: number;
- dd_pct: number;
- cycle: number;
-}
-
-export interface BacktestTradeEvent {
- ts: number;
- symbol: string;
- action: string;
- side?: string;
- qty: number;
- price: number;
- fee: number;
- slippage: number;
- order_value: number;
- realized_pnl: number;
- leverage?: number;
- cycle: number;
- position_after: number;
- liquidation: boolean;
- note?: string;
-}
-
-export interface BacktestMetrics {
- total_return_pct: number;
- max_drawdown_pct: number;
- sharpe_ratio: number;
- profit_factor: number;
- win_rate: number;
- trades: number;
- avg_win: number;
- avg_loss: number;
- best_symbol: string;
- worst_symbol: string;
- liquidated: boolean;
- symbol_stats?: Record<
- string,
- {
- total_trades: number;
- winning_trades: number;
- losing_trades: number;
- total_pnl: number;
- avg_pnl: number;
- win_rate: number;
- }
- >;
-}
-
-export interface BacktestStartConfig {
- run_id?: string;
- ai_model_id?: string;
- strategy_id?: string; // Optional: use saved strategy from Strategy Studio
- symbols: string[];
- timeframes: string[];
- decision_timeframe: string;
- decision_cadence_nbars: number;
- start_ts: number;
- end_ts: number;
- initial_balance: number;
- fee_bps: number;
- slippage_bps: number;
- fill_policy: string;
- prompt_variant?: string;
- prompt_template?: string;
- custom_prompt?: string;
- override_prompt?: boolean;
- cache_ai?: boolean;
- replay_only?: boolean;
- checkpoint_interval_bars?: number;
- checkpoint_interval_seconds?: number;
- replay_decision_dir?: string;
- shared_ai_cache_path?: string;
- ai?: {
- provider?: string;
- model?: string;
- key?: string;
- secret_key?: string;
- base_url?: string;
- };
- leverage?: {
- btc_eth_leverage?: number;
- altcoin_leverage?: number;
- };
-}
-
-// Kline data for backtest chart
-export interface BacktestKline {
- time: number;
- open: number;
- high: number;
- low: number;
- close: number;
- volume: number;
-}
-
-export interface BacktestKlinesResponse {
- symbol: string;
- timeframe: string;
- start_ts: number;
- end_ts: number;
- count: number;
- klines: BacktestKline[];
- run_id: string;
-}
diff --git a/web/src/types/index.ts b/web/src/types/index.ts
index 4a9324c6..6e8218f0 100644
--- a/web/src/types/index.ts
+++ b/web/src/types/index.ts
@@ -1,4 +1,3 @@
export * from './trading'
-export * from './backtest'
export * from './strategy'
export * from './config'