지표의 뼈대

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가 하는 일

이론 말고 운용 데이터로 보면 이 게이트의 역할이 좀 더 만져진다.

내 봇은 스캔할 때마다 게이트별 통과율을 터미널에 찍는다. 어느 관문에서 후보가 얼마나 깎이는지 보려고 넣었다. 계기는 “이틀째 매수가 없다"는 짜증이었고, 게이트별 숫자를 보고 나서야 병목이 어딘지 알았다.

게이트 통과율 로그 (형식 예시)
[게이트통과율] 과: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가 아닌 값이 나와도 놀라지는 않을 것 같다. 정작 못 정한 쪽은 언제 다시 돌리느냐다. 며칠치가 더 필요한지, 매매 건수로 세야 하는지 기준이 없다. 다음엔 이것부터 정해야겠다.


약어 풀이

* 이 글에 나온 영어 약어를 풀어 둔다.

  • RSI (Relative Strength Index): 상대강도지수. 일정 기간 평균 상승폭과 평균 하락폭의 비율을 0~100 으로 나타내 과매수·과매도를 재는 모멘텀 오실레이터.
  • SMA (Simple Moving Average): 단순이동평균. 기간 안의 가격을 같은 가중치로 평균한 값.
  • ATR (Average True Range): 평균진폭. 고가·저가·전일 종가를 아우르는 하루 실제 변동폭(True Range)의 이동평균. 변동성 지표.
  • BTC (Bitcoin): 비트코인. 이 블로그에서는 코인 시장 전체의 방향(레짐)을 재는 기준 자산.
  • RS (Relative Strength): 상대강도. RSI 계산의 중간값으로, 평균 상승폭을 평균 하락폭으로 나눈 비율.
  • ROC (Rate of Change): 변화율. 현재 가격이 n 기간 전 가격 대비 몇 % 움직였는지.