가상투자 종목카드박스 레이아웃 수정
This commit is contained in:
@@ -7,6 +7,8 @@ import {
|
||||
loadPaperTrades,
|
||||
loadStrategies,
|
||||
saveLiveStrategySettings,
|
||||
pinCandleWatch,
|
||||
loadLiveStrategySettings,
|
||||
type PaperSummaryDto,
|
||||
type PaperTradeDto,
|
||||
type StrategyDto,
|
||||
@@ -39,7 +41,9 @@ import {
|
||||
type VirtualSessionConfig,
|
||||
type VirtualTargetItem,
|
||||
type VirtualCardViewMode,
|
||||
resolveTargetCandleType,
|
||||
} from '../utils/virtualTradingStorage';
|
||||
import { normalizeStartCandleType } from '../utils/strategyStartNodes';
|
||||
import { useAppSettings, resolveAppDefaults } from '../hooks/useAppSettings';
|
||||
import { coerceFiniteNumber } from '../utils/safeFormat';
|
||||
import {
|
||||
@@ -231,10 +235,15 @@ const VirtualTradingPage: React.FC<Props> = ({
|
||||
t.market === market ? { ...t, strategyId } : t,
|
||||
));
|
||||
if (!session.running || !strategyId) return;
|
||||
const target = targets.find(t => t.market === market);
|
||||
const candleType = normalizeStartCandleType(
|
||||
resolveTargetCandleType(target ?? { candleType: undefined }, snapshots[market]?.timeframe),
|
||||
);
|
||||
try {
|
||||
await saveLiveStrategySettings({
|
||||
market,
|
||||
strategyId,
|
||||
candleType,
|
||||
isLiveCheck: true,
|
||||
executionType: session.executionType,
|
||||
positionMode: session.positionMode,
|
||||
@@ -243,7 +252,59 @@ const VirtualTradingPage: React.FC<Props> = ({
|
||||
} catch {
|
||||
window.alert('종목 전략 변경 저장에 실패했습니다.');
|
||||
}
|
||||
}, [session]);
|
||||
}, [session, targets, snapshots]);
|
||||
|
||||
const handleTargetCandleTypeChange = useCallback(async (market: string, candleType: string) => {
|
||||
const normalized = normalizeStartCandleType(candleType);
|
||||
setTargets(prev => prev.map(t =>
|
||||
t.market === market ? { ...t, candleType: normalized } : t,
|
||||
));
|
||||
if (!session.running) return;
|
||||
const target = targets.find(t => t.market === market);
|
||||
const strategyId = target?.strategyId ?? session.globalStrategyId;
|
||||
if (!strategyId) return;
|
||||
try {
|
||||
await saveLiveStrategySettings({
|
||||
market,
|
||||
strategyId,
|
||||
candleType: normalized,
|
||||
isLiveCheck: true,
|
||||
executionType: session.executionType,
|
||||
positionMode: session.positionMode,
|
||||
skipWatchlistSync: true,
|
||||
});
|
||||
await pinCandleWatch(market, normalized);
|
||||
if (normalized !== '1m') {
|
||||
await pinCandleWatch(market, '1m');
|
||||
}
|
||||
} catch {
|
||||
window.alert('종목 평가 분봉 변경 저장에 실패했습니다.');
|
||||
}
|
||||
}, [session, targets]);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
const missing = targets.filter(t => !t.candleType);
|
||||
if (missing.length === 0) return;
|
||||
void Promise.all(
|
||||
missing.map(async t => {
|
||||
try {
|
||||
const s = await loadLiveStrategySettings(t.market);
|
||||
return { market: t.market, candleType: s.candleType };
|
||||
} catch {
|
||||
return { market: t.market, candleType: undefined };
|
||||
}
|
||||
}),
|
||||
).then(rows => {
|
||||
if (cancelled) return;
|
||||
setTargets(prev => prev.map(t => {
|
||||
const row = rows.find(r => r.market === t.market);
|
||||
if (t.candleType || !row?.candleType) return t;
|
||||
return { ...t, candleType: normalizeStartCandleType(row.candleType) };
|
||||
}));
|
||||
});
|
||||
return () => { cancelled = true; };
|
||||
}, [targets.map(t => `${t.market}:${t.candleType ?? ''}`).join('|')]);
|
||||
|
||||
const resyncRunningSession = useCallback(async (nextSession: VirtualSessionConfig) => {
|
||||
if (!session.running || targets.length === 0) return;
|
||||
@@ -334,6 +395,7 @@ const VirtualTradingPage: React.FC<Props> = ({
|
||||
snapshots={snapshots}
|
||||
session={session}
|
||||
onTargetStrategyChange={(market, strategyId) => void handleTargetStrategyChange(market, strategyId)}
|
||||
onTargetCandleTypeChange={(market, ct) => void handleTargetCandleTypeChange(market, ct)}
|
||||
liveStatusByMarket={mergedLiveStatus}
|
||||
lastTickAtByMarket={lastTickAtByMarket}
|
||||
selectedMarket={selectedMarket}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
getConditionValuePeriod,
|
||||
getCompositePeriodPresetOptions,
|
||||
getPeriodPresetOptions,
|
||||
getPeriodSettingsLabel,
|
||||
getThresholdBounds,
|
||||
getThresholdPresetOptions,
|
||||
hasEditableThreshold,
|
||||
@@ -121,7 +122,7 @@ export default function ConditionNodeSettings({
|
||||
</>
|
||||
) : usesValuePeriodField(condition) ? (
|
||||
<label className="se-flow-settings-field">
|
||||
<span>{condition.indicatorType} 기간 (일)</span>
|
||||
<span>{getPeriodSettingsLabel(condition) ?? `${condition.indicatorType} 기간 (일)`}</span>
|
||||
<ComboNumberInput
|
||||
value={getConditionValuePeriod(condition, def)}
|
||||
options={periodPresets}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/** 팔레트·Logic Expression 공통 — 지표 카테고리 */
|
||||
|
||||
export const BAND_INDICATORS = new Set(['MA', 'EMA', 'BOLLINGER', 'DONCHIAN', 'ICHIMOKU']);
|
||||
export const BAND_INDICATORS = new Set(['MA', 'EMA', 'BOLLINGER', 'DONCHIAN', 'NEW_HIGH', 'NEW_LOW', 'ICHIMOKU']);
|
||||
|
||||
export type PaletteIndicatorCategory = 'band' | 'ind';
|
||||
|
||||
|
||||
@@ -5,6 +5,9 @@ import type { VirtualIndicatorSnapshot } from '../../hooks/useVirtualIndicatorSn
|
||||
import { useLiveReceiveFlash } from '../../hooks/useLiveReceiveFlash';
|
||||
import type { ChartRealtimeSource } from '../../hooks/useChartRealtimeData';
|
||||
import { buildConditionMetrics, resolveVirtualTradeTiming } from '../../utils/virtualSignalMetrics';
|
||||
import { candleTypeToTimeframe } from '../../utils/strategyToChartIndicators';
|
||||
import { normalizeStartCandleType } from '../../utils/strategyStartNodes';
|
||||
import type { Timeframe } from '../../types';
|
||||
import VirtualLiveBadge from './VirtualLiveBadge';
|
||||
import type { VirtualLiveStatus } from '../../hooks/useVirtualTargetLiveStatus';
|
||||
import type { VirtualCardViewMode } from '../../utils/virtualTradingStorage';
|
||||
@@ -13,6 +16,7 @@ import VirtualTargetSignalPanel from './VirtualTargetSignalPanel';
|
||||
import type { Theme } from '../../types';
|
||||
import type { TickerData } from '../../hooks/useMarketTicker';
|
||||
import VirtualTargetQuote from './VirtualTargetQuote';
|
||||
import VirtualTargetCardFoot from './VirtualTargetCardFoot';
|
||||
|
||||
export type VirtualCardDisplayMode = 'signal' | 'chart';
|
||||
|
||||
@@ -23,6 +27,8 @@ interface Props {
|
||||
strategyId: number | null;
|
||||
globalStrategyId: number | null;
|
||||
onStrategyChange?: (strategyId: number | null) => void;
|
||||
candleType: string;
|
||||
onCandleTypeChange?: (candleType: string) => void;
|
||||
snapshot: VirtualIndicatorSnapshot | undefined;
|
||||
running: boolean;
|
||||
liveStatus?: VirtualLiveStatus;
|
||||
@@ -47,6 +53,8 @@ const VirtualTargetCard: React.FC<Props> = ({
|
||||
strategyId,
|
||||
globalStrategyId,
|
||||
onStrategyChange,
|
||||
candleType,
|
||||
onCandleTypeChange,
|
||||
snapshot,
|
||||
running,
|
||||
liveStatus = 'idle',
|
||||
@@ -65,7 +73,6 @@ const VirtualTargetCard: React.FC<Props> = ({
|
||||
}) => {
|
||||
const ko = getKoreanName(market);
|
||||
const sym = market.replace(/^KRW-/, '');
|
||||
const tf = snapshot?.timeframe ?? '—';
|
||||
const rows = snapshot?.rows ?? [];
|
||||
|
||||
const metrics = useMemo(() => buildConditionMetrics(rows), [rows]);
|
||||
@@ -75,6 +82,8 @@ const VirtualTargetCard: React.FC<Props> = ({
|
||||
|
||||
const isDetail = viewMode === 'detail';
|
||||
const isChart = displayMode === 'chart';
|
||||
const evalCandleType = normalizeStartCandleType(candleType);
|
||||
const chartTimeframe = candleTypeToTimeframe(evalCandleType) as Timeframe;
|
||||
|
||||
const receiveSignal = useMemo(() => {
|
||||
if (!running) return null;
|
||||
@@ -113,23 +122,6 @@ const VirtualTargetCard: React.FC<Props> = ({
|
||||
<span className="vtd-card-ko">{ko}</span>
|
||||
<span className="vtd-card-sym">{sym}</span>
|
||||
</div>
|
||||
<label className="vtd-card-strategy-field" onClick={e => e.stopPropagation()}>
|
||||
<select
|
||||
className="vtd-card-strategy-select"
|
||||
aria-label="투자전략"
|
||||
value={strategyId ?? globalStrategyId ?? ''}
|
||||
onChange={e => {
|
||||
const v = e.target.value;
|
||||
onStrategyChange?.(v ? Number(v) : null);
|
||||
}}
|
||||
>
|
||||
<option value="">— 선택 —</option>
|
||||
{strategies.map(s => (
|
||||
<option key={s.id} value={s.id}>{s.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<span className="vtd-card-tf">시간봉 {tf}</span>
|
||||
</div>
|
||||
<div className="vtd-card-head-actions">
|
||||
{onEnterFocus && (
|
||||
@@ -185,6 +177,7 @@ const VirtualTargetCard: React.FC<Props> = ({
|
||||
running={running}
|
||||
chartRealtimeSource={chartRealtimeSource}
|
||||
chartSeriesPriceLabels={chartSeriesPriceLabels}
|
||||
chartTimeframe={chartTimeframe}
|
||||
/>
|
||||
) : (
|
||||
<VirtualTargetSignalPanel
|
||||
@@ -193,6 +186,16 @@ const VirtualTargetCard: React.FC<Props> = ({
|
||||
receiving={highlightReceiving}
|
||||
/>
|
||||
)}
|
||||
|
||||
<VirtualTargetCardFoot
|
||||
snapshot={snapshot}
|
||||
strategies={strategies}
|
||||
strategyId={strategyId}
|
||||
globalStrategyId={globalStrategyId}
|
||||
candleType={evalCandleType}
|
||||
onStrategyChange={onStrategyChange}
|
||||
onCandleTypeChange={onCandleTypeChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -33,6 +33,8 @@ interface Props {
|
||||
chartSeriesPriceLabels?: boolean;
|
||||
/** 전체보기 분할 pane — 캔버스 높이 100% */
|
||||
fillHeight?: boolean;
|
||||
/** 카드 푸터 평가 분봉과 동기화 */
|
||||
chartTimeframe?: Timeframe;
|
||||
}
|
||||
|
||||
const noop = () => {};
|
||||
@@ -45,14 +47,21 @@ const VirtualTargetCardChart: React.FC<Props> = ({
|
||||
chartRealtimeSource = 'BACKEND_STOMP',
|
||||
chartSeriesPriceLabels = true,
|
||||
fillHeight = false,
|
||||
chartTimeframe,
|
||||
}) => {
|
||||
const { getParams, getVisualConfig } = useIndicatorSettings();
|
||||
const tfOptions = useMemo(() => resolveStrategyTimeframes(strategy), [strategy]);
|
||||
const [timeframe, setTimeframe] = useState<Timeframe>(() => resolveStrategyPrimaryTimeframe(strategy));
|
||||
const [timeframe, setTimeframe] = useState<Timeframe>(
|
||||
() => chartTimeframe ?? resolveStrategyPrimaryTimeframe(strategy),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (chartTimeframe) {
|
||||
setTimeframe(chartTimeframe);
|
||||
return;
|
||||
}
|
||||
setTimeframe(resolveStrategyPrimaryTimeframe(strategy));
|
||||
}, [strategy, market]);
|
||||
}, [strategy, market, chartTimeframe]);
|
||||
|
||||
const indicators = useMemo(
|
||||
() => buildVirtualTradingChartIndicators(strategy, getParams, getVisualConfig),
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import React from 'react';
|
||||
import type { StrategyDto } from '../../utils/backendApi';
|
||||
import type { VirtualIndicatorSnapshot } from '../../hooks/useVirtualIndicatorSnapshots';
|
||||
import { formatUpdatedTime } from '../../utils/virtualSignalMetrics';
|
||||
import { STRATEGY_CANDLE_TYPE_OPTIONS } from '../../utils/strategyStartNodes';
|
||||
|
||||
interface Props {
|
||||
snapshot: VirtualIndicatorSnapshot | undefined;
|
||||
strategies: StrategyDto[];
|
||||
strategyId: number | null;
|
||||
globalStrategyId: number | null;
|
||||
candleType: string;
|
||||
onStrategyChange?: (strategyId: number | null) => void;
|
||||
onCandleTypeChange?: (candleType: string) => void;
|
||||
}
|
||||
|
||||
/** 카드 하단 — 갱신 시각(좌) · 전략·시간봉 드롭다운(우) */
|
||||
const VirtualTargetCardFoot: React.FC<Props> = ({
|
||||
snapshot,
|
||||
strategies,
|
||||
strategyId,
|
||||
globalStrategyId,
|
||||
candleType,
|
||||
onStrategyChange,
|
||||
onCandleTypeChange,
|
||||
}) => (
|
||||
<div className="vtd-card-foot">
|
||||
<span className="vtd-card-updated">갱신 {formatUpdatedTime(snapshot?.updatedAt)}</span>
|
||||
<div className="vtd-card-foot-controls" onClick={e => e.stopPropagation()}>
|
||||
<label className="vtd-card-foot-field">
|
||||
<select
|
||||
className="vtd-card-foot-select"
|
||||
aria-label="투자전략"
|
||||
value={strategyId ?? globalStrategyId ?? ''}
|
||||
onChange={e => {
|
||||
const v = e.target.value;
|
||||
onStrategyChange?.(v ? Number(v) : null);
|
||||
}}
|
||||
>
|
||||
<option value="">— 선택 —</option>
|
||||
{strategies.map(s => (
|
||||
<option key={s.id} value={s.id}>{s.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label className="vtd-card-foot-field">
|
||||
<select
|
||||
className="vtd-card-foot-select vtd-card-foot-select--tf"
|
||||
aria-label="시간봉"
|
||||
value={candleType}
|
||||
onChange={e => onCandleTypeChange?.(e.target.value)}
|
||||
>
|
||||
{STRATEGY_CANDLE_TYPE_OPTIONS.map(o => (
|
||||
<option key={o.value} value={o.value}>{o.label}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default VirtualTargetCardFoot;
|
||||
@@ -12,6 +12,10 @@ import VirtualLiveBadge from './VirtualLiveBadge';
|
||||
import VirtualTargetQuote from './VirtualTargetQuote';
|
||||
import VirtualTargetCardChart from './VirtualTargetCardChart';
|
||||
import VirtualTargetSignalPanel from './VirtualTargetSignalPanel';
|
||||
import VirtualTargetCardFoot from './VirtualTargetCardFoot';
|
||||
import { candleTypeToTimeframe } from '../../utils/strategyToChartIndicators';
|
||||
import { normalizeStartCandleType } from '../../utils/strategyStartNodes';
|
||||
import type { Timeframe } from '../../types';
|
||||
|
||||
interface Props {
|
||||
market: string;
|
||||
@@ -20,6 +24,8 @@ interface Props {
|
||||
strategyId: number | null;
|
||||
globalStrategyId: number | null;
|
||||
onStrategyChange?: (strategyId: number | null) => void;
|
||||
candleType: string;
|
||||
onCandleTypeChange?: (candleType: string) => void;
|
||||
snapshot: VirtualIndicatorSnapshot | undefined;
|
||||
running: boolean;
|
||||
liveStatus?: VirtualLiveStatus;
|
||||
@@ -41,6 +47,8 @@ const VirtualTargetFocusView: React.FC<Props> = ({
|
||||
strategyId,
|
||||
globalStrategyId,
|
||||
onStrategyChange,
|
||||
candleType,
|
||||
onCandleTypeChange,
|
||||
snapshot,
|
||||
running,
|
||||
liveStatus = 'idle',
|
||||
@@ -55,7 +63,6 @@ const VirtualTargetFocusView: React.FC<Props> = ({
|
||||
}) => {
|
||||
const ko = getKoreanName(market);
|
||||
const sym = market.replace(/^KRW-/, '');
|
||||
const tf = snapshot?.timeframe ?? '—';
|
||||
|
||||
const receiveSignal = useMemo(() => {
|
||||
if (!running) return null;
|
||||
@@ -63,6 +70,8 @@ const VirtualTargetFocusView: React.FC<Props> = ({
|
||||
}, [running, lastTickAt, snapshot?.updatedAt]);
|
||||
const receiving = useLiveReceiveFlash(receiveSignal, running);
|
||||
const highlightReceiving = chartLiveReceiveHighlight && receiving;
|
||||
const evalCandleType = normalizeStartCandleType(candleType);
|
||||
const chartTimeframe = candleTypeToTimeframe(evalCandleType) as Timeframe;
|
||||
|
||||
return (
|
||||
<div className={`vtd-focus-wrap${highlightReceiving ? ' vtd-focus-wrap--receiving' : ''}`}>
|
||||
@@ -72,23 +81,6 @@ const VirtualTargetFocusView: React.FC<Props> = ({
|
||||
<span className="vtd-card-ko">{ko}</span>
|
||||
<span className="vtd-card-sym">{sym}</span>
|
||||
</div>
|
||||
<label className="vtd-card-strategy-field" onClick={e => e.stopPropagation()}>
|
||||
<select
|
||||
className="vtd-card-strategy-select"
|
||||
aria-label="투자전략"
|
||||
value={strategyId ?? globalStrategyId ?? ''}
|
||||
onChange={e => {
|
||||
const v = e.target.value;
|
||||
onStrategyChange?.(v ? Number(v) : null);
|
||||
}}
|
||||
>
|
||||
<option value="">— 선택 —</option>
|
||||
{strategies.map(s => (
|
||||
<option key={s.id} value={s.id}>{s.name}</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<span className="vtd-card-tf">시간봉 {tf}</span>
|
||||
{running && <VirtualLiveBadge status={liveStatus} receiving={receiving} />}
|
||||
</div>
|
||||
<button
|
||||
@@ -123,6 +115,7 @@ const VirtualTargetFocusView: React.FC<Props> = ({
|
||||
chartRealtimeSource={chartRealtimeSource}
|
||||
chartSeriesPriceLabels={chartSeriesPriceLabels}
|
||||
fillHeight
|
||||
chartTimeframe={chartTimeframe}
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
@@ -137,6 +130,16 @@ const VirtualTargetFocusView: React.FC<Props> = ({
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<VirtualTargetCardFoot
|
||||
snapshot={snapshot}
|
||||
strategies={strategies}
|
||||
strategyId={strategyId}
|
||||
globalStrategyId={globalStrategyId}
|
||||
candleType={evalCandleType}
|
||||
onStrategyChange={onStrategyChange}
|
||||
onCandleTypeChange={onCandleTypeChange}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,7 +4,12 @@ import VirtualTargetFocusView from './VirtualTargetFocusView';
|
||||
import type { StrategyDto } from '../../utils/backendApi';
|
||||
import type { Theme } from '../../types';
|
||||
import type { ChartRealtimeSource } from '../../hooks/useChartRealtimeData';
|
||||
import type { VirtualTargetItem, VirtualCardViewMode, VirtualSessionConfig } from '../../utils/virtualTradingStorage';
|
||||
import {
|
||||
type VirtualTargetItem,
|
||||
type VirtualCardViewMode,
|
||||
type VirtualSessionConfig,
|
||||
resolveTargetCandleType,
|
||||
} from '../../utils/virtualTradingStorage';
|
||||
import type { VirtualLiveStatus } from '../../hooks/useVirtualTargetLiveStatus';
|
||||
import type { VirtualIndicatorSnapshot } from '../../hooks/useVirtualIndicatorSnapshots';
|
||||
import type { TickerData } from '../../hooks/useMarketTicker';
|
||||
@@ -15,6 +20,7 @@ interface Props {
|
||||
snapshots: Record<string, VirtualIndicatorSnapshot>;
|
||||
session: Pick<VirtualSessionConfig, 'globalStrategyId' | 'executionType' | 'positionMode' | 'running'>;
|
||||
onTargetStrategyChange: (market: string, strategyId: number | null) => void;
|
||||
onTargetCandleTypeChange: (market: string, candleType: string) => void;
|
||||
liveStatusByMarket?: Record<string, VirtualLiveStatus>;
|
||||
lastTickAtByMarket?: Record<string, number>;
|
||||
selectedMarket?: string;
|
||||
@@ -32,6 +38,7 @@ interface Props {
|
||||
const VirtualTargetGrid: React.FC<Props> = ({
|
||||
targets, strategies, snapshots, session,
|
||||
onTargetStrategyChange,
|
||||
onTargetCandleTypeChange,
|
||||
liveStatusByMarket = {}, lastTickAtByMarket = {}, selectedMarket,
|
||||
onSelectMarket,
|
||||
theme = 'dark',
|
||||
@@ -109,6 +116,8 @@ const VirtualTargetGrid: React.FC<Props> = ({
|
||||
strategyId={focusTarget.strategyId}
|
||||
globalStrategyId={session.globalStrategyId}
|
||||
onStrategyChange={id => onTargetStrategyChange(focusTarget.market, id)}
|
||||
candleType={resolveTargetCandleType(focusTarget, snapshots[focusTarget.market]?.timeframe)}
|
||||
onCandleTypeChange={ct => onTargetCandleTypeChange(focusTarget.market, ct)}
|
||||
snapshot={snapshots[focusTarget.market]}
|
||||
running={session.running}
|
||||
liveStatus={liveStatusByMarket[focusTarget.market] ?? (session.running ? 'connecting' : 'idle')}
|
||||
@@ -134,6 +143,8 @@ const VirtualTargetGrid: React.FC<Props> = ({
|
||||
strategyId={t.strategyId}
|
||||
globalStrategyId={session.globalStrategyId}
|
||||
onStrategyChange={id => onTargetStrategyChange(t.market, id)}
|
||||
candleType={resolveTargetCandleType(t, snapshots[t.market]?.timeframe)}
|
||||
onCandleTypeChange={ct => onTargetCandleTypeChange(t.market, ct)}
|
||||
snapshot={snapshots[t.market]}
|
||||
running={session.running}
|
||||
liveStatus={liveStatusByMarket[t.market] ?? (session.running ? 'connecting' : 'idle')}
|
||||
|
||||
@@ -3,7 +3,6 @@ import type { VirtualIndicatorSnapshot } from '../../hooks/useVirtualIndicatorSn
|
||||
import {
|
||||
buildConditionMetrics,
|
||||
computeMatchRate,
|
||||
formatUpdatedTime,
|
||||
getTrafficLightState,
|
||||
} from '../../utils/virtualSignalMetrics';
|
||||
import VirtualSignalEqualizer from './VirtualSignalEqualizer';
|
||||
@@ -67,10 +66,6 @@ const VirtualTargetSignalPanel: React.FC<Props> = ({
|
||||
<VirtualIndicatorCompareTable metrics={metrics} />
|
||||
</div>
|
||||
)}
|
||||
<div className="vtd-card-foot">
|
||||
<span className="vtd-card-updated">갱신 {formatUpdatedTime(snapshot?.updatedAt)}</span>
|
||||
<span className="vtd-card-match-summary">매매조건 일치율 {matchRate}%</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user