From d770a195ff2bfb9c7b7e455c1cbc9c5f85b7448f Mon Sep 17 00:00:00 2001 From: toki Date: Mon, 15 Jun 2026 21:13:32 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20backtest=20scenario=20automation=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - cli: scenario 실행 명령어 추가 - operator: scenario 처리 로직 구현 및 테스트 --- .../code_review_local_G06_0.log | 140 +++++++++ .../code_review_local_G07_1.log | 154 ++++++++++ .../01_scenario_matrix/complete.log | 44 +++ .../01_scenario_matrix/plan_local_G06_0.log | 277 +++++++++++++++++ .../01_scenario_matrix/plan_local_G07_1.log | 91 ++++++ .../CODE_REVIEW-cloud-G07.md | 115 +++++++ .../02+01_batch_runner/PLAN-cloud-G07.md | 278 +++++++++++++++++ .../CODE_REVIEW-local-G05.md | 120 ++++++++ .../03+01,02_result_handoff/PLAN-local-G05.md | 272 +++++++++++++++++ apps/cli/internal/cli/cli.go | 10 + apps/cli/internal/cli/cli_test.go | 38 +++ apps/cli/internal/operator/scenario.go | 135 +++++++- apps/cli/internal/operator/scenario_test.go | 287 ++++++++++++++++++ 13 files changed, 1955 insertions(+), 6 deletions(-) create mode 100644 agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/code_review_local_G06_0.log create mode 100644 agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/code_review_local_G07_1.log create mode 100644 agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/complete.log create mode 100644 agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/plan_local_G06_0.log create mode 100644 agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/plan_local_G07_1.log create mode 100644 agent-task/m-backtest-scenario-automation/02+01_batch_runner/CODE_REVIEW-cloud-G07.md create mode 100644 agent-task/m-backtest-scenario-automation/02+01_batch_runner/PLAN-cloud-G07.md create mode 100644 agent-task/m-backtest-scenario-automation/03+01,02_result_handoff/CODE_REVIEW-local-G05.md create mode 100644 agent-task/m-backtest-scenario-automation/03+01,02_result_handoff/PLAN-local-G05.md diff --git a/agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/code_review_local_G06_0.log b/agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/code_review_local_G06_0.log new file mode 100644 index 0000000..bd1263d --- /dev/null +++ b/agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/code_review_local_G06_0.log @@ -0,0 +1,140 @@ + + +# Code Review Reference - API + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only. + +## 개요 + +date=2026-06-15 +task=m-backtest-scenario-automation/01_scenario_matrix, plan=0, tag=API + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md` +- Task ids: + - `scenario-matrix`: universe, 기간, frequency, strategy 조합을 YAML scenario에서 표현하고 검증할 수 있게 한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 판정 append, active 파일 log rename, PASS 시 `complete.log` 작성과 archive 이동까지 끝난 상태를 의미합니다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [API-1] Matrix Schema And Validation | [x] | +| [API-2] Deterministic Matrix Expansion | [x] | +| [API-3] CLI Validate Output For Matrix | [x] | + +## 구현 체크리스트 + +- [x] `Scenario`에 optional backtest matrix schema를 추가하고 matrix-only scenario를 dry-run validation 대상으로 허용한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` +- [x] matrix combination expansion helper를 deterministic order로 추가하고 validation tests를 작성한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` +- [x] CLI `operator scenario validate`가 matrix-only scenario를 유효하게 보고하는 test를 추가한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. + +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G06_0.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_local_G06_0.log`로 아카이브한다. +- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [ ] PASS이면 active task 디렉터리 `agent-task/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [ ] PASS이고 task group이 `m-`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [x] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G07.md`와 `CODE_REVIEW-local-G07.md`를 작성하고 `complete.log`를 작성하지 않는다. +- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. +- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +계획에 맞춰 그대로 진행하였으며 계획 대비 변경 사항은 없습니다. + +## 주요 설계 결정 + +- `Scenario` 구조체에 `Matrix *BacktestScenarioMatrix` 필드를 optional 형태로 추가하여, steps가 비어있어도 matrix 조합이 있다면 유효하도록 `Validate` 함수를 개선하였습니다. +- `Matrix` 조합 ID 생성을 `universe -> strategy -> timeframe -> period` 순으로 고정하여 결정적인(deterministic) 순서를 가지도록 nested 루프로 구현하였고, 조합된 ID에 공백이 포함되거나 중복 ID가 발생하는 경우 validate 단계에서 거절하도록 validation을 강화했습니다. +- CLI `operator scenario validate` 시 matrix-only scenario인 경우, output에 `matrix_runs=`이 출력되도록 `cli.go`를 개선하였습니다. + +## 사용자 리뷰 요청 + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- Matrix-only scenario가 기존 step-only scenario를 깨지 않는지 확인한다. +- `MatrixRuns` ordering과 duplicate ID validation이 deterministic인지 확인한다. +- CLI validate output이 기존 tests와 scripts에 호환되는지 확인한다. + +## 검증 결과 + +### API-1 중간 검증 + +```bash +$ go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.192s +ok git.toki-labs.com/toki/alt/apps/cli/internal/cli 0.009s +``` + +### API-2 중간 검증 + +```bash +$ go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.192s +ok git.toki-labs.com/toki/alt/apps/cli/internal/cli 0.009s +``` + +### API-3 중간 검증 + +```bash +$ go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.210s +ok git.toki-labs.com/toki/alt/apps/cli/internal/cli 0.008s +``` + +### 최종 검증 + +```bash +$ go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.210s +ok git.toki-labs.com/toki/alt/apps/cli/internal/cli 0.008s +``` + +## 코드리뷰 결과 + +- 종합 판정: FAIL +- 차원별 평가: + - Correctness: Fail + - Completeness: Fail + - Test coverage: Fail + - API contract: Fail + - Code quality: Pass + - Plan deviation: Pass + - Verification trust: Pass +- 발견된 문제: + - Required: `apps/cli/internal/operator/scenario.go:528`의 matrix period 검증이 `from_unix_ms`와 `to_unix_ms` 누락을 거르지 않습니다. YAML에서 둘 중 하나가 빠지면 Go zero value `0`으로 들어오는데, 현재는 `from_unix_ms > to_unix_ms`만 검사하므로 `from_unix_ms: 0, to_unix_ms: 200` 또는 `to_unix_ms` 누락 케이스가 valid matrix run으로 확장됩니다. 기존 `start_backtest` request 검증은 두 값을 모두 필수로 취급하고 있어, 02 batch runner가 이 helper를 사용하면 dry-run validation을 통과한 invalid backtest run을 만들 수 있습니다. `MatrixPeriod` 검증에 `FromUnixMs == 0 || ToUnixMs == 0` 거부를 추가하고, `apps/cli/internal/operator/scenario_test.go:1448`의 invalid matrix table에 missing `from_unix_ms`, missing `to_unix_ms` 케이스를 추가하세요. +- 다음 단계: + - FAIL follow-up: `PLAN-local-G07.md`와 `CODE_REVIEW-local-G07.md`를 작성해 period 필수값 검증과 회귀 테스트만 보완한다. diff --git a/agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/code_review_local_G07_1.log b/agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/code_review_local_G07_1.log new file mode 100644 index 0000000..c2964a2 --- /dev/null +++ b/agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/code_review_local_G07_1.log @@ -0,0 +1,154 @@ + + +# Code Review Reference - REVIEW_API + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts are normal follow-up issues, not user-review blockers by themselves. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record the needed decision in `사용자 리뷰 요청` and stop for code-review. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume. +> Follow the ownership table at the bottom of this file for which sections you own. + +## 개요 + +date=2026-06-15 +task=m-backtest-scenario-automation/01_scenario_matrix, plan=1, tag=REVIEW_API + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md` +- Task ids: + - `scenario-matrix`: universe, 기간, frequency, strategy 조합을 YAML scenario에서 표현하고 검증할 수 있게 한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다. + +1. 판정을 append한다. +2. `CODE_REVIEW-{review_lane}-GNN.md` -> `code_review_{review_lane}_GNN_N.log`, `PLAN-{build_lane}-GNN.md` -> `plan_{build_lane}_GNN_M.log`로 아카이브한다. +3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다. +4. PASS이고 task group이 `m-`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다. +5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [REVIEW_API-1] Matrix Period Required Timestamps | [x] | + +## 구현 체크리스트 + +- [x] Matrix period validation이 `from_unix_ms` 또는 `to_unix_ms` 누락을 거부하도록 보강한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` +- [x] invalid matrix tests에 missing `from_unix_ms`와 missing `to_unix_ms` 케이스를 추가한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` +- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. +> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다. + +- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다. +- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G07_1.log`로 아카이브한다. +- [x] active `PLAN-*-G??.md`를 `plan_local_G07_1.log`로 아카이브한다. +- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다. +- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다. +- [x] PASS이면 active task 디렉터리 `agent-task/m-backtest-scenario-automation/01_scenario_matrix/`를 `agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다. +- [x] PASS이고 task group이 `m-backtest-scenario-automation`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다. +- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-backtest-scenario-automation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. +- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다. +- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다. +- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다. + +## 계획 대비 변경 사항 + +계획에 맞춰 그대로 진행하였으며 계획 대비 변경 사항은 없습니다. + +## 주요 설계 결정 + +- `MatrixPeriod` validation 시 `from_unix_ms` 및 `to_unix_ms`가 Go zero value(0)인 경우 YAML에서 값이 생략된 것으로 취급하여 검증 에러로 거부하도록 구현하였습니다. 이는 기존 `start_backtest` request의 timestamp 필수 조건과 일관성을 유지하기 위함입니다. + +## 사용자 리뷰 요청 + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- Matrix period에서 `from_unix_ms == 0` 또는 `to_unix_ms == 0`이 validation error인지 확인한다. +- 기존 reversed period와 duplicate generated ID validation이 유지되는지 확인한다. +- 최종 검증 출력이 실제 `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` 실행 결과인지 확인한다. + +## 검증 결과 + +필수 규칙: +- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다. +- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다. +- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다. +- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다. +- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다. + +### REVIEW_API-1 중간 검증 + +```bash +$ go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.187s +ok git.toki-labs.com/toki/alt/apps/cli/internal/cli 0.007s +``` + +### 최종 검증 + +```bash +$ go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.187s +ok git.toki-labs.com/toki/alt/apps/cli/internal/cli 0.007s +``` + +--- + +> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?** +> If anything is blank, go back and fill it in before saving this file. +> Leave review-agent-only sections unchanged. + +Sections and their ownership: + +| 섹션 | 소유자 | 설명 | +|------|--------|------| +| 헤더 주석, 개요(date/task/plan/tag), 리뷰 에이전트 지시 | 스텁 생성 시 고정 | 구현 에이전트가 수정하거나 실행하지 않음 | +| Roadmap Targets | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 수정하지 않음; PASS 시 code-review가 `complete.log`의 `Roadmap Completion`으로 복사 | +| 구현 항목별 완료 여부 (항목명) | 스텁 생성 시 고정 | `[ ]` -> `[x]` 체크만 구현 에이전트가 수행 | +| 구현 체크리스트 (항목 텍스트/순서) | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 `[ ]` -> `[x]` 체크만 수행; 마지막 체크박스는 저장 전 필수 | +| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section | +| 계획 대비 변경 사항, 주요 설계 결정 | 구현 에이전트가 채움 | placeholder 텍스트를 실제 내용으로 교체 | +| 사용자 리뷰 요청 | 구현 에이전트가 채움 | 진행에 사용자 입력이 필요하지 않으면 `상태: 없음` 유지 | +| 리뷰어를 위한 체크포인트 | 스텁 생성 시 고정 | 계획에서 추출한 리뷰 포인트 | +| 검증 결과 (섹션 제목 + 명령) | 스텁 생성 시 고정 | 실행 출력만 구현 에이전트가 채움 | +| 코드리뷰 결과 | 리뷰 에이전트가 append | 스텁에 포함하지 않음 | + +## 코드리뷰 결과 + +- 종합 판정: PASS +- 차원별 평가: + - Correctness: Pass + - Completeness: Pass + - Test coverage: Pass + - API contract: Pass + - Code quality: Pass + - Plan deviation: Pass + - Verification trust: Pass +- 발견된 문제: 없음 +- 다음 단계: + - PASS: `complete.log`를 작성하고 task directory를 archive로 이동한다. diff --git a/agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/complete.log b/agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/complete.log new file mode 100644 index 0000000..c1d0a94 --- /dev/null +++ b/agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/complete.log @@ -0,0 +1,44 @@ +# Complete - m-backtest-scenario-automation/01_scenario_matrix + +## 완료 일시 + +2026-06-15 + +## 요약 + +Backtest scenario matrix schema, deterministic expansion, CLI validate output, and required period timestamp validation completed after 2 review loops; final verdict PASS. + +## 루프 이력 + +| Plan | Review | Verdict | 메모 | +|------|--------|---------|------| +| `plan_local_G06_0.log` | `code_review_local_G06_0.log` | FAIL | Matrix period missing `from_unix_ms` / `to_unix_ms` validation was required. | +| `plan_local_G07_1.log` | `code_review_local_G07_1.log` | PASS | Required timestamp validation and missing-field tests were added. | + +## 구현/정리 내용 + +- Added optional backtest matrix schema to operator scenarios and allowed matrix-only validation. +- Added deterministic matrix expansion with generated run IDs. +- Added CLI validate `matrix_runs=` output for matrix scenarios. +- Added required matrix period `from_unix_ms` / `to_unix_ms` validation and regression tests. + +## 최종 검증 + +- `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` - PASS; output: + `ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.207s` + `ok git.toki-labs.com/toki/alt/apps/cli/internal/cli 0.011s` + +## Roadmap Completion + +- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md` +- Completed task ids: + - `scenario-matrix`: PASS; evidence=`agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/plan_local_G06_0.log`, `agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/code_review_local_G06_0.log`, `agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/plan_local_G07_1.log`, `agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/code_review_local_G07_1.log`; verification=`go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` +- Not completed task ids: 없음 + +## 잔여 Nit + +- 없음 + +## 후속 작업 + +- 없음 diff --git a/agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/plan_local_G06_0.log b/agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/plan_local_G06_0.log new file mode 100644 index 0000000..ecd8b5c --- /dev/null +++ b/agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/plan_local_G06_0.log @@ -0,0 +1,277 @@ + + +# Plan - API Scenario Matrix + +## 이 파일을 읽는 구현 에이전트에게 + +이 plan의 구현이 끝나면 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 검증 출력, 계획 대비 변경 사항으로 채운다. 구현 중 사용자 결정, 사용자 소유 외부 환경, 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정 필요 내용과 증거를 남기고 멈춘다. 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 명령 재실행이나 산출물 수집으로 해소 가능한 evidence 공백은 사용자 리뷰 요청이 아니다. + +## 배경 + +`Backtest Scenario Automation` 마일스톤은 universe, 기간, frequency/timeframe, strategy 조합을 YAML scenario로 반복 실행할 수 있어야 한다. 현재 `apps/cli/internal/operator/scenario.go`는 단일 `steps` 목록과 named `universes`만 검증하며, 조합 matrix를 표현하거나 결정적으로 펼치는 구조가 없다. 이 subtask는 실행 없이 matrix schema, dry-run validation, expansion helper까지만 만든다. + +## 사용자 리뷰 요청 흐름 + +구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 prompt는 금지이며, code-review가 해당 요청을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md` +- Task ids: + - `scenario-matrix`: universe, 기간, frequency, strategy 조합을 YAML scenario에서 표현하고 검증할 수 있게 한다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-ops/rules/project/rules.md` +- `agent-ops/rules/private/rules.md` +- `agent-ops/rules/common/rules-roadmap.md` +- `agent-ops/rules/project/domain/operations/rules.md` +- `agent-test/local/rules.md` +- `agent-test/local/operations-smoke.md` +- `agent-roadmap/current.md` +- `agent-roadmap/phase/backtest-loop/PHASE.md` +- `agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md` +- `apps/cli/go.mod` +- `apps/cli/internal/operator/scenario.go` +- `apps/cli/internal/operator/scenario_test.go` +- `apps/cli/internal/cli/cli.go` +- `apps/cli/internal/cli/cli_test.go` +- `apps/cli/testdata/operator/backtest_run_request.yaml` +- `apps/cli/testdata/operator/backtest_run_polling.yaml` +- `apps/cli/testdata/operator/backtest_result_summary.yaml` + +### 테스트 환경 규칙 + +- 선택 env: `local`. +- `agent-test/local/rules.md`와 `agent-test/local/operations-smoke.md`를 읽었다. +- 적용 명령: milestone 검증에 명시된 `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli`. +- baseline 확인: 위 명령은 현재 checkout에서 exit 0으로 통과했다. +- `<확인 필요>` 값 없음. fallback 없음. + +### 테스트 커버리지 공백 + +- matrix root schema, matrix period validation, matrix expansion ordering: 기존 테스트 없음. 새 tests 필요. +- 기존 universe 참조와 `start_backtest` selector 검증: `scenario_test.go`가 일부 커버한다. +- CLI `validate`의 matrix-only scenario 출력: 기존 테스트 없음. 새 tests 필요. + +### 심볼 참조 + +- renamed/removed symbol: none. + +### 분할 판단 + +- split policy를 먼저 평가했다. +- shared task group: `agent-task/m-backtest-scenario-automation/`. +- `01_scenario_matrix`: matrix schema/validation foundation, depends_on=[]. +- `02+01_batch_runner`: matrix 실행 및 CLI output, depends_on=[01]. +- `03+01,02_result_handoff`: fixtures/docs/handoff evidence, depends_on=[01,02]. +- 이 subtask는 선행 의존성이 없다. + +### 범위 결정 근거 + +- `services/api/**`, `services/worker/**`, `packages/contracts/**`, `packages/domain/**`는 제외한다. 이 subtask는 CLI YAML schema와 validation만 다루며 runtime protobuf 계약을 바꾸지 않는다. +- result fixture와 `headless_validation.md`는 03에서 다룬다. +- batch 실행, polling output 변경은 02에서 다룬다. + +### 빌드 등급 + +- build: `local-G06`, review: `local-G06`. +- 이유: bounded CLI schema/test change이며 deterministic Go tests로 검증 가능하다. + +## 구현 체크리스트 + +- [ ] `Scenario`에 optional backtest matrix schema를 추가하고 matrix-only scenario를 dry-run validation 대상으로 허용한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` +- [ ] matrix combination expansion helper를 deterministic order로 추가하고 validation tests를 작성한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` +- [ ] CLI `operator scenario validate`가 matrix-only scenario를 유효하게 보고하는 test를 추가한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## [API-1] Matrix Schema And Validation + +### 문제 + +[scenario.go](/config/workspace/alt/apps/cli/internal/operator/scenario.go:386)는 `Scenario`가 `steps`만 실행 단위로 갖는다. [scenario.go](/config/workspace/alt/apps/cli/internal/operator/scenario.go:426)는 `len(s.Steps) == 0`이면 무조건 오류를 반환해 matrix-only YAML을 표현할 수 없다. + +### 해결 방법 + +`Scenario`에 optional `Matrix *BacktestScenarioMatrix`를 추가하고, `steps`가 비어 있어도 matrix가 있으면 유효하게 둔다. Matrix는 named universe, strategy, timeframe, period 조합을 표현한다. + +Before: + +```go +// apps/cli/internal/operator/scenario.go:386 +type Scenario struct { + Name string `yaml:"name"` + Timeout Duration `yaml:"timeout"` + Universes []UniverseConfig `yaml:"universes"` + Steps []Step `yaml:"steps"` +} +``` + +After: + +```go +type Scenario struct { + Name string `yaml:"name"` + Timeout Duration `yaml:"timeout"` + Universes []UniverseConfig `yaml:"universes"` + Matrix *BacktestScenarioMatrix `yaml:"matrix"` + Steps []Step `yaml:"steps"` +} + +type BacktestScenarioMatrix struct { + Universes []string `yaml:"universes"` + Strategies []string `yaml:"strategies"` + Timeframes []string `yaml:"timeframes"` + Periods []MatrixPeriod `yaml:"periods"` +} + +type MatrixPeriod struct { + ID string `yaml:"id"` + FromUnixMs int64 `yaml:"from_unix_ms"` + ToUnixMs int64 `yaml:"to_unix_ms"` +} +``` + +### 수정 파일 및 체크리스트 + +- [ ] `apps/cli/internal/operator/scenario.go`: matrix structs 추가. +- [ ] `apps/cli/internal/operator/scenario.go`: `Validate`에서 `steps` 또는 `matrix` 중 하나를 요구하도록 변경. +- [ ] `apps/cli/internal/operator/scenario_test.go`: matrix-only valid fixture test 추가. + +### 테스트 작성 + +- 작성: `TestValidateBacktestScenarioMatrix`로 `matrix.universes/strategies/timeframes/periods`가 parse되고 유효한지 검증한다. +- 작성: `TestValidateRejectsInvalidBacktestScenarioMatrix`로 empty universe, unknown universe, invalid timeframe, reversed period를 검증한다. + +### 중간 검증 + +```bash +go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +``` + +예상: 두 package 모두 `ok`. + +## [API-2] Deterministic Matrix Expansion + +### 문제 + +[runner.go](/config/workspace/alt/apps/cli/internal/operator/runner.go:129)는 이미 `steps`를 순서대로 실행하지만, matrix 조합을 실행 단위로 펼치는 helper가 없다. 02 subtask가 실행을 맡으려면 01에서 source-of-truth expansion 순서를 제공해야 한다. + +### 해결 방법 + +`Scenario.MatrixRuns() ([]MatrixRun, error)`를 추가한다. 순서는 universe -> strategy -> timeframe -> period로 고정한다. `MatrixRun.ID`는 `______` 형태로 만들고 공백은 validation에서 거부한다. + +Before: + +```go +// apps/cli/internal/operator/scenario.go:420 +// Validate runs the dry-run checks: a named scenario, at least one step, and +// every step having a unique id and a known action. +func (s *Scenario) Validate() error { +``` + +After: + +```go +type MatrixRun struct { + ID string + Universe string + StrategyID string + Timeframe string + FromUnixMs int64 + ToUnixMs int64 +} + +func (s *Scenario) MatrixRuns() ([]MatrixRun, error) { + // Called after Validate, but still returns errors so tests can exercise it directly. +} +``` + +### 수정 파일 및 체크리스트 + +- [ ] `apps/cli/internal/operator/scenario.go`: `MatrixRun`과 `MatrixRuns` 추가. +- [ ] `apps/cli/internal/operator/scenario.go`: matrix validation에서 duplicate generated ID를 거부. +- [ ] `apps/cli/internal/operator/scenario_test.go`: expansion order와 generated IDs 검증. + +### 테스트 작성 + +- 작성: `TestBacktestScenarioMatrixRunsExpandsInDeterministicOrder`. + +### 중간 검증 + +```bash +go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +``` + +예상: 두 package 모두 `ok`. + +## [API-3] CLI Validate Output For Matrix + +### 문제 + +[cli.go](/config/workspace/alt/apps/cli/internal/cli/cli.go:86)는 validate 성공 시 `steps=%d`만 출력한다. matrix-only scenario가 유효해지면 operator가 조합 수를 볼 수 있어야 한다. + +### 해결 방법 + +`operator scenario validate` 출력에 matrix가 있으면 `matrix_runs=`을 추가한다. 기존 step-only scenario output은 유지한다. + +Before: + +```go +// apps/cli/internal/cli/cli.go:86 +fmt.Fprintf(stdout, "scenario=%s status=valid steps=%d\n", sc.Name, len(sc.Steps)) +``` + +After: + +```go +runs, err := sc.MatrixRuns() +if err != nil { + fmt.Fprintf(stderr, "validate failed: %v\n", err) + return exitParseError +} +if len(runs) > 0 { + fmt.Fprintf(stdout, "scenario=%s status=valid steps=%d matrix_runs=%d\n", sc.Name, len(sc.Steps), len(runs)) + return exitOK +} +fmt.Fprintf(stdout, "scenario=%s status=valid steps=%d\n", sc.Name, len(sc.Steps)) +``` + +### 수정 파일 및 체크리스트 + +- [ ] `apps/cli/internal/cli/cli.go`: validate output 보강. +- [ ] `apps/cli/internal/cli/cli_test.go`: matrix-only validate output test 추가. + +### 테스트 작성 + +- 작성: `TestRunScenarioValidateMatrix`에서 `matrix_runs=...` 포함을 검증한다. + +### 중간 검증 + +```bash +go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +``` + +예상: 두 package 모두 `ok`. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `apps/cli/internal/operator/scenario.go` | API-1, API-2 | +| `apps/cli/internal/operator/scenario_test.go` | API-1, API-2 | +| `apps/cli/internal/cli/cli.go` | API-3 | +| `apps/cli/internal/cli/cli_test.go` | API-3 | + +## 최종 검증 + +```bash +go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +``` + +예상: 두 package 모두 `ok`; Go test cache는 fresh 실행이 필요하므로 `-count=1`을 유지한다. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/plan_local_G07_1.log b/agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/plan_local_G07_1.log new file mode 100644 index 0000000..bbd9ace --- /dev/null +++ b/agent-task/archive/2026/06/m-backtest-scenario-automation/01_scenario_matrix/plan_local_G07_1.log @@ -0,0 +1,91 @@ + + +# Plan - REVIEW_API Matrix Period Required Validation + +## 이 파일을 읽는 구현 에이전트에게 + +이 plan의 구현이 끝나면 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 검증 출력, 계획 대비 변경 사항으로 채운다. 구현 중 사용자 결정, 사용자 소유 외부 환경, 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정 필요 내용과 증거를 남기고 멈춘다. 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 명령 재실행이나 산출물 수집으로 해소 가능한 evidence 공백은 사용자 리뷰 요청이 아니다. + +## 배경 + +이 follow-up은 `code_review_local_G06_0.log`의 Required 이슈만 보완한다. Matrix period가 `from_unix_ms` 또는 `to_unix_ms`를 누락해도 Go zero value `0`으로 들어와 현재 `Validate`를 통과한다. 기존 `start_backtest` request validation은 두 timestamp를 모두 필수로 취급하므로, matrix dry-run validation도 같은 기준을 적용해야 한다. + +## 사용자 리뷰 요청 흐름 + +구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 prompt는 금지이며, code-review가 해당 요청을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md` +- Task ids: + - `scenario-matrix`: universe, 기간, frequency, strategy 조합을 YAML scenario에서 표현하고 검증할 수 있게 한다. +- Completion mode: check-on-pass + +## 범위 결정 근거 + +- 수정 범위는 `apps/cli/internal/operator/scenario.go`와 `apps/cli/internal/operator/scenario_test.go`로 제한한다. +- CLI validate는 `operator.LoadScenario`를 통해 같은 validation을 사용하므로 별도 CLI behavior 변경은 하지 않는다. +- Batch runner, output contract, fixture handoff는 후속 subtask 범위이므로 건드리지 않는다. + +## 구현 체크리스트 + +- [ ] Matrix period validation이 `from_unix_ms` 또는 `to_unix_ms` 누락을 거부하도록 보강한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` +- [ ] invalid matrix tests에 missing `from_unix_ms`와 missing `to_unix_ms` 케이스를 추가한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## [REVIEW_API-1] Matrix Period Required Timestamps + +### 문제 + +[scenario.go](/config/workspace/alt/apps/cli/internal/operator/scenario.go:528)는 matrix period마다 `ID`와 inverted range만 검사한다. YAML에서 `from_unix_ms` 또는 `to_unix_ms`가 빠지면 값이 `0`이 되지만, `0 > 200`은 false이고 `100 > 0`만 일부 케이스를 잡는다. 이 때문에 dry-run validation을 통과한 matrix run이 나중에 invalid `start_backtest` request가 될 수 있다. + +### 해결 방법 + +`MatrixPeriod` validation을 기존 `start_backtest` request validation과 맞춘다. + +Before: + +```go +if p.FromUnixMs > p.ToUnixMs { + return fmt.Errorf("matrix period %q: from_unix_ms %d cannot be after to_unix_ms %d", p.ID, p.FromUnixMs, p.ToUnixMs) +} +``` + +After: + +```go +if p.FromUnixMs == 0 || p.ToUnixMs == 0 { + return fmt.Errorf("matrix period %q: from_unix_ms and to_unix_ms are required", p.ID) +} +if p.FromUnixMs > p.ToUnixMs { + return fmt.Errorf("matrix period %q: from_unix_ms %d cannot be after to_unix_ms %d", p.ID, p.FromUnixMs, p.ToUnixMs) +} +``` + +### 수정 파일 및 체크리스트 + +- [ ] `apps/cli/internal/operator/scenario.go`: matrix period missing timestamp validation 추가. +- [ ] `apps/cli/internal/operator/scenario_test.go`: missing `from_unix_ms` case 추가. +- [ ] `apps/cli/internal/operator/scenario_test.go`: missing `to_unix_ms` case 추가. + +### 테스트 작성 + +- `TestValidateRejectsInvalidBacktestScenarioMatrix` table에 두 케이스를 추가한다. +- 기대 에러는 필수 timestamp 누락을 명확히 드러내는 문자열이어야 한다. +- 기존 reversed period, duplicate generated ID tests는 유지한다. + +### 중간 검증 + +```bash +go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +``` + +예상: 두 package 모두 `ok`. + +## 최종 검증 + +```bash +go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +``` + +예상: 두 package 모두 `ok`; Go test cache는 fresh 실행이 필요하므로 `-count=1`을 유지한다. diff --git a/agent-task/m-backtest-scenario-automation/02+01_batch_runner/CODE_REVIEW-cloud-G07.md b/agent-task/m-backtest-scenario-automation/02+01_batch_runner/CODE_REVIEW-cloud-G07.md new file mode 100644 index 0000000..b82e975 --- /dev/null +++ b/agent-task/m-backtest-scenario-automation/02+01_batch_runner/CODE_REVIEW-cloud-G07.md @@ -0,0 +1,115 @@ + + +# Code Review Reference - API + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only. + +## 개요 + +date=2026-06-15 +task=m-backtest-scenario-automation/02+01_batch_runner, plan=0, tag=API + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md` +- Task ids: + - `batch-runner`: 여러 backtest run을 순차 실행하고 각 run의 polling 결과를 stable text/JSONL로 출력한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 판정 append, active 파일 log rename, PASS 시 `complete.log` 작성과 archive 이동까지 끝난 상태를 의미합니다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [API-1] Connect Matrix To Runner | [ ] | +| [API-2] Stable Batch Output | [ ] | +| [API-3] Polling And Exit Semantics | [ ] | + +## 구현 체크리스트 + +- [ ] 01 subtask completion을 확인한 뒤 matrix batch path를 `RunScenario`에 연결한다. 검증: `go test -count=1 ./apps/cli/internal/operator` +- [ ] 각 matrix run을 start 후 terminal status까지 poll하고 stable text/JSONL fields를 출력한다. 검증: `go test -count=1 ./apps/cli/internal/operator` +- [ ] batch success, terminal failed, timeout/transport, expectation mismatch behavior를 tests로 고정한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./services/api/internal/socket ./services/worker/internal/socket ./services/worker/internal/jobs` +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. + +- [ ] predecessor `01` complete evidence를 확인했다. +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] PASS이면 active task directory를 archive로 이동하고 `complete.log`를 작성한다. +- [ ] PASS이고 task group이 `m-backtest-scenario-automation`이면 완료 이벤트 메타데이터를 보고한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- Matrix batch path가 기존 step scenario behavior를 깨지 않는지 확인한다. +- Text/JSONL output keys가 deterministic이고 fixtures로 소비 가능하게 유지되는지 확인한다. +- Polling timeout과 terminal failed status의 exit code 분류가 계획과 일치하는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +### API-1 중간 검증 + +```bash +$ go test -count=1 ./apps/cli/internal/operator +(output) +``` + +### API-2 중간 검증 + +```bash +$ go test -count=1 ./apps/cli/internal/operator +(output) +``` + +### API-3 중간 검증 + +```bash +$ go test -count=1 ./apps/cli/internal/operator ./services/api/internal/socket ./services/worker/internal/socket ./services/worker/internal/jobs +(output) +``` + +### 최종 검증 + +```bash +$ go test -count=1 ./apps/cli/internal/operator ./services/api/internal/socket ./services/worker/internal/socket ./services/worker/internal/jobs +(output) +``` diff --git a/agent-task/m-backtest-scenario-automation/02+01_batch_runner/PLAN-cloud-G07.md b/agent-task/m-backtest-scenario-automation/02+01_batch_runner/PLAN-cloud-G07.md new file mode 100644 index 0000000..4771153 --- /dev/null +++ b/agent-task/m-backtest-scenario-automation/02+01_batch_runner/PLAN-cloud-G07.md @@ -0,0 +1,278 @@ + + +# Plan - API Batch Runner + +## 이 파일을 읽는 구현 에이전트에게 + +이 plan의 구현이 끝나면 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 검증 출력, 계획 대비 변경 사항으로 채운다. 구현 중 사용자 결정, 사용자 소유 외부 환경, 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정 필요 내용과 증거를 남기고 멈춘다. 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 명령 재실행이나 산출물 수집으로 해소 가능한 evidence 공백은 사용자 리뷰 요청이 아니다. + +## 배경 + +01 subtask가 matrix schema와 deterministic expansion을 제공하면, `operator scenario run`은 그 matrix를 실제 backtest run batch로 실행해야 한다. 현재 [runner.go](/config/workspace/alt/apps/cli/internal/operator/runner.go:129)는 사용자가 직접 작성한 `steps`만 순차 실행하고, 여러 generated run의 start/poll lifecycle을 요약하는 batch path가 없다. + +## 사용자 리뷰 요청 흐름 + +구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 prompt는 금지이며, code-review가 해당 요청을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md` +- Task ids: + - `batch-runner`: 여러 backtest run을 순차 실행하고 각 run의 polling 결과를 stable text/JSONL로 출력한다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-ops/rules/project/rules.md` +- `agent-ops/rules/private/rules.md` +- `agent-ops/rules/common/rules-roadmap.md` +- `agent-ops/rules/project/domain/operations/rules.md` +- `agent-ops/rules/project/domain/api/rules.md` +- `agent-ops/rules/project/domain/worker/rules.md` +- `agent-test/local/rules.md` +- `agent-test/local/operations-smoke.md` +- `agent-test/local/api-smoke.md` +- `agent-test/local/worker-smoke.md` +- `agent-roadmap/current.md` +- `agent-roadmap/phase/backtest-loop/PHASE.md` +- `agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md` +- `apps/cli/go.mod` +- `apps/cli/internal/operator/scenario.go` +- `apps/cli/internal/operator/runner.go` +- `apps/cli/internal/operator/output.go` +- `apps/cli/internal/operator/client.go` +- `apps/cli/internal/operator/client_test.go` +- `apps/cli/internal/operator/runner_backtest_test.go` +- `apps/cli/internal/operator/runner_error_test.go` +- `apps/cli/internal/cli/cli.go` +- `apps/cli/internal/cli/cli_test.go` +- `apps/cli/testdata/operator/backtest_run_request.yaml` +- `apps/cli/testdata/operator/backtest_run_polling.yaml` +- `apps/cli/testdata/operator/backtest_result_summary.yaml` + +### 테스트 환경 규칙 + +- 선택 env: `local`. +- 읽은 profile: `operations-smoke`, `api-smoke`, `worker-smoke`. +- 적용 명령: roadmap task 검증 명령 `go test -count=1 ./apps/cli/internal/operator ./services/api/internal/socket ./services/worker/internal/socket ./services/worker/internal/jobs`. +- CLI baseline으로 `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli`를 실행했고 exit 0이었다. +- 이 plan은 API/worker 코드를 변경하지 않지만 boundary regression 확인을 위해 milestone 명령을 최종 검증에 둔다. + +### 테스트 커버리지 공백 + +- generated matrix runs를 순차 start/poll하는 runner path: 기존 테스트 없음. +- batch JSONL/text output의 `matrix_run_id`, universe, strategy, timeframe, period field: 기존 테스트 없음. +- terminal `failed` run을 transport/app failure가 아닌 terminal lifecycle evidence로 출력하는 path: 기존 테스트 없음. +- 기존 single scenario steps, typed error, transport failure: 기존 tests가 커버한다. + +### 심볼 참조 + +- renamed/removed symbol: none. +- `RunScenario` call sites: `apps/cli/internal/cli/cli.go`, `apps/cli/internal/operator/runner_error_test.go`, `runner_market_test.go`, `runner_live_test.go`, `runner_paper_test.go`. + +### 분할 판단 + +- split policy를 먼저 평가했다. +- shared task group: `agent-task/m-backtest-scenario-automation/`. +- 현재 subtask: `02+01_batch_runner`. +- predecessor `01`: missing active/archive `complete.log` at plan creation. 구현은 `agent-task/m-backtest-scenario-automation/01_scenario_matrix/complete.log` 또는 matching archive complete가 생긴 뒤 시작한다. + +### 범위 결정 근거 + +- Protobuf schema, API socket handlers, worker execution/storage는 변경하지 않는다. Existing `StartBacktestRequest`, `GetBacktestRunRequest`, result query surface를 CLI에서 orchestration한다. +- `headless_validation.md`와 expected fixture 갱신은 03에서 수행한다. +- 새 dependency는 추가하지 않는다. `apps/cli/go.mod`에는 이미 YAML과 proto-socket dependencies가 있다. + +### 빌드 등급 + +- build: `cloud-G07`, review: `cloud-G07`. +- 이유: CLI workflow, stdout/JSONL contract, polling/timeout/exit-code behavior가 중심이며 여러 test contexts를 함께 판단해야 한다. + +## 구현 체크리스트 + +- [ ] 01 subtask completion을 확인한 뒤 matrix batch path를 `RunScenario`에 연결한다. 검증: `go test -count=1 ./apps/cli/internal/operator` +- [ ] 각 matrix run을 start 후 terminal status까지 poll하고 stable text/JSONL fields를 출력한다. 검증: `go test -count=1 ./apps/cli/internal/operator` +- [ ] batch success, terminal failed, timeout/transport, expectation mismatch behavior를 tests로 고정한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./services/api/internal/socket ./services/worker/internal/socket ./services/worker/internal/jobs` +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 의존 관계 및 구현 순서 + +- 이 directory 이름의 runtime dependency는 `01`이다. +- 구현 시작 전 `agent-task/m-backtest-scenario-automation/01_scenario_matrix/complete.log` 또는 matching archive complete가 필요하다. + +## [API-1] Connect Matrix To Runner + +### 문제 + +[runner.go](/config/workspace/alt/apps/cli/internal/operator/runner.go:96)는 `RunScenario`에서 단일 `Scenario`를 열고 [runner.go](/config/workspace/alt/apps/cli/internal/operator/runner.go:129)의 `for _, step := range sc.Steps`만 실행한다. 01의 matrix expansion이 있어도 runner가 이를 사용하지 않으면 batch automation이 동작하지 않는다. + +### 해결 방법 + +`RunScenario`에서 `runs, err := sc.MatrixRuns()`를 호출하고, `len(runs) > 0`이면 `runMatrixBatch` path를 실행한다. Matrix가 없는 기존 step scenario는 현 동작을 유지한다. + +Before: + +```go +// apps/cli/internal/operator/runner.go:126 +passed := 0 +exitCode := codeOK +savedValues := make(map[string]string) +for _, step := range sc.Steps { +``` + +After: + +```go +runs, err := sc.MatrixRuns() +if err != nil { + out.WriteStep(StepEvent{Scenario: sc.Name, Status: statusError, ErrorCode: "invalid_matrix", ErrorMessage: err.Error()}) + out.WriteSummary(RunSummary{Scenario: sc.Name, Status: summaryFailed, Steps: 0, Passed: 0, ExitCode: codeBadInput}) + return codeBadInput +} +if len(runs) > 0 { + return runMatrixBatch(ctx, client, sc.Name, runs, timeout, out) +} +``` + +### 수정 파일 및 체크리스트 + +- [ ] `apps/cli/internal/operator/runner.go`: matrix path branch 추가. +- [ ] `apps/cli/internal/operator/runner_backtest_test.go`: matrix branch가 기존 step branch와 분리되는지 검증. + +### 테스트 작성 + +- 작성: `TestRunBacktestMatrixRunsSequentially`로 two-run matrix가 두 번 `StartBacktest`와 polling을 호출하는지 검증한다. + +### 중간 검증 + +```bash +go test -count=1 ./apps/cli/internal/operator +``` + +예상: package `ok`. + +## [API-2] Stable Batch Output + +### 문제 + +[output.go](/config/workspace/alt/apps/cli/internal/operator/output.go:25)는 backtest output에 `run_id/run_status`는 있지만 generated matrix run identity, universe, strategy, timeframe, period를 담지 않는다. Batch output이 stable하지 않으면 후속 handoff가 조합별 결과를 소비할 수 없다. + +### 해결 방법 + +`StepEvent`에 matrix fields를 추가하고 text/JSONL renderer에 조건부 출력한다. + +Before: + +```go +// apps/cli/internal/operator/output.go:34 +// Backtest-specific output fields +RunID string +RunStatus string +StartingCash string +``` + +After: + +```go +// Batch/matrix-specific output fields. +MatrixRunID string +Universe string +StrategyID string +Timeframe string +PeriodID string + +// Backtest-specific output fields. +RunID string +RunStatus string +StartingCash string +``` + +### 수정 파일 및 체크리스트 + +- [ ] `apps/cli/internal/operator/output.go`: `StepEvent` fields 추가. +- [ ] `apps/cli/internal/operator/output.go`: text output keys `matrix_run_id`, `universe`, `strategy_id`, `timeframe`, `period_id` 추가. +- [ ] `apps/cli/internal/operator/output.go`: JSONL fields 추가. +- [ ] `apps/cli/internal/operator/runner_backtest_test.go`: text and JSONL output keys 검증. + +### 테스트 작성 + +- 작성: matrix run test에서 text output key를 검증한다. +- 작성: JSONL mode test에서 `matrix_run_id`와 `period_id` key를 검증한다. + +### 중간 검증 + +```bash +go test -count=1 ./apps/cli/internal/operator +``` + +예상: package `ok`. + +## [API-3] Polling And Exit Semantics + +### 문제 + +[runner.go](/config/workspace/alt/apps/cli/internal/operator/runner.go:338)는 explicit `poll_backtest_run` step 하나에 대해서만 polling을 수행한다. Batch path는 각 generated run마다 start/poll을 수행하고, terminal `succeeded/failed/canceled`, timeout, transport failure, expectation mismatch를 기존 exit code 계약과 일관되게 분류해야 한다. + +### 해결 방법 + +`runMatrixBatch`는 각 run마다 start event와 poll event를 쓴다. Terminal `succeeded/failed/canceled`는 lifecycle evidence로 `status=ok` 처리한다. Polling timeout은 `codeTransport`로 stop한다. Matrix-level optional expected terminal status를 구현하게 되면 불일치는 `status=mismatch`, `codeAppError`로 처리한다. + +Before: + +```go +// apps/cli/internal/operator/runner.go:387 +run := resp.GetRun() +if run != nil { + statusStr := strings.ToLower(strings.TrimPrefix(run.GetStatus().String(), "BACKTEST_RUN_STATUS_")) +``` + +After: + +```go +func runMatrixBatch(ctx context.Context, client *APIClient, scenario string, runs []MatrixRun, timeout time.Duration, out *Writer) int { + // start each run, poll until terminal, write per-run events, preserve exit code contract. +} +``` + +### 수정 파일 및 체크리스트 + +- [ ] `apps/cli/internal/operator/runner.go`: `runMatrixBatch`, `pollBacktestRun` helper 추출 또는 추가. +- [ ] `apps/cli/internal/operator/client_test.go`: fake API에 sequential start/poll canned response hook 추가. +- [ ] `apps/cli/internal/operator/runner_backtest_test.go`: success, terminal failed, polling timeout tests 추가. +- [ ] `apps/cli/internal/operator/runner_error_test.go`: expectation mismatch가 exit 1로 남는지 필요 시 보강. + +### 테스트 작성 + +- 작성: `TestRunBacktestMatrixReportsTerminalFailedAsLifecycleResult`. +- 작성: `TestRunBacktestMatrixPollingTimeoutExitsTransport`. +- 작성: `TestRunBacktestMatrixExpectedStatusMismatchExitsOne` if matrix expectation field exists after 01/02 implementation. + +### 중간 검증 + +```bash +go test -count=1 ./apps/cli/internal/operator ./services/api/internal/socket ./services/worker/internal/socket ./services/worker/internal/jobs +``` + +예상: 모든 package `ok`. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `apps/cli/internal/operator/runner.go` | API-1, API-3 | +| `apps/cli/internal/operator/output.go` | API-2 | +| `apps/cli/internal/operator/client_test.go` | API-3 | +| `apps/cli/internal/operator/runner_backtest_test.go` | API-1, API-2, API-3 | +| `apps/cli/internal/operator/runner_error_test.go` | API-3 | + +## 최종 검증 + +```bash +go test -count=1 ./apps/cli/internal/operator ./services/api/internal/socket ./services/worker/internal/socket ./services/worker/internal/jobs +``` + +예상: 모든 package `ok`; Go test cache는 fresh 실행이 필요하므로 `-count=1`을 유지한다. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/agent-task/m-backtest-scenario-automation/03+01,02_result_handoff/CODE_REVIEW-local-G05.md b/agent-task/m-backtest-scenario-automation/03+01,02_result_handoff/CODE_REVIEW-local-G05.md new file mode 100644 index 0000000..3a02a30 --- /dev/null +++ b/agent-task/m-backtest-scenario-automation/03+01,02_result_handoff/CODE_REVIEW-local-G05.md @@ -0,0 +1,120 @@ + + +# Code Review Reference - TEST + +> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.** +> The task is NOT complete until every implementation-owned section below is filled in. +> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving. +> Fill implementation-owned sections, then stop with active files in place and report ready for review. +> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. +> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation. +> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only. + +## 개요 + +date=2026-06-15 +task=m-backtest-scenario-automation/03+01,02_result_handoff, plan=0, tag=TEST + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md` +- Task ids: + - `result-handoff`: 성공, 실패, timeout, expectation mismatch 결과를 fixture와 `headless_validation.md`에 남겨 후속 운영 표면이 소비할 수 있게 한다. +- Completion mode: check-on-pass + +## 이 파일을 읽는 리뷰 에이전트에게 + +> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다. + +각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. +리뷰 완료는 판정 append, active 파일 log rename, PASS 시 `complete.log` 작성과 archive 이동까지 끝난 상태를 의미합니다. + +--- + +## 구현 항목별 완료 여부 + +| 항목 | 완료 여부 | +|------|---------| +| [TEST-1] Batch Fixture Set | [ ] | +| [TEST-2] Handoff Matrix Rows | [ ] | +| [TEST-3] Handoff Test Expansion | [ ] | + +## 구현 체크리스트 + +- [ ] Batch result success/failure/timeout/mismatch YAML fixture와 expected JSONL fixture를 추가한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` +- [ ] `headless_validation.md` handoff matrix에 새 backtest batch rows와 output key/exit-code 계약을 추가한다. 검증: `rg --sort path -n "backtest" apps/cli/testdata/operator/headless_validation.md apps/cli/testdata/operator` +- [ ] `handoff_test.go`가 새 fixtures와 promised keys를 검증하도록 확장한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 코드리뷰 전용 체크리스트 + +> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다. + +- [ ] predecessor `01` 및 `02` complete evidence를 확인했다. +- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다. +- [ ] 판정과 Required/Suggested/Nit 분류가 서로 일치한다. +- [ ] PASS이면 active task directory를 archive로 이동하고 `complete.log`를 작성한다. +- [ ] PASS이고 task group이 `m-backtest-scenario-automation`이면 완료 이벤트 메타데이터를 보고한다. + +## 계획 대비 변경 사항 + +_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._ + +## 주요 설계 결정 + +_구현 에이전트가 주요 설계 결정 사항을 기록한다._ + +## 사용자 리뷰 요청 + +_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._ + +- 상태: 없음 +- 사유 유형: 없음 +- 결정 필요: 없음 +- 차단 근거: 없음 +- 실행한 검증/명령: 없음 +- 자동 후속 불가 이유: 없음 +- 재개 조건: 없음 + +## 리뷰어를 위한 체크포인트 + +- 네 batch fixture가 success, terminal failed, timeout, mismatch를 각각 대표하는지 확인한다. +- Handoff matrix의 promised keys와 expected JSONL keys가 일치하는지 확인한다. +- UI 구현 또는 화면 설계가 이 subtask에 섞이지 않았는지 확인한다. + +## 검증 결과 + +_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._ + +### TEST-1 중간 검증 + +```bash +$ go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +(output) +``` + +### TEST-2 중간 검증 + +```bash +$ rg --sort path -n "backtest" apps/cli/testdata/operator/headless_validation.md apps/cli/testdata/operator +(output) +``` + +### TEST-3 중간 검증 + +```bash +$ go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +(output) +``` + +### 최종 검증 + +```bash +$ go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +(output) +``` + +```bash +$ rg --sort path -n "backtest" apps/cli/testdata/operator/headless_validation.md apps/cli/testdata/operator +(output) +``` diff --git a/agent-task/m-backtest-scenario-automation/03+01,02_result_handoff/PLAN-local-G05.md b/agent-task/m-backtest-scenario-automation/03+01,02_result_handoff/PLAN-local-G05.md new file mode 100644 index 0000000..47985d0 --- /dev/null +++ b/agent-task/m-backtest-scenario-automation/03+01,02_result_handoff/PLAN-local-G05.md @@ -0,0 +1,272 @@ + + +# Plan - TEST Result Handoff + +## 이 파일을 읽는 구현 에이전트에게 + +이 plan의 구현이 끝나면 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 검증 출력, 계획 대비 변경 사항으로 채운다. 구현 중 사용자 결정, 사용자 소유 외부 환경, 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정 필요 내용과 증거를 남기고 멈춘다. 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 명령 재실행이나 산출물 수집으로 해소 가능한 evidence 공백은 사용자 리뷰 요청이 아니다. + +## 배경 + +`result-handoff`는 scenario automation 결과를 후속 운영 표면이 소비할 수 있게 fixture와 `headless_validation.md`에 남기는 작업이다. 현재 handoff 문서는 기존 backtest 단일 request/poll/result rows만 갖고 있으며, matrix batch success/failure/timeout/mismatch를 별도 evidence로 고정하지 않는다. + +## 사용자 리뷰 요청 흐름 + +구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 prompt는 금지이며, code-review가 해당 요청을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다. + +## Roadmap Targets + +- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md` +- Task ids: + - `result-handoff`: 성공, 실패, timeout, expectation mismatch 결과를 fixture와 `headless_validation.md`에 남겨 후속 운영 표면이 소비할 수 있게 한다. +- Completion mode: check-on-pass + +## 분석 결과 + +### 읽은 파일 + +- `agent-ops/rules/project/rules.md` +- `agent-ops/rules/private/rules.md` +- `agent-ops/rules/common/rules-roadmap.md` +- `agent-ops/rules/project/domain/operations/rules.md` +- `agent-test/local/rules.md` +- `agent-test/local/operations-smoke.md` +- `agent-roadmap/current.md` +- `agent-roadmap/phase/backtest-loop/PHASE.md` +- `agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md` +- `apps/cli/internal/operator/handoff_test.go` +- `apps/cli/testdata/operator/headless_validation.md` +- `apps/cli/testdata/operator/backtest_run_request.yaml` +- `apps/cli/testdata/operator/backtest_run_polling.yaml` +- `apps/cli/testdata/operator/backtest_result_summary.yaml` +- `apps/cli/testdata/operator/invalid_request_matrix.yaml` +- `apps/cli/testdata/operator/expected/backtest_run_request.jsonl` +- `apps/cli/testdata/operator/expected/backtest_run_polling.jsonl` +- `apps/cli/testdata/operator/expected/backtest_result_summary.jsonl` +- `apps/cli/testdata/operator/expected/invalid_request_matrix.jsonl` +- `apps/cli/internal/operator/runner_backtest_test.go` +- `apps/cli/internal/operator/runner_error_test.go` +- `apps/cli/internal/cli/cli_test.go` + +### 테스트 환경 규칙 + +- 선택 env: `local`. +- `agent-test/local/rules.md`와 `agent-test/local/operations-smoke.md`를 읽었다. +- 적용 명령: roadmap task 검증 명령 `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`. +- CLI baseline으로 `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli`를 실행했고 exit 0이었다. + +### 테스트 커버리지 공백 + +- matrix batch success/failure/timeout/mismatch expected JSONL fixture existence: 기존 coverage 없음. +- handoff matrix row가 새 fixtures와 expected output keys를 참조하는지: existing `handoff_test.go` 구조로 확장 가능. +- 기존 backtest request/poll/result fixtures: existing coverage 있음. + +### 심볼 참조 + +- renamed/removed symbol: none. + +### 분할 판단 + +- split policy를 먼저 평가했다. +- shared task group: `agent-task/m-backtest-scenario-automation/`. +- 현재 subtask: `03+01,02_result_handoff`. +- predecessor `01`: missing active/archive `complete.log` at plan creation. +- predecessor `02`: missing active/archive `complete.log` at plan creation. +- 구현은 두 predecessor complete evidence가 생긴 뒤 시작한다. + +### 범위 결정 근거 + +- Runtime behavior 변경은 01/02에 속한다. 이 subtask는 fixtures, handoff docs, handoff tests만 다룬다. +- Flutter UI, chart/layout, operator console screen definition은 운영 UI gate 때문에 제외한다. +- API/worker/contracts/domain code는 제외한다. + +### 빌드 등급 + +- build: `local-G05`, review: `local-G05`. +- 이유: docs/fixtures/test handoff change이며 deterministic file and Go tests로 검증 가능하다. + +## 구현 체크리스트 + +- [ ] Batch result success/failure/timeout/mismatch YAML fixture와 expected JSONL fixture를 추가한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` +- [ ] `headless_validation.md` handoff matrix에 새 backtest batch rows와 output key/exit-code 계약을 추가한다. 검증: `rg --sort path -n "backtest" apps/cli/testdata/operator/headless_validation.md apps/cli/testdata/operator` +- [ ] `handoff_test.go`가 새 fixtures와 promised keys를 검증하도록 확장한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli` +- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다. + +## 의존 관계 및 구현 순서 + +- 이 directory 이름의 runtime dependency는 `01,02`다. +- 구현 시작 전 `01_scenario_matrix`와 `02+01_batch_runner`의 `complete.log` 또는 matching archive complete가 필요하다. + +## [TEST-1] Batch Fixture Set + +### 문제 + +[headless_validation.md](/config/workspace/alt/apps/cli/testdata/operator/headless_validation.md:35)는 existing `backtest_run_request` row를 다루고, [headless_validation.md](/config/workspace/alt/apps/cli/testdata/operator/headless_validation.md:36)는 `backtest_run_polling` row를 다룬다. Batch matrix 결과 success/failure/timeout/mismatch를 대표하는 fixtures가 없다. + +### 해결 방법 + +다음 fixture set을 추가한다. + +- `apps/cli/testdata/operator/backtest_matrix_success.yaml` +- `apps/cli/testdata/operator/backtest_matrix_failed.yaml` +- `apps/cli/testdata/operator/backtest_matrix_timeout.yaml` +- `apps/cli/testdata/operator/backtest_matrix_mismatch.yaml` +- matching expected JSONL files under `apps/cli/testdata/operator/expected/`. + +Before: + +```text +apps/cli/testdata/operator/backtest_run_request.yaml +apps/cli/testdata/operator/backtest_run_polling.yaml +apps/cli/testdata/operator/backtest_result_summary.yaml +``` + +After: + +```text +apps/cli/testdata/operator/backtest_matrix_success.yaml +apps/cli/testdata/operator/backtest_matrix_failed.yaml +apps/cli/testdata/operator/backtest_matrix_timeout.yaml +apps/cli/testdata/operator/backtest_matrix_mismatch.yaml +``` + +### 수정 파일 및 체크리스트 + +- [ ] `apps/cli/testdata/operator/backtest_matrix_success.yaml` 추가. +- [ ] `apps/cli/testdata/operator/backtest_matrix_failed.yaml` 추가. +- [ ] `apps/cli/testdata/operator/backtest_matrix_timeout.yaml` 추가. +- [ ] `apps/cli/testdata/operator/backtest_matrix_mismatch.yaml` 추가. +- [ ] matching `apps/cli/testdata/operator/expected/*.jsonl` 추가. + +### 테스트 작성 + +- 작성: `handoff_test.go` required scenario list에 네 fixtures를 포함해 missing fixture를 fail하게 한다. + +### 중간 검증 + +```bash +go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +``` + +예상: 두 package 모두 `ok`. + +## [TEST-2] Handoff Matrix Rows + +### 문제 + +[headless_validation.md](/config/workspace/alt/apps/cli/testdata/operator/headless_validation.md:30)의 matrix는 command/input/expected output/keys/exit code를 행 단위로 관리한다. 새 batch fixtures가 있어도 matrix row가 없으면 후속 운영 표면이 어떤 evidence를 소비해야 하는지 알 수 없다. + +### 해결 방법 + +네 batch rows를 추가하고 output keys를 batch output contract와 맞춘다. + +Before: + +```markdown +| `backtest_result_summary` | `alt operator scenario run --file testdata/operator/backtest_result_summary.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | ... | +``` + +After: + +```markdown +| `backtest_matrix_success` | `alt operator scenario run --file testdata/operator/backtest_matrix_success.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | ... | +| `backtest_matrix_failed` | `alt operator scenario run --file testdata/operator/backtest_matrix_failed.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | ... | +| `backtest_matrix_timeout` | `alt operator scenario run --file testdata/operator/backtest_matrix_timeout.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | ... | +| `backtest_matrix_mismatch` | `alt operator scenario run --file testdata/operator/backtest_matrix_mismatch.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | ... | +``` + +### 수정 파일 및 체크리스트 + +- [ ] `apps/cli/testdata/operator/headless_validation.md`: 새 rows 추가. +- [ ] 각 row의 expected output keys에 `matrix_run_id`, `universe`, `strategy_id`, `timeframe`, `period_id`, `run_id`, `run_status`, `exit_code` 포함. +- [ ] expected exit code는 success/failed terminal `0`, timeout `3`, mismatch `1`로 기록. + +### 테스트 작성 + +- 기존 `TestHandoffExpectedFixturesWithMatrixPromisedKeys`를 통과하도록 matrix keys와 expected JSONL을 맞춘다. + +### 중간 검증 + +```bash +rg --sort path -n "backtest" apps/cli/testdata/operator/headless_validation.md apps/cli/testdata/operator +``` + +예상: 새 batch scenario, input fixture, expected fixture path가 모두 검색된다. + +## [TEST-3] Handoff Test Expansion + +### 문제 + +[handoff_test.go](/config/workspace/alt/apps/cli/internal/operator/handoff_test.go:16)의 `requiredScenarios`는 기존 scenarios만 요구한다. 새 batch fixtures를 추가해도 test가 required handoff coverage로 강제하지 않으면 drift가 생긴다. + +### 해결 방법 + +`requiredScenarios`와 `requiredScenarioKeys`를 새 batch scenarios로 확장하고, timeout/mismatch fixture도 valid JSONL 및 promised keys check를 통과하게 한다. + +Before: + +```go +// apps/cli/internal/operator/handoff_test.go:20 +"backtest_run_request", +"backtest_run_polling", +"backtest_result_summary", +``` + +After: + +```go +"backtest_run_request", +"backtest_run_polling", +"backtest_result_summary", +"backtest_matrix_success", +"backtest_matrix_failed", +"backtest_matrix_timeout", +"backtest_matrix_mismatch", +``` + +### 수정 파일 및 체크리스트 + +- [ ] `apps/cli/internal/operator/handoff_test.go`: `requiredScenarios` 확장. +- [ ] `apps/cli/internal/operator/handoff_test.go`: `requiredScenarioKeys` 확장. +- [ ] 필요한 경우 row-level checks를 batch rows에도 적용. + +### 테스트 작성 + +- 기존 handoff tests 확장으로 충분하다. 별도 새 test function은 batch row-specific invariant가 필요할 때만 추가한다. + +### 중간 검증 + +```bash +go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +``` + +예상: 두 package 모두 `ok`. + +## 수정 파일 요약 + +| 파일 | 항목 | +|------|------| +| `apps/cli/testdata/operator/backtest_matrix_success.yaml` | TEST-1 | +| `apps/cli/testdata/operator/backtest_matrix_failed.yaml` | TEST-1 | +| `apps/cli/testdata/operator/backtest_matrix_timeout.yaml` | TEST-1 | +| `apps/cli/testdata/operator/backtest_matrix_mismatch.yaml` | TEST-1 | +| `apps/cli/testdata/operator/expected/backtest_matrix_success.jsonl` | TEST-1 | +| `apps/cli/testdata/operator/expected/backtest_matrix_failed.jsonl` | TEST-1 | +| `apps/cli/testdata/operator/expected/backtest_matrix_timeout.jsonl` | TEST-1 | +| `apps/cli/testdata/operator/expected/backtest_matrix_mismatch.jsonl` | TEST-1 | +| `apps/cli/testdata/operator/headless_validation.md` | TEST-2 | +| `apps/cli/internal/operator/handoff_test.go` | TEST-3 | + +## 최종 검증 + +```bash +go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli +``` + +```bash +rg --sort path -n "backtest" apps/cli/testdata/operator/headless_validation.md apps/cli/testdata/operator +``` + +예상: Go tests는 `ok`; `rg`는 새 batch fixture와 handoff rows를 포함해 backtest references를 stable path order로 출력한다. + +모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다. diff --git a/apps/cli/internal/cli/cli.go b/apps/cli/internal/cli/cli.go index 5c249a2..d20f7d5 100644 --- a/apps/cli/internal/cli/cli.go +++ b/apps/cli/internal/cli/cli.go @@ -83,6 +83,16 @@ func runScenarioValidate(args []string, stdout, stderr io.Writer) int { return exitParseError } + runs, err := sc.MatrixRuns() + if err != nil { + fmt.Fprintf(stderr, "validate failed: %v\n", err) + return exitParseError + } + if len(runs) > 0 { + fmt.Fprintf(stdout, "scenario=%s status=valid steps=%d matrix_runs=%d\n", sc.Name, len(sc.Steps), len(runs)) + return exitOK + } + fmt.Fprintf(stdout, "scenario=%s status=valid steps=%d\n", sc.Name, len(sc.Steps)) return exitOK } diff --git a/apps/cli/internal/cli/cli_test.go b/apps/cli/internal/cli/cli_test.go index f3d0379..ef19ac2 100644 --- a/apps/cli/internal/cli/cli_test.go +++ b/apps/cli/internal/cli/cli_test.go @@ -2,6 +2,7 @@ package cli import ( "bytes" + "os" "path/filepath" "strings" "testing" @@ -79,3 +80,40 @@ func TestRunScenarioRunTransportFailure(t *testing.T) { t.Errorf("stdout %q missing status=transport_error", stdout.String()) } } + +func TestRunScenarioValidateMatrix(t *testing.T) { + tempDir := t.TempDir() + path := filepath.Join(tempDir, "matrix_scenario.yaml") + yamlText := ` +name: valid_matrix_cli +universes: + - name: univ1 + provider: kis + selector_kind: watchlist + market: kr + symbols: ["005930"] +matrix: + universes: ["univ1"] + strategies: ["strat1"] + timeframes: ["daily"] + periods: + - id: p1 + from_unix_ms: 1000 + to_unix_ms: 2000 +` + if err := os.WriteFile(path, []byte(yamlText), 0644); err != nil { + t.Fatalf("failed to write temp file: %v", err) + } + + var stdout, stderr bytes.Buffer + code := Run([]string{"operator", "scenario", "validate", "--file", path}, &stdout, &stderr) + if code != 0 { + t.Fatalf("exit code = %d, want 0 (stderr=%q)", code, stderr.String()) + } + out := stdout.String() + for _, want := range []string{"scenario=valid_matrix_cli", "status=valid", "steps=0", "matrix_runs=1"} { + if !strings.Contains(out, want) { + t.Errorf("stdout %q missing %q", out, want) + } + } +} diff --git a/apps/cli/internal/operator/scenario.go b/apps/cli/internal/operator/scenario.go index 74862f5..604aa60 100644 --- a/apps/cli/internal/operator/scenario.go +++ b/apps/cli/internal/operator/scenario.go @@ -385,10 +385,33 @@ type UniverseConfig struct { // Scenario is the top-level operator scenario document. type Scenario struct { - Name string `yaml:"name"` - Timeout Duration `yaml:"timeout"` - Universes []UniverseConfig `yaml:"universes"` - Steps []Step `yaml:"steps"` + Name string `yaml:"name"` + Timeout Duration `yaml:"timeout"` + Universes []UniverseConfig `yaml:"universes"` + Matrix *BacktestScenarioMatrix `yaml:"matrix"` + Steps []Step `yaml:"steps"` +} + +type BacktestScenarioMatrix struct { + Universes []string `yaml:"universes"` + Strategies []string `yaml:"strategies"` + Timeframes []string `yaml:"timeframes"` + Periods []MatrixPeriod `yaml:"periods"` +} + +type MatrixPeriod struct { + ID string `yaml:"id"` + FromUnixMs int64 `yaml:"from_unix_ms"` + ToUnixMs int64 `yaml:"to_unix_ms"` +} + +type MatrixRun struct { + ID string + Universe string + StrategyID string + Timeframe string + FromUnixMs int64 + ToUnixMs int64 } // LoadScenario reads a scenario file and returns it after strict decode and @@ -423,8 +446,8 @@ func (s *Scenario) Validate() error { if s.Name == "" { return fmt.Errorf("scenario name is required") } - if len(s.Steps) == 0 { - return fmt.Errorf("scenario %q has no steps", s.Name) + if len(s.Steps) == 0 && s.Matrix == nil { + return fmt.Errorf("scenario %q has no steps and no matrix", s.Name) } universeByName := make(map[string]UniverseConfig) @@ -464,6 +487,75 @@ func (s *Scenario) Validate() error { universeByName[u.Name] = u } + if s.Matrix != nil { + if len(s.Matrix.Universes) == 0 { + return fmt.Errorf("matrix universes is empty") + } + if len(s.Matrix.Strategies) == 0 { + return fmt.Errorf("matrix strategies is empty") + } + if len(s.Matrix.Timeframes) == 0 { + return fmt.Errorf("matrix timeframes is empty") + } + if len(s.Matrix.Periods) == 0 { + return fmt.Errorf("matrix periods is empty") + } + + for _, uName := range s.Matrix.Universes { + if uName == "" { + return fmt.Errorf("matrix universe name is required") + } + if _, ok := universeByName[uName]; !ok { + return fmt.Errorf("matrix: referenced universe %q not found", uName) + } + } + + for _, strat := range s.Matrix.Strategies { + if strat == "" { + return fmt.Errorf("matrix strategy name is required") + } + } + + for _, tf := range s.Matrix.Timeframes { + if tf == "" { + return fmt.Errorf("matrix timeframe is required") + } + if !validTimeframes[tf] { + return fmt.Errorf("matrix: unsupported timeframe %q", tf) + } + } + + for _, p := range s.Matrix.Periods { + if p.ID == "" { + return fmt.Errorf("matrix period ID is required") + } + if p.FromUnixMs == 0 || p.ToUnixMs == 0 { + return fmt.Errorf("matrix period %q: from_unix_ms and to_unix_ms are required", p.ID) + } + if p.FromUnixMs > p.ToUnixMs { + return fmt.Errorf("matrix period %q: from_unix_ms %d cannot be after to_unix_ms %d", p.ID, p.FromUnixMs, p.ToUnixMs) + } + } + + seenIDs := make(map[string]bool) + for _, uName := range s.Matrix.Universes { + for _, strat := range s.Matrix.Strategies { + for _, tf := range s.Matrix.Timeframes { + for _, p := range s.Matrix.Periods { + id := fmt.Sprintf("%s__%s__%s__%s", uName, strat, tf, p.ID) + if strings.ContainsAny(id, " \t\n\r") { + return fmt.Errorf("matrix generated ID %q contains whitespace", id) + } + if seenIDs[id] { + return fmt.Errorf("matrix generated duplicate ID %q", id) + } + seenIDs[id] = true + } + } + } + } + } + seen := make(map[string]bool, len(s.Steps)) for i := range s.Steps { step := &s.Steps[i] @@ -493,6 +585,37 @@ func (s *Scenario) Validate() error { return nil } +func (s *Scenario) MatrixRuns() ([]MatrixRun, error) { + if s.Matrix == nil { + return []MatrixRun{}, nil + } + + // Validate first to ensure matrix is valid + if err := s.Validate(); err != nil { + return nil, err + } + + var runs []MatrixRun + for _, uName := range s.Matrix.Universes { + for _, strat := range s.Matrix.Strategies { + for _, tf := range s.Matrix.Timeframes { + for _, p := range s.Matrix.Periods { + id := fmt.Sprintf("%s__%s__%s__%s", uName, strat, tf, p.ID) + runs = append(runs, MatrixRun{ + ID: id, + Universe: uName, + StrategyID: strat, + Timeframe: tf, + FromUnixMs: p.FromUnixMs, + ToUnixMs: p.ToUnixMs, + }) + } + } + } + } + return runs, nil +} + func applyUniverseDefaults(step *Step, universeByName map[string]UniverseConfig) error { if step.Action != ActionImportDailyBars && step.Action != ActionCollectionFreshness && step.Action != ActionStartBacktest { return nil diff --git a/apps/cli/internal/operator/scenario_test.go b/apps/cli/internal/operator/scenario_test.go index a06ea85..8dac17c 100644 --- a/apps/cli/internal/operator/scenario_test.go +++ b/apps/cli/internal/operator/scenario_test.go @@ -1408,3 +1408,290 @@ steps: t.Errorf("error = %v, want it to contain missing selector error", err) } } + +func TestValidateBacktestScenarioMatrix(t *testing.T) { + yamlText := ` +name: valid_matrix_scenario +universes: + - name: univ1 + provider: kis + selector_kind: watchlist + market: kr + symbols: ["005930"] +matrix: + universes: ["univ1"] + strategies: ["strat1", "strat2"] + timeframes: ["daily", "minute_1"] + periods: + - id: p1 + from_unix_ms: 1000 + to_unix_ms: 2000 + - id: p2 + from_unix_ms: 3000 + to_unix_ms: 4000 +` + sc, err := ParseScenario([]byte(yamlText)) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if sc.Name != "valid_matrix_scenario" { + t.Errorf("name = %q, want valid_matrix_scenario", sc.Name) + } + if sc.Matrix == nil { + t.Fatalf("matrix is nil") + } + if len(sc.Matrix.Universes) != 1 || sc.Matrix.Universes[0] != "univ1" { + t.Errorf("matrix universes = %v, want [univ1]", sc.Matrix.Universes) + } +} + +func TestValidateRejectsInvalidBacktestScenarioMatrix(t *testing.T) { + cases := []struct { + name string + yaml string + expectedErr string + }{ + { + name: "no steps and no matrix", + yaml: ` +name: no_steps_no_matrix +`, + expectedErr: "has no steps and no matrix", + }, + { + name: "empty universe list in matrix", + yaml: ` +name: empty_univ +matrix: + universes: [] + strategies: ["strat1"] + timeframes: ["daily"] + periods: [{id: "p1", from_unix_ms: 100, to_unix_ms: 200}] +`, + expectedErr: "matrix universes is empty", + }, + { + name: "unknown universe", + yaml: ` +name: unknown_univ +matrix: + universes: ["univ_unknown"] + strategies: ["strat1"] + timeframes: ["daily"] + periods: [{id: "p1", from_unix_ms: 100, to_unix_ms: 200}] +`, + expectedErr: "referenced universe \"univ_unknown\" not found", + }, + { + name: "invalid timeframe", + yaml: ` +name: invalid_tf +universes: + - name: univ1 + provider: kis + selector_kind: watchlist + market: kr + symbols: ["005930"] +matrix: + universes: ["univ1"] + strategies: ["strat1"] + timeframes: ["invalid_timeframe"] + periods: [{id: "p1", from_unix_ms: 100, to_unix_ms: 200}] +`, + expectedErr: "unsupported timeframe", + }, + { + name: "reversed period", + yaml: ` +name: reversed_period +universes: + - name: univ1 + provider: kis + selector_kind: watchlist + market: kr + symbols: ["005930"] +matrix: + universes: ["univ1"] + strategies: ["strat1"] + timeframes: ["daily"] + periods: [{id: "p1", from_unix_ms: 200, to_unix_ms: 100}] +`, + expectedErr: "cannot be after to_unix_ms", + }, + { + name: "whitespace in generated ID", + yaml: ` +name: whitespace_id +universes: + - name: univ1 + provider: kis + selector_kind: watchlist + market: kr + symbols: ["005930"] +matrix: + universes: ["univ1"] + strategies: ["strat 1"] + timeframes: ["daily"] + periods: [{id: "p1", from_unix_ms: 100, to_unix_ms: 200}] +`, + expectedErr: "contains whitespace", + }, + { + name: "duplicate period ID", + yaml: ` +name: duplicate_period_id +universes: + - name: univ1 + provider: kis + selector_kind: watchlist + market: kr + symbols: ["005930"] +matrix: + universes: ["univ1"] + strategies: ["strat1"] + timeframes: ["daily"] + periods: + - id: p1 + from_unix_ms: 100 + to_unix_ms: 200 + - id: p1 + from_unix_ms: 300 + to_unix_ms: 400 +`, + expectedErr: "matrix generated duplicate ID", + }, + { + name: "missing from_unix_ms", + yaml: ` +name: missing_from_unix_ms +universes: + - name: univ1 + provider: kis + selector_kind: watchlist + market: kr + symbols: ["005930"] +matrix: + universes: ["univ1"] + strategies: ["strat1"] + timeframes: ["daily"] + periods: [{id: "p1", to_unix_ms: 200}] +`, + expectedErr: "from_unix_ms and to_unix_ms are required", + }, + { + name: "missing to_unix_ms", + yaml: ` +name: missing_to_unix_ms +universes: + - name: univ1 + provider: kis + selector_kind: watchlist + market: kr + symbols: ["005930"] +matrix: + universes: ["univ1"] + strategies: ["strat1"] + timeframes: ["daily"] + periods: [{id: "p1", from_unix_ms: 100}] +`, + expectedErr: "from_unix_ms and to_unix_ms are required", + }, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + _, err := ParseScenario([]byte(tc.yaml)) + if err == nil { + t.Fatalf("expected error containing %q, got nil", tc.expectedErr) + } + if !strings.Contains(err.Error(), tc.expectedErr) { + t.Errorf("error = %v, want it to contain %q", err, tc.expectedErr) + } + }) + } +} + +func TestBacktestScenarioMatrixRunsExpandsInDeterministicOrder(t *testing.T) { + yamlText := ` +name: expansion_order +universes: + - name: univ1 + provider: kis + selector_kind: watchlist + market: kr + symbols: ["005930"] + - name: univ2 + provider: kis + selector_kind: watchlist + market: kr + symbols: ["000660"] +matrix: + universes: ["univ1", "univ2"] + strategies: ["strat1", "strat2"] + timeframes: ["daily", "minute_1"] + periods: + - id: p1 + from_unix_ms: 1000 + to_unix_ms: 2000 + - id: p2 + from_unix_ms: 3000 + to_unix_ms: 4000 +` + sc, err := ParseScenario([]byte(yamlText)) + if err != nil { + t.Fatalf("unexpected error parsing matrix scenario: %v", err) + } + + runs, err := sc.MatrixRuns() + if err != nil { + t.Fatalf("unexpected error expanding matrix runs: %v", err) + } + + wantIDs := []string{ + "univ1__strat1__daily__p1", + "univ1__strat1__daily__p2", + "univ1__strat1__minute_1__p1", + "univ1__strat1__minute_1__p2", + "univ1__strat2__daily__p1", + "univ1__strat2__daily__p2", + "univ1__strat2__minute_1__p1", + "univ1__strat2__minute_1__p2", + "univ2__strat1__daily__p1", + "univ2__strat1__daily__p2", + "univ2__strat1__minute_1__p1", + "univ2__strat1__minute_1__p2", + "univ2__strat2__daily__p1", + "univ2__strat2__daily__p2", + "univ2__strat2__minute_1__p1", + "univ2__strat2__minute_1__p2", + } + + if len(runs) != len(wantIDs) { + t.Fatalf("runs = %d, want %d", len(runs), len(wantIDs)) + } + + for i, run := range runs { + if run.ID != wantIDs[i] { + t.Errorf("runs[%d].ID = %q, want %q", i, run.ID, wantIDs[i]) + } + parts := strings.Split(run.ID, "__") + if run.Universe != parts[0] { + t.Errorf("runs[%d].Universe = %q, want %q", i, run.Universe, parts[0]) + } + if run.StrategyID != parts[1] { + t.Errorf("runs[%d].StrategyID = %q, want %q", i, run.StrategyID, parts[1]) + } + if run.Timeframe != parts[2] { + t.Errorf("runs[%d].Timeframe = %q, want %q", i, run.Timeframe, parts[2]) + } + if parts[3] == "p1" { + if run.FromUnixMs != 1000 || run.ToUnixMs != 2000 { + t.Errorf("runs[%d] period p1 from/to = %d/%d, want 1000/2000", i, run.FromUnixMs, run.ToUnixMs) + } + } else if parts[3] == "p2" { + if run.FromUnixMs != 3000 || run.ToUnixMs != 4000 { + t.Errorf("runs[%d] period p2 from/to = %d/%d, want 3000/4000", i, run.FromUnixMs, run.ToUnixMs) + } + } + } +}