# SDD: Backtest Multi-Timeframe Coverage

## 위치

- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-multi-timeframe-coverage.md`
- Phase: `agent-roadmap/phase/backtest-loop/PHASE.md`

## 상태

[승인됨]

## SDD 잠금

- 상태: 해제
- 사용자 리뷰: 없음
- 잠금 항목: 없음

## 문제 / 비목표

- 문제: ALT의 일봉 MVP 경로는 contracts, domain, storage, CLI scenario, backtest run이 닫혀 있지만 월봉과 분봉은 vocabulary, provider capability, 저장 key, backtest selector, freshness/readiness 의미가 함께 확정되어야 한다.
- 비목표:
  - 여러 timeframe을 동시에 참조하는 strategy DSL 또는 factor language 설계
  - tick data, order book, 실시간 streaming bar construction
  - Flutter 운영 화면 구현
  - 실거래 order routing 또는 broker execution 변경
  - 외부 provider credential 값이나 개인 secret 문서화

## Source of Truth

| 영역　　　　　　　| 기준　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　 | 메모　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　|
| -------------------| --------------------------------------------------------------------------------------------------| ---------------------------------------------------------------------------------------------------------------------------------------------------------|
| Roadmap　　　　　 | `agent-roadmap/phase/backtest-loop/milestones/backtest-multi-timeframe-coverage.md`　　　　　　　| Milestone 목표, 기능 Task, 범위 제외, 완료 후보 반영 기준　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　 |
| Code　　　　　　　| `packages/contracts/proto/alt/v1/common.proto`　　　　　　　　　　　　　　　　　　　　　　　　　 | `Timeframe` enum 원천. 현재 `DAILY`, `MINUTE_1`, `MINUTE_5`가 있으며 월봉 추가가 필요하다.　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　|
| Code　　　　　　　| `packages/domain/market/types.go`　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　| domain `market.Timeframe` 원천. 현재 `1d`, `1m`, `5m`가 있다.　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　 |
| Code　　　　　　　| `packages/contracts/proto/alt/v1/market.proto`, `packages/contracts/proto/alt/v1/backtest.proto` | `Bar`, `ListBarsRequest`, `BacktestRunSpec`의 timeframe 전달 계약　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　 |
| Code　　　　　　　| `apps/cli/internal/operator/scenario.go`　　　　　　　　　　　　　　　　　　　　　　　　　　　　 | scenario string vocabulary와 matrix validation 기준　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　 |
| Code　　　　　　　| `services/worker/internal/storage/postgres/queries/queries.sql`　　　　　　　　　　　　　　　　　| `bars` key는 `(instrument_id, timeframe, timestamp)`이며 timeframe별 독립 저장 기준이다.　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　|
| Code　　　　　　　| `services/worker/internal/storage/postgres/migrate_test.go`　　　　　　　　　　　　　　　　　　　| `bars` table은 normalized OHLCV 저장 계약을 유지하며 provider raw payload를 섞지 않는다.　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　|
| Code　　　　　　　| `services/worker/internal/backtest/bar_source.go`　　　　　　　　　　　　　　　　　　　　　　　　| backtest run selector가 storage에서 timeframe별 bars를 읽는 기준이다.　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　 |
| External Provider | KIS　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　| provider capability와 실제 지원 timeframe을 명시적으로 accepted/rejected로 판정해야 한다.　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　 |
| User Decision　　 | `user_review_0.log`　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　　| D01: 분봉 1차 import/backtest 검증 baseline은 `1m`/`5m`를 함께 포함한다. D02: 월봉 source of truth는 일봉 기반 deterministic aggregation 하나로 정한다. |

## State Machine

| 상태 | 진입 조건 | 다음 상태 | 근거 |
|------|-----------|-----------|------|
| `candidate` | monthly/daily/minute timeframe이 scenario 또는 API 입력에 등장한다. | `accepted` 또는 `rejected` | proto/domain/CLI validation, provider capability matrix |
| `rejected` | provider, market, venue, asset type, timeframe 조합이 지원되지 않는다. | terminal | typed error, stable text/JSONL rejected fixture |
| `accepted` | 조합이 capability matrix를 통과한다. | `imported` 또는 `aggregated` | provider import 또는 deterministic aggregation action |
| `imported` | provider 원천 bars가 normalized OHLCV로 저장된다. | `stored` | importer result, `bars` upsert |
| `aggregated` | 일봉 fixture 또는 저장 데이터에서 월봉 OHLCV가 결정적으로 생성된다. | `stored` | aggregation provenance, deterministic monthly fixture |
| `stored` | `bars`가 `(instrument_id, timeframe, timestamp)` key로 저장된다. | `backtest-selectable` 또는 `readiness-checked` | storage query, list bars scenario |
| `backtest-selectable` | `BacktestRunSpec.timeframe`와 selector가 저장 bars를 찾는다. | `backtest-run-terminal` | matrix dry-run, backtest run/result fixture |
| `readiness-checked` | collection freshness가 timeframe별 latest/missing/gap/duplicate를 계산한다. | terminal | CLI text/JSONL freshness output |
| `backtest-run-terminal` | engine이 selected bars로 run을 완료한다. | terminal | deterministic result, run status/result summary |

## Interface Contract

- 계약 원문: 없음
- 입력:
  - `Timeframe`: proto enum과 domain value, CLI string의 동일 의미 매핑. `daily`, `minute_1`, `minute_5` 기존 vocabulary는 호환 유지하고 `monthly`를 추가한다. 분봉 1차 import/backtest 검증 baseline에는 `minute_1`과 `minute_5`를 함께 포함한다.
  - `provider_capability`: provider, market, venue, asset type, timeframe별 accepted/rejected와 거부 사유.
  - `bar_source`: daily/minute provider 원천 bars 또는 월봉 deterministic aggregation 결과. 월봉 source of truth는 일봉 기반 deterministic aggregation 하나로 둔다.
  - `BacktestRunSpec.timeframe`: backtest matrix와 run selector가 사용할 단일 timeframe.
  - `freshness_request`: universe, timeframe, from/to window, expected dates.
- 출력:
  - `Bar`: instrument id, timeframe, timestamp, normalized OHLCV.
  - `provenance`: 월봉이 어떤 일봉 입력 범위와 aggregation 규칙에서 생성되었는지 확인 가능한 근거. 1차 완료 근거는 stable CLI/JSONL 또는 fixture output에 남기고, durable metadata가 필요하면 normalized `bars` key를 깨지 않는 별도 metadata 경계를 둔다.
  - `readiness`: timeframe별 latest, missing, gap, duplicate, provider delay 상태.
  - `backtest_result`: timeframe별 deterministic run id, status, result summary.
- 금지:
  - unsupported provider/timeframe 조합을 daily로 조용히 fallback하지 않는다.
  - monthly/daily/minute bars를 같은 query나 result에서 timeframe 구분 없이 섞지 않는다.
  - provider raw payload를 `bars` table의 정규화 계약에 섞지 않는다.
  - 여러 timeframe을 동시에 참조하는 전략 DSL은 이번 Milestone에서 확정하지 않는다.

## Acceptance Scenarios

| ID | Milestone Task | Given | When | Then |
|----|----------------|-------|------|------|
| S01 | `timeframe-vocab` | 기존 `daily`, `minute_1`, `minute_5` vocabulary와 신규 monthly 요구가 있다. | proto/domain/CLI mapping을 검증한다. | monthly가 추가되고 기존 daily/minute compatibility가 유지된다. |
| S02 | `capability-matrix` | KIS KR/US venue와 monthly/daily/minute 후보가 있다. | capability matrix validation을 실행한다. | accepted/rejected case와 거부 사유가 명시적으로 나온다. |
| S03 | `minute-ingest` | 분봉 import 요청 또는 미지원 조합 fixture가 있다. | provider import 또는 reject path를 실행한다. | success 또는 typed rejection이 stable text/JSONL로 남는다. |
| S04 | `monthly-bars` | 동일 daily fixture가 있다. | 월봉 deterministic aggregation을 실행한다. | deterministic monthly OHLCV와 provenance가 남는다. |
| S05 | `store-query` | 동일 instrument의 monthly/daily/minute bars가 있다. | 저장 후 timeframe별 조회를 실행한다. | 각 timeframe이 독립 조회되고 서로 섞이지 않는다. |
| S06 | `run-selector` | monthly/daily/minute scenario matrix가 있다. | dry-run validation과 matrix expand를 실행한다. | timeframe별 run id와 기간 검증 결과가 생성된다. |
| S07 | `fill-policy` | monthly/daily/minute fixture bars와 동일 전략이 있다. | backtest engine fixture를 실행한다. | timeframe별 deterministic result가 나온다. |
| S08 | `freshness-readiness` | monthly/daily/minute freshness fixture가 있다. | collection freshness/readiness scenario를 실행한다. | latest/missing/gap/duplicate 상태가 timeframe별로 구분된다. |

## Evidence Map

| Scenario | Required Evidence | `agent-task` 연결 | `Spec Completion` 기대 |
|----------|-------------------|------------------|---------------------------|
| S01 | contracts/domain/CLI mapping test | `agent-task/m-backtest-multi-timeframe-coverage/...` | `timeframe-vocab` task와 monthly 추가 및 기존 daily/minute compatibility test 결과 |
| S02 | capability matrix accepted/rejected tests | `agent-task/m-backtest-multi-timeframe-coverage/...` | `capability-matrix` task와 KIS KR/US case 결과 |
| S03 | minute import success 또는 typed reject fixture | `agent-task/m-backtest-multi-timeframe-coverage/...` | `minute-ingest` task와 stable text/JSONL evidence |
| S04 | deterministic monthly OHLCV fixture와 provenance evidence | `agent-task/m-backtest-multi-timeframe-coverage/...` | `monthly-bars` task와 D02 반영 결과 |
| S05 | storage/query test for `(instrument_id, timeframe, timestamp)` isolation | `agent-task/m-backtest-multi-timeframe-coverage/...` | `store-query` task와 독립 조회 결과 |
| S06 | matrix expand and dry-run validation output | `agent-task/m-backtest-multi-timeframe-coverage/...` | `run-selector` task와 monthly/daily/minute run id evidence |
| S07 | deterministic backtest fixture results | `agent-task/m-backtest-multi-timeframe-coverage/...` | `fill-policy` task와 timeframe별 result evidence |
| S08 | freshness/readiness fixture text/JSONL | `agent-task/m-backtest-multi-timeframe-coverage/...` | `freshness-readiness` task와 timeframe별 latest/missing/gap/duplicate evidence |

## Cross-repo Dependencies

- 없음

## Drift Check

- [x] Milestone 기능 Task와 Acceptance Scenario가 일치한다.
- [x] Evidence Map이 plan/code-review/complete.log에서 검증 가능하다.
- [x] agent-contract를 쓰는 경우 SDD에 계약 원문을 복제하지 않았다.
- [x] 사용자 리뷰 항목은 `user_review_0.log`로 해결 기록을 남겼다.

## 사용자 리뷰 이력

- `user_review_0.log`: D01은 `1m`/`5m` 1차 baseline 포함으로 결정했고, D02는 월봉 source of truth를 일봉 기반 deterministic aggregation 하나로 결정했다.

## 작업 컨텍스트

- 표준선: 일봉 MVP 경로는 이미 완료된 기준선이며 multi-timeframe은 기존 daily 경로를 깨지 않고 additive하게 확장한다.
- 표준선: proto-socket과 `packages/contracts/proto`가 ALT runtime 사이의 통신 기준이다.
- 표준선: `services/api`는 얇은 control plane으로 유지하고 provider import, aggregation, backtest execution은 worker 경계에 둔다.
- 표준선: provider가 직접 제공하지 않는 timeframe은 deterministic aggregation 또는 typed rejection 중 하나로만 처리한다.
- 표준선: 월봉 source of truth는 provider 월봉 원천 데이터와 병행하지 않고 일봉 기반 deterministic aggregation 하나로 둔다.
- 표준선: 백테스트는 먼저 단일 timeframe run을 확실히 닫고 multi-timeframe composition은 후속 Milestone으로 미룬다.
- 후속 SDD: `agent-roadmap/sdd/backtest-loop/scheduled-market-data-refresh/SDD.md`
