13세기 수열은 어쩌다 차트에 눌러앉았나

피보나치 얘기가 나오면 대화가 좀 이상해진다. 수열 설명까지는 다들 멀쩡한데, 황금비가 등장하는 순간 목소리가 경건해진다. 그게 싫어서 이 편을 계속 미뤄뒀다.

수열 자체는 시시하다. 1202년 이탈리아 수학자 레오나르도 피보나치가 토끼 번식 문제로 소개했다. 1, 1, 2, 3, 5, 8, 13, 21… 앞의 두 항을 더하면 다음 항. 이게 트레이딩으로 넘어온 건 인접한 두 항의 비율이 황금비 φ ≈ 1.618로 수렴하기 때문이다. 그 역수 0.618, 제곱 역수 0.382 같은 파생 비율이 “가격이 되돌아오는 깊이"의 기준선으로 쓰인다.

fibonacci — 수열에서 비율로, 비율에서 레벨로
수열:   1   1   2   3   5   8   13   21   34   55   89 ...
인접비: 1.0  2.0  1.5  1.667  1.6  1.625  1.615  1.619  1.618 → φ 수렴
파생 비율과 되돌림 레벨
  0.236 = 한 항 건너 셋 비율의 역     "얕은 되돌림"
  0.382 = 1/φ²                        "표준 눌림"
  0.618 = 1/φ                         "황금 되돌림"
  0.786 = √0.618                      "깊은 되돌림"
  0.500 = 피보나치가 아님 — 다우 이론의 반값 되돌림 전통이 얹혀 산다
계산: 상승 스윙(low→high) 기준  레벨 = high - (high - low) × 비율

신화부터 걷어내자. “황금비는 자연의 법칙이니 시장에도 작동한다"는 문장은 검증된 적이 없다. 해바라기 씨앗 배열과 업비트 15분봉 사이에 인과가 있을 이유가 없다.

그런데도 이 레벨들을 무시하기 어려운 이유는 따로 있다. 수백만 명이 같은 자리에 같은 선을 긋는다. 모두가 0.618에 매수 주문을 걸어두면 0.618은 실제로 지지선이 된다. 법칙이라서 작동하는 게 아니다. 합의라서 작동한다. 이 구분이 오늘 글의 뼈대다.

계산은 두 줄, 진짜 문제는 스윙

레벨 계산은 이 코너에서 다룬 것 중 제일 쉽다. 뺄셈과 곱셈이 전부다. 그런데 코드로 옮기는 순간, 차트에서는 안 보이던 문제가 튀어나온다. 어느 스윙에 긋는가.

python — 레벨 계산은 쉽고, 스윙 선택은 어렵다
# 레벨 계산 — 이게 전부다
def fib_levels(swing_low, swing_high):
    r = swing_high - swing_low
    return {k: swing_high - r * k for k in (0.236, 0.382, 0.5, 0.618, 0.786)}
# 진짜 문제 — swing_low, swing_high 를 누가 정하는가
# 사람: 차트를 보고 '의미 있어 보이는' 스윙을 고른다 (재현 불가)
# 봇  : 스윙 정의를 고정한다 — 예: N봉 좌우에서 최고/최저인 봉 (fractal)
def swing_high_confirmed(h, n):
    return h.shift(n) == h.rolling(2*n+1, center=True).max().shift(n)
# 주의: 이 판정은 n봉 뒤에야 확정된다 — SMC 편의 확정 지연과 동일한 문제.
#       n을 바꾸면 스윙이 바뀌고, 스윙이 바뀌면 모든 레벨이 통째로 이동한다

사람이 손으로 긋는 피보나치를 백테스트할 수 없는 이유가 여기 있다. 열 명에게 같은 차트를 주면 열 개의 스윙이 나오고, 열 벌의 레벨이 나온다. 맞은 사람은 “피보나치가 작동했다"고 기억하고, 틀린 사람은 “스윙을 잘못 그었다"고 기억한다. 이론은 영원히 무죄다.

봇에는 그 도피처가 없다. 스윙 정의를 코드로 고정해야 하고, 고정하는 순간 피보나치는 처음으로 반증 가능한 가설이 된다. 나는 이걸 퇴보라고 보지 않는다. 승격이다.

레벨을 다 그어놓고 보면

retracement — 상승 스윙의 되돌림 지도
  high ────────────────────── 0.000   ▲ 스윙 고점
              ╲
  ───────────── ╲ ─────────── 0.236   얕은 눌림 (강한 추세)
  ──────────────╲──────────── 0.382   표준 눌림
  ───────────────╲─────────── 0.500   반값 (비-피보나치)
  ────────────────╲╱╲──────── 0.618   황금 되돌림 ◀ 반등 시도
  ─────────────────────────── 0.786   깊은 되돌림 (추세 의심 구간)
  low  ────────────────────── 1.000   ▼ 스윙 저점 (전량 반납)
  주의: 레벨이 5개면 스윙 범위의 상당 부분이 '레벨 근처'다.
  아무 데서나 반등해도 사후에는 어떤 레벨과든 가깝다 — 대조군이 필요한 이유

실전 사용자들은 여기에 ‘합류(confluence)‘를 얹는다. 0.618 되돌림이 이동평균과 겹치고 전 저항선과도 겹치면 신뢰도가 높다는 논리다. 논리 자체는 건전하다. 독립적인 근거의 교차는 통계에서도 신뢰를 올린다. 실무에서는 합류가 사후 선택으로 미끄러지기 쉽다는 게 걸린다. 레벨 5개, 이동평균 3개, 전고점·전저점 여러 개를 깔아두면 차트 어디를 찍어도 두세 개는 겹친다. 합류를 신호로 쓰려면 어떤 조합을 유효한 합류로 칠지 사전에 고정해야 한다. 또 봇의 언어로 돌아왔다.

되돌림 말고 확장

피보나치의 절반은 되돌림(retracement)이고 나머지 절반은 **확장(extension)**이다. 되돌림이 “얼마나 빠질까"를 묻는다면 확장은 “얼마나 갈까"를 묻는다. 익절 목표를 잡는 쪽이다. 1.272, 1.618, 2.618 같은 비율을 스윙 범위에 곱해 목표가를 만든다.

extension — 되돌림이 진입이라면 확장은 익절이다
  스윙 low → high 를 100 으로 놓고
   2.618 ──────────────────────  공격적 목표 (드물게 도달)
   1.618 ──────────────────────  표준 확장 목표 ◀ 가장 널리 쓰임
   1.272 ──────────────────────  보수적 확장 목표
   1.000 ──────────────────────  전고점
   0.618 ──────────────────────  되돌림 진입 후보
  실무: 0.618 에서 사고 1.618 에서 판다 = 손익비 약 1:1.6 구조
  내 봇의 대응물 — 비율이 아니라 실측으로 정한 값들
   익절 목표    포트폴리오 예상실현 20,000원 (VV209, 빈도 극대화)
   손절         -5% (VV204, 다층 방어 완성 후 확장)
   본전 사수    무장 +0.8% / 바닥 +0.4% (VV206, 호가 관통 실측 반영)
   근거의 출처가 다르다: 수열의 비율 vs 내 계좌의 27건·60건 재현

여기서 두 접근의 차이가 선명해진다. 피보나치 확장은 “스윙 폭의 1.618배"라는 비율의 세계에 산다. 내 봇의 익절은 “예상실현 2만원"이라는 절대금액의 세계에 산다.

어느 쪽이 나은지는 상황에 달렸다. 계좌가 커지면 절대금액 기준은 언젠가 비율 기준으로 갈아타야 한다. 다만 지금 이 계좌, 이 슬롯 크기에서는 절대금액이 더 정직했다. 이익 구간 체류시간 중앙값이 0분이라는 실측(VV210) 앞에서 1.618배까지 기다리는 설계는 성립하기 어려웠다.

대조군 없는 백테스트는 그냥 무효

이 지표의 검증에는 다른 지표에 없던 절차가 하나 붙는다. 대조군이다. “0.618 근처에서 반등이 잦더라"는 아무것도 증명하지 않는다. 레벨이 촘촘하니 반등은 어차피 어딘가의 레벨 근처에서 일어난다. 증명하려면 무작위 레벨과 붙여봐야 한다.

validation design — 자기실현 가설의 검증 절차
가설: "피보나치 레벨 근처에서 반등 확률이 유의하게 높다"
  ① 스윙 정의 고정      fractal n ∈ {5, 10, 20} — n마다 별도 실험
  ② 실험군              가격이 각 레벨 ±0.2×ATR 진입 후 k봉 내 반등률 측정
  ③ 대조군              같은 스윙에 무작위 비율 5개(균등분포)로 같은 측정
  ④ 판정                실험군 반등률이 대조군 분포의 바깥(상위 5%)일 때만 유효
추가 대조: 0.618 단독 vs 0.5 vs 0.382 — '황금비' 가 정말 특별한지 분리 측정
이 표의 결과 칸이 비어 있는 이유: 아직 측정 전이다.
측정 전의 숫자를 적는 순간 이 스터디는 광고가 된다 — 이 코너의 서명.

예상을 하나 적어두자면(예상은 수치가 아니니까 적어도 된다), 실험군이 대조군을 크게 이기지 못할 가능성도 꽤 된다고 본다. 그래도 해볼 값어치는 있다. 지면 ‘피보나치 합류’라는 말을 계좌 근처에서 영구 추방할 근거가 생기고, 이기면 게이트 후보가 하나 는다. 검증의 좋은 점은 어느 쪽 결과든 남는다는 것이다.

이름만 안 붙였을 뿐

SMC 편에서 했던 고백을 여기서 또 하게 된다. 내 봇은 피보나치를 그은 적이 없다. 그런데 되돌림 깊이는 이미 재고 있다. 진입 게이트의 낙폭 상한(며칠 고점 대비 얼마나 빠졌나), 저점근접 게이트(24시간 저점 대비 얼마나 가까운가, VV201 스윕 확정값 3.0). 둘 다 ‘되돌림이 충분히 깊은가’를 피보나치 비율 대신 실측 스윕값으로 판정하는 장치다.

SMC의 디스카운트 존이 스윙 범위 하단이었던 것, 그 존의 중앙(이퀼리브리엄)이 정확히 0.5 되돌림인 것까지. 세 편의 스터디가 결국 같은 질문의 세 표기법으로 수렴하고 있다. “얼마나 빠졌을 때가 사기 좋은 자리인가.”

차이는 기준선을 어디서 가져오느냐다. 피보나치는 13세기 수열에서, SMC는 스윙 범위 등분에서, 내 봇은 내 매매기록 스윕에서 가져온다. 나는 당연히 세 번째를 믿는 쪽이다. RSI 편에서 적었던 원칙 그대로, 공식은 수입해도 파라미터는 국산이어야 한다. 0.618이 내 매매기록에서 유의하게 나온다면 그때 0.618을 쓰면 된다. 황금비라서가 아니라, 측정돼서.

만든다면 이런 모습

가설 검증이 통과했다고 치고, 실제 모듈이 되면 어떤 모습일지도 그려두자. 피보나치는 RSI 같은 단일 값이 아니다. SMC의 오더블록처럼 상태를 관리하는 자료구조가 된다.

implementation — 피보나치를 봇 모듈로 만들면
state.active_swing   = (low_price, low_bar, high_price, high_bar)
state.levels         = {0.382: p1, 0.5: p2, 0.618: p3, 0.786: p4}
state.invalidated    = bool   # 스윙 저점 이탈 시 True → 레벨 전체 폐기
갱신 규칙
  ① 새 스윙 고점 확정 → 활성 스윙 갱신, 레벨 재계산
  ② 가격이 스윙 저점 하향 이탈 → 이 스윙의 레벨 전부 무효 (재사용 금지)
  ③ 상위 TF 스윙과 하위 TF 스윙을 동시에 보유 (합류 판정용, 조합 사전 고정)
질의 인터페이스
  nearest_level_below(price) → (레벨값, 거리/ATR, 비율)
  is_in_zone(price, k=0.2)   → 어떤 레벨의 ±k×ATR 안에 있는가
필수 기록: 각 레벨의 '생성 봉' 과 '확정 봉' 을 분리 저장
          스윙은 n봉 뒤 확정 — 이 지연을 무시하면 미래 참조 백테스트가 된다

②번 규칙을 특히 강조하고 싶다. 손으로 그은 피보나치의 최대 약점은 폐기 규칙이 없다는 것이다. 가격이 스윙 저점을 뚫고 내려가도 선은 차트에 남아 있고, 사람은 남은 선을 계속 쳐다본다. 봇에는 무효화 규칙이 코드로 있어야 한다. 그리고 이 요구사항은 다음 편(엘리엇 파동)의 ‘무효화 라인’ 개념과 정확히 같은 물건이다.

회의론 쪽 얘기도 적어두자

공정하게, 피보나치 회의론의 논거도 정리해 둔다. 첫째, 0.5는 피보나치 수열과 아무 관계가 없다. 인접 항 비율 어디서도 0.5가 나오지 않는데, 실전에서는 반값 되돌림이 제일 널리 인용된다. 다우 이론의 전통이 슬쩍 얹혀 산 것이다. ‘피보나치 레벨’이라 불리는 묶음에 비-피보나치가 섞여 있는데 아무도 문제 삼지 않는다. 이 도구가 수학적 엄밀성으로 쓰이고 있지 않다는 방증이다.

둘째, 레벨의 밀도. 0.236·0.382·0.5·0.618·0.786 다섯 개를 그으면 스윙 범위의 상당 부분이 어떤 레벨의 근처가 된다. 여기에 확장 레벨과 다른 시간축 스윙까지 얹으면 차트가 선으로 뒤덮인다. 사후에 “역시 피보나치에서 돌았다"고 말하기는 너무 쉽다. 셋째, 되돌림 자체의 흔함. 오른 것은 조금 되돌리는 게 정상이라, 어떤 되돌림 이론이든 기본 적중률이 이미 높다. 대조군 없이는 아무 말도 할 수 없는 이유다.

skeptic's checklist — 피보나치 주장을 만났을 때의 3문
누군가 "피보나치가 먹혔다" 고 말할 때 물어야 할 것
  ① 스윙을 언제 정했나?    사후에 골랐다면 데이터가 아니라 이야기다
  ② 대조군은?              무작위 레벨 대비 우위가 없으면 착시다
  ③ 실패 사례의 수는?      맞은 것만 세면 어떤 이론이든 100%다
이 세 질문은 피보나치 전용이 아니다 — 모든 차트 패턴 주장에 그대로 적용된다
그리고 봇 개발자에게는 셋 다 자동으로 해결된다:
  스윙 정의가 코드에 고정 → ① 해결   전수 재현 → ③ 해결
  대조군만 직접 설계하면 된다 → 위 검증 계획

그럼에도 이 지표를 완전히 버리지는 않는다. 이유는 하나다. 유동성은 사람이 보는 곳에 뭉친다. 지지선이 물리 법칙이어서 생기는 게 아니고, 그 가격에 주문을 걸어둔 사람이 실제로 있어서 체결이 일어난다. SMC 편의 EQH/EQL(유동성 풀)과 논리 구조가 똑같다. 그렇다면 검증할 진짜 가설은 황금비의 신비로움 쪽이 아니다. “널리 관측되는 가격대 근처에 주문이 뭉친다”, 이쪽이다. 그리고 이건 측정 가능하다.

남은 생각

피보나치는 이 코너에서 다룬 것 중 가장 오래됐고 가장 신비화된 물건이다. 해부하고 나니 남는 건 뺄셈과 곱셈, 그리고 진짜 문제 두 개였다. 스윙 선택의 자의성, 대조군 없는 검증의 착시. 신비는 수열 쪽에 없었다.

오히려 재밌는 건 사회 현상으로서의 피보나치다. 근거가 희박한 기준선도 충분히 많은 사람이 믿으면 실재가 된다. 시장은 물리계가 아니다. 참여자들이 서로의 예측을 예측하는 게임이다. 그걸 이만큼 선명하게 보여주는 지표를 나는 못 봤다.

그래서 결론은 양가적이다. 황금비의 신화는 버린다. ‘많은 사람이 보는 선 근처에서 유동성이 뭉친다’는 가설은 검증 대상으로 남긴다. 신화와 가설을 가르는 건 딱 하나, 반증 절차의 유무다. 위의 대조군 실험이 그 절차고, 결과가 나오면 속편으로 보고하겠다.

케인즈는 주식 투자를 미인대회에 비유했다. 내가 예쁘다고 생각하는 얼굴 말고, 남들이 예쁘다고 생각할 얼굴을 골라야 이기는 게임. 피보나치 레벨은 그 미인대회의 후보 명단쯤 된다. 0.618이 아름다워서 당선되는 게 아니고, 모두가 그 번호를 적어내서 당선되는 것이다. 그래서 내가 재보고 싶은 건 황금비의 신비 쪽이 아니다. 오늘도 그 번호를 적는 손이 몇이나 남았는가다. 그런데 이걸 재려면 호가창 쪽 데이터를 뒤져야 할 텐데, 업비트 공개 데이터로 어디까지 되는지는 아직 안 알아봤다.


약어 풀이

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

  • RSI (Relative Strength Index): 상대강도지수. 일정 기간 평균 상승폭과 평균 하락폭의 비율을 0~100 으로 나타내 과매수·과매도를 재는 모멘텀 오실레이터.
  • ATR (Average True Range): 평균진폭. 고가·저가·전일 종가를 아우르는 하루 실제 변동폭(True Range)의 이동평균. 변동성 지표.
  • SMC (Smart Money Concepts): 스마트머니 개념. 기관 자금의 흔적을 구조 돌파·오더블록·유동성 사냥 같은 가격 행동으로 읽는 분석 틀.
  • EQH (Equal Highs): 동일 고점. 비슷한 높이의 고점이 겹쳐 손절 주문(유동성)이 몰린 자리.
  • EQL (Equal Lows): 동일 저점. 비슷한 높이의 저점이 겹쳐 유동성이 몰린 자리.
  • TF (Timeframe): 시간 프레임. 캔들 하나의 기간(1분·1시간·일봉 등).