추세검색 설정 추가

This commit is contained in:
Macbook
2026-05-27 01:36:06 +09:00
parent c1bcf88c6c
commit 2e08c6b16f
42 changed files with 1507 additions and 226 deletions
@@ -0,0 +1,122 @@
import { saveLiveStrategySettings } from './backendApi';
import { getVirtualTargetMaxCount } from '../hooks/useAppSettings';
import {
isVirtualTargetAddAllowed,
virtualTargetLimitMessage,
} from '../utils/virtualTargetLimits';
import { normalizeStartCandleType } from './strategyStartNodes';
import { syncVirtualTargetsToBackend } from './virtualLiveStrategySync';
import {
loadVirtualSession,
loadVirtualTargets,
notifyVirtualSessionChanged,
saveVirtualTargets,
type VirtualTargetItem,
} from './virtualTradingStorage';
export interface VirtualTargetMeta {
koreanName?: string;
englishName?: string;
/** 추세검색 캔들 주기 → 투자대상 평가 분봉 기본값 */
candleType?: string;
}
export interface AddVirtualTargetOptions {
/** false면 한도 초과 시 알림 없이 스킵 (자동추가용) */
showLimitAlert?: boolean;
}
/** 가상투자 투자대상 목록에 종목 추가 (localStorage + DB) */
export async function addVirtualTarget(
market: string,
meta: VirtualTargetMeta = {},
opts: AddVirtualTargetOptions = {},
): Promise<VirtualTargetItem[]> {
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 en = meta.englishName ?? market.replace(/^KRW-/, '');
const item: VirtualTargetItem = {
market,
strategyId: session.globalStrategyId,
koreanName: meta.koreanName,
englishName: en,
candleType: meta.candleType ? normalizeStartCandleType(meta.candleType) : undefined,
};
const next = [...targets, item];
saveVirtualTargets(next);
await saveLiveStrategySettings({
market,
strategyId: session.globalStrategyId,
isLiveCheck: session.running,
isPinned: false,
executionType: session.executionType,
positionMode: session.positionMode,
candleType: item.candleType ?? '1m',
skipWatchlistSync: true,
skipGlobalTemplate: true,
});
if (session.running && session.globalStrategyId) {
await syncVirtualTargetsToBackend(next, session, true);
}
notifyVirtualSessionChanged();
return next;
}
/** 가상투자 투자대상 목록에서 종목 제거 (localStorage + DB 비활성화) */
/** 투자대상 고정 상태 DB 저장 */
export async function persistVirtualTargetPinned(market: string, pinned: boolean): Promise<void> {
const session = loadVirtualSession();
const item = loadVirtualTargets().find(t => t.market === market);
await saveLiveStrategySettings({
market,
strategyId: item?.strategyId ?? session.globalStrategyId,
isLiveCheck: session.running,
isPinned: pinned,
executionType: session.executionType,
positionMode: session.positionMode,
skipWatchlistSync: true,
skipGlobalTemplate: true,
});
}
export async function removeVirtualTarget(market: string): Promise<VirtualTargetItem[]> {
const targets = loadVirtualTargets();
const item = targets.find(t => t.market === market);
if (!item) return targets;
if (item.pinned) 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;
}