import React, { useEffect, useMemo, useState } from 'react'; import { loadStrategies, loadLiveStrategySettings, type LiveStrategySettingsDto, type PaperSummaryDto, type StrategyDto, } from '../../utils/backendApi'; import { loadVirtualSession, type VirtualTargetItem } from '../../utils/virtualTradingStorage'; import { resolveVirtualTargetNames } from '../../utils/virtualTargetNames'; import type { TickerData } from '../../hooks/useMarketTicker'; import { fmtKrw } from '../TradeOrderPanel'; import PaperSplitPanel from './PaperSplitPanel'; interface Props { market: string; summary: PaperSummaryDto | null; /** 가상매매 투자대상 — 보유 목록은 이 종목만 표시 */ virtualTargets?: VirtualTargetItem[]; tickers?: Map; paperAutoTradeEnabled?: boolean; onMarketSelect?: (market: string) => void; } const PaperLeftStrategyTab: React.FC = ({ market, summary, virtualTargets = [], tickers, paperAutoTradeEnabled = false, onMarketSelect, }) => { const [strategies, setStrategies] = useState([]); const [settings, setSettings] = useState(null); const [virtualRunning, setVirtualRunning] = useState(() => loadVirtualSession().running); useEffect(() => { loadStrategies().then(setStrategies).catch(() => setStrategies([])); }, []); useEffect(() => { const refreshVirtual = () => setVirtualRunning(loadVirtualSession().running); window.addEventListener('gc_virtual_session_changed', refreshVirtual); window.addEventListener('storage', refreshVirtual); return () => { window.removeEventListener('gc_virtual_session_changed', refreshVirtual); window.removeEventListener('storage', refreshVirtual); }; }, []); useEffect(() => { let cancelled = false; loadLiveStrategySettings(market) .then(s => { if (!cancelled) setSettings(s); }) .catch(() => { if (!cancelled) { setSettings({ market, strategyId: null, isLiveCheck: false, executionType: 'CANDLE_CLOSE', positionMode: 'LONG_ONLY', }); } }); return () => { cancelled = true; }; }, [market, virtualRunning]); const selected = strategies.find(s => s.id === settings?.strategyId); const execLabel = settings?.executionType === 'REALTIME_TICK' ? '실시간 틱 (3초)' : '봉 마감 직후'; const top = ( <>
{summary ? fmtKrw(summary.totalAsset) : '—'} KRW
= 0 ? 'up' : 'down'}`}> {summary ? `${(summary.totalReturnPct ?? 0) >= 0 ? '+' : ''}${summary.totalReturnPct.toFixed(2)}%` : '—'}

실시간 전략·체크 대상 종목은 가상투자 화면에서 설정합니다. {virtualRunning ? ' (세션 실행 중)' : ''}

{settings?.isLiveCheck && selected && (

{market.replace(/^KRW-/, '')}: {selected.name} · {execLabel}

)}

{paperAutoTradeEnabled ? '자동매매 ON — 가상 Match 후 서버가 전략 시그널·모의 체결(화면 꺼져도 동작)' : '자동매매 OFF — 수동 주문만 모의 계좌에 체결'}

); const targetMarkets = useMemo( () => new Set(virtualTargets.map(t => t.market)), [virtualTargets], ); const positions = useMemo(() => { const all = (summary?.positions ?? []).filter(p => p.quantity > 0); if (targetMarkets.size === 0) return []; const order = virtualTargets.map(t => t.market); return all .filter(p => targetMarkets.has(p.symbol)) .sort((a, b) => { const ia = order.indexOf(a.symbol); const ib = order.indexOf(b.symbol); return (ia < 0 ? 999 : ia) - (ib < 0 ? 999 : ib); }); }, [summary?.positions, targetMarkets, virtualTargets]); const bottom = (
보유 목록 {positions.length}종목
{targetMarkets.size === 0 ? (

가상매매 투자대상이 없습니다. 가상투자에서 종목을 추가하세요.

) : positions.length === 0 ? (

투자대상 종목 중 보유 수량이 있는 종목이 없습니다.

) : (
{positions.map(p => { const ticker = tickers?.get(p.symbol); const vt = virtualTargets.find(t => t.market === p.symbol); const { koreanName: ko, englishName: en } = resolveVirtualTargetNames( p.symbol, p.koreanName ?? vt?.koreanName ?? ticker?.koreanName, vt?.englishName, ); const up = (p.profitLoss ?? 0) >= 0; const selected = market === p.symbol; const retPct = p.profitLossPct; return (
onMarketSelect?.(p.symbol)} onKeyDown={e => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onMarketSelect?.(p.symbol); } }} >
{ko} {en}
{p.profitLoss != null && (
{up ? '+' : ''}{fmtKrw(p.profitLoss)}
)}
보유수량 {p.quantity.toFixed(4)}
평단가 {fmtKrw(p.avgPrice)}
{p.evalAmount != null && (
평가금액 {fmtKrw(p.evalAmount)}
)} {retPct != null && (
수익률 = 0 ? 'vtd-target-up' : 'vtd-target-dn'}`}> {retPct >= 0 ? '+' : ''}{retPct.toFixed(2)}%
)}
); })}
)}
); return ( ); }; export default PaperLeftStrategyTab;