손으로 긋는 선의 문제

차트에 수평선을 긋는 건 기술적 분석의 기본기다. 여기가 지지, 저기가 저항. 그런데 이걸 봇에 넣으려고 하면 곧바로 막힌다. 어디에 그을지를 정하는 규칙이 사람 머릿속에만 있기 때문이다.

피보나치 편에서 같은 문제를 봤다. 열 명에게 같은 차트를 주면 열 개의 다른 선이 나오고, 맞은 사람은 지표가 통했다고 하고 틀린 사람은 선을 잘못 그었다고 한다. 이론은 영원히 무죄다.

오토 키 레벨은 이 도피처를 없애는 도구다. 선을 긋는 규칙을 코드로 못 박는 것. 정확도가 사람보다 높아서가 아니라, 재현이 되기 때문에 가치가 있다.

지지·저항이 작동한다는 말의 의미

레벨을 자동으로 뽑는 방법을 보기 전에, 애초에 이게 왜 작동하는지를 짚고 가자. 근거를 알아야 어떤 방식으로 뽑을지 정할 수 있다.

지지선이 지지선인 이유는 그 가격에 주문이 실제로 쌓여 있기 때문이다. 신비로운 힘이 아니라 호가창의 물량이다. 그럼 왜 특정 가격에 주문이 쌓이나.

주문이 특정 가격에 뭉치는 이유
① 본전 심리
   그 가격에 산 사람들이 본전에 팔려고 매도 주문을 걸어둔다
   → 과거에 거래가 많았던 가격대가 저항이 된다
② 재진입 심리
   그 가격에 판 사람들이 다시 사려고 매수 주문을 걸어둔다
   → 같은 가격대가 지지로도 작동한다
③ 관측의 집중
   많은 사람이 같은 차트 도구로 같은 선을 본다
   → 근거가 약해도 주문이 모이면 실재가 된다 (피보나치·라운드 넘버)
④ 알고리즘
   자동매매 프로그램들이 비슷한 조건에 주문을 낸다
①②는 실제 거래 이력에서 나온다 → 거래량 밀집 방식이 이걸 잡는다
③④는 관측 도구에서 나온다 → 피벗·라운드 넘버가 이걸 잡는다
둘은 성격이 다르므로 검증도 따로 해야 한다

이 구분이 중요한 이유는 시장 상황에 따라 어느 쪽이 유효한지가 달라질 수 있기 때문이다. 참여자가 적은 종목에서는 ③번이 약하고, 신규 상장 종목에서는 ①②가 아예 없다. 거래 이력이 없으니 물린 사람도 없다.

내 봇이 신규 상장 종목을 유니버스에서 거르는 이유 중 하나가 이것이다. 지표 워밍업이 안 된다는 기술적 문제가 표면적 이유인데, 더 근본적으로는 가격 구조가 아직 형성되지 않았기 때문이다. 지지도 저항도 없는 가격대에서 눌림목을 논하는 건 의미가 없다.

후보를 만드는 네 가지 방법

레벨을 자동으로 뽑는 방식은 크게 넷이다. 각각 다른 종류의 “중요함"을 잡는다.

레벨 후보 생성 방식
① 스윙 극값       좌우 n봉보다 높은(낮은) 봉 = 프랙탈 고점·저점
   장점: 직관적. 사람이 눈으로 찍는 자리와 가장 비슷
   단점: n봉 뒤에야 확정된다 — 실시간에는 최근 구간이 늘 비어 있다
② 기간 극값       전일·전주·전월의 고가·저가
   장점: 확정 시점이 명확하고 계산이 단순. 참여자 다수가 같은 선을 본다
   단점: 기간 경계가 임의적 (코인 시장에 '하루' 의 경계가 어디인가)
③ 피벗 포인트     (고+저+종)/3 에서 산술적으로 파생
   장점: 정확히 재현된다. 계산에 판단이 안 들어감
   단점: 근거가 계산일 뿐 — 시장에 그 자리가 있어야 할 이유가 없다
④ 거래량 밀집     가격대별 거래량이 몰린 구간
   장점: 실제로 손이 바뀐 자리 — 넷 중 근거가 가장 물질적
   단점: 데이터가 무겁다. 체결 단위 데이터가 필요

내가 제일 신뢰하는 건 ④다. 앞의 셋은 가격의 모양에서 뽑은 것이고, ④만이 실제로 거래가 일어난 사실에서 뽑은 것이기 때문이다. 지지선이 작동하는 메커니즘을 생각해보면 이유가 분명하다. 그 가격에 물린 사람이 본전에 팔려 하고, 그 가격에 판 사람이 되사려 한다. 둘 다 “거기서 실제로 거래가 있었다"를 전제로 한다.

뭉치기 — 진짜 기술은 여기 있다

후보를 뽑고 나면 문제가 생긴다. 너무 많다. 스윙 극값만 해도 몇십 개가 나오고, 그중 상당수는 서로 몇 원 차이로 붙어 있다.

클러스터링 — 붙은 선을 하나로
원본 후보 (예시)
  1,247 · 1,251 · 1,253 · 1,289 · 1,340 · 1,344 · 1,410 …
문제: 1,247/1,251/1,253 은 사실상 같은 자리다
뭉치는 기준을 무엇으로 잡을 것인가
  ✗ 절대 금액 (예: 5원 이내)
     4억짜리 BTC 와 200원짜리 코인에 같은 자를 댈 수 없다
  △ 가격 비율 (예: 0.3% 이내)
     종목 간 비교는 되지만 변동성 차이를 무시한다
  ✓ ATR 배수 (예: 0.5 × ATR 이내)
     변동성이 큰 종목은 넓게, 작은 종목은 좁게 — 자동으로 조정된다
뭉친 뒤 대표값: 단순 평균보다 '후보 개수로 가중한 평균' 이 낫다
여러 번 반복해서 찍힌 자리가 클러스터 중심에 가까워진다

ATR 기준을 쓰는 건 내 봇의 다른 곳에서도 반복되는 패턴이다. 변동성으로 정규화하면 종목마다 다른 자를 대는 문제가 사라진다. 반대로 절대 금액이나 고정 퍼센트로 만든 기준은 언젠가 반드시 특정 가격대의 종목을 차별한다.

이건 경험에서 나온 감각이다. 거래량 게이트를 절대 거래대금으로 판정하다가 “게이트가 종목을 거르는 게 아니라 체급을 차별한다"는 지적을 받고 상대 배수 기준으로 다시 만든 적이 있다. 필터의 편향은 그대로 포트폴리오의 편향이 된다.

거래량 프로파일 — 가장 근거가 튼튼한 방식

앞에서 네 가지 방식 중 ④번(거래량 밀집)을 가장 신뢰한다고 했다. 이건 좀 더 자세히 볼 가치가 있다.

거래량 프로파일은 시간이 아니라 가격을 축으로 거래량을 쌓는 것이다. 일반 거래량 막대는 가로축이 시간인데, 이건 세로축이 가격이다.

거래량 프로파일의 모양
가격대별로 거래량을 누적하면
  1,300원  ██                        거래 적음
  1,250원  ████
  1,200원  ████████████████████      ← 최대 거래 가격대 (POC)
  1,150원  ██████████████
  1,100원  █████
  1,050원  ██
POC(Point of Control) = 가장 많이 거래된 가격
해석: 여기서 가장 많은 손이 바뀌었다 → 물린 사람도 가장 많다
     가격이 돌아오면 매물이 나올 가능성이 크다
밸류 영역 = 전체 거래량의 약 70% 가 몰린 가격 구간
     이 구간 밖은 '거래가 드물었던 가격' — 빠르게 통과하는 경향

구현할 때 필요한 데이터가 문제다. 정확한 거래량 프로파일을 만들려면 체결 단위 데이터가 필요한데, 그건 양이 많고 보관도 부담스럽다.

실무적 타협안은 봉 데이터로 근사하는 것이다. 각 봉의 거래량을 그 봉의 고가-저가 구간에 균등 분배하거나, 종가 기준으로 한 칸에 몰아넣는 방식. 정확도는 떨어지지만 대략의 밀집 구간은 잡힌다.

봉 데이터로 근사하기
가격 구간(bin)을 정한다 — 예: ATR 의 0.2배 단위
각 봉에 대해
    해당 봉의 고가~저가에 걸친 bin 들에 거래량을 분배
    단순 균등 분배 또는 종가에 가중
누적한 뒤 상위 N개 bin 을 레벨 후보로
한계 1: 봉 안에서 실제로 어디에 거래가 몰렸는지는 모른다
한계 2: 오래된 거래에 어떤 가중을 줄지 정해야 한다
   최근 거래가 더 중요하다면 시간 감쇠를 넣는다
완벽하진 않지만 스윙 극값보다 근거가 튼튼하다는 점은 유지된다

한계 2가 설계 판단이 필요한 지점이다. 한 달 전에 크게 거래된 가격대와 어제 크게 거래된 가격대 중 어느 쪽이 더 강한 레벨일까. 직관적으로는 최근이지만, 오래된 매물대가 더 무겁다는 관점도 있다. 오래 물려 있을수록 본전 심리가 강해진다는 논리다.

답은 재봐야 안다. 그리고 이 질문이 재볼 수 있는 형태라는 게 자동화의 이점이다. 손으로 선을 그으면 “이 선이 왜 중요한가"에 대한 답이 감이지만, 코드로 만들면 감쇠 계수를 바꿔가며 비교할 수 있다.

그다음 — 점수 매기기

뭉치고 나서도 레벨이 열 개쯤 남는다. 전부 똑같이 중요할 리는 없다. 그래서 점수를 매긴다.

레벨 강도 점수 — 무엇으로 매길까
터치 횟수      그 자리에서 몇 번이나 반응했나  → 많을수록 강함
최근성         마지막 터치가 얼마나 최근인가   → 오래된 선은 흐려진다
시간축 등급    일봉 레벨 > 15분봉 레벨          → 상위 TF 가 무겁다
반응 크기      그 자리에서 되돌린 폭            → 클수록 실제 저항
거래량         그 가격대에서 체결된 양          → 물린 사람의 수
주의: 터치 횟수는 양날이다
  많이 부딪힌 선은 강하기도 하지만, 많이 부딪힌 선은 곧 뚫리기도 한다
  ('여러 번 두드리면 문은 결국 열린다' 는 관점)
어느 해석이 맞는지는 이 종목·이 시간축에서 재봐야 아는 것이지
일반 법칙으로 정할 수 있는 게 아니다

터치 횟수의 양면성은 지지·저항 이론에서 오래된 논쟁이다. 나는 어느 쪽 편을 들 근거가 없다. 다만 봇에 넣는다면 이걸 상수로 박지 않고 측정 가능한 형태로 두겠다는 건 말할 수 있다. “터치 3회 이상이면 강한 지지"라고 코드에 쓰는 대신, 터치 횟수별로 이후 돌파율이 어떻게 다른지 재는 쪽이다.

스윙 극값 뽑기 — 가장 흔한 방식의 실제 코드

네 방식 중 가장 널리 쓰이는 스윙 극값 방식을 코드로 보자. 프랙탈이라고도 부르는 방법이다.

python — 프랙탈 극값 탐지
# 좌우 n봉보다 높은 봉 = 스윙 고점
def swing_highs(high, n=5):
    win = high.rolling(2*n+1, center=True).max()
    return high[(high == win)]
def swing_lows(low, n=5):
    win = low.rolling(2*n+1, center=True).min()
    return low[(low == win)]
# 함정 1: center=True 는 미래를 본다
#         실시간에서는 오른쪽 n봉이 아직 없다 → n봉 뒤에야 확정
#         백테스트에서 이걸 무시하면 미래 참조가 된다
# 함정 2: 동일한 고가가 연속되면 여러 봉이 동시에 스윙으로 잡힌다
# 함정 3: n 을 바꾸면 스윙 개수가 통째로 달라진다 (n=3 이면 수십 개, n=20 이면 몇 개)
확정 지연을 코드에 명시하는 법
    각 스윙에 '발생 봉' 과 '확정 봉' 을 따로 기록
    판정에는 확정 봉 이후의 데이터만 사용

함정 1이 이 계열 전체의 근본 문제다. center=True로 계산하면 코드는 아무 오류 없이 돌아가고 결과도 예쁘게 나온다. 그런데 그 결과는 미래를 본 것이다.

백테스트에서 이 실수를 하면 성적이 아주 좋게 나온다. 스윙 저점을 정확히 찍고 거기서 산 것처럼 계산되니까. 그리고 실전에 올리면 전혀 다른 결과가 나온다. 실시간에는 그 저점이 저점인지 알 방법이 없기 때문이다.

이 문제를 피하는 방법은 하나뿐이다. 모든 레벨에 확정 시점을 함께 기록하고, 그 시점 이후의 판정에만 쓰는 것. 번거롭지만 이걸 안 하면 백테스트 결과 전체가 의미를 잃는다.

라운드 넘버라는 공짜 레벨

계산이 전혀 필요 없는 레벨도 있다. 1,000원, 10,000원, 100,000원 같은 딱 떨어지는 숫자다.

사람이 만드는 레벨
왜 작동하나 — 근거는 시장 구조가 아니라 인간의 습관이다
  주문을 걸 때 987원보다 1,000원에 거는 사람이 많다
  목표가를 정할 때도 "10,000원 가면 판다" 같은 식으로 잡는다
  → 그 가격에 주문이 실제로 쌓인다 → 지지·저항으로 기능한다
자동 추출은 아주 단순하다
  가격대에 따라 단위를 정하고, 현재가 위아래 가장 가까운 값을 뽑는다
  100~1,000원 → 100원 단위 · 1,000~10,000원 → 1,000원 단위 …
한계: 근거가 얇다. 검증 없이 믿을 만한 물건은 아니다
다만 검증 설계는 쉽다 — 라운드 넘버 ±0.2×ATR 진입 시 반등률 vs
무작위 가격 ±0.2×ATR 진입 시 반등률. 대조군 만들기가 간단하다

피보나치 편에서 다뤘던 자기실현 논리와 정확히 같은 구조다. 근거가 수학적이냐 심리적이냐만 다르고, “많은 사람이 같은 자리를 본다"는 메커니즘은 동일하다. 그리고 검증 방법도 동일하다 — 무작위 레벨을 대조군으로 세우는 것.

라운드 넘버가 흥미로운 건 오히려 이쪽이 근거가 더 정직하다는 점이다. 피보나치는 자연법칙인 척하지만 라운드 넘버는 처음부터 “사람들이 이 숫자를 좋아해서"라고 말한다. 겸손한 가설이 검증하기도 쉽다.

내 봇에 이미 있는 것

사실 내 봇에도 자동 레벨의 원시적인 형태가 이미 들어 있다. 24시간 저점이다.

저점근접 게이트는 현재가가 24시간 저점에서 얼마나 가까운지를 보고, 이 기준값은 8.9일치 매매기록을 전수 스윕해서 3.0으로 확정했다. 열한 개 게이트 축을 전부 쓸어보고, 수익·승률·손절권 진입률이 동시에 개선되는 값만 채택한 결과다.

현행 레벨과 오토 키 레벨의 관계
현행: 24시간 저점 하나
  장점 — 계산이 가볍고, 애매함이 없고, 실측으로 값이 확정돼 있다
  한계 — 하루라는 창 밖의 구조를 못 본다
         일주일 전에 세 번 지지받은 자리가 코앞이어도 모른다
확장안: 다중 시간축 레벨 + 강도 점수
  기대 — '왜 하필 여기서 반등했나' 에 대한 답이 하나 늘어난다
  비용 — 상태 관리가 복잡해진다. 레벨은 값이 아니라 목록이고,
         목록은 생성·갱신·폐기 규칙을 다 설계해야 한다
폐기 규칙이 특히 중요하다 — 뚫린 레벨을 언제 목록에서 지울 것인가
사람이 손으로 그은 선의 최대 문제가 바로 '지우지 않는다' 는 것이다

마지막 줄을 강조하고 싶다. 차트에 선을 그어두면 가격이 그 선을 뚫고 한참 지나가도 선은 그대로 남아 있다. 그리고 사람은 남아 있는 선을 계속 쳐다본다. 봇에는 이 관성이 없어야 한다. 레벨을 만드는 규칙만큼 없애는 규칙을 명시해야 한다.

여러 시간축을 어떻게 합칠 것인가

일봉에서 뽑은 레벨과 15분봉에서 뽑은 레벨을 같이 쓰려면 규칙이 필요하다. 그냥 다 합치면 목록이 감당 안 되게 길어진다.

시간축 통합 규칙
원칙 1 — 무게를 다르게
  일봉 레벨 3점 · 4시간 2점 · 15분 1점 같은 식으로 등급을 매긴다
  상위 시간축일수록 더 많은 참여자가 본다는 전제
원칙 2 — 겹치면 가산
  일봉 레벨과 15분 레벨이 ATR 반경 안에서 겹치면 점수를 합산
  이게 흔히 말하는 '합류(confluence)' 의 자동화 버전이다
원칙 3 — 조합을 미리 고정할 것
  합류를 사후에 찾으면 아무 데서나 찾아진다 (피보나치 편에서 다룬 문제)
  '일봉 + 4시간' 처럼 어떤 조합을 유효로 칠지 코드에 박아두고 시작한다
원칙 4 — 상위 시간축은 확정이 더 늦다
  일봉 스윙은 n일이 지나야 확정된다 — 며칠짜리 지연
  '최근 일봉 레벨' 은 실시간에 존재하지 않는다는 걸 받아들여야 한다

원칙 4가 실무에서 제일 자주 무시된다. 차트에는 며칠 전 스윙 고점이 선명하게 찍혀 있으니 그게 그때부터 존재했던 것 같지만, 실제로는 확정에 시간이 걸린다. 백테스트에서 이걸 무시하면 미래 정보를 쓰게 되고, 그렇게 만든 전략은 실전에서 반드시 다르게 움직인다.

내 봇의 상위 시간축 판정도 이 문제를 겪었다. 시장 상황을 BTC 4시간봉으로 판정하는데, 진행 중인 봉으로 하면 몇 분마다 하락장 판정이 뒤집힌다. 그래서 완성봉 기준으로 못 박았다. 판정이 4시간에 한 번만 바뀌는 게 답답해 보이지만, 그게 정직한 정보량이다.

뚫린 레벨은 어떻게 되나

지지선이 뚫리면 저항선이 된다는 얘기를 자주 듣는다. 역할 전환이라고 부르는 현상이다.

논리는 이렇다. 지지선에서 산 사람들이 그 선이 뚫리면서 손실 구간에 들어가고, 가격이 다시 그 선까지 올라오면 본전에 팔려고 한다. 그래서 이번엔 저항으로 작동한다는 것.

역할 전환을 코드로 다루려면
필요한 상태
   level.price      가격
   level.role       SUPPORT | RESISTANCE
   level.broken_at  뚫린 시점 (없으면 미돌파)
   level.touches    터치 횟수
전환 규칙 (설계 판단이 필요한 것들)
   Q1. 얼마나 뚫려야 '뚫렸다' 인가
       종가 기준? 저가 기준? 관통 폭이 ATR 의 몇 배 이상?
   Q2. 뚫린 뒤 얼마나 지나면 레벨을 폐기하나
       영원히 저항으로 남기면 목록이 무한히 늘어난다
   Q3. 전환된 레벨의 강도는 원래와 같은가
       약해진다고 보는 게 일반적이지만 근거는 약하다
세 질문 다 정답이 없다 — 그래서 코드에 박고 재보는 수밖에 없다
손으로 그은 선은 이 질문들을 통째로 회피한다. 그게 편하지만 그래서 검증도 못 한다

Q1이 특히 까다롭다. 가격이 지지선을 살짝 스치고 올라오는 일이 흔한데, 그걸 돌파로 볼지 말지에 따라 레벨 목록이 완전히 달라진다.

내 봇에서 비슷한 판정 문제를 겪은 적이 있다. 진입한 종목의 저점이 깨지면 조기 청산하는 로직을 검토했는데, 실측해보니 60건 중 27건이 저점을 스치고 회복했다. 절반 가까이가 일시적 관통이었던 것이다. 그래서 그 로직은 채택하지 않았다. 기준선 −213,394원 대비 모든 변형이 나빴다.

이 경험이 알려준 게 관통을 어떻게 정의하느냐가 결과를 좌우한다는 것이다. 종가 기준으로 하면 관대해지고 저가 기준으로 하면 엄격해진다. 어느 쪽이 맞는지는 전략마다 다르고, 결국 재봐야 안다.

이 지표류의 근본 한계

정직하게 짚고 끝내자. 오토 키 레벨은 재현 가능성을 얻는 대신 두 가지를 포기한다.

자동화가 못 하는 것
① 맥락
   사람은 "이 자리는 상장 직후 첫 급락이 멈춘 곳" 같은 서사를 안다
   코드는 그냥 좌우보다 낮은 봉으로만 본다
② 확정 지연
   스윙 극값은 n봉이 지나야 확정된다 — 지금 만들어지는 중인 레벨은 못 쓴다
   차트에 예쁘게 그려진 과거 레벨은 전부 사후 확정된 것이다
   이 지연을 무시하고 백테스트하면 미래를 참조하게 된다
얻는 것: 같은 입력에 항상 같은 선. 그래서 검증이 가능하다
        검증 가능성은 정확도보다 앞선 조건이다 — 틀린 걸 알 수 있어야 고친다

②번 함정은 이 코너에서 계속 반복되는 주제다. 시장 구조를 다루는 지표는 거의 전부 이 문제를 안고 있다. 차트를 열면 과거의 구조가 선명하게 보이는데, 그건 그 구조가 이미 완성됐기 때문이다. 실시간의 봇에게 주어지는 정보는 언제나 더 늦고 더 성기다.

레벨을 만들었으면 무엇에 쓸 것인가

레벨 목록을 만들고 나면 그다음이 문제다. 이걸 어디에 붙일지 정하지 않으면 그냥 예쁜 선으로 끝난다. 쓸 자리는 크게 셋이다.

레벨의 세 가지 용도
① 진입 조건
   "지지 레벨 ±0.3×ATR 안에 있을 때만 산다"
   위험: 레벨이 촘촘하면 사실상 아무 조건도 아니게 된다
         레벨 개수를 강도 상위 N개로 제한하는 게 먼저
② 진입 가격 개선
   진입은 다른 조건으로 결정하되, 지정가를 가장 가까운 지지 레벨에 건다
   위험이 작다 — 진입 여부를 안 바꾸고 체결 가격만 다듬는다
   실험 순서로는 여기가 1순위
③ 청산 기준
   가장 가까운 저항 레벨을 목표가로, 지지 이탈을 손절로
   위험: 현행 청산 로직 전체와 충돌한다. 손대려면 통째로 재설계해야 한다
②→①→③ 순서로 시도하는 게 안전하다. 뒤로 갈수록 시스템에 미치는 범위가 넓다

②번을 1순위로 꼽는 이유가 있다. 진입 여부를 바꾸지 않는 변경은 되돌리기 쉽고, 효과 측정도 깔끔하다. 같은 신호에 대해 체결가만 비교하면 되니까 대조군을 만들기도 간단하다.

반면 ①번처럼 진입 조건을 건드리면 신호 개수 자체가 변해서, 성과가 달라졌을 때 그게 조건 덕인지 표본이 달라진 탓인지 구분하기 어려워진다. 시스템을 고칠 때 변경 범위가 작은 것부터 손대는 게 규율인 이유다.

레벨이 얼마나 자주 지켜지는지부터

이 글 전체가 “레벨을 어떻게 만들 것인가"였는데, 사실 그 앞에 물어야 할 질문이 있다. 레벨이라는 게 실제로 작동하기는 하는가.

이건 측정 가능한 질문이다. 만들어둔 레벨에 가격이 닿았을 때 반등한 비율을 세면 된다. 그리고 반드시 대조군이 필요하다 — 레벨이 아닌 무작위 가격에 닿았을 때의 반등 비율과 비교해야 의미가 나온다. 피보나치 편에서 세운 절차 그대로다.

여기에 한 가지를 더 봐야 한다. 레벨이 뚫렸을 때의 손실 크기다. 지지선이 70% 확률로 지켜져도, 뚫리는 30%에서 크게 잃으면 기대값은 마이너스가 될 수 있다. 승률과 손익 크기를 함께 봐야 한다는 원칙이 여기서도 적용된다.

내 봇의 파라미터를 정할 때 목적함수를 수익·승률·손절권 진입률 셋으로 두는 이유가 이것이다. 하나만 보면 반드시 어딘가에서 대가를 치른다. 레벨 검증도 같은 방식으로 설계해야 한다.

SMC의 오더블록과 무엇이 다른가

지난 SMC 편에서 오더블록을 다뤘는데, 그것도 결국 자동으로 뽑은 레벨이다. 둘을 비교하면 접근의 차이가 보인다.

오더블록 vs 일반 키 레벨
오더블록
   생성 조건: 구조 돌파가 일어났을 때, 그 직전 구간의 극값 봉
   형태: 점이 아니라 구간 (봉의 고가~저가)
   서사: '큰돈이 급히 떠난 자리'
일반 키 레벨
   생성 조건: 스윙 극값 · 기간 극값 · 거래량 밀집 등
   형태: 대개 점 (가격 하나) 또는 좁은 밴드
   서사: '주문이 쌓인 자리'
공통점
   둘 다 생성·소멸 규칙을 가진 자료구조다 (값 하나가 아니다)
   둘 다 확정에 지연이 있다
   둘 다 목록이 길어지는 문제를 관리해야 한다
차이는 '어떤 사건을 계기로 레벨을 만드느냐' 뿐이다

이렇게 놓고 보면 SMC의 오더블록이 특별한 물건이 아니라는 게 분명해진다. 레벨을 뽑는 여러 방식 중 하나이고, 다만 생성 계기를 구조 돌파로 잡았다는 점이 특징이다.

그리고 이 관점에서 보면 검증 방법도 같아진다. 오더블록이든 스윙 레벨이든 거래량 밀집이든, “그 레벨 근처에서 반등 확률이 무작위보다 높은가"를 대조군과 비교해서 재면 된다. 서사가 다르다고 검증 방법이 달라지지 않는다.

여러 방식을 나란히 재보면 어느 생성 규칙이 가장 유효한지 순위가 나올 것이다. 그게 이 주제에서 제일 해보고 싶은 실험이다. 지금은 각 진영이 자기 방식을 옹호하는 서사만 있고, 같은 자로 잰 비교는 잘 안 보인다.

레벨을 화면에 어떻게 보여줄 것인가

봇에 넣는 얘기만 했는데, 운영 관점도 하나 짚고 싶다. 레벨을 계산했으면 그걸 눈에 보이게 해야 한다.

내 봇은 화면이 터미널이다. 차트가 없으니 선을 그릴 수도 없다. 그래서 지표값이든 레벨이든 텍스트로 찍는 수밖에 없는데, 이게 은근히 중요한 문제였다.

보이지 않는 조건의 위험
겪은 일
   "조건은 다 통과했는데 왜 안 샀지?" 하는 종목이 자꾸 보였다
   추적해보니 게이트 밖에서 스프레드 조건에 막히고 있었다
   판정은 하는데 화면에 안 보이니 원인을 몰랐던 것
대응: 스프레드를 정식 게이트로 승격시켜 통과율 통계에 포함
교훈 — 보이지 않는 조건은 없는 조건보다 나쁘다
   없으면 없는 줄 알지만, 있는데 안 보이면 디버깅이 불가능해진다
레벨 시스템에 적용하면
   현재가 위아래 가장 가까운 레벨과 거리를 상시 표시
   진입 판정 때 어느 레벨을 참조했는지 로그에 남기기
   레벨 개수와 폐기 건수를 스캔마다 기록 (목록 폭주 감지)

세 번째 항목이 상태를 가진 부품의 공통 요구사항이다. 레벨 목록은 계속 늘어나고 줄어드는데, 그 개수를 안 보면 어느 날 목록이 수백 개로 불어나 있어도 모른다. 오더블록을 다룰 때도 같은 문제를 봤다 — 목록에 상한을 두고, 그 상한에 걸린 사실도 기록해야 한다.

터미널 폭도 실제 제약이었다. 한글은 두 칸을 차지해서 계산을 잘못하면 줄바꿈이 일어나고 화면이 무너진다. 표시 폭을 맞추는 작업에 생각보다 많은 버전을 썼다. 사소해 보이지만, 화면이 깨지면 그 순간 관측 능력을 잃는다.

이 주제에서 제일 하고 싶은 실험

글을 정리하면서 하고 싶은 실험이 하나 분명해졌다. 레벨 생성 방식들을 같은 자로 재보는 것이다.

생성 방식 대결 설계
참가자
   A 스윙 극값 (n = 5 / 10 / 20)
   B 전일·전주 고저
   C 거래량 밀집 상위 3개
   D 라운드 넘버
   E 무작위 가격  ← 대조군
측정
   각 방식의 레벨 ±0.3×ATR 에 가격이 닿은 뒤 k봉 내 반등률
   그리고 뚫렸을 때의 평균 손실 폭
판정
   E(무작위) 를 못 이기는 방식은 탈락
   이긴 방식들 사이에서는 반등률과 손실 폭을 함께 본다
예상: 상당수가 E 를 못 이길 것 같다. 그래도 재봐야 안다

E를 넣는 게 핵심이다. 대조군 없이 “레벨에서 60% 반등했다"는 숫자만 보면 대단해 보이지만, 무작위 가격에서도 55%가 나온다면 그 레벨은 아무 일도 안 한 것이다.

이 절차는 피보나치 편에서 세운 것과 같다. 근거가 서사에서 오는 도구들 — 피보나치, 오더블록, 지지·저항 — 은 전부 이 방식으로 검증해야 한다고 생각한다. 서사가 그럴듯할수록 대조군이 더 필요하다.

마무리

지지선을 손으로 긋는 일에는 즐거움이 있다. 차트를 들여다보다가 “아, 여기구나” 하는 순간이 오면 시장을 이해한 것 같은 기분이 든다.

그 기분이 문제다. 이해했다는 감각과 실제로 맞히는 능력은 다른 것이고, 손으로 그은 선으로는 그 둘을 구분할 방법이 없다. 규칙을 코드에 못 박는 순간 즐거움은 사라지지만, 대신 틀렸을 때 틀렸다는 걸 알 수 있게 된다.

나는 그 교환을 했고, 지금은 잘한 거래였다고 생각한다.

덧붙임 — 레벨 없이도 되는 이유

마지막으로 균형을 위해 반대편 얘기를 하겠다. 내 봇은 지금 정교한 레벨 시스템 없이도 돌아간다. 24시간 저점 하나뿐이다.

단순한 것의 장점
현행: 24시간 저점 대비 거리 하나
  · 계산이 가볍다 (매 스캔 수십 종목 × 실시간)
  · 상태가 없다 (목록 관리·폐기 규칙 불필요)
  · 애매함이 없다 (어느 저점인지 논쟁의 여지 없음)
  · 값이 실측으로 확정돼 있다 (전수 스윕 결과 3.0)
잃는 것
  · 하루 창 밖의 구조를 못 본다
  · 일주일 전 세 번 지지받은 자리가 코앞이어도 모른다
이 교환이 나쁜지는 재봐야 안다
복잡한 레벨 시스템이 24시간 저점보다 나은지는 아직 증명된 바 없다

이 점을 강조하고 싶다. 이 글에서 클러스터링이니 강도 점수니 시간축 통합이니 한참 설명했지만, 그게 실제로 더 나은지는 아직 모른다. 복잡한 시스템이 단순한 규칙을 이긴다는 보장은 없다.

내 봇 이력에도 그런 사례가 있다. 포지션을 절반으로 나눠 한쪽은 확정하고 한쪽은 큰 상승을 기다리게 하는 설계를 넣었다가 하루 만에 철회했다. 정교한 쪽이 단순한 쪽보다 나빴던 것이다. 복잡성은 그 자체로 비용이고, 그 비용을 넘는 이득이 있는지는 매번 확인해야 한다.

그래서 이 글의 결론은 “레벨 시스템을 만들어라"가 아니다. 만들 거면 검증 가능한 형태로 만들고, 만들기 전에 지금 쓰는 단순한 규칙과 비교하라는 쪽에 가깝다. 손으로 긋는 선을 코드로 옮기는 게 진보인 이유는 정교해져서가 아니라 비교할 수 있게 되기 때문이다.