Files
QuantEngineByItz/spec/60_quant_engine_wbs.yaml
T
kjh2064 3c22798e08
Validators (Pushes and Pull Requests) / validate-core (push) Failing after 0s
Validators (Pushes and Pull Requests) / validate-ui-and-storage (push) Successful in 12s
feat(qe-m3-04): refactor PipelineOrchestrator to compute steps 1-2 and mark 3-7 as STUBBED
2026-07-12 22:08:48 +09:00

1121 lines
61 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.
# =============================================================================
# QuantEngine 데이터 실증 기반 퀀트 엔진 로드맵 + WBS (기계 판정)
# =============================================================================
# formula_id: QUANT_ENGINE_WBS_V1
# 원칙: 모든 작업(task)은 "완료 주장"이 아니라 게이트 실행으로만 DONE 판정된다.
# - BE 실증: pg_query(PostgreSQL 쿼리) + log_pattern(Serilog 로그) + json_gate(아티팩트)
# - FE 실증: playwright_report(스펙 PASS) + file_exists(스크린샷)
# 실행:
# 단일 작업 검증: python tools/verify_wbs_task_v1.py --task <TASK_ID>
# → Temp/evidence/<TASK_ID>/verdict.json + 원시 증거 보존
# 전체 WBS 게이트: python tools/validate_quant_engine_wbs_v1.py
# → Temp/quant_engine_wbs_v1.json (status=DONE 작업의 증거 재검증)
# 관례: spec/16_data_gaps_roadmap.yaml 의 success_criteria 구조
# (expected_success_value / evidence_artifacts / verification_commands) 준수.
# 검증 로직만 하드코딩 → evidence_checks 선언형으로 일반화.
# =============================================================================
meta:
formula_id: QUANT_ENGINE_WBS_V1
version: 1
created: "2026-07-12"
authority: "governance/authority_matrix.yaml"
validator: tools/validate_quant_engine_wbs_v1.py
task_verifier: tools/verify_wbs_task_v1.py
remote_evidence_collector: tools/collect_remote_wbs_evidence_v1.py
evidence_root: Temp/evidence
status_values: [PENDING, IN_PROGRESS, DONE] # DONE = 해당 verdict.json gate=PASS 필수
db_connection:
# 검증기의 PostgreSQL 접속 순서:
# 1) env QE_WBS_PG_DSN (psycopg DSN)
# 2) env ConnectionStrings__DefaultConnection (.NET 형식 → 자동 변환)
# 3) src/dotnet/QuantEngine.Web/appsettings.Development.json 의 ConnectionStrings.DefaultConnection
# (로컬은 SSH 터널 127.0.0.1:5432 전제 — CLAUDE.md "Local Development & Testing")
dotnet_appsettings: src/dotnet/QuantEngine.Web/appsettings.Development.json
remote_evidence:
collector: "python tools/collect_remote_wbs_evidence_v1.py --target <ssh-target>"
policy: "Collect journal and JSON artifacts only; never copy env files or passwords."
postgres: "Use QE_WBS_PG_DSN through an approved SSH tunnel; do not embed credentials in evidence."
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는 실행 직전 작업에만 채우며 미리 전부
작성하지 않는다(과설계 방지).
# -----------------------------------------------------------------------------
# 검증 체크 타입 사전 (verify_wbs_task_v1.py 가 해석하는 선언형 vocabulary)
# -----------------------------------------------------------------------------
evidence_check_types:
pg_query: "PostgreSQL 쿼리 1개 실행, 단일 스칼라 결과를 expect{min,max,equals}와 비교. 원시 결과를 pg_query_<n>.json 으로 보존"
log_pattern: "file_glob 로그 파일들에서 정규식 매칭. expect{min_matches, max_age_hours(파일 mtime 기준)}. 매칭 라인을 log_excerpt.txt 로 보존"
json_gate: "path 의 JSON 아티팩트에서 expect 의 키-값 검사 (점 표기 경로 지원, 값 '>=N' 비교 지원)"
file_exists: "paths 의 모든 파일 존재 (expect.min_bytes 선택)"
playwright_report: "Playwright JSON 리포트(report)에서 spec_file 의 결과가 expect{passed_min, failed} 충족"
# =============================================================================
# 로드맵 (M0 → M5)
# =============================================================================
roadmap:
scope_note: >
전통 팩터(모멘텀/거래량/수급/실적/매크로/밸류/재무건전성 = spec/08_scoring_rules.yaml SS001)
+ ATR 리스크 관리 기본 포함. 최신 기법은 레짐 감지 + 워크포워드 캘리브레이션 + 거래비용
반영 평가로 한정(사용자 확정, 2026-07-12). 딥러닝/인트라데이/대체데이터/실거래 집행 제외
(은퇴자산 + read-only KIS 거버넌스: governance/rules/06_no_direct_api_trading.yaml 유지).
phases:
M0:
name: "실증 하네스 + 정직성 정리"
goal: "완료 주장이 불가능한 구조 확립 — 검증기/증거 규약/CI 편입 + 가짜 검증 제거"
exit_gate: "validate_quant_engine_wbs 가 release DAG/CI 노드로 PASS; dotnet test + Playwright evidence 스위트 CI 편입; 디버그 스펙 격리·가짜 PASS 제거; 중복/가짜 게이트 재발 없음"
tasks: [QE-M0-01, QE-M0-02, QE-M0-03, QE-M0-04, QE-M0-05, QE-M0-06, 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]
# =============================================================================
# WBS 작업 목록
# =============================================================================
tasks:
# ---------------------------------------------------------------------------
# M0 — 실증 하네스 + 정직성 정리
# ---------------------------------------------------------------------------
QE-M0-01:
title: "WBS 스펙(YAML) + 로드맵 작성, 레거시 로드맵 문서에 포인터 추가"
status: DONE
depends_on: []
owner_files:
- spec/60_quant_engine_wbs.yaml
- docs/ROADMAP_WBS.md
success_criteria:
expected_success_value: { spec_exists: true, legacy_pointer_appended: true }
evidence_artifacts: [Temp/evidence/QE-M0-01/verdict.json]
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M0-01"]
evidence_checks:
- type: file_exists
paths: [spec/60_quant_engine_wbs.yaml]
expect: { min_bytes: 10000 }
- type: log_pattern
file_glob: docs/ROADMAP_WBS.md
pattern: "QUANT_ENGINE_WBS_V1"
expect: { min_matches: 1 }
QE-M0-02:
title: "증거 검증기 2종 구현 (단일 작업 verifier + 전체 WBS validator) + 유닛테스트"
status: DONE
depends_on: []
owner_files:
- tools/verify_wbs_task_v1.py
- tools/validate_quant_engine_wbs_v1.py
- tests/unit/test_validate_quant_engine_wbs_v1.py
success_criteria:
expected_success_value: { self_test: PASS, synthetic_pass_fail_bidirectional: true }
evidence_artifacts: [Temp/evidence/QE-M0-02/verdict.json, Temp/quant_engine_wbs_v1.json]
verification_commands:
- "python -m pytest tests/unit/test_validate_quant_engine_wbs_v1.py -q"
- "python tools/verify_wbs_task_v1.py --task QE-M0-02"
evidence_checks:
- type: file_exists
paths:
- tools/verify_wbs_task_v1.py
- tools/validate_quant_engine_wbs_v1.py
- tests/unit/test_validate_quant_engine_wbs_v1.py
- type: 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 } # 파일이 기본 testDir 에 더 이상 존재하지 않아야 함
QE-M0-04:
title: "CI에 dotnet test 편입 + 고아 QuantEngine.Web.Tests 처리"
status: DONE
depends_on: []
owner_files:
- .gitea/workflows/ci.yml
- src/dotnet/QuantEngine.Web.Tests/
notes: >
QuantEngine.Web.Tests/DashboardComponentTests.cs 는 csproj 없는 고아(폐기된 Blazor 대상).
현 Razor Pages UI 에 맞지 않으면 삭제. ci.yml 에 dotnet test 스텝 추가.
success_criteria:
expected_success_value: { ci_has_dotnet_test: true, core_tests_green: true }
evidence_artifacts: [Temp/evidence/QE-M0-04/verdict.json]
verification_commands:
- "dotnet test src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj -c Release --nologo"
- "python tools/verify_wbs_task_v1.py --task QE-M0-04"
evidence_checks:
- type: log_pattern
file_glob: .gitea/workflows/ci.yml
pattern: "dotnet test"
expect: { min_matches: 1 }
QE-M0-05:
title: "release DAG + CI 에 validate_quant_engine_wbs 게이트 노드 등록"
status: DONE
depends_on: [QE-M0-02]
owner_files:
- spec/41_release_dag.yaml
- .gitea/workflows/ci.yml
success_criteria:
expected_success_value: { dag_node: validate_quant_engine_wbs, ci_step: true }
evidence_artifacts: [Temp/evidence/QE-M0-05/verdict.json]
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M0-05"]
evidence_checks:
- type: log_pattern
file_glob: spec/41_release_dag.yaml
pattern: "validate_quant_engine_wbs"
expect: { min_matches: 1 }
- type: log_pattern
file_glob: .gitea/workflows/ci.yml
pattern: "validate_quant_engine_wbs_v1"
expect: { min_matches: 1 }
QE-M0-06:
title: "골든커버리지 정직성 표기 (coverage_basis: FILE_COUNT_ONLY)"
status: DONE
depends_on: []
owner_files:
- tools/validate_golden_coverage_100.py
notes: >
골든 테스트 174개는 실행되지 않는 placeholder. 커버리지 판정 출력에
coverage_basis: FILE_COUNT_ONLY 필드를 추가해 실체를 명시(삭제는 M3 패리티 대체 후).
success_criteria:
expected_success_value: { honesty_field: FILE_COUNT_ONLY }
evidence_artifacts: [Temp/evidence/QE-M0-06/verdict.json]
verification_commands:
- "python tools/validate_golden_coverage_100.py"
- "python tools/verify_wbs_task_v1.py --task QE-M0-06"
evidence_checks:
- type: json_gate
path: Temp/golden_coverage_100_v1.json
expect: { coverage_basis: FILE_COUNT_ONLY }
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 }
# ---------------------------------------------------------------------------
# M1 — 수집 파이프라인 배선 (첫 실데이터 실증)
# ---------------------------------------------------------------------------
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
AND started_at >= (now() - interval '24 hours')::text
expect: { min: 1 }
- type: pg_query
sql: >
SELECT count(DISTINCT s.ticker) FROM quantengine.kis_collection_snapshots s
JOIN quantengine.kis_collection_runs r ON r.run_id = s.run_id
WHERE r.started_at >= (now() - interval '24 hours')::text
expect: { min: 5 }
- type: log_pattern
file_glob: src/dotnet/QuantEngine.Web/logs/quantengine-*.log
# 2026-07-12 정정: 수집은 두 경로로 트리거될 수 있다 —
# (a) SchedulerService.RunDailyCollectionAsync (매일 09:00 cron) → "Collection run {Id} completed: {N} snapshots, {N} errors"
# (b) POST /api/collection/run → Hangfire enqueue → 오케스트레이터 자체 완료 로그 → "Collection run {Id} finished with status {Status}: {N} ok, {N} errors"
# 둘 다 동일한 오케스트레이터/DI/PG 쓰기 경로를 타는 동등한 실증이므로 둘 다 인정.
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: []
# 2026-07-12 정정: 원래 [QE-M1-01] 로 선언했으나, 이 작업은 M1-01의 "코드"(이미 병합됨)만
# 필요했지 M1-01의 "실증 완료(DONE)"까지는 필요 없었다. M1-01은 여전히 PENDING(프로덕션
# 재배포 차단, QE-M1-07 참조)이지만 M1-05는 코드 레벨 게이트로 독립적으로 PASS했다.
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 —
remove the hardcoded 6-ticker array in SchedulerService.RunDailyCollectionAsync and load
the ticker universe from GatherTradingData.json via the existing GatherTradingDataParser.
READ first:
- src/dotnet/QuantEngine.Web/Services/SchedulerService.cs (RunDailyCollectionAsync, ~line 91:
`var tickers = new[] { "005930", "000660", "051910", "005380", "010140", "005490" };`)
- src/dotnet/QuantEngine.Application/Services/GatherTradingDataParser.cs (public API:
`List<Dictionary<string,object>> ParseGatherTradingData(string jsonFilePath)`; each row has
a `"Ticker"` key)
- src/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs, method
`GetOutputPath()` (~line 188): the exact "walk up from AppContext.BaseDirectory looking for
a `.git` directory or `GatherTradingData.json`" pattern already used elsewhere in this repo —
reuse this same pattern to locate `GatherTradingData.json`'s absolute path, don't invent a
new one.
- src/dotnet/QuantEngine.Web/Program.cs (confirm whether GatherTradingDataParser is already
DI-registered; if not, register it `AddScoped<GatherTradingDataParser>()` near the other
collection-pipeline registrations).
Changes (SchedulerService.cs only, plus Program.cs DI registration if needed):
1. Inject `GatherTradingDataParser` via constructor (keep existing params).
2. Add a private method (or reuse the walk-up pattern inline) that locates
`<repoRoot>/GatherTradingData.json`; if not found, fall back to the current hardcoded
6-ticker array with a LogWarning ("GatherTradingData.json not found, falling back to
default universe") — do not throw and break the daily job.
3. In `RunDailyCollectionAsync`, replace the hardcoded array: call
`_parser.ParseGatherTradingData(path)`, extract each row's `"Ticker"` value (cast to
string, skip null/empty), de-duplicate, and use that as `tickers`. Log the resolved
count: `_logger.LogInformation("Loaded {Count} tickers from GatherTradingData.json", tickers.Count);`
Acceptance (run from repo root, report output):
1. `dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj -c Release --nologo` → 0 errors.
2. `grep -n "005930.+000660.+051910" src/dotnet/QuantEngine.Web/Services/SchedulerService.cs`
(or equivalent) should find NOTHING (the literal hardcoded sequence must be gone — a
fallback array is fine as long as it isn't reached in the normal path, but simplest is to
just not have that exact 6-ticker literal sequence in the file at all — e.g. keep a fallback
of a single default ticker or move the fallback list to configuration).
3. `git diff --stat`.
Do not modify CollectionEndpoints.cs, KisDataCollectionOrchestrator.cs, or any other file.
Match existing code style (minimal comments, file-scoped namespace if already used).
QE-M1-06:
title: "LogLineageEvent 침묵 예외 수정 + 신규 캐싱/lineage 로직 유닛테스트"
status: DONE
depends_on: []
# 2026-07-12 정정: QE-M1-05와 동일한 사유로 [QE-M1-01] 의존성 제거 — 코드 레벨
# 하드닝 작업이라 M1-01의 실증 완료를 전제하지 않는다.
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 }
# 참고: verify_wbs_task_v1.py 의 log_pattern 은 라인 단위 매칭이라 catch/{ 를
# 포함한 멀티라인 패턴은 매치되지 않는다(2026-07-12 QE-M1-06 실행 중 발견,
# 원본 패턴으로 수정). LogWarning 호출 한 줄만 대상으로 검사.
- 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
QuantEngine.Core.Tests). Task: WBS QE-M1-06 — fix a silent-exception bug and add missing
unit tests for recently-added logic in KisDataCollectionOrchestrator.
READ FIRST (whole file):
src/dotnet/QuantEngine.Application/Services/KisDataCollectionOrchestrator.cs
You'll find (as of now, ~264 lines):
- `IsMarketClosed()` (private static bool, ~line 208): true if KST weekend, or KST time
outside 09:00:0015:30:00 (KST = `DateTime.UtcNow.AddHours(9)`, no DST — fine for Korea).
- A cache-hit branch inside `RunCollectionAsync`'s per-ticker loop (~line 60-69): when
`IsMarketClosed()` is true, calls `_repository.GetLatestSnapshotsForTickerAsync(ticker, 1)`
and reuses that day's snapshot (source name suffixed `" (Cached)"`) instead of hitting the
live KIS API.
- `LogLineageEvent(string runId, string status, int successCount, int errorCount)` (private
**static** void, ~line 225): walks up from AppContext.BaseDirectory to find the repo root
(a `.git` directory), appends one JSON line to `<repoRoot>/runtime/lineage_events.jsonl`.
Wrapped in `try { ... } catch { /* Robust fallback */ }` — **any exception (I/O error, no
repo root found, etc.) is silently swallowed with zero logging.**
## Fix 1 — stop swallowing the exception silently
Change `LogLineageEvent` from `private static void` to a private **instance** method (so it
can use the instance field `_logger`). Update its single call site (~line 171,
`LogLineageEvent(runId, result.Status, result.SuccessCount, result.ErrorCount);`) — it's
already called from an instance method (`RunCollectionAsync`), so removing `static` from the
signature only and calling it the same way (`LogLineageEvent(...)` — implicit `this`) is a
pure signature change, no call-site edit needed beyond confirming it still compiles. In the
`catch { /* Robust fallback */ }` block, replace with:
```csharp
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to write lineage event for run {RunId}", runId);
}
```
(must catch `Exception ex` by name, not a bare `catch {}` — a WBS log-pattern gate greps for
`catch\s*\(Exception ex\)\s*\{\s*_logger\.LogWarning`).
## Fix 2 — add unit test coverage (≥3 new `[Fact]`/`[Theory]` tests)
Add tests in `src/dotnet/QuantEngine.Core.Tests/` (create a new file, e.g.
`KisDataCollectionOrchestratorTests.cs`, following the style of existing test files in that
directory — check `SchedulerServiceTests.cs` for constructor/mocking conventions, likely
using a mocking library already referenced by the test project, e.g. Moq or NSubstitute —
check the .csproj for what's available). `IsMarketClosed` is private static, so either:
(a) test it indirectly through `RunCollectionAsync`'s observable behavior (mock
`ICollectionRepository.GetLatestSnapshotsForTickerAsync` to return a same-day snapshot and
assert the KIS client is NOT called when run at a time you control — if the orchestrator
doesn't allow injecting a clock, it's acceptable to test the always-current-time behavior
conditionally, e.g. skip/assert differently based on `DateTime.UtcNow`), or (b) if a test
already has reflection-based private-static-method testing conventions elsewhere in this
test project, follow that pattern. Prioritize simplicity: at minimum, write tests that
exercise (1) the cache-hit path returns without invoking `IKisApiClient` when a same-day
cached snapshot exists, (2) the cache-miss path (no same-day snapshot, or market open) does
invoke the KIS client, (3) `LogLineageEvent`/the run-completion path does not throw even when
the lineage file write fails (e.g. point at an unwritable path via a mocked repo-root
resolution, or simply assert `RunCollectionAsync` completes and returns a result even under
a forced I/O condition if you can simulate one — if truly impractical to simulate a file I/O
failure cleanly, it is acceptable to instead assert that a warning-level log call happens via
a mocked `ILogger` when you can trigger the catch path, using whatever mocking library the
test project already uses). Use your judgment on the exact test shape — the WBS gate only
requires ≥3 matches of `IsMarketClosed|LogLineageEvent|Cached` across test files, so name
tests/comments to naturally include these terms.
Acceptance (run from repo root, report output):
1. `dotnet build src/dotnet/QuantEngine.Web/QuantEngine.Web.csproj -c Release --nologo` → 0 errors.
2. `dotnet test src/dotnet/QuantEngine.Core.Tests/QuantEngine.Core.Tests.csproj -c Release --nologo`
→ all green, including your new tests.
3. `git diff --stat`.
Do not modify SchedulerService.cs, CollectionEndpoints.cs, or Program.cs. Match existing
code style (minimal comments).
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 실증 완료.
# ---------------------------------------------------------------------------
# M2 — 히스토리 시계열 저장소
# ---------------------------------------------------------------------------
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 } # DBML 동기화 강제
QE-M2-02:
title: "일봉 OHLCV 시계열 적재 (daily run 마다 upsert, 재실행 중복 0)"
status: DONE
depends_on: [QE-M2-01]
# 2026-07-12 실증 메모: 005930 1행 실적재 확인(2026-07-10, OHLCV 실제값). Dapper가
# System.DateOnly 파라미터를 지원하지 않는 버그를 발견·수정(CollectionRepository.cs,
# DateOnly→DateTime 변환). 나머지 5개 티커는 KIS 모의투자 토큰 발급이 403으로 거부됨
# (외부 자격증명/레이트리밋 이슈, 코드 결함 아님) — QE-M2-03(2년 백필)은 동일 이슈로
# 대량 API 호출 시 악화될 위험이 있어 이번엔 착수하지 않고 보류.
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: PENDING
depends_on: [QE-M2-01]
owner_files:
- src/dotnet/QuantEngine.Tools/
- src/quant_engine/macro_index_collection_v1.py
success_criteria:
expected_success_value: { bars_per_ticker_min: 480, macro_bars_min: 480 }
evidence_artifacts: [Temp/evidence/QE-M2-03/verdict.json]
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M2-03"]
evidence_checks:
- type: pg_query
sql: "SELECT coalesce(min(c),0) FROM (SELECT count(*) c FROM quantengine.price_history_daily GROUP BY ticker) t"
expect: { min: 480 }
- type: pg_query
sql: "SELECT count(*) FROM quantengine.macro_history_daily WHERE symbol IN ('KOSPI','KOSDAQ')"
expect: { min: 960 }
QE-M2-04:
title: "시계열 무결성 게이트 (거래일 캘린더 대비 gap 0, 가격 sanity)"
status: DONE
depends_on: [QE-M2-02]
# 2026-07-12 정정: 원래 [QE-M2-03](2년 백필) 의존 — 그러나 이 게이트는 "수집된 범위 내"
# gap-freeness(각 티커의 min~max trade_date 사이 결측 거래일 수)를 검증하는 것으로,
# 전체 2년 커버리지를 전제하지 않는다. 백필 전에도 코드 완성·정직한 결과 산출 가능.
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]
# 2026-07-12 정정: QE-M2-04와 동일 사유로 [QE-M2-03] 의존 제거 — FE는 현재 존재하는
# 데이터(설사 희소하더라도)를 정직하게 표시하면 되고 풀 백필을 전제하지 않는다.
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
problem in one validator, found during a critical re-review of the QuantEngine WBS evidence
system. Three small, independent edits.
## Edit 1 — tools/validate_market_time_series_schema_v1.py (relabel the misleading field)
Read the whole file first (41 lines). It's a pure file-existence/regex validator (checks
the V6 migration SQL contains `CREATE TABLE IF NOT EXISTS quantengine.price_history_daily`
etc., and that docs/db/quantengine.dbml declares matching tables) — it never opens a
database connection. Yet its output payload (line ~31) has:
```python
"runtime_database_query": "DATA_GATED",
```
This is misleading — "DATA_GATED" elsewhere in this repo (e.g. spec/16) means "intentionally
deferred pending real data," but here it could be misread as "a live DB query happened and
the data just isn't there yet," when actually NO DB query happens at all. Replace that key
with:
```python
"check_scope": "STATIC_STRUCTURAL_ONLY",
"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.",
```
Keep everything else in the file identical (same checks, same gate logic, same REPORT path
`Temp/market_time_series_schema_v1.json`).
## Edit 2 — spec/41_release_dag.yaml (wire this validator into the release DAG)
This validator currently runs ONLY as a direct step in `.gitea/workflows/ci.yml` — it bypasses
the release-DAG lineage/caching system that every other validator in this repo goes through
(`runtime/lineage_events.jsonl`, `Temp/release_dag_run_v3.json`). Add a new node under the
`dag.nodes` mapping (look at the existing `validate_quant_engine_wbs` node — grep for it —
as your template for exact YAML shape: `artifact_policy`, `cache_key`, `command`, `depends_on`,
`id`, `inputs`, `outputs`, `strict`, `timeout_sec`). Add:
```yaml
validate_market_time_series_schema:
artifact_policy: keep
cache_key: validate_market_time_series_schema_v1
command:
- python
- tools/validate_market_time_series_schema_v1.py
depends_on: []
id: validate_market_time_series_schema
inputs:
- tools/validate_market_time_series_schema_v1.py
- src/dotnet/QuantEngine.Infrastructure/Migrations/V6__Add_Market_Time_Series.sql
- docs/db/quantengine.dbml
outputs:
- Temp/market_time_series_schema_v1.json
strict: true
timeout_sec: 30
```
Insert it alphabetically among the other `validate_*` node entries under `dag.nodes` (the file
is organized alphabetically by node id within that mapping — find the right spot, e.g. near
`validate_market_regime` or wherever alphabetical order puts it). Then add its id
`validate_market_time_series_schema` to the appropriate wave list under the top-level
`execution_order:` key (any node with `depends_on: []` can go in `wave_0` — find that list
and insert alphabetically, following the existing pattern, e.g. next to
`validate_low_capability` / `validate_metric_alias_collision` depending on exact alphabetical
position).
After editing, verify the file still parses and has no dangling references:
```
python -c "
import yaml
d = yaml.safe_load(open('spec/41_release_dag.yaml', encoding='utf-8'))
nodes = set(d['dag']['nodes'].keys())
missing = [(n,dep) for n,node in d['dag']['nodes'].items() for dep in (node.get('depends_on') or []) if dep not in nodes]
eo = [x for wave in d['execution_order'].values() for x in wave]
dangling = [x for x in eo if x not in nodes]
print('nodes:', len(nodes), 'dangling depends_on:', missing, 'dangling execution_order:', dangling)
print('validate_market_time_series_schema in nodes:', 'validate_market_time_series_schema' in nodes)
print('validate_market_time_series_schema in execution_order:', 'validate_market_time_series_schema' in eo)
"
```
All four printed values must show the new node present with zero dangling references.
## Edit 3 — spec/64_market_time_series_schema.yaml (cross-reference comment)
Read this file (it's the declarative contract this validator implements). Add a short comment
or note field near the top (follow whatever structure the file already uses — a top-level
`note:` key or a comment line) stating in Korean: "이 계약은 구조적 검증만 수행한다(DB 미연결).
실데이터(테이블 존재/행 도달 가능) 검증의 권위는 QE-M2-01(spec/60_quant_engine_wbs.yaml)의
pg_query 게이트다." — must literally contain the substring "QE-M2-01" (a WBS log-pattern gate
checks for it).
Acceptance (run from repo root, report full output of each):
1. `python tools/validate_market_time_series_schema_v1.py` → still exits 0 (gate PASS), and
`Temp/market_time_series_schema_v1.json` now has `"check_scope": "STATIC_STRUCTURAL_ONLY"`
instead of the old `runtime_database_query` key.
2. The yaml-parse verification snippet from Edit 2, showing the new node present and zero
dangling references.
3. `python tools/validate_specs.py` → exit 0 (confirms nothing else broke).
4. `git diff --stat`.
Do not modify any other file, and do not touch the `validate_quant_engine_wbs` node itself
(only use it as a formatting reference).
# ---------------------------------------------------------------------------
# M3 — 실데이터 팩터 계산
# ---------------------------------------------------------------------------
QE-M3-01:
title: "Point-in-time 리더 (GetBarsAsOf — lookahead 구조적 차단 + xUnit 증명)"
status: DONE
depends_on: [QE-M2-02]
# 2026-07-12 정정: [QE-M2-03](2년 백필) 의존 제거 — 리더의 lookahead 차단 정확성은
# 코드 레벨 유닛테스트(mock/합성 데이터)로 증명 가능하며 실제 2년치 데이터 존재를
# 전제하지 않는다. price_history_daily 쓰기 경로(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" }
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: PENDING
depends_on: [QE-M3-03, QE-M0-03]
owner_files:
- tests/e2e/evidence/qe-m3-05-scores.spec.ts
success_criteria:
expected_success_value: { spec_passed: 1 }
evidence_artifacts: [Temp/evidence/QE-M3-05/verdict.json]
verification_commands:
- "npx playwright test --project=evidence tests/e2e/evidence/qe-m3-05-scores.spec.ts"
- "python tools/verify_wbs_task_v1.py --task QE-M3-05"
evidence_checks:
- type: playwright_report
report: Temp/evidence/playwright-last-run.json
spec_file: qe-m3-05-scores.spec.ts
expect: { passed_min: 1, failed: 0 }
# ---------------------------------------------------------------------------
# M4 — 백테스팅 + 검증
# ---------------------------------------------------------------------------
QE-M4-01:
title: "백테스터 + 거래비용 모델 (Sharpe/MDD/턴오버/비용 드래그 JSON)"
status: PENDING
depends_on: [QE-M3-03]
owner_files:
- src/dotnet/QuantEngine.Core/Domain/Backtester.cs
- src/dotnet/QuantEngine.Tools/
success_criteria:
expected_success_value: { metrics_populated: [sharpe, mdd, turnover, cost_drag] }
evidence_artifacts: [Temp/evidence/QE-M4-01/verdict.json, Temp/backtest_result_v1.json]
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M4-01"]
evidence_checks:
- type: json_gate
path: Temp/backtest_result_v1.json
expect: { gate: PASS }
QE-M4-02:
title: "no-lookahead 게이트 실배선 (정상 PASS + 오염 픽스처 FAIL 양방향 검증)"
status: PENDING
depends_on: [QE-M4-01]
owner_files:
- tools/validate_no_lookahead_bias_v1.py
success_criteria:
expected_success_value: { real_run: PASS, corrupted_fixture: FAIL }
evidence_artifacts: [Temp/evidence/QE-M4-02/verdict.json]
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M4-02"]
evidence_checks:
- type: json_gate
path: Temp/no_lookahead_bias_v1.json
expect: { gate: PASS }
QE-M4-03:
title: "워크포워드 하네스 (24m train / 6m test 롤링, 윈도우 ≥4)"
status: PENDING
depends_on: [QE-M4-01]
owner_files:
- src/dotnet/QuantEngine.Tools/
success_criteria:
expected_success_value: { windows_min: 4, oos_metrics_nonnull: true }
evidence_artifacts: [Temp/evidence/QE-M4-03/verdict.json, Temp/walk_forward_v1.json]
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M4-03"]
evidence_checks:
- type: json_gate
path: Temp/walk_forward_v1.json
expect: { gate: PASS, windows: ">=4" }
QE-M4-04:
title: "T+5/T+20 성과 원장 (prediction_accuracy 실표본 재계산, t5_sample≥30)"
status: PENDING
depends_on: [QE-M2-03]
owner_files:
- tools/
success_criteria:
expected_success_value: { t5_sample_min: 30 }
evidence_artifacts: [Temp/evidence/QE-M4-04/verdict.json, Temp/prediction_accuracy_harness_v2.json]
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M4-04"]
evidence_checks:
- type: json_gate
path: Temp/prediction_accuracy_harness_v2.json
expect: { t5_sample: ">=30" }
QE-M4-05:
title: "백테스트 결과 FE (에쿼티커브/Sharpe/MDD — backtest_result_v1.json 값과 DOM 대조)"
status: PENDING
depends_on: [QE-M4-01, QE-M0-03]
owner_files:
- tests/e2e/evidence/qe-m4-05-backtest.spec.ts
success_criteria:
expected_success_value: { spec_passed: 1 }
evidence_artifacts: [Temp/evidence/QE-M4-05/verdict.json]
verification_commands:
- "npx playwright test --project=evidence tests/e2e/evidence/qe-m4-05-backtest.spec.ts"
- "python tools/verify_wbs_task_v1.py --task QE-M4-05"
evidence_checks:
- type: playwright_report
report: Temp/evidence/playwright-last-run.json
spec_file: qe-m4-05-backtest.spec.ts
expect: { passed_min: 1, failed: 0 }
# ---------------------------------------------------------------------------
# M5 — 포트폴리오 구성 + 최신 기법
# ---------------------------------------------------------------------------
QE-M5-01:
title: "레짐 감지기 (spec/11_market_regime.yaml — 실제 매크로 시계열, 전 거래일 라벨)"
status: PENDING
depends_on: [QE-M2-03]
owner_files:
- src/dotnet/QuantEngine.Core/Domain/
success_criteria:
expected_success_value: { regime_labels_full_window: true }
evidence_artifacts: [Temp/evidence/QE-M5-01/verdict.json, Temp/market_regime_v1.json]
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M5-01"]
evidence_checks:
- type: json_gate
path: Temp/market_regime_v1.json
expect: { gate: PASS }
QE-M5-02:
title: "SS001 가중치 워크포워드 캘리브레이션 (±50% 제약 + shrinkage λ=0.5, 정직 보고)"
status: PENDING
depends_on: [QE-M4-03, QE-M5-01]
owner_files:
- src/dotnet/QuantEngine.Tools/
notes: "게이트는 방법론 필드(제약 준수, OOS 비교 존재)를 검증 — 캘리브레이션이 '이겨야' PASS 가 아님"
success_criteria:
expected_success_value: { weights_within_bounds: true, oos_comparison_reported: true }
evidence_artifacts: [Temp/evidence/QE-M5-02/verdict.json, Temp/weight_calibration_v1.json]
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M5-02"]
evidence_checks:
- type: json_gate
path: Temp/weight_calibration_v1.json
expect: { gate: PASS }
QE-M5-03:
title: "변동성 타게팅 사이징 + heat/집중도 캡 합성 → 최종 목표 포트폴리오 패킷"
status: PENDING
depends_on: [QE-M5-02]
owner_files:
- src/dotnet/QuantEngine.Application/Services/
success_criteria:
expected_success_value: { weights_sum_lte_100: true, all_caps_satisfied: true }
evidence_artifacts: [Temp/evidence/QE-M5-03/verdict.json, Temp/target_portfolio_v1.json]
verification_commands: ["python tools/verify_wbs_task_v1.py --task QE-M5-03"]
evidence_checks:
- type: json_gate
path: Temp/target_portfolio_v1.json
expect: { gate: PASS }
QE-M5-04:
title: "포트폴리오·레짐 대시보드 FE (레짐 배지·목표 가중치 — API 값과 DOM 대조)"
status: PENDING
depends_on: [QE-M5-03, QE-M0-03]
owner_files:
- tests/e2e/evidence/qe-m5-04-portfolio.spec.ts
success_criteria:
expected_success_value: { spec_passed: 1 }
evidence_artifacts: [Temp/evidence/QE-M5-04/verdict.json]
verification_commands:
- "npx playwright test --project=evidence tests/e2e/evidence/qe-m5-04-portfolio.spec.ts"
- "python tools/verify_wbs_task_v1.py --task QE-M5-04"
evidence_checks:
- type: playwright_report
report: Temp/evidence/playwright-last-run.json
spec_file: qe-m5-04-portfolio.spec.ts
expect: { passed_min: 1, failed: 0 }