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/research_workflow_and_hando...

18 KiB

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
  2. journal/improvement_journal.jsonl
  3. journal/experiment_registry.json
  4. runs/

원칙:

  • runs/는 실험 산출물 저장소다. 단독으로는 정식 전략 정의가 아니다.
  • journal은 평가 기록이다. 정식 전략 정의를 대신하지 않는다.
  • 실제로 다시 돌릴 수 있는 전략 정의는 반드시 configs/experiments/*.json에 있어야 한다.
  • run 비교가 안 맞으면 runs/<run_id>/snapshot_manifest.json, snapshot_fingerprint.json을 먼저 본다.
  • snapshot_manifest.jsonexport_enrichments가 다르면 같은 base snapshot name이라도 다른 dataset으로 본다.

2. 용어 정의

Scratch Candidate

임시 탐색용 manifest.

  • /tmp/*.json 또는 배치용 임시 경로에 둘 수 있다.
  • 빠른 조합 탐색과 train scan에는 허용한다.
  • 이 상태로는 leaderboard/journal의 최종 후보가 될 수 없다.

Official Experiment

정식 manifest가 있고 full-split 결과까지 있는 실험.

필수 조건:

  1. 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 이다.
  • 이후 새 실험은 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 이다. 이 전략은 old v1.12 계열에서 disabled named micro를 물리적으로 제거한 clean replacement다. 즉, 지금부터의 baseline 해석은 "계보의 시작"과 "현재 active baseline"을 구분해야 한다.

Step 1. Baseline 선택

  • 기준 전략은 반드시 configs/experiments 안의 manifest여야 한다.
  • baseline 이름은 journal entry와 동일하게 사용한다.
  • paper backtest와 research backtester는 같은 snapshot을 써야 비교가 된다. phase 1부터는 raw dataset_snapshot_id를 바로 디렉터리명으로 보지 않고, 먼저 canonical snapshot id로 resolve한다.
  • canonical main snapshot은 midlarge-liquid-long-v1_bucketfix_full_audit_canonical 이다.
  • canonical OOT snapshot은 midlarge-liquid-long-v1-oot-2020-2021_canonical 이다.
  • legacy tier3, tier3tech, mom, tech 계열 snapshot id는 여전히 manifest에서 허용되지만, run metadata에는 requested_snapshot_idcanonical_snapshot_id를 같이 남긴다.
  • public default SQS용 common-window는 현재 초기 자본 10,000 기준으로 붙인다.
  • official common-window 날짜 범위는 현재 2022-03-02 ~ 2026-03-24다.
  • 25k/100k common-window는 optional diagnostics로만 본다.
  • 최근 v6new.34x ~ 37x lineage는 25k/100k diagnostics도 일부 backfill돼 있지만, 기본 rank는 10k만 쓴다.
  • 기본 leaderboard active window는 현재 IMP-0606 / return_max_long_v6new.29 이후다. 그 전 실험은 기록은 남기되 기본 보드에서는 retired로 숨긴다.

Step 2. Scratch 탐색

  • /tmp에 scratch manifest를 만들고 배치 탐색을 돌릴 수 있다.
  • 이 단계에서는 train-only scan이나 fast full-split batch를 허용한다.
  • 아직 journal 최종 기록 대상은 아니다.

Step 3. Winner 확정

scratch 후보가 baseline을 이기면 먼저 repo에 정식 manifest를 만든다.

권장 순서:

  1. configs/experiments/<experiment_name>.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

그리고 tracker에 attach한다.

Step 5.5. Synthetic Scenario Test (선택, deploy 후보 권장)

합성 시장 데이터를 이용해 역사에 없던 시장 환경에서의 내성을 검증한다. 자세한 해석 가이드는 docs/scenario_test.md를 본다.

# 빠른 핵심 3개 (2~3초)
fithia2 scenario-test --config <experiment_name> --quick

# 전체 12개 시나리오 (~10초)
fithia2 scenario-test --config <experiment_name>

# 취약 환경 집중 점검
fithia2 scenario-test --config <experiment_name> --group structural

RRS 판정 기준: ≥ 70 ROBUST / 4069 FRAGILE / < 40 OVERFIT

deploy 후보 비교 시 유용한 활용:

  • 신규 전략의 sector_rotation, liquidity_drought Sharpe가 baseline보다 개선됐는지 확인
  • no_signal Sharpe > 0이면 가격 패턴 과적합 의심 신호

Step 6. Leaderboard 갱신

정식 manifest + journal + WFV/robustness attach까지 끝난 뒤에 leaderboard를 본다.

public SQS 계산 상세와 디버그 절차는 docs/public_sqs_calculation.md 를 본다.

6. 세션 종료 전 체크리스트

세션을 끝내기 전에 아래를 확인한다.

  1. 오늘 새 winner가 scratch만 있고 repo manifest가 없는가
  2. journal에 기록한 실험명이 실제 configs/experiments/<name>.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

python apps/backtester/run.py \
  --manifest configs/experiments/<experiment>.json \
  --output-root runs/<experiment>_fullsplit

Walk-Forward

python apps/backtester/run.py \
  --manifest configs/experiments/<experiment>.json \
  --walk-forward \
  --wf-train-days 504 \
  --wf-test-days 63 \
  --wf-step-days 63 \
  --output-root runs/<experiment>_wfv

Robustness Matrix

python apps/backtester/run.py \
  --manifest configs/experiments/<experiment>.json \
  --robustness-matrix \
  --rm-horizons 21,63,126,252,504 \
  --rm-step-days 21 \
  --output-root runs/<experiment>_rm

Attach Summaries

python apps/tracker/cli.py attach-wfv \
  --journal-dir journal \
  <experiment_name> \
  --summary runs/<experiment>_wfv/walk_forward/walk_forward_summary.json

python apps/tracker/cli.py attach-robustness \
  --journal-dir journal \
  <experiment_name> \
  --summary runs/<experiment>_rm/robustness_matrix/robustness_matrix_summary.json

python apps/tracker/cli.py attach-oot-robustness \
  --journal-dir journal \
  <experiment_name> \
  --summary runs/<experiment>_oot_rm/robustness_matrix/robustness_matrix_summary.json

Retired Overlay Path

book-of-books overlay 실험은 코드베이스에서 제거됐다.

  • 공식 leaderboard와 paper backtest는 이제 single-book 전략만 지원한다.
  • 과거 overlay journal entry는 재현성 기록으로만 남고, registry/leaderboard 재빌드에는 포함되지 않는다.
  • 새 연구는 configs/experiments 아래 single-book manifest 기준으로 진행한다.

8. 빠른 무결성 점검

journal entry와 manifest 대응을 확인하려면:

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/<run_id>/snapshot_manifest.json
  • runs/<run_id>/snapshot_fingerprint.json
  • 특히 snapshot_manifest.jsonfeature_version, export_enrichments, created_at_utc를 같이 본다.

핵심 필드:

  • dataset_snapshot_id
  • requested_snapshot_id
  • canonical_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일 수 있다. 이 상태에서 성능 비교를 계속하면 안 된다.

requested_snapshot_id != canonical_snapshot_id면 legacy alias가 canonical snapshot으로 resolve된 run이다. attach/leaderboard 비교는 raw id가 아니라 canonical id 기준으로 본다.

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, 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가 의미 있는 비교 지표가 됐다.

추가 원칙:

  • repaired OOT summary가 전 horizon에서 전부 0all-zero sparse no-trade 패턴이면, 기본 SQS에서는 fail로 깎지 않는다.
  • 이 경우는 bad stress performance가 아니라 stress window non-comparable로 보고, OOT gate를 중립 처리한다.
  • 대신 여전히 journal 설명과 diagnostics에는 sparse OOT라는 사실을 남긴다.

10.4 Paper Backtest stale 판정은 manifest 나이가 아니라 coverage로 본다

fithia2 paper backtest는 snapshot이 오래됐다는 이유만으로 refresh하면 안 된다. 실제로는 canonical snapshot이 충분한 날짜 범위를 이미 덮고 있을 수 있다.

현재 원칙:

  • stale 여부는 parquet event_date coverage로 판단한다
  • snapshot 경로는 먼저 canonical id로 resolve하고, canonical dir가 아직 없으면 phase-1 호환성 때문에 legacy alias dir로 fallback할 수 있다
  • refresh가 실패해도 기존 snapshot이 requested period를 덮으면 그대로 사용한다
  • fithia2 refresh <legacy_alias>는 legacy snapshot을 따로 rebuild하지 않고, resolved canonical snapshot 하나만 rebuild한다

즉 "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
  2. return_max_long_v1.52.json
  3. 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%

최근 실패한 축:

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부터 고친다.