feat: backtest scenario automation 구현
- cli: scenario 실행 명령어 추가 - operator: scenario 처리 로직 구현 및 테스트
This commit is contained in:
parent
7b2dfe6751
commit
d770a195ff
13 changed files with 1955 additions and 6 deletions
|
|
@ -0,0 +1,140 @@
|
|||
<!-- task=m-backtest-scenario-automation/01_scenario_matrix plan=0 tag=API -->
|
||||
|
||||
# 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-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, 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=<n>`이 출력되도록 `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 필수값 검증과 회귀 테스트만 보완한다.
|
||||
|
|
@ -0,0 +1,154 @@
|
|||
<!-- task=m-backtest-scenario-automation/01_scenario_matrix plan=1 tag=REVIEW_API -->
|
||||
|
||||
# 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-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. 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로 이동한다.
|
||||
|
|
@ -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=<n>` 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
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,277 @@
|
|||
<!-- task=m-backtest-scenario-automation/01_scenario_matrix plan=0 tag=API -->
|
||||
|
||||
# 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`는 `<universe>__<strategy>__<timeframe>__<period>` 형태로 만들고 공백은 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=<n>`을 추가한다. 기존 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
<!-- task=m-backtest-scenario-automation/01_scenario_matrix plan=1 tag=REVIEW_API -->
|
||||
|
||||
# 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`을 유지한다.
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
<!-- task=m-backtest-scenario-automation/02+01_batch_runner plan=0 tag=API -->
|
||||
|
||||
# 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)
|
||||
```
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
<!-- task=m-backtest-scenario-automation/02+01_batch_runner plan=0 tag=API -->
|
||||
|
||||
# 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
<!-- task=m-backtest-scenario-automation/03+01,02_result_handoff plan=0 tag=TEST -->
|
||||
|
||||
# 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)
|
||||
```
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
<!-- task=m-backtest-scenario-automation/03+01,02_result_handoff plan=0 tag=TEST -->
|
||||
|
||||
# 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue