/** * 분석레포트 — 거래 이력 타임라인 뷰 (목록 / 좌우 분할) */ import React from 'react'; import { LIVE_TIMELINE_KIND_LABEL, LIVE_TIMELINE_SIDE_LABEL, type LiveTimelineEvent, } from '../../utils/liveTradeTimeline'; import { formatSignalPrice } from '../../utils/tradeSignalDisplay'; import { resolveVirtualTargetNames } from '../../utils/virtualTargetNames'; import TimelineBarTimeBox, { eventToBarTimeSec } from './TimelineBarTimeBox'; import TimelineSignalCardShell from './TimelineSignalCardShell'; import type { Timeframe } from '../../types'; export type LiveTimelineViewMode = 'list' | 'split' | 'axis'; export const LIVE_TIMELINE_VIEW_OPTIONS: { id: LiveTimelineViewMode; label: string }[] = [ { id: 'list', label: '세로 목록' }, { id: 'split', label: '좌우 분할' }, { id: 'axis', label: '봉 시간축' }, ]; function kindClass(kind: LiveTimelineEvent['kind']): string { if (kind === 'SIGNAL') return 'signal'; if (kind === 'ORDER_PENDING') return 'pending'; return 'fill'; } function sideClass(side: LiveTimelineEvent['side']): 'buy' | 'sell' { return side === 'BUY' ? 'buy' : 'sell'; } interface EventBodyProps { ev: LiveTimelineEvent; compact?: boolean; } export const TimelineEventBody: React.FC = ({ ev, compact = false }) => { const { koreanName } = resolveVirtualTargetNames(ev.symbol); const kCls = kindClass(ev.kind); const sCls = sideClass(ev.side); return ( <>
{LIVE_TIMELINE_KIND_LABEL[ev.kind]} {LIVE_TIMELINE_SIDE_LABEL[ev.side]}
{koreanName || ev.symbol} {ev.symbol.replace(/^KRW-/, '')}
{ev.price > 0 && {formatSignalPrice(ev.price)}} {ev.quantity != null && ev.quantity > 0 && ( {ev.quantity.toLocaleString(undefined, { maximumFractionDigits: 8 })} )} {ev.strategyName && {ev.strategyName}} {ev.sourceLabel && {ev.sourceLabel}} {ev.detail && {ev.detail}}
); }; interface ListViewProps { events: LiveTimelineEvent[]; timeframe: Timeframe; onSignalDetail?: (ev: LiveTimelineEvent) => void; } export const TimelineListView: React.FC = ({ events, timeframe, onSignalDetail }) => (
{events.map(ev => { const kCls = kindClass(ev.kind); const sCls = sideClass(ev.side); const barTimeSec = eventToBarTimeSec(ev.ts, timeframe); return (
); })}
); interface SplitViewProps { events: LiveTimelineEvent[]; timeframe: Timeframe; onSignalDetail?: (ev: LiveTimelineEvent) => void; } export const TimelineSplitView: React.FC = ({ events, timeframe, onSignalDetail }) => (
{events.map(ev => { const isBuy = ev.side === 'BUY'; const kCls = kindClass(ev.kind); const sCls = sideClass(ev.side); const barTimeSec = eventToBarTimeSec(ev.ts, timeframe); const cardCls = [ 'arp-timeline-split-card', `arp-timeline-split-card--${sCls}`, `arp-timeline-split-card--${kCls}`, isBuy ? 'arp-timeline-split-card--left' : 'arp-timeline-split-card--right', ].join(' '); const card = ( ); return (
{isBuy ? (
{card}
) : null}
{!isBuy ? (
{card}
) : null}
); })}
);