AlgoLab Blog · 퀀트·백테스트 검증 · 2026

ATR 손절 vs 고정 −5% 손절 — 22종목 10년 실측

퀀트 · 손절 설계 2026-10-14 · 약 8분 읽기 · 알고랩 AlgoLab
한 줄 요약

ATR 손절은 수익을 올리는 장치가 아니라, 종목마다 다른 변동성에 손절 폭을 맞춰 "손절이 걸리는 비율"을 고르게 만드는 장치였습니다. 같은 20일 돌파 진입에 손절만 −3%·−5%·−8%·ATR 2배로 바꿔 국내 22종목 10년(2016-01~2026-09, 거래 4,208건)을 돌렸더니, 고정 −5%의 손절 비율은 저변동 종목 34.3%에서 고변동 종목 63.6%까지 벌어졌고 ATR 2배는 세 구간 모두 39.9~40.5%였습니다. 거래당 평균은 ATR 2배 2.92%, 고정 −8% 3.00%로 비슷했습니다. 종목 하나만 굴리면 고정 %로 충분하고, 여러 종목을 한 규칙으로 굴리는 봇일수록 ATR이 의미가 있습니다. (왕복 비용 0.3% 가정, 생존 종목만, 설명용 전략, 과거≠미래)

목차

  1. 왜 −5%가 종목마다 다른 뜻이 되나
  2. 실측 설계 — 진입은 같게, 손절만 4종
  3. 전체 결과 — 수익보다 손절 비율이 갈렸다
  4. 변동성 구간별 — ATR이 실제로 한 일
  5. 손절가보다 아래에서 체결된 거래
  6. 봇에 넣을 때 — ka10081·FHKST03010100으로 ATR 계산
  7. 재현 코드와 한계
  8. 자주 묻는 질문

1. 왜 −5%가 종목마다 다른 뜻이 되나

자동매매 봇을 의뢰하는 분들이 가장 자주 적어 오는 손절 규칙은 "매수가 대비 −5%"입니다. 단순하고 설명하기 쉽습니다. 문제는 같은 −5%라도 종목에 따라 하루치 흔들림의 몇 배인지가 다르다는 점입니다.

ATR(Average True Range)은 그 흔들림을 재는 지표입니다. 하루의 고가−저가, 전일 종가 대비 고가·저가 차이 중 가장 큰 값을 True Range라고 하고, 이를 14일 동안 평활한 값이 ATR(14)입니다. 이번 실험 22종목의 ATR을 종가 대비 %로 바꿔 10년 중앙값을 보면 이렇습니다.

종목ATR% 중앙값−5%는 ATR의ATR 2배 손절폭
KT 0302001.80%2.8배3.48%
삼성전자 0059302.15%2.3배4.26%
현대차 0053802.71%1.8배5.40%
SK하이닉스 0006603.17%1.6배6.14%
에스엠 0415104.24%1.2배7.65%
알테오젠 1961705.33%0.9배10.02%

KT에서 −5%는 "평소 하루 흔들림의 거의 3배"라서 웬만해서는 닿지 않는 선이고, 알테오젠에서 −5%는 "하루 흔들림보다 좁은" 선이라 평범한 하루에도 걸립니다. 같은 규칙이 한쪽에서는 느슨하고 한쪽에서는 빡빡합니다. ATR 2배 손절은 이 차이를 없애려고 손절폭을 종목마다 3.5%~10%로 자동 조절합니다. (ATR 2배 손절폭은 각 거래 진입 시점 값의 중앙값입니다.)

2. 실측 설계 — 진입은 같게, 손절만 4종

손절 효과만 보려면 나머지는 전부 같아야 합니다. 그래서 진입과 추세 청산 규칙을 고정하고 손절만 네 가지로 바꿨습니다.

항목설정
데이터FinanceDataReader 일봉(수정주가), ASOF = "2026-09-30" 고정, 집계 구간 2016-01-04~
종목 22개2016년 이전 상장 KOSPI·KOSDAQ 종목 — 통신·금융(저변동)부터 바이오·2차전지(고변동)까지
진입종가 > 직전 20거래일 고가 최고값 → 다음 날 시가 매수
추세 청산종가 < 직전 10거래일 저가 최저값 → 다음 날 시가 매도
손절 4종진입가 −3% · −5% · −8% · 진입가 − ATR(14)×2 (신호일 기준으로 계산해 고정)
손절 체결저가가 손절가에 닿으면 손절가, 시가부터 손절가 아래면 시가
비용왕복 0.3% (수수료·세금·슬리피지 근사)
환경Python 3.10 · pandas 2.2.3 · numpy · FinanceDataReader 0.9.202

진입 당일 저가가 손절가 아래면 그날 손절로 처리했습니다. 일봉으로는 장중 순서를 모르므로 불리한 쪽을 택한 것입니다. 이 가정이 결과를 얼마나 흔드는지는 손절·익절이 같은 날 닿으면에서 따로 쟀습니다. 종목 선택은 변동성 범위를 넓게 잡기 위한 것이며 추천이 아닙니다.

3. 전체 결과 — 수익보다 손절 비율이 갈렸다

22종목의 거래를 모두 합친 결과입니다.

ASOF 2026-09-30  tickers 22  trades total 4,208

[1] 전체 (22종목 거래 합산)
stop      trades   win%   avg_win  avg_loss  stop_hit%  expectancy
-3%        1268  21.2%   17.12%   -3.19%     70.5%     1.12%
-5%        1043  29.7%   17.69%   -4.57%     49.7%     2.04%
-8%         930  35.9%   18.42%   -5.65%     23.7%     3.00%
ATR x2      967  33.8%   19.02%   -5.30%     40.1%     2.92%

세 가지가 보입니다.

  1. 좁은 손절은 거래를 늘리고 승률을 깎는다. −3%는 거래가 1,268건으로 가장 많지만 70.5%가 손절로 끝났습니다. 잘리고 다시 들어가고를 반복한 결과입니다. 돌파 전략은 원래 승률이 낮고 소수의 큰 수익(평균 이익 17~19%)으로 버티는 구조라, 좁은 손절은 그 큰 수익이 올 기회까지 자릅니다.
  2. ATR 2배와 −8%는 평균이 거의 같다. 거래당 2.92%와 3.00%입니다. 이 평균은 몇 개의 큰 거래에 크게 좌우되므로, 수익 상위 1% 거래를 빼고 다시 계산했더니 0.76%와 0.75%로 사실상 같았습니다. 2016~2020년 진입분(2.99% vs 3.08%)과 2021년 이후 진입분(2.86% vs 2.92%)으로 나눠도 순서가 엇비슷했습니다.
  3. 종목별 누적 수익으로 1등을 세면 −8%가 9개, ATR 2배가 5개였습니다(−3%·−5%가 각 4개). "ATR이 이긴다"는 결론은 이 데이터에서 나오지 않습니다.

그렇다면 ATR은 쓸모가 없나 — 평균만 보면 그렇게 보입니다. 하지만 −8%와 ATR 2배가 비슷한 평균을 낸 과정이 다릅니다. −8%는 저변동 종목에서 거의 안 걸리고(9.4%) 고변동 종목에서 자주 걸리며(38.9%), ATR 2배는 어느 종목에서나 40% 언저리로 걸립니다. 다음 장이 그 차이입니다.

4. 변동성 구간별 — ATR이 실제로 한 일

22종목을 ATR% 중앙값 기준으로 세 묶음(저변동 1.8~2.6% · 중변동 2.7~3.2% · 고변동 3.4~5.3%)으로 나눴습니다.

0%40%80% 34.339.9 50.340.5 63.640.0 저변동 (ATR% 1.8~2.6) 중변동 (2.7~3.2) 고변동 (3.4~5.3) 고정 −5% ATR 2배
거래 중 손절로 끝난 비율 — 고정 −5%는 변동성이 클수록 오르고, ATR 2배는 평평하다 (22종목, 2016-01~2026-09)
[3] 변동성 구간별 — 거래당 기대값 / 손절 비율 / 손절폭 중앙값
  저변동 (ATR% 1.8%~2.6%)
    -5%     n  350  win 33.4%  exp  0.39%  stop_hit 34.3%  dist_med  5.0%
    -8%     n  329  win 36.5%  exp  0.63%  stop_hit  9.4%  dist_med  8.0%
    ATR x2  n  353  win 33.1%  exp  0.47%  stop_hit 39.9%  dist_med  4.4%
  고변동 (ATR% 3.4%~5.3%)
    -5%     n  371  win 25.3%  exp  2.51%  stop_hit 63.6%  dist_med  5.0%
    -8%     n  316  win 33.5%  exp  4.48%  stop_hit 38.9%  dist_med  8.0%
    ATR x2  n  310  win 34.2%  exp  5.00%  stop_hit 40.0%  dist_med  7.9%

읽는 법은 이렇습니다.

손절폭이 종목마다 달라지면 같은 금액을 넣었을 때 잃는 돈도 달라집니다. 그래서 ATR 손절은 보통 "손절 시 잃을 금액을 고정하고 수량을 역산하는" 포지션 사이징과 같이 씁니다. 그 계산은 주문 수량 정하는 법에 정리했습니다.

5. 손절가보다 아래에서 체결된 거래

손절가를 정해 둬도 그 가격에 팔린다는 보장은 없습니다. 전날 종가는 손절가 위였는데 다음 날 시가가 이미 아래에서 열리면 시가에 팔립니다.

[5] 갭 손절(손절가보다 아래에서 체결) 비율
  -3%     stops  894  gap-through  9.1%  worst -11.16%
  -5%     stops  518  gap-through 11.4%  worst -12.52%
  -8%     stops  220  gap-through 12.7%  worst -16.77%
  ATR x2  stops  388  gap-through 11.3%  worst -20.88%

손절의 약 10건 중 1건이 이렇게 미끄러졌습니다. 실제 예로 HLB 028300에 −5% 손절을 건 거래 하나는 2026-01-28 시가 66,300원에 진입해 손절가가 62,985원이었는데, 1월 30일 시가가 그 아래에서 열려 58,200원에 청산됐습니다. 비용 포함 −12.5%입니다. ATR 2배의 최악 거래가 −20.88%로 가장 깊은 것은 고변동 종목에서 손절폭 자체가 넓어서입니다. "−5% 손절이니 한 번에 5%까지만 잃는다"는 계산은 일봉 기준으로도 맞지 않습니다. 장 시작 전 손절 주문 처리는 KIS 장전 손절 처리에서 다뤘습니다.

6. 봇에 넣을 때 — ka10081·FHKST03010100으로 ATR 계산

실전 봇에서 ATR 손절은 "매수 신호가 난 날 장 마감 뒤 일봉으로 ATR을 계산 → 다음 날 진입가에서 손절가 확정"의 순서입니다. 필요한 것은 종목별 일봉 시가·고가·저가·종가 15일 이상입니다.

받은 일봉을 pandas DataFrame(Open·High·Low·Close, 날짜 오름차순)으로 만들었다면 ATR과 손절가 계산은 이 정도입니다.

import pandas as pd

def wilder_atr(df: pd.DataFrame, n: int = 14) -> pd.Series:
    prev_close = df["Close"].shift(1)
    tr = pd.concat([df["High"] - df["Low"],
                    (df["High"] - prev_close).abs(),
                    (df["Low"] - prev_close).abs()], axis=1).max(axis=1)
    return tr.ewm(alpha=1 / n, adjust=False).mean()   # Wilder 평활

def atr_stop_price(df: pd.DataFrame, entry_price: float, k: float = 2.0) -> int:
    atr_today = wilder_atr(df).iloc[-1]                # 신호일 종가까지의 ATR
    return int(entry_price - k * atr_today)            # 진입 후 고정 — 매일 다시 계산하지 않음

실전에서 놓치기 쉬운 점이 세 가지 있습니다. 첫째, 계산된 손절가는 호가 단위로 내림해야 주문이 들어갑니다. 둘째, 백테스트가 수정주가로 ATR을 계산했다면 봇도 수정주가(upd_stkpc_tp=1)로 받아야 숫자가 맞습니다. 셋째, ATR을 매일 다시 계산해 손절가를 따라 올리면 그것은 트레일링 스탑이라 이 글과 다른 규칙입니다. 트레일링 방식은 청산 전략 글에서 비교했습니다.

7. 재현 코드와 한계

위 표 전체를 만드는 스크립트는 atr_vs_fixed_stop_backtest.py로 받을 수 있습니다. ASOF를 2026-09-30으로 고정했기 때문에 지금 실행해도 글과 같은 숫자가 나옵니다. 최신 데이터로 보려면 ASOF = None으로 바꾸면 되고 숫자는 달라집니다. 핵심 루프는 이렇습니다.

stop = entry * (1 - k) if kind == "pct" else entry - k * a[i]
while j < n:
    if j > e and o[j] <= stop:        # 갭으로 손절가 아래 출발 -> 시가 체결
        exit_px, why = o[j], "stop"; break
    if l[j] <= stop:
        exit_px, why = min(stop, o[j]) if j > e else stop, "stop"; break
    if c[j] < lo10[j] and j + 1 < n:  # 추세 이탈 -> 다음 날 시가
        exit_px, why = o[j + 1], "trend"; j += 1; break
    j += 1

한계 — 지금 상장돼 있는 22종목만 썼으므로 생존 편향이 있습니다(상장폐지 종목을 넣으면 손절의 가치가 더 크게 나올 수 있습니다 — 생존 편향 실측). 진입 규칙 1개, ATR 배수 1개(2배)만 시험했고 배수 최적화는 하지 않았습니다. 평균 수익은 소수의 큰 거래에 좌우되며 2025~2026년 상승장 구간이 포함돼 있습니다. 비용은 왕복 0.3% 근사이고 호가 단위·시가 단일가 체결 불확실성은 반영하지 않았습니다. 배당 미포함. 어떤 수치도 미래 수익을 예측하지 않으며 특정 종목·전략을 권하지 않습니다. 과거 성과는 미래를 보장하지 않습니다.

같은 전략을 시작일만 바꿔 돌렸을 때 결과가 얼마나 흔들리는지는 백테스트 시작일 민감도에서, 백테스트 전체 점검 순서는 자동매매 백테스트 완전 가이드에서 볼 수 있습니다.

8. 자주 묻는 질문

ATR 손절이 고정 퍼센트 손절보다 수익이 좋은가요?

이 실험에서는 아니었습니다. 22종목 10년 거래당 평균은 ATR 2배 2.92%, 고정 −8% 3.00%로 비슷했고, 상위 1% 거래를 빼면 0.76%와 0.75%로 거의 같았습니다. ATR 손절이 바꾼 것은 수익이 아니라 종목마다 손절이 걸리는 비율이었습니다. 과거 결과는 미래를 보장하지 않습니다.

ATR 배수는 2배가 정답인가요?

정답 배수는 없습니다. 이 글은 흔히 쓰는 2배 하나만 시험했고 배수를 바꿔 가며 최적화하지 않았습니다. 여러 배수를 돌려 가장 좋은 값을 고르면 과최적화가 되므로, 배수를 정했다면 다른 기간이나 다른 종목에서 같은 값이 버티는지 확인해야 합니다.

ATR은 언제 값을 계산해서 손절가를 정하나요?

이 글에서는 매수 신호가 난 날 종가까지의 일봉으로 ATR(14)을 계산하고, 다음 날 시가 진입가에서 ATR의 2배를 뺀 값을 손절가로 고정했습니다. 진입 뒤 ATR이 바뀌어도 손절가는 움직이지 않습니다. 매일 다시 계산해 올리는 방식은 트레일링 스탑이라 별개의 규칙입니다.

자동매매 봇에서 ATR 손절을 쓰려면 어떤 데이터가 필요한가요?

종목별 일봉 시가·고가·저가·종가 최소 15일 이상이 필요합니다. 키움 REST API는 주식일봉차트조회요청 ka10081, 한국투자증권 KIS Open API는 FHKST03010100(국내주식 기간별 시세)으로 받을 수 있습니다. 수정주가 여부를 백테스트와 같게 맞춰야 합니다.

손절 규칙까지 검증한 봇, 만들어 드립니다

고정 %든 ATR이든, 쓰시려는 손절 규칙을 종목군에 맞춰 백테스트로 먼저 보여 드립니다. 24시간 빠른 답변 가능합니다.

무료 상담 시작하기