STT 모델이 벡터를 포지션으로 바꾸는 법 — 수익 분포 요약과 tanh
가격 창을 인코더 벡터로 바꾸고, 비슷한 벡터 묶음의 다음날 수익 평균·흩어짐을 tanh로 포지션화하는 STT 통계형 모델의 시각적 설명입니다.
> 연구 상태: 메커니즘 설명 글입니다. 예시 run은 5년 심사(rolling 30일 평균 Sharpe)를 통과하지 못했으므로, 아래 숫자는 수익 개선 주장이 아니라 벡터 → 수익 요약 → 포지션 변환을 보여 주기 위한 것입니다.
질문
STT(통계형) 모델은 “가격을 예측”하는가, 아니면 “비슷한 과거 패턴이 오면 다음날 수익이 어떻게 흩어졌는지”를 보고 포지션을 정하는가?
이 글은 후자를 그림으로 풀어서 설명한다.
한 줄 요약
1. 최근 N일 OHLC(시가·고가·저가·종가) 창을 인코더가 벡터 z로 바꾼다. 2. 학습에 쓴 창들의 z를 비슷한끼리 묶는다(기본 32그룹). 3. 각 묶음에 다음날 수익률의 평균·흩어짐·표본 수를 적어 둔다. 4. 포지션은 tanh(평균 / (흩어짐 + ε))로 -1~1에 넣고, 표본이 너무 적으면 0이다. 5. 추론 때는 새 창 → z → 가장 가까운 묶음 → 그 묶음의 미리 저장한 포지션.

Figure 1. STT는 미래 가격을 직접 맞히기보다, 패턴 주소(z)에 붙인 과거 다음날 수익 요약을 조회한다.
예시 run 계약
| 항목 | 값 |
|---|---|
| Run | stt_franceschi_v1_btcusdt_20260727_135008 |
| Backbone | franceschi |
| Target | BTCUSDT (단독 입력 v1) |
| Window | 128 bars |
| 분할 | 학습 10% / 검증 10% / 나머지 80% 미사용 (seed로 1회 고정) |
| 학습 창 수 | 170 |
| 그룹 수 | 32 |
| 최소 표본 | 5 (미만이면 포지션 0) |
| 라벨 | target 다음날 close-to-close 수익만 |
| 5Y 심사 | rolling 30d avg Sharpe ≈ 0.30 (미통과, 업로드 스킵) |
현재 권장 학습 preset sparse_v1도 train/test를 각 10%로 둔다. 예전 1차 설계는 30%/30%였다.
벡터 묶음이 저장하는 “확률”은 무엇인가
직관적으로는 “이 벡터가 오면 다음날 수익의 확률분포”를 보고 싶지만, artifact에 남는 것은 전체 히스토그램이 아니다.
각 그룹에 저장하는 값:
mean_ret— 그날 묶인 창들의 다음날 수익률 평균std_ret— 그 수익률들의 흩어짐count— 표본 수position— 위 요약으로 미리 계산한-1~1포지션centroids— 그룹 중심 벡터 (추론 시 최근접 매칭용)
즉 “확률분포를 본다”는 말은 학습 단계에서 표본을 모아 요약한 뒤, 운용 시에는 그 요약표만 조회한다는 뜻에 가깝다.

Figure 2. 가로축은 그룹 평균 다음날 수익(%), 세로축은 흩어짐(%), 색은 저장된 포지션, 점 크기는 표본 수다. 오른쪽 위(평균↑·흩어짐↓)일수록 강한 매수 쪽 포지션이 나오기 쉽다.
예시 run에서 |position|이 큰 묶음:
| 그룹 | 평균 다음날 수익 | 흩어짐 | n | 포지션 a |
|---|---|---|---|---|
| 12 | +2.00% | 2.05% | 11 | +0.75 |
| 26 | +1.13% | 1.62% | 5 | +0.60 |
| 13 | −1.21% | 1.90% | 5 | −0.56 |
| 19 | +0.49% | 0.81% | 6 | +0.54 |
같은 “평균이 플러스”라도 흩어짐이 크면 score가 작아지고, tanh를 통과한 포지션도 작아진다.
요약 분포를 그림으로 보면
아래 곡선은 artifact에 있는 mean/std로 그린 정규분포 근사다. 실제 표본 막대그래프를 다시 그린 것이 아니다.

Figure 3. 왼쪽(그룹 12)은 평균이 오른쪽으로 치우치고 포지션이 +0.75다. 가운데(그룹 26)도 플러스지만 표본이 5개뿐이라 해석을 보수적으로 봐야 한다. 오른쪽(그룹 0)은 평균이 거의 0에 가깝고 포지션도 거의 0이다.
이 그림이 말하려는 핵심은 “모델이 매 시점 전체 분포를 샘플링한다”가 아니라, 묶음마다 요약된 위치·폭이 다르고, 그 차이가 포지션 크기를 만든다는 점이다.
tanh가 하는 일
코드의 포지션 식은 다음과 같다.
score = mean_ret / (std_ret + eps)
a = 0 if count < min_count
a = clip(tanh(score), -1, 1) otherwisescore는 “평균이 얼마나 크고, 흩어짐 대비 얼마나 또렷한가”다. tanh는 그 점수를 -1~1로 부드럽게 눌러 풀매수/풀매도로 튀지 않게 한다. 표본이 min_count(여기선 5) 미만인 묶음은 점수와 무관하게 관망(0) 이다.

Figure 4. 곡선은 tanh(score)다. 색 점은 표본 5개 이상 그룹이고, 회색 ×는 표본 부족으로 강제 0인 그룹이다. 점들이 곡선 위에 올라와 있으면, 저장된 position이 식과 일치한다는 뜻이다.

Figure 5. 32개 그룹을 포지션 순으로 세운 것이다. 이 예시에서는 18개만 0이 아니고, 14개는 표본 부족으로 0이다. 학습 창 170개를 32칸에 나누면 칸마다 표본이 얇아지는 것이 정상이다.
추론 때 일어나는 일
새 거래일의 창이 오면:
1. 같은 OHLC 전처리로 창을 만든다. 2. 인코더가 z를 낸다. 3. 저장된 centroid 중 가장 가까운 그룹을 고른다. 4. 그 그룹의 position을 그날 target position으로 쓴다.
다시 말해 운용 시점에는 수익 분포를 다시 추정하지 않는다. 학습 때 굳힌 조회표를 읽는다.
이 글이 주장하지 않는 것
- 이 예시 run이 운용에 쓸 만하다 (5Y 심사 미통과).
- 정규분포 근사가 실제 다음날 수익의 참 분포다.
- 10% 랜덤 학습이 최적 분할이다 (인접 창 누수·표본 얇음 위험이 문서에도 기록됨).
- STT가 IQL보다 낫다 (별개 계약; STT는 실가격만, IQL은 합성 transition으로 정책을 학습).
재현
로컬 artifact 기준:
# 예시 run 디렉터리
trading/agents/runs/stt_waves/sparse_v1/stt_franceschi_v1_btcusdt_20260727_135008/
# 핵심 파일
return_groups.json # mean_ret, std_ret, count, position, centroids
stt_meta.json # window, train/test fraction, split indices포지션 식은 trading/agents/statistical/return_table.py의 fit_return_groups와 동일하다.
다음에 볼 것
- 학습 창을 다시 인코딩해 실제 표본 히스토그램과 정규분포 근사를 겹쳐 보기
- backbone(
ts2vec/franceschi/fascl/tcn_infonce)·window(32/64/128)별로 그룹 포지션 분산이 얼마나 다른지 - 5Y 심사를 통과한 STT candidate만 모아 같은 시각화를 반복하기