// frontend/src/components/SameDayReplayPanel.tsx import { useState, useEffect } from 'react'; import { StockChart } from './StockChart'; import { LedgerTable, type LedgerItem } from './LedgerTable'; import { API_BASE } from '../config'; interface SameDayReplayPanelProps { watchlist: string[]; activeTicker: string; onSelectTicker: (ticker: string) => void; } export function SameDayReplayPanel({ watchlist, activeTicker, onSelectTicker }: SameDayReplayPanelProps) { const [availableDates, setAvailableDates] = useState([]); const [selectedDate, setSelectedDate] = useState(''); const [loading, setLoading] = useState(false); const [replayData, setReplayData] = useState(null); const [focusTime, setFocusTime] = useState(undefined); // Fetch available history dates for active ticker useEffect(() => { const fetchDates = async () => { try { const res = await fetch(`${API_BASE}/api/replay/available_dates?ticker=${activeTicker}`); const json = await res.json(); if (json.success && json.dates.length > 0) { setAvailableDates(json.dates); setSelectedDate(json.dates[0]); // default to latest date } } catch (e) { console.error("Failed to fetch available replay dates:", e); } }; fetchDates(); }, [activeTicker]); // Load replay data when ticker or selectedDate changes useEffect(() => { if (!selectedDate) return; const loadDayData = async () => { setLoading(true); setReplayData(null); try { const params = new URLSearchParams({ ticker: activeTicker.toUpperCase(), date: selectedDate, strategy_mode: 'dynamic', stop_loss_pct: '0.015', profit_target_pct: '0.030', trailing_stop_mode: 'atr', trailing_stop_atr_mult: '2.0', rsi_threshold_buy: '65', risk_per_trade_pct: '0.01', max_position_size_pct: '0.50', commission_per_share: '0.005', slippage_rate: '0.0003', market_open_focus: 'true' }); const res = await fetch(`${API_BASE}/api/replay/data?${params.toString()}`); const json = await res.json(); if (json.success) { setReplayData(json); } } catch (e) { console.error("Failed to load same-day replay data:", e); } finally { setLoading(false); } }; loadDayData(); }, [activeTicker, selectedDate]); const ledger: LedgerItem[] = replayData ? replayData.ledger : []; const candles = replayData ? replayData.candles : []; const markers = replayData ? replayData.markers : []; // Calculate day summary metrics const calculateDayMetrics = () => { if (!ledger || ledger.length === 0) { return { pnl: 0, pnlPct: 0, trades: 0, winRate: 0, commission: 0 }; } let totalPnl = 0; let wins = 0; let roundTrips = 0; let totalComm = 0; for (const item of ledger) { totalComm += item.commission || 0; if (item.action === 'SELL' && item.realized_pnl !== undefined) { roundTrips += 1; totalPnl += item.realized_pnl; if (item.realized_pnl > 0) wins += 1; } } const winRate = roundTrips > 0 ? (wins / roundTrips) * 100 : 0; const pnlPct = (totalPnl / 100000) * 100; return { pnl: totalPnl, pnlPct, trades: roundTrips, winRate, commission: totalComm }; }; const metrics = calculateDayMetrics(); const isProfit = metrics.pnl >= 0; const handleRowClick = (item: LedgerItem) => { const t = Math.floor(new Date(item.timestamp).getTime() / 1000); setFocusTime(t); }; return (
{/* 顶部极简过滤条:股票切换器 + 日期选择器 */}
{/* 快捷股票选择按钮组 */}
标的股票: {watchlist.map((ticker) => ( ))}
{/* 交易日期选择 */}
复盘日期:
{/* 极简日内盈亏卡片 */}
当日结算净盈亏 {isProfit ? '+' : ''}${metrics.pnl.toFixed(2)} ({isProfit ? '+' : ''}{metrics.pnlPct.toFixed(2)}%)
成交笔数 (Round Trips) {metrics.trades} 笔
日内胜率 (Win Rate) {metrics.winRate.toFixed(1)}%
交易摩擦损耗 (Commission) ${metrics.commission.toFixed(2)}
{/* 图表展示卡片 */}

📈 {activeTicker} 当日 K 线图 & 买卖标记透视 ({selectedDate})

🟢 箭头为买入点 (BUY) | 🔴 箭头为卖出点 (SELL)
{loading ? (
正在加载 {activeTicker} ({selectedDate}) 同天行情与买卖标记...
) : ( )}
{/* 当日买卖流水账本 */}

📜 当日买卖明细账本 (点击可定位图表标记)

); }