Files
QuantEngineByItz/spec/60_quant_engine_wbs.yaml
T

1295 lines
57 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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:0015: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