스탑 헌팅이란?
스탑 헌팅(Stop Hunting)은 대형 트레이더나 마켓 메이커가 개인 투자자들의 손절 주문이 몰려 있는 가격대를 의도적으로 터치한 뒤, 반대 방향으로 가격을 되돌리는 시장 현상입니다. 암호화폐 선물 시장에서 특히 빈번하게 발생하며, 이를 감지하고 역이용하는 전략은 퀀트 자동매매에서 강력한 엣지가 될 수 있습니다.
스탑 헌팅은 단순한 음모론이 아닙니다. 유동성이 집중된 가격대를 노리는 것은 대형 포지션을 채우기 위한 합리적인 유동성 확보 전략이기 때문입니다. 손절 주문이 트리거되면 대량의 시장가 주문이 쏟아지고, 이 유동성을 흡수하여 원하는 가격에 포지션을 구축할 수 있습니다.
스탑 헌팅 발생 메커니즘
스탑 헌팅은 일반적으로 다음 단계로 진행됩니다.
| 단계 | 시장 행동 | 오더북 변화 |
|---|---|---|
| 1. 축적 | 좁은 레인지에서 횡보 | 지지·저항 근처 손절 주문 축적 |
| 2. 돌파 | 급격한 가격 이동으로 손절 트리거 | 대량 시장가 주문 발생, 유동성 소진 |
| 3. 흡수 | 거래량 급증 후 가격 정체 | 대형 지정가 주문이 유동성 흡수 |
| 4. 반전 | 원래 방향으로 급격히 복귀 | 새로운 추세 형성 |
파이썬으로 스탑 헌팅 감지하기
스탑 헌팅을 자동으로 감지하려면 세 가지 핵심 신호를 조합해야 합니다: 가격 스파이크, 거래량 급증, 그리고 빠른 반전입니다.
import pandas as pd
import numpy as np
from dataclasses import dataclass
from typing import Optional
@dataclass
class StopHuntSignal:
timestamp: str
direction: str # 'long_hunt' or 'short_hunt'
spike_price: float # 스파이크 도달 가격
recovery_price: float
volume_ratio: float # 평균 대비 거래량 배수
wick_ratio: float # 꼬리 비율
confidence: float # 신뢰도 (0~1)
class StopHuntDetector:
def __init__(
self,
wick_threshold: float = 0.6,
volume_multiplier: float = 2.0,
lookback: int = 20,
recovery_pct: float = 0.7
):
self.wick_threshold = wick_threshold
self.volume_multiplier = volume_multiplier
self.lookback = lookback
self.recovery_pct = recovery_pct
def detect(
self, df: pd.DataFrame
) -> list[StopHuntSignal]:
"""
OHLCV 데이터에서 스탑 헌팅 패턴 감지
df 컬럼: timestamp, open, high, low, close, volume
"""
signals = []
df = df.copy()
df['avg_volume'] = (
df['volume'].rolling(self.lookback).mean()
)
df['atr'] = self._calc_atr(df, self.lookback)
df['body'] = abs(df['close'] - df['open'])
df['full_range'] = df['high'] - df['low']
for i in range(self.lookback, len(df)):
row = df.iloc[i]
if row['full_range'] == 0:
continue
signal = self._check_candle(row, df, i)
if signal:
signals.append(signal)
return signals
def _check_candle(
self, row, df, idx
) -> Optional[StopHuntSignal]:
body = abs(row['close'] - row['open'])
full_range = row['high'] - row['low']
if full_range == 0:
return None
# 아래꼬리 비율 (롱 스탑 헌팅 감지)
lower_wick = min(row['open'], row['close']) - row['low']
lower_wick_ratio = lower_wick / full_range
# 위꼬리 비율 (숏 스탑 헌팅 감지)
upper_wick = row['high'] - max(row['open'], row['close'])
upper_wick_ratio = upper_wick / full_range
vol_ratio = row['volume'] / row['avg_volume']
spike_size = full_range / row['atr']
# 롱 스탑 헌팅: 아래로 스파이크 후 회복
if (lower_wick_ratio >= self.wick_threshold
and vol_ratio >= self.volume_multiplier
and spike_size >= 1.5
and row['close'] > row['open']):
confidence = min(1.0, (
lower_wick_ratio * 0.4
+ min(vol_ratio / 5, 1) * 0.3
+ min(spike_size / 3, 1) * 0.3
))
return StopHuntSignal(
timestamp=str(row['timestamp']),
direction='long_hunt',
spike_price=row['low'],
recovery_price=row['close'],
volume_ratio=round(vol_ratio, 2),
wick_ratio=round(lower_wick_ratio, 3),
confidence=round(confidence, 3)
)
# 숏 스탑 헌팅: 위로 스파이크 후 하락
if (upper_wick_ratio >= self.wick_threshold
and vol_ratio >= self.volume_multiplier
and spike_size >= 1.5
and row['close'] < row['open']):
confidence = min(1.0, (
upper_wick_ratio * 0.4
+ min(vol_ratio / 5, 1) * 0.3
+ min(spike_size / 3, 1) * 0.3
))
return StopHuntSignal(
timestamp=str(row['timestamp']),
direction='short_hunt',
spike_price=row['high'],
recovery_price=row['close'],
volume_ratio=round(vol_ratio, 2),
wick_ratio=round(upper_wick_ratio, 3),
confidence=round(confidence, 3)
)
return None
@staticmethod
def _calc_atr(df: pd.DataFrame, period: int):
high_low = df['high'] - df['low']
high_close = abs(df['high'] - df['close'].shift(1))
low_close = abs(df['low'] - df['close'].shift(1))
tr = pd.concat(
[high_low, high_close, low_close], axis=1
).max(axis=1)
return tr.rolling(period).mean()
청산 데이터로 헌팅 존 예측하기
거래소 청산 데이터를 활용하면 스탑 헌팅이 발생할 가능성이 높은 가격대를 미리 예측할 수 있습니다. 대량 포지션의 청산 가격이 집중된 구간이 바로 유동성 풀, 즉 헌팅 타겟입니다.
def estimate_liquidation_clusters(
positions: pd.DataFrame,
current_price: float,
price_range_pct: float = 5.0
) -> pd.DataFrame:
"""
오픈 포지션 데이터에서 청산 클러스터 추정
positions 컬럼: entry_price, leverage, side, size
"""
positions = positions.copy()
# 청산 가격 추정 (단순화)
positions['liq_price'] = np.where(
positions['side'] == 'long',
positions['entry_price'] * (
1 - 1 / positions['leverage'] * 0.9
),
positions['entry_price'] * (
1 + 1 / positions['leverage'] * 0.9
)
)
# 현재 가격 근처만 필터링
lower = current_price * (1 - price_range_pct / 100)
upper = current_price * (1 + price_range_pct / 100)
nearby = positions[
(positions['liq_price'] >= lower)
& (positions['liq_price'] <= upper)
]
# 가격 구간별 청산 규모 집계
bins = np.linspace(lower, upper, 50)
nearby['price_bin'] = pd.cut(
nearby['liq_price'], bins=bins
)
clusters = nearby.groupby('price_bin')['size'].sum()
clusters = clusters.sort_values(ascending=False)
return clusters.head(10)
스탑 헌팅 역이용 매매 전략
감지된 스탑 헌팅 신호를 실제 매매에 활용하는 전략을 구현합니다. 핵심은 스파이크 반전 확인 후 진입하고, 스파이크 극값을 손절로 설정하는 것입니다.
class StopHuntTrader:
def __init__(
self,
risk_per_trade: float = 0.01,
min_confidence: float = 0.6,
reward_ratio: float = 2.0
):
self.risk_per_trade = risk_per_trade
self.min_confidence = min_confidence
self.reward_ratio = reward_ratio
def generate_order(
self,
signal: StopHuntSignal,
account_balance: float,
current_price: float
) -> Optional[dict]:
if signal.confidence < self.min_confidence:
return None
if signal.direction == 'long_hunt':
entry = signal.recovery_price
stop_loss = signal.spike_price * 0.998
risk = (entry - stop_loss) / entry
take_profit = (
entry + (entry - stop_loss) * self.reward_ratio
)
side = 'buy'
else:
entry = signal.recovery_price
stop_loss = signal.spike_price * 1.002
risk = (stop_loss - entry) / entry
take_profit = (
entry - (stop_loss - entry) * self.reward_ratio
)
side = 'sell'
if risk <= 0:
return None
position_size = (
account_balance * self.risk_per_trade / risk
)
return {
'side': side,
'entry_price': round(entry, 2),
'stop_loss': round(stop_loss, 2),
'take_profit': round(take_profit, 2),
'position_size_usd': round(position_size, 2),
'risk_pct': round(risk * 100, 3),
'confidence': signal.confidence,
'signal_time': signal.timestamp
}
# 사용 예시
detector = StopHuntDetector(
wick_threshold=0.65,
volume_multiplier=2.5
)
signals = detector.detect(ohlcv_df)
trader = StopHuntTrader(
risk_per_trade=0.01,
min_confidence=0.65,
reward_ratio=2.5
)
for sig in signals:
order = trader.generate_order(
sig, account_balance=10000,
current_price=84000
)
if order:
print(f"[{sig.direction}] {order}")
필터링으로 승률 높이기
스탑 헌팅 신호의 정확도를 높이기 위해 추가 필터를 적용합니다.
- 상위 타임프레임 추세 확인: 4시간·일봉의 추세 방향과 일치하는 신호만 채택합니다. 상승 추세에서 롱 스탑 헌팅, 하락 추세에서 숏 스탑 헌팅이 더 신뢰도가 높습니다.
- 주요 지지·저항 레벨: 과거 고점·저점, 라운드 넘버(예: BTC $80,000) 근처에서 발생한 신호가 더 유효합니다.
- 펀딩비 확인: 극단적인 펀딩비는 한쪽 포지션 쏠림을 의미하므로, 반대 방향 스탑 헌팅 가능성이 높아집니다.
- 오픈 인터레스트 급변: 스파이크 구간에서 미결제약정이 급감하면 실제 청산이 발생한 것으로, 신호의 신뢰도가 올라갑니다.
- 연속 신호 필터: 같은 방향 신호가 짧은 시간 내 반복되면 하나만 채택하여 중복 진입을 방지합니다.
def filter_by_trend(
signals: list[StopHuntSignal],
higher_tf_trend: str
) -> list[StopHuntSignal]:
"""상위 타임프레임 추세 필터"""
filtered = []
for s in signals:
if (higher_tf_trend == 'bullish'
and s.direction == 'long_hunt'):
filtered.append(s)
elif (higher_tf_trend == 'bearish'
and s.direction == 'short_hunt'):
filtered.append(s)
return filtered
def filter_near_levels(
signals: list[StopHuntSignal],
key_levels: list[float],
tolerance_pct: float = 0.5
) -> list[StopHuntSignal]:
"""주요 가격대 근접 필터"""
filtered = []
for s in signals:
for level in key_levels:
dist = abs(s.spike_price - level) / level * 100
if dist <= tolerance_pct:
filtered.append(s)
break
return filtered
리스크 관리 핵심 포인트
스탑 헌팅 역이용 전략은 높은 손익비를 추구하지만, 모든 신호가 정확하지는 않습니다. 다음 원칙을 반드시 지켜야 합니다.
- 거래당 리스크 1% 이하: 잘못된 신호에서의 손실을 최소화합니다.
- 손절은 스파이크 극값 바깥: 스파이크 저점(롱) 또는 고점(숏) 아래/위에 설정합니다.
- 최소 손익비 2:1: 승률 40%에서도 수익을 유지하려면 손익비가 핵심입니다.
- 신뢰도 0.65 이상만 진입: 낮은 신뢰도 신호는 관망합니다.
- 동시 포지션 제한: 같은 방향 포지션은 최대 2~3개로 제한합니다.
마무리
스탑 헌팅은 시장의 구조적 특성에서 비롯되는 반복적인 패턴입니다. 긴 꼬리 캔들 + 거래량 급증 + 빠른 가격 회복이라는 세 가지 조건을 자동으로 감지하고, 상위 타임프레임 추세와 청산 클러스터 분석을 결합하면 높은 확률의 진입 기회를 포착할 수 있습니다. 다만 완벽한 전략은 없으므로, 철저한 리스크 관리를 병행해야 합니다.
관련 글도 함께 확인해 보세요: