715 lines
34 KiB
YAML
715 lines
34 KiB
YAML
# =============================================================================
|
|
# QuantEngine 데이터 실증 기반 퀀트 엔진 로드맵 + WBS (기계 판정)
|
|
# =============================================================================
|
|
# formula_id: QUANT_ENGINE_WBS_V1
|
|
# 원칙: 모든 작업(task)은 "완료 주장"이 아니라 게이트 실행으로만 DONE 판정된다.
|
|
# - BE 실증: pg_query(PostgreSQL 쿼리) + log_pattern(Serilog 로그) + json_gate(아티팩트)
|
|
# - FE 실증: playwright_report(스펙 PASS) + file_exists(스크린샷)
|
|
# 실행:
|
|
# 단일 작업 검증: python tools/verify_wbs_task_v1.py --task <TASK_ID>
|
|
# → Temp/evidence/<TASK_ID>/verdict.json + 원시 증거 보존
|
|
# 전체 WBS 게이트: python tools/validate_quant_engine_wbs_v1.py
|
|
# → Temp/quant_engine_wbs_v1.json (status=DONE 작업의 증거 재검증)
|
|
# 관례: spec/16_data_gaps_roadmap.yaml 의 success_criteria 구조
|
|
# (expected_success_value / evidence_artifacts / verification_commands) 준수.
|
|
# 검증 로직만 하드코딩 → evidence_checks 선언형으로 일반화.
|
|
# =============================================================================
|
|
|
|
meta:
|
|
formula_id: QUANT_ENGINE_WBS_V1
|
|
version: 1
|
|
created: "2026-07-12"
|
|
authority: "governance/authority_matrix.yaml"
|
|
validator: tools/validate_quant_engine_wbs_v1.py
|
|
task_verifier: tools/verify_wbs_task_v1.py
|
|
remote_evidence_collector: tools/collect_remote_wbs_evidence_v1.py
|
|
evidence_root: Temp/evidence
|
|
status_values: [PENDING, IN_PROGRESS, DONE] # DONE = 해당 verdict.json gate=PASS 필수
|
|
db_connection:
|
|
# 검증기의 PostgreSQL 접속 순서:
|
|
# 1) env QE_WBS_PG_DSN (psycopg DSN)
|
|
# 2) env ConnectionStrings__DefaultConnection (.NET 형식 → 자동 변환)
|
|
# 3) src/dotnet/QuantEngine.Web/appsettings.Development.json 의 ConnectionStrings.DefaultConnection
|
|
# (로컬은 SSH 터널 127.0.0.1:5432 전제 — CLAUDE.md "Local Development & Testing")
|
|
dotnet_appsettings: src/dotnet/QuantEngine.Web/appsettings.Development.json
|
|
remote_evidence:
|
|
collector: "python tools/collect_remote_wbs_evidence_v1.py --target <ssh-target>"
|
|
policy: "Collect journal and JSON artifacts only; never copy env files or passwords."
|
|
postgres: "Use QE_WBS_PG_DSN through an approved SSH tunnel; do not embed credentials in evidence."
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 검증 체크 타입 사전 (verify_wbs_task_v1.py 가 해석하는 선언형 vocabulary)
|
|
# -----------------------------------------------------------------------------
|
|
evidence_check_types:
|
|
pg_query: "PostgreSQL 쿼리 1개 실행, 단일 스칼라 결과를 expect{min,max,equals}와 비교. 원시 결과를 pg_query_<n>.json 으로 보존"
|
|
log_pattern: "file_glob 로그 파일들에서 정규식 매칭. expect{min_matches, max_age_hours(파일 mtime 기준)}. 매칭 라인을 log_excerpt.txt 로 보존"
|
|
json_gate: "path 의 JSON 아티팩트에서 expect 의 키-값 검사 (점 표기 경로 지원, 값 '>=N' 비교 지원)"
|
|
file_exists: "paths 의 모든 파일 존재 (expect.min_bytes 선택)"
|
|
playwright_report: "Playwright JSON 리포트(report)에서 spec_file 의 결과가 expect{passed_min, failed} 충족"
|
|
|
|
# =============================================================================
|
|
# 로드맵 (M0 → M5)
|
|
# =============================================================================
|
|
roadmap:
|
|
scope_note: >
|
|
전통 팩터(모멘텀/거래량/수급/실적/매크로/밸류/재무건전성 = spec/08_scoring_rules.yaml SS001)
|
|
+ ATR 리스크 관리 기본 포함. 최신 기법은 레짐 감지 + 워크포워드 캘리브레이션 + 거래비용
|
|
반영 평가로 한정(사용자 확정, 2026-07-12). 딥러닝/인트라데이/대체데이터/실거래 집행 제외
|
|
(은퇴자산 + read-only KIS 거버넌스: governance/rules/06_no_direct_api_trading.yaml 유지).
|
|
phases:
|
|
M0:
|
|
name: "실증 하네스 + 정직성 정리"
|
|
goal: "완료 주장이 불가능한 구조 확립 — 검증기/증거 규약/CI 편입 + 가짜 검증 제거"
|
|
exit_gate: "validate_quant_engine_wbs 가 release DAG/CI 노드로 PASS; dotnet test + Playwright evidence 스위트 CI 편입; 디버그 스펙 격리·가짜 PASS 제거"
|
|
tasks: [QE-M0-01, QE-M0-02, QE-M0-03, QE-M0-04, QE-M0-05, QE-M0-06]
|
|
M1:
|
|
name: "수집 파이프라인 배선"
|
|
goal: "운영 앱이 실제 KIS 데이터를 수집하도록 고아 오케스트레이터 배선 (첫 실데이터 실증)"
|
|
exit_gate: "Hangfire daily-collection + POST /api/collection/run 으로 kis_collection_* 에 실데이터 적재, Admin Collection 페이지 Playwright 실증"
|
|
tasks: [QE-M1-01, QE-M1-02, QE-M1-03, QE-M1-04, QE-M1-05]
|
|
M2:
|
|
name: "히스토리 시계열 저장소"
|
|
goal: "모멘텀 팩터·백테스트의 전제인 일봉/매크로 시계열 축적 (2년 백필)"
|
|
exit_gate: "price_history_daily/macro_history_daily 에 유니버스 2년치; (ticker,date) 중복 0; 거래일 캘린더 대비 gap 0"
|
|
tasks: [QE-M2-01, QE-M2-02, QE-M2-03, QE-M2-04, QE-M2-05]
|
|
M3:
|
|
name: "실데이터 팩터 계산"
|
|
goal: "SS001 전통 팩터를 PG 히스토리에서 계산해 engine_history 에 적재, 파일 개수 골든커버리지를 수치 패리티로 대체"
|
|
exit_gate: "factor_output_history 에 유니버스 전체 스코어(0-100); Python 참조 대비 패리티 ≥20 formula tol 1e-9 PASS"
|
|
tasks: [QE-M3-01, QE-M3-02, QE-M3-03, QE-M3-04, QE-M3-05]
|
|
M4:
|
|
name: "백테스팅 + 검증"
|
|
goal: "point-in-time 데이터만 사용하는 워크포워드 백테스터 + 거래비용 모델 + no-lookahead 게이트 실배선"
|
|
exit_gate: "Sharpe/MDD/턴오버 JSON 산출; no-lookahead 정상 PASS + 오염 픽스처 FAIL 양방향; T+5/T+20 원장 표본 ≥30"
|
|
tasks: [QE-M4-01, QE-M4-02, QE-M4-03, QE-M4-04, QE-M4-05]
|
|
M5:
|
|
name: "포트폴리오 구성 + 최신 기법"
|
|
goal: "레짐 감지 + SS001 가중치 워크포워드 캘리브레이션(제약+shrinkage) + 변동성 타게팅 사이징"
|
|
exit_gate: "백필 전 기간 레짐 라벨; 캘리브레이션 가중치 제약 준수 + OOS Sharpe 정직 보고; 최종 포트폴리오 패킷 캡 준수"
|
|
tasks: [QE-M5-01, QE-M5-02, QE-M5-03, QE-M5-04]
|
|
|
|
# =============================================================================
|
|
# WBS 작업 목록
|
|
# =============================================================================
|
|
tasks:
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# M0 — 실증 하네스 + 정직성 정리
|
|
# ---------------------------------------------------------------------------
|
|
QE-M0-01:
|
|
title: "WBS 스펙(YAML) + 로드맵 작성, 레거시 로드맵 문서에 포인터 추가"
|
|
status: DONE
|
|
depends_on: []
|
|
owner_files:
|
|
- spec/60_quant_engine_wbs.yaml
|
|
- docs/ROADMAP_WBS.md
|
|
success_criteria:
|
|
expected_success_value: { spec_exists: true, legacy_pointer_appended: true }
|
|
evidence_artifacts: [Temp/evidence/QE-M0-01/verdict.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M0-01"]
|
|
evidence_checks:
|
|
- type: file_exists
|
|
paths: [spec/60_quant_engine_wbs.yaml]
|
|
expect: { min_bytes: 10000 }
|
|
- type: log_pattern
|
|
file_glob: docs/ROADMAP_WBS.md
|
|
pattern: "QUANT_ENGINE_WBS_V1"
|
|
expect: { min_matches: 1 }
|
|
|
|
QE-M0-02:
|
|
title: "증거 검증기 2종 구현 (단일 작업 verifier + 전체 WBS validator) + 유닛테스트"
|
|
status: DONE
|
|
depends_on: []
|
|
owner_files:
|
|
- tools/verify_wbs_task_v1.py
|
|
- tools/validate_quant_engine_wbs_v1.py
|
|
- tests/unit/test_validate_quant_engine_wbs_v1.py
|
|
success_criteria:
|
|
expected_success_value: { self_test: PASS, synthetic_pass_fail_bidirectional: true }
|
|
evidence_artifacts: [Temp/evidence/QE-M0-02/verdict.json, Temp/quant_engine_wbs_v1.json]
|
|
verification_commands:
|
|
- "python -m pytest tests/unit/test_validate_quant_engine_wbs_v1.py -q"
|
|
- "python tools/verify_wbs_task_v1.py --task QE-M0-02"
|
|
evidence_checks:
|
|
- type: file_exists
|
|
paths:
|
|
- tools/verify_wbs_task_v1.py
|
|
- tools/validate_quant_engine_wbs_v1.py
|
|
- tests/unit/test_validate_quant_engine_wbs_v1.py
|
|
- type: json_gate
|
|
path: Temp/quant_engine_wbs_v1.json
|
|
expect: { formula_id: QUANT_ENGINE_WBS_V1, gate: PASS }
|
|
|
|
QE-M0-03:
|
|
title: "Playwright 정직성 정리 + evidence 프로젝트 + npm 스크립트"
|
|
status: DONE
|
|
depends_on: [QE-M0-02]
|
|
owner_files:
|
|
- playwright.config.ts
|
|
- package.json
|
|
- tests/e2e/archive/
|
|
notes: >
|
|
디버그 스펙(~17개: debug-login, html-debug, wasm-test, framework-check, console-check,
|
|
screenshot-diagnosis, inspect-page, login* 변형 등)을 tests/e2e/archive/ 로 이동하고
|
|
testIgnore 로 제외. full-validation.spec.ts 의 assert 없는 가짜 "[PASS]" 배너 테스트
|
|
제거(파일째 archive). evidence 프로젝트: testDir tests/e2e/evidence, screenshot 'on',
|
|
trace 'on', JSON reporter → Temp/evidence/playwright-last-run.json.
|
|
npm 스크립트: verify:task / verify:wbs / test:e2e / test:evidence
|
|
success_criteria:
|
|
expected_success_value: { default_project_specs: ["admin-pages.spec.ts"], fake_pass_removed: true }
|
|
evidence_artifacts: [Temp/evidence/QE-M0-03/verdict.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M0-03"]
|
|
evidence_checks:
|
|
- type: file_exists
|
|
paths: [tests/e2e/archive]
|
|
- type: log_pattern
|
|
file_glob: playwright.config.ts
|
|
pattern: "evidence"
|
|
expect: { min_matches: 1 }
|
|
- type: log_pattern
|
|
file_glob: package.json
|
|
pattern: "verify:task"
|
|
expect: { min_matches: 1 }
|
|
- type: log_pattern
|
|
file_glob: tests/e2e/full-validation.spec.ts
|
|
pattern: ".*"
|
|
expect: { max_matches: 0 } # 파일이 기본 testDir 에 더 이상 존재하지 않아야 함
|
|
|
|
QE-M0-04:
|
|
title: "CI에 dotnet test 편입 + 고아 QuantEngine.Web.Tests 처리"
|
|
status: DONE
|
|
depends_on: []
|
|
owner_files:
|
|
- .gitea/workflows/ci.yml
|
|
- src/dotnet/QuantEngine.Web.Tests/
|
|
notes: >
|
|
QuantEngine.Web.Tests/DashboardComponentTests.cs 는 csproj 없는 고아(폐기된 Blazor 대상).
|
|
현 Razor Pages UI 에 맞지 않으면 삭제. ci.yml 에 dotnet test 스텝 추가.
|
|
success_criteria:
|
|
expected_success_value: { ci_has_dotnet_test: true, core_tests_green: true }
|
|
evidence_artifacts: [Temp/evidence/QE-M0-04/verdict.json]
|
|
verification_commands:
|
|
- "dotnet test src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj -c Release --nologo"
|
|
- "python tools/verify_wbs_task_v1.py --task QE-M0-04"
|
|
evidence_checks:
|
|
- type: log_pattern
|
|
file_glob: .gitea/workflows/ci.yml
|
|
pattern: "dotnet test"
|
|
expect: { min_matches: 1 }
|
|
|
|
QE-M0-05:
|
|
title: "release DAG + CI 에 validate_quant_engine_wbs 게이트 노드 등록"
|
|
status: DONE
|
|
depends_on: [QE-M0-02]
|
|
owner_files:
|
|
- spec/41_release_dag.yaml
|
|
- .gitea/workflows/ci.yml
|
|
success_criteria:
|
|
expected_success_value: { dag_node: validate_quant_engine_wbs, ci_step: true }
|
|
evidence_artifacts: [Temp/evidence/QE-M0-05/verdict.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M0-05"]
|
|
evidence_checks:
|
|
- type: log_pattern
|
|
file_glob: spec/41_release_dag.yaml
|
|
pattern: "validate_quant_engine_wbs"
|
|
expect: { min_matches: 1 }
|
|
- type: log_pattern
|
|
file_glob: .gitea/workflows/ci.yml
|
|
pattern: "validate_quant_engine_wbs_v1"
|
|
expect: { min_matches: 1 }
|
|
|
|
QE-M0-06:
|
|
title: "골든커버리지 정직성 표기 (coverage_basis: FILE_COUNT_ONLY)"
|
|
status: DONE
|
|
depends_on: []
|
|
owner_files:
|
|
- tools/validate_golden_coverage_100.py
|
|
notes: >
|
|
골든 테스트 174개는 실행되지 않는 placeholder. 커버리지 판정 출력에
|
|
coverage_basis: FILE_COUNT_ONLY 필드를 추가해 실체를 명시(삭제는 M3 패리티 대체 후).
|
|
success_criteria:
|
|
expected_success_value: { honesty_field: FILE_COUNT_ONLY }
|
|
evidence_artifacts: [Temp/evidence/QE-M0-06/verdict.json]
|
|
verification_commands:
|
|
- "python tools/validate_golden_coverage_100.py"
|
|
- "python tools/verify_wbs_task_v1.py --task QE-M0-06"
|
|
evidence_checks:
|
|
- type: json_gate
|
|
path: Temp/golden_coverage_100_v1.json
|
|
expect: { coverage_basis: FILE_COUNT_ONLY }
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# M1 — 수집 파이프라인 배선 (첫 실데이터 실증)
|
|
# ---------------------------------------------------------------------------
|
|
QE-M1-01:
|
|
title: "KisDataCollectionOrchestrator DI 등록 + daily-collection Hangfire 잡 실구현"
|
|
status: PENDING
|
|
depends_on: [QE-M0-02]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Web/Program.cs
|
|
- src/dotnet/QuantEngine.Web/Services/SchedulerService.cs
|
|
notes: >
|
|
Program.cs 에 PriceDataNormalizer / SourcePriorityResolver / ICollectionOrchestrator →
|
|
KisDataCollectionOrchestrator 등록. RunDailyCollectionAsync 의 Task.Delay 시뮬레이션을
|
|
IServiceScopeFactory 스코프 → 오케스트레이터 호출로 교체 (runId "daily-yyyyMMdd-HHmmss").
|
|
완료 로그: "Collection run {RunId} completed: {Snapshots} snapshots, {Errors} errors".
|
|
상태값은 대문자 COMPLETED / COMPLETED_WITH_ERRORS (KisDataCollectionOrchestrator.cs:103).
|
|
success_criteria:
|
|
expected_success_value: { runs_completed_min: 1, snapshots_min: 5, hangfire_job: daily-collection }
|
|
evidence_artifacts: [Temp/evidence/QE-M1-01/verdict.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M1-01"]
|
|
evidence_checks:
|
|
- type: pg_query
|
|
sql: >
|
|
SELECT count(*) FROM quantengine.kis_collection_runs
|
|
WHERE status LIKE 'COMPLETED%' AND total_snapshots >= 5
|
|
AND started_at >= (now() - interval '24 hours')::text
|
|
expect: { min: 1 }
|
|
- type: pg_query
|
|
sql: >
|
|
SELECT count(DISTINCT s.ticker) FROM quantengine.kis_collection_snapshots s
|
|
JOIN quantengine.kis_collection_runs r ON r.run_id = s.run_id
|
|
WHERE r.started_at >= (now() - interval '24 hours')::text
|
|
expect: { min: 5 }
|
|
- type: log_pattern
|
|
file_glob: src/dotnet/QuantEngine.Web/logs/quantengine-*.log
|
|
pattern: 'Collection run .+ completed: \d+ snapshots'
|
|
expect: { min_matches: 1, max_age_hours: 24 }
|
|
- type: json_gate
|
|
path: Temp/kis_dotnet_collection_v1.json
|
|
expect: { gate: PASS }
|
|
|
|
QE-M1-02:
|
|
title: "Admin Collection 페이지 FE 실증 (실제 run 렌더링을 Playwright 로 증명)"
|
|
status: PENDING
|
|
depends_on: [QE-M1-01, QE-M0-03]
|
|
owner_files:
|
|
- tests/e2e/evidence/qe-m1-02-collection-run.spec.ts
|
|
notes: >
|
|
필수 3요소: (a) 기대값을 /api/collection/runs API 에서 조회(하드코딩 금지),
|
|
(b) /Admin/Collection DOM 에서 run_id·스냅샷 수·상태 배지를 기대값과 assert,
|
|
(c) assert 시점 스크린샷 → Temp/evidence/QE-M1-02/screenshots/{01-collection-page,02-run-detail}.png
|
|
success_criteria:
|
|
expected_success_value: { spec_passed: 1, screenshots: 2, dom_equals_api: true }
|
|
evidence_artifacts: [Temp/evidence/QE-M1-02/verdict.json]
|
|
verification_commands:
|
|
- "npx playwright test --project=evidence tests/e2e/evidence/qe-m1-02-collection-run.spec.ts"
|
|
- "python tools/verify_wbs_task_v1.py --task QE-M1-02"
|
|
evidence_checks:
|
|
- type: playwright_report
|
|
report: Temp/evidence/playwright-last-run.json
|
|
spec_file: qe-m1-02-collection-run.spec.ts
|
|
expect: { passed_min: 1, failed: 0 }
|
|
- type: file_exists
|
|
paths:
|
|
- Temp/evidence/QE-M1-02/screenshots/01-collection-page.png
|
|
- Temp/evidence/QE-M1-02/screenshots/02-run-detail.png
|
|
expect: { min_bytes: 10000 }
|
|
|
|
QE-M1-03:
|
|
title: "POST /api/collection/run 실구현 (BackgroundJob.Enqueue + 인증 필수화)"
|
|
status: PENDING
|
|
depends_on: [QE-M1-01]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Web/Endpoints/CollectionEndpoints.cs
|
|
notes: >
|
|
202 no-op 스텁을 Hangfire BackgroundJob.Enqueue(오케스트레이터 실행)로 교체,
|
|
응답에 {runId} 포함. AllowAnonymous 제거(쿠키 인증).
|
|
로그: "Collection run {RunId} enqueued".
|
|
success_criteria:
|
|
expected_success_value: { returns_run_id: true, auth_required: true }
|
|
evidence_artifacts: [Temp/evidence/QE-M1-03/verdict.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M1-03"]
|
|
evidence_checks:
|
|
- type: log_pattern
|
|
file_glob: src/dotnet/QuantEngine.Web/logs/quantengine-*.log
|
|
pattern: 'Collection run .+ enqueued'
|
|
expect: { min_matches: 1, max_age_hours: 24 }
|
|
- type: pg_query
|
|
sql: >
|
|
SELECT count(*) FROM quantengine.kis_collection_runs
|
|
WHERE run_id LIKE 'api-%' AND started_at >= (now() - interval '24 hours')::text
|
|
expect: { min: 1 }
|
|
|
|
QE-M1-04:
|
|
title: "오케스트레이터 로깅 복원 + 출력 아티팩트 표준화 + 멀티소스 폴백 배선"
|
|
status: PENDING
|
|
depends_on: [QE-M1-01]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs
|
|
notes: >
|
|
"// Log: skipped" → ILogger<KisDataCollectionOrchestrator> 복원.
|
|
출력 경로 Path.GetTempPath() → <repo>/Temp/kis_dotnet_collection_v1.json,
|
|
형식 {formula_id: KIS_DOTNET_COLLECTION_V1, gate, summary{success_count, error_count, source_counts}}.
|
|
SourcePriorityResolver 를 통해 Naver/Yahoo 폴백 경로 활성화.
|
|
success_criteria:
|
|
expected_success_value: { gate: PASS, source_counts_min: 1, error_rows_on_bad_ticker: true }
|
|
evidence_artifacts: [Temp/evidence/QE-M1-04/verdict.json, Temp/kis_dotnet_collection_v1.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M1-04"]
|
|
evidence_checks:
|
|
- type: json_gate
|
|
path: Temp/kis_dotnet_collection_v1.json
|
|
expect: { formula_id: KIS_DOTNET_COLLECTION_V1, gate: PASS }
|
|
- type: log_pattern
|
|
file_glob: src/dotnet/QuantEngine.Web/logs/quantengine-*.log
|
|
pattern: 'Collecting ticker'
|
|
expect: { min_matches: 1, max_age_hours: 24 }
|
|
|
|
QE-M1-05:
|
|
title: "티커 유니버스를 GatherTradingData 파서/DB 설정에서 로드 (하드코딩 제거)"
|
|
status: PENDING
|
|
depends_on: [QE-M1-01]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Web/Services/SchedulerService.cs
|
|
success_criteria:
|
|
expected_success_value: { distinct_tickers_equals_universe: true }
|
|
evidence_artifacts: [Temp/evidence/QE-M1-05/verdict.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M1-05"]
|
|
evidence_checks:
|
|
- type: log_pattern
|
|
file_glob: src/dotnet/QuantEngine.Web/Services/SchedulerService.cs
|
|
pattern: '005930.+000660.+051910'
|
|
expect: { max_matches: 0 } # 하드코딩 티커 배열 부재
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# M2 — 히스토리 시계열 저장소
|
|
# ---------------------------------------------------------------------------
|
|
QE-M2-01:
|
|
title: "V6 마이그레이션: price_history_daily + macro_history_daily"
|
|
status: PENDING
|
|
depends_on: [QE-M1-01]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Infrastructure/Migrations/V6__Add_Market_Time_Series.sql
|
|
notes: >
|
|
price_history_daily(ticker, trade_date, open/high/low/close numeric, volume bigint,
|
|
source text, collected_at timestamptz, PK(ticker, trade_date));
|
|
macro_history_daily(symbol, trade_date, value numeric, source, PK(symbol, trade_date)).
|
|
DbUp 마이그레이션 추가 시 docs/db/quantengine.dbml 동기화 필수 (CLAUDE.md 규칙 — 아래 체크로 강제).
|
|
success_criteria:
|
|
expected_success_value: { tables_created: 2 }
|
|
evidence_artifacts: [Temp/evidence/QE-M2-01/verdict.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M2-01"]
|
|
evidence_checks:
|
|
- type: pg_query
|
|
sql: >
|
|
SELECT count(*) FROM information_schema.tables
|
|
WHERE table_schema='quantengine' AND table_name IN ('price_history_daily','macro_history_daily')
|
|
expect: { equals: 2 }
|
|
- type: log_pattern
|
|
file_glob: docs/db/quantengine.dbml
|
|
pattern: 'price_history_daily'
|
|
expect: { min_matches: 1 } # DBML 동기화 강제
|
|
|
|
QE-M2-02:
|
|
title: "일봉 OHLCV 시계열 적재 (daily run 마다 upsert, 재실행 중복 0)"
|
|
status: PENDING
|
|
depends_on: [QE-M2-01]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs
|
|
success_criteria:
|
|
expected_success_value: { rows_per_ticker_min: 1, duplicate_on_rerun: 0 }
|
|
evidence_artifacts: [Temp/evidence/QE-M2-02/verdict.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M2-02"]
|
|
evidence_checks:
|
|
- type: pg_query
|
|
sql: "SELECT count(*) FROM quantengine.price_history_daily WHERE collected_at >= now() - interval '24 hours'"
|
|
expect: { min: 1 }
|
|
- type: pg_query
|
|
sql: >
|
|
SELECT count(*) FROM (SELECT ticker, trade_date, count(*) c
|
|
FROM quantengine.price_history_daily GROUP BY 1,2 HAVING count(*) > 1) d
|
|
expect: { equals: 0 }
|
|
|
|
QE-M2-03:
|
|
title: "2년치 백필 툴 (KIS chart API 페이지네이션 + rate-limit, 매크로는 yfinance→PG)"
|
|
status: PENDING
|
|
depends_on: [QE-M2-01]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Tools/
|
|
- src/quant_engine/macro_index_collection_v1.py
|
|
success_criteria:
|
|
expected_success_value: { bars_per_ticker_min: 480, macro_bars_min: 480 }
|
|
evidence_artifacts: [Temp/evidence/QE-M2-03/verdict.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M2-03"]
|
|
evidence_checks:
|
|
- type: pg_query
|
|
sql: "SELECT coalesce(min(c),0) FROM (SELECT count(*) c FROM quantengine.price_history_daily GROUP BY ticker) t"
|
|
expect: { min: 480 }
|
|
- type: pg_query
|
|
sql: "SELECT count(*) FROM quantengine.macro_history_daily WHERE symbol IN ('KOSPI','KOSDAQ')"
|
|
expect: { min: 960 }
|
|
|
|
QE-M2-04:
|
|
title: "시계열 무결성 게이트 (거래일 캘린더 대비 gap 0, 가격 sanity)"
|
|
status: PENDING
|
|
depends_on: [QE-M2-03]
|
|
owner_files:
|
|
- tools/validate_price_history_integrity_v1.py
|
|
success_criteria:
|
|
expected_success_value: { gap_count: 0, invalid_price_rows: 0 }
|
|
evidence_artifacts: [Temp/evidence/QE-M2-04/verdict.json, Temp/price_history_integrity_v1.json]
|
|
verification_commands:
|
|
- "python tools/validate_price_history_integrity_v1.py"
|
|
- "python tools/verify_wbs_task_v1.py --task QE-M2-04"
|
|
evidence_checks:
|
|
- type: json_gate
|
|
path: Temp/price_history_integrity_v1.json
|
|
expect: { gate: PASS, gap_count: 0 }
|
|
|
|
QE-M2-05:
|
|
title: "히스토리 현황 FE (per-ticker bar 수/기간/gap — API 값과 DOM 대조)"
|
|
status: PENDING
|
|
depends_on: [QE-M2-03, QE-M0-03]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Web/Pages/Admin/Collection/
|
|
- tests/e2e/evidence/qe-m2-05-history-tab.spec.ts
|
|
success_criteria:
|
|
expected_success_value: { spec_passed: 1, screenshots_min: 1 }
|
|
evidence_artifacts: [Temp/evidence/QE-M2-05/verdict.json]
|
|
verification_commands:
|
|
- "npx playwright test --project=evidence tests/e2e/evidence/qe-m2-05-history-tab.spec.ts"
|
|
- "python tools/verify_wbs_task_v1.py --task QE-M2-05"
|
|
evidence_checks:
|
|
- type: playwright_report
|
|
report: Temp/evidence/playwright-last-run.json
|
|
spec_file: qe-m2-05-history-tab.spec.ts
|
|
expect: { passed_min: 1, failed: 0 }
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# M3 — 실데이터 팩터 계산
|
|
# ---------------------------------------------------------------------------
|
|
QE-M3-01:
|
|
title: "Point-in-time 리더 (GetBarsAsOf — lookahead 구조적 차단 + xUnit 증명)"
|
|
status: PENDING
|
|
depends_on: [QE-M2-03]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Infrastructure/Repositories/
|
|
- src/dotnet/QuantEngine.Core.Tests/
|
|
success_criteria:
|
|
expected_success_value: { asof_leak_tests_green: true }
|
|
evidence_artifacts: [Temp/evidence/QE-M3-01/verdict.json]
|
|
verification_commands:
|
|
- "dotnet test src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj -c Release --filter PriceHistoryReader"
|
|
- "python tools/verify_wbs_task_v1.py --task QE-M3-01"
|
|
evidence_checks:
|
|
- type: log_pattern
|
|
file_glob: src/dotnet/QuantEngine.Core.Tests/**/*.cs
|
|
pattern: 'GetBarsAsOf'
|
|
expect: { min_matches: 1 }
|
|
|
|
QE-M3-02:
|
|
title: "전통 팩터 계산기 (모멘텀 20/60/120d·RS, 저변동성 ATR%·stdev·beta, 밸류/퀄리티)"
|
|
status: PENDING
|
|
depends_on: [QE-M3-01]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Core/Domain/
|
|
- tools/validate_factor_parity_v1.py
|
|
success_criteria:
|
|
expected_success_value: { parity_formulas_min: 20, tolerance: 1e-9 }
|
|
evidence_artifacts: [Temp/evidence/QE-M3-02/verdict.json, Temp/factor_parity_v1.json]
|
|
verification_commands:
|
|
- "python tools/validate_factor_parity_v1.py"
|
|
- "python tools/verify_wbs_task_v1.py --task QE-M3-02"
|
|
evidence_checks:
|
|
- type: json_gate
|
|
path: Temp/factor_parity_v1.json
|
|
expect: { gate: PASS, compared_count: ">=20" }
|
|
|
|
QE-M3-03:
|
|
title: "SS001 합성 스코어 + HF001-09 → engine_history.factor_output_history 적재"
|
|
status: PENDING
|
|
depends_on: [QE-M3-02]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Application/Services/
|
|
success_criteria:
|
|
expected_success_value: { scored_universe_full: true, score_range_0_100: true }
|
|
evidence_artifacts: [Temp/evidence/QE-M3-03/verdict.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M3-03"]
|
|
evidence_checks:
|
|
- type: pg_query
|
|
sql: "SELECT count(*) FROM engine_history.factor_output_history WHERE created_at >= now() - interval '24 hours'"
|
|
expect: { min: 5 }
|
|
|
|
QE-M3-04:
|
|
title: "PipelineOrchestrator 정직화 (1-2단계 실구현, 나머지 STUBBED 표기 — mock PASS 금지)"
|
|
status: PENDING
|
|
depends_on: [QE-M3-03]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Application/Services/PipelineOrchestrator.cs
|
|
success_criteria:
|
|
expected_success_value: { computed_steps_min: 2, stub_steps_marked: STUBBED }
|
|
evidence_artifacts: [Temp/evidence/QE-M3-04/verdict.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M3-04"]
|
|
evidence_checks:
|
|
- type: log_pattern
|
|
file_glob: src/dotnet/QuantEngine.Application/Services/PipelineOrchestrator.cs
|
|
pattern: 'STUBBED'
|
|
expect: { min_matches: 1 }
|
|
|
|
QE-M3-05:
|
|
title: "스코어 FE (SS001 테이블 — factor_output_history 값과 DOM 대조)"
|
|
status: PENDING
|
|
depends_on: [QE-M3-03, QE-M0-03]
|
|
owner_files:
|
|
- tests/e2e/evidence/qe-m3-05-scores.spec.ts
|
|
success_criteria:
|
|
expected_success_value: { spec_passed: 1 }
|
|
evidence_artifacts: [Temp/evidence/QE-M3-05/verdict.json]
|
|
verification_commands:
|
|
- "npx playwright test --project=evidence tests/e2e/evidence/qe-m3-05-scores.spec.ts"
|
|
- "python tools/verify_wbs_task_v1.py --task QE-M3-05"
|
|
evidence_checks:
|
|
- type: playwright_report
|
|
report: Temp/evidence/playwright-last-run.json
|
|
spec_file: qe-m3-05-scores.spec.ts
|
|
expect: { passed_min: 1, failed: 0 }
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# M4 — 백테스팅 + 검증
|
|
# ---------------------------------------------------------------------------
|
|
QE-M4-01:
|
|
title: "백테스터 + 거래비용 모델 (Sharpe/MDD/턴오버/비용 드래그 JSON)"
|
|
status: PENDING
|
|
depends_on: [QE-M3-03]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Core/Domain/Backtester.cs
|
|
- src/dotnet/QuantEngine.Tools/
|
|
success_criteria:
|
|
expected_success_value: { metrics_populated: [sharpe, mdd, turnover, cost_drag] }
|
|
evidence_artifacts: [Temp/evidence/QE-M4-01/verdict.json, Temp/backtest_result_v1.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M4-01"]
|
|
evidence_checks:
|
|
- type: json_gate
|
|
path: Temp/backtest_result_v1.json
|
|
expect: { gate: PASS }
|
|
|
|
QE-M4-02:
|
|
title: "no-lookahead 게이트 실배선 (정상 PASS + 오염 픽스처 FAIL 양방향 검증)"
|
|
status: PENDING
|
|
depends_on: [QE-M4-01]
|
|
owner_files:
|
|
- tools/validate_no_lookahead_bias_v1.py
|
|
success_criteria:
|
|
expected_success_value: { real_run: PASS, corrupted_fixture: FAIL }
|
|
evidence_artifacts: [Temp/evidence/QE-M4-02/verdict.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M4-02"]
|
|
evidence_checks:
|
|
- type: json_gate
|
|
path: Temp/no_lookahead_bias_v1.json
|
|
expect: { gate: PASS }
|
|
|
|
QE-M4-03:
|
|
title: "워크포워드 하네스 (24m train / 6m test 롤링, 윈도우 ≥4)"
|
|
status: PENDING
|
|
depends_on: [QE-M4-01]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Tools/
|
|
success_criteria:
|
|
expected_success_value: { windows_min: 4, oos_metrics_nonnull: true }
|
|
evidence_artifacts: [Temp/evidence/QE-M4-03/verdict.json, Temp/walk_forward_v1.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M4-03"]
|
|
evidence_checks:
|
|
- type: json_gate
|
|
path: Temp/walk_forward_v1.json
|
|
expect: { gate: PASS, windows: ">=4" }
|
|
|
|
QE-M4-04:
|
|
title: "T+5/T+20 성과 원장 (prediction_accuracy 실표본 재계산, t5_sample≥30)"
|
|
status: PENDING
|
|
depends_on: [QE-M2-03]
|
|
owner_files:
|
|
- tools/
|
|
success_criteria:
|
|
expected_success_value: { t5_sample_min: 30 }
|
|
evidence_artifacts: [Temp/evidence/QE-M4-04/verdict.json, Temp/prediction_accuracy_harness_v2.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M4-04"]
|
|
evidence_checks:
|
|
- type: json_gate
|
|
path: Temp/prediction_accuracy_harness_v2.json
|
|
expect: { t5_sample: ">=30" }
|
|
|
|
QE-M4-05:
|
|
title: "백테스트 결과 FE (에쿼티커브/Sharpe/MDD — backtest_result_v1.json 값과 DOM 대조)"
|
|
status: PENDING
|
|
depends_on: [QE-M4-01, QE-M0-03]
|
|
owner_files:
|
|
- tests/e2e/evidence/qe-m4-05-backtest.spec.ts
|
|
success_criteria:
|
|
expected_success_value: { spec_passed: 1 }
|
|
evidence_artifacts: [Temp/evidence/QE-M4-05/verdict.json]
|
|
verification_commands:
|
|
- "npx playwright test --project=evidence tests/e2e/evidence/qe-m4-05-backtest.spec.ts"
|
|
- "python tools/verify_wbs_task_v1.py --task QE-M4-05"
|
|
evidence_checks:
|
|
- type: playwright_report
|
|
report: Temp/evidence/playwright-last-run.json
|
|
spec_file: qe-m4-05-backtest.spec.ts
|
|
expect: { passed_min: 1, failed: 0 }
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# M5 — 포트폴리오 구성 + 최신 기법
|
|
# ---------------------------------------------------------------------------
|
|
QE-M5-01:
|
|
title: "레짐 감지기 (spec/11_market_regime.yaml — 실제 매크로 시계열, 전 거래일 라벨)"
|
|
status: PENDING
|
|
depends_on: [QE-M2-03]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Core/Domain/
|
|
success_criteria:
|
|
expected_success_value: { regime_labels_full_window: true }
|
|
evidence_artifacts: [Temp/evidence/QE-M5-01/verdict.json, Temp/market_regime_v1.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M5-01"]
|
|
evidence_checks:
|
|
- type: json_gate
|
|
path: Temp/market_regime_v1.json
|
|
expect: { gate: PASS }
|
|
|
|
QE-M5-02:
|
|
title: "SS001 가중치 워크포워드 캘리브레이션 (±50% 제약 + shrinkage λ=0.5, 정직 보고)"
|
|
status: PENDING
|
|
depends_on: [QE-M4-03, QE-M5-01]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Tools/
|
|
notes: "게이트는 방법론 필드(제약 준수, OOS 비교 존재)를 검증 — 캘리브레이션이 '이겨야' PASS 가 아님"
|
|
success_criteria:
|
|
expected_success_value: { weights_within_bounds: true, oos_comparison_reported: true }
|
|
evidence_artifacts: [Temp/evidence/QE-M5-02/verdict.json, Temp/weight_calibration_v1.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M5-02"]
|
|
evidence_checks:
|
|
- type: json_gate
|
|
path: Temp/weight_calibration_v1.json
|
|
expect: { gate: PASS }
|
|
|
|
QE-M5-03:
|
|
title: "변동성 타게팅 사이징 + heat/집중도 캡 합성 → 최종 목표 포트폴리오 패킷"
|
|
status: PENDING
|
|
depends_on: [QE-M5-02]
|
|
owner_files:
|
|
- src/dotnet/QuantEngine.Application/Services/
|
|
success_criteria:
|
|
expected_success_value: { weights_sum_lte_100: true, all_caps_satisfied: true }
|
|
evidence_artifacts: [Temp/evidence/QE-M5-03/verdict.json, Temp/target_portfolio_v1.json]
|
|
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M5-03"]
|
|
evidence_checks:
|
|
- type: json_gate
|
|
path: Temp/target_portfolio_v1.json
|
|
expect: { gate: PASS }
|
|
|
|
QE-M5-04:
|
|
title: "포트폴리오·레짐 대시보드 FE (레짐 배지·목표 가중치 — API 값과 DOM 대조)"
|
|
status: PENDING
|
|
depends_on: [QE-M5-03, QE-M0-03]
|
|
owner_files:
|
|
- tests/e2e/evidence/qe-m5-04-portfolio.spec.ts
|
|
success_criteria:
|
|
expected_success_value: { spec_passed: 1 }
|
|
evidence_artifacts: [Temp/evidence/QE-M5-04/verdict.json]
|
|
verification_commands:
|
|
- "npx playwright test --project=evidence tests/e2e/evidence/qe-m5-04-portfolio.spec.ts"
|
|
- "python tools/verify_wbs_task_v1.py --task QE-M5-04"
|
|
evidence_checks:
|
|
- type: playwright_report
|
|
report: Temp/evidence/playwright-last-run.json
|
|
spec_file: qe-m5-04-portfolio.spec.ts
|
|
expect: { passed_min: 1, failed: 0 }
|