전략평가 전략목록 수정

This commit is contained in:
Macbook
2026-06-24 01:51:17 +09:00
parent 1ed6a11b36
commit 80cf8daf55
2 changed files with 33 additions and 11 deletions
@@ -94,6 +94,7 @@ import {
} from '../utils/strategyTemplateList'; } from '../utils/strategyTemplateList';
import type { TemplatePaletteRow } from './strategyEditor/TemplatePaletteTab'; import type { TemplatePaletteRow } from './strategyEditor/TemplatePaletteTab';
import { buildStrategyEditorDefFromSettings } from '../utils/strategyEditorShared'; import { buildStrategyEditorDefFromSettings } from '../utils/strategyEditorShared';
import { isEvaluationTemplateStrategy } from '../utils/strategyEvaluationTemplateApply';
const LEFT_KEY = 'seval-left-width'; const LEFT_KEY = 'seval-left-width';
const LEFT_OPEN_KEY = 'seval-left-open'; const LEFT_OPEN_KEY = 'seval-left-open';
@@ -261,6 +262,21 @@ function StrategyEvaluationPageInner({ theme = 'dark' }: Props) {
[templateRows, templateSearch], [templateRows, templateSearch],
); );
/** 전략 탭·일괄평가용 — `[템플릿]` 접두 전략 제외 (템플릿 탭 전용) */
const listStrategies = useMemo(
() => strategies.filter(s => !isEvaluationTemplateStrategy(s.name)),
[strategies],
);
/** 차트 전략 선택 — 목록 제외, 현재 선택된 템플릿 평가 전략만 예외 표시 */
const chartStrategies = useMemo(() => {
if (!selectedStrategy?.id || !isEvaluationTemplateStrategy(selectedStrategy.name)) {
return listStrategies;
}
if (listStrategies.some(s => s.id === selectedStrategy.id)) return listStrategies;
return [selectedStrategy, ...listStrategies];
}, [listStrategies, selectedStrategy]);
const [leftWidth, setLeftWidth] = useState(() => readMinWidth(LEFT_KEY, LEFT_DEFAULT)); const [leftWidth, setLeftWidth] = useState(() => readMinWidth(LEFT_KEY, LEFT_DEFAULT));
const [leftOpen, setLeftOpen] = useState(() => readStoredBool(LEFT_OPEN_KEY, true)); const [leftOpen, setLeftOpen] = useState(() => readStoredBool(LEFT_OPEN_KEY, true));
const [rightWidth, setRightWidth] = useState(() => readMinWidth(RIGHT_KEY, RIGHT_DEFAULT)); const [rightWidth, setRightWidth] = useState(() => readMinWidth(RIGHT_KEY, RIGHT_DEFAULT));
@@ -443,12 +459,12 @@ function StrategyEvaluationPageInner({ theme = 'dark' }: Props) {
const filteredStrategies = useMemo(() => { const filteredStrategies = useMemo(() => {
const q = strategySearch.trim().toLowerCase(); const q = strategySearch.trim().toLowerCase();
if (!q) return strategies; if (!q) return listStrategies;
return strategies.filter(s => return listStrategies.filter(s =>
(s.name ?? '').toLowerCase().includes(q) (s.name ?? '').toLowerCase().includes(q)
|| String(s.id ?? '').includes(q), || String(s.id ?? '').includes(q),
); );
}, [strategies, strategySearch]); }, [listStrategies, strategySearch]);
const sortedFilteredStrategies = useMemo( const sortedFilteredStrategies = useMemo(
() => sortStrategies(filteredStrategies, strategyListSort), () => sortStrategies(filteredStrategies, strategyListSort),
@@ -533,7 +549,7 @@ function StrategyEvaluationPageInner({ theme = 'dark' }: Props) {
]); ]);
const bulkEval = useStrategyBulkEvaluation({ const bulkEval = useStrategyBulkEvaluation({
strategies, strategies: listStrategies,
market, market,
timeframe: chartTimeframe, timeframe: chartTimeframe,
bars, bars,
@@ -547,7 +563,7 @@ function StrategyEvaluationPageInner({ theme = 'dark' }: Props) {
const autoBulkEvalTriggerKey = useMemo(() => { const autoBulkEvalTriggerKey = useMemo(() => {
const lastBarTime = bars[bars.length - 1]?.time ?? 0; const lastBarTime = bars[bars.length - 1]?.time ?? 0;
const strategyIds = strategies.map(s => s.id).filter(Boolean).join(','); const strategyIds = listStrategies.map(s => s.id).filter(Boolean).join(',');
return [ return [
market, market,
chartTimeframe, chartTimeframe,
@@ -567,7 +583,7 @@ function StrategyEvaluationPageInner({ theme = 'dark' }: Props) {
evalWindowMeta.evaluationBarCount, evalWindowMeta.evaluationBarCount,
bars, bars,
paramsRevision, paramsRevision,
strategies, listStrategies,
]); ]);
const lastAutoBulkEvalKeyRef = useRef<string | null>(null); const lastAutoBulkEvalKeyRef = useRef<string | null>(null);
@@ -581,7 +597,7 @@ function StrategyEvaluationPageInner({ theme = 'dark' }: Props) {
useEffect(() => { useEffect(() => {
const waitingForSelectedSignals = selectedStrategyId != null && signalScanRunning; const waitingForSelectedSignals = selectedStrategyId != null && signalScanRunning;
if (chartLoading || waitingForSelectedSignals) return; if (chartLoading || waitingForSelectedSignals) return;
if (bars.length < 10 || !strategies.some(s => s.id != null)) return; if (bars.length < 10 || !listStrategies.some(s => s.id != null)) return;
const key = autoBulkEvalTriggerKey; const key = autoBulkEvalTriggerKey;
if (lastAutoBulkEvalKeyRef.current === key) return; if (lastAutoBulkEvalKeyRef.current === key) return;
@@ -594,7 +610,7 @@ function StrategyEvaluationPageInner({ theme = 'dark' }: Props) {
signalScanRunning, signalScanRunning,
selectedStrategyId, selectedStrategyId,
bars.length, bars.length,
strategies, listStrategies,
bulkEval.startBulkEvaluation, bulkEval.startBulkEvaluation,
]); ]);
@@ -1128,7 +1144,7 @@ function StrategyEvaluationPageInner({ theme = 'dark' }: Props) {
timeframe={chartTimeframe} timeframe={chartTimeframe}
timeframeChoice={timeframeChoice} timeframeChoice={timeframeChoice}
tfOptions={tfOptions} tfOptions={tfOptions}
strategies={strategies} strategies={chartStrategies}
selectedStrategyId={selectedStrategyId} selectedStrategyId={selectedStrategyId}
onStrategySelect={handleSelectStrategyById} onStrategySelect={handleSelectStrategyById}
onMarketChange={setMarket} onMarketChange={setMarket}
@@ -44,6 +44,12 @@ import {
type StrategyFlowLayoutStore, type StrategyFlowLayoutStore,
} from './strategyEditorLayoutStorage'; } from './strategyEditorLayoutStorage';
export const EVALUATION_TEMPLATE_STRATEGY_PREFIX = '[템플릿]';
export function isEvaluationTemplateStrategy(name: string | null | undefined): boolean {
return (name ?? '').trimStart().startsWith(EVALUATION_TEMPLATE_STRATEGY_PREFIX);
}
export type TemplateEvaluationPayload = { export type TemplateEvaluationPayload = {
buy: EditorConditionState; buy: EditorConditionState;
sell: EditorConditionState; sell: EditorConditionState;
@@ -118,7 +124,7 @@ function payloadForSide(
sellOrphans, sellOrphans,
flowLayout: buildStrategyFlowLayoutStore(buyLayout, sellLayout, buyOrphans, sellOrphans), flowLayout: buildStrategyFlowLayoutStore(buyLayout, sellLayout, buyOrphans, sellOrphans),
preferredTab: signal, preferredTab: signal,
strategyName: `[템플릿] ${row.label}`, strategyName: `${EVALUATION_TEMPLATE_STRATEGY_PREFIX} ${row.label}`,
strategyDescription: row.description?.trim() || `전략 템플릿: ${row.label}`, strategyDescription: row.description?.trim() || `전략 템플릿: ${row.label}`,
}; };
} }
@@ -168,7 +174,7 @@ export function suggestTemplateEvaluationStrategyName(
baseLabel: string, baseLabel: string,
existingNames: ReadonlySet<string>, existingNames: ReadonlySet<string>,
): string { ): string {
const base = `[템플릿] ${baseLabel.trim() || '새 전략'}`; const base = `${EVALUATION_TEMPLATE_STRATEGY_PREFIX} ${baseLabel.trim() || '새 전략'}`;
if (!existingNames.has(base)) return base; if (!existingNames.has(base)) return base;
let n = 2; let n = 2;
while (existingNames.has(`${base} (${n})`)) n += 1; while (existingNames.has(`${base} (${n})`)) n += 1;