import { loadStrategies, saveLiveStrategySettings } from './backendApi'; import { getVirtualTargetMaxCount } from '../hooks/useAppSettings'; import { isVirtualTargetAddAllowed, virtualTargetLimitMessage, } from '../utils/virtualTargetLimits'; import { syncVirtualTargetsToBackend } from './virtualLiveStrategySync'; import { ensureDefaultGlobalStrategyId, resolveVirtualTargetStrategyId, } from './virtualTargetStrategy'; import { loadVirtualSession, loadVirtualTargets, notifyVirtualSessionChanged, saveVirtualTargets, type VirtualTargetItem, } from './virtualTradingStorage'; export interface VirtualTargetMeta { koreanName?: string; englishName?: string; } export interface AddVirtualTargetOptions { /** false면 한도 초과 시 알림 없이 스킵 (자동추가용) */ showLimitAlert?: boolean; } /** 가상투자 투자대상 목록에 종목 추가 (localStorage + DB) */ export async function addVirtualTarget( market: string, meta: VirtualTargetMeta = {}, opts: AddVirtualTargetOptions = {}, ): Promise { const showLimitAlert = opts.showLimitAlert !== false; const targets = loadVirtualTargets(); if (targets.some(t => t.market === market)) return targets; const maxCount = getVirtualTargetMaxCount(); if (!isVirtualTargetAddAllowed(targets.length, maxCount)) { if (showLimitAlert) window.alert(virtualTargetLimitMessage(maxCount)); return targets; } const session = loadVirtualSession(); const strategies = await loadStrategies().catch(() => []); const strategyId = ensureDefaultGlobalStrategyId(session.globalStrategyId, strategies); const en = meta.englishName ?? market.replace(/^KRW-/, ''); const item: VirtualTargetItem = { market, /** 신규 추가 시점의 상단 기본전략(템플릿)을 종목에 고정 */ strategyId, koreanName: meta.koreanName, englishName: en, }; const next = [...targets, item]; saveVirtualTargets(next); await saveLiveStrategySettings({ market, strategyId, isLiveCheck: session.running, isPinned: false, executionType: session.executionType, positionMode: session.positionMode, skipWatchlistSync: true, skipGlobalTemplate: true, }); if (session.running && strategyId) { await syncVirtualTargetsToBackend(next, session, true); } notifyVirtualSessionChanged(); return next; } /** 가상투자 투자대상 목록에서 종목 제거 (localStorage + DB 비활성화) */ /** 투자대상 고정 상태 DB 저장 */ export async function persistVirtualTargetPinned(market: string, pinned: boolean): Promise { const session = loadVirtualSession(); const item = loadVirtualTargets().find(t => t.market === market); await saveLiveStrategySettings({ market, strategyId: item ? resolveVirtualTargetStrategyId(item, session.globalStrategyId) : session.globalStrategyId, isLiveCheck: session.running, isPinned: pinned, executionType: session.executionType, positionMode: session.positionMode, skipWatchlistSync: true, skipGlobalTemplate: true, }); } export async function removeVirtualTarget(market: string): Promise { const targets = loadVirtualTargets(); const item = targets.find(t => t.market === market); if (!item) return targets; const session = loadVirtualSession(); const next = targets.filter(t => t.market !== market); saveVirtualTargets(next); await saveLiveStrategySettings({ market, strategyId: session.globalStrategyId, isLiveCheck: false, isPinned: false, executionType: session.executionType, positionMode: session.positionMode, skipWatchlistSync: true, skipGlobalTemplate: true, }); if (session.running) { await syncVirtualTargetsToBackend(next, session, true); } notifyVirtualSessionChanged(); return next; }