TestForge | 📊 Plogger ✍️ Blog 📚 Docs
TestForge Blog
← 전체 포스트

WebSocket 틱 데이터로 수급 흐름을 간접 추적하는 법 - 체결강도·VWAP·대량체결 감지 패턴

외인·기관 수급 정보는 WebSocket으로 직접 받을 수 없습니다. 하지만 S3_/K3_ 틱 데이터에서 체결강도·VWAP·대량체결 신호를 뽑아내면, REST 조회 시점을 정확하게 잡을 수 있습니다. 실전 3-Layer 패턴을 정리합니다.

TestForge Team ·

틱 데이터로 수급을 알 수 있을까

결론부터 말하면 “직접은 안 된다, 간접은 된다”입니다.

증권사 WebSocket(xingAPI S3_, K3_)에서 오는 실시간 체결 데이터에는 외인이 샀는지 기관이 샀는지 구분하는 필드가 없습니다.

실제로 수급 정보를 얻으려면 REST 조회가 필요합니다.

  • t1702: 종목별 외인·기관 일별 매매동향
  • t1602: 시장 전체 투자자별 순매수 흐름

그런데 REST를 모든 종목에 항상 호출하면 Rate Limit에 걸립니다.
바로 이 지점에서 WebSocket 틱 데이터가 역할을 가집니다.

틱 데이터 = REST 조회 타이밍을 정하는 감지기

S3_/K3_ 에서 읽어야 하는 필드

보통 구현에서는 price, volume, cvolume만 읽는 경우가 많습니다.
하지만 수급 추적에 쓰이는 핵심 필드가 따로 있습니다.

필드설명활용
cvolume이 틱의 체결량대량체결 감지
msvolume매수 체결수량체결강도 계산
mdvolume매도 체결수량체결강도 계산
volume누적 거래량가속도 계산
price현재가VWAP 누적

msvolumemdvolume이 빠지면 체결 방향을 알 수 없습니다.

패턴 1: 체결강도 지수 (Buy Pressure Index)

# 틱마다 계산
buy_pressure = msvolume / (msvolume + mdvolume)

# 해석 기준
# 0.65 이상 지속 → 매수 주도 체결 구간
# 0.35 이하 지속 → 매도 주도 체결 구간

단일 틱보다 롤링 윈도우로 봐야 합니다.

from collections import deque

class BuyPressureTracker:
    def __init__(self, window: int = 50):
        self._buy = deque(maxlen=window)
        self._sell = deque(maxlen=window)

    def push(self, ms: int, md: int):
        self._buy.append(ms)
        self._sell.append(md)

    @property
    def pressure(self) -> float:
        total_buy = sum(self._buy)
        total_sell = sum(self._sell)
        denom = total_buy + total_sell
        return total_buy / denom if denom > 0 else 0.5

50틱 누적 기준으로 pressure > 0.65가 유지되면
”지금 이 종목에 매수 수요가 공급을 압도하고 있다”는 신호입니다.

패턴 2: 실시간 VWAP 계산

VWAP은 기관이 분할 매수 집행 시 기준으로 삼는 지표입니다.
현재가가 VWAP 위에서 계속 거래되면 “기관이 VWAP보다 비싸게 사도 계속 사고 있다”는 의미입니다.

class VWAPTracker:
    def __init__(self):
        self._cum_pv = 0.0  # 가격 × 체결량 누적
        self._cum_vol = 0   # 체결량 누적

    def push(self, price: int, cvolume: int):
        self._cum_pv += price * cvolume
        self._cum_vol += cvolume

    @property
    def vwap(self) -> float:
        return self._cum_pv / self._cum_vol if self._cum_vol > 0 else 0.0

    def above_vwap(self, price: int) -> bool:
        return self.vwap > 0 and price > self.vwap

장 시작 시 초기화하고 틱마다 업데이트합니다.

패턴 3: 대량체결 감지 (Block Trade Detector)

기관 주문은 분할되지만, 특정 시점에 평균보다 훨씬 큰 틱이 터지는 경우가 있습니다.

from collections import deque
import statistics

class BlockTradeDetector:
    def __init__(self, window: int = 100, threshold: float = 5.0):
        self._history = deque(maxlen=window)
        self._threshold = threshold

    def push(self, cvolume: int) -> bool:
        self._history.append(cvolume)
        if len(self._history) < 20:
            return False
        avg = statistics.mean(self._history)
        return avg > 0 and cvolume > avg * self._threshold

단일 틱에서 평균 체결량의 5배 이상이 터지면 플래그를 올립니다.
이 플래그가 올라간 직후 t1702 REST 조회를 트리거하면 됩니다.

패턴 4: 거래량 가속도 (Volume Acceleration)

분 단위 거래량이 갑자기 늘어나는 구간을 잡는 패턴입니다.

class VolumeAccelerationDetector:
    def __init__(self, window_minutes: int = 5, threshold: float = 3.0):
        self._minute_vols: deque[int] = deque(maxlen=window_minutes)
        self._current_minute_start_vol = 0
        self._threshold = threshold

    def update(self, cumulative_volume: int, minute_changed: bool):
        if minute_changed:
            minute_vol = cumulative_volume - self._current_minute_start_vol
            self._minute_vols.append(minute_vol)
            self._current_minute_start_vol = cumulative_volume

    @property
    def is_accelerating(self) -> bool:
        if len(self._minute_vols) < 3:
            return False
        avg = statistics.mean(list(self._minute_vols)[:-1])
        latest = self._minute_vols[-1]
        return avg > 0 and latest > avg * self._threshold

3-Layer 패턴으로 조합하기

이 네 가지 신호를 단독으로 쓰면 오탐이 많습니다.
조건을 조합해서 REST 조회 트리거를 만드는 것이 핵심입니다.

틱 푸시 (S3_/K3_)

  ├─ buy_pressure > 0.65 (50틱 롤링)
  ├─ is_accelerating (분 단위 가속)
  └─ block_trade 감지

        └─ 2개 이상 동시 충족


        t1702 REST 조회 → 5일 외인·기관 순매수 추세 확인
        t1602 REST 조회 → 시장 전체 외인 자금 방향 확인


        ✅ REST도 같은 방향 → 진입 시그널
        ❌ REST가 다른 방향 → HOLD

조건 하나만 봐서는 노이즈입니다.
WebSocket이 먼저 “지금 뭔가 일어나고 있다”를 알려주고,
REST가 “그게 외인인지 기관인지”를 확인해주는 역할 분리가 핵심입니다.

구현 시 주의할 점

하루 시작마다 초기화
VWAP, 누적 거래량은 장 시작(09:00)에 리셋해야 합니다.

Rate Limit 고려
REST 트리거는 종목당 최소 30초 간격을 두는 것이 좋습니다.
짧은 시간 안에 같은 종목의 조건이 반복 충족되면 첫 번째만 트리거합니다.

코스피와 코스닥 분리
S3_는 코스피, K3_는 코스닥입니다.
같은 로직을 각각 인스턴스로 만들어 독립 운영해야 합니다.

정리

신호계산 근거REST 연계
체결강도msvolume / (msvolume + mdvolume)t1702 트리거
VWAP 이탈가격 × 체결량 누적t1702 트리거
대량체결cvolume vs 평균t1702 즉시 트리거
거래량 가속분 단위 비율t1602 트리거

WebSocket은 감지기, REST는 확인기.
이 역할 분리가 실시간 수급 추적의 핵심 구조입니다.