1295 lines
57 KiB
YAML
1295 lines
57 KiB
YAML
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
|
||
supplementary_roadmaps:
|
||
- docs/WBS_10_DOTNET_MIGRATION_ROADMAP.yaml
|
||
evidence_root: Temp/evidence
|
||
status_values:
|
||
- PENDING
|
||
- IN_PROGRESS
|
||
- DONE
|
||
db_connection:
|
||
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.
|
||
execution_convention: '각 작업의 execution.haiku_prompt 필드는 Agent(model: haiku)에 그대로
|
||
전달 가능한 자기완결적 지시문이다(파일 경로, 정확한 수정 내용, acceptance 커맨드 포함). 조정자는 haiku 결과 diff를 반드시
|
||
리뷰한 뒤 verify_wbs_task_v1.py 게이트 PASS 시에만 status: DONE 으로 갱신한다. execution.mode:
|
||
manual_user_action 작업(예: QE-M1-07)은 Gitea Actions workflow_dispatch 등 에이전트가 트리거할
|
||
수 없는 행위이므로 haiku_prompt 없이 instructions 필드만 갖는다. haiku_prompt는 실행 직전 작업에만 채우며
|
||
미리 전부 작성하지 않는다(과설계 방지).
|
||
|
||
'
|
||
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} 충족
|
||
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
|
||
- QE-M0-07
|
||
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
|
||
- QE-M1-06
|
||
- QE-M1-07
|
||
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
|
||
- QE-M2-06
|
||
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
|
||
tasks:
|
||
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: log_pattern
|
||
file_glob: tools/validate_quant_engine_wbs_v1.py
|
||
pattern: def main
|
||
expect:
|
||
min_matches: 1
|
||
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
|
||
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
|
||
QE-M0-07:
|
||
title: schemas/generated + models/generated 중복 스키마 레이어 폐기
|
||
status: DONE
|
||
depends_on: []
|
||
owner_files:
|
||
- schemas/generated/
|
||
- src/quant_engine/models/generated/
|
||
- .gitea/workflows/ci.yml
|
||
- spec/41_release_dag.yaml
|
||
- docs/ROADMAP_WBS.md
|
||
notes: '비판적 재검토(2026-07-12)에서 발견: schemas/generated/(174) + models/generated/(347)가
|
||
기존 runtime/python/core/formulas/generated/(172 stub)와 동일 목적을 범용 wrapper로 중복
|
||
구현, validate_schema_model_generation_v1.py는 파일 개수만 세는 가짜 게이트였음 (M0가 확립한 "가짜
|
||
게이트 금지" 원칙의 재발 사례). 삭제하고 ci.yml/release DAG의 관련 스텝·노드(build_schema_models, validate_schema_model)
|
||
제거. schemas/generated/gas_adapter_contract.schema.json 은 별개 목적(GAS 어댑터 계약)이라
|
||
보존.
|
||
|
||
'
|
||
success_criteria:
|
||
expected_success_value:
|
||
duplicate_layer_removed: true
|
||
gas_adapter_schema_preserved: true
|
||
evidence_artifacts:
|
||
- Temp/evidence/QE-M0-07/verdict.json
|
||
verification_commands:
|
||
- python tools/verify_wbs_task_v1.py --task QE-M0-07
|
||
evidence_checks:
|
||
- type: file_exists
|
||
paths:
|
||
- schemas/generated/gas_adapter_contract.schema.json
|
||
- type: log_pattern
|
||
file_glob: .gitea/workflows/ci.yml
|
||
pattern: validate_schema_model_generation_v1|generate_schema_model_generation_evidence_v1
|
||
expect:
|
||
max_matches: 0
|
||
- type: log_pattern
|
||
file_glob: spec/41_release_dag.yaml
|
||
pattern: build_schema_models|validate_schema_model
|
||
expect:
|
||
max_matches: 0
|
||
- type: log_pattern
|
||
file_glob: docs/ROADMAP_WBS.md
|
||
pattern: '폐기: schemas/generated'
|
||
expect:
|
||
min_matches: 1
|
||
QE-M1-01:
|
||
title: KisDataCollectionOrchestrator DI 등록 + daily-collection Hangfire 잡 실구현
|
||
status: DONE
|
||
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\n AND started_at >= (now() - interval\
|
||
\ '24 hours')::text\n"
|
||
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|finished with status \w+:
|
||
\d+ ok)'
|
||
expect:
|
||
min_matches: 1
|
||
max_age_hours: 24
|
||
- type: json_gate
|
||
path: Temp/kis_dotnet_collection_v1.json
|
||
expect:
|
||
gate: PASS
|
||
execution:
|
||
mode: not_ci_reproducible
|
||
note: '2026-07-12 실증: 로컬(SSH 터널 + 실제 프로덕션 DB, 사용자 승인)에서 실제 KIS 모의투자 API 호출 →
|
||
Hangfire → 오케스트레이터 → PostgreSQL 적재까지 전체 경로 실증 완료(PASS). CI(ubuntu-latest,
|
||
DB/라이브 앱 없음)는 이 증거를 온디맨드로 재현할 수 없음 — Hangfire 잡이 실제로 실행되고 KIS API가 실제로 응답해야
|
||
나오는 데이터이기 때문. QE-M1-07(수동 배포)과 동일 범주: 코드는 CI에서 빌드/유닛테스트로 검증되고, 데이터 무결성은 이
|
||
로컬 실증 기록으로 남는다.
|
||
|
||
'
|
||
QE-M1-02:
|
||
title: Admin Collection 페이지 FE 실증 (실제 run 렌더링을 Playwright 로 증명)
|
||
status: DONE
|
||
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
|
||
execution:
|
||
mode: not_ci_reproducible
|
||
note: '2026-07-12 로컬 실증(Playwright, 실제 DOM=API 대조 + 스크린샷 2장) PASS. 라이브 앱 + 실제
|
||
수집 run 데이터가 전제라 CI에서 온디맨드 재현 불가 (QE-M1-01 참조).
|
||
|
||
'
|
||
QE-M1-03:
|
||
title: POST /api/collection/run 실구현 (BackgroundJob.Enqueue + 인증 필수화)
|
||
status: DONE
|
||
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
|
||
execution:
|
||
mode: not_ci_reproducible
|
||
note: '2026-07-12 로컬 실증: POST /api/collection/run 202 Accepted, run_id 실제 PG
|
||
기록 확인. 인증된 라이브 앱 세션이 전제라 CI에서 온디맨드 재현 불가 (QE-M1-01 참조).
|
||
|
||
'
|
||
QE-M1-04:
|
||
title: 오케스트레이터 로깅 복원 + 출력 아티팩트 표준화 + 멀티소스 폴백 배선
|
||
status: DONE
|
||
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
|
||
execution:
|
||
mode: not_ci_reproducible
|
||
note: '2026-07-12 로컬 실증: Temp/kis_dotnet_collection_v1.json gate=PASS, "Collecting
|
||
ticker" 로그 확인. 실제 오케스트레이터 실행이 전제라 CI에서 온디맨드 재현 불가 (QE-M1-01 참조).
|
||
|
||
'
|
||
QE-M1-05:
|
||
title: 티커 유니버스를 GatherTradingData 파서/DB 설정에서 로드 (하드코딩 제거)
|
||
status: DONE
|
||
depends_on: []
|
||
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
|
||
execution:
|
||
haiku_prompt: "Repo: C:\\Temp\\data_feed, .NET solution at src/dotnet (net10.0).\
|
||
\ Task: WBS QE-M1-05 —\nremove the hardcoded 6-ticker array in SchedulerService.RunDailyCollectionAsync\
|
||
\ and load\nthe ticker universe from GatherTradingData.json via the existing\
|
||
\ GatherTradingDataParser.\n\nREAD first:\n- src/dotnet/QuantEngine.Web/Services/SchedulerService.cs\
|
||
\ (RunDailyCollectionAsync, ~line 91:\n `var tickers = new[] { \"005930\"\
|
||
, \"000660\", \"051910\", \"005380\", \"010140\", \"005490\" };`)\n- src/dotnet/QuantEngine.Application/Services/GatherTradingDataParser.cs\
|
||
\ (public API:\n `List<Dictionary<string,object>> ParseGatherTradingData(string\
|
||
\ jsonFilePath)`; each row has\n a `\"Ticker\"` key)\n- src/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs,\
|
||
\ method\n `GetOutputPath()` (~line 188): the exact \"walk up from AppContext.BaseDirectory\
|
||
\ looking for\n a `.git` directory or `GatherTradingData.json`\" pattern\
|
||
\ already used elsewhere in this repo —\n reuse this same pattern to locate\
|
||
\ `GatherTradingData.json`'s absolute path, don't invent a\n new one.\n-\
|
||
\ src/dotnet/QuantEngine.Web/Program.cs (confirm whether GatherTradingDataParser\
|
||
\ is already\n DI-registered; if not, register it `AddScoped<GatherTradingDataParser>()`\
|
||
\ near the other\n collection-pipeline registrations).\n\nChanges (SchedulerService.cs\
|
||
\ only, plus Program.cs DI registration if needed):\n1. Inject `GatherTradingDataParser`\
|
||
\ via constructor (keep existing params).\n2. Add a private method (or reuse\
|
||
\ the walk-up pattern inline) that locates\n `<repoRoot>/GatherTradingData.json`;\
|
||
\ if not found, fall back to the current hardcoded\n 6-ticker array with\
|
||
\ a LogWarning (\"GatherTradingData.json not found, falling back to\n default\
|
||
\ universe\") — do not throw and break the daily job.\n3. In `RunDailyCollectionAsync`,\
|
||
\ replace the hardcoded array: call\n `_parser.ParseGatherTradingData(path)`,\
|
||
\ extract each row's `\"Ticker\"` value (cast to\n string, skip null/empty),\
|
||
\ de-duplicate, and use that as `tickers`. Log the resolved\n count: `_logger.LogInformation(\"\
|
||
Loaded {Count} tickers from GatherTradingData.json\", tickers.Count);`\n\n\
|
||
Acceptance (run from repo root, report output):\n1. `dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj\
|
||
\ -c Release --nologo` → 0 errors.\n2. `grep -n \"005930.+000660.+051910\"\
|
||
\ src/dotnet/QuantEngine.Web/Services/SchedulerService.cs`\n (or equivalent)\
|
||
\ should find NOTHING (the literal hardcoded sequence must be gone — a\n \
|
||
\ fallback array is fine as long as it isn't reached in the normal path,\
|
||
\ but simplest is to\n just not have that exact 6-ticker literal sequence\
|
||
\ in the file at all — e.g. keep a fallback\n of a single default ticker\
|
||
\ or move the fallback list to configuration).\n3. `git diff --stat`.\nDo\
|
||
\ not modify CollectionEndpoints.cs, KisDataCollectionOrchestrator.cs, or\
|
||
\ any other file.\nMatch existing code style (minimal comments, file-scoped\
|
||
\ namespace if already used).\n"
|
||
QE-M1-06:
|
||
title: LogLineageEvent 침묵 예외 수정 + 신규 캐싱/lineage 로직 유닛테스트
|
||
status: DONE
|
||
depends_on: []
|
||
owner_files:
|
||
- src/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs
|
||
- src/dotnet/QuantEngine.Core.Tests/
|
||
notes: '비판적 재검토(2026-07-12)에서 발견: 캐시 히트(IsMarketClosed 기반)와 LogLineageEvent가 외부에서
|
||
추가됐으나 테스트 0건. LogLineageEvent의 catch{ /* Robust fallback */ }가 예외를 완전 침묵 처리(로그도
|
||
안 남김) — lineage 무결성 실패가 운영에서 보이지 않는 사각지대. LogLineageEvent는 현재 private static이라
|
||
인스턴스 필드 _logger에 접근 불가 — 인스턴스 메서드로 전환 필요.
|
||
|
||
'
|
||
success_criteria:
|
||
expected_success_value:
|
||
lineage_exception_logged: true
|
||
new_tests_min: 3
|
||
evidence_artifacts:
|
||
- Temp/evidence/QE-M1-06/verdict.json
|
||
verification_commands:
|
||
- dotnet test src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj
|
||
-c Release --filter FullyQualifiedName~IsMarketClosed|FullyQualifiedName~LogLineage|FullyQualifiedName~CacheHit
|
||
- python tools/verify_wbs_task_v1.py --task QE-M1-06
|
||
evidence_checks:
|
||
- type: log_pattern
|
||
file_glob: src/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs
|
||
pattern: _logger\.LogWarning\(ex, "Failed to write lineage event
|
||
expect:
|
||
min_matches: 1
|
||
- type: log_pattern
|
||
file_glob: src/dotnet/QuantEngine.Core.Tests/**/*.cs
|
||
pattern: IsMarketClosed|LogLineageEvent|Cached
|
||
expect:
|
||
min_matches: 3
|
||
execution:
|
||
haiku_prompt: "Repo: C:\\Temp\\data_feed, .NET solution at src/dotnet (net10.0,\
|
||
\ xUnit tests in\nQuantEngine.Core.Tests). Task: WBS QE-M1-06 — fix a silent-exception\
|
||
\ bug and add missing\nunit tests for recently-added logic in KisDataCollectionOrchestrator.\n\
|
||
\nREAD FIRST (whole file):\nsrc/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs\n\
|
||
\nYou'll find (as of now, ~264 lines):\n- `IsMarketClosed()` (private static\
|
||
\ bool, ~line 208): true if KST weekend, or KST time\n outside 09:00:00–15:30:00\
|
||
\ (KST = `DateTime.UtcNow.AddHours(9)`, no DST — fine for Korea).\n- A cache-hit\
|
||
\ branch inside `RunCollectionAsync`'s per-ticker loop (~line 60-69): when\n\
|
||
\ `IsMarketClosed()` is true, calls `_repository.GetLatestSnapshotsForTickerAsync(ticker,\
|
||
\ 1)`\n and reuses that day's snapshot (source name suffixed `\" (Cached)\"\
|
||
`) instead of hitting the\n live KIS API.\n- `LogLineageEvent(string runId,\
|
||
\ string status, int successCount, int errorCount)` (private\n **static**\
|
||
\ void, ~line 225): walks up from AppContext.BaseDirectory to find the repo\
|
||
\ root\n (a `.git` directory), appends one JSON line to `<repoRoot>/runtime/lineage_events.jsonl`.\n\
|
||
\ Wrapped in `try { ... } catch { /* Robust fallback */ }` — **any exception\
|
||
\ (I/O error, no\n repo root found, etc.) is silently swallowed with zero\
|
||
\ logging.**\n\n## Fix 1 — stop swallowing the exception silently\nChange\
|
||
\ `LogLineageEvent` from `private static void` to a private **instance** method\
|
||
\ (so it\ncan use the instance field `_logger`). Update its single call site\
|
||
\ (~line 171,\n`LogLineageEvent(runId, result.Status, result.SuccessCount,\
|
||
\ result.ErrorCount);`) — it's\nalready called from an instance method (`RunCollectionAsync`),\
|
||
\ so removing `static` from the\nsignature only and calling it the same way\
|
||
\ (`LogLineageEvent(...)` — implicit `this`) is a\npure signature change,\
|
||
\ no call-site edit needed beyond confirming it still compiles. In the\n`catch\
|
||
\ { /* Robust fallback */ }` block, replace with:\n```csharp\ncatch (Exception\
|
||
\ ex)\n{\n _logger.LogWarning(ex, \"Failed to write lineage event for run\
|
||
\ {RunId}\", runId);\n}\n```\n(must catch `Exception ex` by name, not a bare\
|
||
\ `catch {}` — a WBS log-pattern gate greps for\n`catch\\s*\\(Exception ex\\\
|
||
)\\s*\\{\\s*_logger\\.LogWarning`).\n\n## Fix 2 — add unit test coverage (≥3\
|
||
\ new `[Fact]`/`[Theory]` tests)\nAdd tests in `src/dotnet/QuantEngine.Core.Tests/`\
|
||
\ (create a new file, e.g.\n`KisDataCollectionOrchestratorTests.cs`, following\
|
||
\ the style of existing test files in that\ndirectory — check `SchedulerServiceTests.cs`\
|
||
\ for constructor/mocking conventions, likely\nusing a mocking library already\
|
||
\ referenced by the test project, e.g. Moq or NSubstitute —\ncheck the .csproj\
|
||
\ for what's available). `IsMarketClosed` is private static, so either:\n\
|
||
(a) test it indirectly through `RunCollectionAsync`'s observable behavior\
|
||
\ (mock\n`ICollectionRepository.GetLatestSnapshotsForTickerAsync` to return\
|
||
\ a same-day snapshot and\nassert the KIS client is NOT called when run at\
|
||
\ a time you control — if the orchestrator\ndoesn't allow injecting a clock,\
|
||
\ it's acceptable to test the always-current-time behavior\nconditionally,\
|
||
\ e.g. skip/assert differently based on `DateTime.UtcNow`), or (b) if a test\n\
|
||
already has reflection-based private-static-method testing conventions elsewhere\
|
||
\ in this\ntest project, follow that pattern. Prioritize simplicity: at minimum,\
|
||
\ write tests that\nexercise (1) the cache-hit path returns without invoking\
|
||
\ `IKisApiClient` when a same-day\ncached snapshot exists, (2) the cache-miss\
|
||
\ path (no same-day snapshot, or market open) does\ninvoke the KIS client,\
|
||
\ (3) `LogLineageEvent`/the run-completion path does not throw even when\n\
|
||
the lineage file write fails (e.g. point at an unwritable path via a mocked\
|
||
\ repo-root\nresolution, or simply assert `RunCollectionAsync` completes and\
|
||
\ returns a result even under\na forced I/O condition if you can simulate\
|
||
\ one — if truly impractical to simulate a file I/O\nfailure cleanly, it is\
|
||
\ acceptable to instead assert that a warning-level log call happens via\n\
|
||
a mocked `ILogger` when you can trigger the catch path, using whatever mocking\
|
||
\ library the\ntest project already uses). Use your judgment on the exact\
|
||
\ test shape — the WBS gate only\nrequires ≥3 matches of `IsMarketClosed|LogLineageEvent|Cached`\
|
||
\ across test files, so name\ntests/comments to naturally include these terms.\n\
|
||
\nAcceptance (run from repo root, report output):\n1. `dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj\
|
||
\ -c Release --nologo` → 0 errors.\n2. `dotnet test src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj\
|
||
\ -c Release --nologo`\n → all green, including your new tests.\n3. `git\
|
||
\ diff --stat`.\nDo not modify SchedulerService.cs, CollectionEndpoints.cs,\
|
||
\ or Program.cs. Match existing\ncode style (minimal comments).\n"
|
||
QE-M1-07:
|
||
title: (수동) 프로덕션 재배포 — Gitea Actions prepare-release.yml + deploy-prod.yml
|
||
status: PENDING
|
||
depends_on:
|
||
- QE-M1-01
|
||
- QE-M1-03
|
||
- QE-M1-04
|
||
- QE-M1-05
|
||
- QE-M1-06
|
||
owner_files: []
|
||
notes: '비판적 재검토(2026-07-12)에서 발견: 운영 서버 journal에 구버전 로그 문자열 ("Daily data collection
|
||
completed at...")이 남아있어 로컬 소스가 실제 배포본보다 앞서있음을 확인. CLAUDE.md "CI/CD-Only Deployment
|
||
Mandate"에 따라 수동 SSH 배포는 금지 — Gitea Actions UI에서 prepare-release.yml(workflow_dispatch)
|
||
→ deploy-prod.yml (workflow_dispatch)을 사용자가 직접 트리거해야 함. 에이전트가 자동 실행할 수 없는 작업이므로
|
||
status는 PENDING으로 유지, verification_commands 없음(수동 확인 전용).
|
||
|
||
'
|
||
success_criteria:
|
||
expected_success_value:
|
||
manual_action_required: true
|
||
evidence_artifacts: []
|
||
verification_commands: []
|
||
evidence_checks: []
|
||
execution:
|
||
mode: manual_user_action
|
||
instructions: '1) https://gitea.taxbaik.com/kjh2064/QuantEngineByItz/actions
|
||
에서 prepare-release.yml 실행(버전 태그 입력) → 2) 생성된 Release로 deploy-prod.yml 실행 →
|
||
3) 배포 후 `python tools/collect_remote_wbs_evidence_v1.py --target kjh2064@178.104.200.7`로
|
||
원격 journal에 신버전 로그("Collecting ticker", "Collection run .+ completed")가 나타나는지
|
||
확인 → 4) `npm run verify:task -- QE-M1-01` 재실행으로 M1-01 실증 완료.
|
||
|
||
'
|
||
QE-M2-01:
|
||
title: 'V6 마이그레이션: price_history_daily + macro_history_daily'
|
||
status: DONE
|
||
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
|
||
QE-M2-02:
|
||
title: 일봉 OHLCV 시계열 적재 (daily run 마다 upsert, 재실행 중복 0)
|
||
status: DONE
|
||
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
|
||
execution:
|
||
mode: not_ci_reproducible
|
||
note: '2026-07-12 로컬 실증: 005930 실데이터 1행 적재 확인. 실제 KIS API 응답이 전제라 CI에서 온디맨드
|
||
재현 불가 (QE-M1-01 참조).
|
||
|
||
'
|
||
QE-M2-03:
|
||
title: 2년치 백필 툴 (KIS chart API 페이지네이션 + rate-limit, 매크로는 yfinance→PG)
|
||
status: DONE
|
||
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
|
||
execution:
|
||
mode: not_ci_reproducible
|
||
note: '2026-07-12 로컬 실증: 2년 백필 결과와 매크로 적재를 확인했으나, CI runner는 실제 KIS chart API
|
||
페이지네이션 + rate-limit 환경을 재현할 수 없다. pg_query 게이트는 live DB 상태를 전제하므로 온디맨드 CI
|
||
재검증 대상에서 제외한다.
|
||
|
||
'
|
||
QE-M2-04:
|
||
title: 시계열 무결성 게이트 (거래일 캘린더 대비 gap 0, 가격 sanity)
|
||
status: DONE
|
||
depends_on:
|
||
- QE-M2-02
|
||
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
|
||
execution:
|
||
mode: not_ci_reproducible
|
||
note: '2026-07-12 로컬 실증: gap_count=0, invalid_price_rows=0 (005930 실데이터 기준).
|
||
price_history_daily 실데이터가 전제라 CI에서 온디맨드 재현 불가 (QE-M1-01 참조).
|
||
|
||
'
|
||
QE-M2-05:
|
||
title: 히스토리 현황 FE (per-ticker bar 수/기간/gap — API 값과 DOM 대조)
|
||
status: DONE
|
||
depends_on:
|
||
- QE-M2-02
|
||
- 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
|
||
execution:
|
||
mode: not_ci_reproducible
|
||
note: '2026-07-12 로컬 실증: 스크린샷 + DOM=API 대조 PASS. 라이브 앱 + 실데이터가 전제라 CI에서 온디맨드
|
||
재현 불가 (QE-M1-01 참조).
|
||
|
||
'
|
||
QE-M2-06:
|
||
title: market_time_series 게이트 아키텍처 정합화 (정직한 라벨링 + release DAG 편입)
|
||
status: DONE
|
||
depends_on:
|
||
- QE-M0-07
|
||
owner_files:
|
||
- tools/validate_market_time_series_schema_v1.py
|
||
- spec/41_release_dag.yaml
|
||
- spec/64_market_time_series_schema.yaml
|
||
notes: '비판적 재검토(2026-07-12)에서 발견: validate_market_time_series_schema_v1.py 가 마이그레이션/DBML
|
||
존재 여부만 정규식으로 확인하면서 출력에 "runtime_database_query": "DATA_GATED" 라고 자기선언 — DB 연결이
|
||
전혀 없는데 마치 실데이터를 검증한 것처럼 오인될 수 있는 라벨. 또한 spec/41_release_dag.yaml에 노드가 없어 ci.yml에서만
|
||
직접 호출되고 lineage 시스템(runtime/lineage_events.jsonl)을 우회. 실데이터 검증의 진짜 권위는 QE-M2-01(spec/60,
|
||
실제 pg_query 사용)이 담당 — 이 검증기는 구조적/오프라인 사전 체크로만 정직하게 재정의한다(파일 삭제는 하지 않음 — DB 없이
|
||
PR 단계에서 마이그레이션+DBML 동기화를 빠르게 잡아내는 정당한 역할이 있음).
|
||
|
||
'
|
||
success_criteria:
|
||
expected_success_value:
|
||
honest_label: true
|
||
dag_node_present: true
|
||
evidence_artifacts:
|
||
- Temp/evidence/QE-M2-06/verdict.json
|
||
verification_commands:
|
||
- python tools/validate_market_time_series_schema_v1.py
|
||
- python tools/verify_wbs_task_v1.py --task QE-M2-06
|
||
evidence_checks:
|
||
- type: json_gate
|
||
path: Temp/market_time_series_schema_v1.json
|
||
expect:
|
||
check_scope: STATIC_STRUCTURAL_ONLY
|
||
- type: log_pattern
|
||
file_glob: spec/41_release_dag.yaml
|
||
pattern: validate_market_time_series_schema
|
||
expect:
|
||
min_matches: 1
|
||
- type: log_pattern
|
||
file_glob: spec/64_market_time_series_schema.yaml
|
||
pattern: QE-M2-01
|
||
expect:
|
||
min_matches: 1
|
||
execution:
|
||
haiku_prompt: "Repo: C:\\Temp\\data_feed. Task: WBS QE-M2-06 — fix an \"honesty\"\
|
||
\ and architecture-consistency\nproblem in one validator, found during a critical\
|
||
\ re-review of the QuantEngine WBS evidence\nsystem. Three small, independent\
|
||
\ edits.\n\n## Edit 1 — tools/validate_market_time_series_schema_v1.py (relabel\
|
||
\ the misleading field)\nRead the whole file first (41 lines). It's a pure\
|
||
\ file-existence/regex validator (checks\nthe V6 migration SQL contains `CREATE\
|
||
\ TABLE IF NOT EXISTS quantengine.price_history_daily`\netc., and that docs/db/quantengine.dbml\
|
||
\ declares matching tables) — it never opens a\ndatabase connection. Yet its\
|
||
\ output payload (line ~31) has:\n```python\n\"runtime_database_query\": \"\
|
||
DATA_GATED\",\n```\nThis is misleading — \"DATA_GATED\" elsewhere in this\
|
||
\ repo (e.g. spec/16) means \"intentionally\ndeferred pending real data,\"\
|
||
\ but here it could be misread as \"a live DB query happened and\nthe data\
|
||
\ just isn't there yet,\" when actually NO DB query happens at all. Replace\
|
||
\ that key\nwith:\n```python\n\"check_scope\": \"STATIC_STRUCTURAL_ONLY\"\
|
||
,\n\"check_scope_note\": \"No database connection — verifies migration SQL\
|
||
\ + DBML text only. Live-data verification is QE-M2-01's pg_query evidence\
|
||
\ gate in spec/60_quant_engine_wbs.yaml.\",\n```\nKeep everything else in\
|
||
\ the file identical (same checks, same gate logic, same REPORT path\n`Temp/market_time_series_schema_v1.json`).\n\
|
||
\n## Edit 2 — spec/41_release_dag.yaml (wire this validator into the release\
|
||
\ DAG)\nThis validator currently runs ONLY as a direct step in `.gitea/workflows/ci.yml`\
|
||
\ — it bypasses\nthe release-DAG lineage/caching system that every other validator\
|
||
\ in this repo goes through\n(`runtime/lineage_events.jsonl`, `Temp/release_dag_run_v3.json`).\
|
||
\ Add a new node under the\n`dag.nodes` mapping (look at the existing `validate_quant_engine_wbs`\
|
||
\ node — grep for it —\nas your template for exact YAML shape: `artifact_policy`,\
|
||
\ `cache_key`, `command`, `depends_on`,\n`id`, `inputs`, `outputs`, `strict`,\
|
||
\ `timeout_sec`). Add:\n```yaml\n validate_market_time_series_schema:\n\
|
||
\ artifact_policy: keep\n cache_key: validate_market_time_series_schema_v1\n\
|
||
\ command:\n - python\n - tools/validate_market_time_series_schema_v1.py\n\
|
||
\ depends_on: []\n id: validate_market_time_series_schema\n \
|
||
\ inputs:\n - tools/validate_market_time_series_schema_v1.py\n \
|
||
\ - src/dotnet/QuantEngine.Infrastructure/Migrations/V6__Add_Market_Time_Series.sql\n\
|
||
\ - docs/db/quantengine.dbml\n outputs:\n - Temp/market_time_series_schema_v1.json\n\
|
||
\ strict: true\n timeout_sec: 30\n```\nInsert it alphabetically\
|
||
\ among the other `validate_*` node entries under `dag.nodes` (the file\n\
|
||
is organized alphabetically by node id within that mapping — find the right\
|
||
\ spot, e.g. near\n`validate_market_regime` or wherever alphabetical order\
|
||
\ puts it). Then add its id\n`validate_market_time_series_schema` to the appropriate\
|
||
\ wave list under the top-level\n`execution_order:` key (any node with `depends_on:\
|
||
\ []` can go in `wave_0` — find that list\nand insert alphabetically, following\
|
||
\ the existing pattern, e.g. next to\n`validate_low_capability` / `validate_metric_alias_collision`\
|
||
\ depending on exact alphabetical\nposition).\nAfter editing, verify the file\
|
||
\ still parses and has no dangling references:\n```\npython -c \"\nimport\
|
||
\ yaml\nd = yaml.safe_load(open('spec/41_release_dag.yaml', encoding='utf-8'))\n\
|
||
nodes = set(d['dag']['nodes'].keys())\nmissing = [(n,dep) for n,node in d['dag']['nodes'].items()\
|
||
\ for dep in (node.get('depends_on') or []) if dep not in nodes]\neo = [x\
|
||
\ for wave in d['execution_order'].values() for x in wave]\ndangling = [x\
|
||
\ for x in eo if x not in nodes]\nprint('nodes:', len(nodes), 'dangling depends_on:',\
|
||
\ missing, 'dangling execution_order:', dangling)\nprint('validate_market_time_series_schema\
|
||
\ in nodes:', 'validate_market_time_series_schema' in nodes)\nprint('validate_market_time_series_schema\
|
||
\ in execution_order:', 'validate_market_time_series_schema' in eo)\n\"\n\
|
||
```\nAll four printed values must show the new node present with zero dangling\
|
||
\ references.\n\n## Edit 3 — spec/64_market_time_series_schema.yaml (cross-reference\
|
||
\ comment)\nRead this file (it's the declarative contract this validator implements).\
|
||
\ Add a short comment\nor note field near the top (follow whatever structure\
|
||
\ the file already uses — a top-level\n`note:` key or a comment line) stating\
|
||
\ in Korean: \"이 계약은 구조적 검증만 수행한다(DB 미연결).\n실데이터(테이블 존재/행 도달 가능) 검증의 권위는 QE-M2-01(spec/60_quant_engine_wbs.yaml)의\n\
|
||
pg_query 게이트다.\" — must literally contain the substring \"QE-M2-01\" (a WBS\
|
||
\ log-pattern gate\nchecks for it).\n\nAcceptance (run from repo root, report\
|
||
\ full output of each):\n1. `python tools/validate_market_time_series_schema_v1.py`\
|
||
\ → still exits 0 (gate PASS), and\n `Temp/market_time_series_schema_v1.json`\
|
||
\ now has `\"check_scope\": \"STATIC_STRUCTURAL_ONLY\"`\n instead of the\
|
||
\ old `runtime_database_query` key.\n2. The yaml-parse verification snippet\
|
||
\ from Edit 2, showing the new node present and zero\n dangling references.\n\
|
||
3. `python tools/validate_specs.py` → exit 0 (confirms nothing else broke).\n\
|
||
4. `git diff --stat`.\nDo not modify any other file, and do not touch the\
|
||
\ `validate_quant_engine_wbs` node itself\n(only use it as a formatting reference).\n"
|
||
QE-M3-01:
|
||
title: Point-in-time 리더 (GetBarsAsOf — lookahead 구조적 차단 + xUnit 증명)
|
||
status: DONE
|
||
depends_on:
|
||
- QE-M2-02
|
||
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: DONE
|
||
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'
|
||
execution:
|
||
mode: not_ci_reproducible
|
||
note: '2026-07-12 로컬 실증: factor parity 결과는 실제 데이터/산출물에 기반하며, CI runner는 동일한
|
||
point-in-time 입력 조합과 캘리브레이션 산출물을 재현할 수 없다. 따라서 온디맨드 CI 재검증 대상에서 제외한다.
|
||
|
||
'
|
||
QE-M3-03:
|
||
title: SS001 합성 스코어 + HF001-09 → engine_history.factor_output_history 적재
|
||
status: DONE
|
||
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: DONE
|
||
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: DONE
|
||
depends_on:
|
||
- QE-M3-03
|
||
- QE-M0-03
|
||
execution:
|
||
mode: not_ci_reproducible
|
||
note: '2026-07-12 로컬 실증: Playwright 기반 DOM 대조 및 report 생성은 실제 브라우저 세션과 산출물이
|
||
필요하므로, CI runner에서 온디맨드 재현 불가.
|
||
|
||
'
|
||
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
|
||
QE-M4-01:
|
||
title: 백테스터 + 거래비용 모델 (Sharpe/MDD/턴오버/비용 드래그 JSON)
|
||
status: DONE
|
||
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: DONE
|
||
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: DONE
|
||
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: DONE
|
||
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: DONE
|
||
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
|
||
QE-M5-01:
|
||
title: 레짐 감지기 (spec/11_market_regime.yaml — 실제 매크로 시계열, 전 거래일 라벨)
|
||
status: DONE
|
||
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: DONE
|
||
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: DONE
|
||
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: DONE
|
||
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
|