# 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`)로 관리한다.
