/** * LiveStrategyPanel * * 실시간 전략 체크 — 전역 설정 UI. * 관심종목(★) 등록 종목 전체가 체크 대상이며, 여기서 전략·실행 방식을 지정합니다. */ import React, { useState, useCallback } from 'react'; import { createPortal } from 'react-dom'; import type { Theme } from '../types'; import { useDraggablePanel } from '../hooks/useDraggablePanel'; import { repairStrategyTimeframes, saveLiveStrategySettings, type LiveStrategySettingsDto, } from '../utils/backendApi'; import { syncStrategyTimeframesFromLayoutIfNeeded, warnStrategyTimeframeMismatch, } from '../utils/strategyTimeframeSync'; import { getKoreanName } from '../utils/marketNameCache'; interface Strategy { id: number; name: string; } interface LiveStrategyPanelProps { theme: Theme; market: string; strategies: Strategy[]; settings: LiveStrategySettingsDto; watchlistCount?: number; monitoredMarkets?: string[]; /** 가상투자 세션에서 제어 — 차트 팝업은 읽기 전용 */ virtualDriven?: boolean; onClearMarkers?: () => void; onClose?: () => void; onSettingsChange?: (settings: LiveStrategySettingsDto) => void; } const ACCENT = '#3f7ef5'; const ACCENT_GLOW = 'rgba(63,126,245,0.22)'; const CandleIcon: React.FC<{ color: string }> = ({ color }) => ( ); const LiveStrategyPanel: React.FC = ({ theme, market, strategies, settings, watchlistCount = 0, monitoredMarkets = [], virtualDriven = false, onClearMarkers, onClose, onSettingsChange, }) => { const [saving, setSaving] = useState(false); const { panelRef, dragging, onHeaderPointerDown, headerTouchStyle, panelStyle, headerCursor, } = useDraggablePanel({ centerOnMount: true, initialPosition: { x: 40, y: 40 }, }); const persist = useCallback(async (patch: Partial) => { if (virtualDriven) return; const next: LiveStrategySettingsDto = { ...settings, ...patch, market }; const prev = settings; if (next.isLiveCheck && next.strategyId != null) { const synced = await syncStrategyTimeframesFromLayoutIfNeeded(next.strategyId); if (!synced) return; const ok = await warnStrategyTimeframeMismatch(next.strategyId); if (!ok) return; try { await repairStrategyTimeframes(next.strategyId); } catch { /* repair optional */ } } onSettingsChange?.(next); setSaving(true); try { const saved = await saveLiveStrategySettings(next); if (saved) { onSettingsChange?.(saved); onClearMarkers?.(); } else { onSettingsChange?.(prev); window.alert('실시간 전략 설정 저장에 실패했습니다. 네트워크·서버 상태를 확인해 주세요.'); } } catch { onSettingsChange?.(prev); window.alert('실시간 전략 설정 저장 중 오류가 발생했습니다.'); } finally { setSaving(false); } }, [settings, market, onClearMarkers, onSettingsChange, virtualDriven]); const isOn = settings.isLiveCheck; const execType = settings.executionType; const stratId = settings.strategyId; const monCount = monitoredMarkets.length; const selectedStrategy = strategies.find(s => s.id === stratId); const strategyTimeframes = settings.strategyCandleTypes?.length ? settings.strategyCandleTypes.join(', ') : '1m'; const execLabel = execType === 'REALTIME_TICK' ? '실시간 틱 (3초)' : '봉 마감 직후'; const posModeLabel = (settings.positionMode ?? 'LONG_ONLY') === 'LONG_ONLY' ? '보유 자산 기준' : '순수 지표 기준'; const panel = (
e.stopPropagation()} >
LIVE STRATEGY CHECK (실시간 전략 체크)
{saving && 저장 중…} {onClose && ( )}
관심 종목 {watchlistCount}개
체크 대상 0 ? ' lsp-summary-value--active' : ''}`}> {monCount}개
{isOn && stratId && watchlistCount > 0 && (

★ 관심종목으로 등록한 종목이 자동으로 전략 체크 대상입니다.

)}
{monCount > 0 && (
모니터링 {monitoredMarkets.slice(0, 8).map(m => ( {getKoreanName(m)} ))} {monCount > 8 && +{monCount - 8}}
{isOn && selectedStrategy && ( <>
적용 전략 {selectedStrategy.name}
평가 분봉 {strategyTimeframes}
)}
)}
실시간 체크
전략 선택
실행 방식
시그널 모드
{isOn && !stratId && (

전략을 선택하면 관심 {watchlistCount}개 종목에 적용됩니다.

)} {isOn && (
{stratId ? `${selectedStrategy?.name ?? '전략'} · ${strategyTimeframes} · ${execLabel} · ${posModeLabel} · ${monCount}종목` : '전략을 선택하세요'}
)}

※ 실시간 체크 ON 시 관심종목(★) 전체에 선택한 전략이 적용됩니다.

); if (typeof document === 'undefined') return null; return createPortal(panel, document.body); }; export default LiveStrategyPanel;