diff --git a/agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md b/agent-roadmap/archive/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md similarity index 84% rename from agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md rename to agent-roadmap/archive/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md index 5816851..199c976 100644 --- a/agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md +++ b/agent-roadmap/archive/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md @@ -11,7 +11,7 @@ ## 상태 -[진행중] +[완료] ## 승격 조건 @@ -54,18 +54,18 @@ 정제 데이터가 실제 백테스트 실행 입력으로 반복 사용될 수 있게 연결한다. -- [ ] [input-selector] 저장된 정제 데이터에서 종목군, 기간, frequency를 선택해 backtest run input으로 넘기는 경계를 만든다. +- [x] [input-selector] 저장된 정제 데이터에서 종목군, 기간, frequency를 선택해 backtest run input으로 넘기는 경계를 만든다. - [x] [headless-smoke] 샘플 종목군 수집부터 기본 backtest 실행까지 headless smoke로 검증한다. ## 완료 리뷰 -- 상태: 없음 -- 요청일: 없음 -- 완료 근거: `watchlist-config`, `daily-collect`, `freshness-status`, `provider-boundary`, `normalized-store`, `gap-handling`, `headless-smoke` 기능 Task가 완료되었고, `input-selector` split 작업은 `01_input_selector_contract`만 PASS되었다. 2026-06-15 기준 `02+01_storage_bar_selector`, `03+01,02_backtest_selector_flow`가 active plan/review 상태로 남아 있어 전체 마일스톤 완료 조건은 아직 충족되지 않았다. +- 상태: 승인됨 +- 요청일: 2026-06-15 +- 완료 근거: `watchlist-config`, `daily-collect`, `freshness-status`, `provider-boundary`, `normalized-store`, `gap-handling`, `headless-smoke`, `input-selector` 기능 Task가 모두 충족되었다. `input-selector`는 `01_input_selector_contract`, `02+01_storage_bar_selector`, `03+01,02_backtest_selector_flow` split 완료로 저장 데이터 selector부터 backtest run input 연결까지 충족되었다. 2026-06-15 코드레벨 검토 중 `start_backtest` dry-run market 검증 공백을 작게 보강했고, 전체 테스트/lint/build가 통과했다. - 리뷰 필요: - - [ ] 사용자가 완료 결과를 확인했다 - - [ ] archive 이동을 승인했다 -- 리뷰 코멘트: 없음 + - [x] 사용자가 완료 결과를 확인했다 + - [x] archive 이동을 승인했다 +- 리뷰 코멘트: 사용자 요청에 따라 2026-06-15 완료 처리 및 archive 이동을 수행했다. ## 범위 제외 @@ -89,9 +89,9 @@ - 완료 근거(gap-handling): `collection_freshness` scenario가 `expected_yyyymmdd` 기반 gap, duplicate, provider delay를 text/JSONL 출력과 expectation mismatch 검증에 포함하며, matched-zero-bars symbol의 double count regression을 보강했다. 2026-06-14에 `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli`가 통과했다. 증거: `agent-task/archive/2026/06/m-backtest-data-collection-infrastructure/03+02_gap_handling/complete.log`. - 완료 근거(headless-smoke): `backtest_run_polling` operator scenario가 named universe 기반 `import_daily_bars` 후 `start_backtest`와 `poll_backtest_run`으로 terminal 상태까지 확인하는 headless smoke를 문서화했다. 2026-06-14에 `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli`가 통과했다. - 작업 현황(input-selector): `01_input_selector_contract`는 selector contract/domain/forwarding 보존을 PASS 처리했다. 증거: `agent-task/archive/2026/06/m-backtest-data-collection-infrastructure/01_input_selector_contract/complete.log`. -- 작업 현황(input-selector): 2026-06-15 동기화 기준 active task는 `agent-task/m-backtest-data-collection-infrastructure/02+01_storage_bar_selector/`와 `agent-task/m-backtest-data-collection-infrastructure/03+01,02_backtest_selector_flow/` 두 개다. 작업 트리는 깨끗하며 active 구현 체크리스트는 아직 미완료 상태다. -- 작업 현황(input-selector): `02+01_storage_bar_selector`는 다음 실행 대상이다. `01_input_selector_contract` 선행 조건은 충족되었고, 남은 범위는 저장된 bar source와 engine selector filtering 검증이다. -- 작업 현황(input-selector): `03+01,02_backtest_selector_flow`는 `02+01_storage_bar_selector` PASS 후 실행한다. 이 subtask의 `Roadmap Targets`가 `input-selector` Task를 check-on-pass로 닫는다. +- 작업 현황(input-selector): `02+01_storage_bar_selector`는 저장된 bar source와 engine selector filtering 검증을 PASS 처리했다. 증거: `agent-task/archive/2026/06/m-backtest-data-collection-infrastructure/02+01_storage_bar_selector/complete.log`. +- 작업 현황(input-selector): `03+01,02_backtest_selector_flow`는 `input-selector` Roadmap Target을 닫는 backtest selector flow를 PASS 처리했다. 증거: `agent-task/archive/2026/06/m-backtest-data-collection-infrastructure/03+01,02_backtest_selector_flow/complete.log`. +- 작업 현황(input-selector): 2026-06-15 기준 active task 디렉터리는 없으며, 전체 마일스톤은 사용자 완료 확인과 archive 승인 대기 상태다. - 선행 작업: Backtest Engine Baseline, Backtest Analysis Surface - 후속 작업: Backtest Scenario Automation, Data Quality Monitoring, Agent Tooling Bridge 후보 - 확인 필요: 없음 diff --git a/agent-roadmap/phase/backtest-loop/PHASE.md b/agent-roadmap/phase/backtest-loop/PHASE.md index 1609543..87ba312 100644 --- a/agent-roadmap/phase/backtest-loop/PHASE.md +++ b/agent-roadmap/phase/backtest-loop/PHASE.md @@ -19,9 +19,12 @@ - [완료] Backtest Analysis Surface - 경로: `agent-roadmap/archive/phase/backtest-loop/milestones/backtest-analysis-surface.md` - 요약: backtest 결과를 operator가 비교하고 해석할 수 있는 기본 분석 표면을 만든다. -- [진행중] Backtest Data Collection Infrastructure - - 경로: `agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md` +- [완료] Backtest Data Collection Infrastructure + - 경로: `agent-roadmap/archive/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md` - 요약: 선택 종목을 자동/일별로 수집하고 정제된 백테스트 입력 데이터만 DB에 남기는 headless 인프라를 만든다. +- [계획] Backtest Scenario Automation + - 경로: `agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md` + - 요약: 선택 universe와 기간 조합을 반복 가능한 backtest scenario로 실행하고 결과 handoff를 안정화한다. ## Phase 경계 diff --git a/agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md b/agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md new file mode 100644 index 0000000..6e9175c --- /dev/null +++ b/agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md @@ -0,0 +1,68 @@ +# Milestone: Backtest Scenario Automation + +## 위치 + +- Roadmap: `agent-roadmap/ROADMAP.md` +- Phase: `agent-roadmap/phase/backtest-loop/PHASE.md` + +## 목표 + +선택 universe, 기간, frequency, strategy 조합을 headless scenario로 반복 실행하고, run lifecycle과 결과 요약을 안정적인 CLI/YAML/JSONL 산출물로 남긴다. 이 마일스톤은 화면 없이 백테스트 반복 운영을 자동화하고 후속 데이터 품질/운영 UI 작업이 소비할 검증 표면을 만든다. + +## 상태 + +[계획] + +## 승격 조건 + +- 없음 + +## 구현 잠금 + +- 상태: 해제 +- 결정 필요: 없음 + +## 범위 + +- named universe와 selector 기반 backtest scenario matrix 정의 +- 여러 backtest run의 순차 실행, polling, terminal status 확인 흐름 +- 결과 summary, 실패/timeout, expectation mismatch를 stable text/JSONL로 남기는 headless handoff +- 기존 API/worker/contracts 경계를 유지한 CLI/operator workflow 보강 +- 반복 실행 가능한 fixture와 local smoke test + +## 기능 + +### Epic: [scenario] Scenario automation + +백테스트 실행 조합을 사람이 반복 입력하지 않아도 재현 가능한 headless scenario로 관리한다. + +- [ ] [scenario-matrix] universe, 기간, frequency, strategy 조합을 YAML scenario에서 표현하고 검증할 수 있게 한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` +- [ ] [batch-runner] 여러 backtest run을 순차 실행하고 각 run의 polling 결과를 stable text/JSONL로 출력한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./services/api/internal/socket ./services/worker/internal/socket ./services/worker/internal/jobs` +- [ ] [result-handoff] 성공, 실패, timeout, expectation mismatch 결과를 fixture와 `headless_validation.md`에 남겨 후속 운영 표면이 소비할 수 있게 한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` 및 `rg --sort path -n "backtest" apps/cli/testdata/operator/headless_validation.md apps/cli/testdata/operator` + +## 완료 리뷰 + +- 상태: 없음 +- 요청일: 없음 +- 완료 근거: 없음 +- 리뷰 필요: + - [ ] 사용자가 완료 결과를 확인했다 + - [ ] archive 이동을 승인했다 +- 리뷰 코멘트: 없음 + +## 범위 제외 + +- 새 strategy 판단 로직 또는 투자 의사결정 알고리즘 설계 +- Flutter 운영 화면 구현 +- 실시간 전략 실행, paper/live order routing, 실거래 adapter 변경 +- 외부 provider credential 값이나 개인 secret 문서화 +- 에이전트 도입, agent runtime 연결 + +## 작업 컨텍스트 + +- 관련 경로: `apps/cli/`, `services/api/`, `services/worker/`, `packages/contracts/`, `packages/domain/` +- 표준선(선택): 백테스트 반복 운영은 먼저 CLI, YAML fixture, JSONL/text output, Go tests로 검증한다. +- 표준선(선택): API는 얇은 control plane으로 유지하고 run 실행과 결과 저장은 worker 경계를 따른다. +- 선행 작업: Backtest Data Collection Infrastructure +- 후속 작업: Data Quality Monitoring, Agent Tooling Bridge 후보 +- 확인 필요: 없음 diff --git a/apps/cli/internal/operator/scenario.go b/apps/cli/internal/operator/scenario.go index 241f68a..74862f5 100644 --- a/apps/cli/internal/operator/scenario.go +++ b/apps/cli/internal/operator/scenario.go @@ -651,6 +651,9 @@ func validateRequest(step Step) error { if step.Request.StrategyID == "" { return fmt.Errorf("step %q: start_backtest requires request.strategy_id", step.ID) } + if step.Request.Market == "" || step.Request.Market == "unspecified" { + return fmt.Errorf("step %q: start_backtest requires request.market", step.ID) + } if !validMarkets[step.Request.Market] { return fmt.Errorf("step %q: unsupported market %q", step.ID, step.Request.Market) } diff --git a/apps/cli/internal/operator/scenario_test.go b/apps/cli/internal/operator/scenario_test.go index 55e54fe..a06ea85 100644 --- a/apps/cli/internal/operator/scenario_test.go +++ b/apps/cli/internal/operator/scenario_test.go @@ -1328,6 +1328,65 @@ steps: } } +func TestValidateStartBacktestRejectsMissingMarket(t *testing.T) { + for _, tc := range []struct { + name string + marketLine string + }{ + {name: "missing", marketLine: ""}, + {name: "unspecified", marketLine: " market: unspecified\n"}, + } { + t.Run(tc.name, func(t *testing.T) { + yamlText := ` +name: missing_market +steps: + - id: step1 + action: start_backtest + request: + strategy_id: strat1 +` + tc.marketLine + ` timeframe: daily + from_unix_ms: 100 + to_unix_ms: 200 + symbols: ["005930"] +` + _, err := ParseScenario([]byte(yamlText)) + if err == nil { + t.Fatal("expected error, got nil") + } + if !strings.Contains(err.Error(), "start_backtest requires request.market") { + t.Errorf("error = %v, want it to contain missing market error", err) + } + }) + } +} + +func TestValidateStartBacktestUniverseRejectsMissingMarket(t *testing.T) { + yamlText := ` +name: missing_universe_market +universes: + - name: kr-backtest-smoke + provider: kis + selector_kind: watchlist + symbols: ["005930"] +steps: + - id: step1 + action: start_backtest + request: + strategy_id: strat1 + universe: kr-backtest-smoke + timeframe: daily + from_unix_ms: 100 + to_unix_ms: 200 +` + _, err := ParseScenario([]byte(yamlText)) + if err == nil { + t.Fatal("expected error, got nil") + } + if !strings.Contains(err.Error(), "start_backtest requires request.market") { + t.Errorf("error = %v, want it to contain missing market error", err) + } +} + func TestValidateStartBacktestRejectsMissingSelector(t *testing.T) { yamlText := ` name: missing_selector