feat: backtest multi-timeframe coverage completion

- Add agent-task/m-backtest-multi-timeframe-coverage with plans and code reviews
- Update roadmap PHASE and milestone documents
- Add bar_source_test.go for worker backtest
This commit is contained in:
toki 2026-06-18 10:53:03 +09:00
parent 65f037bc79
commit e5bb46aaab
9 changed files with 1137 additions and 5 deletions

View file

@ -25,7 +25,7 @@
- [완료] Backtest Scenario Automation
- 경로: `agent-roadmap/archive/phase/backtest-loop/milestones/backtest-scenario-automation.md`
- 요약: 선택 universe와 기간 조합을 반복 가능한 backtest scenario로 실행하고, 원격 runner에서 migration/runtime/secret 주입/결과 handoff를 안정화한다.
- [계획] Backtest Multi-Timeframe Coverage
- [진행중] Backtest Multi-Timeframe Coverage
- 경로: `agent-roadmap/phase/backtest-loop/milestones/backtest-multi-timeframe-coverage.md`
- 요약: 월봉, 일봉, 분봉을 백테스트 기본 분석 timeframe으로 정리하고 data import/store/backtest/freshness 검증을 맞춘다.
- [계획] Scheduled Market Data Refresh

View file

@ -11,7 +11,7 @@
## 상태
[계획]
[진행중]
## 승격 조건
@ -48,8 +48,8 @@
월봉, 일봉, 분봉을 사용자 입력, contracts, domain, CLI output에서 같은 의미로 다루게 한다.
- [ ] [timeframe-vocab] proto/domain/CLI scenario에 monthly/daily/minute timeframe vocabulary를 정리하고 기존 `daily`, `minute_1`, `minute_5` 매핑과 호환성을 유지한다. 검증: contracts/domain/CLI mapping test가 monthly와 minute baseline을 포함한다.
- [ ] [capability-matrix] provider, market, venue, asset type, timeframe별 지원/미지원 사유를 capability matrix로 표현한다. 검증: KIS KR/US daily, minute, monthly 조합의 accepted/rejected case가 명시적으로 통과한다.
- [x] [timeframe-vocab] proto/domain/CLI scenario에 monthly/daily/minute timeframe vocabulary를 정리하고 기존 `daily`, `minute_1`, `minute_5` 매핑과 호환성을 유지한다. 검증: contracts/domain/CLI mapping test가 monthly와 minute baseline을 포함한다.
- [x] [capability-matrix] provider, market, venue, asset type, timeframe별 지원/미지원 사유를 capability matrix로 표현한다. 검증: KIS KR/US daily, minute, monthly 조합의 accepted/rejected case가 명시적으로 통과한다.
### Epic: [data-coverage] Multi-timeframe data coverage
@ -57,7 +57,7 @@
- [ ] [minute-ingest] 분봉 provider import 경계를 추가하거나, provider 미지원 조합을 typed error로 거부한다. 검증: minute import success 또는 rejected fixture가 stable text/JSONL로 남는다.
- [ ] [monthly-bars] 월봉을 일봉 aggregation으로 생성하고, 생성 기준과 provenance를 저장/출력한다. 검증: 동일 daily fixture에서 deterministic monthly OHLCV가 생성된다.
- [ ] [store-query] `bars` 저장/조회와 backtest bar source가 timeframe별 key를 유지하고 월/일/분 데이터를 섞지 않는다. 검증: 동일 instrument의 monthly/daily/minute bars가 독립 조회된다.
- [x] [store-query] `bars` 저장/조회와 backtest bar source가 timeframe별 key를 유지하고 월/일/분 데이터를 섞지 않는다. 검증: 동일 instrument의 monthly/daily/minute bars가 독립 조회된다.
### Epic: [backtest-semantics] Backtest semantics

View file

@ -0,0 +1,137 @@
<!-- task=m-backtest-multi-timeframe-coverage/01_import_timeframe_contract 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 selected SDD decision or selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Environment/secret/service blockers, generic scope changes, repeated failures, and evidence gaps that a follow-up agent can close 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 only SDD/Milestone lock decisions 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-18
task=m-backtest-multi-timeframe-coverage/01_import_timeframe_contract, plan=0, tag=API
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
1. 판정을 append한다.
2. `CODE_REVIEW-local-G06.md``code_review_local_G06_N.log`, `PLAN-local-G06.md``plan_local_G06_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-backtest-multi-timeframe-coverage/01_import_timeframe_contract/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [API-1] ImportDailyBarsRequest Timeframe Field | [ ] |
## 구현 체크리스트
- [ ] `packages/contracts/proto/alt/v1/market.proto``ImportDailyBarsRequest`에 additive `Timeframe timeframe = 9;`를 추가한다.
- [ ] `bin/contracts-gen`으로 Go/Dart generated contracts를 갱신한다. generated 파일은 손으로 수정하지 않는다.
- [ ] `bin/contracts-check``go test ./packages/contracts/gen/go/...`를 실행해 generated drift와 compile을 확인한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [ ] `코드리뷰 결과``PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [ ] active `CODE_REVIEW-*-G??.md``code_review_local_G06_N.log`로 아카이브한다.
- [ ] active `PLAN-*-G??.md``plan_local_G06_M.log`로 아카이브한다.
- [ ] `.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/m-backtest-multi-timeframe-coverage/01_import_timeframe_contract/``agent-task/archive/YYYY/MM/m-backtest-multi-timeframe-coverage/01_import_timeframe_contract/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-backtest-multi-timeframe-coverage/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-local-G06.md``CODE_REVIEW-local-G06.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가 연결된 SDD/Milestone 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- `market.proto` field number가 9로 additive인지 확인한다.
- generated Go/Dart contract가 generator 산출물인지 확인한다.
- downstream behavior 변경이 이 task에 섞이지 않았는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
### API-1 중간 검증
```bash
$ bin/contracts-gen
(output)
$ bin/contracts-check
(output)
$ go test ./packages/contracts/gen/go/...
(output)
```
### 최종 검증
```bash
$ bin/contracts-check
(output)
$ go test ./packages/contracts/gen/go/...
(output)
```
---
> **[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.
## Section Ownership
| Section | Owner | Note |
|---------|-------|------|
| Header comment, 개요, 리뷰 에이전트 지시 | Fixed at stub creation | Implementing agent must not modify or execute these |
| 구현 항목별 완료 여부 | Implementing agent | Check `[ ]` to `[x]` only |
| 구현 체크리스트 | Implementing agent | Check `[ ]` to `[x]` only |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify |
| 계획 대비 변경 사항, 주요 설계 결정 | Implementing agent | Replace placeholder with actual content |
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless selected SDD/Milestone lock decision blocks implementation |
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus |
| 검증 결과 | Implementing agent | Paste actual stdout/stderr |

View file

@ -0,0 +1,173 @@
<!-- task=m-backtest-multi-timeframe-coverage/01_import_timeframe_contract plan=0 tag=API -->
# Plan - Import Timeframe Contract
## 이 파일을 읽는 구현 에이전트에게
구현 마지막 단계는 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 변경 내용과 검증 출력으로 채우는 것이다. 구현 후 active 파일을 그대로 두고 리뷰 준비를 보고한다. 종결, archive 이동, `complete.log` 작성은 code-review 전용이다.
선택된 SDD 결정 또는 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막을 때만 review stub의 `사용자 리뷰 요청` 섹션을 채우고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 환경/secret/서비스 차단, 일반 범위 조정, 검증 공백은 사용자 리뷰 요청이 아니라 `검증 결과` 또는 `계획 대비 변경 사항`에 기록한다.
## 배경
`ImportDailyBarsRequest`는 이름과 실제 importer가 모두 일봉 중심이라 현재 요청자가 `minute_1`/`minute_5` import를 명시할 계약 필드가 없다. SDD S03의 stable typed rejection을 만들려면 먼저 proto 요청에 timeframe을 additive하게 실어야 한다. 이 계획은 downstream 동작 변경 없이 contract와 generated output만 준비하는 선행 작업이다.
## 사용자 리뷰 요청 흐름
사용자 리뷰 요청은 active `CODE_REVIEW-*-G??.md``사용자 리뷰 요청` 섹션에만 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 내용을 따른다. 직접 사용자 프롬프트는 금지하며, code-review가 요청 타당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다.
## 분석 결과
### 읽은 파일
- `AGENTS.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/private/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-roadmap/phase/backtest-loop/milestones/backtest-multi-timeframe-coverage.md`
- `agent-roadmap/sdd/backtest-loop/backtest-multi-timeframe-coverage/SDD.md`
- `agent-test/local/rules.md`
- `agent-test/local/contracts-smoke.md`
- `agent-test/local/api-smoke.md`
- `agent-test/local/worker-smoke.md`
- `agent-test/local/operations-smoke.md`
- `packages/contracts/proto/alt/v1/market.proto`
- `apps/cli/internal/operator/runner.go`
- `apps/cli/internal/operator/scenario.go`
- `apps/cli/internal/operator/scenario_test.go`
- `apps/cli/internal/operator/runner_market_test.go`
- `services/worker/internal/socket/market.go`
- `services/worker/internal/socket/market_test.go`
- `services/worker/internal/jobs/marketdata_jobs.go`
- `services/worker/internal/jobs/marketdata_jobs_test.go`
- `bin/contracts-gen`
- `bin/contracts-check`
### 테스트 환경 규칙
- `test_env=local`.
- `agent-test/local/rules.md`를 읽었고, `.proto` 변경이므로 `agent-test/local/contracts-smoke.md`를 적용한다.
- public proto socket request가 바뀌므로 `api-smoke`, worker call site 계획이 이어지므로 `worker-smoke`, generator/check script 검증 때문에 `operations-smoke`도 참고했다.
- 적용 명령: `.proto` 변경 후 `bin/contracts-gen`, generated drift는 `bin/contracts-check`, generated Go는 `go test ./packages/contracts/gen/go/...`.
- fallback 없음. `<확인 필요>` 값 없음.
### 테스트 커버리지 공백
- `ImportDailyBarsRequest.timeframe` field addition: 기존 parser map은 메시지 타입 등록만 검증하고 필드별 round-trip은 없다. generated Go compile와 downstream dependent plan에서 request propagation test로 보완한다.
- Generated Dart output: `bin/contracts-check` drift로 확인한다. Dart unit은 이 작업에서 직접 추가하지 않는다.
### 심볼 참조
- renamed/removed symbol 없음.
- 추가 필드 call site 후보: `apps/cli/internal/operator/runner.go:257`, `services/worker/internal/socket/market.go:69`, `services/worker/internal/jobs/marketdata_jobs.go:27`. 이 계획에서는 field만 추가하고, propagation/rejection은 `02+01_minute_import_rejection`에서 처리한다.
### 분할 판단
- split policy를 plan 작성 전에 평가했다.
- 공유 task group: `agent-task/m-backtest-multi-timeframe-coverage/`
- sibling split:
- `01_import_timeframe_contract`: proto/generator 선행 작업, 독립.
- `02+01_minute_import_rejection`: `01` 완료 후 request propagation과 typed rejection 구현.
- `03_monthly_aggregation`: 월봉 aggregation 구현, `01`과 독립.
- 이 plan은 Milestone 기능 Task 자체를 닫지 않는 선행 하위 작업이므로 `Roadmap Targets``Spec Targets`를 쓰지 않는다.
### 범위 결정 근거
- worker socket, jobs, CLI scenario validation 동작은 제외한다. contract field가 먼저 생성되어야 downstream 구현이 컴파일 가능하다.
- provider KIS 실제 분봉 import는 제외한다. SDD는 미지원 typed rejection도 허용한다.
- 월봉 aggregation은 `03_monthly_aggregation`으로 분리한다.
### 빌드 등급
- `local-G06`: public proto contract와 generated output을 바꾸지만, 변경은 additive field 하나이고 검증 명령이 명확하다.
## 구현 체크리스트
- [ ] `packages/contracts/proto/alt/v1/market.proto``ImportDailyBarsRequest`에 additive `Timeframe timeframe = 9;`를 추가한다.
- [ ] `bin/contracts-gen`으로 Go/Dart generated contracts를 갱신한다. generated 파일은 손으로 수정하지 않는다.
- [ ] `bin/contracts-check``go test ./packages/contracts/gen/go/...`를 실행해 generated drift와 compile을 확인한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
### [API-1] ImportDailyBarsRequest Timeframe Field
#### 문제
`packages/contracts/proto/alt/v1/market.proto:62``ImportDailyBarsRequest`는 provider/selector/date만 갖고 있어 import 요청에서 timeframe 후보를 표현할 수 없다.
Before:
```proto
62 message ImportDailyBarsRequest {
63 string provider = 1;
64 string selector_kind = 2;
65 Market market = 3;
66 Venue venue = 4;
67 string name = 5;
68 repeated string symbols = 6;
69 string from_yyyymmdd = 7;
70 string to_yyyymmdd = 8;
71 }
```
#### 해결 방법
필드 번호 9에 `Timeframe timeframe = 9;`를 추가한다. `TIMEFRAME_UNSPECIFIED`는 downstream에서 backward-compatible daily default로 처리하게 두고, 이 plan은 schema와 generated output만 만든다.
After:
```proto
62 message ImportDailyBarsRequest {
63 string provider = 1;
64 string selector_kind = 2;
65 Market market = 3;
66 Venue venue = 4;
67 string name = 5;
68 repeated string symbols = 6;
69 string from_yyyymmdd = 7;
70 string to_yyyymmdd = 8;
71 Timeframe timeframe = 9;
72 }
```
#### 수정 파일 및 체크리스트
- [ ] `packages/contracts/proto/alt/v1/market.proto`: additive field 추가.
- [ ] `packages/contracts/gen/go/alt/v1/market.pb.go`: `bin/contracts-gen` 결과 반영.
- [ ] Dart generated contract 파일: `bin/contracts-gen` 결과 반영.
#### 테스트 작성
- 새 수동 unit test는 작성하지 않는다. protobuf generated accessor/descriptor compile과 `bin/contracts-check`가 field generation drift를 검증한다.
- dependent plan `02+01_minute_import_rejection`에서 CLI/worker propagation boundary test를 작성한다.
#### 중간 검증
```bash
bin/contracts-gen
bin/contracts-check
go test ./packages/contracts/gen/go/...
```
기대 결과: 세 명령 모두 exit code 0.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `packages/contracts/proto/alt/v1/market.proto` | API-1 |
| `packages/contracts/gen/go/alt/v1/market.pb.go` | API-1 |
| generated Dart contract files | API-1 |
## 최종 검증
```bash
bin/contracts-check
go test ./packages/contracts/gen/go/...
```
기대 결과: generated drift 없음, generated Go contract package test 통과.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,161 @@
<!-- task=m-backtest-multi-timeframe-coverage/02+01_minute_import_rejection 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 selected SDD decision or selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked 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-18
task=m-backtest-multi-timeframe-coverage/02+01_minute_import_rejection, plan=0, tag=API
## Roadmap Targets
- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-multi-timeframe-coverage.md`
- Task ids:
- `minute-ingest`: 분봉 provider import 경계를 추가하거나, provider 미지원 조합을 typed error로 거부한다.
- Completion mode: check-on-pass
## Spec Targets
- SDD: `agent-roadmap/sdd/backtest-loop/backtest-multi-timeframe-coverage/SDD.md`
- Acceptance scenarios:
- `S03`: task=`minute-ingest`; evidence=`minute import success 또는 typed reject fixture`
- Completion mode: spec-check-on-pass
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
1. 판정을 append한다.
2. active plan/review 파일을 `.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다.
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 수정은 런타임 책임이다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [API-1] CLI Import Timeframe Propagation | [ ] |
| [API-2] Worker Socket Typed Rejection | [ ] |
| [API-3] Worker Job Payload Boundary | [ ] |
| [API-4] Stable Minute Rejection Fixture | [ ] |
## 구현 체크리스트
- [ ] `apps/cli/internal/operator/scenario.go`에서 `import_daily_bars`의 optional `request.timeframe`을 검증한다. 빈 값은 daily default, 값이 있으면 `validTimeframes`만 허용한다.
- [ ] `apps/cli/internal/operator/runner.go`에서 `ImportDailyBarsRequest.Timeframe`을 채운다. 빈 값은 `TIMEFRAME_DAILY`, 명시 값은 `timeframeByName`을 사용한다.
- [ ] `services/worker/internal/socket/market.go`에서 `ImportDailyBarsRequest.timeframe`을 daily default로 해석하고, daily가 아니면 typed `invalid_request`로 거부하며 importer를 호출하지 않는다.
- [ ] `services/worker/internal/jobs/marketdata_jobs.go`에 optional payload `timeframe`을 추가하고 job handler도 daily 외 timeframe을 capability/rejection error로 막는다.
- [ ] CLI/worker/job tests와 stable JSONL/text fixture를 추가한다. 검증: minute import rejected fixture가 stable text/JSONL로 남는다.
- [ ] `bin/contracts-check`, `go test ./apps/cli/internal/operator ./services/worker/internal/socket ./services/worker/internal/jobs`, `go test ./services/api/...`, `go test ./services/worker/...`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [ ] `코드리뷰 결과``PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [ ] active `CODE_REVIEW-*-G??.md``code_review_local_G06_N.log`로 아카이브한다.
- [ ] active `PLAN-*-G??.md``plan_local_G06_M.log`로 아카이브한다.
- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md``agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [ ] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task 디렉터리를 archive로 이동한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active plan/review 파일을 작성하고 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `USER_REVIEW.md`를 작성하고 active plan/review/complete 파일을 남기지 않는다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- `01_import_timeframe_contract` predecessor가 완료된 뒤 구현되었는지 확인한다.
- `minute_1``minute_5`가 daily importer로 조용히 fallback하지 않는지 확인한다.
- stable fixture가 typed error를 기대한 성공 이벤트로 남는지 확인한다.
- Roadmap/Spec Targets가 `minute-ingest`/`S03`과 일치하는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
### API-1 중간 검증
```bash
$ go test ./apps/cli/internal/operator
(output)
```
### API-2 중간 검증
```bash
$ go test ./services/worker/internal/socket
(output)
```
### API-3 중간 검증
```bash
$ go test ./services/worker/internal/jobs
(output)
```
### API-4 중간 검증
```bash
$ go test ./apps/cli/internal/operator
(output)
```
### 최종 검증
```bash
$ bin/contracts-check
(output)
$ go test ./apps/cli/internal/operator ./services/worker/internal/socket ./services/worker/internal/jobs
(output)
$ go test ./services/api/...
(output)
$ go test ./services/worker/...
(output)
$ git diff --check
(output)
```
---
> **[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.

View file

@ -0,0 +1,305 @@
<!-- task=m-backtest-multi-timeframe-coverage/02+01_minute_import_rejection plan=0 tag=API -->
# Plan - Minute Import Typed Rejection
## 이 파일을 읽는 구현 에이전트에게
구현 마지막 단계는 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 변경 내용과 검증 출력으로 채우는 것이다. 구현 후 active 파일을 그대로 두고 리뷰 준비를 보고한다. 종결, archive 이동, `complete.log` 작성은 code-review 전용이다.
선택된 SDD 결정 또는 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막을 때만 review stub의 `사용자 리뷰 요청` 섹션을 채우고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 환경/secret/서비스 차단, 일반 범위 조정, 검증 공백은 사용자 리뷰 요청이 아니라 `검증 결과` 또는 `계획 대비 변경 사항`에 기록한다.
## 배경
KIS daily importer capability matrix는 monthly/minute를 이미 unsupported로 판단하지만, `import_daily_bars` socket/CLI/job 요청 자체에는 timeframe을 실을 수 없어 분봉 import 거부를 stable fixture로 남기기 어렵다. `01_import_timeframe_contract`가 proto field를 추가한 뒤, 이 계획은 CLI scenario부터 worker boundary까지 timeframe을 전달하고 `minute_1`/`minute_5`를 typed `invalid_request`로 거부한다. 실제 분봉 provider import 구현은 이번 plan 범위가 아니며 SDD S03은 typed rejection evidence로 충족한다.
## 사용자 리뷰 요청 흐름
사용자 리뷰 요청은 active `CODE_REVIEW-*-G??.md``사용자 리뷰 요청` 섹션에만 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 내용을 따른다. 직접 사용자 프롬프트는 금지하며, code-review가 요청 타당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다.
## Roadmap Targets
- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-multi-timeframe-coverage.md`
- Task ids:
- `minute-ingest`: 분봉 provider import 경계를 추가하거나, provider 미지원 조합을 typed error로 거부한다.
- Completion mode: check-on-pass
## Spec Targets
- SDD: `agent-roadmap/sdd/backtest-loop/backtest-multi-timeframe-coverage/SDD.md`
- Acceptance scenarios:
- `S03`: task=`minute-ingest`; evidence=`minute import success 또는 typed reject fixture`
- Completion mode: spec-check-on-pass
## 분석 결과
### 읽은 파일
- `AGENTS.md`
- `agent-roadmap/phase/backtest-loop/milestones/backtest-multi-timeframe-coverage.md`
- `agent-roadmap/sdd/backtest-loop/backtest-multi-timeframe-coverage/SDD.md`
- `agent-test/local/rules.md`
- `agent-test/local/contracts-smoke.md`
- `agent-test/local/api-smoke.md`
- `agent-test/local/worker-smoke.md`
- `agent-test/local/operations-smoke.md`
- `packages/contracts/proto/alt/v1/market.proto`
- `apps/cli/internal/operator/scenario.go`
- `apps/cli/internal/operator/runner.go`
- `apps/cli/internal/operator/scenario_test.go`
- `apps/cli/internal/operator/runner_market_test.go`
- `apps/cli/internal/operator/client.go`
- `apps/cli/internal/operator/client_test.go`
- `services/worker/internal/socket/market.go`
- `services/worker/internal/socket/market_test.go`
- `services/worker/internal/socket/backtest_mapping.go`
- `services/worker/internal/jobs/marketdata_jobs.go`
- `services/worker/internal/jobs/marketdata_jobs_test.go`
- `services/worker/internal/providers/kis/daily_itemchartprice.go`
- `services/worker/internal/providers/kis/daily_itemchartprice_test.go`
### 테스트 환경 규칙
- `test_env=local`.
- `contracts-smoke`: `ImportDailyBarsRequest.timeframe` generated contract가 선행되어야 하므로 `bin/contracts-check`를 최종 확인에 포함한다.
- `api-smoke`: proto-socket request boundary가 바뀌므로 `go test ./services/api/...`를 포함한다.
- `worker-smoke`: worker socket/jobs 거부 경계를 변경하므로 `go test ./services/worker/...`를 포함한다.
- `operations-smoke`: fixture/generator drift 확인용으로 `git diff --check`를 포함한다.
- fallback 없음. `<확인 필요>` 값 없음.
### 테스트 커버리지 공백
- CLI validation은 import action의 `timeframe`을 아직 검사하지 않는다. `scenario_test.go`에 invalid timeframe dry-run case와 daily default/valid minute pass-through case를 추가한다.
- CLI runner는 `ImportDailyBarsRequest`에 timeframe을 아직 채우지 않는다. `runner_market_test.go`에서 `minute_1`/`minute_5` mapping을 확인한다.
- Worker socket `handleImportDailyBars`는 timeframe을 아직 보지 않는다. `market_test.go`에서 minute request가 `invalid_request`이고 importer가 호출되지 않는지 확인한다.
- Worker job payload는 hardcoded daily capability check만 한다. `marketdata_jobs_test.go`에서 optional `timeframe`과 minute rejection을 확인한다.
- Stable JSONL/text fixture는 기존 typed-error event 평가가 있으나 분봉 import fixture는 없다. CLI operator fixture를 추가한다.
### 심볼 참조
- renamed/removed symbol 없음.
- 추가 field call sites:
- `apps/cli/internal/operator/runner.go:257` `ActionImportDailyBars` request 생성.
- `services/worker/internal/socket/market.go:69` `handleImportDailyBars`.
- `services/worker/internal/jobs/marketdata_jobs.go:27` `DailyBarImportPayload`.
- `services/worker/internal/jobs/marketdata_jobs.go:113` capability check.
### 분할 판단
- split policy를 plan 작성 전에 평가했다.
- 공유 task group: `agent-task/m-backtest-multi-timeframe-coverage/`
- sibling split:
- `01_import_timeframe_contract`: 선행 proto field/generation.
- `02+01_minute_import_rejection`: 이 plan. predecessor `01` complete.log 필요.
- `03_monthly_aggregation`: 독립 monthly aggregation.
- predecessor 상태: `01` active/archived `complete.log`는 아직 없다. 구현 시작 전 `agent-task/m-backtest-multi-timeframe-coverage/01_import_timeframe_contract/complete.log` 또는 matching archive `complete.log`를 확인해야 한다.
### 범위 결정 근거
- 실제 KIS minute provider endpoint 연동은 제외한다. Milestone과 SDD가 typed rejection을 허용한다.
- 월봉은 이 plan에서 daily fallback 또는 rejection으로 처리하지 않는다. 월봉 source of truth는 `03_monthly_aggregation`이다.
- `ImportDailyBarsRequest` field 생성은 predecessor plan 소유다. 이 plan은 generated field가 존재한다고 가정한다.
### 빌드 등급
- `local-G06`: public request field propagation, CLI/worker/job boundary, stable fixture가 함께 바뀌지만 동작은 typed rejection으로 제한되어 로컬 테스트로 닫힌다.
## 의존 관계 및 구현 순서
- 이 subtask directory의 `02+01_...`가 runtime source of truth다.
- `01_import_timeframe_contract`가 PASS되어 `complete.log`를 만든 뒤 구현한다. 선행 없이 시작하면 generated `ImportDailyBarsRequest.Timeframe` accessor가 없어서 컴파일되지 않는다.
## 구현 체크리스트
- [ ] `apps/cli/internal/operator/scenario.go`에서 `import_daily_bars`의 optional `request.timeframe`을 검증한다. 빈 값은 daily default, 값이 있으면 `validTimeframes`만 허용한다.
- [ ] `apps/cli/internal/operator/runner.go`에서 `ImportDailyBarsRequest.Timeframe`을 채운다. 빈 값은 `TIMEFRAME_DAILY`, 명시 값은 `timeframeByName`을 사용한다.
- [ ] `services/worker/internal/socket/market.go`에서 `ImportDailyBarsRequest.timeframe`을 daily default로 해석하고, daily가 아니면 typed `invalid_request`로 거부하며 importer를 호출하지 않는다.
- [ ] `services/worker/internal/jobs/marketdata_jobs.go`에 optional payload `timeframe`을 추가하고 job handler도 daily 외 timeframe을 capability/rejection error로 막는다.
- [ ] CLI/worker/job tests와 stable JSONL/text fixture를 추가한다. 검증: minute import rejected fixture가 stable text/JSONL로 남는다.
- [ ] `bin/contracts-check`, `go test ./apps/cli/internal/operator ./services/worker/internal/socket ./services/worker/internal/jobs`, `go test ./services/api/...`, `go test ./services/worker/...`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
### [API-1] CLI Import Timeframe Propagation
#### 문제
`apps/cli/internal/operator/scenario.go:290`의 comment는 timeframe을 `list_bars` 전용으로 설명하고, `scenario.go:967`의 import validation은 timeframe을 검사하지 않는다. `runner.go:257``ImportDailyBarsRequest` 생성도 timeframe을 보내지 않는다.
Before:
```go
290 // Timeframe selects the bar timeframe for a list_bars query.
291 Timeframe string `yaml:"timeframe"`
...
967 case ActionImportDailyBars:
968 if step.Request.Provider == "" {
```
```go
257 case ActionImportDailyBars:
258 resp, err := client.ImportDailyBars(stepCtx, &altv1.ImportDailyBarsRequest{
259 Provider: step.Request.Provider,
260 SelectorKind: step.Request.SelectorKind,
```
#### 해결 방법
comment를 import/list/freshness/backtest 공용 의미로 고치고, import validation에서 `step.Request.Timeframe != "" && !validTimeframes[...]`면 bad input으로 막는다. runner는 helper로 `importTimeframe := altv1.Timeframe_TIMEFRAME_DAILY`를 기본값으로 두고 명시 값은 `timeframeByName`을 사용한다.
#### 수정 파일 및 체크리스트
- [ ] `apps/cli/internal/operator/scenario.go`: comment와 import validation 추가.
- [ ] `apps/cli/internal/operator/runner.go`: import request timeframe mapping 추가.
- [ ] `apps/cli/internal/operator/scenario_test.go`: invalid timeframe, valid `minute_1`, empty default validation case.
- [ ] `apps/cli/internal/operator/runner_market_test.go`: request timeframe mapping assertion.
#### 테스트 작성
- `TestValidateImportDailyBars` 계열에 optional timeframe 검증을 추가한다.
- `TestRunScenarioImportDailyBarsSuccess` 또는 새 test에서 empty default daily와 `minute_1` request field를 확인한다.
#### 중간 검증
```bash
go test ./apps/cli/internal/operator
```
기대 결과: operator package 통과.
### [API-2] Worker Socket Typed Rejection
#### 문제
`services/worker/internal/socket/market.go:69``handleImportDailyBars`는 request timeframe을 읽지 않고 `DailyBarImporter`를 호출한다. minute request가 들어와도 daily importer로 조용히 흘러갈 수 있다.
Before:
```go
87 from, to, err := parseImportDateRange(req.GetFromYyyymmdd(), req.GetToYyyymmdd())
88 if err != nil {
89 return &altv1.ImportDailyBarsResponse{Error: marketInvalidRequest(err.Error())}, nil
90 }
91 if deps.DailyBarImporter == nil {
```
#### 해결 방법
`req.GetTimeframe()`이 unspecified면 daily로 default한다. 명시 값은 `timeframeFromProto`로 변환하고 daily가 아니면 `ImportDailyBarsResponse{Error: marketInvalidRequest("import_daily_bars supports timeframe daily; got <value>")}`를 반환한다. 이 거부는 importer nil check보다 먼저 둬서 provider 미지원 조합을 typed input error로 안정화한다.
#### 수정 파일 및 체크리스트
- [ ] `services/worker/internal/socket/market.go`: import timeframe normalization/rejection helper 추가.
- [ ] `services/worker/internal/socket/market_test.go`: `TIMEFRAME_MINUTE_1`, `TIMEFRAME_MINUTE_5` rejection과 importer not-called assertion.
#### 테스트 작성
- `TestHandleImportDailyBarsValidation` 또는 새 `TestHandleImportDailyBarsRejectsUnsupportedTimeframe`을 추가한다.
- error code는 `invalid_request`, message는 stable prefix를 assert한다.
#### 중간 검증
```bash
go test ./services/worker/internal/socket
```
기대 결과: socket package 통과.
### [API-3] Worker Job Payload Boundary
#### 문제
`services/worker/internal/jobs/marketdata_jobs.go:27``DailyBarImportPayload`에는 timeframe이 없고, `marketdata_jobs.go:113`은 capability check에 `market.TimeframeDaily`를 hardcode한다.
Before:
```go
27 type DailyBarImportPayload struct {
28 Provider string `json:"provider"`
...
35 To string `json:"to,omitempty"`
}
...
113 decision := capability.CheckBars(market.ProviderCapabilityRequest{
114 Provider: market.Provider(p.Provider),
115 Market: market.Market(p.Market),
116 Venue: market.Venue(p.Venue),
117 Timeframe: market.TimeframeDaily,
118 })
```
#### 해결 방법
`Timeframe string json:"timeframe,omitempty"`를 payload에 추가한다. empty는 `market.TimeframeDaily`, 명시 값은 `market.Timeframe(...)`로 해석하되 daily가 아니면 capability decision 또는 explicit error로 importer 전에 거부한다. error text는 fixture에서 안정적으로 비교할 수 있게 prefix를 고정한다.
#### 수정 파일 및 체크리스트
- [ ] `services/worker/internal/jobs/marketdata_jobs.go`: payload field와 timeframe normalization.
- [ ] `services/worker/internal/jobs/marketdata_jobs_test.go`: empty daily compatibility, `1m`/`5m` rejection, importer not-called.
#### 테스트 작성
- 기존 `TestRegisterDailyBarImportHandlerRejectsTimeframeViaCapabilityMatrix`를 실제 payload timeframe rejection test로 보강하거나 새 test를 추가한다.
#### 중간 검증
```bash
go test ./services/worker/internal/jobs
```
기대 결과: jobs package 통과.
### [API-4] Stable Minute Rejection Fixture
#### 문제
SDD S03은 minute import success 또는 typed reject fixture를 요구한다. 현재 operator fixture에는 `minute_1`/`minute_5` import rejection을 보여주는 stable JSONL/text가 없다.
#### 해결 방법
기존 operator fixture 위치와 naming을 따른다. `import_daily_bars` step에 `timeframe: minute_1` 또는 `minute_5`, `expect.status: error`, `expect.error_code: invalid_request`를 둔다. JSONL expected fixture는 `status=ok` step event가 typed error를 기대대로 받았음을 보여야 한다.
#### 수정 파일 및 체크리스트
- [ ] 기존 operator fixture 디렉터리의 import/freshness fixture naming을 확인한다.
- [ ] minute import rejected scenario YAML 추가.
- [ ] expected JSONL/text fixture 추가.
- [ ] handoff/runner fixture test에 새 fixture를 연결한다.
#### 테스트 작성
- fixture를 읽어 runner dry/run 또는 fixture comparison test가 stable output을 비교하게 한다.
#### 중간 검증
```bash
go test ./apps/cli/internal/operator
```
기대 결과: 새 fixture test 포함 통과.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `apps/cli/internal/operator/scenario.go` | API-1 |
| `apps/cli/internal/operator/runner.go` | API-1 |
| `apps/cli/internal/operator/scenario_test.go` | API-1 |
| `apps/cli/internal/operator/runner_market_test.go` | API-1 |
| `services/worker/internal/socket/market.go` | API-2 |
| `services/worker/internal/socket/market_test.go` | API-2 |
| `services/worker/internal/jobs/marketdata_jobs.go` | API-3 |
| `services/worker/internal/jobs/marketdata_jobs_test.go` | API-3 |
| operator scenario/expected fixture files | API-4 |
## 최종 검증
```bash
bin/contracts-check
go test ./apps/cli/internal/operator ./services/worker/internal/socket ./services/worker/internal/jobs
go test ./services/api/...
go test ./services/worker/...
git diff --check
```
기대 결과: 모든 명령 exit code 0. Go test cache output은 허용하지만, fixture 변경 검증이 의심되면 해당 package는 `-count=1`로 재실행하고 변경 사유를 review stub에 기록한다.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,141 @@
<!-- task=m-backtest-multi-timeframe-coverage/03_monthly_aggregation plan=0 tag=REFACTOR -->
# Code Review Reference - REFACTOR
> **[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 selected SDD decision or selected Milestone `구현 잠금 > 결정 필요` item, fill `사용자 리뷰 요청` with linked 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-18
task=m-backtest-multi-timeframe-coverage/03_monthly_aggregation, plan=0, tag=REFACTOR
## Roadmap Targets
- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-multi-timeframe-coverage.md`
- Task ids:
- `monthly-bars`: 월봉을 일봉 aggregation으로 생성하고, 생성 기준과 provenance를 저장/출력한다.
- Completion mode: check-on-pass
## Spec Targets
- SDD: `agent-roadmap/sdd/backtest-loop/backtest-multi-timeframe-coverage/SDD.md`
- Acceptance scenarios:
- `S04`: task=`monthly-bars`; evidence=`deterministic monthly OHLCV fixture와 provenance evidence`
- Completion mode: spec-check-on-pass
## 이 파일을 읽는 리뷰 에이전트에게
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
1. 판정을 append한다.
2. active plan/review 파일을 `.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 archive로 이동한다.
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 수정은 런타임 책임이다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [REFACTOR-1] Monthly Aggregation Core | [ ] |
| [REFACTOR-2] Provenance Fixture Evidence | [ ] |
## 구현 체크리스트
- [ ] `services/worker/internal/marketdata/aggregation` package를 추가하고 daily bars를 monthly bars와 provenance로 변환하는 순수 함수를 작성한다.
- [ ] aggregation rule을 고정한다: 월별 정렬, open=첫 daily open, close=마지막 daily close, high=max high, low=min low, volume=sum, timestamp=해당 월 1일 UTC, output timeframe=`market.TimeframeMonthly`.
- [ ] mixed instrument, non-daily timeframe, mixed currency, invalid decimal input을 error로 거부한다.
- [ ] 동일 daily fixture에서 deterministic monthly OHLCV와 provenance JSON fixture를 생성/검증한다. 검증: 동일 daily fixture에서 deterministic monthly OHLCV가 생성된다.
- [ ] `go test ./services/worker/internal/marketdata/...`, `go test ./services/worker/...`, `git diff --check`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [ ] `코드리뷰 결과``PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [ ] active `CODE_REVIEW-*-G??.md``code_review_local_G05_N.log`로 아카이브한다.
- [ ] active `PLAN-*-G??.md``plan_local_G05_M.log`로 아카이브한다.
- [ ] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md``agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [ ] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task 디렉터리를 archive로 이동한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active plan/review 파일을 작성하고 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `USER_REVIEW.md`를 작성하고 active plan/review/complete 파일을 남기지 않는다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 새 결정이 필요해 보여도 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 이 섹션은 선택된 SDD 결정 또는 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 차단할 때만 채운다. 외부 환경/secret/서비스 준비, 검증 증거 공백, 반복 실패, 일반 범위 조정은 사용자 리뷰 요청이 아니며 `검증 결과`, `계획 대비 변경 사항`, 또는 code-review의 일반 follow-up plan으로 처리한다._
- 상태: 없음
- 사유 유형: 없음
- 연결 대상: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- 월봉 OHLCV 규칙이 plan과 일치하는지 확인한다.
- fixture/provenance가 SDD S04 evidence로 충분한지 확인한다.
- DB schema나 provider 월봉 import가 불필요하게 섞이지 않았는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
### REFACTOR-1 중간 검증
```bash
$ go test ./services/worker/internal/marketdata/aggregation
(output)
```
### REFACTOR-2 중간 검증
```bash
$ go test ./services/worker/internal/marketdata/aggregation
(output)
```
### 최종 검증
```bash
$ go test ./services/worker/internal/marketdata/...
(output)
$ go test ./services/worker/...
(output)
$ git diff --check
(output)
```
---
> **[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.

View file

@ -0,0 +1,174 @@
<!-- task=m-backtest-multi-timeframe-coverage/03_monthly_aggregation plan=0 tag=REFACTOR -->
# Plan - Monthly Aggregation
## 이 파일을 읽는 구현 에이전트에게
구현 마지막 단계는 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 변경 내용과 검증 출력으로 채우는 것이다. 구현 후 active 파일을 그대로 두고 리뷰 준비를 보고한다. 종결, archive 이동, `complete.log` 작성은 code-review 전용이다.
선택된 SDD 결정 또는 Milestone `구현 잠금 > 결정 필요` 항목이 실구현을 막을 때만 review stub의 `사용자 리뷰 요청` 섹션을 채우고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 환경/secret/서비스 차단, 일반 범위 조정, 검증 공백은 사용자 리뷰 요청이 아니라 `검증 결과` 또는 `계획 대비 변경 사항`에 기록한다.
## 배경
SDD D02는 월봉 source of truth를 provider 월봉과 병행하지 않고 일봉 기반 deterministic aggregation 하나로 둔다. 현재 codebase에는 monthly vocabulary와 capability rejection은 있지만, 동일 daily fixture에서 월봉 OHLCV와 provenance를 생성하는 경계가 없다. 이 계획은 저장소 스키마를 흔들지 않고 worker 내부 aggregation 함수와 stable fixture evidence를 추가한다.
## 사용자 리뷰 요청 흐름
사용자 리뷰 요청은 active `CODE_REVIEW-*-G??.md``사용자 리뷰 요청` 섹션에만 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 내용을 따른다. 직접 사용자 프롬프트는 금지하며, code-review가 요청 타당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다.
## Roadmap Targets
- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-multi-timeframe-coverage.md`
- Task ids:
- `monthly-bars`: 월봉을 일봉 aggregation으로 생성하고, 생성 기준과 provenance를 저장/출력한다.
- Completion mode: check-on-pass
## Spec Targets
- SDD: `agent-roadmap/sdd/backtest-loop/backtest-multi-timeframe-coverage/SDD.md`
- Acceptance scenarios:
- `S04`: task=`monthly-bars`; evidence=`deterministic monthly OHLCV fixture와 provenance evidence`
- Completion mode: spec-check-on-pass
## 분석 결과
### 읽은 파일
- `AGENTS.md`
- `agent-roadmap/phase/backtest-loop/milestones/backtest-multi-timeframe-coverage.md`
- `agent-roadmap/sdd/backtest-loop/backtest-multi-timeframe-coverage/SDD.md`
- `agent-test/local/rules.md`
- `agent-test/local/worker-smoke.md`
- `agent-test/local/operations-smoke.md`
- `packages/domain/market/types.go`
- `services/worker/internal/providers/kis/daily_itemchartprice.go`
- `services/worker/internal/providers/kis/daily_itemchartprice_test.go`
- `services/worker/internal/marketdata/importer/importer.go`
- `services/worker/internal/marketdata/importer/importer_test.go`
- `services/worker/internal/storage/postgres/queries/queries.sql`
- `services/worker/internal/backtest/bar_source.go`
- `services/worker/internal/backtest/bar_source_test.go`
### 테스트 환경 규칙
- `test_env=local`.
- worker 내부 market data 경계 변경이므로 `agent-test/local/worker-smoke.md`를 적용한다.
- fixture 파일과 task artifact 변경 확인에는 `operations-smoke``git diff --check`를 보조로 사용한다.
- 적용 명령: `go test ./services/worker/internal/marketdata/...`, `go test ./services/worker/...`, `git diff --check`.
- fallback 없음. `<확인 필요>` 값 없음.
### 테스트 커버리지 공백
- monthly aggregation 함수 없음. 신규 package test가 필요하다.
- provenance durable metadata 저장소 없음. SDD는 1차 완료 근거를 stable CLI/JSONL 또는 fixture output으로 허용하므로 fixture output을 먼저 남기고 DB metadata는 후속 확장으로 둔다.
- decimal 비교/합산 helper 없음. 신규 test가 high/low 선택, open/close 순서, volume sum을 검증해야 한다.
### 심볼 참조
- renamed/removed symbol 없음.
- 추가 예정 symbol은 신규 package 내부에 한정한다. call site integration은 이 plan에서 fixture/test 중심으로 제한한다.
### 분할 판단
- split policy를 plan 작성 전에 평가했다.
- 공유 task group: `agent-task/m-backtest-multi-timeframe-coverage/`
- sibling split:
- `01_import_timeframe_contract`: independent contract foundation.
- `02+01_minute_import_rejection`: dependent minute rejection.
- `03_monthly_aggregation`: 이 plan, independent.
- predecessor 없음. 이 plan은 `01` 완료 없이 시작 가능하다.
### 범위 결정 근거
- DB migration은 제외한다. `bars` table은 이미 `(instrument_id, timeframe, timestamp)` key를 갖고 있고 provenance는 SDD가 fixture/output 1차 근거를 허용한다.
- provider 월봉 import는 제외한다. D02는 일봉 기반 deterministic aggregation 하나를 source of truth로 정했다.
- backtest run selector integration은 후속 `backtest-semantics` epic에서 다룬다.
### 빌드 등급
- `local-G05`: worker 내부 순수 aggregation과 fixture/test 중심이며, 저장소 또는 public API를 바꾸지 않는다.
## 구현 체크리스트
- [ ] `services/worker/internal/marketdata/aggregation` package를 추가하고 daily bars를 monthly bars와 provenance로 변환하는 순수 함수를 작성한다.
- [ ] aggregation rule을 고정한다: 월별 정렬, open=첫 daily open, close=마지막 daily close, high=max high, low=min low, volume=sum, timestamp=해당 월 1일 UTC, output timeframe=`market.TimeframeMonthly`.
- [ ] mixed instrument, non-daily timeframe, mixed currency, invalid decimal input을 error로 거부한다.
- [ ] 동일 daily fixture에서 deterministic monthly OHLCV와 provenance JSON fixture를 생성/검증한다. 검증: 동일 daily fixture에서 deterministic monthly OHLCV가 생성된다.
- [ ] `go test ./services/worker/internal/marketdata/...`, `go test ./services/worker/...`, `git diff --check`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
### [REFACTOR-1] Monthly Aggregation Core
#### 문제
SDD `Interface Contract`는 월봉 provenance와 deterministic aggregation을 요구하지만, `rg --sort path -n "Aggregate|Monthly|provenance"` 결과 worker marketdata 영역에 월봉 생성 함수가 없다.
#### 해결 방법
새 worker 내부 package를 만든다. 입력은 `[]market.Bar`, 출력은 `[]market.Bar`와 provenance slice/struct다. 입력을 timestamp 기준으로 stable sort하고, `market.TimeframeDaily`만 받는다. 월별 grouping은 UTC 기준 `time.Date(year, month, 1, 0, 0, 0, 0, time.UTC)`를 monthly timestamp로 쓴다.
#### 수정 파일 및 체크리스트
- [ ] `services/worker/internal/marketdata/aggregation/monthly.go`: aggregator와 provenance type.
- [ ] `services/worker/internal/marketdata/aggregation/monthly_test.go`: deterministic output tests.
#### 테스트 작성
- `TestAggregateDailyToMonthlyDeterministicOHLCV`: unordered daily input에서 월별 output ordering과 OHLCV를 검증한다.
- `TestAggregateDailyToMonthlyRejectsMixedInputs`: mixed instrument/timeframe/currency/invalid decimal을 검증한다.
#### 중간 검증
```bash
go test ./services/worker/internal/marketdata/aggregation
```
기대 결과: 신규 package test 통과.
### [REFACTOR-2] Provenance Fixture Evidence
#### 문제
Milestone task는 "생성 기준과 provenance를 저장/출력"을 요구하고, SDD는 1차 완료 근거로 stable fixture output을 허용한다. 현재 월봉 provenance fixture가 없다.
#### 해결 방법
기존 worker testdata convention을 따라 deterministic daily input과 expected monthly/provenance JSON을 둔다. provenance는 source timeframe, target timeframe, instrument id, source range, source count, aggregation rule version을 포함한다. test는 fixture를 marshal/compare하거나 expected fixture를 decode해 output과 비교한다.
#### 수정 파일 및 체크리스트
- [ ] `services/worker/internal/marketdata/aggregation/testdata/monthly_daily_input.json`
- [ ] `services/worker/internal/marketdata/aggregation/testdata/monthly_expected.json`
- [ ] `services/worker/internal/marketdata/aggregation/monthly_test.go`
#### 테스트 작성
- fixture comparison test를 추가한다. JSON key order는 Go struct marshal 또는 decoded semantic comparison 중 하나로 안정화한다.
#### 중간 검증
```bash
go test ./services/worker/internal/marketdata/aggregation
```
기대 결과: fixture comparison 통과.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `services/worker/internal/marketdata/aggregation/monthly.go` | REFACTOR-1 |
| `services/worker/internal/marketdata/aggregation/monthly_test.go` | REFACTOR-1, REFACTOR-2 |
| `services/worker/internal/marketdata/aggregation/testdata/*.json` | REFACTOR-2 |
## 최종 검증
```bash
go test ./services/worker/internal/marketdata/...
go test ./services/worker/...
git diff --check
```
기대 결과: 모든 명령 exit code 0. Go test cache output은 허용하지만, 신규 aggregation package는 최초 구현 시 실제 실행 결과를 review stub에 붙인다.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -97,6 +97,47 @@ func TestStorageBarSourceReadsMarketBars(t *testing.T) {
}
}
func TestStorageBarSourceKeepsTimeframesSeparate(t *testing.T) {
store := &sourceStore{}
inst := market.Instrument{ID: "KRX:005930", Market: market.MarketKR, Venue: market.VenueKRX, Currency: market.CurrencyKRW}
if err := store.UpsertInstrument(context.Background(), inst); err != nil {
t.Fatalf("upsert instrument: %v", err)
}
ts := time.Date(2024, 5, 27, 0, 0, 0, 0, time.UTC)
for _, bar := range []market.Bar{
{InstrumentID: inst.ID, Timeframe: market.TimeframeMonthly, Timestamp: ts},
{InstrumentID: inst.ID, Timeframe: market.TimeframeDaily, Timestamp: ts},
{InstrumentID: inst.ID, Timeframe: market.TimeframeMin1, Timestamp: ts},
} {
if err := store.UpsertBar(context.Background(), bar); err != nil {
t.Fatalf("upsert %s bar: %v", bar.Timeframe, err)
}
}
source := NewStorageBarSource(store, store)
for _, timeframe := range []market.Timeframe{market.TimeframeMonthly, market.TimeframeDaily, market.TimeframeMin1} {
t.Run(string(timeframe), func(t *testing.T) {
got, err := source.GetBarsForRun(context.Background(), backtest.RunSpec{
Market: market.MarketKR,
Timeframe: timeframe,
From: ts,
To: ts,
Selector: backtest.InputSelector{InstrumentIDs: []market.InstrumentID{inst.ID}},
})
if err != nil {
t.Fatalf("get bars for run: %v", err)
}
if len(got) != 1 {
t.Fatalf("bars: got %d, want 1", len(got))
}
if got[0].Timeframe != timeframe {
t.Fatalf("timeframe: got %q, want %q", got[0].Timeframe, timeframe)
}
})
}
}
func TestStorageBarSourceReturnsOnlyUSBarsForUSMarket(t *testing.T) {
store := &sourceStore{}
krx := market.Instrument{ID: "KRX:005930", Market: market.MarketKR, Venue: market.VenueKRX, Currency: market.CurrencyKRW}