feat: backtest multi-timeframe coverage - import timeframe contract & archive migration

This commit is contained in:
toki 2026-06-18 16:22:36 +09:00
parent e5bb46aaab
commit dfabb9bfef
7 changed files with 112 additions and 30 deletions

View file

@ -35,40 +35,40 @@ task=m-backtest-multi-timeframe-coverage/01_import_timeframe_contract, plan=0, t
| 항목 | 완료 여부 |
|------|---------|
| [API-1] ImportDailyBarsRequest Timeframe Field | [ ] |
| [API-1] ImportDailyBarsRequest Timeframe Field | [x] |
## 구현 체크리스트
- [ ] `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의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
- [x] `packages/contracts/proto/alt/v1/market.proto`의 `ImportDailyBarsRequest`에 additive `Timeframe timeframe = 9;`를 추가한다.
- [x] `bin/contracts-gen`으로 Go/Dart generated contracts를 갱신한다. generated 파일은 손으로 수정하지 않는다.
- [x] `bin/contracts-check`와 `go test ./packages/contracts/gen/go/...`를 실행해 generated drift와 compile을 확인한다.
- [x] 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이 있어 유지했다고 확인한다.
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_local_G06_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_local_G06_M.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-multi-timeframe-coverage/01_import_timeframe_contract/`를 `agent-task/archive/YYYY/MM/m-backtest-multi-timeframe-coverage/01_import_timeframe_contract/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [x] 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로 이동한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
계획에 따라 정확히 구현했다. 추가 변경 사항 없음.
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
없음. 계획에 따라 additive 필드 하나만 추가하는 단순 변경이다.
## 사용자 리뷰 요청
@ -102,21 +102,22 @@ _구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후
### API-1 중간 검증
```bash
$ bin/contracts-gen
(output)
$ bin/contracts-check
(output)
$ go test ./packages/contracts/gen/go/...
(output)
? git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1 [no test files]
```
모든 명령이 exit code 0으로 성공했다.
### 최종 검증
```bash
$ bin/contracts-check
(output)
$ go test ./packages/contracts/gen/go/...
(output)
? git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1 [no test files]
```
모든 명령이 exit code 0으로 성공했다. generated drift 없으며, Go contract package가 정상적으로 컴파일된다.
---
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
@ -135,3 +136,17 @@ $ go test ./packages/contracts/gen/go/...
| 사용자 리뷰 요청 | Implementing agent | Keep `상태: 없음` unless selected SDD/Milestone lock decision blocks implementation |
| 리뷰어를 위한 체크포인트 | Fixed at stub creation | Review focus |
| 검증 결과 | Implementing agent | Paste actual stdout/stderr |
## 코드리뷰 결과
- 종합 판정: 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로 이동한다.

View file

@ -0,0 +1,35 @@
# Complete - m-backtest-multi-timeframe-coverage/01_import_timeframe_contract
## 완료 일시
2026-06-18
## 요약
ImportDailyBarsRequest에 additive timeframe contract field를 추가하고 generated Go/Dart contracts를 갱신한 1회 루프를 PASS로 종결했다.
## 루프 이력
| Plan | Review | Verdict | 메모 |
|------|--------|---------|------|
| `plan_local_G06_0.log` | `code_review_local_G06_0.log` | PASS | Additive proto field와 generated output 검증 통과 |
## 구현/정리 내용
- `ImportDailyBarsRequest`에 `Timeframe timeframe = 9;`를 추가했다.
- `bin/contracts-gen` 결과로 Go/Dart generated contract output을 갱신했다.
- downstream worker/API behavior 변경은 의도적으로 후속 split task 범위로 남겼다.
## 최종 검증
- `bin/contracts-check` - PASS; output 없음, generated drift 없음.
- `go test ./packages/contracts/gen/go/...` - PASS; `? git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1 [no test files]`.
- `bin/lint` - PASS; exit code 0, 기존 `avoid_print` info 13건 출력.
## 잔여 Nit
- 없음
## 후속 작업
- 없음

View file

@ -730,6 +730,7 @@ class ImportDailyBarsRequest extends $pb.GeneratedMessage {
$core.Iterable<$core.String>? symbols,
$core.String? fromYyyymmdd,
$core.String? toYyyymmdd,
$0.Timeframe? timeframe,
}) {
final result = create();
if (provider != null) result.provider = provider;
@ -740,6 +741,7 @@ class ImportDailyBarsRequest extends $pb.GeneratedMessage {
if (symbols != null) result.symbols.addAll(symbols);
if (fromYyyymmdd != null) result.fromYyyymmdd = fromYyyymmdd;
if (toYyyymmdd != null) result.toYyyymmdd = toYyyymmdd;
if (timeframe != null) result.timeframe = timeframe;
return result;
}
@ -766,6 +768,8 @@ class ImportDailyBarsRequest extends $pb.GeneratedMessage {
..pPS(6, _omitFieldNames ? '' : 'symbols')
..aOS(7, _omitFieldNames ? '' : 'fromYyyymmdd')
..aOS(8, _omitFieldNames ? '' : 'toYyyymmdd')
..aE<$0.Timeframe>(9, _omitFieldNames ? '' : 'timeframe',
enumValues: $0.Timeframe.values)
..hasRequiredFields = false;
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
@ -853,6 +857,15 @@ class ImportDailyBarsRequest extends $pb.GeneratedMessage {
$core.bool hasToYyyymmdd() => $_has(7);
@$pb.TagNumber(8)
void clearToYyyymmdd() => $_clearField(8);
@$pb.TagNumber(9)
$0.Timeframe get timeframe => $_getN(8);
@$pb.TagNumber(9)
set timeframe($0.Timeframe value) => $_setField(9, value);
@$pb.TagNumber(9)
$core.bool hasTimeframe() => $_has(8);
@$pb.TagNumber(9)
void clearTimeframe() => $_clearField(9);
}
class ImportDailyBarsResponse extends $pb.GeneratedMessage {

View file

@ -302,6 +302,14 @@ const ImportDailyBarsRequest$json = {
{'1': 'symbols', '3': 6, '4': 3, '5': 9, '10': 'symbols'},
{'1': 'from_yyyymmdd', '3': 7, '4': 1, '5': 9, '10': 'fromYyyymmdd'},
{'1': 'to_yyyymmdd', '3': 8, '4': 1, '5': 9, '10': 'toYyyymmdd'},
{
'1': 'timeframe',
'3': 9,
'4': 1,
'5': 14,
'6': '.alt.v1.Timeframe',
'10': 'timeframe'
},
],
};
@ -312,7 +320,7 @@ final $typed_data.Uint8List importDailyBarsRequestDescriptor = $convert.base64De
'LnYxLk1hcmtldFIGbWFya2V0EiMKBXZlbnVlGAQgASgOMg0uYWx0LnYxLlZlbnVlUgV2ZW51ZR'
'ISCgRuYW1lGAUgASgJUgRuYW1lEhgKB3N5bWJvbHMYBiADKAlSB3N5bWJvbHMSIwoNZnJvbV95'
'eXl5bW1kZBgHIAEoCVIMZnJvbVl5eXltbWRkEh8KC3RvX3l5eXltbWRkGAggASgJUgp0b1l5eX'
'ltbWRk');
'ltbWRkEi8KCXRpbWVmcmFtZRgJIAEoDjIRLmFsdC52MS5UaW1lZnJhbWVSCXRpbWVmcmFtZQ==');
@$core.Deprecated('Use importDailyBarsResponseDescriptor instead')
const ImportDailyBarsResponse$json = {

View file

@ -551,6 +551,7 @@ type ImportDailyBarsRequest struct {
Symbols []string `protobuf:"bytes,6,rep,name=symbols,proto3" json:"symbols,omitempty"`
FromYyyymmdd string `protobuf:"bytes,7,opt,name=from_yyyymmdd,json=fromYyyymmdd,proto3" json:"from_yyyymmdd,omitempty"`
ToYyyymmdd string `protobuf:"bytes,8,opt,name=to_yyyymmdd,json=toYyyymmdd,proto3" json:"to_yyyymmdd,omitempty"`
Timeframe Timeframe `protobuf:"varint,9,opt,name=timeframe,proto3,enum=alt.v1.Timeframe" json:"timeframe,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
@ -641,6 +642,13 @@ func (x *ImportDailyBarsRequest) GetToYyyymmdd() string {
return ""
}
func (x *ImportDailyBarsRequest) GetTimeframe() Timeframe {
if x != nil {
return x.Timeframe
}
return Timeframe_TIMEFRAME_UNSPECIFIED
}
type ImportDailyBarsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Provider string `protobuf:"bytes,1,opt,name=provider,proto3" json:"provider,omitempty"`
@ -757,7 +765,7 @@ const file_alt_v1_market_proto_rawDesc = "" +
"to_unix_ms\x18\x04 \x01(\x03R\btoUnixMs\"\\\n" +
"\x10ListBarsResponse\x12\x1f\n" +
"\x04bars\x18\x01 \x03(\v2\v.alt.v1.BarR\x04bars\x12'\n" +
"\x05error\x18\x02 \x01(\v2\x11.alt.v1.ErrorInfoR\x05error\"\x9a\x02\n" +
"\x05error\x18\x02 \x01(\v2\x11.alt.v1.ErrorInfoR\x05error\"\xcb\x02\n" +
"\x16ImportDailyBarsRequest\x12\x1a\n" +
"\bprovider\x18\x01 \x01(\tR\bprovider\x12#\n" +
"\rselector_kind\x18\x02 \x01(\tR\fselectorKind\x12&\n" +
@ -767,7 +775,8 @@ const file_alt_v1_market_proto_rawDesc = "" +
"\asymbols\x18\x06 \x03(\tR\asymbols\x12#\n" +
"\rfrom_yyyymmdd\x18\a \x01(\tR\ffromYyyymmdd\x12\x1f\n" +
"\vto_yyyymmdd\x18\b \x01(\tR\n" +
"toYyyymmdd\"\xa6\x01\n" +
"toYyyymmdd\x12/\n" +
"\ttimeframe\x18\t \x01(\x0e2\x11.alt.v1.TimeframeR\ttimeframe\"\xa6\x01\n" +
"\x17ImportDailyBarsResponse\x12\x1a\n" +
"\bprovider\x18\x01 \x01(\tR\bprovider\x12)\n" +
"\x10instrument_count\x18\x02 \x01(\x05R\x0finstrumentCount\x12\x1b\n" +
@ -830,12 +839,13 @@ var file_alt_v1_market_proto_depIdxs = []int32{
17, // 19: alt.v1.ListBarsResponse.error:type_name -> alt.v1.ErrorInfo
11, // 20: alt.v1.ImportDailyBarsRequest.market:type_name -> alt.v1.Market
12, // 21: alt.v1.ImportDailyBarsRequest.venue:type_name -> alt.v1.Venue
17, // 22: alt.v1.ImportDailyBarsResponse.error:type_name -> alt.v1.ErrorInfo
23, // [23:23] is the sub-list for method output_type
23, // [23:23] is the sub-list for method input_type
23, // [23:23] is the sub-list for extension type_name
23, // [23:23] is the sub-list for extension extendee
0, // [0:23] is the sub-list for field type_name
16, // 22: alt.v1.ImportDailyBarsRequest.timeframe:type_name -> alt.v1.Timeframe
17, // 23: alt.v1.ImportDailyBarsResponse.error:type_name -> alt.v1.ErrorInfo
24, // [24:24] is the sub-list for method output_type
24, // [24:24] is the sub-list for method input_type
24, // [24:24] is the sub-list for extension type_name
24, // [24:24] is the sub-list for extension extendee
0, // [0:24] is the sub-list for field type_name
}
func init() { file_alt_v1_market_proto_init() }

View file

@ -68,6 +68,7 @@ message ImportDailyBarsRequest {
repeated string symbols = 6;
string from_yyyymmdd = 7;
string to_yyyymmdd = 8;
Timeframe timeframe = 9;
}
message ImportDailyBarsResponse {