alt/agent-task/m-backtest-multi-timeframe-coverage/02_freshness_readiness/PLAN-local-G07.md

10 KiB

Plan - FRESHNESS

이 파일을 읽는 구현 에이전트에게

이 task는 구현 후 CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채워야 완료된다. 검증을 실행하고 active 파일은 그대로 둔 채 리뷰 준비 상태를 보고한다. 종결, archive, complete.log 작성은 code-review 전용이다. 선택된 SDD 결정 또는 Milestone 구현 잠금 > 결정 필요 항목이 막을 때만 review stub의 사용자 리뷰 요청을 채우고, 사용자에게 직접 질문하지 않는다.

배경

freshness-readiness Task는 collection freshness/readiness output이 monthly/daily/minute 상태를 구분해야 한다. 현재 freshness runner는 request timeframe으로 bar를 조회하지만 gap/duplicate/readiness expected list는 daily expected_yyyymmdd만 허용한다. 출력도 freshness step의 timeframe을 렌더링하지 않아 여러 timeframe 상태를 한 fixture에 담기 어렵다.

사용자 리뷰 요청 흐름

구현 중 직접 사용자 프롬프트를 띄우지 않는다. 선택된 SDD 결정 또는 Milestone lock 결정만 agent-ops/skills/common/_templates/implementation-user-review-request-section.md 형식으로 active review stub의 사용자 리뷰 요청에 기록하고, code-review가 USER_REVIEW.md 작성 여부를 판단한다.

Roadmap Targets

  • Milestone: agent-roadmap/phase/backtest-loop/milestones/backtest-multi-timeframe-coverage.md
  • Task ids:
    • freshness-readiness: collection freshness와 readiness output이 월/일/분 timeframe별 latest/missing/gap/duplicate 상태를 구분한다.
  • Completion mode: check-on-pass

Spec Targets

  • SDD: agent-roadmap/sdd/backtest-loop/backtest-multi-timeframe-coverage/SDD.md
  • Acceptance scenarios:
    • S08: task=freshness-readiness; evidence=freshness/readiness fixture text/JSONL for monthly/daily/minute latest/missing/gap/duplicate
  • Completion mode: spec-check-on-pass

분석 결과

읽은 파일

  • agent-test/local/rules.md
  • agent-test/local/operations-smoke.md
  • apps/cli/internal/operator/scenario.go
  • apps/cli/internal/operator/scenario_test.go
  • apps/cli/internal/operator/runner.go
  • apps/cli/internal/operator/runner_market_test.go
  • apps/cli/internal/operator/output.go

테스트 환경 규칙

  • test_env: local.
  • agent-test/local/rules.md를 읽었고 CLI/operator fixture 변경은 local Go test로 검증한다. operations-smoke는 bin/go.work 중심이지만 앱 CLI 작업용 별도 profile이 없어 repository Go package test를 fallback으로 사용한다.
  • 최종 명령은 go test -count=1 ./apps/cli/internal/operator로 둔다.

테스트 커버리지 공백

  • apps/cli/internal/operator/scenario.go:763-766expected_yyyymmdd를 daily 외 timeframe에서 거부한다.
  • apps/cli/internal/operator/runner.go:1321-1448은 timestamp를 YYYYMMDD로만 bucket 처리한다.
  • apps/cli/internal/operator/output.go:186-190timeframe을 matrix output에서만 렌더링하고 freshness output에는 포함하지 않는다.
  • committed freshness fixtures는 market_data_status_queryus_market_data_status_query daily만 있다.

심볼 참조

  • rename/remove 없음. 새 YAML field와 StepEvent field를 추가하되 기존 expected_yyyymmdd, latest_yyyymmdd, gap_yyyymmdd는 compatibility로 유지한다.

분할 판단

  • split policy를 먼저 평가했다.
  • shared task group: m-backtest-multi-timeframe-coverage.
  • sibling plans:
    • 01_fill_policy: domain/worker backtest 체결 정책.
    • 02_freshness_readiness: operator freshness/readiness schema/output/fixture.
  • 이 plan은 02_... 형식이며 01_...에 의존하지 않는다. predecessor complete.log가 없다.

범위 결정 근거

  • proto/API contract는 변경하지 않는다. collection_freshness는 기존 ListInstruments/ListBars 조합으로 계산한다.
  • provider import 성공 여부나 실제 market calendar는 다루지 않는다. fixture는 fake API bars로 stable evidence를 만든다.
  • 기존 daily fixtures는 compatibility evidence로 유지하되, multi-timeframe readiness fixture를 별도로 추가한다.

빌드 등급

  • local-G07: operator schema/runner/output/fixtures를 함께 바꾸지만 앱 CLI package test로 deterministic 검증 가능하다.

구현 체크리스트

  • apps/cli/internal/operator/scenario.go에 timeframe-aware expected bucket schema를 추가하고 daily expected_yyyymmdd compatibility를 유지한다.
  • apps/cli/internal/operator/runner.go가 monthly/daily/minute bucket으로 latest/missing/gap/duplicate/provider_delay를 계산하게 만든다.
  • apps/cli/internal/operator/output.go가 freshness/readiness step에 timeframe과 generic latest/gap/duplicate bucket fields를 text/JSONL로 출력하게 한다.
  • apps/cli/internal/operator/scenario_test.gorunner_market_test.go에 monthly/daily/minute_1/minute_5 validation/runner tests를 추가한다.
  • apps/cli/testdata/operator에 multi-timeframe freshness/readiness YAML과 expected JSONL fixture를 추가하고 runner/fixture validation tests에 evidence path를 연결한다. 검증: monthly/daily/minute freshness fixture가 stable text/JSONL로 구분된다.
  • 최종 검증 명령을 실행한다.
  • CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

[FRESHNESS-1] Timeframe-Aware Expected Buckets

문제

apps/cli/internal/operator/scenario.go:763-766expected_yyyymmdd를 daily에만 허용해 monthly/minute gap/duplicate readiness를 표현할 수 없다.

763 if len(step.Request.ExpectedYYYYMMDD) > 0 {
764     if step.Request.Timeframe != "daily" {
765         return fmt.Errorf("step %q: expected_yyyymmdd is only supported for timeframe \"daily\"", step.ID)

해결 방법

Request.ExpectedBuckets []string yaml:"expected_buckets"Expect.LatestBucket string yaml:"latest_bucket"을 추가한다. format은 timeframe별로 검증한다: monthly=YYYYMM, daily=YYYYMMDD, minute_1/minute_5=YYYYMMDDHHMM. daily expected_yyyymmdd는 기존 fixtures용으로 유지하고 내부에서 daily expected buckets로 normalize한다.

수정 파일 및 체크리스트

  • apps/cli/internal/operator/scenario.go: schema field, validation, helper 추가.
  • apps/cli/internal/operator/scenario_test.go: monthly/minute valid/invalid/duplicate/out-of-window tests 추가.

테스트 작성

  • 작성: TestValidateExpectedBuckets.

중간 검증

go test -count=1 ./apps/cli/internal/operator -run 'TestValidateExpected(Buckets|YYYYMMDD)'

기대: exit 0.

[FRESHNESS-2] Bucketed Freshness Runner

문제

apps/cli/internal/operator/runner.go:1321-1448은 모든 bar timestamp를 YYYYMMDD로 집계한다. minute duplicate와 monthly gap을 같은 날짜 단위로 뭉개므로 S08을 증명할 수 없다.

1322 t := time.UnixMilli(bar.GetTimestampUnixMs()).UTC()
1323 d := t.Format("20060102")
1324 dateCounts[d]++

해결 방법

runner에 freshnessBucket(tf, unixMs)와 normalized expected buckets를 둔다. latest bucket은 max timestamp의 bucket으로 계산하고, gap/duplicate/provider delay는 bucket index 기준으로 계산한다. daily compatibility output은 기존 latest_yyyymmdd/gap_yyyymmdd도 계속 채운다.

수정 파일 및 체크리스트

  • apps/cli/internal/operator/runner.go: bucket map 계산, expectation comparison 추가.
  • apps/cli/internal/operator/runner_market_test.go: monthly gap, minute duplicate, minute delay tests 추가.

테스트 작성

  • 작성: TestRunCollectionFreshnessBucketsByTimeframe.

중간 검증

go test -count=1 ./apps/cli/internal/operator -run 'TestRunCollectionFreshness(BucketsByTimeframe|ZeroBarRegression)'

기대: exit 0.

[FRESHNESS-3] Stable Output And Fixture Evidence

문제

apps/cli/internal/operator/output.go:186-190timeframe을 matrix events에만 출력하고, output.go:233-255의 freshness block에는 timeframe/generic bucket keys가 없다. Existing freshness fixtures are daily only.

186 if ev.MatrixRunID != "" {
189     fields["timeframe"] = ev.Timeframe

해결 방법

freshness events에 timeframe, latest_bucket, gap_buckets, duplicate_buckets를 text/JSONL로 출력한다. 새 fixture multi_timeframe_freshness_readiness.yamlexpected/multi_timeframe_freshness_readiness.jsonl을 추가해 monthly, daily, minute_1, minute_5 lines를 모두 포함한다. runner_market_test.go가 새 fixture path와 JSONL field evidence를 검증하게 한다.

수정 파일 및 체크리스트

  • apps/cli/internal/operator/output.go: StepEvent fields와 text/JSONL rendering.
  • apps/cli/testdata/operator/multi_timeframe_freshness_readiness.yaml: 새 fixture.
  • apps/cli/testdata/operator/expected/multi_timeframe_freshness_readiness.jsonl: 새 expected output.
  • apps/cli/internal/operator/runner_market_test.go: 새 fixture JSONL validity/path 검증.

테스트 작성

  • 작성: TestMultiTimeframeFreshnessReadinessFixture.

중간 검증

go test -count=1 ./apps/cli/internal/operator -run 'Test(MultiTimeframeFreshnessReadinessFixture|ExpectedOutputFixturesAreValidJSONLines)'

기대: exit 0.

수정 파일 요약

파일 항목
apps/cli/internal/operator/scenario.go FRESHNESS-1
apps/cli/internal/operator/scenario_test.go FRESHNESS-1
apps/cli/internal/operator/runner.go FRESHNESS-2
apps/cli/internal/operator/runner_market_test.go FRESHNESS-2, FRESHNESS-3
apps/cli/internal/operator/output.go FRESHNESS-3
apps/cli/testdata/operator/*.yaml FRESHNESS-3
apps/cli/testdata/operator/expected/*.jsonl FRESHNESS-3

최종 검증

go test -count=1 ./apps/cli/internal/operator

기대: exit 0, new multi-timeframe freshness/readiness fixture and output tests pass.

모든 코드 변경 완료 후 반드시 CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.