지표의 뼈대

RSI(상대강도지수)는 1978년 웰레스 와일더가 만든 모멘텀 지표다. 최근 N봉(보통 14봉) 동안 오른 날의 평균 상승폭과 내린 날의 평균 하락폭의 비율을 0~100 사이 숫자 하나로 압축한다. 요지는 단순하다 — 최근 이 가격이 얼마나 일방적으로 움직였나.

RSI — 공식에서 코드까지
# RS = 평균 상승폭 / 평균 하락폭  →  RSI = 100 - 100/(1+RS)
delta = close.diff()
up   = delta.clip(lower=0).rolling(14).mean()
down = (-delta.clip(upper=0)).rolling(14).mean()
rsi  = 100 - 100 / (1 + up / down)
--------------------------------------------------
교과서: 70 이상 과매수 · 30 이하 과매도
내 봇 : RSI 45 이하 진입 (8.9일 매매기록 전수 스윕 최적값)

이 수식이 실제로 무엇을 재고 있나

공식을 외우는 것보다 중요한 건 이 숫자가 무엇의 요약인지 아는 것이다. RSI를 분해하면 결국 최근 N봉의 상승 에너지가 전체 에너지에서 차지하는 비율이다.

RS = 평균 상승폭 / 평균 하락폭이라는 정의를 RSI 공식에 대입해서 정리하면 이렇게 된다.

RSI 를 다르게 쓰면
RSI = 100 − 100/(1 + up/down)
    = 100 × (up/down) / (1 + up/down)
    = 100 × up / (up + down)
즉 RSI 는 '상승분 / (상승분 + 하락분)' 의 백분율이다
  N봉 전부 양봉  →  down = 0  →  RSI = 100
  N봉 전부 음봉  →  up   = 0  →  RSI = 0
  상승분과 하락분이 같음      →  RSI = 50
여기서 나오는 성질 — RSI 50 은 '중립' 이 아니라 '균형' 이다
  최근 구간에서 오른 만큼 내렸다는 뜻일 뿐,
  가격이 시작점으로 돌아왔다는 뜻이 아니다

마지막 줄이 흔한 오해를 정리해준다. RSI 50을 “가격이 제자리"로 읽는 사람이 많은데, 실제로는 상승 폭의 합과 하락 폭의 합이 비슷하다는 뜻이다. 상승이 크고 드물게, 하락이 작고 자주 일어났다면 RSI가 50이어도 가격은 크게 올라 있을 수 있다.

이 성질을 알고 나면 RSI를 쓰는 방식이 조금 달라진다. 이 지표는 가격의 위치가 아니라 에너지의 배분을 재는 도구다. 위치를 알고 싶으면 다른 지표를 써야 한다.

평균 방식이라는 첫 번째 함정

코드 다섯 줄에 함정이 둘 있다고 했는데, 첫 번째가 평균 방식이다. 위에 적은 코드는 rolling(14).mean() — 단순이동평균을 썼다. 그런데 와일더의 원본은 지수평활을 쓴다.

같은 RSI(14), 다른 값
단순평균 방식 (SMA)
  up = delta.clip(lower=0).rolling(14).mean()
  성질: 14봉 전 값이 창에서 빠지는 순간 값이 툭 튄다 (드롭오프)
와일더 방식 (RMA · 정본)
  up = delta.clip(lower=0).ewm(alpha=1/14, adjust=False).mean()
  성질: 과거가 지수적으로 감쇠. 값이 부드럽다
둘의 차이
  같은 차트, 같은 기간인데 값이 몇 포인트씩 어긋난다
  문턱 근처(45 언저리)에서는 그 몇 포인트가 통과/탈락을 가른다
거래소 앱 RSI 와 내 봇 RSI 가 다르다면 십중팔구 이것이다
트레이딩뷰·대부분의 차트 플랫폼은 와일더 방식을 쓴다

이게 왜 중요하냐면, 백테스트와 실전이 갈리는 조용한 경로이기 때문이다. 백테스트를 단순평균으로 돌리고 실전 판정은 거래소 앱을 보며 감으로 확인하면, 두 세계가 미세하게 다른 지표를 쓰고 있는 것이다. 어긋남이 크지 않아 눈치채기도 어렵다.

지표를 이식할 때 원본 플랫폼과 값을 맞춰보는 절차를 반드시 넣어야 하는 이유다. 같은 종목, 같은 시각의 값을 뽑아 소수점까지 대조해보면 5분이면 끝나는 확인인데, 이걸 건너뛰면 나중에 원인을 못 찾는다.

진행 중인 봉이라는 두 번째 함정

두 번째 함정은 완성봉 문제다. 15분봉을 쓴다고 하면, 지금 만들어지는 중인 봉의 종가는 계속 바뀐다. 그 값으로 RSI를 계산하면 RSI도 초 단위로 움직인다.

진행봉을 넣으면 벌어지는 일
15분봉이 진행되는 동안의 RSI (진행봉 포함 계산)
  09:00:10   RSI 45.8   ← 게이트 통과 (45 이하 조건이라면 아직 미달)
  09:03:22   RSI 44.9   ← 통과! 매수 신호
  09:07:41   RSI 45.3   ← 다시 미달
  09:11:05   RSI 44.7   ← 또 통과
  09:14:59   RSI 46.1   ← 봉 마감 시점엔 결국 미달이었다
봇의 입장에서 이건 재앙이다
  · 같은 자리에서 사고팔기를 반복한다
  · 백테스트로 재현이 불가능하다 (봉 안의 경로는 캔들에 안 남는다)
  · 봇을 재시작하면 같은 시점에 다른 판단을 한다
해법: 완성된 봉만 확정 큐에 넣고, 지표는 확정 봉으로만 계산한다
대가: 최대 한 봉만큼 늦어진다 — 15분봉이면 최대 15분
그 지연을 받아들이는 대신 재현 가능성을 얻는 거래다

“재현되지 않는 신호는 검증할 수 없고, 검증할 수 없는 신호는 운용할 수 없다.” 이 원칙은 내 봇에서 여러 번 값을 치르고 얻은 것이다. 지연을 감수하더라도 같은 입력에 같은 판단이 나오는 쪽을 골라야 한다.

워밍업 — 세 번째 함정

함정이 하나 더 있다. 지수평활을 쓰면 초기 구간의 값이 시드에 오염된다.

RSI(14)를 계산할 때 처음 14봉으로는 값을 못 만들고, 그 이후에도 한동안은 초기값의 영향이 남는다. 지수평활의 특성상 영향이 완전히 사라지지는 않고 지수적으로 줄어들 뿐이다. 실무적으로는 기간의 5배 정도를 워밍업으로 잡는 게 안전하다. RSI(14)면 최소 70봉, 여유 있게 100봉을 채운 뒤부터 값을 신뢰한다.

이게 왜 문제가 되냐면, 봇을 재시작할 때마다 캔들을 새로 불러오기 때문이다. 100봉을 불러왔는데 앞의 70봉을 버려야 한다면 실제로 쓸 수 있는 건 30봉뿐이다. 데이터 로딩 개수를 지표 기간과 함께 설계하지 않으면, 재시작 직후의 봇이 미묘하게 다른 판단을 한다.

워밍업 설계
필요 봉 수 = 지표 최대 기간 × 5 + 실제 판정에 쓸 봉 수
  예: RSI(14) · 볼린저(20) · 중기추세(14일) 를 동시에 쓴다면
      가장 긴 기간을 기준으로 잡는다
흔한 실수: 지표가 값을 뱉으니까 맞다고 생각하는 것
  pandas 는 워밍업이 부족해도 값을 준다. NaN 이 아니라 '틀린 값' 을 준다
점검법: 500봉으로 계산한 RSI 와 100봉으로 계산한 RSI 를
        같은 시점에서 비교한다. 차이가 0.1 이내로 수렴하는 지점이 안전선

문턱은 왜 30이 아니라 45가 됐나

이제 이 글의 제목이 된 이야기다. 교과서의 30/70은 와일더가 1970년대 상품 선물 시장에서 잡은 경험값이다. 자연법칙이 아니라 그 시장, 그 시대의 관찰이다.

코인 시장은 다르다. 24시간 돌고, 변동성이 크고, 참여자 구성도 다르다. 실제로 RSI 30 이하는 생각보다 드물게 온다. 기다리다 장이 끝나는 것이다.

내 봇은 과매도 눌림목 반등 전략을 쓴다. 빠진 것을 사되, 빠지는 중이 아니라 돌기 시작한 것을 사는 전략이다. 그러려면 “충분히 빠졌다"의 기준이 필요한데, 그 기준을 교과서에서 빌려오지 않고 내 매매기록에서 뽑기로 했다.

RSI 문턱의 이동 — 30 → 40 → 50 → 45
① 교과서 30
   근거: 와일더의 경험값. 내 시장에서 검증된 적 없음
   문제: 신호가 거의 안 나온다 (진입 가뭄)
② 40 으로 완화
   근거: "이틀째 매수가 없다" 는 조급함. 데이터 아님
   같이 푼 것: 낙폭 상한 −5% → −3%
③ 50 으로 재조정
   근거: 게이트 전수조사. 이때는 거래량 게이트가 없던 시점
④ 45 로 확정 (현행)
   근거: 8.9일 매매기록 전수 스윕 · 11개 게이트 축 전부
   결과: RSI 50→45 단독 +0.415% / 승률 55% / 손절권 18%
         저점근접 4→3 단독 +0.433% / 54% / 20%
         두 값 조합    +0.456% / 56% / 손절권 18% (신호 −10%, 37건/일)

③에서 ④로 가는 대목이 이 이야기의 핵심이다. 두 측정 모두 정직하게 수행됐는데 결론이 달랐다. 왜냐면 측정 시점의 게이트 조합이 달랐기 때문이다.

50을 채택했을 때는 거래량 게이트가 없었다. 그 뒤 거래량 조건이 들어오면서, RSI 45~50 사이에 남아 있던 후보들의 성격이 바뀌었다. 그 구간의 평균 성과가 마이너스로 돌아선 것이다. 그래서 45가 됐다.

이 사례가 알려주는 원칙은 이렇다. 파라미터에는 유효기간과 전제조건이 있다. 값만 적어두면 안 되고 “어떤 조합에서 측정된 값인지"를 같이 적어야 한다. 실험 노트의 규율이 코드에도 필요하다는 뜻이다.

채택 기준 — 왜 파레토 우세인가

스윕에서 값을 고를 때 수익률만 보면 안 된다. 이건 여러 번 겪은 뒤에 세운 규칙이다.

기각된 후보들과 그 이유
채택 기준: 평균수익 · 승률 · 손절권 진입률 — 셋 다 개선일 때만
✓ RSI 45         세 축 동시 개선 → 채택
✓ 저점근접 3.0    세 축 동시 개선 → 채택
✗ 낙폭 −2.5      먼 구간 수익은 최고였으나 손절권 33% → 기각
   수익만 보고 골랐다면 채택했을 값이다
✗ ATR 0.3        대형 종목이 통째로 차단됨 → 기각
✗ 밴드 0.6~0.7   단독 개선분이 조합 후 소멸 → 기각
현행 유지로 확인된 축: 거량 · 중기추세 · 반등 · ROC
절차: 축별 전수 스윕 → 상위 조합 재검증 → 기간 반분 안정성
      셋을 다 통과해야 배포 후보가 된다

낙폭 −2.5의 사례가 특히 교훈적이다. 단일 지표로 보면 성적이 제일 좋았는데 손절권 진입률이 33%였다. 열 번 중 세 번은 손절 구간까지 밀린다는 뜻이다. 평균이 좋아도 그 경로가 험하면 실제 운용에서는 견디기 어렵다.

목적함수를 하나로 두면 반드시 어딘가에서 대가를 치른다. 수익만 보면 변동성이 커지고, 승률만 보면 작은 이익을 자주 확정하다 큰 손실 한 방에 무너진다. 세 축을 함께 보는 건 그 대가를 눈에 보이게 만드는 장치다.

RSI를 단독으로 쓰지 않는 이유

문턱을 45로 낮췄다는 건 조건이 느슨해졌다는 뜻이다. 그런데도 성과가 개선된 건, 다른 게이트가 나머지를 걸러주기 때문이다.

RSI 가 못 보는 것들
RSI 가 보는 것: 최근 N봉의 상승/하락 에너지 배분
RSI 가 못 보는 것
  · 이 종목이 거래되는 종목인가        → 거래대금 필터
  · 호가 한 칸이 얼마나 넓은가          → 호가단위 게이트 (0.15% 상한)
  · 지금 저점 근처인가                  → 저점근접 게이트 (3.0)
  · 최근 봉에 거래가 있었나             → 거량 게이트 (0.6x)
  · 시장 전체가 무너지는 중인가          → BTC 4시간봉 레짐 차단
  · 며칠째 흘러내리는 중인가            → 중기추세 게이트
게이트가 10개인 이유가 이것이다 — 지표마다 사각이 다르다
그래서 문턱을 낮출 수 있다. 판정을 혼자 하지 않으니까

이건 반대로도 성립한다. 게이트가 하나뿐이라면 그 하나를 아주 엄격하게 잡아야 하고, 그러면 신호가 마른다. 여러 개를 두면 각각은 느슨해도 된다. 필터를 겹치는 설계의 진짜 이득은 정확도가 아니라 각 조건을 완화할 여유다.

다만 겹치는 데는 비용도 있다. 조건을 하나 더 얹으면 통과 후보가 줄어드는데, 그 감소가 예상보다 클 때가 있다. 실제로 단독으로는 성적이 좋았던 거래량 조건을 다른 게이트 위에 올렸다가 후보가 46건에서 6건으로 줄어든 적이 있다. 87%가 사라진 것이다. 좋은 필터를 얻은 게 아니라 통행량을 죽인 결과였다.

그래서 새 조건을 넣을 때 물어야 할 건 “이게 좋은 조건인가"가 아니라 “이게 나머지가 못 거르는 것을 거르는가“다. 정보의 독립성이 필터의 가치다.

기간 14는 어디서 왔나

문턱값 얘기를 했으니 기간도 짚어야 한다. RSI(14)의 14는 어디서 온 숫자일까.

와일더가 쓴 시장은 상품 선물이었고, 그가 기준으로 삼은 건 대략 반달 주기였다. 거래일 기준 2주가 10일이고 여기에 주말을 낀 자연일로 보면 14일 — 이런 식의 어림이다. 다시 말해 달력에서 온 숫자이지 최적화의 결과가 아니다.

기간을 바꾸면 무엇이 달라지나
RSI(7)   짧은 창
  반응이 빠르다 · 극단값(0, 100)에 자주 닿는다
  봉 하나의 영향이 1/7 → 큰 봉 한 방에 값이 통째로 뒤집힌다
RSI(14)  표준
  대부분의 플랫폼 기본값 — '남들이 보는 값' 이라는 것 자체가 의미가 있다
RSI(21~28)  긴 창
  값이 부드럽다 · 30/70 에 거의 안 닿는다
  문턱을 그대로 두면 신호가 사실상 사라진다
중요한 성질: 기간과 문턱은 한 쌍이다
  기간을 늘리면 값의 분포가 50 쪽으로 좁아지므로 문턱도 함께 조여야 한다
  기간만 바꾸고 문턱을 그대로 두는 건 두 파라미터를 동시에 바꾼 것과 같다

마지막 줄이 스윕 설계에서 자주 놓치는 부분이다. 기간 축과 문턱 축을 독립적으로 훑으면 조합의 절반은 애초에 말이 안 되는 지점이다. RSI(28)에 문턱 30을 걸면 신호가 거의 0이라 표본 자체가 안 쌓인다. 두 축을 함께 움직이거나, 문턱을 절대값이 아니라 분포의 백분위로 정의하는 쪽이 낫다.

백분위 방식은 이런 식이다. “RSI 45 이하"가 아니라 “이 종목 최근 30일 RSI 분포의 하위 25%”. 종목마다 RSI가 놀던 범위가 다르니 자동으로 조정된다. 다만 계산이 무거워지고 해석이 어려워져서, 나는 아직 절대값을 쓰고 있다. 언젠가 재볼 후보 목록에 올려둔 항목이다.

과매수 쪽은 왜 안 쓰나

교과서는 30과 70을 대칭으로 가르치는데, 내 봇은 45만 쓴다. 70은 아예 조건에 없다.

이유는 단순하다. 숏 포지션이 없기 때문이다. 업비트 현물만 다루니 오를 것 같으면 사고 아니면 안 사는 두 가지 선택뿐이다. 과매수 판정은 “팔아라"에 쓰이는데, 안 들고 있는 종목에 대해서는 할 일이 없다.

그럼 보유 중인 종목의 청산에 RSI 70을 쓰면 되지 않느냐고 할 수 있다. 실제로 시도해볼 만한 아이디어인데, 지금 구조에서는 잘 안 맞는다.

청산에 RSI 를 쓰기 어려운 이유
현행 청산 구조
  손절        −5% (고정)
  본전 사수    +0.8% 도달 시 +0.4% 잠금
  포트 익절    계좌 합산 예상실현 2만원 도달 시 전량, 스테이 30분
여기에 RSI 70 청산을 얹으면
  · 이익 구간 체류시간 중앙값이 0분 — RSI 가 70 까지 오를 시간이 없다
  · 계좌 합산 수확과 개별 종목 청산이 서로 간섭한다
  · 청산 경로가 하나 늘면 손익 귀속 분석이 그만큼 복잡해진다
가능성이 있는 자리: 스윙 전략으로 전환하거나, 보유 기간이 길어질 때
지금은 안 맞는다는 것이지 나쁜 아이디어라는 뜻이 아니다

이 판단에는 실측 근거가 있다. 눌림목 진입 18건을 재현했을 때 최대 평가이익의 중앙값이 +3.09%였다. 평균은 +12.27%였지만 그건 한 종목이 +98.65%로 튄 탓이고, 중앙값이 실제 감각에 가깝다. +3% 남짓 오르는 동안 RSI가 70을 넘기는 경우는 드물다.

같은 재현에서 더 뼈아픈 숫자도 나왔다. 어떤 익절 수준을 골라도 결과의 중앙값이 −2.100%로 고정됐다는 것. 익절 지점을 바꿔봐야 결과가 안 달라졌다는 뜻이다. 손절 11건(61%)이 전부를 좌우하고 있었다. 이 발견이 개별 익절을 포기하고 계좌 합산 수확으로 넘어간 계기였다.

실제 스캔에서 RSI가 하는 일

이론 말고 운용 데이터로 보면 RSI 게이트의 역할이 좀 더 구체적으로 보인다.

내 봇은 매 스캔마다 게이트별 통과율을 터미널에 찍는다. 어느 관문에서 후보가 얼마나 걸러지는지를 보기 위해서다. 이 로그를 도입한 계기가 “이틀째 매수가 없다"였는데, 그때 게이트별로 숫자를 보고 나서야 어디가 병목인지 알았다.

게이트 통과율 로그 (형식 예시)
[게이트통과율] 과:10/15 | 거:8/15 | 모:6/15 | 변:11/15 | 급:12/15 | 박:9/15
읽는 법 — 분모는 유니버스 통과 종목 수, 분자는 그 게이트를 통과한 수
여기서 얻는 것
  · 어느 게이트가 병목인지 즉시 보인다
  · 통과율이 갑자기 0 이 되면 코드 사고를 의심할 수 있다
  · 게이트 완화의 효과를 다음 스캔에서 바로 확인할 수 있다
이 로그가 없던 시절엔 "왜 안 사지" 를 감으로 추측했다
측정하지 않는 필터는 튜닝할 수 없다 — 값을 바꿔도 효과를 모르니까

이 로그를 붙이고 나서 게이트 튜닝의 성격이 바뀌었다. 이전에는 “신호가 안 나오니 아무거나 풀어보자"였는데, 이후에는 “통과율이 제일 낮은 축부터 본다"가 됐다. 같은 완화라도 병목이 아닌 게이트를 풀면 신호는 안 늘고 품질만 떨어진다.

RSI 게이트는 이 통계에서 대체로 중간쯤에 있다. 압도적 병목도 아니고 무의미한 관문도 아니다. 게이트 하나가 후보를 절반 이하로 깎으면 그건 사실상 그 게이트가 전략을 결정하고 있다는 뜻이고, 반대로 90% 이상을 통과시키면 없는 거나 마찬가지다. 적당히 걸러주는 자리에 있는 게 건강한 상태라고 본다.

RSI 다이버전스는 다루지 않는다

RSI 얘기를 하면 반드시 나오는 게 다이버전스다. 가격은 신저점인데 RSI는 더 낮아지지 않는 상황을 반등 신호로 읽는 것.

이 글에서 다루지 않는 이유는 하나다. 자동 판정 규칙을 만들기가 생각보다 까다롭기 때문이다. 어느 고점과 어느 고점을 비교할지, 그 고점은 몇 봉 뒤에 확정되는지, 두 지점 사이 간격은 얼마까지 허용할지 — 규칙을 못 박지 않으면 사람마다 다른 다이버전스를 본다.

피보나치의 스윙 선택 문제와 똑같은 구조다. 재현 불가능한 판정은 검증도 불가능하다. 이 주제는 별도로 다룰 가치가 있어서 다음 편에서 파인스크립트 지표 하나를 재료 삼아 따로 정리했다.

다른 시장으로 가져갈 때

혹시 이 글의 45라는 숫자를 그대로 쓰려는 분이 있을까 봐 적어둔다. 그러면 안 된다.

45 가 성립하는 전제들
이 값은 아래 조건이 전부 맞을 때의 측정치다
  거래소     업비트 원화 마켓
  시간축     15분봉
  전략       과매도 눌림목 반등
  동반 게이트 거량 0.6x · 저점근접 3.0 · 호가단위 0.15% 외
  계좌 규모   소액 (내 주문이 호가를 안 움직이는 수준)
  표본       8.9일치 실매매 기록
하나라도 다르면 최적점이 이동한다
가져갈 것은 45 라는 숫자가 아니라, 45 를 얻은 절차다
  축별 스윕 → 조합 검증 → 기간 반분 → 파레토 우세 채택

특히 표본 8.9일은 결코 넉넉한 양이 아니다. 그 위에서 뽑은 값에는 과적합이 섞여 있을 수밖에 없다. 기간을 반으로 갈라 앞뒤에서 모두 성립하는지 확인한 건 그 위험을 조금이라도 줄이려는 절차였지, 위험을 없앤 게 아니다.

이 한계를 인정하고 적어두는 게 중요하다고 생각한다. 숫자를 자신 있게 제시하면 글은 강해 보이지만, 그 숫자가 어디까지 유효한지 밝히지 않으면 읽는 사람이 잘못 가져다 쓴다.

시간축을 바꾸면 다른 지표가 된다

같은 RSI(14)라도 15분봉이냐 일봉이냐에 따라 완전히 다른 물건이다. 이건 당연해 보이지만 실무에서 자주 뒤섞인다.

RSI(14) 가 커버하는 실제 시간
  1분봉    14분
  5분봉    1시간 10분
 15분봉    3시간 30분      ← 내 봇의 진입 판정 시간축
 1시간봉    14시간
 4시간봉    2일 8시간       ← 시장 레짐 판정에 쓰는 시간축
  일봉      2주
같은 "RSI 45 이하" 조건도
  15분봉에서는 '오늘 오전에 좀 빠졌다'
  일봉에서는  '2주째 흘러내리는 중이다'
두 상황은 대응이 정반대여야 한다 — 앞은 살 자리, 뒤는 피할 자리일 수 있다
그래서 시간축은 지표의 파라미터가 아니라 전략의 정의에 속한다

내 봇은 이 둘을 역할로 분리해서 쓴다. 종목 진입은 15분봉으로 보고, 시장 전체의 허가 여부는 BTC 4시간봉으로 판정한다. 하위 시간축은 타이밍을, 상위 시간축은 허가를 담당하는 구조다.

이 분리는 실측이 시킨 것이기도 하다. 7주 연속 마이너스를 겪고 나서 원인을 뒤졌는데, 개별 진입 조건이 나빴다기보다 시장이 통째로 흘러내리는 구간에서 계속 사고 있었던 것이 문제였다. 종목 레벨 지표로는 그게 안 잡힌다. RSI 45짜리 눌림목이 하락 추세의 중간 지점일 때, 15분봉 RSI는 그 사실을 모른다.

그래서 하락장이면 신규 진입 자체를 안 하는 규칙이 생겼다. 종목을 더 잘 고르려는 노력이 한계에 달했을 때, 남은 개선은 “언제 게임을 쉬는가"에 있었다.

이 지표를 처음 붙이는 사람에게

정리 삼아, RSI를 봇에 처음 넣는다면 순서를 이렇게 잡으라고 권하고 싶다.

도입 순서
① 값을 맞춘다
   내 코드의 RSI 와 차트 플랫폼의 RSI 를 같은 시점에서 대조
   소수점까지 맞을 때까지 — 대부분 평활 방식이 원인이다
② 완성봉만 쓰도록 고정한다
   진행봉 제외 · 워밍업 구간 폐기. 재현 가능성이 검증의 전제다
③ 문턱을 정하지 말고 기록부터 쌓는다
   진입할 때마다 그 시점의 RSI 를 함께 저장한다
   나중에 '익절한 건과 손절한 건의 진입 RSI 분포' 를 비교할 수 있다
④ 표본이 쌓이면 스윕한다
   축별 → 조합 → 기간 반분. 세 축(수익·승률·손절권) 동시 개선만 채택
③ 을 건너뛰면 ④ 를 할 수 없다. 그런데 대부분 ③ 을 건너뛴다
나도 건너뛰었고, 그래서 초기 백여 개 버전의 절반은 감으로 만든 것이었다

③번이 이 목록에서 제일 중요하다고 생각한다. 지표를 넣는 것보다 지표값을 기록으로 남기는 것이 먼저다. 진입 시점의 지표값을 저장해두지 않으면, 나중에 어떤 조건에서 잘 됐고 어떤 조건에서 안 됐는지 비교할 방법이 없다.

내 봇에서 이 습관이 생긴 건 꽤 늦었다. 단일 파일로 개발하던 시절에는 매매 기록을 체계적으로 안 남겼고, 그래서 파라미터를 바꿀 때 근거가 없었다. “이게 나을 것 같다"로 바꾸고 며칠 지켜보다 느낌이 안 좋으면 되돌리고. 지금 돌아보면 그 시기 버전의 절반은 만들 필요가 없었다. 기록이 있었다면 데이터가 답을 줬을 것이다.

나중에 진입 기록에 그 시점의 시장 레짐까지 함께 저장하기 시작했는데, 이게 이후 분석의 기반이 됐다. 시간대별 성과를 갈라 볼 수 있었던 것도, 게이트 조합을 바꿔가며 재현할 수 있었던 것도 전부 그때 심어둔 기록 덕분이다. 오늘 심는 데이터가 내일의 분석을 결정한다. 분석 인프라는 필요해진 다음에 만들면 늦다. 그때는 이미 과거가 비어 있다.

변형 지표들 — 스토캐스틱 RSI와 그 친척들

RSI가 워낙 널리 쓰이다 보니 파생 지표도 많다. 그중 실무에서 자주 마주치는 게 스토캐스틱 RSI다.

발상은 이렇다. RSI가 45라는 값만으로는 그게 이 종목 기준으로 낮은 건지 아닌지 모른다. 어떤 종목은 평소 RSI가 4060에서만 놀고, 어떤 종목은 2080을 오간다. 그래서 RSI 자체에 다시 스토캐스틱을 적용해서 최근 구간 안에서의 상대 위치로 바꾼다.

스토캐스틱 RSI
StochRSI = (RSI − 최근 n봉 RSI 최솟값) / (최댓값 − 최솟값)
  결과: 0~1 범위. 이 종목 기준으로 지금 RSI 가 어디쯤인가
장점: 종목별 RSI 범위 차이를 자동으로 흡수한다
단점 1: 정규화를 한 번 더 하면서 원래 크기 정보가 사라진다
   RSI 20 → 25 도, RSI 55 → 60 도 같은 StochRSI 상승으로 보일 수 있다
단점 2: 극단값에 자주 닿는다 (0 과 1 에 붙어 있는 시간이 길다)
단점 3: 지연이 이중으로 쌓인다 (RSI 의 지연 + 스토캐스틱 창의 지연)
앞서 얘기한 '백분위 문턱' 아이디어와 사실상 같은 계열의 처방이다

내가 아직 이걸 안 쓰는 이유는 단점 1 때문이다. 눌림목 전략에서는 “얼마나 깊게 빠졌나"가 실제로 중요한데, 정규화를 두 번 하면 그 정보가 흐려진다. 다만 종목별 차이를 흡수한다는 장점은 분명하니, 랭킹 가점처럼 위험이 작은 자리에서 먼저 시험해볼 후보로 남겨뒀다.

랭킹 자리를 고르는 데는 이유가 있다. 진입 여부를 안 바꾸고 순서만 바꾸는 변경은 되돌리기 쉽고 효과 측정도 깔끔하다. 반대로 게이트를 건드리면 신호 개수가 변해서, 성과가 달라졌을 때 조건 덕인지 표본이 달라진 탓인지 구분하기 어려워진다.

다만 랭킹에도 함정이 있다는 걸 배운 적이 있다. 과매도 정도에 가점을 주는 항목과 밴드 압축에 가점을 주는 항목을 각각 3.0과 2.5로 두고 있었는데, 실제 결과와 대조해보니 두 항목 모두 상관이 음수였다. 점수가 높을수록 오히려 나쁜 종목을 먼저 사고 있었던 것이다. 두 가중치를 0으로 내리는 게 그때의 결론이었다.

그러니 새 지표를 랭킹에 넣을 때도 “유용한가"만 묻지 말고 “부호가 내 생각과 같은가“를 물어야 한다. 그럴듯한 논리로 만든 가점이 반대로 작동하는 일은 생각보다 자주 일어난다. 나중에 RSI 깊이 가점을 다시 살렸는데, 그때는 근거가 달랐다 — 실제 완결 매매 35건에서 RSI 25 미만 구간의 승률이 78%, 25~35 구간이 21%로 갈렸기 때문이다. 같은 아이디어라도 감으로 넣은 것과 실측으로 넣은 것은 다른 물건이다.

소회

지표 공부의 끝은 언제나 같은 결론이었다 — 숫자는 빌려 오되, 문턱은 내 데이터로 정한다. RSI는 내 봇의 첫 게이트지만, 30이라는 남의 경험값을 45라는 내 검증값으로 바꾸는 데 두 달이 걸렸다.

두 달 동안 한 일을 돌아보면 대부분은 값을 바꾼 게 아니라 값을 정하는 방법을 만든 일이었다. 매매기록을 분석 가능한 형태로 남기고, 그 기록으로 과거를 재현하는 코드를 짜고, 채택 기준을 세 축으로 정하고, 기간을 갈라 확인하는 절차를 만들고. 45라는 숫자는 그 인프라가 뱉어낸 첫 결과물일 뿐이다.

그래서 이 코너의 이름을 지표 스터디로 지었지만, 실제로 공부하고 있는 건 지표가 아니라 검증 절차인지도 모르겠다. 지표는 계속 바뀔 것이고 실제로 여러 번 바뀌었다. 바뀌지 않은 건 “이 값이 왜 이 값인지 답할 수 있어야 한다"는 규칙 하나다.

와일더는 지표를 만들었지만, 그 지표의 문턱은 시장마다 다시 태어나야 한다. 공식은 수입품이어도 파라미터는 국산이어야 한다 — 이게 이 스터디의 첫 번째 원칙이다.

덧붙임 — 자주 받는 질문 몇 가지

“RSI가 30 아래로 갔는데 계속 떨어지는 건 왜인가요.” RSI는 상한과 하한이 있는 지표라 극단에서 오래 머무를 수 있다. 강한 하락 추세에서는 RSI가 20대에 며칠씩 붙어 있는 게 정상이다. “과매도니까 곧 오른다"는 해석이 위험한 이유가 이것이다. 과매도는 상태이지 예측이 아니다. 그래서 나는 RSI를 진입 신호가 아니라 진입 자격으로 쓴다. 45 이하는 후보가 될 자격이 있다는 뜻일 뿐, 사라는 뜻이 아니다. 실제로 사는 건 나머지 아홉 개 게이트를 다 통과했을 때다.

“거래소 앱 RSI와 제 계산이 다릅니다.” 평활 방식(단순평균 vs 와일더), 시간축, 봉 마감 기준 시각, 그리고 진행봉 포함 여부 — 이 넷 중 하나다. 순서대로 확인하면 대개 첫 번째에서 잡힌다.

“RSI 하나만 쓰면 안 되나요.” 안 될 건 없지만 문턱을 훨씬 보수적으로 잡아야 한다. 조건이 하나뿐이면 그 하나가 모든 걸 판정해야 하니까. 그러면 신호가 마르고, 신호가 마르면 표본이 안 쌓이고, 표본이 없으면 그 문턱이 옳은지 확인할 방법이 없다. 이 악순환에서 나가는 길이 게이트를 늘리는 것이었다.

“8.9일은 표본이 너무 적지 않나요.” 맞다. 적다. 그래서 그 스윕 결과를 확정이 아니라 현재까지의 최선으로 취급한다. 표본이 쌓이면 다시 돌릴 예정이고, 그때 45가 아닌 다른 값이 나와도 놀라지 않을 것이다. 중요한 건 값이 아니라 값을 갱신할 수 있는 상태를 유지하는 것이다.