전략평가 로직 오류 수정

This commit is contained in:
Macbook
2026-06-17 01:00:16 +09:00
parent b168498421
commit f1ce499809
7 changed files with 463 additions and 66 deletions
@@ -87,6 +87,10 @@ interface MarketSearchPanelProps {
marketInfos?: HookMarketInfo[];
marketLoading?: boolean;
refreshAllTickers?: () => Promise<void>;
/** 내부 훅 사용 시 전 종목 REST·폴링 (기본 true). 전략평가 검색은 false 권장 */
tickerLoadFull?: boolean;
/** 내부 훅 우선 종목 (전략평가 차트 종목 등) */
tickerPriorityMarkets?: string[];
}
export const MarketSearchPanel: React.FC<MarketSearchPanelProps> = ({
@@ -94,7 +98,7 @@ export const MarketSearchPanel: React.FC<MarketSearchPanelProps> = ({
variant = 'overlay', query: controlledQuery, onQueryChange,
actionMode = 'favorite', onAdd, onRemove, addedMarkets, maxTargetsReached = false,
tickers: tickersProp, marketInfos: marketInfosProp, marketLoading: marketLoadingProp,
refreshAllTickers: refreshProp,
refreshAllTickers: refreshProp, tickerLoadFull = true, tickerPriorityMarkets,
}) => {
const embedded = variant === 'embedded';
const isCompact = useIsCompactDevice();
@@ -102,7 +106,8 @@ export const MarketSearchPanel: React.FC<MarketSearchPanelProps> = ({
const internalFeed = useMarketTicker({
enabled: !useExternalFeed,
loadFull: true,
loadFull: tickerLoadFull,
priorityMarkets: tickerPriorityMarkets ?? (currentMarket ? [currentMarket] : undefined),
});
const tickers = tickersProp ?? internalFeed.tickers;
@@ -31,6 +31,7 @@ import SePanelCollapseHandle from './strategyEditor/SePanelCollapseHandle';
import { readStoredSize, readStoredBool, storeBool, storeSize, usePanelResize } from './strategyEditor/usePanelResize';
import { getKoreanName } from '../utils/marketNameCache';
import { useIndicatorSettings } from '../hooks/useIndicatorSettings';
import { useMarketTicker } from '../hooks/useMarketTicker';
import {
mergeEvalGetParams,
buildEvalParamsFromStrategy,
@@ -69,6 +70,7 @@ interface Props {
export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
const { getParams: baseGetParams, getVisualConfig } = useIndicatorSettings();
const [leftTab, setLeftTab] = useState<LeftTab>('strategy');
const [marketSearchOpen, setMarketSearchOpen] = useState(false);
const [strategies, setStrategies] = useState<StrategyDto[]>([]);
const [strategySearch, setStrategySearch] = useState('');
const [selectedStrategyId, setSelectedStrategyId] = useState<number | null>(null);
@@ -100,6 +102,15 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
[baseGetParams, appliedIndicatorParams],
);
/** 종목 탭·종목검색 전용 ticker — 차트 OHLCV·scan-signals 와 분리 (upbitWsBroker ticker 채널) */
const marketTabActive = leftOpen && leftTab === 'market';
const marketTickerOpts = useMemo(() => ({
enabled: marketTabActive || marketSearchOpen,
loadFull: marketTabActive,
priorityMarkets: [market],
}), [marketTabActive, marketSearchOpen, market]);
const marketFeed = useMarketTicker(marketTickerOpts);
const [leftWidth, setLeftWidth] = useState(() => readMinWidth(LEFT_KEY, LEFT_DEFAULT));
const [leftOpen, setLeftOpen] = useState(() => readStoredBool(LEFT_OPEN_KEY, true));
const [rightWidth, setRightWidth] = useState(() => readMinWidth(RIGHT_KEY, RIGHT_DEFAULT));
@@ -543,7 +554,11 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
<StrategyEvaluationMarketTab
activeMarket={market}
onSelectMarket={setMarket}
enabled={leftOpen && leftTab === 'market'}
tickers={marketFeed.tickers}
marketInfos={marketFeed.marketInfos}
loading={marketFeed.loading}
usdRate={marketFeed.usdRate}
refreshAllTickers={marketFeed.refreshAllTickers}
/>
) : (
<StrategyEvaluationSettingsTab
@@ -602,6 +617,11 @@ export default function StrategyEvaluationPage({ theme = 'dark' }: Props) {
onOpenReport={() => { void handleOpenReport(); }}
reportDisabled={reportDisabled}
reportLoading={reportLoading}
marketTickers={marketFeed.tickers}
marketInfos={marketFeed.marketInfos}
marketLoading={marketFeed.loading}
refreshMarketTickers={marketFeed.refreshAllTickers}
onMarketSearchOpenChange={setMarketSearchOpen}
/>
<StrategyEvaluationConditionPanel
strategy={selectedStrategy}
@@ -15,6 +15,7 @@ import { DEFAULT_DISPLAY_TIMEZONE } from '../../utils/timezone';
import { getKoreanName } from '../../utils/marketNameCache';
import type { BacktestSignal, StrategyDto } from '../../utils/backendApi';
import { fetchLiveConditionScanSignals } from '../../utils/backendApi';
import type { MarketInfo, TickerData } from '../../hooks/useMarketTicker';
import {
BACKTEST_DISPLAY_BAR_COUNT,
fetchBacktestCandleBundle,
@@ -73,6 +74,12 @@ interface Props {
onOpenReport?: () => void;
reportDisabled?: boolean;
reportLoading?: boolean;
/** StrategyEvaluationPage 공유 ticker (종목 탭·검색과 단일 useMarketTicker) */
marketTickers?: Map<string, TickerData>;
marketInfos?: MarketInfo[];
marketLoading?: boolean;
refreshMarketTickers?: () => Promise<void>;
onMarketSearchOpenChange?: (open: boolean) => void;
}
const isOverlayType = (type: string) => getIndicatorDef(type)?.overlay === true;
@@ -109,6 +116,11 @@ const StrategyEvaluationChart: React.FC<Props> = ({
onOpenReport,
reportDisabled = false,
reportLoading = false,
marketTickers,
marketInfos,
marketLoading,
refreshMarketTickers,
onMarketSearchOpenChange,
}) => {
const { getParams: baseGetParams, getVisualConfig: baseGetVisual, settingsRevision } = useIndicatorSettings();
const getParams = getParamsOverride ?? baseGetParams;
@@ -405,6 +417,10 @@ const StrategyEvaluationChart: React.FC<Props> = ({
return () => window.removeEventListener('keydown', onKeyDown);
}, [bars.length, selectedBarIndex, stepBar]);
useEffect(() => {
onMarketSearchOpenChange?.(showMarketSearch);
}, [showMarketSearch, onMarketSearchOpenChange]);
const selectedBar = bars[selectedBarIndex] ?? null;
return (
@@ -611,6 +627,10 @@ const StrategyEvaluationChart: React.FC<Props> = ({
currentMarket={market}
query={marketQuery}
onQueryChange={setMarketQuery}
tickers={marketTickers}
marketInfos={marketInfos}
marketLoading={marketLoading}
refreshAllTickers={refreshMarketTickers}
onSelect={m => {
onMarketChange(m);
setShowMarketSearch(false);
@@ -1,34 +1,28 @@
import React, { useMemo } from 'react';
import React from 'react';
import MarketPanel from '../MarketPanel';
import { useMarketTicker } from '../../hooks/useMarketTicker';
import type { MarketInfo, TickerData } from '../../hooks/useMarketTicker';
interface Props {
activeMarket: string;
onSelectMarket: (market: string) => void;
/** 종목 탭 표시 중 — 전 종목 시세 로드 */
enabled: boolean;
/** StrategyEvaluationPage 공유 useMarketTicker — 차트·시그널과 분리된 ticker 전용 */
tickers: Map<string, TickerData>;
marketInfos: MarketInfo[];
loading: boolean;
usdRate: number;
refreshAllTickers?: () => Promise<void>;
}
/** 전략평가 좌측 — 실시간 차트 MarketPanel 과 동일 종목 목록 */
/** 전략평가 좌측 — 실시간 차트 MarketPanel 과 동일 종목 목록 (현재가 WS/REST만, OHLCV·시그널 무관) */
const StrategyEvaluationMarketTab: React.FC<Props> = ({
activeMarket,
onSelectMarket,
enabled,
tickers,
marketInfos,
loading,
usdRate,
refreshAllTickers,
}) => {
const tickerOpts = useMemo(() => ({
enabled,
loadFull: enabled,
priorityMarkets: [activeMarket],
}), [enabled, activeMarket]);
const {
tickers,
marketInfos,
loading,
usdRate,
refreshAllTickers,
} = useMarketTicker(tickerOpts);
return (
<div className="seval-market-tab">
<MarketPanel