# Strategy Research Workflow And Handoff 이 문서는 `return-max long` 전략 연구를 여러 AI 에이전트가 이어서 하더라도 같은 실수를 반복하지 않도록 하기 위한 운영 기준이다. 가장 중요한 배경은 과거 `v797_v793_epam_rklb_alk_combo` 누락 사례와, 그 뒤에 드러난 `disabled named micro contamination`, `repaired OOT snapshot` 문제다. 이 문서는 그런 실수와 재오염을 다시 만들지 않기 위한 운영 규칙을 정의한다. ## 1. Source Of Truth 전략 연구의 source of truth는 아래 순서로 본다. 1. [`configs/experiments`](/Users/yirugi/mycloud/personal/workspace/fithia2/configs/experiments) 2. [`journal/improvement_journal.jsonl`](/Users/yirugi/mycloud/personal/workspace/fithia2/journal/improvement_journal.jsonl) 3. [`journal/experiment_registry.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/journal/experiment_registry.json) 4. [`runs/`](/Users/yirugi/mycloud/personal/workspace/fithia2/runs) 원칙: - `runs/`는 실험 산출물 저장소다. 단독으로는 정식 전략 정의가 아니다. - `journal`은 평가 기록이다. 정식 전략 정의를 대신하지 않는다. - 실제로 다시 돌릴 수 있는 전략 정의는 반드시 `configs/experiments/*.json`에 있어야 한다. - run 비교가 안 맞으면 `runs//snapshot_manifest.json`, `snapshot_fingerprint.json`을 먼저 본다. - `snapshot_manifest.json`의 `export_enrichments`가 다르면 같은 base snapshot name이라도 다른 dataset으로 본다. ## 2. 용어 정의 ### Scratch Candidate 임시 탐색용 manifest. - `/tmp/*.json` 또는 배치용 임시 경로에 둘 수 있다. - 빠른 조합 탐색과 train scan에는 허용한다. - 이 상태로는 leaderboard/journal의 최종 후보가 될 수 없다. ### Official Experiment 정식 manifest가 있고 full-split 결과까지 있는 실험. 필수 조건: 1. [`configs/experiments`](/Users/yirugi/mycloud/personal/workspace/fithia2/configs/experiments)에 `.json` 파일이 있다. 2. 파일명 stem과 `experiment_name`이 같다. 3. `train / valid / test` full split 결과가 있다. 4. journal entry가 있다. ### Deploy Candidate 실전 승격을 검토할 수 있는 실험. Official Experiment 조건에 더해: 1. walk-forward summary가 있다. 2. robustness matrix summary가 있다. 3. repaired out-of-time robustness summary가 있다. 4. public `SQS`가 계산돼 있다. ### Archived Overfit Branch 다음 조건 중 하나라도 만족하면 기본 workflow에서 archive 대상으로 본다. - manifest의 `strategy_engines[].engine_id` 중 하나라도 `exact`를 포함 - manifest에 named micro engine이 존재함 - `enabled: false`여도 contamination으로 본다. - symbol/date-specific pocket을 누적해 funded trade set을 직접 외우는 구조 이 계열은 연구 참고용으로만 남기고, 기본 leaderboard와 실전 후보군에서는 제외한다. ## 2.1 버전 규칙 - 기존 `return_max_long_v326` 같은 historical 전략은 **의미상 `v0.326`** 으로 취급한다. - 과거 journal/manifest 파일명을 전부 물리적으로 바꾸지는 않는다. 재현성과 참조 무결성 때문이다. - 새 clean lineage는 **`v1.1`부터 시작**한다. - lineage 시작점은 `v1.1`이지만, 문서 작성 시점의 active clean baseline은 [`return_max_long_v1.51.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/configs/experiments/return_max_long_v1.51.json) 이다. - 이후 새 실험은 `v1.52`, `v1.53` 식으로 올린다. ## 3. 정식 승격 기준 다음 중 하나에 해당하면 scratch를 official로 승격한다. - full-split에서 현재 baseline을 이겼다. - leaderboard에 올릴 가치가 있다. - 다음 세션에서도 다시 이어서 연구할 가능성이 높다. - WFV/robustness까지 붙일 대상이다. 즉, "의미 있는 winner"는 반드시 repo manifest로 옮긴다. ## 4. 금지 규칙 아래는 금지한다. - scratch manifest 상태로 journal만 기록하는 것 - `experiment_name`과 다른 파일명으로 공식 manifest를 저장하는 것 - leaderboard 상위 전략이 `configs/experiments`에 없는 상태로 남는 것 - WFV/robustness가 붙은 전략의 summary 경로를 journal에 연결하지 않는 것 ## 5. 표준 연구 흐름 현재 active clean baseline은 [`return_max_long_v1.51.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/configs/experiments/return_max_long_v1.51.json) 이다. 이 전략은 old `v1.12` 계열에서 disabled named micro를 물리적으로 제거한 clean replacement다. 즉, 지금부터의 baseline 해석은 "계보의 시작"과 "현재 active baseline"을 구분해야 한다. ### Step 1. Baseline 선택 - 기준 전략은 반드시 [`configs/experiments`](/Users/yirugi/mycloud/personal/workspace/fithia2/configs/experiments) 안의 manifest여야 한다. - baseline 이름은 journal entry와 동일하게 사용한다. - paper backtest와 research backtester는 같은 snapshot을 써야 비교가 된다. 경로 해석 우선순위는 `settings.parquet_dir` 다음 `data/datasets/snapshots`다. ### Step 2. Scratch 탐색 - `/tmp`에 scratch manifest를 만들고 배치 탐색을 돌릴 수 있다. - 이 단계에서는 train-only scan이나 fast full-split batch를 허용한다. - 아직 journal 최종 기록 대상은 아니다. ### Step 3. Winner 확정 scratch 후보가 baseline을 이기면 먼저 repo에 정식 manifest를 만든다. 권장 순서: 1. `configs/experiments/.json` 생성 2. 해당 파일로 다시 full split 실행 3. 그 뒤에 journal 기록 ### Step 4. Journal 기록 정식 manifest 기준으로만 기록한다. 필수 확인: - `experiment_name == manifest filename stem` - `train / valid / test` 결과가 모두 존재 - hypothesis / config_delta / verdict가 비어 있지 않음 ### Step 5. WFV / Robustness deploy 후보는 아래를 붙인다. - walk-forward validation - robustness matrix - repaired out-of-time robustness - 현재 기준 snapshot은 [`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) 그리고 tracker에 attach한다. ### Step 6. Leaderboard 갱신 정식 manifest + journal + WFV/robustness attach까지 끝난 뒤에 leaderboard를 본다. public `SQS` 계산 상세와 디버그 절차는 [`docs/public_sqs_calculation.md`](/Users/yirugi/mycloud/personal/workspace/fithia2/docs/public_sqs_calculation.md) 를 본다. ## 6. 세션 종료 전 체크리스트 세션을 끝내기 전에 아래를 확인한다. 1. 오늘 새 winner가 scratch만 있고 repo manifest가 없는가 2. journal에 기록한 실험명이 실제 `configs/experiments/.json`과 대응하는가 3. deploy 후보인데 WFV/robustness attach가 빠진 것이 없는가 4. deploy 후보인데 repaired OOT attach가 빠진 것이 없는가 5. leaderboard 상단 전략 중 repo manifest가 없는 것이 없는가 6. default leaderboard에 named micro manifest가 보이지 않는가 7. 새로 만든 run의 `snapshot_fingerprint.json`에서 manifest mismatch가 없는가 하나라도 `yes`면 세션 종료 전에 정리한다. ## 7. 권장 명령 흐름 ### Full Split ```bash python apps/backtester/run.py \ --manifest configs/experiments/.json \ --snapshot-dir data/datasets/snapshots \ --output-root runs/_fullsplit ``` ### Walk-Forward ```bash python apps/backtester/run.py \ --manifest configs/experiments/.json \ --snapshot-dir data/datasets/snapshots \ --walk-forward \ --wf-train-days 504 \ --wf-test-days 63 \ --wf-step-days 63 \ --output-root runs/_wfv ``` ### Robustness Matrix ```bash python apps/backtester/run.py \ --manifest configs/experiments/.json \ --snapshot-dir data/datasets/snapshots \ --robustness-matrix \ --rm-horizons 21,63,126,252,504 \ --rm-step-days 21 \ --output-root runs/_rm ``` ### Attach Summaries ```bash python apps/tracker/cli.py attach-wfv \ --journal-dir journal \ \ --summary runs/_wfv/walk_forward/walk_forward_summary.json python apps/tracker/cli.py attach-robustness \ --journal-dir journal \ \ --summary runs/_rm/robustness_matrix/robustness_matrix_summary.json python apps/tracker/cli.py attach-oot-robustness \ --journal-dir journal \ \ --summary runs/_oot_rm/robustness_matrix/robustness_matrix_summary.json ``` ### Book Overlay Evaluation single-book manifest를 억지로 섞지 말고, 별도 book을 각각 먼저 고정 기간으로 돌린 뒤 overlay를 따로 평가한다. 1. 같은 기간, 같은 초기 자본으로 각 book의 equity curve를 만든다. 2. [`configs/overlays`](/Users/yirugi/mycloud/personal/workspace/fithia2/configs/overlays) 아래 spec에 regime별 자본 배분을 적는다. 3. [`apps/tools/evaluate_book_overlay.py`](/Users/yirugi/mycloud/personal/workspace/fithia2/apps/tools/evaluate_book_overlay.py)로 overlay equity와 summary를 만든다. 4. full-window spec/summary와 stress-window spec/summary를 모두 만든 뒤 [`apps/tracker/cli.py`](/Users/yirugi/mycloud/personal/workspace/fithia2/apps/tracker/cli.py) `record-overlay`로 공식 journal/leaderboard에 등록한다. 주의: - overlay spec의 `books[].equity_csv`는 **재현 기준 입력**이다. - 공식 평가와 [`fithia2 paper backtest --overlay`](/Users/yirugi/mycloud/personal/workspace/fithia2/apps/paper_trader/cli.py)는 둘 다 이 frozen curve를 우선 replay해야 한다. - OOT overlay는 full-window csv를 재사용하면 안 된다. `runs/book_overlay_oot_inputs/...`처럼 OOT 전용 book curve를 따로 만든다. - `regime_source.snapshot_dir`가 explicit snapshot directory라면 evaluator는 그 디렉터리를 그대로 merged load해야 한다. 예시: ```bash fithia2 paper backtest \ --config configs/experiments/return_max_long_v6.221.json \ --config configs/experiments/return_max_long_v6new.54.json \ --capital 10000 \ --start 2022-03-03 \ --end 2026-03-13 \ --output runs/book_overlay_v1_inputs \ --no-trades python apps/tools/evaluate_book_overlay.py \ --spec configs/overlays/return_book_overlay_v1.json \ --output-dir runs/return_book_overlay_v1_eval python apps/tracker/cli.py record-overlay \ --spec configs/overlays/return_book_overlay_v3.json \ --summary runs/return_book_overlay_v3_eval/overlay_summary.json \ --stress-spec configs/overlays/return_book_overlay_v3_oot.json \ --stress-summary runs/return_book_overlay_v3_oot_eval/overlay_summary.json \ --hypothesis "Official overlay candidate" ``` ## 8. 빠른 무결성 점검 journal entry와 manifest 대응을 확인하려면: ```bash python - <<'PY' import json from pathlib import Path cfg = Path("configs/experiments") missing = [] for line in Path("journal/improvement_journal.jsonl").read_text().splitlines(): if not line.strip(): continue obj = json.loads(line) name = obj.get("experiment_name") if name and not (cfg / f"{name}.json").exists(): missing.append(name) print("missing", len(missing)) for name in missing[-20:]: print(name) PY ``` 이 출력은 항상 `0`이어야 한다. ### Snapshot Provenance 점검 run 결과가 journal/leaderboard 숫자와 다르면 아래 두 파일을 먼저 본다. - `runs//snapshot_manifest.json` - `runs//snapshot_fingerprint.json` - 특히 `snapshot_manifest.json`의 `feature_version`, `export_enrichments`, `created_at_utc`를 같이 본다. 핵심 필드: - `dataset_snapshot_id` - `source_manifest_path` - `snapshot_dir_name` - `manifest_snapshot_id` - `snapshot_id_matches_manifest` - `output_dir_matches_manifest` `snapshot_id_matches_manifest=false` 또는 `output_dir_matches_manifest=false`면 같은 snapshot id 아래 다른 내용이 덮어써졌거나, 잘못 복사된 manifest일 수 있다. 이 상태에서 성능 비교를 계속하면 안 된다. ## 9. 다른 에이전트에게 넘길 때 남겨야 할 것 handoff에는 최소 아래를 포함한다. - 현재 active baseline manifest 경로 - 현재 raw-return winner와 deploy winner - 마지막으로 유효했던 개선 축 - 실패한 축 3~5개 - 아직 scratch 상태인 강한 후보가 있으면 그 manifest 경로 - 진행 중인 WFV / robustness 실행 경로 즉, "무엇이 최고였는가"보다 "무엇이 먹혔고 무엇이 죽었는가"를 남겨야 한다. ## 10. 운영 교훈 ### 10.1 Disabled Named Micro도 오염이다 manifest에 named micro engine이 남아 있으면 `enabled: false`여도 clean 전략으로 보지 않는다. 실제로 old `v1.10~v1.13`은 disabled named micro가 남아 있었고, 이 때문에 현재 clean replacement로 [`return_max_long_v1.51.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/configs/experiments/return_max_long_v1.51.json), [`return_max_long_v1.52.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/configs/experiments/return_max_long_v1.52.json) 을 분리했다. ### 10.2 OOT는 최적화 목표가 아니라 품질 계층이다 `2020~2021` repaired OOT는 점수를 올리기 위한 최적화 목표가 아니라, 현재 전략이 특정 시기 구조에만 맞는지 확인하는 추가 품질 계층이다. OOT를 보고 규칙을 직접 맞추기 시작하면 그 순간 또 오염된다. ### 10.3 Snapshot 결함부터 의심한다 OOT가 전부 `0 trade`로 보이면 전략 탓만 하지 말고 snapshot 자체를 먼저 점검한다. 실제로 symbol 기반 export에서 `market_cap_proxy`, `exchange_proxy`가 비어 있던 버그가 있었고, 이를 고친 뒤에야 repaired OOT가 의미 있는 비교 지표가 됐다. ### 10.4 Paper Backtest stale 판정은 manifest 나이가 아니라 coverage로 본다 `fithia2 paper backtest`는 snapshot이 오래됐다는 이유만으로 refresh하면 안 된다. 실제로는 기존 snapshot이 충분한 날짜 범위를 이미 덮고 있을 수 있다. 현재 원칙: - stale 여부는 parquet `event_date` coverage로 판단한다 - snapshot 경로는 `settings.parquet_dir`를 먼저 보고, 없으면 `data/datasets/snapshots`를 본다 - refresh가 실패해도 기존 snapshot이 requested period를 덮으면 그대로 사용한다 즉 "manifest created_at이 오래됐다"는 이유만으로 refresh를 강제하면 안 된다. ### 10.5 Screener 장애 fallback은 local-only여야 한다 snapshot export 중 live screener가 `500`으로 죽을 수 있다. 이때 조용히 다른 remote endpoint로 넘어가면, 시점마다 다른 값이 섞여 재현성이 깨진다. 현재 원칙: - universe screener가 실패하면 우선 기존 local snapshot metadata를 deterministic하게 재사용한다 - local fallback조차 없으면 export는 그대로 실패시킨다 - 임의의 remote fallback으로 조용히 성공시키지 않는다 즉 장애 대응보다 재현성을 우선한다. ## 11. 현재 상태 문서 작성 시점의 active clean 후보는 아래다. 1. [`return_max_long_v1.51.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/configs/experiments/return_max_long_v1.51.json) 2. [`return_max_long_v1.52.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/configs/experiments/return_max_long_v1.52.json) 3. [`return_max_long_v1.53.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/configs/experiments/return_max_long_v1.53.json) 현재 baseline은 `v1.51`이다. - full split `+37.9 / +35.3 / +42.7` - WFV mean `+11.10%`, gap `29.67%` - repaired OOT positive window rate `48.7%` 최근 실패한 축: - [`return_max_long_v1.53.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/configs/experiments/return_max_long_v1.53.json) - raw return은 좋아졌지만 WFV gap/OOT가 나빠져 탈락 - [`return_max_long_v1.55.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/configs/experiments/return_max_long_v1.55.json) - test는 좋아졌지만 repaired OOT가 크게 악화 - [`return_max_long_v1.56.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/configs/experiments/return_max_long_v1.56.json), [`return_max_long_v1.57.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/configs/experiments/return_max_long_v1.57.json), [`return_max_long_v1.58.json`](/Users/yirugi/mycloud/personal/workspace/fithia2/configs/experiments/return_max_long_v1.58.json) - general-only 미세조정이었지만 no-op 또는 후퇴 ## 12. 현재 예외 현재 문서 작성 시점의 active known exception은 없다. 과거 `v797_v793_epam_rklb_alk_combo` 누락은 이미 역사적 교훈으로만 남기고, 현재 workflow에선 `journal ↔ manifest parity`와 auto-sync 규칙으로 막는다. ## 13. 운영 원칙 요약 - scratch는 빠르게, official은 엄격하게 관리한다. - journal에 적을 정도면 manifest도 repo에 있어야 한다. - leaderboard 상단 전략은 반드시 재현 가능해야 한다. - deploy 후보는 full split만으로 끝내지 않고 WFV/robustness/repaired OOT까지 붙인다. - named micro는 disabled여도 clean 전략에 남겨두지 않는다. - 세션 종료 전 `journal ↔ manifest` parity를 확인한다. - snapshot provenance가 틀리면 점수 논쟁보다 먼저 metadata부터 고친다.