실시간 웹소켓 리팩토링
This commit is contained in:
@@ -3,12 +3,16 @@ import type { KisRuntimeCredentials } from "@/features/settings/store/use-kis-ru
|
||||
import type { DashboardRealtimeTradeTick } from "@/features/trade/types/trade.types";
|
||||
import { useKisWebSocketStore } from "@/features/kis-realtime/stores/kisWebSocketStore";
|
||||
import {
|
||||
extractKisRealtimeTrId,
|
||||
parseKisRealtimeTickBatch,
|
||||
resolveTradeTrIds,
|
||||
shouldAcceptRealtimeMessageByPriority,
|
||||
} from "@/features/trade/utils/kisRealtimeUtils";
|
||||
import type { DomesticKisSession } from "@/lib/kis/domestic-market-session";
|
||||
|
||||
const MAX_TRADE_TICKS = 10;
|
||||
const STABLE_SOURCE_STALE_MS = Number.POSITIVE_INFINITY;
|
||||
const FLEXIBLE_SOURCE_STALE_MS = 3_000;
|
||||
|
||||
interface UseTradeTickSubscriptionParams {
|
||||
symbol: string | undefined;
|
||||
@@ -38,6 +42,8 @@ export function useTradeTickSubscription({
|
||||
>([]);
|
||||
const [lastTickAt, setLastTickAt] = useState<number | null>(null);
|
||||
const seenTickRef = useRef<Set<string>>(new Set());
|
||||
const activeTradeTrIdRef = useRef<string | null>(null);
|
||||
const activeTradeTrUpdatedAtRef = useRef(0);
|
||||
|
||||
const { subscribe, connect } = useKisWebSocketStore();
|
||||
const onTickRef = useRef(onTick);
|
||||
@@ -59,6 +65,8 @@ export function useTradeTickSubscription({
|
||||
// Ref는 렌더링 도중 수정하면 안 되므로 useEffect에서 초기화
|
||||
useEffect(() => {
|
||||
seenTickRef.current.clear();
|
||||
activeTradeTrIdRef.current = null;
|
||||
activeTradeTrUpdatedAtRef.current = 0;
|
||||
}, [symbol]);
|
||||
|
||||
// 2. 실시간 데이터 구독
|
||||
@@ -71,21 +79,63 @@ export function useTradeTickSubscription({
|
||||
const unsubscribers: Array<() => void> = [];
|
||||
|
||||
const handleTradeMessage = (data: string) => {
|
||||
const incomingTrId = extractKisRealtimeTrId(data);
|
||||
if (!incomingTrId) return;
|
||||
|
||||
// UI 흐름: 소켓 수신 -> TR 우선순위 고정(ST 우선) -> 파싱 -> 상태 반영
|
||||
const shouldAccept = shouldAcceptRealtimeMessageByPriority({
|
||||
incomingTrId,
|
||||
preferredTrIds: trIds,
|
||||
activeTrId: activeTradeTrIdRef.current,
|
||||
activeTrUpdatedAtMs: activeTradeTrUpdatedAtRef.current,
|
||||
// 장중에는 상위 소스를 고정하고, 시간외에서는 상위 소스가 잠잠할 때 하위(예상체결)로 폴백 허용
|
||||
staleAfterMs:
|
||||
marketSession === "regular" ||
|
||||
marketSession === "openAuction" ||
|
||||
marketSession === "closeAuction"
|
||||
? STABLE_SOURCE_STALE_MS
|
||||
: FLEXIBLE_SOURCE_STALE_MS,
|
||||
});
|
||||
if (!shouldAccept) return;
|
||||
|
||||
const ticks = parseKisRealtimeTickBatch(data, symbol);
|
||||
if (ticks.length === 0) return;
|
||||
|
||||
const meaningfulTicks = ticks.filter((tick) => tick.tradeVolume > 0);
|
||||
if (meaningfulTicks.length === 0) return;
|
||||
const executedTicks = ticks.filter(
|
||||
(tick) => !tick.isExpected && tick.tradeVolume > 0,
|
||||
);
|
||||
const expectedTicks = ticks.filter((tick) => tick.isExpected);
|
||||
|
||||
const dedupedTicks = meaningfulTicks.filter((tick) => {
|
||||
const key = `${tick.tickTime}-${tick.price}-${tick.tradeVolume}`;
|
||||
// 시간외 예상체결은 가격 갱신용으로만 사용하고, 체결목록(recentTradeTicks)에는 넣지 않습니다.
|
||||
// UI 흐름: 소켓 수신 -> 예상체결 파싱 -> latestTick 갱신 -> 헤더/중앙가 반영
|
||||
if (executedTicks.length === 0 && expectedTicks.length > 0) {
|
||||
const latestExpected = expectedTicks[expectedTicks.length - 1];
|
||||
const expectedForDisplay = {
|
||||
...latestExpected,
|
||||
tradeVolume: 0,
|
||||
};
|
||||
activeTradeTrIdRef.current = incomingTrId;
|
||||
activeTradeTrUpdatedAtRef.current = Date.now();
|
||||
setLatestTick(expectedForDisplay);
|
||||
setLastTickAt(Date.now());
|
||||
onTickRef.current?.(expectedForDisplay);
|
||||
return;
|
||||
}
|
||||
|
||||
if (executedTicks.length === 0) return;
|
||||
|
||||
activeTradeTrIdRef.current = incomingTrId;
|
||||
activeTradeTrUpdatedAtRef.current = Date.now();
|
||||
|
||||
const dedupedTicks = executedTicks.filter((tick) => {
|
||||
const key = `exe-${incomingTrId}-${tick.tickTime}-${tick.price}-${tick.tradeVolume}`;
|
||||
if (seenTickRef.current.has(key)) return false;
|
||||
seenTickRef.current.add(key);
|
||||
if (seenTickRef.current.size > 5_000) seenTickRef.current.clear();
|
||||
return true;
|
||||
});
|
||||
|
||||
const latest = meaningfulTicks[meaningfulTicks.length - 1];
|
||||
const latest = executedTicks[executedTicks.length - 1];
|
||||
setLatestTick(latest);
|
||||
setLastTickAt(Date.now());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user