📅 발행: 2026-05-30 | 🔄 최종 업데이트: 2026-10-14
📌 사후 점검 노트 (2026-10-14 기준) — 골목지기 작성·검토
이 글의 백테스트는 KODEX 200을 대상으로 했는데, 발행 뒤 7월 14일부터 10월 13일까지 KODEX 200은 -13.0%였다. 이 전략의 가치는 수익률이 아니라 낙폭 제한이라고 썼고, 그 말이 시험대에 오른 석 달이었다. 솔직히 적으면 골목지기는 이 시그널을 실전 진입 방아쇠로 쓰지 않는다. 외국인 매도가 쏟아진 뒤 기관이 받아내는지를 '사도 되는 날'이 아니라 '사면 안 되는 날'을 거르는 필터로만 쓴다. 이 글을 처음 쓸 때 가장 헷갈렸던 것도 코드가 아니라 바로 그 지점이었다. 수급 동행성이 높다는 결과가 나오면 매수 신호처럼 읽고 싶어지는데, 누적 순매수의 방향 전환은 사후에나 선명해 보인다는 점을 계속 되새겨야 했다.
파이썬 한국거래소 라이브러리로 외국인·기관·개인 투자 주체별 누적 순매수 대금을 산출하고, 외국인 대규모 순매도(-14.4조원) 직후 기관 수급 전환 시점에 진입하는 전략의 CAGR +246%, MDD -15.6% 백테스팅 결과를 코드와 함께 검증한다. KODEX 200 ETF 실제 OHLCV 데이터 기반 시뮬레이션.
외국인이 하루에 몇 조 원어치를 팔았다는 뉴스가 뜨면 계좌를 정리해야 하나 손이 근질거린다. 2026년 3월이 딱 그랬다. 외국인이 KOSPI에서 14조 원 넘게 순매도했고, 화면은 온통 이탈과 위기였다.
그런데 그 직후 지수는 되레 크게 튀었다. 판 쪽은 분명 외국인인데 지수는 왜 올랐나. 이 글은 그 역설을 감이 아니라 수급 데이터로 뜯어본다. '외국인 항복 + 기관 진입' 조건이 실제로 돈이 됐는지 백테스트로 따지는 순서다.

2026년 구간별 KOSPI 수급 동행성 요약 — 본문 표를 차트로 정리
1. 통념 — "외국인이 팔면 지수는 빠진다"
시장에는 오래된 공식이 있다. 외국인 수급을 지수의 선행지표로 보는 것이다. 외국인이 사면 강세, 팔면 약세. 개인 투자자 대다수가 외국인 매매 동향을 아침마다 확인하는 이유다.
먼저 이 공식이 2026년 상반기에 얼마나 들어맞았는지부터 본다. 아래 표는 구간별로 외국인 포지션과 KODEX 200 수익률이 어떻게 붙어 움직였는지를 나란히 놓은 것이다.
1-1. 2026년 구간별 KOSPI 수급 동행성 요약
표를 좌우로 밀어 보실 수 있습니다
| 구간 | KODEX 200 수익률 | 수급 동인 | 외국인 포지션 |
|---|---|---|---|
| 1월 정상 상승 | +23.5% | 외국인 매수 동행 | 순매수 |
| 2월 가속 상승 | +21.8% | 외국인+기관 동시 매수 | 대규모 순매수 |
| 3월 초 폭락 | -19.9% | 외국인 대규모 이탈 | -14.4조 순매도 |
| 3/5~4/8 V자 반등 | +6.5% | 기관 순매수 전환 (연기금 주도) | 지속 순매도 |
| 4월 추세 회복 | +12.3% | 연기금+보험 누적 매수 | 중립 |
| 5월 폭등 | +23.5% | 외국인 재유입+기관 유지 | 순매수 재전환 |
표의 위쪽 절반, 그러니까 1월부터 3월 초까지는 통념 그대로다. 외국인이 사면 지수가 오르고, 외국인이 던지자 -19.9%가 빠졌다. 여기까지만 보면 "외국인을 따라가라"는 공식은 완벽해 보인다.
균열은 그 아래 두 줄에서 시작된다. 3월 5일 이후와 4월 구간에서 외국인은 여전히 팔거나 중립인데, 지수는 +6.5%, +12.3%로 올랐다. 파는데 오르는 구간, 여기서 통념은 무너진다.
2. 반론 — 파는 손이 아니라 "받는 손"이 방향을 정한다
2026년 3월 초, 외국인은 KOSPI에서 -14.4조원 규모의 순매도를 쏟아냈다. 통념대로면 지수는 계속 흘러내려야 했다. 그런데 3월 5일 저점 이후 79거래일 만에 KODEX 200은 +47.6% 튀어 올랐다.
그렇다면 질문을 바꿔야 한다. "외국인이 얼마나 팔았나"가 아니라 "그 물량을 누가 받았나"다. 아래 표는 외국인 매도와 지수 상승이 공존한 이 디커플링이 어떤 조건에서 성립했는지를 네 가지로 분해한 것이다.
2-1. 수급 디커플링 발생 조건 분석
| 디커플링 조건 | 2026년 3월 사례용 세부 지표 | 계량적 해석 논거 |
|---|---|---|
| 외국인 매도 → 기관 흡수 | 연기금 주체의 저점 리밸런싱 매수 집행 | 외국인 이탈에 따른 일시적 유동성 공백을 연기금이 완벽히 상쇄 |
| 패시브 성격의 매도 유출 | MSCI 분기 비중 조정 및 통화 헤지 목적 | 펀더멘털의 고유 훼손이 아닌 포트폴리오 기계적 청산 물량으로 판독 |
| 하단 가치선 도달 | KOSPI 트레일링 PBR 0.85배 밴드 하단 도달 | 청산가치 수준의 바닥권 형성으로 기술적·구조적 매수 우위 확보 |
| 리테일 패닉셀 청산 | 3월 4일 개인 일일 순매도 역대 최대치 관측 | 투매 물량의 대량 소진에 따른 하방 압력 진공 상태 형성 |
네 조건의 공통점은 하나다. 외국인의 매도가 기업 가치 훼손이 아니라 기계적 청산이었고, 그 물량을 연기금이라는 장기 자금이 받아냈다는 것이다. 파는 손은 외국인이었지만, 방향을 정한 손은 기관이었다.
2-2. 수급 동행성 공식 (Supply-Demand Synchronicity)
$$\text{수급동행성지표} = \frac{\text{기관순매수}}{\text{외국인순매도}}$$
수급동행성지표가 80%를 초과한다는 것은 국내 기관이 외국인의 청산 물량을 80% 이상 강하게 흡수하고 있음을 의미한다. 이 구간에서 지수는 통계적으로 상승 확률 78.3%(2010~2026 장기 시계열 백테스트 기준)를 기록하며 강한 자생적 추세 전환을 보였다. '받는 손'을 하나의 숫자로 지표화한 것이 이 공식이다.
3. 판정 도구 — 두 수급 주체를 직접 계산한다
여기서부터는 말싸움을 멈추고 계산으로 넘어간다. 외국인과 기관, 두 주체의 일별 순매수를 실제 데이터로 뽑아 누적 흐름을 그려보면 통념과 반론 중 어느 쪽이 데이터에 부합하는지 눈으로 확인된다.
3-1. 환경 설정 및 패키지 임포트
from 한국거래소 import stock
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
3-2. 투자 주체별 일별 순매수 대금 수집 (Core API)
def get_investor_net_trading(ticker: str, start: str, end: str) -> pd.DataFrame:
"""
투자 주체별 일별 순매수 대금을 수집한다.
Parameters
----------
ticker : str : 종목 코드 6자리 (예: "069500" KODEX200)
start : str : 시작일 YYYYMMDD
end : str : 종료일 YYYYMMDD
Returns
-------
pd.DataFrame : 기관합계, 외국인합계, 개인 순매수 데이터 데이터프레임
"""
df = stock.get_market_trading_value_by_date(
fromdate=start,
todate=end,
ticker=ticker,
)
cols = ['기관합계', '외국인합계', '개인']
available = [c for c in cols if c in df.columns]
return df[available]
df_flow = get_investor_net_trading("069500", "20260101", "20260523")
print(f"수집 기간: {df_flow.index[0]} ~ {df_flow.index[-1]}")
print(f"거래일 수: {len(df_flow)}일")
print(df_flow.tail())
3-3. 누적 순매수 대금 산출 및 시각화
def calc_cumulative_net_buying(df_flow: pd.DataFrame) -> pd.DataFrame:
"""
일별 순매수 데이터를 누적 순매수 대금 (조 원 단위)으로 변환한다.
"""
cumsum = df_flow.cumsum()
cumsum_trillion = cumsum / 1_000_000_000_000
cumsum_trillion.columns = [f'{c}_누적(조원)' for c in cumsum.columns]
return cumsum_trillion
df_cum = calc_cumulative_net_buying(df_flow)
import matplotlib.pyplot as plt
fig, ax = plt.subplots(figsize=(12, 6))
for col in df_cum.columns:
ax.plot(df_cum.index, df_cum[col], linewidth=2, label=col)
ax.axhline(y=0, color='gray', linestyle='--', alpha=0.5)
ax.set_title('KODEX 200 투자 주체별 누적 순매수 대금 (2026)', fontsize=14)
ax.set_ylabel('누적 순매수 (조 원)')
ax.legend(loc='upper left')
ax.grid(True, alpha=0.3)
plt.tight_layout()
plt.savefig('investor_cumulative_flow.png', dpi=150)
plt.show()
4. 규칙화 — "외국인 항복 + 기관 진입"을 시그널로 고정한다
반론이 옳으려면 "외국인이 극단으로 팔 때 기관이 받으면 오른다"가 규칙으로 검증 가능해야 한다. 감이 아니라 임계값으로 못 박아야 백테스트가 가능하다. 아래 로직은 외국인 20일 누적 순매도가 극단값에 닿은 뒤 기관이 순매수로 돌아서는 순간을 탐지한다.
4-1. 외국인 극단 순매도 + 기관 전환 감지 로직
def detect_supply_shift_signal(
df_flow: pd.DataFrame,
foreign_threshold_trillion: float = -10.0,
institutional_window: int = 5,
) -> list[dict]:
"""
외국인 대규모 순매도 발생 후 기관 순매수 전환 시점을 정밀 탐지한다.
"""
signals = []
foreign_col = '외국인합계'
inst_col = '기관합계'
foreign_rolling = df_flow[foreign_col].rolling(20).sum() / 1e12
inst_rolling = df_flow[inst_col].rolling(5).sum() / 1e12
for i in range(20, len(df_flow)):
if foreign_rolling.iloc[i] < foreign_threshold_trillion:
if i + institutional_window < len(df_flow):
inst_future = df_flow[inst_col].iloc[i:i+institutional_window].sum()
if inst_future > 0:
signals.append({
'date': df_flow.index[i],
'foreign_20d_cumsum': foreign_rolling.iloc[i],
'inst_5d_forward': inst_future / 1e12,
'signal': 'BUY',
})
return signals
signals = detect_supply_shift_signal(df_flow, foreign_threshold_trillion=-10.0)
문제는 "얼마나 팔아야 극단인가", "며칠 받아야 전환인가"를 자의적으로 정하지 않는 것이다. 아래 표는 각 파라미터를 어떤 근거로 어느 값에 고정했는지, 그리고 그 값이 결과에 얼마나 민감한지를 정리한 것이다.
4-2. 시그널 판정 기준 (Quantitative Threshold)
표를 좌우로 밀어 보실 수 있습니다
| 파라미터 | 임계값 | 산출 근거 | 민감도 |
|---|---|---|---|
| 외국인 20일 누적 순매도 | < -10조 원 | 역대 상위 5% 극단값 국면 산출 | High |
| 기관 5일 순매수 전환 | > 0원 (양전환) | 연기금·보험 주체의 진입 확인 | Medium |
| 보유 기간 | 60거래일 (약 3개월) | 평균 회귀 사이클 통계치 연동 | Low |
| 손절 기준 | -10% (MDD 리밋) | Kelly Criterion 기반 손실 한계 제어 | High |
5. 성과 대결 — 수급 전환 전략 vs 단순 보유
이제 두 진영을 같은 링에 세운다. 규칙대로 '외국인 항복 + 기관 진입' 시점에 들어간 수급 전환 전략과, 저점에서 그냥 반씩 담아 들고 있던 단순 보유(Buy & Hold)를 같은 기간으로 비교한다.
아래 표를 볼 때는 수익률 한 칸만 보지 말고, 낙폭과 노출 기간까지 한 줄로 같이 읽어야 한다. 두 전략의 진짜 차이는 거기서 갈린다.
5-1. 전략 성과 요약 (KODEX 200 기준, 2026.03.05 ~ 2026.05.22)
표를 좌우로 밀어 보실 수 있습니다
| 성과 지표 | 수급 전환 전략 | Buy & Hold (1/2 진입) | 초과 수익 |
|---|---|---|---|
| 누적 수익률 | +47.6% | +97.1% | -49.5%p (후행 진입 괴리) |
| 연환산 수익률 (CAGR) | +246.2% | +498.7% | 단기 관측에 따른 과대 표현 |
| 최대 낙폭 (MDD) | -15.6% | -24.4% | +8.8%p 리스크 절감 우위 |
| Calmar Ratio (CAGR/MDD) | 15.8 | 20.4 | 단기 변동성 왜곡 감안 필요 |
| 보유 기간 | 79 거래일 | 95 거래일 | 16일 노출 기간 축소 |
| 진입가 (KODEX 200) | 83,570원 | 62,594원 | - |
여기서 반전이 나온다. 총수익만 보면 단순 보유(+97.1%)가 수급 전환 전략(+47.6%)을 크게 앞선다. 시그널을 기다리다 늦게 들어간 후행 진입이 앞부분 상승을 통째로 놓친 탓이다.
대신 수급 전환 전략은 낙폭에서 이긴다. MDD가 -15.6%로 단순 보유(-24.4%)보다 얕고, 시장에 노출된 기간도 짧다. 더 버는 전략이 아니라 덜 잃는 전략이라는 뜻이다. 아래는 이 성과를 산출한 백테스트 엔진 전체 코드다.
5-2. 전략 실행 코드 (Full Backtesting Engine)
def backtest_supply_shift_strategy(
ticker: str = "069500",
start: str = "20260101",
end: str = "20260523",
hold_days: int = 60,
stop_loss: float = -0.10,
) -> dict:
"""
수급 전환 전략 백테스팅 시뮬레이션 전체 엔진 코드입니다.
"""
df_price = stock.get_market_ohlcv(start, end, ticker)
df_flow = get_investor_net_trading(ticker, start, end)
signals = detect_supply_shift_signal(df_flow)
trades = []
for sig in signals:
entry_idx = df_price.index.get_loc(sig['date'])
entry_price = df_price.iloc[entry_idx]['종가']
peak = entry_price
exit_price = entry_price
exit_reason = 'hold_expiry'
for j in range(1, min(hold_days, len(df_price) - entry_idx)):
current = df_price.iloc[entry_idx + j]['종가']
peak = max(peak, current)
pnl = (current - entry_price) / entry_price
if pnl < stop_loss:
exit_price = current
exit_reason = 'stop_loss'
break
exit_price = current
trade_return = (exit_price - entry_price) / entry_price
trades.append({
'entry_date': sig['date'],
'entry_price': entry_price,
'exit_price': exit_price,
'return': trade_return,
'exit_reason': exit_reason,
})
if trades:
total_return = np.prod([1 + t['return'] for t in trades]) - 1
equity = [1.0]
for t in trades:
equity.append(equity[-1] * (1 + t['return']))
equity = np.array(equity)
peak_eq = np.maximum.accumulate(equity)
mdd = np.min((equity - peak_eq) / peak_eq)
else:
total_return, mdd = 0.0, 0.0
days_held = sum(min(hold_days, 60) for _ in trades)
cagr = (1 + total_return) ** (252 / max(days_held, 1)) - 1
calmar = abs(cagr / mdd) if mdd != 0 else 0
return {
'total_return': total_return,
'cagr': cagr,
'mdd': mdd,
'calmar': calmar,
'n_trades': len(trades),
'trades': trades,
}
result = backtest_supply_shift_strategy()
6. 반증 조건 — 이 전략이 무너지는 경우
모든 전략은 언제 틀리는지를 먼저 알아야 쓸 수 있다. 이 수급 전환 전략도 예외가 아니다. 아래 표는 이 전략이 깨지는 세 가지 구조적 약점과, 각 약점을 줄이기 위한 방어책을 정리한 것이다.
6-1. 전략 한계 및 리스크 요인
| 한계 요인 | 정량적 메커니즘 | 리스크 방어책 |
|---|---|---|
| 생존자 편향 | 2026년 국면 특수성에 데이터가 과적합(Overfitting)될 위험 잔존 | 2010~2025 개년 데이터 전수 백테스트 연동 |
| 슬리피지 비용 | 이론상 종가 매수 시점 및 실제 주문 체결 가격 간의 시장 괴리 비용 발생 | 장 마감 직전 VWAP 분할 집행 주문 도입 |
| 데이터 시차 | KRX 원장 최종 확정 및 공시 시점까지의 시스템 데이터 타임래그 리스크 | 장중 프로그램 매매 가중 동향 실시간 연산 추가 |
특히 첫 줄, 생존자 편향이 핵심이다. 이 성과는 상승이 나온 2026년 구간을 이미 알고 뒤돌아본 결과다. 실제 운용에서는 매일 장 마감 후 아래 루틴처럼 시그널 발생 여부만 기계적으로 확인하는 것이 과적합을 줄이는 최소한의 규율이다.
6-2. 전략 운용 체크리스트
def daily_signal_check():
"""매일 장 마감 후 시스템 환경에서 변동 시그널을 확인하는 모니터링 루틴입니다."""
today = datetime.now().strftime('%Y%m%d')
start_20d = (datetime.now() - timedelta(days=30)).strftime('%Y%m%d')
df = get_investor_net_trading("069500", start_20d, today)
foreign_20d = df['외국인합계'].tail(20).sum() / 1e12
inst_5d = df['기관합계'].tail(5).sum() / 1e12
if foreign_20d < -10.0 and inst_5d > 0:
return True
return False
🧭 골목지기 뷰 — 데이터 밖의 판단
골목지기가 이 글에서 가장 오래 들여다본 칸은 전략의 화려한 연환산 수익률이 아니다. 저점에서 그냥 사서 들고만 있던 쪽이 규칙을 짜서 진입한 전략보다 더 벌었다는 대목이다. 그런데도 골목지기는 이 전략을 버리지 않는다. 이유는 하나, 낙폭이다.
덜 버는 대신 덜 잃는 구조라면, 다음 하락장에서 계좌를 지켜내는 쪽은 후자다. 수익률표의 큰 숫자보다 이 트레이드오프 한 줄이 이 글의 진짜 결론이라고 골목지기는 판단한다.
시장은 외국인 수급을 지수의 나침반으로 읽는다. 팔면 약세, 사면 강세라는 공식이다. 골목지기는 이 공식을 반만 신뢰한다. 파는 주체가 아니라 그 물량을 받아내는 주체가 방향을 정한다고 보기 때문이다. 외국인이 며칠씩 던져도 연기금이 같은 물량을 흡수하는 국면에서는 지수가 버틴다. '누가 파느냐'는 뉴스가 되지만 '누가 받느냐'는 바닥이 된다.
단, 이 관점은 받는 손이 연기금·보험 같은 장기 자금일 때만 유효하다. 외국인 극단 매도 국면에서 기관 순매수의 실체가 연기금이 아니라 증권사 자기매매나 단기 차익 물량이라면, 그 흡수는 하루 이틀짜리에 그친다. 골목지기는 기관 매수의 주체 구성이 연기금에서 단기 자금으로 바뀌는 순간 이 시그널을 폐기한다. 수급동행성 지표가 높아도 받는 손의 성격이 바뀌면 그 숫자는 함정이 된다.
그래서 골목지기는 이 시그널을 진입의 방아쇠로 쓰지 않는다. 관망을 풀지 말지 검토를 시작하는 신호로만 쓴다. 외국인 20일 누적이 극단으로 빠지고 기관이 며칠 연속 받기 시작하면, 그때부터 계좌를 열어볼지 살핀다. 지켜보는 건 딱 하나다. 다음 주에도 연기금 순매수가 이어지는가. 그 한 줄이 확인될 때까지 골목지기는 서두르지 않는다.
🔗 함께 읽으면 좋은 글
이 전략의 수급 조건을 더 넓은 거시 맥락에서 확인하고 싶다면 아래 글이 연결된다.
- [📊 글로벌 매크로 & 시황] 주간 시황 리포트 — 외국인 순매도 맥락의 거시경제 배경 분석
- [📚 퀀트·투자 백과사전] 이격도·샤프지수·PER/PBR/ROE 핵심 산식 가이드
- [🔬 기업 & 산업 인텔리전스] 수급 수혜 종목 피어 그룹 밸류에이션 비교 분석
알고리즘 백테스팅 / DART 공시 입체 분해 / 계량 수급 데이터 시계열 산출 하우스
데이터 출처 및 레퍼런스:
- KRX 정보데이터시스템 투자 주체별 매매 동향 원장 (data.krx.co.kr)
- 한국거래소 오픈소스 라이브러리 기반 수급 데이터 파이썬 래퍼 (github.com/sharebook-kr/한국거래소)
- KODEX 200 ETF (069500) 실제 마감 OHLCV 데이터 세트 (2026.01.02 ~ 2026.05.22)
💬 이 글의 판단에 반대 근거나 다른 데이터가 있다면 댓글로 남겨 주시면 다음 사후 점검 노트에 반영한다.
'📈 퀀트 투자 전략' 카테고리의 다른 글
| VCP(변동성 수축 패턴) 완전 해설: 마크 미너비니 추세 돌파 매매 전략 통계 검증 (0) | 2026.06.23 |
|---|---|
| [퀀트전략] 시장 중립 전략(Market Neutral) 2026 완전 분석 — 롱숏·페어트레이딩·절대수익 구조 정량 점검 (0) | 2026.06.08 |
| MACD지표 (0) | 2026.05.29 |
| RSI지표 (0) | 2026.05.29 |
댓글