You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
fithia2/docs/public_sqs_calculation.md

724 lines
25 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# Public SQS Calculation
이 문서는 tracker가 최종 public `SQS`를 어떻게 계산하는지, 그리고 왜
split-only 점수와 final leaderboard 점수가 다를 수 있는지를 정리한다.
2026-04-07부터 기본 leaderboard는 **public `SQS v9`**을 쓴다.
- `SQS v9`: **3-pillar additive core** + regime adaptability (scenario-test RRS) (기본 rank)
- `SQS v8`: `SQS v7` backbone + reset common-window blend (이전 기본; 하위호환 유지)
- `SQS v7`: continuous OOT factor + 3-criterion deployment gate (v8 backbone)
- `SQS v6`: `SQS v3` + reset common-window
- `SQS v4`: `SQS v3` + comparable `10k common-window`
- `SQS v5`: optional diagnostic. `SQS v3` + multi-capital common-window capital-growth blend
- `sqs_v3_score`: deployment/WFV-first primary rank
- `stress_sqs_score`: 예전 public score. stress OOT quality를 추가 패널티로 곱한 legacy score
핵심 결론부터 말하면:
- `test split`만으로 나오는 `sqs_v2_score`는 final public `SQS`가 아니다.
- final public `SQS v9`의 backbone은 아래다.
- `train` / `valid` / `test` split (RQS 35%)
- walk-forward summary (WFQS_v2 40%)
- regime adaptability: scenario-test RRS → OOT quality → 50.0 neutral (25%)
- main robustness summary (multiplied gate)
- 여기에 comparable reset common-window summary가 있으면 CW blend 적용.
- reset summary 없이 comparable `10k common-window summary`만 있으면 common-window blend.
- `multi-capital common-window`는 diagnostics로는 저장하지만, 기본 rank에는 쓰지 않는다.
- optional comparable capital buckets은 현재 `10,000 / 25,000 / 100,000`이다.
- official comparable common-window date range는 현재 `2022-03-02 ~ 2026-03-24`다.
- 기본 leaderboard active window는 현재 `IMP-0606` / `return_max_long_v6new.29` 이후다.
그 전 실험은 retired research로 남고, `--include-retired`에서만 본다.
- 다만 `stress_sqs_score`도 같이 계산해 registry에 남긴다.
## v9 핵심 변경사항 (2026-04-07)
### 배경: OOT 편향 문제
v8의 OOT factor는 2020-2021 COVID 특수 구간 성과에 의존한다:
- `compute_oot_factor_v2` = 0.75 + 0.25 × (oot_quality / 100)
- COVID-era에서 잘 된 전략은 최대 +25% 보너스, 저조한 전략은 최대 -25% 패널티
실증 문제 (v8 기준):
- v7.70: OOT quality 100 → factor 1.000 (+0pp 패널티 없음)
- v7.119: OOT quality 50.2 → factor 0.876 (-8.7점 감점)
- v7.119의 수익률/WFQS가 우수함에도 COVID 특이성으로 인한 의미 없는 감점
→ OOT를 multiplier에서 제거하고, 12개 합성 시장 환경(scenario-test) 점수로 대체.
### v9 공식
```
core = RQS × 0.35 + WFQS_v2 × 0.40 + regime_score × 0.25
v9_base = core × deployment_gate × rb_gate × activity_factor
```
#### 3 Pillars
| Pillar | 가중치 | 출처 | 측정 대상 |
|--------|--------|------|----------|
| **RQS** | 35% | `compute_rqs` | test split 성과: 수익률, Sharpe, DD, PF |
| **WFQS_v2** | 40% | `compute_wfqs_v2` | WFV 교차검증 일관성 + overfitting_penalty |
| **Regime Adaptability** | 25% | scenario-test RRS | 12개 가상 시장 환경 적응력 |
#### regime_score 결정 (fallback chain)
```python
if scenario_robustness_score is not None:
regime_score = scenario_robustness_score # scenario-test RRS (0-100)
elif oot_quality is not None:
regime_score = oot_quality # 기존 OOT quality (하위호환)
else:
regime_score = 50.0 # 중립 fallback
```
#### CW blend (v8과 동일)
reset common-window 또는 common-window가 comparable하면 15% weight blend.
### v9 시뮬레이션 (regime=50 중립 기준)
| 전략 | v8 SQS | v9 SQS (est.) | 변동 | 근거 |
|------|--------|---------------|------|------|
| v7.119 | 57.3 | ~61.0 | **+3.7** | OOT 패널티(-8.7점) 해소 |
| v17.72 | 53.6 | ~57.5 | +3.9 | OOT 패널티 해소 |
| v7.70 | 67.9 | ~68.4 | +0.5 | OOT 보너스(100) 제거되지만 높은 RQS 보상 |
| v19.1 | 69.9 | ~71.3 | +1.4 | RQS+WFQS 모두 우수 |
| v7.123 | 70.1 | ~68.6 | -1.5 | OOT 보너스 제거 |
### scenario-test 자동 실행
`fithia2 rescore-public`이 실행될 때, `scenario_robustness_score`가 없는 전략은
자동으로 12개 시나리오를 실행하여 RRS를 계산한다 (~10초/전략).
```bash
fithia2 rescore-public # scenario-test 자동 실행 포함
fithia2 scenario-test --config v7.70 # 수동으로 단일 전략 테스트
```
### overfit-check 분리
`fithia2 overfit-check` (7-30분 소요)는 SQS에 포함하지 않고 별도 필드로 관리:
```bash
fithia2 aoc <experiment> --report overfit_report.json # 기존 결과 attach
fithia2 aoc <experiment> --run [--quick] # 직접 실행
```
`JournalEntry.overfit_check_score` (0-100) + `overfit_check_breakdown`으로 저장.
leaderboard에서 SQS 옆 참고 정보로 표시.
---
## v7/v8 핵심 변경사항 (2026-04-07)
### 변경 1: OOT binary gate → continuous factor
기존 v3 backbone은 4개 기준의 pass/fail step function:
```
4/4 → 1.00, 3/4 → 0.85, 2/4 → 0.65, 1/4 → 0.40, 0/4 → 0.20
```
v7부터 `compute_oot_factor_v2` (continuous, [0.75, 1.00]):
```
factor = 0.75 + 0.25 * (oot_quality / 100.0)
```
- OOT quality score는 기존 `compute_oot_robustness_quality` 재활용 (COVID 조정 정규화 범위)
- 252d median 범위: [-15%, +10%] → 비례 점수 (기존 pass/fail 임계값 3.0% 폐기)
- 최대 페널티: 25% (기존 80%)
**이유**: COVID(2020-2021)는 단일 극단 이벤트. "orderly" 이벤트 전략이 COVID에서 저조한 건
설계 의도대로. binary gate는 역 인센티브 유발 (v7.123 사례: COVID 엔진 추가 → 수익률 하락).
### 변경 2: deployment gate 이중 페널티 제거
기존 deployment gate는 4개 기준 (positive_fold_rate, median, worst, **WFV gap**).
WFV gap은 이미 `_gap_penalty`에서 연속적으로 WFQS_v2에 반영됨.
v7부터 3개 기준:
```
pass_positive = positive_fold_rate_pct >= 70%
pass_median = median_return_pct >= 5%
pass_worst = worst_return_pct >= -5%
factor: {3: 1.00, 2: 0.85, 1: 0.65, 0: 0.40}
```
## 1. 어디서 계산되나
최종 public `SQS v9` 계산 진입점은
[`compute_public_sqs`](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L568)
이고, 실제 계산은 [`compute_public_sqs_v9`](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L1762)가 한다.
`v8` backbone인 `v7` 계산은
[`compute_public_sqs_v7`](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L1312)
에 있다.
`v7` backbone인 `v3` 계산은
[`compute_public_sqs_v3`](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L1258)
에 남아 있다.
이전 reset CW 기본 표준 `v6` 계산은
[`compute_public_sqs_v6`](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L1552)
에 남아 있다 (하위호환).
multi-capital experimental score는
[`compute_public_sqs_v5`](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L1467)
에 남아 있다.
legacy stress-adjusted score는
[`compute_public_sqs_v2`](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L1199)
가 그대로 유지한다.
public leaderboard / registry rebuild도 같은 함수를 쓴다:
- [`rebuild_registry`](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L2929)
즉, 수동 계산과 leaderboard 계산은 같은 코드 경로를 따라야 한다.
## 1.5 Overlay 경로는 retired 상태다
overlay/book-of-books 평가는 코드베이스에서 제거됐다.
- 기본 leaderboard는 single-book 전략만 포함한다.
- journal에 남아 있는 과거 overlay entry는 historical record로만 취급한다.
- registry rebuild와 `fithia2 lb`는 overlay entry를 건너뛴다.
## 2. 흔한 오해
### `sqs_v2_score`는 final public `SQS`가 아니다
`journal/improvement_journal.jsonl`에 들어가는 `sqs_v2_score`
`test split` 하나만 보고 계산한 split-level quality 점수다.
이 값은 아래 함수에서 나온다:
- [`compute_sqs_v2`](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L178)
반면 final public `SQS v8`의 backbone인 `SQS v7/v3`는:
- `RQS`
- `WFQS v2`
- deployment gate
- main robustness gate
- OOT robustness factor (v7부터 continuous [0.75, 1.00])
- activity factor
를 모두 반영한 값이다.
그래서 `sqs_v2_score=90.7`인데 public `SQS=54.7` 같은 일이 정상적으로 발생할 수 있다.
## 2.5 왜 `v3`로 바뀌었나
기존 public score는:
```text
base_score * main_rb_gate * oot_gate * oot_quality_factor * activity_factor
```
였다. 이 구조는 stress OOT를
- eligibility gate로 한 번 쓰고
- quality penalty로 한 번 더 써서
최근/실전 적합도가 강한 전략을 과하게 깎을 수 있었다.
`SQS v3`는 다음처럼 바꿨다.
```text
base_score * main_rb_gate * oot_gate * activity_factor
```
즉 stress OOT는 **통과 여부를 확인하는 safety layer**로 남기고,
연속 quality 패널티는 primary ranking에서 제거했다.
대신 예전 점수는 `stress_sqs_score`로 registry에 그대로 남긴다.
## 2.6 왜 `v8`이 default인가
### v3 → v4/v5/v6 변천사
한동안 `v5``10k/25k/100k` multi-capital common-window를 기본 점수에 섞었다.
하지만 이 방식은 연구 속도를 떨어뜨렸고, historical baseline까지 전부 같은 three-bucket으로
다시 채워 넣어야 공정 비교가 가능했다.
그래서 기본 rank는 `v4`(10k reset CW)로 돌렸다가, 이후 `v6`(reset CW + v3 backbone)로 올랐다.
### v6 → v7/v8 (2026-04-07)
`v6`는 OOT binary gate(5-step)와 deployment gate의 WFV gap 이중 페널티 문제가 있었다.
`v7`는 두 가지를 동시에 고쳤다:
1. OOT binary gate → continuous factor `[0.75, 1.00]`
2. deployment gate에서 WFV gap 기준 제거 (이미 `_gap_penalty`에서 반영)
`v8``v7` backbone + reset common-window blend:
```text
final_v8 = v7_score * 0.80 + reset_common_window_score * 0.20 # reset CW 있을 때
final_v8 = v7_score * 0.80 + common_window_score * 0.20 # fallback: 10k CW
final_v8 = v7_score # fallback: CW 없음
```
multi-capital common-window summary는 계속 저장한다.
다만 이건 `default rank`가 아니라 `capital scalability` 진단용으로 본다.
### validation stack 중 하나라도 없으면 public `SQS`는 `None`
아래 세 개 중 하나라도 빠지면 public `SQS`는 계산되지 않는다.
- walk-forward
- main robustness
- out-of-time robustness
이 경우 함수는 숫자 대신 `None`을 주고, breakdown에 missing reason을 넣는다.
예:
```python
score, breakdown, source = compute_public_sqs(...)
# score == None
# source == "pending_validation"
# breakdown == {"requires_out_of_time_robustness": 1.0}
```
## 3. 입력 산출물 경로
표준 경로는 아래다.
### Walk-Forward
[`runs/<experiment>_wfv/walk_forward/walk_forward_summary.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/runs)
### Main Robustness
[`runs/<experiment>_rm/robustness_matrix/robustness_matrix_summary.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/runs)
### OOT Robustness
[`runs/<experiment>_oot_rm/robustness_matrix/robustness_matrix_summary.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/runs)
journal에 attach된 경우엔 file path가 없어도 journal entry만으로 rebuild가 가능하다.
다만 디버그와 재현을 위해서는 run file도 남아 있는 쪽이 낫다.
## 4. 실제 계산 순서
public `SQS v8`은 아래 순서로 계산된다.
### 4.1 RQS 계산
split 3개에서
[`compute_rqs`](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L691)
를 계산한다.
### 4.2 WFV 기반 `WFQS v2`
walk-forward summary에서
[`compute_wfqs_v2`](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L979)
를 계산한다.
WFQS v2는 test fold 성과를 기반으로 한 base score에 4개의 multiplicative penalty를 곱한다:
- **overfitting_penalty** (`_overfitting_penalty`): win rate gap 기반. train/test 간 per-trade 정확도 차이 탐지.
- 0~3pp: 1.0 (정상 노이즈), 3~10pp: 1.0→0.85, 10~20pp: 0.85→0.60, >20pp: 0.5
- raw return gap 비교는 train/test 기간 차이(e.g. 504d vs 63d) 때문에 과적합이 없어도 큰 gap이 생기므로 사용하지 않는다
- 구 journal 데이터에 win rate 없으면 기존 `_gap_penalty`로 fallback
- **fold_variance_penalty**: test fold 수익률 CV (변동계수) 기반. 특정 시기에만 작동하는 전략 탐지.
- **trade_credibility**: fold당 거래 수 + win rate 이상값 탐지.
- **engine_reliability**: engine 신뢰도 비율.
### 4.3 Deployment gate를 반영한 base score
코드 그대로 쓰면 (v7/v8 기준):
```text
base_score = (RQS * 0.45 + WFQS_v2 * 0.55) * deployment_gate
```
deployment gate는 아래 **3개** 통과 개수로 정해진다 (v7부터 WFV gap 기준 제거).
- WFV positive fold rate >= 70%
- WFV median return >= 5%
- WFV worst return >= -5%
통과 개수별 factor:
- 3개: `1.00`
- 2개: `0.85`
- 1개: `0.65`
- 0개: `0.40`
> **v6 이하**에서는 4번째 기준(`WFV mean train-test gap <= 35%`)이 있어 5-step
> `{4:1.00, 3:0.85, 2:0.65, 1:0.40, 0:0.20}`이었다. v7부터 gap은 `_gap_penalty`에서
> WFQS_v2에 이미 연속 반영되므로 deployment gate에서 제거.
구현:
[`_compute_public_sqs_components_v2`](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L1128)
### 4.4 Main robustness gate
[`compute_robustness_gate`](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L1860)
를 곱한다.
기준:
- overall positive window rate >= 65%
- 63d median >= 3%
- 252d median >= 8%
- overall worst return >= -12%
factor는 `1.00 / 0.85 / 0.65 / 0.40 / 0.20` 구조 (4-criterion 그대로).
### 4.5 OOT robustness factor (v7부터 continuous)
[`compute_oot_factor_v2`](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L2084)
를 곱한다.
v7/v8에서는 binary gate(5-step) 대신 continuous factor `[0.75, 1.00]`:
```text
oot_factor = 0.75 + 0.25 * (oot_quality / 100.0)
```
- `oot_quality`는 기존 `compute_oot_robustness_quality` 재활용 (COVID 조정 정규화 범위)
- 최소 0.75 (최악 OOT에서도 25% 감점) ← 기존 최대 80% 감점 대비
OOT quality 정규화 범위:
| 지표 | 낮음 (0점) | 높음 (100점) |
|------|-----------|-------------|
| 252d median | -15% | +10% |
| 63d median | -8% | +3% |
| positive_rate 63d+ | 20% | 70% |
| worst_return | -30% | -5% |
예외: OOT summary 전 horizon에서 `all-zero sparse no-trade` 패턴이면
`factor = 1.0` 중립 처리 (stress에서 거래하지 않은 것 = 전략 설계 의도).
> **v6 이하**에서는 `compute_oot_robustness_gate`
> (L[1945](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L1945))의
> binary 4-criterion gate를 사용했다. v7부터 이 함수는 하위호환 유지용으로만 남음.
### 4.6 OOT quality — v7/v8에서 factor로 사용
[`compute_oot_robustness_quality`](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L2033)
`0~100` quality를 계산한다.
v7/v8에서는 이 값이 **OOT factor**에 비례 반영된다 (위 4.5 참조).
`SQS v3`/`v4`/`v5`/`v6`에서는 랭킹 점수에 곱하지 않고 registry breakdown과
`stress_sqs_score`에서만 사용했었다.
### 4.7 Activity factor
마지막으로
[`_public_activity_factor`](/Users/yirugi/mycloud/personal/workspace/fithia2/libs/backtest/tracker.py#L940)
를 곱한다.
이 단계 때문에:
- replacement 전략
- 지나치게 trade 수가 적은 rotation 전략
은 split/WFV가 좋아도 public 점수가 낮아질 수 있다.
### 4.8 Common-window blend (v8 우선순위)
v8는 아래 우선순위로 CW blend를 적용한다:
**1순위: reset common-window (`v8_continuous_oot+reset_common_window`)**
comparable reset CW summary가 있으면:
```text
final_v8 = v7_score * 0.80 + reset_common_window_score * 0.20
```
**2순위: 10k common-window (`v8_continuous_oot+common_window`)**
reset CW 없고 comparable 10k CW summary가 있으면:
```text
final_v8 = v7_score * 0.80 + common_window_score * 0.20
```
**fallback: bare v7 (`v8_fallback_v7`)**
CW summary 없으면 `v7_score` 그대로.
개별 `common_window_score`는 같은 연속 기간, 같은 초기 자본으로 돌린 run의:
- total return
- profit factor
- Sharpe
- max drawdown
- return on gross exposure
- capital velocity
를 합성한 값이다.
### 4.9 Multi-capital common-window diagnostics
`10k/25k/100k` common-window summary가 모두 comparable이면
multi-capital score도 계산해 registry에 저장한다.
multi-capital score는:
- `10k` 점수 `60%`
- `25k` 점수 `25%`
- `100k` 점수 `15%`
- 세 bucket 중 최저 점수를 floor로 `10%` blend
구조다.
이 값은 현재 **기본 leaderboard rank**가 아니라, 자본 규모 민감도와 scalability를 보는
보조 진단이다.
## 5. `return_max_long_v6new.29` / `v6new.28` 같은 사례를 어떻게 읽어야 하나
`v6new.29`는 split/WFV/deployment도 강하고,
`10k common-window` capital-growth가 특히 강한 전형적인 케이스다.
- `SQS v4`에서는 이 전략이 더 높은 순위를 받는다.
- `stress_sqs_score`는 여전히 낮아서 stress 취약성은 숨겨지지 않는다.
`v6new.28`은 다른 방향의 예시다.
### 5.1 왜 헷갈렸나
journal에는 아래 값이 이미 있었다.
- `sqs_v2_score = 90.7`
- `rqs_score = 83.5`
- `wfqs_v2_score = 48.6`
그런데 final public `SQS`는 비어 있었다.
이유는 간단했다.
- walk-forward 있음
- main robustness 있음
- OOT robustness 없음
즉 tracker 기준으로는 아직 `pending_validation`이었다.
실제로:
```python
score, breakdown, source = compute_public_sqs(...)
```
결과는:
- `score = None`
- `source = "pending_validation"`
- `breakdown = {"requires_out_of_time_robustness": 1.0}`
였다.
### 5.2 OOT를 붙인 뒤 최종 계산
OOT run:
- [robustness_matrix_summary.json](/Users/yirugi/mycloud/personal/workspace/fithia2/runs/return_max_long_v6new.28_oot_rm/robustness_matrix/robustness_matrix_summary.json)
OOT 주요 수치:
- overall positive `90.0%`
- overall worst `-4.59%`
- `63d median 11.8%`
- `252d median 72.07%`
최종 계산 결과:
- `RQS = 83.5`
- `WFQS v2 = 48.6`
- public `SQS = 54.7`
- source = `v2_deployment+robustness+oot`
이 케이스에서는:
- deployment gate = `0.85`
- main robustness gate = `1.0`
- OOT gate = `1.0`
- OOT quality factor = `1.0`
- activity factor = `1.0`
라서 실질적으로:
```text
base_score = (83.5 * 0.45 + 48.6 * 0.55) * 0.85 = 54.7
final_public_sqs = 54.7
```
`90.7`이 틀린 값이 아니라,
**그 값은 public `SQS`가 아니라 split-only `sqs_v2_score`였다**가 정확한 설명이다.
## 6. 권장 계산 절차
### 6.1 validation run 만들기
```bash
python apps/backtester/run.py \
--manifest configs/experiments/<experiment>.json \
--snapshot-dir data/datasets/snapshots \
--walk-forward \
--wf-train-days 252 \
--wf-test-days 63 \
--wf-step-days 63 \
--output-root runs/<experiment>_wfv
python apps/backtester/run.py \
--manifest configs/experiments/<experiment>.json \
--snapshot-dir data/datasets/snapshots \
--robustness-matrix \
--rm-horizons 21,63,126,252,504 \
--rm-step-days 21 \
--output-root runs/<experiment>_rm
python apps/backtester/run.py \
--manifest configs/experiments/<experiment>.json \
--snapshot-dir data/datasets/snapshots \
--snapshot-id <oot_snapshot_id> \
--robustness-matrix \
--rm-horizons 21,63,126,252 \
--rm-step-days 21 \
--output-root runs/<experiment>_oot_rm
```
기본 `<oot_snapshot_id>`
[`midlarge-liquid-long-v1-oot-2020-2021`](/Users/yirugi/mycloud/personal/workspace/fithia2/data/datasets/snapshots/midlarge-liquid-long-v1-oot-2020-2021/manifest.json)이다.
다만 `pre_event_hurst_60d`, `pre_event_entropy_60d`, `pre_event_market_temperature` 같은
`tier2/tier3` feature를 실제 선택 규칙에 쓰는 전략은
[`midlarge-liquid-long-v1-oot-2020-2021_tier3`](/Users/yirugi/mycloud/personal/workspace/fithia2/data/parquet/midlarge-liquid-long-v1-oot-2020-2021_tier3/manifest.json)
처럼 feature-matched OOT snapshot을 써야 한다.
### 6.2 journal에 attach
```bash
python apps/tracker/cli.py attach-wfv \
--journal-dir journal \
<experiment> \
--summary runs/<experiment>_wfv/walk_forward/walk_forward_summary.json
python apps/tracker/cli.py attach-robustness \
--journal-dir journal \
<experiment> \
--summary runs/<experiment>_rm/robustness_matrix/robustness_matrix_summary.json
python apps/tracker/cli.py attach-oot-robustness \
--journal-dir journal \
<experiment> \
--summary runs/<experiment>_oot_rm/robustness_matrix/robustness_matrix_summary.json
```
### 6.3 Python으로 직접 계산
```python
import json
from pathlib import Path
from libs.backtest.domain import SplitResult, WalkForwardSummary, RobustnessMatrixSummary
from libs.backtest.tracker import compute_public_sqs
entry = ... # journal entry dict
train = SplitResult(**entry["results"]["train"])
valid = SplitResult(**entry["results"]["valid"])
test = SplitResult(**entry["results"]["test"])
wfv = WalkForwardSummary(**entry["walk_forward_summary"])
rm = RobustnessMatrixSummary(**entry["robustness_matrix_summary"])
oot = RobustnessMatrixSummary(**entry["out_of_time_robustness_summary"])
score, breakdown, source = compute_public_sqs(
train,
valid,
test,
walk_forward_summary=wfv,
robustness_matrix_summary=rm,
out_of_time_robustness_summary=oot,
)
print(score, source, breakdown)
```
### 6.4 Common-window attach
common-window는 같은 기간과 같은 초기 자본으로 맞춰서 별도로 붙인다.
권장 원칙:
- contenders끼리는 같은 `start_date / end_date`를 쓴다
- initial equity도 같게 맞춘다
- 현재 official `v8` 기준값은 `10,000`이다
- partial backfill 상태에선 `v8``v7 fallback`이 섞일 수 있음을 인지한다
attach는 tracker CLI 또는 Python helper로 한다.
요약만 붙어 있으면 registry rebuild가 가능하다.
## 7. 디버그 체크리스트
### score가 `None`이면
아래를 먼저 본다.
- `walk_forward_summary` 존재 여부
- `robustness_matrix_summary` 존재 여부
- `out_of_time_robustness_summary` 존재 여부
### `sqs_v2_score`는 큰데 public `SQS`가 낮으면
아래를 본다.
- `wfqs_v2_score` (WFV gap이 크면 `_gap_penalty`로 크게 감점)
- deployment gate (3-criterion: positive_rate, median, worst)
- robustness gate
- OOT factor (breakdown의 `oot_quality` 값 확인; v7/v8에서 0.75~1.00)
- activity factor
- common-window summary 존재 여부와 comparable window 설정
특히 rotation / low-activity 전략은 activity factor 때문에 public 점수가 크게 깎일 수 있다.
반대로 연속 구간 복리 수익이 강한 전략은 reset common-window가 붙으면 더 올라갈 수 있다.
### registry와 수동 계산이 다르면
아래를 본다.
- journal entry에 attach가 실제로 되었는가
- registry가 rebuild 되었는가 (`rsp` 또는 `python -m apps.tracker.cli leaderboard`)
- 같은 experiment 이름의 중복 journal entry 중 최신 exact match가 선택되었는가
- common-window가 일부 전략에만 backfill된 partial 상태가 아닌가
- `sqs_score` 필드가 최신 v8 기준인지 확인 (이전 v6 기준 registry 캐시가 남아 있을 수 있음)
### run 숫자가 leaderboard/journal과 다르면
점수 계산식보다 먼저 snapshot provenance를 본다.
- run dir의 [`snapshot_manifest.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/runs)
- run dir의 [`snapshot_fingerprint.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/runs)
- `snapshot_manifest.json``export_enrichments`가 다르면 같은 snapshot id 계열이라도 직접 비교 대상으로 섞지 않는다.
여기서 특히 아래를 본다.
- `dataset_snapshot_id`
- `source_manifest_path`
- `snapshot_dir_name`
- `manifest_snapshot_id`
- `snapshot_id_matches_manifest`
- `output_dir_matches_manifest`
둘 중 하나라도 `false`면,
같은 snapshot id 아래 다른 내용이 덮어써졌거나 잘못 복사된 manifest일 수 있다.
이 경우 점수 비교 전에 snapshot provenance부터 정리해야 한다.
## 8. 운영 원칙
- split-only `sqs_v2_score`를 final public `SQS`로 말하지 않는다.
- leaderboard 숫자는 반드시 `compute_public_sqs()` 경로로만 확인한다.
- WFV/robustness/OOT 또는 scenario-test가 없는 후보는 “점수 미계산”이지 “점수 0”이 아니다.
- journal과 registry가 다르면 journal + tracker 함수가 source of truth다.
- `v9` 비교는 comparable common-window가 붙은 후보끼리 우선 본다.
- scenario-test RRS가 없으면 OOT quality로 fallback, 없으면 50.0 중립.
- deployment gate는 WFV gap을 포함하지 않는다 (이미 WFQS_v2 `_gap_penalty`에서 반영).
- **SQS 점수는 날짜 간 비교 불가** — Yahoo Finance adjusted close 비결정성으로 ±15pp 변동. 같은 날 계산된 점수끼리만 상대 비교 신뢰.
- `overfit-check` 점수는 SQS에 포함되지 않으며, 별도 필드(`overfit_check_score`)로 관리한다.