03. Transformer의 등장과 수익률 32,687%의 환상
Attention Is All You Need
2025년 10월 27일. v2.0에서 로그수익률이라는 기반을 다진 후, 나는 다음 카드를 꺼내들었다. Transformer.
자연어 처리를 혁명적으로 바꾼 그 Transformer를 시계열에 적용하면 어떨까. LSTM은 순차적으로 데이터를 처리하면서 앞쪽 정보를 점점 잊어버린다. 하지만 Self-Attention은 시퀀스의 어떤 시점이든 직접 참조할 수 있다. "3시간 전에 거래량이 폭발했다"는 정보를 잊지 않고 현재 예측에 반영할 수 있다.
Self-Attention의 수학
Transformer의 핵심은 Scaled Dot-Product Attention이다. 수식은 놀라울 정도로 간결하다:
$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$
여기서:
- $Q$ (Query): "나는 어떤 정보를 찾고 있는가"
- $K$ (Key): "나는 어떤 정보를 갖고 있는가"
- $V$ (Value): "내가 실제로 전달할 정보"
- $d_k$: Key 벡터의 차원
내 시계열 데이터에서 이것이 의미하는 바를 구체적으로 생각해보자. 시퀀스의 각 시점이 하나의 토큰이다. 현재 시점(Query)이 과거 시점들(Key)과의 관련성을 계산하고, 그 관련성에 비례하여 정보(Value)를 가져온다.
$QK^T$의 $(i,j)$ 원소는 시점 $i$와 시점 $j$ 사이의 유사도다. 소프트맥스를 씌우면 이것이 확률 분포가 되어, "시점 $i$의 예측을 위해 시점 $j$의 정보를 얼마나 참고할 것인가"라는 어텐션 가중치가 된다.
$\sqrt{d_k}$로 나누는 이유는 수학적으로 명확하다. $Q$와 $K$의 원소가 평균 0, 분산 1인 독립 확률변수라면, 내적 $q \cdot k = \sum_{i=1}^{d_k} q_i k_i$의 분산은 $d_k$가 된다. $d_k$가 크면 내적 값이 극단적으로 커져서, 소프트맥스의 기울기가 거의 0에 수렴한다(saturation). 스케일링은 이 문제를 방지한다.
Scaled Dot-Product Attention을 도서관에 비유하면 이렇다. 내가 "비트코인 변동성"이라는 질문(Query)을 들고 도서관에 간다. 책꽂이의 각 책 제목(Key)과 내 질문의 관련성을 계산하고, 관련 높은 책의 내용(Value)을 많이, 관련 낮은 책은 적게 읽어오는 것이다. $\sqrt{d_k}$로 나누는 건, 관련성 점수가 너무 극단적으로 벌어져서 한 권만 읽고 나머지를 무시하는 사태를 막아주는 볼륨 조절이다.
Multi-Head Attention
내 모델에서는 4개의 헤드를 사용했다. Multi-Head Attention은 여러 개의 어텐션을 병렬로 수행하여, 서로 다른 관점에서 정보를 추출한다:
$$\text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \ldots, \text{head}_h) W^O$$
$$\text{where } \text{head}_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V)$$
$W_i^Q \in \mathbb{R}^{d_{model} \times d_k}$, $W_i^K \in \mathbb{R}^{d_{model} \times d_k}$, $W_i^V \in \mathbb{R}^{d_{model} \times d_v}$는 각 헤드의 선형 변환 행렬이다.
내 시스템에서 4개의 헤드는 각각 다른 유형의 시간적 관계를 학습할 수 있다. 예를 들어:
- 헤드 1: 직전 몇 분간의 미시적 추세
- 헤드 2: 1시간 전후의 거래량 패턴
- 헤드 3: 특정 기술적 지표 간의 상호작용
- 헤드 4: 장기 변동성 패턴
물론 실제로 각 헤드가 무엇을 학습하는지는 해석하기 어렵다. 하지만 여러 관점의 병렬 추출이 단일 어텐션보다 풍부한 표현을 만든다는 것은 실험적으로 입증된 사실이다.
Positional Encoding
Transformer에는 본질적으로 순서 개념이 없다. Self-Attention은 집합(set)에 대한 연산이지, 시퀀스(sequence)에 대한 연산이 아니다. 시간 순서를 알려주기 위해 Positional Encoding을 입력에 더한다:
$$PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d_{model}}}\right)$$
$$PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d_{model}}}\right)$$
여기서 $pos$는 시퀀스에서의 위치, $i$는 차원 인덱스다. 사인/코사인의 주기가 차원마다 다르게 설정되어, 각 위치가 고유한 벡터를 갖는다.
왜 삼각함수인가? 임의의 고정 오프셋 $k$에 대해, $PE_{pos+k}$를 $PE_{pos}$의 선형 함수로 표현할 수 있기 때문이다. 이는 모델이 상대적 위치를 학습하기 쉽게 만든다. 금융 시계열에서 "30분 전"이라는 상대적 시간 관계는, "오후 2시 30분"이라는 절대적 시간보다 중요하다.
Positional Encoding은 시험지에 문제 번호를 매기는 것과 비슷하다. Transformer는 원래 모든 문제를 한꺼번에 보는데, 번호가 없으면 1번과 50번의 순서를 모른다. 사인/코사인으로 만든 위치 벡터는 "3번 문제는 1번에서 두 칸 뒤"라는 상대적 거리를 자연스럽게 표현할 수 있는, 일종의 좌표계 역할을 한다.
다만, 내 구현에서는 LSTM 위에 Transformer를 얹는 하이브리드 구조를 썼기 때문에, LSTM이 이미 순서 정보를 학습하고 있어서 별도의 Positional Encoding을 명시적으로 추가하지는 않았다. LSTM의 순차 처리 자체가 암묵적인 위치 인코딩 역할을 한다.
# v2.1: LSTM 위에 Transformer 레이어를 얹다
Bidirectional LSTM(128)
-> Bidirectional LSTM(64)
-> Multi-Head Attention (4 heads, key_dim=32) # NEW
-> LayerNormalization # NEW
-> LSTM(32)
-> Dense(64) -> Dense(32) -> Dense(2, softmax)
단순히 모델 구조만 바꾼 것이 아니었다. 데이터도 대폭 확장했다.
마이크로스트럭처의 세계
v2.1에서 나는 시장 미시구조(Microstructure) 피처라는 새로운 세계에 발을 들였다.
기존의 RSI, MACD 같은 기술적 지표는 "가격이 어떻게 움직였는가"를 보여준다. 하지만 마이크로스트럭처 피처는 "가격이 왜 움직이는가"에 가깝다.
# v2.1에서 추가한 마이크로스트럭처 피처
new_features = [
"Order_Imbalance", # 매수/매도 압력 불균형
"Price_Acceleration", # 가격 변화의 가속도 (2차 미분)
"Volume_Acceleration", # 거래량 변화의 가속도
"Price_Efficiency", # 가격 이동의 효율성
]
Order_Imbalance는 매수 압력과 매도 압력의 불균형을 측정한다. 주문서가 매수 쪽으로 기울어져 있으면, 아직 가격에 반영되지 않은 상승 압력이 존재한다는 뜻이다.
Price_Efficiency는 가격이 얼마나 "효율적으로" 이동했는지를 본다. 직선으로 올라갔다면 효율적이고, 지그재그로 올라갔다면 비효율적이다. 효율적인 움직임은 추세의 강도가 높다는 신호다.
데이터도 20,000개에서 48,205개로 2.4배 확장했다. LSTM 피처는 21개, XGBoost 피처는 42개. 모델이 볼 수 있는 세상이 훨씬 넓어졌다.
60.74%
학습 결과가 나왔다.
| 모델 | 정확도 |
|---|---|
| LSTM | 56.47% |
| XGBoost | 60.35% |
| LightGBM | 60.33% |
| 앙상블 | 60.74% |
60.74%. 처음으로 60%의 벽을 넘었다. v1.0의 55.58%에서 시작해서, 로그수익률(v2.0)과 Transformer + 마이크로스트럭처(v2.1)를 거쳐 여기까지 왔다.
이진 분류에서 60%는 생각보다 의미 있는 숫자다. 코인 시장처럼 노이즈가 극심한 환경에서, 모델이 동전 던지기보다 10% 이상 나은 판단을 내린다는 뜻이니까.
하지만 진짜 충격은 이 다음에 왔다.
32,687%
백테스트를 돌렸다. 조건은 이랬다.
- 초기 자본: 1,000만 원
- 포지션: 현재 자본의 20%
- 레버리지: 1배
- Long/Short 양방향 거래
- 손절매: 10%
- 수수료: 0.1%
터미널에 결과가 찍혔을 때, 나는 숫자를 세 번 다시 읽었다.
최종 자본: 3,278,665,832 KRW
총 수익률: 32,686.66%
승률: 55.0%
샤프 비율: 31.23
32억. 1,000만 원이 32억이 됐다. 326배. 레버리지 1배로.
가슴이 뛰기 시작했다. 머리로는 "이건 백테스트일 뿐이야"라고 말하면서도, 가슴은 이미 계산기를 두드리고 있었다. 1억을 넣으면 326억? 10억이면?
잠시 정신을 가다듬었다.
백테스트 수익률의 수학
여기서 이 32,687%라는 숫자가 어떻게 만들어졌는지 수학적으로 분해해보겠다.
백테스트에서 복리 수익률은 다음과 같이 계산된다:
$$\text{최종 자본} = C_0 \cdot \prod_{i=1}^{N}(1 + r_i \cdot w)$$
여기서 $C_0$는 초기 자본, $r_i$는 $i$번째 거래의 수익률, $w$는 포지션 비율(내 경우 20%), $N$은 총 거래 수(9,539회)다. 이것을 총 수익률로 표현하면:
$$R_{total} = \prod_{i=1}^{N}(1 + r_i \cdot w) - 1$$
반면 단리 계산이었다면:
$$R_{simple} = \sum_{i=1}^{N} r_i \cdot w$$
복리의 마법(혹은 환상)은 $(1 + x)^n$의 지수적 성장에 있다. 거래당 평균 수익률이 $\bar{r} = 0.314\%$이고 포지션 비율 $w = 0.2$일 때, 한 거래당 자본 증가율은 약 $1 + 0.00314 \times 0.2 = 1.000628$이다. 이것을 9,539회 반복하면:
$$1.000628^{9539} \approx 400$$
단리라면 $9539 \times 0.000628 \approx 5.99$, 즉 약 600% 수익이다. 같은 거래 기록이 단리에서는 600%, 복리에서는 40,000%가 된다. 복리는 작은 우위를 환상적인 숫자로 증폭한다. 이것이 백테스트 수익률을 읽을 때 반드시 알아야 하는 첫 번째 함정이다.
환상인가, 실력인가
흥분이 가라앉자 의심이 시작됐다. 326배 수익이라니, 이게 말이 되나?
거래 통계를 자세히 뜯어봤다.
총 거래: 9,539회
승률: 55.0% (5,245승 / 4,294패)
거래당 평균 수익률: 0.314%
손절매 발동: 11회 (0.12%)
거래당 0.314%. 한 번에 대박을 치는 게 아니라, 9,500번이 넘는 거래에서 아주 작은 우위를 꾸준히 쌓아올린 결과였다. 55%의 승률로 0.3%씩 복리로 불리면 -- 맞다, 수학적으로는 그렇게 된다.
하지만 이것이 바로 복리의 환상이기도 했다.
의심 1: 과적합
모델이 테스트 데이터의 패턴을 "외운" 건 아닐까? 학습/검증/테스트 데이터를 시간순으로 분할하긴 했지만, 단일 split으로는 특정 시장 상황에 과적합될 수 있다. Walk-Forward 검증을 하지 않았다.
과적합을 감지하는 통계적 방법
과적합은 "학습 데이터에서의 성능"과 "보지 못한 데이터에서의 성능" 사이의 괴리다. 이를 정량화하는 방법들이 있다.
Train-Validation Gap: 가장 직관적인 지표다.
$$\Delta_{gap} = \text{Acc}_{train} - \text{Acc}_{val}$$
Train-Val Gap은 모의고사와 수능의 점수 차이와 같다. 모의고사(학습 데이터)에서 95점인데 수능(검증 데이터)에서 80점이면, 그 15점 차이가 과적합의 크기다. 모의고사 문제를 외워서 푼 거지, 진짜 실력이 아니라는 뜻이다.
이 값이 클수록 과적합이 심하다. 경험적으로 $\Delta_{gap} > 5\%$이면 심각한 과적합을 의심해야 한다. 내 v2.1 모델에서 이 값은 약 3~4%였는데, 경고 수준이면서도 즉시 위험하지는 않은 애매한 지대였다.
Learning Curve 분석: 학습 데이터 크기를 점진적으로 늘리며 train/validation 성능을 관찰한다.
- 두 곡선이 수렴하면: 충분히 학습됨 (low variance)
- 두 곡선 사이에 큰 간격이 유지되면: 과적합 (high variance)
- 두 곡선 모두 낮으면: 과소적합 (high bias)
v2.1에서 나는 이런 체계적 분석을 하지 않았다. 하나의 split에서 나온 하나의 숫자를 보고 판단했다. 이것이 32,687%라는 숫자를 맹신하게 만든 원인 중 하나였다.
의심 2: 백테스트의 한계
백테스트에는 반영되지 않는 현실이 있다.
- 슬리피지: 실제로 주문을 넣으면 원하는 가격에 체결되지 않는다
- 유동성: 내 주문이 시장에 영향을 줄 수 있다
- 시장 미시구조 변화: 과거의 패턴이 미래에도 유효하리라는 보장이 없다
특히 9,539회 거래에서 매번 0.1% 수수료를 낸다는 가정 자체가 낙관적이다. 실전에서는 슬리피지가 수수료보다 클 수도 있다.
의심 3: MDD 41.2%
최대 낙폭(Maximum Drawdown)이 41.2%였다. 자본이 41% 녹는 구간이 있었다는 뜻이다.
Maximum Drawdown의 수학적 정의
MDD는 다음과 같이 정의된다:
$$MDD = \max_{t \in [0,T]} \left( \max_{s \in [0,t]} V_s - V_t \right) / \max_{s \in [0,t]} V_s$$
풀어쓰면, 시점 $t$까지의 최고점 대비 현재 시점의 하락 비율 중 가장 큰 값이다. 이것을 좀 더 직관적으로 쓰면:
$$MDD = \max_{0 \leq s \leq t \leq T} \frac{V_s - V_t}{V_s}$$
여기서 $V_t$는 시점 $t$의 포트폴리오 가치다. MDD 41.2%는 최고점에서 최저점까지 자본의 41.2%가 증발했다는 뜻이다.
MDD를 등산에 비유하면, 산 정상에서 가장 깊은 골짜기까지 얼마나 굴러 떨어졌느냐를 재는 것이다. 전체 등산 경로에서 최악의 추락 구간을 찾는 셈이다. 41.2%면 정상에서 거의 반 아래까지 미끄러진 것이고, 다시 정상으로 올라가려면 원래보다 더 많이 걸어야 한다.
그리고 여기에 비대칭성의 함정이 있다. 41% 손실을 복구하려면:
$$\frac{1}{1 - 0.412} - 1 = \frac{1}{0.588} - 1 \approx 0.70 = 70\%$$
즉, 41% 잃으면 70% 벌어야 원금 회복이다. 이 비대칭성은 손실이 커질수록 극적으로 심해진다. 50% 손실은 100% 수익이 필요하고, 80% 손실은 400% 수익이 필요하다.
LSTM 단독 모델의 MDD는 84.5%였다. 이것을 복구하려면 $1/(1-0.845) - 1 \approx 545\%$의 수익이 필요하다. 앙상블이 아니었다면 사실상 계좌가 파괴된 것과 같다.
Sharpe Ratio: 31.23이 의미하는 것
백테스트 결과에 Sharpe Ratio 31.23이 찍혀 있었다. Sharpe Ratio의 정의는:
$$S = \frac{E[R_p - R_f]}{\sigma_p} = \frac{\bar{R}_p - R_f}{\sigma_p}$$
여기서 $\bar{R}_p$는 포트폴리오 평균 수익률, $R_f$는 무위험 수익률, $\sigma_p$는 포트폴리오 수익률의 표준편차다.
Sharpe Ratio는 "위험 1단위당 얼마나 초과 수익을 얻었는가"를 측정한다.
Sharpe Ratio를 음식점 평가에 비유하면, 단순히 "맛있다(수익률 높다)"가 아니라 "가격 대비 맛(위험 대비 수익)"을 따지는 것이다. 3만원짜리 코스요리가 맛있는 건 당연하고, 5천원짜리 백반이 감동적으로 맛있으면 Sharpe Ratio가 높은 거다. SR 31.23은 5천원짜리 백반집이 미슐랭 3스타급이라고 주장하는 것과 같아서, "이거 리뷰 조작 아닌가?" 의심이 드는 수준이다.
일반적으로:
- $S < 1$: 좋지 않음
- $1 \leq S < 2$: 양호
- $2 \leq S < 3$: 매우 좋음
- $S \geq 3$: 의심할 것
31.23은 현실에서 거의 불가능한 숫자다. 르네상스 테크놀로지의 메달리온 펀드가 연간 Sharpe Ratio 약 6~7 수준이다. 31.23이라는 것은, 이 백테스트 결과가 현실을 과대 반영하고 있다는 강력한 증거다.
이 비정상적으로 높은 Sharpe Ratio가 나온 원인을 분석하면:
- 거래 빈도가 극도로 높아(9,539회) 일일 수익률의 분산이 인위적으로 낮아짐
- 백테스트의 이상적 가정(슬리피지 무시 등)이 수익률을 부풀림
- 단일 기간 백테스트로 시장 상황 변화를 고려하지 않음
32,687%가 가르쳐준 것
나는 이 결과를 세 가지 층위로 이해하기로 했다.
1층: 모델은 실제로 무언가를 학습했다. 55%의 승률은 우연이 아니다. 60.74%의 정확도로 양방향 거래를 하면 장기적으로 수익이 나는 것은 수학적으로 당연하다.
2층: 하지만 32,687%라는 숫자는 환상이다. 백테스트는 이상적인 조건에서의 시뮬레이션이다. 실전에서 이 수익률을 그대로 재현할 수는 없다. 복리 효과가 극적으로 작동하는 장기 백테스트는 특히 결과를 과대평가한다.
3층: 중요한 건 방향이지 크기가 아니다. 32,687%가 아니라 3,268%여도, 326%여도, 수익이 나는 전략이라면 가치가 있다. 핵심은 시장에서 통계적 우위를 확보했느냐이다.
이 시점에서 나는 갈림길에 섰다.
방향 A: 이 모델을 바로 실전에 투입하고 돈을 벌기 시작한다. 방향 B: 의심을 더 파고들어 모델을 개선한다.
나는 B를 택했다. 그리고 이 선택이 v2.2의 실패, v2.3의 반등, 그리고 결국 v2.7의 전면 재설계로 이어지는 긴 여정의 시작이었다.
정확도 60%의 정체
사실 v2.1 이후로 긴 정체기가 찾아왔다. v2.2에서 SMOTE와 앙상블 가중치를 조정했지만 60.48%로 오히려 하락했다. v2.3에서 겨우 60.67%로 회복했다. v2.4에서 60.90%. v2.5에서 60.16%.
60%대에서 벗어나지 못했다. 정확도를 0.1% 올리기 위해 몇 시간씩 학습을 돌리고, 수많은 가설을 세우고 검증했지만, 벽은 무너지지 않았다.
나중에야 깨달았다. 벽이 무너지지 않은 게 아니라, 내가 잘못된 벽을 두드리고 있었다. 정확도를 높이는 것이 아니라, "무엇을 예측하는가"를 바꿔야 했던 것이다. 하지만 그 이야기는 다음 시리즈에서.
돌이켜보며
v2.1의 32,687%는 나에게 두 가지를 가르쳤다.
첫째, 환상적인 숫자에 속지 마라. 백테스트 수익률은 현실이 아니다. 특히 복리가 장기간 작동하는 시뮬레이션에서는 작은 우위도 천문학적 숫자로 증폭된다. 숫자의 크기보다 그 숫자를 만든 구조의 건전성을 봐야 한다. Sharpe Ratio 31.23이 그 경고 신호였다. 현실의 최고 퀀트 펀드도 SR 7을 넘기기 어렵다는 사실을 알았다면, 더 일찍 의심했을 것이다.
둘째, 의심은 미덕이다. "이게 진짜야?"라고 물을 수 있는 능력이, 모델을 만드는 능력보다 중요할 수 있다. v2.1에서 바로 실전에 뛰어들었다면, v2.7의 진짜 성능 개선(승률 73.5%, PF 5.01, MDD 3.62%)에 도달하지 못했을 것이다.
Transformer는 강력한 도구였다. 하지만 도구가 아무리 날카로워도, 잘못된 질문에 답하고 있다면 소용이 없다. 진짜 돌파구는 모델을 더 복잡하게 만드는 데 있지 않았다. "미래에 가격이 오를까?"라는 질문을 "TP에 먼저 닿을까, SL에 먼저 닿을까?"로 바꾸는 데 있었다.
하지만 그걸 깨닫기까지는 아직 3개월의 실험과 실패가 더 필요했다.