From 8cba6a612afe33f70b9fe1200f886f23c79c2eef Mon Sep 17 00:00:00 2001 From: Macbook Date: Wed, 3 Jun 2026 02:16:48 +0900 Subject: [PATCH] =?UTF-8?q?=EB=B3=B4=EC=A1=B0=EC=A7=80=ED=91=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=ED=8C=9D=EC=97=85=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EC=A7=80=ED=91=9C=20=EB=93=9C=EB=9E=98=EA=B7=B8=20=EC=9D=B4?= =?UTF-8?q?=EB=8F=99=EB=AC=B8=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/BacktestHistoryPage.tsx | 37 +++++++++++--- frontend/src/components/Toolbar.tsx | 33 ++++++++++--- .../backtest/BacktestExecutionList.tsx | 18 +++++-- frontend/src/styles/backtestDashboard.css | 49 ++++++++++++++++--- .../src/utils/indicatorCustomTabsStorage.ts | 2 +- 5 files changed, 114 insertions(+), 25 deletions(-) diff --git a/frontend/src/components/BacktestHistoryPage.tsx b/frontend/src/components/BacktestHistoryPage.tsx index 1cbdb52..2cbeb6d 100644 --- a/frontend/src/components/BacktestHistoryPage.tsx +++ b/frontend/src/components/BacktestHistoryPage.tsx @@ -27,6 +27,11 @@ import { usePanelResize, } from './strategyEditor/usePanelResize'; import '../styles/strategyEditorTheme.css'; +import { getKoreanName } from '../utils/marketNameCache'; + +function toUpbitMarket(symbol: string): string { + return symbol.startsWith('KRW-') ? symbol : `KRW-${symbol}`; +} const LEFT_KEY = 'btd-left-width'; const RIGHT_KEY = 'btd-right-width'; @@ -188,11 +193,25 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) { return null; }, [tab, selectedBacktest, selectedLive, backtestDetail]); - const centerTitle = tab === 'backtest' && selectedBacktest - ? `${selectedBacktest.strategyName || '전략'} · ${selectedBacktest.symbol.replace(/^KRW-/, '')} · ${selectedBacktest.timeframe}` - : tab === 'live' && selectedLive - ? `${selectedLive.strategyLabel} · ${selectedLive.symbol.replace(/^KRW-/, '')} · ${selectedLive.sourceLabel}` - : ''; + const centerHead = useMemo(() => { + if (tab === 'backtest' && selectedBacktest) { + const market = toUpbitMarket(selectedBacktest.symbol); + return { + ko: getKoreanName(market), + strategy: selectedBacktest.strategyName || '전략 없음', + meta: selectedBacktest.timeframe, + }; + } + if (tab === 'live' && selectedLive) { + const market = toUpbitMarket(selectedLive.symbol); + return { + ko: getKoreanName(market), + strategy: selectedLive.strategyLabel, + meta: selectedLive.sourceLabel, + }; + } + return null; + }, [tab, selectedBacktest, selectedLive]); const footerTrades = equityData.trades; @@ -252,9 +271,13 @@ export function BacktestHistoryPage({ theme = 'dark' }: Props) { />
- {centerTitle && ( + {centerHead && (
-

{centerTitle}

+

{centerHead.ko}

+

+ {centerHead.strategy} + {centerHead.meta ? · {centerHead.meta} : null} +

)}
diff --git a/frontend/src/components/Toolbar.tsx b/frontend/src/components/Toolbar.tsx index 48497a9..7b11fc5 100644 --- a/frontend/src/components/Toolbar.tsx +++ b/frontend/src/components/Toolbar.tsx @@ -340,6 +340,7 @@ const IndDropdown: React.FC = ({ const [category, setCategory] = React.useState('All'); const [customTabs, setCustomTabs] = React.useState(() => loadCustomTabs()); const [mainOrderTick, setMainOrderTick] = React.useState(0); + const [customTabsRevision, setCustomTabsRevision] = React.useState(0); const [draggingType, setDraggingType] = React.useState(null); const [dragOverType, setDragOverType] = React.useState(null); const [editorOpen, setEditorOpen] = React.useState(false); @@ -349,8 +350,15 @@ const IndDropdown: React.FC = ({ const refreshCustomTabs = useCallback(() => { setCustomTabs(loadCustomTabs()); + setCustomTabsRevision(t => t + 1); }, []); + useEffect(() => { + const onTabsChanged = () => refreshCustomTabs(); + window.addEventListener('gc-indicator-custom-tabs-changed', onTabsChanged); + return () => window.removeEventListener('gc-indicator-custom-tabs-changed', onTabsChanged); + }, [refreshCustomTabs]); + const selectedCustomTab = React.useMemo(() => { const id = parseCustomTabKey(category); return id ? customTabs.find(t => t.id === id) ?? null : null; @@ -390,13 +398,17 @@ const IndDropdown: React.FC = ({ const tabTypeOrder = React.useMemo((): string[] | null => { void mainOrderTick; + void customTabsRevision; if (search.trim()) return null; if (category === 'Main') return getOrderedMainIndicatorTypes(); - if (isCustomTabKey(category) && selectedCustomTab) { - return [...selectedCustomTab.indicatorTypes]; + if (isCustomTabKey(category)) { + const id = parseCustomTabKey(category); + if (!id) return null; + const tab = customTabs.find(t => t.id === id); + return tab ? [...tab.indicatorTypes] : null; } return null; - }, [category, selectedCustomTab, customTabs, search, mainOrderTick]); + }, [category, customTabs, search, mainOrderTick, customTabsRevision]); const canReorderList = tabTypeOrder != null && tabTypeOrder.length > 1; @@ -414,12 +426,19 @@ const IndDropdown: React.FC = ({ setMainOrderTick(t => t + 1); saveIndicatorListOrder(mergeIndicatorListOrder(next, getSettingsIndicatorTypes())); onIndicatorOrderChange?.(next); - } else if (isCustomTabKey(category) && selectedCustomTab) { - updateCustomTab(selectedCustomTab.id, { indicatorTypes: next }); - refreshCustomTabs(); + } else if (isCustomTabKey(category)) { + const id = parseCustomTabKey(category); + if (!id) return; + const updated = updateCustomTab(id, { indicatorTypes: next }); + if (updated) { + setCustomTabs(prev => prev.map(t => (t.id === id ? updated : t))); + setCustomTabsRevision(t => t + 1); + } else { + refreshCustomTabs(); + } onIndicatorOrderChange?.(next); } - }, [tabTypeOrder, category, selectedCustomTab, refreshCustomTabs, onIndicatorOrderChange]); + }, [tabTypeOrder, category, refreshCustomTabs, onIndicatorOrderChange]); useEffect(() => { const clearDrag = () => { diff --git a/frontend/src/components/backtest/BacktestExecutionList.tsx b/frontend/src/components/backtest/BacktestExecutionList.tsx index 02c1bb7..f837734 100644 --- a/frontend/src/components/backtest/BacktestExecutionList.tsx +++ b/frontend/src/components/backtest/BacktestExecutionList.tsx @@ -5,6 +5,11 @@ import BacktestSparkline from './BacktestSparkline'; import { buildEquityFromSignals } from '../../utils/backtestEquity'; import { paperTradesToSignals } from '../../utils/liveExecutionGroups'; import { fmtListTimestamp, fmtShortTimestamp, pct, pctAbs } from '../../utils/backtestUiUtils'; +import { getKoreanName } from '../../utils/marketNameCache'; + +function toUpbitMarket(symbol: string): string { + return symbol.startsWith('KRW-') ? symbol : `KRW-${symbol}`; +} export type ExecutionListTab = 'backtest' | 'live'; export type BacktestListSort = 'strategy' | 'symbol' | 'return'; @@ -82,12 +87,15 @@ export default function BacktestExecutionList({ const ret = r.totalReturn ?? 0; const positive = ret >= 0; const active = selectedBacktestId === r.id; - const sym = r.symbol.replace(/^KRW-/, ''); + const market = toUpbitMarket(r.symbol); + const ko = getKoreanName(market); + const strategy = r.strategyName || '전략 없음'; return (