2026-08-17. 설계 문서: docs/superpowers/specs/2026-08-17-taa-strategy-design.md.
이 문서의 목적은 07-experiment-log.md(팩터 엔진)와 같다 — 같은 실험을 두 번
하지 않는 것. 결과와 함께 §7 의 사전 예상이 맞았는지 틀렸는지를 적는다.
공통 구간 2008-07-31 ~ 2026-08-31, 218개월. PBO = 0.770. 9개 구성 중 채택 0개.
채택 없음이 결과다. 아래에서 완화하거나 되돌리지 않는다.
이 PBO 행렬은 9개 사전등록 구성 전부(패시브 기준선 spy·static_60_40
포함)로 잰 값이다. 기준선은 9개 탐색의 산물이 아니지만 §5 스펙이 정한
행렬 그대로 실었다 — 이유와 그로 인한 부작용(기준선까지 “PBO 초과”로
기각되는 것)은 §4-4 에 적는다. 탐색 대상만의 값(BAA 6개, 0.861)은 §1 에 있다.
uv run python scripts/run_taa.py 실측 (2026-08-17, summarize() 의 MDD
계산 결함 수정 후 — §2-예상2 참조):
가격 패널: 18종목 1997-12-31 ~ 2026-08-14
⚠ 마지막 데이터 2026-08-14 가 월말이 아니다 — 마지막 달은 반달치 데이터를 한 달로 라벨링한 것일 수 있다
공통 구간: 2008-07-31 ~ 2026-08-31 (218개월)
=== 지표 ===
cagr mdd vol calmar sharpe dsr months defensive
name
spy 0.1246 -0.4181 0.1559 0.2980 0.8353 0.9678 218.0000 0.0000
static_60_40 0.0887 -0.2507 0.0964 0.3537 0.9322 0.9856 218.0000 0.0000
vaa_g4 0.0607 -0.2093 0.1044 0.2899 0.6169 0.8706 218.0000 0.5565
baa_agg 0.0882 -0.1650 0.1099 0.5350 0.8263 0.9764 218.0000 0.5525
baa_bal 0.0728 -0.1113 0.0780 0.6540 0.9423 0.9938 218.0000 0.5525
baa_agg_ma 0.0881 -0.1344 0.1035 0.6556 0.8695 0.9853 218.0000 0.5525
baa_bal_ma 0.0672 -0.0828 0.0683 0.8121 0.9879 0.9972 218.0000 0.5525
baa_bal_tranche 0.0811 -0.1131 0.0816 0.7174 0.9998 0.9957 218.0000 0.5464
baa_bal_ma_tranche 0.0732 -0.1002 0.0711 0.7305 1.0318 0.9972 218.0000 0.5464
PBO = 0.770 (관측 218개월 × 구성 9개)
=== 판정 ===
adopted reason
name
spy False PBO 0.77 > 0.5 · MDD -41.8% 가 한도 초과 · Calmar 0.30 가 60/40(0.35) 이하
static_60_40 False PBO 0.77 > 0.5 · MDD -25.1% 가 한도 초과
vaa_g4 False PBO 0.77 > 0.5 · MDD -20.9% 가 한도 초과 · DSR 0.871 < 0.95 · Calmar 0.29 가 60/40(0.35) 이하
baa_agg False PBO 0.77 > 0.5
baa_bal False PBO 0.77 > 0.5
baa_agg_ma False PBO 0.77 > 0.5
baa_bal_ma False PBO 0.77 > 0.5
baa_bal_tranche False PBO 0.77 > 0.5
baa_bal_ma_tranche False PBO 0.77 > 0.5
summarize() 결함 (2026-08-17 후속 리뷰에서 발견, blocking). equity =
(1 + returns).cumprod() 로만 만들면 첫 원소가 자기 자신의 cummax 가 되어
첫 달 손실이 낙폭 계산에서 빠진다. BacktestOutput.equity(원금을 맨 앞에
붙이는 규약)가 있는데도 evaluate_all 은 out.returns 만 읽고 .equity
는 어디서도 소비하지 않았다 — Task 6 의 수정이 아무도 읽지 않는 객체 위에
남아 있었던 셈이다. 영향은 baa_bal_tranche 하나뿐이지만 그 하나가
예상 대조 결론을 뒤집는다: MDD -9.72% → -11.31%, Calmar 0.835 → 0.717.
수정: summarize 도 원금(1.0)을 equity 맨 앞에 붙인다.
PBO 0.770 은 “이 9개 중 인샘플 1등을 뽑아도 아웃샘플에서 중간 이하로 떨어질 확률이 77%” 라는 뜻이다. 9개가 서로 순위를 구분할 수 없을 만큼 가깝다는 뜻이지, “아무것도 안 통한다”는 뜻이 아니다.
BAA 계열 6개(baa_agg·baa_bal·baa_agg_ma·baa_bal_ma·baa_bal_tranche·
baa_bal_ma_tranche)만 따로 PBO 를 재면 0.861 이다 — 사실상 구분 불가다.
서로 간 상관은 0.62~0.79 (Task 7 실측). 그런데 이 6개 전부가 Calmar 로
60/40(0.3537)을 이긴다, 예외 없이:
| 구성 | Calmar |
|---|---|
baa_agg |
0.535 |
baa_bal |
0.654 |
baa_agg_ma |
0.656 |
baa_bal_tranche |
0.717 |
baa_bal_ma_tranche |
0.731 |
baa_bal_ma |
0.812 |
| 60/40 (기준) | 0.354 |
두 사실은 같이 있어야 한다. “BAA 계열이 60/40 을 이긴다”와 “BAA 계열 중 어느 것이 최선인지는 이 데이터로 못 고른다”는 둘 다 참이고, 둘 중 하나로 뭉개면(잘 된다/안 된다) 틀린 결론이 된다.
클래스 수준 우위는 관찰이지 채택된 발견이 아니다. §5 에서 다음 실험으로 남긴다.
설계 문서가 결과 전에 못박은 다섯 예상을 실측과 대조한다. 틀린 것도 그대로 남긴다 — 맞은 것만 골라 적으면 다음에 같은 예상을 또 하게 된다.
전반(카나리아 분리가 성과를 개선한다)은 맞다. baa_agg Calmar 0.535 vs
vaa_g4 0.290 — 거의 두 배다.
후반은 “오발 빈도가 줄 것” 이라는 추정이었는데 틀렸다. 방어 비중이
baa_agg 55.25% vs vaa_g4 55.65% — 거의 같다. BAA 의 카나리아(SPY·EFA·
EEM·AGG)가 VAA 의 공격 유니버스와 같은 4자산이기 때문이다. 카나리아를
투자 대상에서 분리해도 카나리아 자체는 그대로이므로 방어로 도는 빈도는 안
바뀐다. 바뀐 것은 방어로 돌 때 무엇을 사는가다 — VAA 는 LQD·IEF·SHY
중에서, BAA 는 BIL 대비 dual momentum 을 거쳐 방어군 3종에서 고른다. 성과
개선은 오발을 줄여서가 아니라 오발했을 때 덜 잃어서 나온다.
Task 7 진행 중 중간 판정(“무효/마이너스 지지”)은 누적수익률로 봤기 때문에 틀렸다 — 채택 기준인 Calmar 로 다시 보면 정반대다. 이평은 세 쌍 모두에서 Calmar 를 올린다:
| 쌍 | 이평 전 | 이평 후 |
|---|---|---|
baa_agg → baa_agg_ma |
0.535 | 0.656 |
baa_bal → baa_bal_ma |
0.654 | 0.812 |
baa_bal_tranche → baa_bal_ma_tranche |
0.717 | 0.731 |
세 쌍 모두 이평이 Calmar 를 올린다 — MDD 를 CAGR 보다 더 많이 깎기 때문이다
(예: baa_bal MDD −11.1% → baa_bal_ma −8.3%, CAGR 은 7.3%→6.7% 로 소폭만
하락). 이중 필터가 VAA 의 병(과잉 방어)을 재발시킬 것이라는 예상은 세
쌍 전부에서 빗나갔다.
정정 이력. 이 문서의 이전 버전은 트랜치 쌍에서 이평이 Calmar 를 0.835→0.731 로 내린다고 적었다.
summarize()가baa_bal_tranche의 MDD 를 −9.72%(실제 −11.31%)로 과소평가해 Calmar 를 부풀렸던 결함 때문이었다(위 “summarize()결함” 참조). 수정 후에는 0.717→0.731 로 오른다 — 세 쌍 모두 개선이므로 예상 2 는 부분이 아니라 완전히 틀렸다. 흥미로운 점: 이 정정 직전 버전 자체가 “Task 7 의 트랜치 착시 재사용을 거부한 정정”이었다 — 같은 종류의 결함(낙관적인 방향으로 치우친 파생 지표)이 한 층 아래에서 다시 나온 것이다.
트랜치 방법론 결함(§3)을 고친 뒤 다시 잰 슬리브 간 분산 감소 효과는 약 4%
(Task 7 fix round 2) — 예상이 말한 “조금”에 해당한다. summarize() 의 MDD
결함(§0)까지 고친 결과표도 이제 같은 크기로 말한다: baa_bal(0.654) →
baa_bal_tranche(0.717), 약 9.6% 상승. 수정 전 버전이 보인 큰 격차(0.654→0.835)
는 두 결함(트랜치 평활화 + summarize 원금 누락)이 같은 방향(과대평가)으로
겹친 산물이었고, 둘 다 걷어내자 표의 숫자와 4% 분산 감소 측정이 서로 맞아떨어진다.
SPY CAGR 12.46% 가 최고다. 나머지 8개 전부 그 아래다. 승부는 예상대로 MDD 제약 하의 Calmar 에서 났다.
결론(“전부 기각”)은 맞다. 하지만 판정표의 reason 열을 보면 BAA 6개는
전부 PBO 0.77 > 0.5 하나로만 기각된다 — Calmar 로 60/40 을 못 이겨서가
아니다. 위 §1 의 표대로 6개 전부 60/40 을 이긴다. 예상이 세운 “60/40 을 못
이기면 기각”이라는 인과는 이번 결과에서 한 번도 작동하지 않았다. 기각의
실제 원인은 PBO 다. 이유가 다르면 대응도 달라진다 — “더 나은 구성을 찾자”가
아니라 “구성 선택 자체를 줄이자”가 맞는 다음 수다(§5).
baa_bal_tranche 최초 구현은 가격 패널 전체를 시프트해 4개 슬리브를
만들었다. 그 결과 슬리브마다 “같은 달”이라는 라벨이 서로 다른 실제 기간을
가리켰다. 서로 다른 기간의 수익률을 평균 내는 것은 분산 투자가 아니라
평활화(smoothing)다 — 상관이 낮아 보이는 것은 리밸런싱이 분산된 결과가
아니라,애초에 겹치지 않는 구간을 잰 결과였을 뿐이다.
측정으로 잡았다: 슬리브 간 평균 상관 0.381 (슬리브 0 대 3 은 0.076 —
같은 전략·같은 자산을 일주일 시차로 굴리는 슬리브가 이럴 수 없다). 결정적
증거는 평균 후 변동성이 sqrt((1+3ρ)/4) × 평균개별변동성 과 거의 정확히
일치한 것 — 감소분 전부가 순수 통계적 평균 효과였고, 리밸런싱 시점 분산의
효과가 아니었다.
재구현(단일 패널 위에서 리밸런싱 시점만 슬리브마다 다르게 배치, 같은 달력월로 집계)으로 상관은 0.819(최소 0.702)로 올라갔다. 그 결과:
PBO 0.139 → 0.770.
착시가 만든 것은 “트랜치가 좋다”는 결론이 아니라, 한 구성이 인샘플에서 뚜렷한 1등처럼 보이게 만들어 선택 불안정성을 가린 것이었다. 버그가 있을 때 9개 중 승자가 또렷했고(PBO 낮음), 고치자 9개가 다시 뭉쳤다(PBO 높음).
주의: 다중시드 분산 테스트만으로는 이 버그를 못 잡는다. Task 7 재확인에서 버그 버전이 정상 버전보다 더 강한 분산 감소를 보였다(평균비율 0.832 vs 0.962 — 서로 다른 기간을 평균 내는 쪽이 상관 자산을 평균 내는 쪽보다 분산을 더 줄이기 때문). 이 회귀를 실제로 잡는 것은 슬리브 상관 하한 테스트 하나 뿐이다.
설계 문서(2026-08-17-taa-strategy-design.md)를 구현하며 여섯 가지 결함을
찾았다. 다음번에 같은 결함을 다시 만들지 않기 위해 여기 적는다.
BIL(2007-05 상장) + 12개월 모멘텀 워밍업 때문이다.
스펙은 230개월이라 적었고 실제는 218개월이다(§0). 이 문서와 코드는
218개월을 기준으로 삼는다.run_taa.py 의 if/elif
디스패치가 트랜치와 이평 오버레이를 동시에 요구하는 baa_bal_ma_tranche
에서 이평을 못 받았다 — 사전등록 9개 중 실질 8개만 서로 달랐다. 두 변형을
합성(슬리브별 개별 적용 후 평균)하도록 고쳤다.spy·static_60_40)까지 “PBO 초과”로 기각된다 — 위 판정표의
reason 열을 보면 그대로 나온다. 기준선은 9개 탐색의 산물이 아니므로
선택 과최적화 지표를 적용할 대상이 아니다. 고쳐도 채택 결과(0개)는
바뀌지 않는다 — BAA 구성들도 PBO 에 걸리므로.run_with_ma_overlay·run_with_tranches 두 진입점이 run_backtest 가
확립한 “equity 에 원금 시점을 붙인다”는 규약을 처음엔 따르지 않아 MDD 가
과소평가됐다(트랜치 −9.35% vs 실제 −10.99%). 채택 기준이 MDD ≤ 20% 이므로
경계선 구성을 통과시킬 수 있었고, 영향받는 4개가 하필 개선안이었다 —
개선안에 유리한 방향의 오차였다. 세 진입점 전부에 파라미터화된 불변식
테스트를 추가해 고쳤다.summarize() 에도 따로 있었다. BacktestOutput.equity
는 원금을 붙이지만 evaluate_all → summarize 경로는 matrix(공통
구간으로 자른 returns)만 받아 자체적으로 equity = (1 + returns).cumprod()
를 다시 만들었고, 거기엔 원금이 없었다. 두 결함이 같은 원인(원금 없는
equity → 첫 달 손실이 낙폭에서 빠짐)을 두 곳에서 반복한 것이다. 하나를
고쳤다고 다른 하나가 저절로 고쳐지지 않는다 — 이번엔 결과표 자체(§0)에
반영해 잡았다. 영향: baa_bal_tranche MDD −9.72%→−11.31%, Calmar
0.835→0.717(§2-예상2 정정 이력 참조).표본이 218개월이고, 그 안의 하락은 세 번뿐이다(2008 · 2020 · 2022).
“BAA 계열이 붕괴를 방어한다”는 주장은 사건 3개에 근거하게 된다. 이 한계를
결과 뒤 각주로 붙이지 않고 여기 같은 자리에 적는다 — 07-experiment-log.md
§2.2 에서 표제 숫자가 방어 장치를 끈 상태의 산물이었던 일을 겪었기 때문이다.
그 밖에:
to_monthly 가 마지막 반달치 가격을 “그 달 전체”로
라벨링한다 — 마지막 달 수익률이 실제로는 절반 달치 움직임이라는 뜻이다.
run_taa.py 가 이 상태를 경고로 찍도록 했다(위 실행 로그의 ⚠ 줄).클래스 수준 우위(BAA 가 60/40 을 이긴다)는 사후 재해석이 아니라 다음 실험의 사전 가설로 남긴다. 이번 결과에서 그것을 채택으로 전환하지 않은 이유는 §1 — 9개 중에서 골랐다는 사실 자체가 전환을 무효화한다.
정직하게 시험하려면 BAA 구성 하나를 미리 골라 60/40 과 단일 가설로 사전등록해야 한다. 여러 BAA 변형 중 성과가 가장 좋은 것을 골라 “이게 이긴다”고 하는 것은 이번 실험이 이미 겪은 실수(사후 선택)를 반복하는 것이다.
미완으로 남긴 것(§7 스펙, 시도 횟수에 넣지 않음):
채택된 구성이 없으므로 이번 라운드에서는 실행하지 않았다.