가상투자 설정 수정
This commit is contained in:
+106
-32
@@ -84,6 +84,9 @@ import {
|
||||
type LiveStrategySettingsDto,
|
||||
type BacktestSignal,
|
||||
} from './utils/backendApi';
|
||||
import { useVirtualLiveStrategy } from './hooks/useVirtualLiveStrategy';
|
||||
import { VIRTUAL_SESSION_CHANGED_EVENT } from './utils/virtualTradingStorage';
|
||||
import { notifyPaperTradesChanged } from './utils/paperTradeEvents';
|
||||
import { useIsMobile } from './hooks/useMediaQuery';
|
||||
import MobileChartDock from './components/MobileChartDock';
|
||||
import LoginModal from './components/LoginModal';
|
||||
@@ -583,6 +586,11 @@ function App() {
|
||||
const [paperCoinQty, setPaperCoinQty] = useState(0);
|
||||
const [paperRefreshKey, setPaperRefreshKey] = useState(0);
|
||||
|
||||
const handlePaperOrderFilled = useCallback(() => {
|
||||
setPaperRefreshKey(k => k + 1);
|
||||
notifyPaperTradesChanged();
|
||||
}, []);
|
||||
|
||||
const refreshPaperAccount = useCallback(async (forMarket?: string) => {
|
||||
const summary = await loadPaperSummary();
|
||||
if (!summary) return;
|
||||
@@ -698,55 +706,104 @@ function App() {
|
||||
|
||||
syncBacktestMarkersRef.current = syncBacktestMarkers;
|
||||
|
||||
// ── 실시간 전략 체크 (전역 설정 + DB 관심종목 = 체크 대상) ─────────────────
|
||||
// ── 실시간 전략 체크 (가상투자 세션 우선, 없으면 전역+관심종목) ─────────────
|
||||
const [showLivePanel, setShowLivePanel] = useState(false);
|
||||
const [liveStrategies, setLiveStrategies] = useState<{id:number;name:string}[]>([]);
|
||||
const [liveStrategyCandleTypes, setLiveStrategyCandleTypes] = useState<string[] | undefined>();
|
||||
const [activeLiveSettings, setActiveLiveSettings] = useState<LiveStrategySettingsDto[]>([]);
|
||||
const { session: virtualSession, isVirtualActive, monitoredMarkets: virtualMonitoredMarkets } = useVirtualLiveStrategy();
|
||||
|
||||
const refreshActiveLiveSettings = useCallback(() => {
|
||||
loadActiveLiveStrategySettings()
|
||||
.then(setActiveLiveSettings)
|
||||
.catch(() => setActiveLiveSettings([]));
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
refreshActiveLiveSettings();
|
||||
}, [refreshActiveLiveSettings, isVirtualActive, virtualMonitoredMarkets.join(','), virtualSession.executionType, virtualSession.positionMode]);
|
||||
|
||||
useEffect(() => {
|
||||
const onVirtualChanged = () => refreshActiveLiveSettings();
|
||||
window.addEventListener(VIRTUAL_SESSION_CHANGED_EVENT, onVirtualChanged);
|
||||
return () => window.removeEventListener(VIRTUAL_SESSION_CHANGED_EVENT, onVirtualChanged);
|
||||
}, [refreshActiveLiveSettings]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!appSettingsLoaded) return;
|
||||
if (isVirtualActive) {
|
||||
const active = activeLiveSettings.find(s => s.market === symbol);
|
||||
setLiveStrategyCandleTypes(active?.strategyCandleTypes);
|
||||
return;
|
||||
}
|
||||
loadLiveStrategySettings(symbol)
|
||||
.then(s => setLiveStrategyCandleTypes(s.strategyCandleTypes))
|
||||
.catch(() => { /* optional */ });
|
||||
}, [symbol, appSettingsLoaded, appDefaults.liveStrategyId]);
|
||||
}, [symbol, appSettingsLoaded, appDefaults.liveStrategyId, isVirtualActive, activeLiveSettings]);
|
||||
|
||||
const liveStrategySettings: LiveStrategySettingsDto = useMemo(() => ({
|
||||
market: symbol,
|
||||
strategyId: appDefaults.liveStrategyId ?? null,
|
||||
isLiveCheck: appDefaults.liveStrategyCheck ?? false,
|
||||
executionType: appDefaults.liveExecutionType ?? 'CANDLE_CLOSE',
|
||||
positionMode: appDefaults.livePositionMode ?? 'LONG_ONLY',
|
||||
strategyCandleTypes: liveStrategyCandleTypes,
|
||||
}), [symbol, appDefaults, liveStrategyCandleTypes]);
|
||||
const liveStrategySettings: LiveStrategySettingsDto = useMemo(() => {
|
||||
if (isVirtualActive) {
|
||||
const active = activeLiveSettings.find(s => s.market === symbol);
|
||||
return {
|
||||
market: symbol,
|
||||
strategyId: active?.strategyId ?? virtualSession.globalStrategyId,
|
||||
isLiveCheck: true,
|
||||
executionType: active?.executionType ?? virtualSession.executionType,
|
||||
positionMode: active?.positionMode ?? virtualSession.positionMode,
|
||||
strategyCandleTypes: active?.strategyCandleTypes ?? liveStrategyCandleTypes,
|
||||
};
|
||||
}
|
||||
return {
|
||||
market: symbol,
|
||||
strategyId: appDefaults.liveStrategyId ?? null,
|
||||
isLiveCheck: appDefaults.liveStrategyCheck ?? false,
|
||||
executionType: appDefaults.liveExecutionType ?? 'CANDLE_CLOSE',
|
||||
positionMode: appDefaults.livePositionMode ?? 'LONG_ONLY',
|
||||
strategyCandleTypes: liveStrategyCandleTypes,
|
||||
};
|
||||
}, [isVirtualActive, activeLiveSettings, symbol, virtualSession, appDefaults, liveStrategyCandleTypes]);
|
||||
|
||||
/** 실시간 체크 ON + 전략 선택 시 STOMP 구독 대상 (관심종목 + 현재 차트 종목) */
|
||||
/** STOMP 구독 대상 — 가상투자 실행 중이면 투자대상 종목, 아니면 관심종목 */
|
||||
const monitoredMarkets = useMemo(() => {
|
||||
if (isVirtualActive) return virtualMonitoredMarkets;
|
||||
if (!appDefaults.liveStrategyCheck || !appDefaults.liveStrategyId) return [];
|
||||
const set = new Set(watchlist);
|
||||
if (symbol) set.add(symbol);
|
||||
return [...set];
|
||||
}, [watchlist, symbol, appDefaults.liveStrategyCheck, appDefaults.liveStrategyId]);
|
||||
}, [isVirtualActive, virtualMonitoredMarkets, watchlist, symbol, appDefaults.liveStrategyCheck, appDefaults.liveStrategyId]);
|
||||
|
||||
const [marketSubscriptions, setMarketSubscriptions] = useState<{ market: string; candleType: string }[]>([]);
|
||||
|
||||
/** STOMP 구독 목록 — API 로드 전에도 관심종목 × 1m 폴백 구독 */
|
||||
const liveStompSubscriptions = useMemo(() => {
|
||||
if (!appDefaults.liveStrategyCheck || !appDefaults.liveStrategyId) return [];
|
||||
if (monitoredMarkets.length === 0) return [];
|
||||
const filtered = activeLiveSettings.filter(s => monitoredMarkets.includes(s.market));
|
||||
if (filtered.length > 0) return expandLiveStrategySubscriptions(filtered);
|
||||
if (marketSubscriptions.length > 0) return marketSubscriptions;
|
||||
return monitoredMarkets.map(m => ({ market: m, candleType: '1m' }));
|
||||
}, [appDefaults.liveStrategyCheck, appDefaults.liveStrategyId, marketSubscriptions, monitoredMarkets]);
|
||||
}, [monitoredMarkets, activeLiveSettings, marketSubscriptions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (monitoredMarkets.length === 0) {
|
||||
setMarketSubscriptions([]);
|
||||
return;
|
||||
}
|
||||
if (isVirtualActive) {
|
||||
setMarketSubscriptions(expandLiveStrategySubscriptions(
|
||||
activeLiveSettings.filter(s => monitoredMarkets.includes(s.market)),
|
||||
));
|
||||
return;
|
||||
}
|
||||
if (!appDefaults.liveStrategyCheck) {
|
||||
setMarketSubscriptions([]);
|
||||
return;
|
||||
}
|
||||
loadActiveLiveStrategySettings()
|
||||
.then(list => setMarketSubscriptions(expandLiveStrategySubscriptions(list)))
|
||||
.catch(() => { /* liveStompSubscriptions 가 monitoredMarkets 폴백 사용 */ });
|
||||
}, [appDefaults.liveStrategyCheck, appDefaults.liveStrategyId, monitoredMarkets.join(',')]);
|
||||
.catch(() => { /* liveStompSubscriptions 폴백 */ });
|
||||
}, [isVirtualActive, appDefaults.liveStrategyCheck, appDefaults.liveStrategyId, monitoredMarkets.join(','), activeLiveSettings]);
|
||||
|
||||
const handleLiveSettingsChange = useCallback((saved: LiveStrategySettingsDto) => {
|
||||
if (isVirtualActive) return;
|
||||
saveAppDef({
|
||||
liveStrategyCheck: saved.isLiveCheck,
|
||||
liveStrategyId: saved.strategyId ?? undefined,
|
||||
@@ -757,11 +814,9 @@ function App() {
|
||||
setLiveStrategyCandleTypes(saved.strategyCandleTypes);
|
||||
}
|
||||
if (appDefaults.liveStrategyCheck && saved.isLiveCheck) {
|
||||
loadActiveLiveStrategySettings()
|
||||
.then(list => setMarketSubscriptions(expandLiveStrategySubscriptions(list)))
|
||||
.catch(() => { /* 폴백 유지 */ });
|
||||
refreshActiveLiveSettings();
|
||||
}
|
||||
}, [saveAppDef, appDefaults.liveStrategyCheck]);
|
||||
}, [saveAppDef, appDefaults.liveStrategyCheck, isVirtualActive, refreshActiveLiveSettings]);
|
||||
|
||||
const prevFavoritesRef = useRef<string[]>(getFavorites());
|
||||
|
||||
@@ -808,10 +863,21 @@ function App() {
|
||||
return () => { cancelled = true; };
|
||||
}, [dbLoaded]);
|
||||
|
||||
const liveStrategyName = useMemo(
|
||||
() => liveStrategies.find(s => s.id === appDefaults.liveStrategyId)?.name ?? null,
|
||||
[liveStrategies, appDefaults.liveStrategyId],
|
||||
);
|
||||
const liveStrategyName = useMemo(() => {
|
||||
const sid = liveStrategySettings.strategyId;
|
||||
return sid != null ? liveStrategies.find(s => s.id === sid)?.name ?? null : null;
|
||||
}, [liveStrategies, liveStrategySettings.strategyId]);
|
||||
|
||||
const liveStrategyNameByMarket = useMemo(() => {
|
||||
const map: Record<string, string> = {};
|
||||
for (const s of activeLiveSettings) {
|
||||
if (s.strategyId != null) {
|
||||
const name = liveStrategies.find(st => st.id === s.strategyId)?.name;
|
||||
if (name) map[s.market] = name;
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}, [activeLiveSettings, liveStrategies]);
|
||||
|
||||
const goToMarketChart = useCallback((market: string) => {
|
||||
if (layoutDef.count > 1) {
|
||||
@@ -1477,9 +1543,13 @@ function App() {
|
||||
activeMarket={symbol}
|
||||
enabled={monitoredMarkets.length > 0}
|
||||
popupEnabled={appDefaults.tradeAlertPopup ?? true}
|
||||
strategyId={appDefaults.liveStrategyId ?? undefined}
|
||||
executionType={appDefaults.liveExecutionType ?? 'CANDLE_CLOSE'}
|
||||
strategyId={liveStrategySettings.strategyId ?? undefined}
|
||||
executionType={liveStrategySettings.executionType ?? 'CANDLE_CLOSE'}
|
||||
strategyName={liveStrategyName}
|
||||
strategyNameByMarket={liveStrategyNameByMarket}
|
||||
executionTypeByMarket={Object.fromEntries(
|
||||
activeLiveSettings.map(s => [s.market, s.executionType ?? 'CANDLE_CLOSE']),
|
||||
)}
|
||||
chartMarkersActive={menuPage === 'chart'}
|
||||
onMarkersChange={markers => {
|
||||
managerRef.current?.setLiveStrategyMarkers(markers, appDefaults.btShowPrice);
|
||||
@@ -1537,7 +1607,7 @@ function App() {
|
||||
defaultMarket={symbol}
|
||||
paperTradingEnabled={paperTradingEnabled}
|
||||
paperAutoTradeEnabled={paperAutoTradeEnabled}
|
||||
onPaperOrderFilled={() => setPaperRefreshKey(k => k + 1)}
|
||||
onPaperOrderFilled={handlePaperOrderFilled}
|
||||
onGoChart={m => {
|
||||
goToMarketChart(m);
|
||||
setMenuPage('chart');
|
||||
@@ -1552,7 +1622,9 @@ function App() {
|
||||
defaultMarket={symbol}
|
||||
paperTradingEnabled={paperTradingEnabled}
|
||||
paperAutoTradeEnabled={paperAutoTradeEnabled}
|
||||
onPaperOrderFilled={() => setPaperRefreshKey(k => k + 1)}
|
||||
paperAutoTradeBudgetPct={appDefaults.paperAutoTradeBudgetPct}
|
||||
onPaperAutoTradeEnabled={v => saveAppDef({ paperAutoTradeEnabled: v })}
|
||||
onPaperOrderFilled={handlePaperOrderFilled}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1596,6 +1668,7 @@ function App() {
|
||||
onLiveSettingsChange={handleLiveSettingsChange}
|
||||
watchlistCount={watchlist.length}
|
||||
monitoredMarkets={monitoredMarkets}
|
||||
virtualDriven={isVirtualActive}
|
||||
getIndicatorParams={getIndicatorParams}
|
||||
saveIndicatorParams={saveIndicatorParams}
|
||||
getIndicatorVisual={getIndicatorVisual}
|
||||
@@ -2080,8 +2153,9 @@ function App() {
|
||||
market={symbol}
|
||||
strategies={liveStrategies}
|
||||
settings={liveStrategySettings}
|
||||
watchlistCount={watchlist.length}
|
||||
watchlistCount={isVirtualActive ? monitoredMarkets.length : watchlist.length}
|
||||
monitoredMarkets={monitoredMarkets}
|
||||
virtualDriven={isVirtualActive}
|
||||
onClearMarkers={() => { clearLiveMarkers(); managerRef.current?.clearLiveStrategyMarkers(); }}
|
||||
onSettingsChange={handleLiveSettingsChange}
|
||||
onClose={() => setShowLivePanel(false)}
|
||||
@@ -2303,7 +2377,7 @@ function App() {
|
||||
paperAutoTradeEnabled={paperAutoTradeEnabled}
|
||||
onPaperOrderFilled={() => {
|
||||
refreshPaperAccount(symbol);
|
||||
setPaperRefreshKey(k => k + 1);
|
||||
handlePaperOrderFilled();
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -2407,7 +2481,7 @@ function App() {
|
||||
tradeAlertPopupGridCols={appDefaults.tradeAlertPopupGridCols}
|
||||
onOrderDone={() => {
|
||||
refreshPaperAccount(symbol);
|
||||
setPaperRefreshKey(k => k + 1);
|
||||
handlePaperOrderFilled();
|
||||
}}
|
||||
/>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user