update backtest loop roadmap and scenario implementation

This commit is contained in:
toki 2026-06-15 18:05:16 +09:00
parent 60440f83de
commit 7b2dfe6751
5 changed files with 146 additions and 13 deletions

View file

@ -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 후보
- 확인 필요: 없음

View file

@ -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 경계

View file

@ -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 후보
- 확인 필요: 없음

View file

@ -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)
}

View file

@ -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