서버 가상매매 목록 지표없음 문제 개선
This commit is contained in:
@@ -44,7 +44,7 @@ import {
|
||||
type VirtualTargetItem,
|
||||
type VirtualCardViewMode,
|
||||
} from '../utils/virtualTradingStorage';
|
||||
import { useAppSettings, resolveAppDefaults } from '../hooks/useAppSettings';
|
||||
import { useAppSettings, resolveAppDefaults, subscribeAppSettings } from '../hooks/useAppSettings';
|
||||
import { coerceFiniteNumber } from '../utils/safeFormat';
|
||||
import type { TradeNotificationGridPresetId } from '../utils/tradeNotificationGridPresets';
|
||||
import {
|
||||
@@ -62,6 +62,12 @@ import {
|
||||
resolveVirtualTargetStrategyId,
|
||||
} from '../utils/virtualTargetStrategy';
|
||||
import { virtualTargetLimitMessage } from '../utils/virtualTargetLimits';
|
||||
import { hydrateVirtualTargetsIfEmpty } from '../utils/virtualTargetsHydrate';
|
||||
import {
|
||||
mergeTargetsWithLiveStrategySettings,
|
||||
mergeVirtualSessionWithAppLive,
|
||||
resolveVirtualTargetStrategyIdWithApp,
|
||||
} from '../utils/virtualTargetHydrate';
|
||||
import '../styles/virtualTradingDashboard.css';
|
||||
|
||||
type RightTab = 'trade' | 'orderbook' | 'history';
|
||||
@@ -102,7 +108,8 @@ const VirtualTradingPage: React.FC<Props> = ({
|
||||
const [globalDisplayMode, setGlobalDisplayMode] = useState<VirtualCardDisplayMode>('signal');
|
||||
const [focusMarket, setFocusMarket] = useState<string | null>(null);
|
||||
const orderAnchorRef = useRef<HTMLDivElement>(null);
|
||||
const { settings: appSettings } = useAppSettings();
|
||||
const preferencesHydratedRef = useRef(false);
|
||||
const { settings: appSettings, isLoaded: settingsLoaded } = useAppSettings();
|
||||
const appChartDefaults = resolveAppDefaults(appSettings);
|
||||
const chartRealtimeSource = (appChartDefaults.chartRealtimeSource
|
||||
?? 'BACKEND_STOMP') as ChartRealtimeSource;
|
||||
@@ -111,59 +118,127 @@ const VirtualTradingPage: React.FC<Props> = ({
|
||||
const chartPaneSeparator = appChartDefaults.chartPaneSeparator;
|
||||
const chartLiveReceiveHighlight = appChartDefaults.chartLiveReceiveHighlight;
|
||||
const virtualTargetMaxCount = appChartDefaults.virtualTargetMaxCount;
|
||||
const appLiveStrategyId = appChartDefaults.liveStrategyId ?? null;
|
||||
|
||||
const reloadFromUiPreferences = useCallback(() => {
|
||||
const sessionFromDb = mergeVirtualSessionWithAppLive(loadVirtualSession(), appLiveStrategyId);
|
||||
setSession(sessionFromDb);
|
||||
setTargets(loadVirtualTargets());
|
||||
}, [appLiveStrategyId]);
|
||||
|
||||
useEffect(() => {
|
||||
void Promise.all([
|
||||
loadStrategies()
|
||||
.then(list => {
|
||||
setStrategies(list);
|
||||
const validIds = new Set(
|
||||
list.map(s => s.id).filter((id): id is number => id != null && id > 0),
|
||||
);
|
||||
setSession(prev =>
|
||||
prev.globalStrategyId != null && !validIds.has(prev.globalStrategyId)
|
||||
? { ...prev, globalStrategyId: null }
|
||||
: prev,
|
||||
);
|
||||
setTargets(prev =>
|
||||
prev.map(t =>
|
||||
t.strategyId != null && !validIds.has(t.strategyId)
|
||||
? { ...t, strategyId: null }
|
||||
: t,
|
||||
),
|
||||
);
|
||||
})
|
||||
.catch(() => setStrategies([])),
|
||||
loadPaperSummary().then(setSummary),
|
||||
loadPaperTrades().then(setTrades),
|
||||
]).finally(() => setLoading(false));
|
||||
}, []);
|
||||
if (!settingsLoaded) return;
|
||||
let cancelled = false;
|
||||
preferencesHydratedRef.current = false;
|
||||
setLoading(true);
|
||||
void (async () => {
|
||||
reloadFromUiPreferences();
|
||||
let nextTargets = loadVirtualTargets();
|
||||
if (nextTargets.length === 0) {
|
||||
nextTargets = await hydrateVirtualTargetsIfEmpty();
|
||||
if (!cancelled && nextTargets.length > 0) {
|
||||
setTargets(nextTargets);
|
||||
saveVirtualTargets(nextTargets);
|
||||
}
|
||||
}
|
||||
const sessionFromDb = mergeVirtualSessionWithAppLive(loadVirtualSession(), appLiveStrategyId);
|
||||
if (!cancelled) {
|
||||
setSession(sessionFromDb);
|
||||
}
|
||||
if (nextTargets.length > 0) {
|
||||
const withLive = await mergeTargetsWithLiveStrategySettings(
|
||||
nextTargets,
|
||||
sessionFromDb,
|
||||
appLiveStrategyId,
|
||||
);
|
||||
if (!cancelled && withLive !== nextTargets) {
|
||||
setTargets(withLive);
|
||||
}
|
||||
}
|
||||
if (!cancelled) {
|
||||
preferencesHydratedRef.current = true;
|
||||
}
|
||||
await Promise.all([
|
||||
loadStrategies()
|
||||
.then(list => {
|
||||
if (cancelled) return;
|
||||
setStrategies(list);
|
||||
const validIds = new Set(
|
||||
list.map(s => s.id).filter((id): id is number => id != null && id > 0),
|
||||
);
|
||||
setSession(prev => {
|
||||
const next = prev.globalStrategyId != null && !validIds.has(prev.globalStrategyId)
|
||||
? { ...prev, globalStrategyId: null }
|
||||
: prev;
|
||||
return mergeVirtualSessionWithAppLive(next, appLiveStrategyId);
|
||||
});
|
||||
setTargets(prev =>
|
||||
prev.map(t =>
|
||||
t.strategyId != null && !validIds.has(t.strategyId)
|
||||
? { ...t, strategyId: null }
|
||||
: t,
|
||||
),
|
||||
);
|
||||
})
|
||||
.catch(() => { if (!cancelled) setStrategies([]); }),
|
||||
loadPaperSummary().then(sum => { if (!cancelled) setSummary(sum); }),
|
||||
loadPaperTrades().then(tr => { if (!cancelled) setTrades(tr); }),
|
||||
]);
|
||||
if (!cancelled) setLoading(false);
|
||||
})();
|
||||
return () => { cancelled = true; };
|
||||
}, [settingsLoaded, appLiveStrategyId, reloadFromUiPreferences]);
|
||||
|
||||
/** DB is_pinned → localStorage 투자대상 목록 동기화 (최초 1회) */
|
||||
useEffect(() => {
|
||||
if (!settingsLoaded) return;
|
||||
return subscribeAppSettings(() => {
|
||||
reloadFromUiPreferences();
|
||||
});
|
||||
}, [settingsLoaded, reloadFromUiPreferences]);
|
||||
|
||||
/** DB is_pinned·strategyId → ui_preferences 투자대상 동기화 */
|
||||
useEffect(() => {
|
||||
if (!settingsLoaded) return;
|
||||
let cancelled = false;
|
||||
const initial = loadVirtualTargets();
|
||||
if (initial.length === 0) return;
|
||||
const sessionFromDb = mergeVirtualSessionWithAppLive(loadVirtualSession(), appLiveStrategyId);
|
||||
void (async () => {
|
||||
const merged = await Promise.all(
|
||||
initial.map(async t => {
|
||||
try {
|
||||
const s = await loadLiveStrategySettings(t.market);
|
||||
const pinned = !!s.isPinned;
|
||||
if (!!t.pinned === pinned) return t;
|
||||
return { ...t, pinned };
|
||||
let next = t;
|
||||
if (!!t.pinned !== !!s.isPinned) {
|
||||
next = { ...next, pinned: !!s.isPinned };
|
||||
}
|
||||
if (next.strategyId == null && s.strategyId != null && s.strategyId > 0 && s.isLiveCheck) {
|
||||
next = { ...next, strategyId: s.strategyId };
|
||||
}
|
||||
return next;
|
||||
} catch {
|
||||
return t;
|
||||
}
|
||||
}),
|
||||
);
|
||||
if (!cancelled) setTargets(merged);
|
||||
const withLive = await mergeTargetsWithLiveStrategySettings(
|
||||
merged,
|
||||
sessionFromDb,
|
||||
appLiveStrategyId,
|
||||
);
|
||||
if (!cancelled) setTargets(withLive);
|
||||
})();
|
||||
return () => { cancelled = true; };
|
||||
}, []);
|
||||
}, [settingsLoaded, appLiveStrategyId]);
|
||||
|
||||
useEffect(() => { saveVirtualTargets(targets); }, [targets]);
|
||||
useEffect(() => { saveVirtualSession(session); }, [session]);
|
||||
useEffect(() => {
|
||||
if (!preferencesHydratedRef.current) return;
|
||||
saveVirtualTargets(targets);
|
||||
}, [targets]);
|
||||
useEffect(() => {
|
||||
if (!preferencesHydratedRef.current) return;
|
||||
saveVirtualSession(session);
|
||||
}, [session]);
|
||||
|
||||
useEffect(() => { saveVirtualCardViewMode(viewMode); }, [viewMode]);
|
||||
useEffect(() => { saveVirtualGridPreset(gridPreset); }, [gridPreset]);
|
||||
@@ -194,9 +269,9 @@ const VirtualTradingPage: React.FC<Props> = ({
|
||||
const targetRefs = useMemo(() =>
|
||||
targets.map(t => ({
|
||||
market: t.market,
|
||||
strategyId: resolveVirtualTargetStrategyId(t, session.globalStrategyId),
|
||||
strategyId: resolveVirtualTargetStrategyIdWithApp(t, session, appLiveStrategyId),
|
||||
})),
|
||||
[targets, session.globalStrategyId]);
|
||||
[targets, session, appLiveStrategyId]);
|
||||
|
||||
const { snapshots, loadingByMarket: snapshotLoadingByMarket } = useVirtualIndicatorSnapshots(
|
||||
targetRefs,
|
||||
@@ -474,6 +549,7 @@ const VirtualTradingPage: React.FC<Props> = ({
|
||||
globalDisplayMode={globalDisplayMode}
|
||||
onFocusMarketChange={setFocusMarket}
|
||||
gridPreset={gridPreset}
|
||||
appLiveStrategyId={appLiveStrategyId}
|
||||
/>
|
||||
)}
|
||||
rightTabs={rightTabs}
|
||||
|
||||
Reference in New Issue
Block a user