feat: backtest data collection infrastructure implementation
- Add backtest proto definitions with selector and state types - Update domain types for backtest session and configuration - Implement backtest selector flow for data range input - Add CLI operator client and handoff test coverage - Update generated code for proto changes - Add worker socket backtest mapping implementations - Include agent-task for milestone tracking
This commit is contained in:
parent
5f7b972c44
commit
a533efce9e
21 changed files with 1722 additions and 137 deletions
|
|
@ -48,20 +48,20 @@
|
||||||
|
|
||||||
- [x] [provider-boundary] KIS/KRX/DART/US provider 등 확장 가능한 data source adapter 경계를 정리한다.
|
- [x] [provider-boundary] KIS/KRX/DART/US provider 등 확장 가능한 data source adapter 경계를 정리한다.
|
||||||
- [x] [normalized-store] 백테스트에 필요한 정제 필드만 DB에 저장하고 원천 payload 전체 저장은 기본값에서 제외한다.
|
- [x] [normalized-store] 백테스트에 필요한 정제 필드만 DB에 저장하고 원천 payload 전체 저장은 기본값에서 제외한다.
|
||||||
- [ ] [gap-handling] 결측, 휴장일, 중복, provider 지연을 백테스트 입력에서 식별할 수 있게 한다.
|
- [x] [gap-handling] 결측, 휴장일, 중복, provider 지연을 백테스트 입력에서 식별할 수 있게 한다.
|
||||||
|
|
||||||
### Epic: [backtest-ready] Repeatable execution input
|
### Epic: [backtest-ready] Repeatable execution input
|
||||||
|
|
||||||
정제 데이터가 실제 백테스트 실행 입력으로 반복 사용될 수 있게 연결한다.
|
정제 데이터가 실제 백테스트 실행 입력으로 반복 사용될 수 있게 연결한다.
|
||||||
|
|
||||||
- [ ] [input-selector] 저장된 정제 데이터에서 종목군, 기간, frequency를 선택해 backtest run input으로 넘기는 경계를 만든다.
|
- [ ] [input-selector] 저장된 정제 데이터에서 종목군, 기간, frequency를 선택해 backtest run input으로 넘기는 경계를 만든다.
|
||||||
- [ ] [headless-smoke] 샘플 종목군 수집부터 기본 backtest 실행까지 headless smoke로 검증한다.
|
- [x] [headless-smoke] 샘플 종목군 수집부터 기본 backtest 실행까지 headless smoke로 검증한다.
|
||||||
|
|
||||||
## 완료 리뷰
|
## 완료 리뷰
|
||||||
|
|
||||||
- 상태: 없음
|
- 상태: 없음
|
||||||
- 요청일: 없음
|
- 요청일: 없음
|
||||||
- 완료 근거: `watchlist-config`, `daily-collect`, `freshness-status`, `provider-boundary`, `normalized-store` 기능 Task가 완료되었으나 전체 마일스톤 완료 조건은 아직 충족되지 않았다.
|
- 완료 근거: `watchlist-config`, `daily-collect`, `freshness-status`, `provider-boundary`, `normalized-store`, `gap-handling`, `headless-smoke` 기능 Task가 완료되었으나 `input-selector`가 남아 있어 전체 마일스톤 완료 조건은 아직 충족되지 않았다.
|
||||||
- 리뷰 필요:
|
- 리뷰 필요:
|
||||||
- [ ] 사용자가 완료 결과를 확인했다
|
- [ ] 사용자가 완료 결과를 확인했다
|
||||||
- [ ] archive 이동을 승인했다
|
- [ ] archive 이동을 승인했다
|
||||||
|
|
@ -86,6 +86,8 @@
|
||||||
- 완료 근거(freshness-status): `collection_freshness` operator scenario가 freshness status, missing count, latest date를 text/JSONL 출력과 handoff matrix에 포함하며, 2026-06-14에 `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli`가 통과했다. 증거: `agent-task/archive/2026/06/m-backtest-data-collection-infrastructure/02+01_freshness_status/complete.log`.
|
- 완료 근거(freshness-status): `collection_freshness` operator scenario가 freshness status, missing count, latest date를 text/JSONL 출력과 handoff matrix에 포함하며, 2026-06-14에 `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli`가 통과했다. 증거: `agent-task/archive/2026/06/m-backtest-data-collection-infrastructure/02+01_freshness_status/complete.log`.
|
||||||
- 완료 근거(provider-boundary): provider vocabulary에 KIS/KRX/DART/Yahoo data source id를 고정하고 `ProviderCapability`가 provider, venue, market, daily-bar timeframe 경계를 검증하도록 보강했다. 2026-06-14에 `go test -count=1 ./packages/domain/...`와 `go test -count=1 ./services/worker/internal/jobs`가 통과했다.
|
- 완료 근거(provider-boundary): provider vocabulary에 KIS/KRX/DART/Yahoo data source id를 고정하고 `ProviderCapability`가 provider, venue, market, daily-bar timeframe 경계를 검증하도록 보강했다. 2026-06-14에 `go test -count=1 ./packages/domain/...`와 `go test -count=1 ./services/worker/internal/jobs`가 통과했다.
|
||||||
- 완료 근거(normalized-store): PostgreSQL bars schema와 SQL query contract가 normalized OHLCV 필드만 사용하고 raw provider payload column을 포함하지 않도록 guard test를 추가했다. 2026-06-14에 `go test -count=1 ./services/worker/internal/storage/postgres`가 통과했다.
|
- 완료 근거(normalized-store): PostgreSQL bars schema와 SQL query contract가 normalized OHLCV 필드만 사용하고 raw provider payload column을 포함하지 않도록 guard test를 추가했다. 2026-06-14에 `go test -count=1 ./services/worker/internal/storage/postgres`가 통과했다.
|
||||||
|
- 완료 근거(gap-handling): `collection_freshness` scenario가 `expected_yyyymmdd` 기반 gap, duplicate, provider delay를 text/JSONL 출력과 expectation mismatch 검증에 포함하며, matched-zero-bars symbol의 double count regression을 보강했다. 2026-06-14에 `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli`가 통과했다. 증거: `agent-task/archive/2026/06/m-backtest-data-collection-infrastructure/03+02_gap_handling/complete.log`.
|
||||||
|
- 완료 근거(headless-smoke): `backtest_run_polling` operator scenario가 named universe 기반 `import_daily_bars` 후 `start_backtest`와 `poll_backtest_run`으로 terminal 상태까지 확인하는 headless smoke를 문서화했다. 2026-06-14에 `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli`가 통과했다.
|
||||||
- 선행 작업: Backtest Engine Baseline, Backtest Analysis Surface
|
- 선행 작업: Backtest Engine Baseline, Backtest Analysis Surface
|
||||||
- 후속 작업: Backtest Scenario Automation, Data Quality Monitoring, Agent Tooling Bridge 후보
|
- 후속 작업: Backtest Scenario Automation, Data Quality Monitoring, Agent Tooling Bridge 후보
|
||||||
- 확인 필요: 없음
|
- 확인 필요: 없음
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
<!-- task=m-backtest-data-collection-infrastructure/01_input_selector_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 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-14
|
||||||
|
task=m-backtest-data-collection-infrastructure/01_input_selector_contract, plan=0, tag=API
|
||||||
|
|
||||||
|
## 이 파일을 읽는 리뷰 에이전트에게
|
||||||
|
|
||||||
|
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||||
|
|
||||||
|
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||||
|
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||||
|
|
||||||
|
1. 판정을 append한다.
|
||||||
|
2. `CODE_REVIEW-cloud-G06.md` -> `code_review_cloud_G06_N.log`, `PLAN-cloud-G06.md` -> `plan_cloud_G06_M.log`로 아카이브한다.
|
||||||
|
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-backtest-data-collection-infrastructure/01_input_selector_contract/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다.
|
||||||
|
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
|
||||||
|
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 구현 항목별 완료 여부
|
||||||
|
|
||||||
|
| 항목 | 완료 여부 |
|
||||||
|
|------|---------|
|
||||||
|
| [API-1] Proto Selector Shape | [x] |
|
||||||
|
| [API-2] Domain And Worker Mapping | [x] |
|
||||||
|
| [API-3] Forwarding And Client Request Preservation | [x] |
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
|
||||||
|
- [x] `BacktestRunSpec`에 additive selector message/field를 추가하고 generated Go를 갱신한다.
|
||||||
|
- [x] domain `RunSpec`과 worker socket mapping이 selector를 정상/경계 케이스로 왕복하도록 구현한다.
|
||||||
|
- [x] API/workerclient/CLI request 생성 테스트가 selector 필드 보존을 확인하도록 갱신한다.
|
||||||
|
- [x] `bin/contracts-gen`, `bin/contracts-check`, 지정 `go test -count=1` 검증을 실행한다.
|
||||||
|
- [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_cloud_G06_N.log`로 아카이브한다.
|
||||||
|
- [x] active `PLAN-*-G??.md`를 `plan_cloud_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-data-collection-infrastructure/01_input_selector_contract/`를 `agent-task/archive/YYYY/MM/m-backtest-data-collection-infrastructure/01_input_selector_contract/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||||
|
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||||
|
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-backtest-data-collection-infrastructure/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
|
||||||
|
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G06.md`와 `CODE_REVIEW-cloud-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가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
|
||||||
|
|
||||||
|
## 계획 대비 변경 사항
|
||||||
|
|
||||||
|
- 계획대로 모두 이상 없이 구현 완료되었습니다.
|
||||||
|
|
||||||
|
## 주요 설계 결정
|
||||||
|
|
||||||
|
- `BacktestInputSelector`는 `repeated string instrument_ids`와 `repeated string symbols`를 필드로 하여 `BacktestRunSpec`의 6번 태그에 추가되었습니다.
|
||||||
|
- domain layer의 `RunSpec`에 `Selector InputSelector` 필드를 추가하고, worker socket mapping(`backtest_mapping.go`)에서 `strings.TrimSpace` 검증을 통해 blank symbol/instrument ID가 요청되었을 때 reject하도록 견고화하였습니다.
|
||||||
|
- API layer 및 CLI operator client layer에서 `StartBacktest` 요청 생성 시 selector의 보존을 검증하는 round-trip 테스트들을 추가 및 보완하였습니다.
|
||||||
|
|
||||||
|
## 사용자 리뷰 요청
|
||||||
|
|
||||||
|
- 상태: 없음
|
||||||
|
- 사유 유형: 없음
|
||||||
|
- 결정 필요: 없음
|
||||||
|
- 차단 근거: 없음
|
||||||
|
- 실행한 검증/명령: 없음
|
||||||
|
- 자동 후속 불가 이유: 없음
|
||||||
|
- 재개 조건: 없음
|
||||||
|
|
||||||
|
## 리뷰어를 위한 체크포인트
|
||||||
|
|
||||||
|
- `BacktestRunSpec.selector`가 additive field이고 기존 field number를 바꾸지 않았는지 확인한다.
|
||||||
|
- worker socket mapping이 blank selector entries를 reject하고 정상 selector를 from/to proto 모두 보존하는지 확인한다.
|
||||||
|
- API/workerclient/CLI request path가 selector를 임의로 검증하거나 삭제하지 않는지 확인한다.
|
||||||
|
|
||||||
|
## 검증 결과
|
||||||
|
|
||||||
|
### API-1 중간 검증
|
||||||
|
```bash
|
||||||
|
$ bin/contracts-gen
|
||||||
|
$ bin/contracts-check
|
||||||
|
(exit 0, no output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### API-2 중간 검증
|
||||||
|
```bash
|
||||||
|
$ go test -count=1 ./packages/domain/... ./services/worker/internal/socket
|
||||||
|
ok git.toki-labs.com/toki/alt/packages/domain/backtest 0.004s
|
||||||
|
ok git.toki-labs.com/toki/alt/packages/domain/market 0.002s
|
||||||
|
ok git.toki-labs.com/toki/alt/packages/domain/trading 0.002s
|
||||||
|
ok git.toki-labs.com/toki/alt/services/worker/internal/socket 0.062s
|
||||||
|
```
|
||||||
|
|
||||||
|
### API-3 중간 검증
|
||||||
|
```bash
|
||||||
|
$ go test -count=1 ./services/api/internal/socket ./services/api/internal/workerclient ./apps/cli/internal/operator
|
||||||
|
ok git.toki-labs.com/toki/alt/services/api/internal/socket 0.012s
|
||||||
|
ok git.toki-labs.com/toki/alt/services/api/internal/workerclient 0.412s
|
||||||
|
ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.206s
|
||||||
|
```
|
||||||
|
|
||||||
|
### 최종 검증
|
||||||
|
```bash
|
||||||
|
$ bin/contracts-gen
|
||||||
|
$ bin/contracts-check
|
||||||
|
$ go test -count=1 ./packages/domain/... ./services/worker/internal/socket ./services/api/internal/socket ./services/api/internal/workerclient ./apps/cli/internal/operator
|
||||||
|
ok git.toki-labs.com/toki/alt/packages/domain/backtest 0.002s
|
||||||
|
ok git.toki-labs.com/toki/alt/packages/domain/market 0.002s
|
||||||
|
ok git.toki-labs.com/toki/alt/packages/domain/trading 0.002s
|
||||||
|
ok git.toki-labs.com/toki/alt/services/worker/internal/socket 0.062s
|
||||||
|
ok git.toki-labs.com/toki/alt/services/api/internal/socket 0.010s
|
||||||
|
ok git.toki-labs.com/toki/alt/services/api/internal/workerclient 0.412s
|
||||||
|
ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.187s
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ownership
|
||||||
|
|
||||||
|
| 섹션 | 소유자 |
|
||||||
|
|------|--------|
|
||||||
|
| 구현 항목별 완료 여부 | Implementing agent |
|
||||||
|
| 구현 체크리스트 | Implementing agent |
|
||||||
|
| 계획 대비 변경 사항 | Implementing agent |
|
||||||
|
| 주요 설계 결정 | Implementing agent |
|
||||||
|
| 사용자 리뷰 요청 | Implementing agent |
|
||||||
|
| 검증 결과 | Implementing agent |
|
||||||
|
| 코드리뷰 전용 체크리스트 | Review agent |
|
||||||
|
| 코드리뷰 결과 | Review agent |
|
||||||
|
|
||||||
|
## 코드리뷰 결과
|
||||||
|
|
||||||
|
- 종합 판정: PASS
|
||||||
|
- 차원별 평가:
|
||||||
|
- correctness: Pass
|
||||||
|
- completeness: Pass
|
||||||
|
- test coverage: Pass
|
||||||
|
- API contract: Pass
|
||||||
|
- code quality: Pass
|
||||||
|
- plan deviation: Pass
|
||||||
|
- verification trust: Pass
|
||||||
|
- 발견된 문제: 없음
|
||||||
|
- 리뷰어 검증:
|
||||||
|
- `bin/contracts-gen`: PASS; exit 0, no output
|
||||||
|
- `bin/contracts-check`: PASS; exit 0, no output
|
||||||
|
- `go test -count=1 ./packages/contracts/gen/go/...`: PASS; no test files
|
||||||
|
- `go test -count=1 ./packages/domain/... ./services/worker/internal/socket ./services/api/internal/socket ./services/api/internal/workerclient ./apps/cli/internal/operator`: PASS
|
||||||
|
- `bin/lint`: PASS; exit 0, Flutter analyze info-only `avoid_print` findings in existing Mattermost auth service
|
||||||
|
- `flutter test` in `apps/client`: PASS; `All tests passed!`
|
||||||
|
- `git diff --check`: PASS; no output
|
||||||
|
- 다음 단계: PASS이므로 `complete.log`를 작성하고 active task directory를 archive로 이동한다.
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Complete - m-backtest-data-collection-infrastructure/01_input_selector_contract
|
||||||
|
|
||||||
|
## 완료 일시
|
||||||
|
|
||||||
|
2026-06-14
|
||||||
|
|
||||||
|
## 요약
|
||||||
|
|
||||||
|
Backtest input selector contract, domain mapping, and forwarding preservation were reviewed in loop 0 with final verdict PASS.
|
||||||
|
|
||||||
|
## 루프 이력
|
||||||
|
|
||||||
|
| Plan | Review | Verdict | 메모 |
|
||||||
|
|------|--------|---------|------|
|
||||||
|
| `plan_cloud_G06_0.log` | `code_review_cloud_G06_0.log` | PASS | Selector contract and round-trip tests are complete; no Required, Suggested, or Nit issues remain. |
|
||||||
|
|
||||||
|
## 구현/정리 내용
|
||||||
|
|
||||||
|
- Added additive `BacktestInputSelector` and `BacktestRunSpec.selector = 6` to the ALT protobuf contract, with regenerated Go and Dart outputs.
|
||||||
|
- Added domain `backtest.InputSelector` and worker socket mapping for proto-to-domain validation and domain-to-proto preservation.
|
||||||
|
- Added API, workerclient, worker socket, and operator client tests that verify selector forwarding, round-trip behavior, and blank selector entry rejection.
|
||||||
|
|
||||||
|
## 최종 검증
|
||||||
|
|
||||||
|
- `bin/contracts-gen` - PASS; exit 0, no output.
|
||||||
|
- `bin/contracts-check` - PASS; exit 0, no output.
|
||||||
|
- `go test -count=1 ./packages/contracts/gen/go/...` - PASS; `? git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1 [no test files]`.
|
||||||
|
- `go test -count=1 ./packages/domain/... ./services/worker/internal/socket ./services/api/internal/socket ./services/api/internal/workerclient ./apps/cli/internal/operator` - PASS; all listed packages returned `ok`.
|
||||||
|
- `bin/lint` - PASS; exit 0, Flutter analyze reported existing info-only `avoid_print` findings in `mattermost_auth_service.dart`.
|
||||||
|
- `flutter test` in `apps/client` - PASS; `All tests passed!`.
|
||||||
|
- `git diff --check` - PASS; no output.
|
||||||
|
|
||||||
|
## 잔여 Nit
|
||||||
|
|
||||||
|
- 없음
|
||||||
|
|
||||||
|
## 후속 작업
|
||||||
|
|
||||||
|
- 없음
|
||||||
|
|
@ -0,0 +1,262 @@
|
||||||
|
<!-- task=m-backtest-data-collection-infrastructure/01_input_selector_contract plan=0 tag=API -->
|
||||||
|
# PLAN-cloud-G06: Backtest Input Selector Contract
|
||||||
|
|
||||||
|
## 이 파일을 읽는 구현 에이전트에게
|
||||||
|
이 계획은 backtest run input에 종목군 selector를 싣기 위한 계약, domain, mapping, round-trip 테스트만 만든다. 구현 후 반드시 검증을 실행하고 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션에 실제 변경 내용과 stdout/stderr를 채운 뒤 active 파일을 유지한 채 리뷰 준비를 보고한다. 최종 PASS, log rename, `complete.log`, archive 이동은 code-review skill 전용이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret, 또는 범위 충돌 때문에 멈춰야 하면 사용자에게 직접 묻지 말고 review stub의 `사용자 리뷰 요청`에 증거를 채운 뒤 중단한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 공백은 사용자 리뷰 요청 사유가 아니다.
|
||||||
|
|
||||||
|
## 배경
|
||||||
|
현재 `BacktestRunSpec`은 strategy, market, timeframe, 기간만 담고 종목군을 지정할 수 없다. `backtest-ready`의 `input-selector`는 저장된 정제 데이터에서 종목군/기간/frequency를 backtest run input으로 넘기는 경계가 필요하다. 이 subtask는 wire/domain 경계를 먼저 만들고, 저장소 filtering과 operator flow는 후속 subtask에서 연결한다.
|
||||||
|
|
||||||
|
## 사용자 리뷰 요청 흐름
|
||||||
|
구현 중 사용자 전용 결정이나 범위 충돌이 생기면 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식 그대로 기록한다. 구현 에이전트는 채팅으로 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. code-review가 사용자 리뷰 요청의 타당성을 검증하고 실제 `USER_REVIEW.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/update-roadmap/SKILL.md`
|
||||||
|
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
|
||||||
|
- `agent-test/local/rules.md`
|
||||||
|
- `agent-test/local/operations-smoke.md`
|
||||||
|
- `agent-test/local/worker-smoke.md`
|
||||||
|
- `agent-test/local/contracts-smoke.md`
|
||||||
|
- `agent-test/local/domain-model-smoke.md`
|
||||||
|
- `agent-test/local/api-smoke.md`
|
||||||
|
- `agent-ops/rules/project/domain/operations/rules.md`
|
||||||
|
- `agent-ops/rules/project/domain/worker/rules.md`
|
||||||
|
- `agent-ops/rules/project/domain/contracts/rules.md`
|
||||||
|
- `agent-ops/rules/project/domain/domain-model/rules.md`
|
||||||
|
- `agent-ops/rules/project/domain/api/rules.md`
|
||||||
|
- `agent-roadmap/current.md`
|
||||||
|
- `agent-roadmap/phase/backtest-loop/PHASE.md`
|
||||||
|
- `agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md`
|
||||||
|
- `packages/contracts/proto/alt/v1/backtest.proto`
|
||||||
|
- `packages/contracts/proto/alt/v1/market.proto`
|
||||||
|
- `packages/domain/backtest/types.go`
|
||||||
|
- `packages/domain/backtest/types_test.go`
|
||||||
|
- `packages/domain/market/types.go`
|
||||||
|
- `services/worker/internal/socket/backtest_mapping.go`
|
||||||
|
- `services/worker/internal/socket/backtest_mapping_test.go`
|
||||||
|
- `services/worker/internal/socket/backtest.go`
|
||||||
|
- `services/worker/internal/socket/backtest_test.go`
|
||||||
|
- `services/api/internal/socket/backtest.go`
|
||||||
|
- `services/api/internal/socket/backtest_test.go`
|
||||||
|
- `services/api/internal/workerclient/client.go`
|
||||||
|
- `services/api/internal/workerclient/client_test.go`
|
||||||
|
- `apps/cli/internal/operator/scenario.go`
|
||||||
|
- `apps/cli/internal/operator/scenario_test.go`
|
||||||
|
- `apps/cli/internal/operator/runner.go`
|
||||||
|
- `apps/cli/internal/operator/runner_backtest_test.go`
|
||||||
|
- `apps/cli/internal/operator/client.go`
|
||||||
|
- `apps/cli/internal/operator/client_test.go`
|
||||||
|
|
||||||
|
### 테스트 환경 규칙
|
||||||
|
- `test_env=local`로 판단했다.
|
||||||
|
- `agent-test/local/rules.md`가 존재해 읽었다.
|
||||||
|
- 매칭 profile로 `operations-smoke.md`, `worker-smoke.md`, `contracts-smoke.md`, `domain-model-smoke.md`, `api-smoke.md`를 읽었다.
|
||||||
|
- 계약 변경이 있으므로 `bin/contracts-gen` 후 `bin/contracts-check`를 사용한다.
|
||||||
|
- Go 검증은 fresh 실행이 필요하므로 `go test -count=1`만 허용하고 test cache 출력은 허용하지 않는다.
|
||||||
|
- 현재 기준으로 `bin/contracts-check`와 통합 `go test -count=1 ./packages/domain/... ./services/worker/internal/backtest ./services/worker/internal/jobs ./services/worker/internal/socket ./services/api/internal/socket ./services/api/internal/workerclient ./apps/cli/internal/operator ./apps/cli/internal/cli` 실행 가능함을 확인했다.
|
||||||
|
- `agent-contract/index.md`는 없어서 계약 포인터 문서는 적용하지 않았다.
|
||||||
|
|
||||||
|
### 테스트 커버리지 공백
|
||||||
|
- 새 public proto field는 generated Go, worker socket mapping, API/workerclient round-trip 정상/경계 테스트가 필요하다.
|
||||||
|
- selector가 실제 저장소 bar를 줄이는 동작은 이 subtask에서 구현하지 않는다. 그 공백은 `02+01_storage_bar_selector`가 닫는다.
|
||||||
|
- operator scenario가 named universe를 start request selector로 바꾸는 흐름은 이 subtask에서 구현하지 않는다. 그 공백은 `03+01,02_backtest_selector_flow`가 닫는다.
|
||||||
|
|
||||||
|
### 심볼 참조
|
||||||
|
- renamed/removed symbol: none.
|
||||||
|
- 기존 `BacktestRunSpec` 참조: `packages/contracts/proto/alt/v1/backtest.proto:19`, `packages/contracts/proto/alt/v1/paper_trading.proto:18`, `packages/domain/backtest/types.go:25`, `services/worker/internal/socket/backtest_mapping.go:20`, `services/worker/internal/socket/backtest_mapping.go:144`, `services/worker/internal/jobs/backtest_starter.go:66`, `services/worker/internal/jobs/backtest_jobs.go:99`, `apps/cli/internal/operator/runner.go:248`, `services/api/internal/workerclient/client.go:168`.
|
||||||
|
|
||||||
|
### 분할 판단
|
||||||
|
- split decision policy를 평가했고 multi-plan을 선택했다.
|
||||||
|
- shared task group: `agent-task/m-backtest-data-collection-infrastructure/`
|
||||||
|
- 이 subtask: `01_input_selector_contract`, predecessor 없음.
|
||||||
|
- sibling: `02+01_storage_bar_selector`, predecessor `01_input_selector_contract` complete.log 필요, 현재 missing.
|
||||||
|
- sibling: `03+01,02_backtest_selector_flow`, predecessor `01_input_selector_contract`, `02+01_storage_bar_selector` complete.log 필요, 현재 missing.
|
||||||
|
|
||||||
|
### 범위 결정 근거
|
||||||
|
- 실제 bar filtering, storage source selector resolution, engine execution filtering은 제외한다. 계약 shape가 먼저 안정되어야 한다.
|
||||||
|
- CLI scenario `start_backtest`에서 universe를 selector로 전환하는 작업은 제외한다. operator flow는 selector가 실행까지 연결된 뒤 검증해야 한다.
|
||||||
|
- postgres schema 변경은 하지 않는다. run spec의 기존 DB columns는 strategy/market/timeframe/from/to만 저장하고, selector persistence 여부는 후속 작업에서 필요한 경우 별도 판단한다.
|
||||||
|
|
||||||
|
### 빌드 등급
|
||||||
|
- build lane: `cloud-G06`. public proto, generated code, worker/API/CLI test surface를 건드리는 cross-boundary 변경이므로 G06로 잡는다.
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
- [ ] `BacktestRunSpec`에 additive selector message/field를 추가하고 generated Go를 갱신한다.
|
||||||
|
- [ ] domain `RunSpec`과 worker socket mapping이 selector를 정상/경계 케이스로 왕복하도록 구현한다.
|
||||||
|
- [ ] API/workerclient/CLI request 생성 테스트가 selector 필드 보존을 확인하도록 갱신한다.
|
||||||
|
- [ ] `bin/contracts-gen`, `bin/contracts-check`, 지정 `go test -count=1` 검증을 실행한다.
|
||||||
|
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||||
|
|
||||||
|
### [API-1] Proto Selector Shape
|
||||||
|
#### 문제
|
||||||
|
`BacktestRunSpec`이 기간과 frequency만 표현하고 종목군 selector를 담을 필드가 없다.
|
||||||
|
|
||||||
|
```proto
|
||||||
|
// packages/contracts/proto/alt/v1/backtest.proto:19
|
||||||
|
message BacktestRunSpec {
|
||||||
|
string strategy_id = 1;
|
||||||
|
Market market = 2;
|
||||||
|
Timeframe timeframe = 3;
|
||||||
|
int64 from_unix_ms = 4;
|
||||||
|
int64 to_unix_ms = 5;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 해결 방법
|
||||||
|
기존 field number를 유지하고 additive field만 추가한다. selector가 비어 있으면 기존 full-market 동작을 보존한다.
|
||||||
|
|
||||||
|
```proto
|
||||||
|
message BacktestInputSelector {
|
||||||
|
repeated string instrument_ids = 1;
|
||||||
|
repeated string symbols = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BacktestRunSpec {
|
||||||
|
string strategy_id = 1;
|
||||||
|
Market market = 2;
|
||||||
|
Timeframe timeframe = 3;
|
||||||
|
int64 from_unix_ms = 4;
|
||||||
|
int64 to_unix_ms = 5;
|
||||||
|
BacktestInputSelector selector = 6;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 수정 파일 및 체크리스트
|
||||||
|
- [ ] `packages/contracts/proto/alt/v1/backtest.proto`에 `BacktestInputSelector`와 `BacktestRunSpec.selector = 6`을 추가한다.
|
||||||
|
- [ ] `bin/contracts-gen`으로 `packages/contracts/gen/go/alt/v1/backtest.pb.go`를 갱신한다.
|
||||||
|
- [ ] parser map에는 새 message type을 직접 주고받지 않으므로 등록을 추가하지 않는다.
|
||||||
|
|
||||||
|
#### 테스트 작성
|
||||||
|
- generated contract shape는 `bin/contracts-check`로 확인한다.
|
||||||
|
- 별도 proto-only unit test는 작성하지 않는다. field behavior는 mapping/round-trip 테스트에서 검증한다.
|
||||||
|
|
||||||
|
#### 중간 검증
|
||||||
|
```bash
|
||||||
|
bin/contracts-gen
|
||||||
|
bin/contracts-check
|
||||||
|
```
|
||||||
|
예상 결과: 두 명령 모두 exit 0.
|
||||||
|
|
||||||
|
### [API-2] Domain And Worker Mapping
|
||||||
|
#### 문제
|
||||||
|
worker socket mapping은 proto spec을 domain `RunSpec`으로 변환하지만 selector를 담을 domain field가 없다.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// packages/domain/backtest/types.go:25
|
||||||
|
type RunSpec struct {
|
||||||
|
StrategyID StrategyID
|
||||||
|
Market market.Market
|
||||||
|
Timeframe market.Timeframe
|
||||||
|
From time.Time
|
||||||
|
To time.Time
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
// services/worker/internal/socket/backtest_mapping.go:51
|
||||||
|
return backtest.RunSpec{
|
||||||
|
StrategyID: backtest.StrategyID(spec.GetStrategyId()),
|
||||||
|
Market: mkt,
|
||||||
|
Timeframe: timeframe,
|
||||||
|
From: from,
|
||||||
|
To: to,
|
||||||
|
}, nil
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 해결 방법
|
||||||
|
domain에 `InputSelector`를 추가하고 mapping에서 trim/blank 검증 후 복사한다. `runSpecToProto`도 selector를 되돌려 run 조회 응답에서 손실이 없게 한다.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type InputSelector struct {
|
||||||
|
InstrumentIDs []market.InstrumentID
|
||||||
|
Symbols []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RunSpec struct {
|
||||||
|
StrategyID StrategyID
|
||||||
|
Market market.Market
|
||||||
|
Timeframe market.Timeframe
|
||||||
|
From time.Time
|
||||||
|
To time.Time
|
||||||
|
Selector InputSelector
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 수정 파일 및 체크리스트
|
||||||
|
- [ ] `packages/domain/backtest/types.go`에 `InputSelector`와 `RunSpec.Selector`를 추가한다.
|
||||||
|
- [ ] `services/worker/internal/socket/backtest_mapping.go`에 selector from/to proto helper를 추가한다.
|
||||||
|
- [ ] `services/worker/internal/socket/backtest_mapping_test.go`에 정상 selector round-trip과 blank `instrument_ids`/`symbols` reject 케이스를 추가한다.
|
||||||
|
- [ ] `packages/domain/backtest/types_test.go`는 domain helper를 추가한 경우에만 갱신한다.
|
||||||
|
|
||||||
|
#### 테스트 작성
|
||||||
|
- `TestRunSpecFromProtoValid`에 `Selector` assertion을 추가한다.
|
||||||
|
- `TestRunSpecFromProtoRejectsInvalid`에 blank selector entries를 추가한다.
|
||||||
|
- `TestRunToProto`에 selector가 proto로 보존되는 assertion을 추가한다.
|
||||||
|
|
||||||
|
#### 중간 검증
|
||||||
|
```bash
|
||||||
|
go test -count=1 ./packages/domain/... ./services/worker/internal/socket
|
||||||
|
```
|
||||||
|
예상 결과: selector mapping 테스트 포함 exit 0.
|
||||||
|
|
||||||
|
### [API-3] Forwarding And Client Request Preservation
|
||||||
|
#### 문제
|
||||||
|
API는 start request를 worker로 그대로 전달하고 CLI runner는 `BacktestRunSpec`을 직접 구성한다. selector field가 추가되면 request 생성과 forwarding이 손실 없이 유지되는지 확인해야 한다.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// apps/cli/internal/operator/runner.go:248
|
||||||
|
Spec: &altv1.BacktestRunSpec{
|
||||||
|
StrategyId: step.Request.StrategyID,
|
||||||
|
Market: marketByName[step.Request.Market],
|
||||||
|
Timeframe: timeframeByName[step.Request.Timeframe],
|
||||||
|
FromUnixMs: step.Request.FromUnixMs,
|
||||||
|
ToUnixMs: step.Request.ToUnixMs,
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 해결 방법
|
||||||
|
이 subtask에서는 CLI schema를 확장하지 않는다. 대신 API/workerclient tests가 generated selector를 보존하는지 확인하고, CLI request 생성부는 후속 plan에서 selector를 채울 수 있도록 기존 구조를 유지한다.
|
||||||
|
|
||||||
|
#### 수정 파일 및 체크리스트
|
||||||
|
- [ ] `services/api/internal/socket/backtest_test.go`의 `validStart` 또는 forwarding test에 selector field를 포함해 worker fake가 받은 request를 assertion한다.
|
||||||
|
- [ ] `services/api/internal/workerclient/client_test.go`의 `TestWorkerClient_StartBacktest_Success`에 selector round-trip assertion을 추가한다.
|
||||||
|
- [ ] `apps/cli/internal/operator/client_test.go`의 start backtest request capture가 새 field를 깨뜨리지 않는지 필요한 assertion을 추가한다.
|
||||||
|
|
||||||
|
#### 테스트 작성
|
||||||
|
- public wire field 보존 테스트이므로 정상 round-trip 테스트를 작성한다.
|
||||||
|
- boundary validation은 worker socket mapping test에서 담당하므로 API handler에는 중복 validation을 추가하지 않는다.
|
||||||
|
|
||||||
|
#### 중간 검증
|
||||||
|
```bash
|
||||||
|
go test -count=1 ./services/api/internal/socket ./services/api/internal/workerclient ./apps/cli/internal/operator
|
||||||
|
```
|
||||||
|
예상 결과: selector field가 start request에서 손실되지 않고 exit 0.
|
||||||
|
|
||||||
|
## 수정 파일 요약
|
||||||
|
| 파일 | 항목 |
|
||||||
|
|------|------|
|
||||||
|
| `packages/contracts/proto/alt/v1/backtest.proto` | API-1 |
|
||||||
|
| `packages/contracts/gen/go/alt/v1/backtest.pb.go` | API-1 |
|
||||||
|
| `packages/domain/backtest/types.go` | API-2 |
|
||||||
|
| `packages/domain/backtest/types_test.go` | API-2 |
|
||||||
|
| `services/worker/internal/socket/backtest_mapping.go` | API-2 |
|
||||||
|
| `services/worker/internal/socket/backtest_mapping_test.go` | API-2 |
|
||||||
|
| `services/api/internal/socket/backtest_test.go` | API-3 |
|
||||||
|
| `services/api/internal/workerclient/client_test.go` | API-3 |
|
||||||
|
| `apps/cli/internal/operator/client_test.go` | API-3 |
|
||||||
|
|
||||||
|
## 최종 검증
|
||||||
|
```bash
|
||||||
|
bin/contracts-gen
|
||||||
|
bin/contracts-check
|
||||||
|
go test -count=1 ./packages/domain/... ./services/worker/internal/socket ./services/api/internal/socket ./services/api/internal/workerclient ./apps/cli/internal/operator
|
||||||
|
```
|
||||||
|
예상 결과: 모든 명령 exit 0. Go test cache 출력은 허용하지 않는다.
|
||||||
|
|
||||||
|
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||||
|
|
@ -0,0 +1,142 @@
|
||||||
|
<!-- task=m-backtest-data-collection-infrastructure/02+01_storage_bar_selector 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 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-14
|
||||||
|
task=m-backtest-data-collection-infrastructure/02+01_storage_bar_selector, plan=0, tag=REFACTOR
|
||||||
|
|
||||||
|
## 이 파일을 읽는 리뷰 에이전트에게
|
||||||
|
|
||||||
|
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||||
|
|
||||||
|
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||||
|
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||||
|
|
||||||
|
1. 판정을 append한다.
|
||||||
|
2. `CODE_REVIEW-cloud-G05.md` -> `code_review_cloud_G05_N.log`, `PLAN-cloud-G05.md` -> `plan_cloud_G05_M.log`로 아카이브한다.
|
||||||
|
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-backtest-data-collection-infrastructure/02+01_storage_bar_selector/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다.
|
||||||
|
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
|
||||||
|
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 구현 항목별 완료 여부
|
||||||
|
|
||||||
|
| 항목 | 완료 여부 |
|
||||||
|
|------|---------|
|
||||||
|
| [REFACTOR-1] Backtest Engine RunSpec Bar Boundary | [ ] |
|
||||||
|
| [REFACTOR-2] Storage Selector Resolution | [ ] |
|
||||||
|
| [REFACTOR-3] Worker Command Flow Evidence | [ ] |
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
|
||||||
|
- [ ] backtest engine 전용 RunSpec 기반 bar source 경계를 추가하고 paper trading용 `GetBars` compatibility를 유지한다.
|
||||||
|
- [ ] `StorageBarSource`가 selector instrument id/symbol을 resolve해 선택된 저장 bar만 반환하도록 구현한다.
|
||||||
|
- [ ] worker unit/flow tests가 selector 적용, missing selector error, 기존 full-market fallback을 검증하도록 갱신한다.
|
||||||
|
- [ ] 지정 `go test -count=1` 검증을 실행한다.
|
||||||
|
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||||
|
|
||||||
|
## 코드리뷰 전용 체크리스트
|
||||||
|
|
||||||
|
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||||
|
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||||
|
|
||||||
|
- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||||
|
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||||
|
- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G05_N.log`로 아카이브한다.
|
||||||
|
- [ ] active `PLAN-*-G??.md`를 `plan_cloud_G05_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-data-collection-infrastructure/02+01_storage_bar_selector/`를 `agent-task/archive/YYYY/MM/m-backtest-data-collection-infrastructure/02+01_storage_bar_selector/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||||
|
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||||
|
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-backtest-data-collection-infrastructure/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
|
||||||
|
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G05.md`와 `CODE_REVIEW-cloud-G05.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로 이동한다.
|
||||||
|
|
||||||
|
## 계획 대비 변경 사항
|
||||||
|
|
||||||
|
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||||
|
|
||||||
|
## 주요 설계 결정
|
||||||
|
|
||||||
|
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||||
|
|
||||||
|
## 사용자 리뷰 요청
|
||||||
|
|
||||||
|
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
|
||||||
|
|
||||||
|
- 상태: 없음
|
||||||
|
- 사유 유형: 없음
|
||||||
|
- 결정 필요: 없음
|
||||||
|
- 차단 근거: 없음
|
||||||
|
- 실행한 검증/명령: 없음
|
||||||
|
- 자동 후속 불가 이유: 없음
|
||||||
|
- 재개 조건: 없음
|
||||||
|
|
||||||
|
## 리뷰어를 위한 체크포인트
|
||||||
|
|
||||||
|
- `StorageBarSource.GetBars`가 paper trading compatibility를 위해 남아 있는지 확인한다.
|
||||||
|
- backtest engine이 `RunSpec.Selector`를 source에 전달하고 selector를 무시하지 않는지 확인한다.
|
||||||
|
- missing selector가 조용한 empty run이 아니라 typed worker error로 이어질 수 있는 error를 반환하는지 확인한다.
|
||||||
|
|
||||||
|
## 검증 결과
|
||||||
|
|
||||||
|
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||||
|
|
||||||
|
필수 규칙:
|
||||||
|
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||||
|
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||||
|
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||||
|
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
|
||||||
|
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.
|
||||||
|
|
||||||
|
### REFACTOR-1 중간 검증
|
||||||
|
```bash
|
||||||
|
$ go test -count=1 ./services/worker/internal/backtest
|
||||||
|
(output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### REFACTOR-2 중간 검증
|
||||||
|
```bash
|
||||||
|
$ go test -count=1 ./services/worker/internal/backtest
|
||||||
|
(output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### REFACTOR-3 중간 검증
|
||||||
|
```bash
|
||||||
|
$ go test -count=1 ./services/worker/internal/backtest ./services/worker/internal/jobs
|
||||||
|
(output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 최종 검증
|
||||||
|
```bash
|
||||||
|
$ go test -count=1 ./packages/domain/... ./services/worker/internal/backtest ./services/worker/internal/jobs
|
||||||
|
(output)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ownership
|
||||||
|
|
||||||
|
| 섹션 | 소유자 |
|
||||||
|
|------|--------|
|
||||||
|
| 구현 항목별 완료 여부 | Implementing agent |
|
||||||
|
| 구현 체크리스트 | Implementing agent |
|
||||||
|
| 계획 대비 변경 사항 | Implementing agent |
|
||||||
|
| 주요 설계 결정 | Implementing agent |
|
||||||
|
| 사용자 리뷰 요청 | Implementing agent |
|
||||||
|
| 검증 결과 | Implementing agent |
|
||||||
|
| 코드리뷰 전용 체크리스트 | Review agent |
|
||||||
|
| 코드리뷰 결과 | Review agent |
|
||||||
|
|
@ -0,0 +1,232 @@
|
||||||
|
<!-- task=m-backtest-data-collection-infrastructure/02+01_storage_bar_selector plan=0 tag=REFACTOR -->
|
||||||
|
# PLAN-cloud-G05: Storage Bar Selector
|
||||||
|
|
||||||
|
## 이 파일을 읽는 구현 에이전트에게
|
||||||
|
이 계획은 `01_input_selector_contract`가 PASS되어 `complete.log`를 만든 뒤에만 구현한다. 구현 후 반드시 검증을 실행하고 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션에 실제 변경 내용과 stdout/stderr를 채운 뒤 active 파일을 유지한 채 리뷰 준비를 보고한다. 최종 PASS, log rename, `complete.log`, archive 이동은 code-review skill 전용이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret, 또는 범위 충돌 때문에 멈춰야 하면 사용자에게 직접 묻지 말고 review stub의 `사용자 리뷰 요청`에 증거를 채운 뒤 중단한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 공백은 사용자 리뷰 요청 사유가 아니다.
|
||||||
|
|
||||||
|
## 배경
|
||||||
|
현재 backtest engine은 market 전체 instrument의 bar를 가져온다. `input-selector`를 완성하려면 저장된 정제 데이터에서 선택된 instrument/symbol만 읽어야 한다. 이 subtask는 worker 내부 storage-backed bar source와 engine 경계에 selector 동작을 적용한다.
|
||||||
|
|
||||||
|
## 사용자 리뷰 요청 흐름
|
||||||
|
구현 중 사용자 전용 결정이나 범위 충돌이 생기면 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식 그대로 기록한다. 구현 에이전트는 채팅으로 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. code-review가 사용자 리뷰 요청의 타당성을 검증하고 실제 `USER_REVIEW.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/update-roadmap/SKILL.md`
|
||||||
|
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
|
||||||
|
- `agent-test/local/rules.md`
|
||||||
|
- `agent-test/local/operations-smoke.md`
|
||||||
|
- `agent-test/local/worker-smoke.md`
|
||||||
|
- `agent-test/local/contracts-smoke.md`
|
||||||
|
- `agent-test/local/domain-model-smoke.md`
|
||||||
|
- `agent-test/local/api-smoke.md`
|
||||||
|
- `agent-ops/rules/project/domain/worker/rules.md`
|
||||||
|
- `agent-ops/rules/project/domain/domain-model/rules.md`
|
||||||
|
- `agent-roadmap/current.md`
|
||||||
|
- `agent-roadmap/phase/backtest-loop/PHASE.md`
|
||||||
|
- `agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md`
|
||||||
|
- `packages/domain/backtest/types.go`
|
||||||
|
- `packages/domain/market/types.go`
|
||||||
|
- `services/worker/internal/backtest/bar_source.go`
|
||||||
|
- `services/worker/internal/backtest/bar_source_test.go`
|
||||||
|
- `services/worker/internal/backtest/engine.go`
|
||||||
|
- `services/worker/internal/backtest/engine_test.go`
|
||||||
|
- `services/worker/internal/backtest/flow_test.go`
|
||||||
|
- `services/worker/internal/backtest/fixture_test.go`
|
||||||
|
- `services/worker/internal/jobs/backtest_jobs.go`
|
||||||
|
- `services/worker/internal/jobs/backtest_jobs_test.go`
|
||||||
|
- `services/worker/internal/jobs/backtest_starter.go`
|
||||||
|
- `services/worker/internal/jobs/backtest_starter_test.go`
|
||||||
|
- `services/worker/internal/storage/ports.go`
|
||||||
|
- `services/worker/internal/storage/postgres/mapping.go`
|
||||||
|
- `services/worker/cmd/alt-worker/main.go`
|
||||||
|
|
||||||
|
### 테스트 환경 규칙
|
||||||
|
- `test_env=local`로 판단했다.
|
||||||
|
- `agent-test/local/rules.md`가 존재해 읽었다.
|
||||||
|
- 매칭 profile로 `worker-smoke.md`, `domain-model-smoke.md`, `operations-smoke.md`를 읽었다.
|
||||||
|
- worker/domain 변경이므로 `go test -count=1 ./packages/domain/... ./services/worker/internal/backtest ./services/worker/internal/jobs`를 필수로 사용한다.
|
||||||
|
- `go test -count=1` fresh 실행만 허용하고 test cache 출력은 허용하지 않는다.
|
||||||
|
- 현재 기준으로 더 넓은 묶음 `go test -count=1 ./packages/domain/... ./services/worker/internal/backtest ./services/worker/internal/jobs ./services/worker/internal/socket ./services/api/internal/socket ./services/api/internal/workerclient ./apps/cli/internal/operator ./apps/cli/internal/cli`가 exit 0임을 확인했다.
|
||||||
|
|
||||||
|
### 테스트 커버리지 공백
|
||||||
|
- 기존 `TestStorageBarSourceReadsMarketBars`는 market filter와 정렬만 검증한다.
|
||||||
|
- selector가 instrument id, symbol/provider symbol로 resolve되고 누락 selector를 error로 돌려주는 테스트가 없다.
|
||||||
|
- `Engine.Execute`가 `RunSpec.Selector`를 bar source까지 넘기는 테스트가 없다.
|
||||||
|
- 저장소 기반 command flow가 선택되지 않은 같은 market instrument를 배제하는 테스트가 없다.
|
||||||
|
|
||||||
|
### 심볼 참조
|
||||||
|
- renamed/removed symbol: none.
|
||||||
|
- `StorageBarSource.GetBars` call sites: `services/worker/internal/backtest/bar_source_test.go:82`, `services/worker/internal/backtest/bar_source_test.go:136`, `services/worker/internal/backtest/engine.go:47`, `services/worker/internal/papertrading/engine.go:70`.
|
||||||
|
- `BarSource` interface call sites: `services/worker/internal/backtest/engine.go:15`, `services/worker/internal/backtest/engine_test.go:16`, `services/worker/internal/backtest/fixture_test.go:47`.
|
||||||
|
|
||||||
|
### 분할 판단
|
||||||
|
- split decision policy를 평가했고 multi-plan을 선택했다.
|
||||||
|
- shared task group: `agent-task/m-backtest-data-collection-infrastructure/`
|
||||||
|
- 이 subtask: `02+01_storage_bar_selector`.
|
||||||
|
- predecessor `01_input_selector_contract`: `complete.log` 현재 missing. 구현 시작 전 `agent-task/m-backtest-data-collection-infrastructure/01_input_selector_contract/complete.log` 또는 matching archive complete.log가 있어야 한다.
|
||||||
|
- sibling `03+01,02_backtest_selector_flow`는 이 subtask 완료 후 진행한다.
|
||||||
|
|
||||||
|
### 범위 결정 근거
|
||||||
|
- proto/domain selector shape는 선행 `01_input_selector_contract` 범위이므로 여기서 재설계하지 않는다.
|
||||||
|
- CLI scenario universe handoff는 후속 `03+01,02_backtest_selector_flow` 범위다.
|
||||||
|
- paper trading engine의 public behavior는 변경하지 않는다. `StorageBarSource.GetBars`를 유지해 paper trading compatibility를 보존한다.
|
||||||
|
|
||||||
|
### 빌드 등급
|
||||||
|
- build lane: `cloud-G05`. worker 내부 execution/storage 경계 변경이고 public proto 변경은 선행 plan에 있으므로 G05로 잡는다.
|
||||||
|
|
||||||
|
## 의존 관계 및 구현 순서
|
||||||
|
- `01_input_selector_contract` 완료가 선행이다.
|
||||||
|
- 구현 시작 전에 `agent-task/m-backtest-data-collection-infrastructure/01_input_selector_contract/complete.log`가 없으면 이 plan을 구현하지 말고 선행 subtask를 먼저 완료한다.
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
- [ ] backtest engine 전용 RunSpec 기반 bar source 경계를 추가하고 paper trading용 `GetBars` compatibility를 유지한다.
|
||||||
|
- [ ] `StorageBarSource`가 selector instrument id/symbol을 resolve해 선택된 저장 bar만 반환하도록 구현한다.
|
||||||
|
- [ ] worker unit/flow tests가 selector 적용, missing selector error, 기존 full-market fallback을 검증하도록 갱신한다.
|
||||||
|
- [ ] 지정 `go test -count=1` 검증을 실행한다.
|
||||||
|
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||||
|
|
||||||
|
### [REFACTOR-1] Backtest Engine RunSpec Bar Boundary
|
||||||
|
#### 문제
|
||||||
|
backtest engine은 bar source에 market/timeframe/date만 넘긴다. selector가 domain spec에 추가되어도 engine이 넘기지 않으면 실행에 반영되지 않는다.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// services/worker/internal/backtest/engine.go:14
|
||||||
|
type BarSource interface {
|
||||||
|
GetBars(ctx context.Context, mkt market.Market, timeframe market.Timeframe, from, to time.Time) ([]market.Bar, error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
// services/worker/internal/backtest/engine.go:47
|
||||||
|
bars, err := e.barSource.GetBars(ctx, run.Spec.Market, run.Spec.Timeframe, run.Spec.From, run.Spec.To)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 해결 방법
|
||||||
|
backtest package의 engine-facing interface를 `RunSpec` 기반으로 바꾼다. `StorageBarSource.GetBars`는 paper trading compatibility용으로 유지하고, backtest engine용 method를 추가한다.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type BarSource interface {
|
||||||
|
GetBarsForRun(ctx context.Context, spec backtest.RunSpec) ([]market.Bar, error)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
bars, err := e.barSource.GetBarsForRun(ctx, run.Spec)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 수정 파일 및 체크리스트
|
||||||
|
- [ ] `services/worker/internal/backtest/engine.go`의 `BarSource` interface와 호출부를 `GetBarsForRun(ctx, run.Spec)`로 변경한다.
|
||||||
|
- [ ] `services/worker/internal/backtest/engine_test.go` mock이 받은 `RunSpec.Selector`를 assertion하도록 갱신한다.
|
||||||
|
- [ ] `services/worker/internal/backtest/fixture_test.go`의 test source interface 구현을 새 method에 맞춘다.
|
||||||
|
- [ ] `services/worker/internal/papertrading/engine.go`는 변경하지 않는다.
|
||||||
|
|
||||||
|
#### 테스트 작성
|
||||||
|
- `engine_test.go`에 `TestEnginePassesRunSpecToBarSource` 또는 기존 engine test assertion을 추가한다.
|
||||||
|
- 목적: `RunSpec.Selector`가 engine에서 손실되지 않고 bar source에 전달되는지 검증한다.
|
||||||
|
|
||||||
|
#### 중간 검증
|
||||||
|
```bash
|
||||||
|
go test -count=1 ./services/worker/internal/backtest
|
||||||
|
```
|
||||||
|
예상 결과: backtest engine tests exit 0.
|
||||||
|
|
||||||
|
### [REFACTOR-2] Storage Selector Resolution
|
||||||
|
#### 문제
|
||||||
|
storage-backed source는 market 전체 instrument를 순회한다.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// services/worker/internal/backtest/bar_source.go:29
|
||||||
|
instruments, err := s.instruments.ListInstruments(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("backtest storage bar source: list instruments: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var out []market.Bar
|
||||||
|
for _, inst := range instruments {
|
||||||
|
if inst.Market != mkt {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
bars, err := s.bars.GetBars(ctx, inst.ID, timeframe, from, to)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 해결 방법
|
||||||
|
`GetBarsForRun`에서 `RunSpec.Selector`를 해석한다.
|
||||||
|
- selector가 비어 있으면 기존처럼 market 전체를 사용한다.
|
||||||
|
- `InstrumentIDs`는 exact `market.Instrument.ID`와 비교한다.
|
||||||
|
- `Symbols`는 `Instrument.Symbol`과 `Instrument.ProviderSymbols` value 모두와 비교한다.
|
||||||
|
- selector가 일부라도 resolve되지 않으면 error를 반환해 잘못된 input으로 조용히 실행하지 않는다.
|
||||||
|
- 반환 bar 정렬은 timestamp, instrument id tie-breaker를 유지한다.
|
||||||
|
|
||||||
|
#### 수정 파일 및 체크리스트
|
||||||
|
- [ ] `services/worker/internal/backtest/bar_source.go`에 `GetBarsForRun`과 selector resolution helper를 추가한다.
|
||||||
|
- [ ] 기존 `GetBars(ctx, mkt, timeframe, from, to)`는 유지하고 내부 helper를 공유한다.
|
||||||
|
- [ ] error message는 `backtest storage bar source:` prefix를 유지한다.
|
||||||
|
- [ ] selector 비교 전후로 whitespace-only 값은 선행 mapping에서 reject되지만, 방어적으로 trim해서 missing 처리한다.
|
||||||
|
|
||||||
|
#### 테스트 작성
|
||||||
|
- `services/worker/internal/backtest/bar_source_test.go`
|
||||||
|
- `TestStorageBarSourceSelectsInstrumentIDs`: 같은 market instrument 2개 중 선택 id만 반환.
|
||||||
|
- `TestStorageBarSourceSelectsSymbols`: `Instrument.Symbol` 또는 provider symbol로 선택.
|
||||||
|
- `TestStorageBarSourceRejectsMissingSelector`: 요청 selector가 resolve되지 않으면 error.
|
||||||
|
- 기존 full-market tests는 유지해 empty selector fallback을 검증한다.
|
||||||
|
|
||||||
|
#### 중간 검증
|
||||||
|
```bash
|
||||||
|
go test -count=1 ./services/worker/internal/backtest
|
||||||
|
```
|
||||||
|
예상 결과: selector resolution tests 포함 exit 0.
|
||||||
|
|
||||||
|
### [REFACTOR-3] Worker Command Flow Evidence
|
||||||
|
#### 문제
|
||||||
|
현재 storage-backed command flow는 단일 instrument만 심어 두어서 selector가 실제 실행 입력을 줄였는지 증명하지 못한다.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// services/worker/internal/backtest/flow_test.go:140
|
||||||
|
instID := market.InstrumentID("KRX:005930")
|
||||||
|
if err := store.UpsertInstrument(ctx, market.Instrument{ID: instID, Market: market.MarketKR}); err != nil {
|
||||||
|
t.Fatalf("upsert instrument: %v", err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 해결 방법
|
||||||
|
flow test에 같은 market의 미선택 instrument와 bars를 추가하고 run spec에는 선택 selector만 넣는다. 결과의 trades/equity/trade count가 선택 instrument bars만 처리한 값임을 확인한다.
|
||||||
|
|
||||||
|
#### 수정 파일 및 체크리스트
|
||||||
|
- [ ] `services/worker/internal/backtest/flow_test.go`에 second KR instrument와 bars를 seed한다.
|
||||||
|
- [ ] `spec.Selector`에 선택 instrument id 또는 symbol을 넣는다.
|
||||||
|
- [ ] result assertions가 미선택 instrument bar가 처리되지 않았음을 보여주도록 기대값을 유지하거나 명시 assertion을 추가한다.
|
||||||
|
- [ ] `services/worker/internal/jobs/backtest_jobs_test.go`는 payload decode가 selector를 포함하지 않는 상태라면 `03`에서 다룬다. 이 plan에서는 engine/storage behavior에 집중한다.
|
||||||
|
|
||||||
|
#### 테스트 작성
|
||||||
|
- 기존 `TestBacktestCommandFlowFromImportedBars`를 확장한다.
|
||||||
|
- 새 별도 flow test를 만들 경우 이름은 `TestBacktestCommandFlowUsesInputSelector`로 한다.
|
||||||
|
|
||||||
|
#### 중간 검증
|
||||||
|
```bash
|
||||||
|
go test -count=1 ./services/worker/internal/backtest ./services/worker/internal/jobs
|
||||||
|
```
|
||||||
|
예상 결과: worker backtest/jobs tests exit 0.
|
||||||
|
|
||||||
|
## 수정 파일 요약
|
||||||
|
| 파일 | 항목 |
|
||||||
|
|------|------|
|
||||||
|
| `services/worker/internal/backtest/engine.go` | REFACTOR-1 |
|
||||||
|
| `services/worker/internal/backtest/engine_test.go` | REFACTOR-1 |
|
||||||
|
| `services/worker/internal/backtest/fixture_test.go` | REFACTOR-1 |
|
||||||
|
| `services/worker/internal/backtest/bar_source.go` | REFACTOR-2 |
|
||||||
|
| `services/worker/internal/backtest/bar_source_test.go` | REFACTOR-2 |
|
||||||
|
| `services/worker/internal/backtest/flow_test.go` | REFACTOR-3 |
|
||||||
|
|
||||||
|
## 최종 검증
|
||||||
|
```bash
|
||||||
|
go test -count=1 ./packages/domain/... ./services/worker/internal/backtest ./services/worker/internal/jobs
|
||||||
|
```
|
||||||
|
예상 결과: 모든 package exit 0. Go test cache 출력은 허용하지 않는다.
|
||||||
|
|
||||||
|
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
<!-- task=m-backtest-data-collection-infrastructure/03+01,02_backtest_selector_flow 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`. 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-14
|
||||||
|
task=m-backtest-data-collection-infrastructure/03+01,02_backtest_selector_flow, plan=0, tag=API
|
||||||
|
|
||||||
|
## Roadmap Targets
|
||||||
|
|
||||||
|
- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md`
|
||||||
|
- Task ids:
|
||||||
|
- `input-selector`: 저장된 정제 데이터에서 종목군, 기간, frequency를 선택해 backtest run input으로 넘기는 경계를 만든다.
|
||||||
|
- Completion mode: check-on-pass
|
||||||
|
|
||||||
|
## 이 파일을 읽는 리뷰 에이전트에게
|
||||||
|
|
||||||
|
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||||
|
|
||||||
|
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||||
|
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||||
|
|
||||||
|
1. 판정을 append한다.
|
||||||
|
2. `CODE_REVIEW-cloud-G06.md` -> `code_review_cloud_G06_N.log`, `PLAN-cloud-G06.md` -> `plan_cloud_G06_M.log`로 아카이브한다.
|
||||||
|
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-backtest-data-collection-infrastructure/03+01,02_backtest_selector_flow/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다.
|
||||||
|
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
|
||||||
|
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 구현 항목별 완료 여부
|
||||||
|
|
||||||
|
| 항목 | 완료 여부 |
|
||||||
|
|------|---------|
|
||||||
|
| [API-1] Async Run Payload Selector Preservation | [ ] |
|
||||||
|
| [API-2] Operator Start Backtest Selector | [ ] |
|
||||||
|
| [API-3] Headless Fixtures And Handoff Evidence | [ ] |
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
|
||||||
|
- [ ] `RunBacktestPayload`가 selector를 encode/decode하고 handler가 실행 spec에 복원하도록 구현한다.
|
||||||
|
- [ ] operator `start_backtest`가 universe/symbol/instrument_ids selector를 검증하고 `BacktestRunSpec.selector`로 전송하도록 구현한다.
|
||||||
|
- [ ] headless backtest fixtures와 handoff 문서가 import universe -> start selector -> poll/result 흐름을 증명하도록 갱신한다.
|
||||||
|
- [ ] 지정 `go test -count=1` 검증을 실행한다.
|
||||||
|
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||||
|
|
||||||
|
## 코드리뷰 전용 체크리스트
|
||||||
|
|
||||||
|
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||||
|
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
|
||||||
|
|
||||||
|
- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||||
|
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||||
|
- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G06_N.log`로 아카이브한다.
|
||||||
|
- [ ] active `PLAN-*-G??.md`를 `plan_cloud_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-data-collection-infrastructure/03+01,02_backtest_selector_flow/`를 `agent-task/archive/YYYY/MM/m-backtest-data-collection-infrastructure/03+01,02_backtest_selector_flow/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||||
|
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||||
|
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-backtest-data-collection-infrastructure/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
|
||||||
|
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G06.md`와 `CODE_REVIEW-cloud-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가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
|
||||||
|
|
||||||
|
## 계획 대비 변경 사항
|
||||||
|
|
||||||
|
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||||
|
|
||||||
|
## 주요 설계 결정
|
||||||
|
|
||||||
|
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||||
|
|
||||||
|
## 사용자 리뷰 요청
|
||||||
|
|
||||||
|
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
|
||||||
|
|
||||||
|
- 상태: 없음
|
||||||
|
- 사유 유형: 없음
|
||||||
|
- 결정 필요: 없음
|
||||||
|
- 차단 근거: 없음
|
||||||
|
- 실행한 검증/명령: 없음
|
||||||
|
- 자동 후속 불가 이유: 없음
|
||||||
|
- 재개 조건: 없음
|
||||||
|
|
||||||
|
## 리뷰어를 위한 체크포인트
|
||||||
|
|
||||||
|
- selector가 starter payload와 job handler spec 재구성 과정에서 손실되지 않는지 확인한다.
|
||||||
|
- `start_backtest` selector requirement가 paper trading이나 non-backtest actions에 번지지 않았는지 확인한다.
|
||||||
|
- headless fixtures가 import와 start 모두 같은 universe를 참조하고 expected JSONL 변경이 불필요한지 확인한다.
|
||||||
|
- PASS 시 Roadmap Targets의 `input-selector`만 완료 이벤트 대상인지 확인한다.
|
||||||
|
|
||||||
|
## 검증 결과
|
||||||
|
|
||||||
|
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||||
|
|
||||||
|
필수 규칙:
|
||||||
|
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
|
||||||
|
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
|
||||||
|
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
|
||||||
|
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
|
||||||
|
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.
|
||||||
|
|
||||||
|
### API-1 중간 검증
|
||||||
|
```bash
|
||||||
|
$ go test -count=1 ./services/worker/internal/jobs ./services/worker/internal/backtest
|
||||||
|
(output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### API-2 중간 검증
|
||||||
|
```bash
|
||||||
|
$ go test -count=1 ./apps/cli/internal/operator
|
||||||
|
(output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### API-3 중간 검증
|
||||||
|
```bash
|
||||||
|
$ go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli
|
||||||
|
(output)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 최종 검증
|
||||||
|
```bash
|
||||||
|
$ go test -count=1 ./packages/domain/... ./services/worker/internal/backtest ./services/worker/internal/jobs ./services/worker/internal/socket ./services/api/internal/socket ./services/api/internal/workerclient ./apps/cli/internal/operator ./apps/cli/internal/cli
|
||||||
|
(output)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Ownership
|
||||||
|
|
||||||
|
| 섹션 | 소유자 |
|
||||||
|
|------|--------|
|
||||||
|
| 구현 항목별 완료 여부 | Implementing agent |
|
||||||
|
| 구현 체크리스트 | Implementing agent |
|
||||||
|
| 계획 대비 변경 사항 | Implementing agent |
|
||||||
|
| 주요 설계 결정 | Implementing agent |
|
||||||
|
| 사용자 리뷰 요청 | Implementing agent |
|
||||||
|
| 검증 결과 | Implementing agent |
|
||||||
|
| 코드리뷰 전용 체크리스트 | Review agent |
|
||||||
|
| 코드리뷰 결과 | Review agent |
|
||||||
|
|
@ -0,0 +1,274 @@
|
||||||
|
<!-- task=m-backtest-data-collection-infrastructure/03+01,02_backtest_selector_flow plan=0 tag=API -->
|
||||||
|
# PLAN-cloud-G06: Backtest Selector Flow
|
||||||
|
|
||||||
|
## 이 파일을 읽는 구현 에이전트에게
|
||||||
|
이 계획은 `01_input_selector_contract`와 `02+01_storage_bar_selector`가 PASS되어 각각 `complete.log`를 만든 뒤에만 구현한다. 구현 후 반드시 검증을 실행하고 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션에 실제 변경 내용과 stdout/stderr를 채운 뒤 active 파일을 유지한 채 리뷰 준비를 보고한다. 최종 PASS, log rename, `complete.log`, archive 이동은 code-review skill 전용이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret, 또는 범위 충돌 때문에 멈춰야 하면 사용자에게 직접 묻지 말고 review stub의 `사용자 리뷰 요청`에 증거를 채운 뒤 중단한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 공백은 사용자 리뷰 요청 사유가 아니다.
|
||||||
|
|
||||||
|
## 배경
|
||||||
|
선행 subtasks가 selector 계약과 storage filtering을 만들더라도 async job payload와 operator scenario가 selector를 잃으면 실제 backtest run input까지 이어지지 않는다. 이 subtask는 start command, async run job, operator named universe, headless handoff fixture를 연결한다. PASS되면 현재 마일스톤의 `backtest-ready/input-selector` 기능 Task를 완료할 수 있다.
|
||||||
|
|
||||||
|
## 사용자 리뷰 요청 흐름
|
||||||
|
구현 중 사용자 전용 결정이나 범위 충돌이 생기면 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식 그대로 기록한다. 구현 에이전트는 채팅으로 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. code-review가 사용자 리뷰 요청의 타당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.
|
||||||
|
|
||||||
|
## Roadmap Targets
|
||||||
|
|
||||||
|
- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md`
|
||||||
|
- Task ids:
|
||||||
|
- `input-selector`: 저장된 정제 데이터에서 종목군, 기간, frequency를 선택해 backtest run input으로 넘기는 경계를 만든다.
|
||||||
|
- 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/skills/common/router.md`
|
||||||
|
- `agent-ops/skills/common/plan/SKILL.md`
|
||||||
|
- `agent-ops/skills/common/update-roadmap/SKILL.md`
|
||||||
|
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
|
||||||
|
- `agent-test/local/rules.md`
|
||||||
|
- `agent-test/local/operations-smoke.md`
|
||||||
|
- `agent-test/local/worker-smoke.md`
|
||||||
|
- `agent-test/local/contracts-smoke.md`
|
||||||
|
- `agent-test/local/domain-model-smoke.md`
|
||||||
|
- `agent-test/local/api-smoke.md`
|
||||||
|
- `agent-ops/rules/project/domain/operations/rules.md`
|
||||||
|
- `agent-ops/rules/project/domain/worker/rules.md`
|
||||||
|
- `agent-ops/rules/project/domain/contracts/rules.md`
|
||||||
|
- `agent-ops/rules/project/domain/domain-model/rules.md`
|
||||||
|
- `agent-ops/rules/project/domain/api/rules.md`
|
||||||
|
- `agent-roadmap/current.md`
|
||||||
|
- `agent-roadmap/phase/backtest-loop/PHASE.md`
|
||||||
|
- `agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md`
|
||||||
|
- `packages/domain/backtest/types.go`
|
||||||
|
- `services/worker/internal/jobs/backtest_starter.go`
|
||||||
|
- `services/worker/internal/jobs/backtest_starter_test.go`
|
||||||
|
- `services/worker/internal/jobs/backtest_jobs.go`
|
||||||
|
- `services/worker/internal/jobs/backtest_jobs_test.go`
|
||||||
|
- `services/worker/internal/backtest/flow_test.go`
|
||||||
|
- `apps/cli/internal/operator/scenario.go`
|
||||||
|
- `apps/cli/internal/operator/scenario_test.go`
|
||||||
|
- `apps/cli/internal/operator/runner.go`
|
||||||
|
- `apps/cli/internal/operator/runner_backtest_test.go`
|
||||||
|
- `apps/cli/internal/operator/client.go`
|
||||||
|
- `apps/cli/internal/operator/client_test.go`
|
||||||
|
- `apps/cli/internal/operator/handoff_test.go`
|
||||||
|
- `apps/cli/testdata/operator/backtest_run_request.yaml`
|
||||||
|
- `apps/cli/testdata/operator/backtest_result_summary.yaml`
|
||||||
|
- `apps/cli/testdata/operator/backtest_run_polling.yaml`
|
||||||
|
- `apps/cli/testdata/operator/invalid_request_matrix.yaml`
|
||||||
|
- `apps/cli/testdata/operator/headless_validation.md`
|
||||||
|
|
||||||
|
### 테스트 환경 규칙
|
||||||
|
- `test_env=local`로 판단했다.
|
||||||
|
- `agent-test/local/rules.md`가 존재해 읽었다.
|
||||||
|
- 매칭 profile로 `operations-smoke.md`, `worker-smoke.md`, `api-smoke.md`, `contracts-smoke.md`, `domain-model-smoke.md`를 읽었다.
|
||||||
|
- operations/headless 변경이므로 CLI operator tests와 fixture handoff tests를 포함한다.
|
||||||
|
- worker async job payload 변경이므로 worker jobs/backtest tests를 포함한다.
|
||||||
|
- `go test -count=1` fresh 실행만 허용하고 test cache 출력은 허용하지 않는다.
|
||||||
|
- 현재 기준으로 최종 검증 후보 `go test -count=1 ./packages/domain/... ./services/worker/internal/backtest ./services/worker/internal/jobs ./services/worker/internal/socket ./services/api/internal/socket ./services/api/internal/workerclient ./apps/cli/internal/operator ./apps/cli/internal/cli`가 exit 0임을 확인했다.
|
||||||
|
|
||||||
|
### 테스트 커버리지 공백
|
||||||
|
- `RunBacktestPayload`는 strategy/market/timeframe/date만 담고 selector를 보존하지 않는다.
|
||||||
|
- `RegisterRunBacktestHandler`는 payload에서 spec을 재구성하며 기존 run spec을 덮어쓰므로 selector가 사라질 수 있다.
|
||||||
|
- operator `start_backtest`는 named universe를 지원하지 않고 selector를 `BacktestRunSpec`에 넣지 않는다.
|
||||||
|
- headless fixtures는 import 후 start가 같은 universe를 backtest input selector로 넘기는지 문서화하지 않는다.
|
||||||
|
|
||||||
|
### 심볼 참조
|
||||||
|
- renamed/removed symbol: none.
|
||||||
|
- `RunBacktestPayload` references: `services/worker/internal/jobs/backtest_starter.go:80`, `services/worker/internal/jobs/backtest_jobs.go:20`, `services/worker/internal/jobs/backtest_jobs.go:41`, `services/worker/internal/jobs/backtest_jobs.go:90`, `services/worker/internal/jobs/backtest_starter_test.go:66`, `services/worker/internal/jobs/backtest_jobs_test.go:350`.
|
||||||
|
- `ActionStartBacktest` references: `apps/cli/internal/operator/scenario.go:35`, `apps/cli/internal/operator/scenario.go:638`, `apps/cli/internal/operator/runner.go:276`, `apps/cli/internal/operator/runner_backtest_test.go:27`, `apps/cli/internal/operator/runner_backtest_test.go:92`.
|
||||||
|
|
||||||
|
### 분할 판단
|
||||||
|
- split decision policy를 평가했고 multi-plan을 선택했다.
|
||||||
|
- shared task group: `agent-task/m-backtest-data-collection-infrastructure/`
|
||||||
|
- 이 subtask: `03+01,02_backtest_selector_flow`.
|
||||||
|
- predecessor `01_input_selector_contract`: `complete.log` 현재 missing. 구현 시작 전 active 또는 archived complete.log가 필요하다.
|
||||||
|
- predecessor `02+01_storage_bar_selector`: `complete.log` 현재 missing. 구현 시작 전 active 또는 archived complete.log가 필요하다.
|
||||||
|
|
||||||
|
### 범위 결정 근거
|
||||||
|
- 새로운 universe persistence나 DB schema는 만들지 않는다. CLI scenario root `universes`를 start request selector로 materialize하는 범위까지만 다룬다.
|
||||||
|
- paper trading start flow에는 selector requirement를 강제하지 않는다. `BacktestRunSpec`은 paper trading도 재사용하므로 이 subtask의 CLI validation은 `ActionStartBacktest`에 한정한다.
|
||||||
|
- 실제 provider import 동작은 기존 `import_daily_bars`를 사용하고, provider client나 KIS adapter는 변경하지 않는다.
|
||||||
|
|
||||||
|
### 빌드 등급
|
||||||
|
- build lane: `cloud-G06`. async job payload, worker execution handoff, CLI scenario schema, headless fixtures를 함께 건드리는 cross-surface 변경이므로 G06로 잡는다.
|
||||||
|
|
||||||
|
## 의존 관계 및 구현 순서
|
||||||
|
- `01_input_selector_contract`와 `02+01_storage_bar_selector` 완료가 선행이다.
|
||||||
|
- 구현 시작 전에 두 경로의 `complete.log`가 없으면 이 plan을 구현하지 말고 선행 subtask를 먼저 완료한다.
|
||||||
|
- 구현 순서: worker job payload 보존 -> CLI scenario/runner selector 생성 -> fixtures/handoff 갱신 -> 전체 검증.
|
||||||
|
|
||||||
|
## 구현 체크리스트
|
||||||
|
- [ ] `RunBacktestPayload`가 selector를 encode/decode하고 handler가 실행 spec에 복원하도록 구현한다.
|
||||||
|
- [ ] operator `start_backtest`가 universe/symbol/instrument_ids selector를 검증하고 `BacktestRunSpec.selector`로 전송하도록 구현한다.
|
||||||
|
- [ ] headless backtest fixtures와 handoff 문서가 import universe -> start selector -> poll/result 흐름을 증명하도록 갱신한다.
|
||||||
|
- [ ] 지정 `go test -count=1` 검증을 실행한다.
|
||||||
|
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||||
|
|
||||||
|
### [API-1] Async Run Payload Selector Preservation
|
||||||
|
#### 문제
|
||||||
|
starter payload와 job handler spec 재구성이 selector를 포함하지 않는다.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// services/worker/internal/jobs/backtest_starter.go:80
|
||||||
|
payload := RunBacktestPayload{
|
||||||
|
RunID: string(run.ID),
|
||||||
|
StrategyID: string(spec.StrategyID),
|
||||||
|
Market: string(spec.Market),
|
||||||
|
Timeframe: string(spec.Timeframe),
|
||||||
|
From: spec.From.UTC().Format(time.RFC3339),
|
||||||
|
To: spec.To.UTC().Format(time.RFC3339),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
// services/worker/internal/jobs/backtest_jobs.go:99
|
||||||
|
spec := backtest.RunSpec{
|
||||||
|
StrategyID: backtest.StrategyID(p.StrategyID),
|
||||||
|
Market: market.Market(p.Market),
|
||||||
|
Timeframe: market.Timeframe(p.Timeframe),
|
||||||
|
From: fromTime,
|
||||||
|
To: toTime,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 해결 방법
|
||||||
|
payload에 selector DTO를 추가하고 blank entry validation을 넣는다. handler가 payload selector를 `backtest.RunSpec.Selector`에 복원한 뒤 executor에 전달한다.
|
||||||
|
|
||||||
|
```go
|
||||||
|
type RunBacktestSelectorPayload struct {
|
||||||
|
InstrumentIDs []string `json:"instrument_ids,omitempty"`
|
||||||
|
Symbols []string `json:"symbols,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type RunBacktestPayload struct {
|
||||||
|
RunID string `json:"run_id"`
|
||||||
|
StrategyID string `json:"strategy_id"`
|
||||||
|
Market string `json:"market"`
|
||||||
|
Timeframe string `json:"timeframe"`
|
||||||
|
From string `json:"from"`
|
||||||
|
To string `json:"to"`
|
||||||
|
Selector RunBacktestSelectorPayload `json:"selector,omitempty"`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 수정 파일 및 체크리스트
|
||||||
|
- [ ] `services/worker/internal/jobs/backtest_jobs.go`에 selector payload type, decode validation, domain conversion helper를 추가한다.
|
||||||
|
- [ ] `services/worker/internal/jobs/backtest_starter.go`가 `spec.Selector`를 payload에 encode하도록 한다.
|
||||||
|
- [ ] `services/worker/internal/jobs/backtest_starter_test.go`가 decoded payload selector를 assertion한다.
|
||||||
|
- [ ] `services/worker/internal/jobs/backtest_jobs_test.go`가 executor가 받은 run spec selector와 blank selector reject를 assertion한다.
|
||||||
|
|
||||||
|
#### 테스트 작성
|
||||||
|
- `TestBacktestStarterRecordsPendingAndLaunches`에 selector payload assertion 추가.
|
||||||
|
- `TestRegisterRunBacktestHandlerTransitionsSucceeded`에 `executor.run.Spec.Selector` assertion 추가.
|
||||||
|
- `TestDecodeRunBacktestPayloadRejectsMissingFields` 또는 새 test에 blank selector entry reject 추가.
|
||||||
|
|
||||||
|
#### 중간 검증
|
||||||
|
```bash
|
||||||
|
go test -count=1 ./services/worker/internal/jobs ./services/worker/internal/backtest
|
||||||
|
```
|
||||||
|
예상 결과: jobs/backtest tests exit 0.
|
||||||
|
|
||||||
|
### [API-2] Operator Start Backtest Selector
|
||||||
|
#### 문제
|
||||||
|
operator scenario root에는 universe가 있지만 `start_backtest`는 universe를 적용하지 않고 raw market/timeframe/date만 보낸다.
|
||||||
|
|
||||||
|
```go
|
||||||
|
// apps/cli/internal/operator/scenario.go:495
|
||||||
|
func applyUniverseDefaults(step *Step, universeByName map[string]UniverseConfig) error {
|
||||||
|
if step.Action != ActionImportDailyBars && step.Action != ActionCollectionFreshness {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
// apps/cli/internal/operator/runner.go:276
|
||||||
|
case ActionStartBacktest:
|
||||||
|
resp, err := client.StartBacktest(stepCtx, &altv1.StartBacktestRequest{
|
||||||
|
Spec: &altv1.BacktestRunSpec{
|
||||||
|
StrategyId: step.Request.StrategyID,
|
||||||
|
Market: marketByName[step.Request.Market],
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 해결 방법
|
||||||
|
`Request`에 repeated `instrument_ids`를 추가하고 `start_backtest`가 `universe`, `symbols`, 또는 `instrument_ids` 중 하나를 요구하게 한다. `universe`는 scenario root에서 market/symbols를 materialize한다. runner는 `BacktestInputSelector`를 만들어 `BacktestRunSpec.selector`에 넣는다.
|
||||||
|
|
||||||
|
#### 수정 파일 및 체크리스트
|
||||||
|
- [ ] `apps/cli/internal/operator/scenario.go`의 `Request`에 `InstrumentIDs []string yaml:"instrument_ids"`를 추가한다.
|
||||||
|
- [ ] `applyUniverseDefaults`가 `ActionStartBacktest`에도 universe defaults를 적용하도록 확장한다.
|
||||||
|
- [ ] `ActionStartBacktest` validation에 selector requirement와 blank `symbols`/`instrument_ids` reject를 추가한다.
|
||||||
|
- [ ] `apps/cli/internal/operator/runner.go`에 start selector helper를 추가하고 `BacktestRunSpec.selector`를 설정한다.
|
||||||
|
- [ ] `apps/cli/internal/operator/scenario_test.go`에 valid universe start와 missing selector reject를 추가한다.
|
||||||
|
- [ ] `apps/cli/internal/operator/runner_backtest_test.go`가 fake API에 전달된 selector를 assertion한다.
|
||||||
|
|
||||||
|
#### 테스트 작성
|
||||||
|
- `TestValidateStartBacktestUniverseReference`: universe가 market/symbols로 materialize되는지 검증.
|
||||||
|
- `TestValidateStartBacktestRejectsMissingSelector`: selector 없는 start가 dry-run에서 실패하는지 검증.
|
||||||
|
- `TestRunBacktestRequestOutputsRunID` 또는 별도 test: fake API가 `spec.selector.symbols=["005930"]`를 받는지 검증.
|
||||||
|
|
||||||
|
#### 중간 검증
|
||||||
|
```bash
|
||||||
|
go test -count=1 ./apps/cli/internal/operator
|
||||||
|
```
|
||||||
|
예상 결과: operator scenario/runner tests exit 0.
|
||||||
|
|
||||||
|
### [API-3] Headless Fixtures And Handoff Evidence
|
||||||
|
#### 문제
|
||||||
|
현재 fixtures는 import와 start가 같은 종목군을 공유한다는 것을 request로 증명하지 않는다.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# apps/cli/testdata/operator/backtest_run_polling.yaml:19
|
||||||
|
- id: start
|
||||||
|
action: start_backtest
|
||||||
|
request:
|
||||||
|
strategy_id: strategy-v1
|
||||||
|
market: kr
|
||||||
|
timeframe: daily
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 해결 방법
|
||||||
|
backtest 관련 fixtures에 root `universes`를 두고 `import_daily_bars`와 `start_backtest`가 같은 universe를 참조하게 한다. `invalid_request_matrix`는 missing-strategy typed error를 유지해야 하므로 selector를 추가하되 strategy id는 nonexistent로 둔다. `headless_validation.md`의 checked field는 import universe와 start selector handoff를 명시한다.
|
||||||
|
|
||||||
|
#### 수정 파일 및 체크리스트
|
||||||
|
- [ ] `apps/cli/testdata/operator/backtest_run_request.yaml`에 universe를 추가하고 import/start가 이를 참조하게 한다.
|
||||||
|
- [ ] `apps/cli/testdata/operator/backtest_result_summary.yaml`에 universe를 추가하고 import/start가 이를 참조하게 한다.
|
||||||
|
- [ ] `apps/cli/testdata/operator/backtest_run_polling.yaml`의 start request가 `universe: kr-backtest-smoke`를 사용하게 한다.
|
||||||
|
- [ ] `apps/cli/testdata/operator/invalid_request_matrix.yaml`의 `start_backtest_missing_strategy` request에 selector를 추가해 API typed error 경로를 유지한다.
|
||||||
|
- [ ] `apps/cli/testdata/operator/headless_validation.md` checked field에 `StartBacktestRequest.spec.selector` 증거를 추가한다.
|
||||||
|
- [ ] expected JSONL fixture는 output key가 바뀌지 않으면 수정하지 않는다.
|
||||||
|
|
||||||
|
#### 테스트 작성
|
||||||
|
- 기존 handoff tests가 fixture 존재와 promised keys를 검증한다.
|
||||||
|
- selector가 출력되지 않는 내부 request field라 expected JSONL key 추가는 하지 않는다.
|
||||||
|
|
||||||
|
#### 중간 검증
|
||||||
|
```bash
|
||||||
|
go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli
|
||||||
|
```
|
||||||
|
예상 결과: fixture/handoff tests 포함 exit 0.
|
||||||
|
|
||||||
|
## 수정 파일 요약
|
||||||
|
| 파일 | 항목 |
|
||||||
|
|------|------|
|
||||||
|
| `services/worker/internal/jobs/backtest_jobs.go` | API-1 |
|
||||||
|
| `services/worker/internal/jobs/backtest_jobs_test.go` | API-1 |
|
||||||
|
| `services/worker/internal/jobs/backtest_starter.go` | API-1 |
|
||||||
|
| `services/worker/internal/jobs/backtest_starter_test.go` | API-1 |
|
||||||
|
| `apps/cli/internal/operator/scenario.go` | API-2 |
|
||||||
|
| `apps/cli/internal/operator/scenario_test.go` | API-2 |
|
||||||
|
| `apps/cli/internal/operator/runner.go` | API-2 |
|
||||||
|
| `apps/cli/internal/operator/runner_backtest_test.go` | API-2 |
|
||||||
|
| `apps/cli/testdata/operator/backtest_run_request.yaml` | API-3 |
|
||||||
|
| `apps/cli/testdata/operator/backtest_result_summary.yaml` | API-3 |
|
||||||
|
| `apps/cli/testdata/operator/backtest_run_polling.yaml` | API-3 |
|
||||||
|
| `apps/cli/testdata/operator/invalid_request_matrix.yaml` | API-3 |
|
||||||
|
| `apps/cli/testdata/operator/headless_validation.md` | API-3 |
|
||||||
|
|
||||||
|
## 최종 검증
|
||||||
|
```bash
|
||||||
|
go test -count=1 ./packages/domain/... ./services/worker/internal/backtest ./services/worker/internal/jobs ./services/worker/internal/socket ./services/api/internal/socket ./services/api/internal/workerclient ./apps/cli/internal/operator ./apps/cli/internal/cli
|
||||||
|
```
|
||||||
|
예상 결과: 모든 package exit 0. Go test cache 출력은 허용하지 않는다.
|
||||||
|
|
||||||
|
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||||
|
|
@ -715,3 +715,49 @@ func TestAPIClientImportDailyBars(t *testing.T) {
|
||||||
t.Errorf("server received to_yyyymmdd = %q, want 20240528", got.GetToYyyymmdd())
|
t.Errorf("server received to_yyyymmdd = %q, want 20240528", got.GetToYyyymmdd())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAPIClientStartBacktest(t *testing.T) {
|
||||||
|
api := &fakeAPI{startBacktestResp: &altv1.StartBacktestResponse{
|
||||||
|
Run: &altv1.BacktestRun{Id: "run-1"},
|
||||||
|
}}
|
||||||
|
url := startFakeAPIServer(t, api)
|
||||||
|
client := dialTestClient(t, url)
|
||||||
|
|
||||||
|
req := &altv1.StartBacktestRequest{
|
||||||
|
Spec: &altv1.BacktestRunSpec{
|
||||||
|
StrategyId: "strat-abc",
|
||||||
|
Selector: &altv1.BacktestInputSelector{
|
||||||
|
InstrumentIds: []string{"005930"},
|
||||||
|
Symbols: []string{"AAPL"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
resp, err := client.StartBacktest(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("StartBacktest: %v", err)
|
||||||
|
}
|
||||||
|
if resp.GetError() != nil {
|
||||||
|
t.Fatalf("unexpected typed error: %+v", resp.GetError())
|
||||||
|
}
|
||||||
|
if resp.GetRun().GetId() != "run-1" {
|
||||||
|
t.Errorf("resp run id = %q, want run-1", resp.GetRun().GetId())
|
||||||
|
}
|
||||||
|
|
||||||
|
got := api.lastStartBacktestReq()
|
||||||
|
if got == nil {
|
||||||
|
t.Fatal("server did not receive a start backtest request")
|
||||||
|
}
|
||||||
|
if got.GetSpec().GetStrategyId() != "strat-abc" {
|
||||||
|
t.Errorf("server received strategy_id = %q, want strat-abc", got.GetSpec().GetStrategyId())
|
||||||
|
}
|
||||||
|
pSel := got.GetSpec().GetSelector()
|
||||||
|
if pSel == nil {
|
||||||
|
t.Fatal("server received nil selector")
|
||||||
|
}
|
||||||
|
if len(pSel.GetInstrumentIds()) != 1 || pSel.GetInstrumentIds()[0] != "005930" {
|
||||||
|
t.Errorf("server received instrument_ids = %v, want [005930]", pSel.GetInstrumentIds())
|
||||||
|
}
|
||||||
|
if len(pSel.GetSymbols()) != 1 || pSel.GetSymbols()[0] != "AAPL" {
|
||||||
|
t.Errorf("server received symbols = %v, want [AAPL]", pSel.GetSymbols())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -233,7 +233,7 @@ func TestUSStatusScenarioFixtureImportsBeforeReads(t *testing.T) {
|
||||||
var requiredScenarioKeys = map[string][]string{
|
var requiredScenarioKeys = map[string][]string{
|
||||||
"api_connection_smoke": {"scenario", "status", "type", "action"},
|
"api_connection_smoke": {"scenario", "status", "type", "action"},
|
||||||
"backtest_result_summary": {"run_id", "run_status", "starting_cash", "ending_equity", "total_return", "trade_count"},
|
"backtest_result_summary": {"run_id", "run_status", "starting_cash", "ending_equity", "total_return", "trade_count"},
|
||||||
"backtest_run_polling": {"run_id", "run_status", "bar_count", "instrument_count"},
|
"backtest_run_polling": {"run_id", "run_status", "provider", "bar_count", "instrument_count"},
|
||||||
"backtest_run_request": {"run_id", "run_status", "bar_count", "instrument_count", "provider"},
|
"backtest_run_request": {"run_id", "run_status", "bar_count", "instrument_count", "provider"},
|
||||||
"invalid_request_matrix": {"scenario", "status", "type", "action", "error_code", "error_message"},
|
"invalid_request_matrix": {"scenario", "status", "type", "action", "error_code", "error_message"},
|
||||||
"kis_daily_import_smoke": {"provider", "instrument_count", "bar_count"},
|
"kis_daily_import_smoke": {"provider", "instrument_count", "bar_count"},
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
name: backtest_run_polling
|
name: backtest_run_polling
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
|
universes:
|
||||||
|
- name: kr-backtest-smoke
|
||||||
|
provider: kis
|
||||||
|
selector_kind: watchlist
|
||||||
|
market: kr
|
||||||
|
venue: krx
|
||||||
|
symbols: ["005930"]
|
||||||
steps:
|
steps:
|
||||||
- id: import_kr_daily_bars
|
- id: import_kr_daily_bars
|
||||||
action: import_daily_bars
|
action: import_daily_bars
|
||||||
request:
|
request:
|
||||||
provider: kis
|
universe: kr-backtest-smoke
|
||||||
selector_kind: watchlist
|
|
||||||
market: kr
|
|
||||||
venue: krx
|
|
||||||
symbols: ["005930"]
|
|
||||||
from_yyyymmdd: "20240527"
|
from_yyyymmdd: "20240527"
|
||||||
to_yyyymmdd: "20240528"
|
to_yyyymmdd: "20240528"
|
||||||
expect:
|
expect:
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ ALT 운영 UI 게이트(`agent-ops/rules/project/rules.md`의 "운영 UI 구현
|
||||||
| `market_data_status_query` | `alt operator scenario run --file testdata/operator/market_data_status_query.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | `testdata/operator/market_data_status_query.yaml` | `testdata/operator/expected/market_data_status_query.jsonl` | `scenario`, `status`, `type`, `action`, `provider`, `instrument_count`, `bar_count`, `count`, `universe`, `freshness_status`, `missing_count`, `latest_yyyymmdd`, `gap_status`, `gap_count`, `duplicate_count`, `provider_delay_days` | `0` (성공/empty), `1` (mismatch) | yes | market data status card (종목수·바개수·최신도) | `import_daily_bars` → `ImportDailyBarsResponse.instrument_count`/`bar_count`; `ListInstrumentsResponse.instruments`, `ListBarsResponse.bars`; `collection_freshness` → `freshness_status`, `missing_count`, `latest_yyyymmdd`; `ErrorInfo.code` | 차트/바 시각화 레이아웃 미정 |
|
| `market_data_status_query` | `alt operator scenario run --file testdata/operator/market_data_status_query.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | `testdata/operator/market_data_status_query.yaml` | `testdata/operator/expected/market_data_status_query.jsonl` | `scenario`, `status`, `type`, `action`, `provider`, `instrument_count`, `bar_count`, `count`, `universe`, `freshness_status`, `missing_count`, `latest_yyyymmdd`, `gap_status`, `gap_count`, `duplicate_count`, `provider_delay_days` | `0` (성공/empty), `1` (mismatch) | yes | market data status card (종목수·바개수·최신도) | `import_daily_bars` → `ImportDailyBarsResponse.instrument_count`/`bar_count`; `ListInstrumentsResponse.instruments`, `ListBarsResponse.bars`; `collection_freshness` → `freshness_status`, `missing_count`, `latest_yyyymmdd`; `ErrorInfo.code` | 차트/바 시각화 레이아웃 미정 |
|
||||||
| `us_market_data_status_query` | `alt operator scenario run --file testdata/operator/us_market_data_status_query.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | `testdata/operator/us_market_data_status_query.yaml` | `testdata/operator/expected/us_market_data_status_query.jsonl` | `scenario`, `status`, `type`, `action`, `provider`, `instrument_count`, `bar_count`, `count`, `universe`, `freshness_status`, `missing_count`, `latest_yyyymmdd`, `gap_status`, `gap_count`, `duplicate_count`, `provider_delay_days` | `0` (성공/empty), `1` (mismatch) | yes | market data status card (종목수·바개수·최신도) | `import_daily_bars` → `ImportDailyBarsResponse.instrument_count`/`bar_count`; `ListInstrumentsResponse.instruments`, `ListBarsResponse.bars`; `collection_freshness` → `freshness_status`, `missing_count`, `latest_yyyymmdd`; `ErrorInfo.code` | 차트/바 시각화 레이아웃 미정 |
|
| `us_market_data_status_query` | `alt operator scenario run --file testdata/operator/us_market_data_status_query.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | `testdata/operator/us_market_data_status_query.yaml` | `testdata/operator/expected/us_market_data_status_query.jsonl` | `scenario`, `status`, `type`, `action`, `provider`, `instrument_count`, `bar_count`, `count`, `universe`, `freshness_status`, `missing_count`, `latest_yyyymmdd`, `gap_status`, `gap_count`, `duplicate_count`, `provider_delay_days` | `0` (성공/empty), `1` (mismatch) | yes | market data status card (종목수·바개수·최신도) | `import_daily_bars` → `ImportDailyBarsResponse.instrument_count`/`bar_count`; `ListInstrumentsResponse.instruments`, `ListBarsResponse.bars`; `collection_freshness` → `freshness_status`, `missing_count`, `latest_yyyymmdd`; `ErrorInfo.code` | 차트/바 시각화 레이아웃 미정 |
|
||||||
| `backtest_run_request` | `alt operator scenario run --file testdata/operator/backtest_run_request.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | `testdata/operator/backtest_run_request.yaml` | `testdata/operator/expected/backtest_run_request.jsonl` | `scenario`, `status`, `type`, `action`, `run_id`, `run_status` | `0` (성공), `1` (typed error/mismatch) | yes | backtest 생성 form (parameter 입력/검증) | `StartBacktestResponse.run.id`, `BacktestRun.status` | form validation layout 미정 |
|
| `backtest_run_request` | `alt operator scenario run --file testdata/operator/backtest_run_request.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | `testdata/operator/backtest_run_request.yaml` | `testdata/operator/expected/backtest_run_request.jsonl` | `scenario`, `status`, `type`, `action`, `run_id`, `run_status` | `0` (성공), `1` (typed error/mismatch) | yes | backtest 생성 form (parameter 입력/검증) | `StartBacktestResponse.run.id`, `BacktestRun.status` | form validation layout 미정 |
|
||||||
| `backtest_run_polling` | `alt operator scenario run --file testdata/operator/backtest_run_polling.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | `testdata/operator/backtest_run_polling.yaml` | `testdata/operator/expected/backtest_run_polling.jsonl` | `scenario`, `status`, `type`, `action`, `run_id`, `run_status` | `0` (terminal 도달), `3` (polling timeout) | yes | backtest 진행 상태 표시 (상태 칩/진행바) | `GetBacktestRunResponse.run.status` 전이 (pending→running→succeeded/failed/canceled) | 진행바/상태 전이 애니메이션 미정 |
|
| `backtest_run_polling` | `alt operator scenario run --file testdata/operator/backtest_run_polling.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | `testdata/operator/backtest_run_polling.yaml` | `testdata/operator/expected/backtest_run_polling.jsonl` | `scenario`, `status`, `type`, `action`, `provider`, `instrument_count`, `bar_count`, `run_id`, `run_status` | `0` (terminal 도달), `3` (polling timeout) | yes | backtest 진행 상태 표시 (상태 칩/진행바) | named universe `import_daily_bars` → `ImportDailyBarsResponse.provider`/`instrument_count`/`bar_count`; `StartBacktestResponse.run.id`; `GetBacktestRunResponse.run.status` 전이 (pending→running→succeeded/failed/canceled) | 진행바/상태 전이 애니메이션 미정 |
|
||||||
| `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` | `testdata/operator/backtest_result_summary.yaml` | `testdata/operator/expected/backtest_result_summary.jsonl` | `scenario`, `status`, `type`, `action`, `run_id`, `starting_cash`, `ending_equity`, `total_return`, `trade_count` | `0` (성공), `1` (missing run/typed error) | yes | backtest 결과 요약 (metrics chips) | `GetBacktestResultResponse.result.summary` | 차트/메트릭 레이아웃 미정 |
|
| `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` | `testdata/operator/backtest_result_summary.yaml` | `testdata/operator/expected/backtest_result_summary.jsonl` | `scenario`, `status`, `type`, `action`, `run_id`, `starting_cash`, `ending_equity`, `total_return`, `trade_count` | `0` (성공), `1` (missing run/typed error) | yes | backtest 결과 요약 (metrics chips) | `GetBacktestResultResponse.result.summary` | 차트/메트릭 레이아웃 미정 |
|
||||||
| `invalid_request_matrix` | `alt operator scenario run --file testdata/operator/invalid_request_matrix.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | `testdata/operator/invalid_request_matrix.yaml` | `testdata/operator/expected/invalid_request_matrix.jsonl` | `scenario`, `status`, `type`, `action`, `error_code`, `error_message` | `0` (기대한 typed error 도달), `2` (malformed scenario) | no (개발/검증 전용) | error/message 노출 (검증용) | `ErrorInfo.code` (`invalid_request`, `not_found`) | error 메시지 UI 미정 |
|
| `invalid_request_matrix` | `alt operator scenario run --file testdata/operator/invalid_request_matrix.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | `testdata/operator/invalid_request_matrix.yaml` | `testdata/operator/expected/invalid_request_matrix.jsonl` | `scenario`, `status`, `type`, `action`, `error_code`, `error_message` | `0` (기대한 typed error 도달), `2` (malformed scenario) | no (개발/검증 전용) | error/message 노출 (검증용) | `ErrorInfo.code` (`invalid_request`, `not_found`) | error 메시지 UI 미정 |
|
||||||
| `kis_daily_import_smoke` | `alt operator scenario run --file testdata/operator/kis_daily_import_smoke.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | `testdata/operator/kis_daily_import_smoke.yaml` | `testdata/operator/expected/kis_daily_import_smoke.jsonl` | `scenario`, `status`, `type`, `action`, `provider`, `instrument_count`, `bar_count` | `0` (성공), `1` (mismatch/typed error) | yes | KIS import status (진행률/결과) | `ImportDailyBarsResponse.provider`, `instrument_count`, `bar_count` | import 진행 UI 미정 |
|
| `kis_daily_import_smoke` | `alt operator scenario run --file testdata/operator/kis_daily_import_smoke.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl` | `testdata/operator/kis_daily_import_smoke.yaml` | `testdata/operator/expected/kis_daily_import_smoke.jsonl` | `scenario`, `status`, `type`, `action`, `provider`, `instrument_count`, `bar_count` | `0` (성공), `1` (mismatch/typed error) | yes | KIS import status (진행률/결과) | `ImportDailyBarsResponse.provider`, `instrument_count`, `bar_count` | import 진행 UI 미정 |
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,61 @@ export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions;
|
||||||
|
|
||||||
export 'backtest.pbenum.dart';
|
export 'backtest.pbenum.dart';
|
||||||
|
|
||||||
|
class BacktestInputSelector extends $pb.GeneratedMessage {
|
||||||
|
factory BacktestInputSelector({
|
||||||
|
$core.Iterable<$core.String>? instrumentIds,
|
||||||
|
$core.Iterable<$core.String>? symbols,
|
||||||
|
}) {
|
||||||
|
final result = create();
|
||||||
|
if (instrumentIds != null) result.instrumentIds.addAll(instrumentIds);
|
||||||
|
if (symbols != null) result.symbols.addAll(symbols);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
BacktestInputSelector._();
|
||||||
|
|
||||||
|
factory BacktestInputSelector.fromBuffer($core.List<$core.int> data,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromBuffer(data, registry);
|
||||||
|
factory BacktestInputSelector.fromJson($core.String json,
|
||||||
|
[$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) =>
|
||||||
|
create()..mergeFromJson(json, registry);
|
||||||
|
|
||||||
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||||
|
_omitMessageNames ? '' : 'BacktestInputSelector',
|
||||||
|
package: const $pb.PackageName(_omitMessageNames ? '' : 'alt.v1'),
|
||||||
|
createEmptyInstance: create)
|
||||||
|
..pPS(1, _omitFieldNames ? '' : 'instrumentIds')
|
||||||
|
..pPS(2, _omitFieldNames ? '' : 'symbols')
|
||||||
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
BacktestInputSelector clone() => deepCopy();
|
||||||
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
BacktestInputSelector copyWith(
|
||||||
|
void Function(BacktestInputSelector) updates) =>
|
||||||
|
super.copyWith((message) => updates(message as BacktestInputSelector))
|
||||||
|
as BacktestInputSelector;
|
||||||
|
|
||||||
|
@$core.override
|
||||||
|
$pb.BuilderInfo get info_ => _i;
|
||||||
|
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static BacktestInputSelector create() => BacktestInputSelector._();
|
||||||
|
@$core.override
|
||||||
|
BacktestInputSelector createEmptyInstance() => create();
|
||||||
|
@$core.pragma('dart2js:noInline')
|
||||||
|
static BacktestInputSelector getDefault() => _defaultInstance ??=
|
||||||
|
$pb.GeneratedMessage.$_defaultFor<BacktestInputSelector>(create);
|
||||||
|
static BacktestInputSelector? _defaultInstance;
|
||||||
|
|
||||||
|
@$pb.TagNumber(1)
|
||||||
|
$pb.PbList<$core.String> get instrumentIds => $_getList(0);
|
||||||
|
|
||||||
|
@$pb.TagNumber(2)
|
||||||
|
$pb.PbList<$core.String> get symbols => $_getList(1);
|
||||||
|
}
|
||||||
|
|
||||||
class BacktestRunSpec extends $pb.GeneratedMessage {
|
class BacktestRunSpec extends $pb.GeneratedMessage {
|
||||||
factory BacktestRunSpec({
|
factory BacktestRunSpec({
|
||||||
$core.String? strategyId,
|
$core.String? strategyId,
|
||||||
|
|
@ -30,6 +85,7 @@ class BacktestRunSpec extends $pb.GeneratedMessage {
|
||||||
$0.Timeframe? timeframe,
|
$0.Timeframe? timeframe,
|
||||||
$fixnum.Int64? fromUnixMs,
|
$fixnum.Int64? fromUnixMs,
|
||||||
$fixnum.Int64? toUnixMs,
|
$fixnum.Int64? toUnixMs,
|
||||||
|
BacktestInputSelector? selector,
|
||||||
}) {
|
}) {
|
||||||
final result = create();
|
final result = create();
|
||||||
if (strategyId != null) result.strategyId = strategyId;
|
if (strategyId != null) result.strategyId = strategyId;
|
||||||
|
|
@ -37,6 +93,7 @@ class BacktestRunSpec extends $pb.GeneratedMessage {
|
||||||
if (timeframe != null) result.timeframe = timeframe;
|
if (timeframe != null) result.timeframe = timeframe;
|
||||||
if (fromUnixMs != null) result.fromUnixMs = fromUnixMs;
|
if (fromUnixMs != null) result.fromUnixMs = fromUnixMs;
|
||||||
if (toUnixMs != null) result.toUnixMs = toUnixMs;
|
if (toUnixMs != null) result.toUnixMs = toUnixMs;
|
||||||
|
if (selector != null) result.selector = selector;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -60,6 +117,8 @@ class BacktestRunSpec extends $pb.GeneratedMessage {
|
||||||
enumValues: $0.Timeframe.values)
|
enumValues: $0.Timeframe.values)
|
||||||
..aInt64(4, _omitFieldNames ? '' : 'fromUnixMs')
|
..aInt64(4, _omitFieldNames ? '' : 'fromUnixMs')
|
||||||
..aInt64(5, _omitFieldNames ? '' : 'toUnixMs')
|
..aInt64(5, _omitFieldNames ? '' : 'toUnixMs')
|
||||||
|
..aOM<BacktestInputSelector>(6, _omitFieldNames ? '' : 'selector',
|
||||||
|
subBuilder: BacktestInputSelector.create)
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||||
|
|
@ -125,6 +184,17 @@ class BacktestRunSpec extends $pb.GeneratedMessage {
|
||||||
$core.bool hasToUnixMs() => $_has(4);
|
$core.bool hasToUnixMs() => $_has(4);
|
||||||
@$pb.TagNumber(5)
|
@$pb.TagNumber(5)
|
||||||
void clearToUnixMs() => $_clearField(5);
|
void clearToUnixMs() => $_clearField(5);
|
||||||
|
|
||||||
|
@$pb.TagNumber(6)
|
||||||
|
BacktestInputSelector get selector => $_getN(5);
|
||||||
|
@$pb.TagNumber(6)
|
||||||
|
set selector(BacktestInputSelector value) => $_setField(6, value);
|
||||||
|
@$pb.TagNumber(6)
|
||||||
|
$core.bool hasSelector() => $_has(5);
|
||||||
|
@$pb.TagNumber(6)
|
||||||
|
void clearSelector() => $_clearField(6);
|
||||||
|
@$pb.TagNumber(6)
|
||||||
|
BacktestInputSelector ensureSelector() => $_ensure(5);
|
||||||
}
|
}
|
||||||
|
|
||||||
class BacktestRun extends $pb.GeneratedMessage {
|
class BacktestRun extends $pb.GeneratedMessage {
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,20 @@ final $typed_data.Uint8List backtestRunStatusDescriptor = $convert.base64Decode(
|
||||||
'VTVF9SVU5fU1RBVFVTX0ZBSUxFRBAEEiAKHEJBQ0tURVNUX1JVTl9TVEFUVVNfQ0FOQ0VMRUQQ'
|
'VTVF9SVU5fU1RBVFVTX0ZBSUxFRBAEEiAKHEJBQ0tURVNUX1JVTl9TVEFUVVNfQ0FOQ0VMRUQQ'
|
||||||
'BQ==');
|
'BQ==');
|
||||||
|
|
||||||
|
@$core.Deprecated('Use backtestInputSelectorDescriptor instead')
|
||||||
|
const BacktestInputSelector$json = {
|
||||||
|
'1': 'BacktestInputSelector',
|
||||||
|
'2': [
|
||||||
|
{'1': 'instrument_ids', '3': 1, '4': 3, '5': 9, '10': 'instrumentIds'},
|
||||||
|
{'1': 'symbols', '3': 2, '4': 3, '5': 9, '10': 'symbols'},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `BacktestInputSelector`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
|
final $typed_data.Uint8List backtestInputSelectorDescriptor = $convert.base64Decode(
|
||||||
|
'ChVCYWNrdGVzdElucHV0U2VsZWN0b3ISJQoOaW5zdHJ1bWVudF9pZHMYASADKAlSDWluc3RydW'
|
||||||
|
'1lbnRJZHMSGAoHc3ltYm9scxgCIAMoCVIHc3ltYm9scw==');
|
||||||
|
|
||||||
@$core.Deprecated('Use backtestRunSpecDescriptor instead')
|
@$core.Deprecated('Use backtestRunSpecDescriptor instead')
|
||||||
const BacktestRunSpec$json = {
|
const BacktestRunSpec$json = {
|
||||||
'1': 'BacktestRunSpec',
|
'1': 'BacktestRunSpec',
|
||||||
|
|
@ -59,6 +73,14 @@ const BacktestRunSpec$json = {
|
||||||
},
|
},
|
||||||
{'1': 'from_unix_ms', '3': 4, '4': 1, '5': 3, '10': 'fromUnixMs'},
|
{'1': 'from_unix_ms', '3': 4, '4': 1, '5': 3, '10': 'fromUnixMs'},
|
||||||
{'1': 'to_unix_ms', '3': 5, '4': 1, '5': 3, '10': 'toUnixMs'},
|
{'1': 'to_unix_ms', '3': 5, '4': 1, '5': 3, '10': 'toUnixMs'},
|
||||||
|
{
|
||||||
|
'1': 'selector',
|
||||||
|
'3': 6,
|
||||||
|
'4': 1,
|
||||||
|
'5': 11,
|
||||||
|
'6': '.alt.v1.BacktestInputSelector',
|
||||||
|
'10': 'selector'
|
||||||
|
},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -67,7 +89,8 @@ final $typed_data.Uint8List backtestRunSpecDescriptor = $convert.base64Decode(
|
||||||
'Cg9CYWNrdGVzdFJ1blNwZWMSHwoLc3RyYXRlZ3lfaWQYASABKAlSCnN0cmF0ZWd5SWQSJgoGbW'
|
'Cg9CYWNrdGVzdFJ1blNwZWMSHwoLc3RyYXRlZ3lfaWQYASABKAlSCnN0cmF0ZWd5SWQSJgoGbW'
|
||||||
'Fya2V0GAIgASgOMg4uYWx0LnYxLk1hcmtldFIGbWFya2V0Ei8KCXRpbWVmcmFtZRgDIAEoDjIR'
|
'Fya2V0GAIgASgOMg4uYWx0LnYxLk1hcmtldFIGbWFya2V0Ei8KCXRpbWVmcmFtZRgDIAEoDjIR'
|
||||||
'LmFsdC52MS5UaW1lZnJhbWVSCXRpbWVmcmFtZRIgCgxmcm9tX3VuaXhfbXMYBCABKANSCmZyb2'
|
'LmFsdC52MS5UaW1lZnJhbWVSCXRpbWVmcmFtZRIgCgxmcm9tX3VuaXhfbXMYBCABKANSCmZyb2'
|
||||||
'1Vbml4TXMSHAoKdG9fdW5peF9tcxgFIAEoA1IIdG9Vbml4TXM=');
|
'1Vbml4TXMSHAoKdG9fdW5peF9tcxgFIAEoA1IIdG9Vbml4TXMSOQoIc2VsZWN0b3IYBiABKAsy'
|
||||||
|
'HS5hbHQudjEuQmFja3Rlc3RJbnB1dFNlbGVjdG9yUghzZWxlY3Rvcg==');
|
||||||
|
|
||||||
@$core.Deprecated('Use backtestRunDescriptor instead')
|
@$core.Deprecated('Use backtestRunDescriptor instead')
|
||||||
const BacktestRun$json = {
|
const BacktestRun$json = {
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,58 @@ func (BacktestRunStatus) EnumDescriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{0}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{0}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type BacktestInputSelector struct {
|
||||||
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
|
InstrumentIds []string `protobuf:"bytes,1,rep,name=instrument_ids,json=instrumentIds,proto3" json:"instrument_ids,omitempty"`
|
||||||
|
Symbols []string `protobuf:"bytes,2,rep,name=symbols,proto3" json:"symbols,omitempty"`
|
||||||
|
unknownFields protoimpl.UnknownFields
|
||||||
|
sizeCache protoimpl.SizeCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *BacktestInputSelector) Reset() {
|
||||||
|
*x = BacktestInputSelector{}
|
||||||
|
mi := &file_alt_v1_backtest_proto_msgTypes[0]
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *BacktestInputSelector) String() string {
|
||||||
|
return protoimpl.X.MessageStringOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*BacktestInputSelector) ProtoMessage() {}
|
||||||
|
|
||||||
|
func (x *BacktestInputSelector) ProtoReflect() protoreflect.Message {
|
||||||
|
mi := &file_alt_v1_backtest_proto_msgTypes[0]
|
||||||
|
if x != nil {
|
||||||
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
ms.StoreMessageInfo(mi)
|
||||||
|
}
|
||||||
|
return ms
|
||||||
|
}
|
||||||
|
return mi.MessageOf(x)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprecated: Use BacktestInputSelector.ProtoReflect.Descriptor instead.
|
||||||
|
func (*BacktestInputSelector) Descriptor() ([]byte, []int) {
|
||||||
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *BacktestInputSelector) GetInstrumentIds() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.InstrumentIds
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *BacktestInputSelector) GetSymbols() []string {
|
||||||
|
if x != nil {
|
||||||
|
return x.Symbols
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type BacktestRunSpec struct {
|
type BacktestRunSpec struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
StrategyId string `protobuf:"bytes,1,opt,name=strategy_id,json=strategyId,proto3" json:"strategy_id,omitempty"`
|
StrategyId string `protobuf:"bytes,1,opt,name=strategy_id,json=strategyId,proto3" json:"strategy_id,omitempty"`
|
||||||
|
|
@ -86,13 +138,14 @@ type BacktestRunSpec struct {
|
||||||
Timeframe Timeframe `protobuf:"varint,3,opt,name=timeframe,proto3,enum=alt.v1.Timeframe" json:"timeframe,omitempty"`
|
Timeframe Timeframe `protobuf:"varint,3,opt,name=timeframe,proto3,enum=alt.v1.Timeframe" json:"timeframe,omitempty"`
|
||||||
FromUnixMs int64 `protobuf:"varint,4,opt,name=from_unix_ms,json=fromUnixMs,proto3" json:"from_unix_ms,omitempty"`
|
FromUnixMs int64 `protobuf:"varint,4,opt,name=from_unix_ms,json=fromUnixMs,proto3" json:"from_unix_ms,omitempty"`
|
||||||
ToUnixMs int64 `protobuf:"varint,5,opt,name=to_unix_ms,json=toUnixMs,proto3" json:"to_unix_ms,omitempty"`
|
ToUnixMs int64 `protobuf:"varint,5,opt,name=to_unix_ms,json=toUnixMs,proto3" json:"to_unix_ms,omitempty"`
|
||||||
|
Selector *BacktestInputSelector `protobuf:"bytes,6,opt,name=selector,proto3" json:"selector,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *BacktestRunSpec) Reset() {
|
func (x *BacktestRunSpec) Reset() {
|
||||||
*x = BacktestRunSpec{}
|
*x = BacktestRunSpec{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[0]
|
mi := &file_alt_v1_backtest_proto_msgTypes[1]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -104,7 +157,7 @@ func (x *BacktestRunSpec) String() string {
|
||||||
func (*BacktestRunSpec) ProtoMessage() {}
|
func (*BacktestRunSpec) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *BacktestRunSpec) ProtoReflect() protoreflect.Message {
|
func (x *BacktestRunSpec) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[0]
|
mi := &file_alt_v1_backtest_proto_msgTypes[1]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -117,7 +170,7 @@ func (x *BacktestRunSpec) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use BacktestRunSpec.ProtoReflect.Descriptor instead.
|
// Deprecated: Use BacktestRunSpec.ProtoReflect.Descriptor instead.
|
||||||
func (*BacktestRunSpec) Descriptor() ([]byte, []int) {
|
func (*BacktestRunSpec) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{0}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{1}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *BacktestRunSpec) GetStrategyId() string {
|
func (x *BacktestRunSpec) GetStrategyId() string {
|
||||||
|
|
@ -155,6 +208,13 @@ func (x *BacktestRunSpec) GetToUnixMs() int64 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *BacktestRunSpec) GetSelector() *BacktestInputSelector {
|
||||||
|
if x != nil {
|
||||||
|
return x.Selector
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type BacktestRun struct {
|
type BacktestRun struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||||
|
|
@ -168,7 +228,7 @@ type BacktestRun struct {
|
||||||
|
|
||||||
func (x *BacktestRun) Reset() {
|
func (x *BacktestRun) Reset() {
|
||||||
*x = BacktestRun{}
|
*x = BacktestRun{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[1]
|
mi := &file_alt_v1_backtest_proto_msgTypes[2]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -180,7 +240,7 @@ func (x *BacktestRun) String() string {
|
||||||
func (*BacktestRun) ProtoMessage() {}
|
func (*BacktestRun) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *BacktestRun) ProtoReflect() protoreflect.Message {
|
func (x *BacktestRun) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[1]
|
mi := &file_alt_v1_backtest_proto_msgTypes[2]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -193,7 +253,7 @@ func (x *BacktestRun) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use BacktestRun.ProtoReflect.Descriptor instead.
|
// Deprecated: Use BacktestRun.ProtoReflect.Descriptor instead.
|
||||||
func (*BacktestRun) Descriptor() ([]byte, []int) {
|
func (*BacktestRun) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{1}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{2}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *BacktestRun) GetId() string {
|
func (x *BacktestRun) GetId() string {
|
||||||
|
|
@ -240,7 +300,7 @@ type StartBacktestRequest struct {
|
||||||
|
|
||||||
func (x *StartBacktestRequest) Reset() {
|
func (x *StartBacktestRequest) Reset() {
|
||||||
*x = StartBacktestRequest{}
|
*x = StartBacktestRequest{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[2]
|
mi := &file_alt_v1_backtest_proto_msgTypes[3]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -252,7 +312,7 @@ func (x *StartBacktestRequest) String() string {
|
||||||
func (*StartBacktestRequest) ProtoMessage() {}
|
func (*StartBacktestRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *StartBacktestRequest) ProtoReflect() protoreflect.Message {
|
func (x *StartBacktestRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[2]
|
mi := &file_alt_v1_backtest_proto_msgTypes[3]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -265,7 +325,7 @@ func (x *StartBacktestRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use StartBacktestRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use StartBacktestRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*StartBacktestRequest) Descriptor() ([]byte, []int) {
|
func (*StartBacktestRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{2}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{3}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *StartBacktestRequest) GetSpec() *BacktestRunSpec {
|
func (x *StartBacktestRequest) GetSpec() *BacktestRunSpec {
|
||||||
|
|
@ -285,7 +345,7 @@ type StartBacktestResponse struct {
|
||||||
|
|
||||||
func (x *StartBacktestResponse) Reset() {
|
func (x *StartBacktestResponse) Reset() {
|
||||||
*x = StartBacktestResponse{}
|
*x = StartBacktestResponse{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[3]
|
mi := &file_alt_v1_backtest_proto_msgTypes[4]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -297,7 +357,7 @@ func (x *StartBacktestResponse) String() string {
|
||||||
func (*StartBacktestResponse) ProtoMessage() {}
|
func (*StartBacktestResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *StartBacktestResponse) ProtoReflect() protoreflect.Message {
|
func (x *StartBacktestResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[3]
|
mi := &file_alt_v1_backtest_proto_msgTypes[4]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -310,7 +370,7 @@ func (x *StartBacktestResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use StartBacktestResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use StartBacktestResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*StartBacktestResponse) Descriptor() ([]byte, []int) {
|
func (*StartBacktestResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{3}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{4}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *StartBacktestResponse) GetRun() *BacktestRun {
|
func (x *StartBacktestResponse) GetRun() *BacktestRun {
|
||||||
|
|
@ -336,7 +396,7 @@ type GetBacktestRunRequest struct {
|
||||||
|
|
||||||
func (x *GetBacktestRunRequest) Reset() {
|
func (x *GetBacktestRunRequest) Reset() {
|
||||||
*x = GetBacktestRunRequest{}
|
*x = GetBacktestRunRequest{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[4]
|
mi := &file_alt_v1_backtest_proto_msgTypes[5]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -348,7 +408,7 @@ func (x *GetBacktestRunRequest) String() string {
|
||||||
func (*GetBacktestRunRequest) ProtoMessage() {}
|
func (*GetBacktestRunRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *GetBacktestRunRequest) ProtoReflect() protoreflect.Message {
|
func (x *GetBacktestRunRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[4]
|
mi := &file_alt_v1_backtest_proto_msgTypes[5]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -361,7 +421,7 @@ func (x *GetBacktestRunRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use GetBacktestRunRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use GetBacktestRunRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*GetBacktestRunRequest) Descriptor() ([]byte, []int) {
|
func (*GetBacktestRunRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{4}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{5}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetBacktestRunRequest) GetRunId() string {
|
func (x *GetBacktestRunRequest) GetRunId() string {
|
||||||
|
|
@ -381,7 +441,7 @@ type GetBacktestRunResponse struct {
|
||||||
|
|
||||||
func (x *GetBacktestRunResponse) Reset() {
|
func (x *GetBacktestRunResponse) Reset() {
|
||||||
*x = GetBacktestRunResponse{}
|
*x = GetBacktestRunResponse{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[5]
|
mi := &file_alt_v1_backtest_proto_msgTypes[6]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -393,7 +453,7 @@ func (x *GetBacktestRunResponse) String() string {
|
||||||
func (*GetBacktestRunResponse) ProtoMessage() {}
|
func (*GetBacktestRunResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *GetBacktestRunResponse) ProtoReflect() protoreflect.Message {
|
func (x *GetBacktestRunResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[5]
|
mi := &file_alt_v1_backtest_proto_msgTypes[6]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -406,7 +466,7 @@ func (x *GetBacktestRunResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use GetBacktestRunResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use GetBacktestRunResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*GetBacktestRunResponse) Descriptor() ([]byte, []int) {
|
func (*GetBacktestRunResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{5}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{6}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetBacktestRunResponse) GetRun() *BacktestRun {
|
func (x *GetBacktestRunResponse) GetRun() *BacktestRun {
|
||||||
|
|
@ -436,7 +496,7 @@ type BacktestTrade struct {
|
||||||
|
|
||||||
func (x *BacktestTrade) Reset() {
|
func (x *BacktestTrade) Reset() {
|
||||||
*x = BacktestTrade{}
|
*x = BacktestTrade{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[6]
|
mi := &file_alt_v1_backtest_proto_msgTypes[7]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -448,7 +508,7 @@ func (x *BacktestTrade) String() string {
|
||||||
func (*BacktestTrade) ProtoMessage() {}
|
func (*BacktestTrade) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *BacktestTrade) ProtoReflect() protoreflect.Message {
|
func (x *BacktestTrade) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[6]
|
mi := &file_alt_v1_backtest_proto_msgTypes[7]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -461,7 +521,7 @@ func (x *BacktestTrade) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use BacktestTrade.ProtoReflect.Descriptor instead.
|
// Deprecated: Use BacktestTrade.ProtoReflect.Descriptor instead.
|
||||||
func (*BacktestTrade) Descriptor() ([]byte, []int) {
|
func (*BacktestTrade) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{6}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{7}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *BacktestTrade) GetInstrumentId() string {
|
func (x *BacktestTrade) GetInstrumentId() string {
|
||||||
|
|
@ -510,7 +570,7 @@ type BacktestPosition struct {
|
||||||
|
|
||||||
func (x *BacktestPosition) Reset() {
|
func (x *BacktestPosition) Reset() {
|
||||||
*x = BacktestPosition{}
|
*x = BacktestPosition{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[7]
|
mi := &file_alt_v1_backtest_proto_msgTypes[8]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -522,7 +582,7 @@ func (x *BacktestPosition) String() string {
|
||||||
func (*BacktestPosition) ProtoMessage() {}
|
func (*BacktestPosition) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *BacktestPosition) ProtoReflect() protoreflect.Message {
|
func (x *BacktestPosition) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[7]
|
mi := &file_alt_v1_backtest_proto_msgTypes[8]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -535,7 +595,7 @@ func (x *BacktestPosition) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use BacktestPosition.ProtoReflect.Descriptor instead.
|
// Deprecated: Use BacktestPosition.ProtoReflect.Descriptor instead.
|
||||||
func (*BacktestPosition) Descriptor() ([]byte, []int) {
|
func (*BacktestPosition) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{7}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{8}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *BacktestPosition) GetInstrumentId() string {
|
func (x *BacktestPosition) GetInstrumentId() string {
|
||||||
|
|
@ -571,7 +631,7 @@ type BacktestSummaryMetrics struct {
|
||||||
|
|
||||||
func (x *BacktestSummaryMetrics) Reset() {
|
func (x *BacktestSummaryMetrics) Reset() {
|
||||||
*x = BacktestSummaryMetrics{}
|
*x = BacktestSummaryMetrics{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[8]
|
mi := &file_alt_v1_backtest_proto_msgTypes[9]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -583,7 +643,7 @@ func (x *BacktestSummaryMetrics) String() string {
|
||||||
func (*BacktestSummaryMetrics) ProtoMessage() {}
|
func (*BacktestSummaryMetrics) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *BacktestSummaryMetrics) ProtoReflect() protoreflect.Message {
|
func (x *BacktestSummaryMetrics) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[8]
|
mi := &file_alt_v1_backtest_proto_msgTypes[9]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -596,7 +656,7 @@ func (x *BacktestSummaryMetrics) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use BacktestSummaryMetrics.ProtoReflect.Descriptor instead.
|
// Deprecated: Use BacktestSummaryMetrics.ProtoReflect.Descriptor instead.
|
||||||
func (*BacktestSummaryMetrics) Descriptor() ([]byte, []int) {
|
func (*BacktestSummaryMetrics) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{8}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{9}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *BacktestSummaryMetrics) GetStartingCash() *Price {
|
func (x *BacktestSummaryMetrics) GetStartingCash() *Price {
|
||||||
|
|
@ -637,7 +697,7 @@ type BacktestEquityPoint struct {
|
||||||
|
|
||||||
func (x *BacktestEquityPoint) Reset() {
|
func (x *BacktestEquityPoint) Reset() {
|
||||||
*x = BacktestEquityPoint{}
|
*x = BacktestEquityPoint{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[9]
|
mi := &file_alt_v1_backtest_proto_msgTypes[10]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -649,7 +709,7 @@ func (x *BacktestEquityPoint) String() string {
|
||||||
func (*BacktestEquityPoint) ProtoMessage() {}
|
func (*BacktestEquityPoint) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *BacktestEquityPoint) ProtoReflect() protoreflect.Message {
|
func (x *BacktestEquityPoint) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[9]
|
mi := &file_alt_v1_backtest_proto_msgTypes[10]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -662,7 +722,7 @@ func (x *BacktestEquityPoint) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use BacktestEquityPoint.ProtoReflect.Descriptor instead.
|
// Deprecated: Use BacktestEquityPoint.ProtoReflect.Descriptor instead.
|
||||||
func (*BacktestEquityPoint) Descriptor() ([]byte, []int) {
|
func (*BacktestEquityPoint) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{9}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{10}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *BacktestEquityPoint) GetTimestampUnixMs() int64 {
|
func (x *BacktestEquityPoint) GetTimestampUnixMs() int64 {
|
||||||
|
|
@ -694,7 +754,7 @@ type BacktestResult struct {
|
||||||
|
|
||||||
func (x *BacktestResult) Reset() {
|
func (x *BacktestResult) Reset() {
|
||||||
*x = BacktestResult{}
|
*x = BacktestResult{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[10]
|
mi := &file_alt_v1_backtest_proto_msgTypes[11]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -706,7 +766,7 @@ func (x *BacktestResult) String() string {
|
||||||
func (*BacktestResult) ProtoMessage() {}
|
func (*BacktestResult) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *BacktestResult) ProtoReflect() protoreflect.Message {
|
func (x *BacktestResult) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[10]
|
mi := &file_alt_v1_backtest_proto_msgTypes[11]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -719,7 +779,7 @@ func (x *BacktestResult) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use BacktestResult.ProtoReflect.Descriptor instead.
|
// Deprecated: Use BacktestResult.ProtoReflect.Descriptor instead.
|
||||||
func (*BacktestResult) Descriptor() ([]byte, []int) {
|
func (*BacktestResult) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{10}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{11}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *BacktestResult) GetRunId() string {
|
func (x *BacktestResult) GetRunId() string {
|
||||||
|
|
@ -780,7 +840,7 @@ type GetBacktestResultRequest struct {
|
||||||
|
|
||||||
func (x *GetBacktestResultRequest) Reset() {
|
func (x *GetBacktestResultRequest) Reset() {
|
||||||
*x = GetBacktestResultRequest{}
|
*x = GetBacktestResultRequest{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[11]
|
mi := &file_alt_v1_backtest_proto_msgTypes[12]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -792,7 +852,7 @@ func (x *GetBacktestResultRequest) String() string {
|
||||||
func (*GetBacktestResultRequest) ProtoMessage() {}
|
func (*GetBacktestResultRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *GetBacktestResultRequest) ProtoReflect() protoreflect.Message {
|
func (x *GetBacktestResultRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[11]
|
mi := &file_alt_v1_backtest_proto_msgTypes[12]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -805,7 +865,7 @@ func (x *GetBacktestResultRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use GetBacktestResultRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use GetBacktestResultRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*GetBacktestResultRequest) Descriptor() ([]byte, []int) {
|
func (*GetBacktestResultRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{11}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{12}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetBacktestResultRequest) GetRunId() string {
|
func (x *GetBacktestResultRequest) GetRunId() string {
|
||||||
|
|
@ -825,7 +885,7 @@ type GetBacktestResultResponse struct {
|
||||||
|
|
||||||
func (x *GetBacktestResultResponse) Reset() {
|
func (x *GetBacktestResultResponse) Reset() {
|
||||||
*x = GetBacktestResultResponse{}
|
*x = GetBacktestResultResponse{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[12]
|
mi := &file_alt_v1_backtest_proto_msgTypes[13]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -837,7 +897,7 @@ func (x *GetBacktestResultResponse) String() string {
|
||||||
func (*GetBacktestResultResponse) ProtoMessage() {}
|
func (*GetBacktestResultResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *GetBacktestResultResponse) ProtoReflect() protoreflect.Message {
|
func (x *GetBacktestResultResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[12]
|
mi := &file_alt_v1_backtest_proto_msgTypes[13]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -850,7 +910,7 @@ func (x *GetBacktestResultResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use GetBacktestResultResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use GetBacktestResultResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*GetBacktestResultResponse) Descriptor() ([]byte, []int) {
|
func (*GetBacktestResultResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{12}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{13}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetBacktestResultResponse) GetResult() *BacktestResult {
|
func (x *GetBacktestResultResponse) GetResult() *BacktestResult {
|
||||||
|
|
@ -876,7 +936,7 @@ type ListBacktestRunsRequest struct {
|
||||||
|
|
||||||
func (x *ListBacktestRunsRequest) Reset() {
|
func (x *ListBacktestRunsRequest) Reset() {
|
||||||
*x = ListBacktestRunsRequest{}
|
*x = ListBacktestRunsRequest{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[13]
|
mi := &file_alt_v1_backtest_proto_msgTypes[14]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -888,7 +948,7 @@ func (x *ListBacktestRunsRequest) String() string {
|
||||||
func (*ListBacktestRunsRequest) ProtoMessage() {}
|
func (*ListBacktestRunsRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ListBacktestRunsRequest) ProtoReflect() protoreflect.Message {
|
func (x *ListBacktestRunsRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[13]
|
mi := &file_alt_v1_backtest_proto_msgTypes[14]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -901,7 +961,7 @@ func (x *ListBacktestRunsRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use ListBacktestRunsRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ListBacktestRunsRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*ListBacktestRunsRequest) Descriptor() ([]byte, []int) {
|
func (*ListBacktestRunsRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{13}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{14}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListBacktestRunsRequest) GetStatus() BacktestRunStatus {
|
func (x *ListBacktestRunsRequest) GetStatus() BacktestRunStatus {
|
||||||
|
|
@ -921,7 +981,7 @@ type ListBacktestRunsResponse struct {
|
||||||
|
|
||||||
func (x *ListBacktestRunsResponse) Reset() {
|
func (x *ListBacktestRunsResponse) Reset() {
|
||||||
*x = ListBacktestRunsResponse{}
|
*x = ListBacktestRunsResponse{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[14]
|
mi := &file_alt_v1_backtest_proto_msgTypes[15]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -933,7 +993,7 @@ func (x *ListBacktestRunsResponse) String() string {
|
||||||
func (*ListBacktestRunsResponse) ProtoMessage() {}
|
func (*ListBacktestRunsResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *ListBacktestRunsResponse) ProtoReflect() protoreflect.Message {
|
func (x *ListBacktestRunsResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[14]
|
mi := &file_alt_v1_backtest_proto_msgTypes[15]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -946,7 +1006,7 @@ func (x *ListBacktestRunsResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use ListBacktestRunsResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use ListBacktestRunsResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*ListBacktestRunsResponse) Descriptor() ([]byte, []int) {
|
func (*ListBacktestRunsResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{14}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{15}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *ListBacktestRunsResponse) GetRuns() []*BacktestRun {
|
func (x *ListBacktestRunsResponse) GetRuns() []*BacktestRun {
|
||||||
|
|
@ -972,7 +1032,7 @@ type GetBacktestRunDetailRequest struct {
|
||||||
|
|
||||||
func (x *GetBacktestRunDetailRequest) Reset() {
|
func (x *GetBacktestRunDetailRequest) Reset() {
|
||||||
*x = GetBacktestRunDetailRequest{}
|
*x = GetBacktestRunDetailRequest{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[15]
|
mi := &file_alt_v1_backtest_proto_msgTypes[16]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -984,7 +1044,7 @@ func (x *GetBacktestRunDetailRequest) String() string {
|
||||||
func (*GetBacktestRunDetailRequest) ProtoMessage() {}
|
func (*GetBacktestRunDetailRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *GetBacktestRunDetailRequest) ProtoReflect() protoreflect.Message {
|
func (x *GetBacktestRunDetailRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[15]
|
mi := &file_alt_v1_backtest_proto_msgTypes[16]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -997,7 +1057,7 @@ func (x *GetBacktestRunDetailRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use GetBacktestRunDetailRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use GetBacktestRunDetailRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*GetBacktestRunDetailRequest) Descriptor() ([]byte, []int) {
|
func (*GetBacktestRunDetailRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{15}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{16}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetBacktestRunDetailRequest) GetRunId() string {
|
func (x *GetBacktestRunDetailRequest) GetRunId() string {
|
||||||
|
|
@ -1018,7 +1078,7 @@ type GetBacktestRunDetailResponse struct {
|
||||||
|
|
||||||
func (x *GetBacktestRunDetailResponse) Reset() {
|
func (x *GetBacktestRunDetailResponse) Reset() {
|
||||||
*x = GetBacktestRunDetailResponse{}
|
*x = GetBacktestRunDetailResponse{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[16]
|
mi := &file_alt_v1_backtest_proto_msgTypes[17]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -1030,7 +1090,7 @@ func (x *GetBacktestRunDetailResponse) String() string {
|
||||||
func (*GetBacktestRunDetailResponse) ProtoMessage() {}
|
func (*GetBacktestRunDetailResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *GetBacktestRunDetailResponse) ProtoReflect() protoreflect.Message {
|
func (x *GetBacktestRunDetailResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[16]
|
mi := &file_alt_v1_backtest_proto_msgTypes[17]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -1043,7 +1103,7 @@ func (x *GetBacktestRunDetailResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use GetBacktestRunDetailResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use GetBacktestRunDetailResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*GetBacktestRunDetailResponse) Descriptor() ([]byte, []int) {
|
func (*GetBacktestRunDetailResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{16}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{17}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *GetBacktestRunDetailResponse) GetRun() *BacktestRun {
|
func (x *GetBacktestRunDetailResponse) GetRun() *BacktestRun {
|
||||||
|
|
@ -1076,7 +1136,7 @@ type CompareBacktestRunsRequest struct {
|
||||||
|
|
||||||
func (x *CompareBacktestRunsRequest) Reset() {
|
func (x *CompareBacktestRunsRequest) Reset() {
|
||||||
*x = CompareBacktestRunsRequest{}
|
*x = CompareBacktestRunsRequest{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[17]
|
mi := &file_alt_v1_backtest_proto_msgTypes[18]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -1088,7 +1148,7 @@ func (x *CompareBacktestRunsRequest) String() string {
|
||||||
func (*CompareBacktestRunsRequest) ProtoMessage() {}
|
func (*CompareBacktestRunsRequest) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *CompareBacktestRunsRequest) ProtoReflect() protoreflect.Message {
|
func (x *CompareBacktestRunsRequest) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[17]
|
mi := &file_alt_v1_backtest_proto_msgTypes[18]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -1101,7 +1161,7 @@ func (x *CompareBacktestRunsRequest) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use CompareBacktestRunsRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use CompareBacktestRunsRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*CompareBacktestRunsRequest) Descriptor() ([]byte, []int) {
|
func (*CompareBacktestRunsRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{17}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{18}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CompareBacktestRunsRequest) GetRunIds() []string {
|
func (x *CompareBacktestRunsRequest) GetRunIds() []string {
|
||||||
|
|
@ -1121,7 +1181,7 @@ type CompareBacktestRunsResponse struct {
|
||||||
|
|
||||||
func (x *CompareBacktestRunsResponse) Reset() {
|
func (x *CompareBacktestRunsResponse) Reset() {
|
||||||
*x = CompareBacktestRunsResponse{}
|
*x = CompareBacktestRunsResponse{}
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[18]
|
mi := &file_alt_v1_backtest_proto_msgTypes[19]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
ms.StoreMessageInfo(mi)
|
ms.StoreMessageInfo(mi)
|
||||||
}
|
}
|
||||||
|
|
@ -1133,7 +1193,7 @@ func (x *CompareBacktestRunsResponse) String() string {
|
||||||
func (*CompareBacktestRunsResponse) ProtoMessage() {}
|
func (*CompareBacktestRunsResponse) ProtoMessage() {}
|
||||||
|
|
||||||
func (x *CompareBacktestRunsResponse) ProtoReflect() protoreflect.Message {
|
func (x *CompareBacktestRunsResponse) ProtoReflect() protoreflect.Message {
|
||||||
mi := &file_alt_v1_backtest_proto_msgTypes[18]
|
mi := &file_alt_v1_backtest_proto_msgTypes[19]
|
||||||
if x != nil {
|
if x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
if ms.LoadMessageInfo() == nil {
|
if ms.LoadMessageInfo() == nil {
|
||||||
|
|
@ -1146,7 +1206,7 @@ func (x *CompareBacktestRunsResponse) ProtoReflect() protoreflect.Message {
|
||||||
|
|
||||||
// Deprecated: Use CompareBacktestRunsResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use CompareBacktestRunsResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*CompareBacktestRunsResponse) Descriptor() ([]byte, []int) {
|
func (*CompareBacktestRunsResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{18}
|
return file_alt_v1_backtest_proto_rawDescGZIP(), []int{19}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CompareBacktestRunsResponse) GetResults() []*BacktestResult {
|
func (x *CompareBacktestRunsResponse) GetResults() []*BacktestResult {
|
||||||
|
|
@ -1167,7 +1227,10 @@ var File_alt_v1_backtest_proto protoreflect.FileDescriptor
|
||||||
|
|
||||||
const file_alt_v1_backtest_proto_rawDesc = "" +
|
const file_alt_v1_backtest_proto_rawDesc = "" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"\x15alt/v1/backtest.proto\x12\x06alt.v1\x1a\x13alt/v1/common.proto\x1a\x13alt/v1/market.proto\"\xcb\x01\n" +
|
"\x15alt/v1/backtest.proto\x12\x06alt.v1\x1a\x13alt/v1/common.proto\x1a\x13alt/v1/market.proto\"X\n" +
|
||||||
|
"\x15BacktestInputSelector\x12%\n" +
|
||||||
|
"\x0einstrument_ids\x18\x01 \x03(\tR\rinstrumentIds\x12\x18\n" +
|
||||||
|
"\asymbols\x18\x02 \x03(\tR\asymbols\"\x86\x02\n" +
|
||||||
"\x0fBacktestRunSpec\x12\x1f\n" +
|
"\x0fBacktestRunSpec\x12\x1f\n" +
|
||||||
"\vstrategy_id\x18\x01 \x01(\tR\n" +
|
"\vstrategy_id\x18\x01 \x01(\tR\n" +
|
||||||
"strategyId\x12&\n" +
|
"strategyId\x12&\n" +
|
||||||
|
|
@ -1176,7 +1239,8 @@ const file_alt_v1_backtest_proto_rawDesc = "" +
|
||||||
"\ffrom_unix_ms\x18\x04 \x01(\x03R\n" +
|
"\ffrom_unix_ms\x18\x04 \x01(\x03R\n" +
|
||||||
"fromUnixMs\x12\x1c\n" +
|
"fromUnixMs\x12\x1c\n" +
|
||||||
"\n" +
|
"\n" +
|
||||||
"to_unix_ms\x18\x05 \x01(\x03R\btoUnixMs\"\xd7\x01\n" +
|
"to_unix_ms\x18\x05 \x01(\x03R\btoUnixMs\x129\n" +
|
||||||
|
"\bselector\x18\x06 \x01(\v2\x1d.alt.v1.BacktestInputSelectorR\bselector\"\xd7\x01\n" +
|
||||||
"\vBacktestRun\x12\x0e\n" +
|
"\vBacktestRun\x12\x0e\n" +
|
||||||
"\x02id\x18\x01 \x01(\tR\x02id\x12+\n" +
|
"\x02id\x18\x01 \x01(\tR\x02id\x12+\n" +
|
||||||
"\x04spec\x18\x02 \x01(\v2\x17.alt.v1.BacktestRunSpecR\x04spec\x121\n" +
|
"\x04spec\x18\x02 \x01(\v2\x17.alt.v1.BacktestRunSpecR\x04spec\x121\n" +
|
||||||
|
|
@ -1263,74 +1327,76 @@ func file_alt_v1_backtest_proto_rawDescGZIP() []byte {
|
||||||
}
|
}
|
||||||
|
|
||||||
var file_alt_v1_backtest_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
var file_alt_v1_backtest_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
|
||||||
var file_alt_v1_backtest_proto_msgTypes = make([]protoimpl.MessageInfo, 19)
|
var file_alt_v1_backtest_proto_msgTypes = make([]protoimpl.MessageInfo, 20)
|
||||||
var file_alt_v1_backtest_proto_goTypes = []any{
|
var file_alt_v1_backtest_proto_goTypes = []any{
|
||||||
(BacktestRunStatus)(0), // 0: alt.v1.BacktestRunStatus
|
(BacktestRunStatus)(0), // 0: alt.v1.BacktestRunStatus
|
||||||
(*BacktestRunSpec)(nil), // 1: alt.v1.BacktestRunSpec
|
(*BacktestInputSelector)(nil), // 1: alt.v1.BacktestInputSelector
|
||||||
(*BacktestRun)(nil), // 2: alt.v1.BacktestRun
|
(*BacktestRunSpec)(nil), // 2: alt.v1.BacktestRunSpec
|
||||||
(*StartBacktestRequest)(nil), // 3: alt.v1.StartBacktestRequest
|
(*BacktestRun)(nil), // 3: alt.v1.BacktestRun
|
||||||
(*StartBacktestResponse)(nil), // 4: alt.v1.StartBacktestResponse
|
(*StartBacktestRequest)(nil), // 4: alt.v1.StartBacktestRequest
|
||||||
(*GetBacktestRunRequest)(nil), // 5: alt.v1.GetBacktestRunRequest
|
(*StartBacktestResponse)(nil), // 5: alt.v1.StartBacktestResponse
|
||||||
(*GetBacktestRunResponse)(nil), // 6: alt.v1.GetBacktestRunResponse
|
(*GetBacktestRunRequest)(nil), // 6: alt.v1.GetBacktestRunRequest
|
||||||
(*BacktestTrade)(nil), // 7: alt.v1.BacktestTrade
|
(*GetBacktestRunResponse)(nil), // 7: alt.v1.GetBacktestRunResponse
|
||||||
(*BacktestPosition)(nil), // 8: alt.v1.BacktestPosition
|
(*BacktestTrade)(nil), // 8: alt.v1.BacktestTrade
|
||||||
(*BacktestSummaryMetrics)(nil), // 9: alt.v1.BacktestSummaryMetrics
|
(*BacktestPosition)(nil), // 9: alt.v1.BacktestPosition
|
||||||
(*BacktestEquityPoint)(nil), // 10: alt.v1.BacktestEquityPoint
|
(*BacktestSummaryMetrics)(nil), // 10: alt.v1.BacktestSummaryMetrics
|
||||||
(*BacktestResult)(nil), // 11: alt.v1.BacktestResult
|
(*BacktestEquityPoint)(nil), // 11: alt.v1.BacktestEquityPoint
|
||||||
(*GetBacktestResultRequest)(nil), // 12: alt.v1.GetBacktestResultRequest
|
(*BacktestResult)(nil), // 12: alt.v1.BacktestResult
|
||||||
(*GetBacktestResultResponse)(nil), // 13: alt.v1.GetBacktestResultResponse
|
(*GetBacktestResultRequest)(nil), // 13: alt.v1.GetBacktestResultRequest
|
||||||
(*ListBacktestRunsRequest)(nil), // 14: alt.v1.ListBacktestRunsRequest
|
(*GetBacktestResultResponse)(nil), // 14: alt.v1.GetBacktestResultResponse
|
||||||
(*ListBacktestRunsResponse)(nil), // 15: alt.v1.ListBacktestRunsResponse
|
(*ListBacktestRunsRequest)(nil), // 15: alt.v1.ListBacktestRunsRequest
|
||||||
(*GetBacktestRunDetailRequest)(nil), // 16: alt.v1.GetBacktestRunDetailRequest
|
(*ListBacktestRunsResponse)(nil), // 16: alt.v1.ListBacktestRunsResponse
|
||||||
(*GetBacktestRunDetailResponse)(nil), // 17: alt.v1.GetBacktestRunDetailResponse
|
(*GetBacktestRunDetailRequest)(nil), // 17: alt.v1.GetBacktestRunDetailRequest
|
||||||
(*CompareBacktestRunsRequest)(nil), // 18: alt.v1.CompareBacktestRunsRequest
|
(*GetBacktestRunDetailResponse)(nil), // 18: alt.v1.GetBacktestRunDetailResponse
|
||||||
(*CompareBacktestRunsResponse)(nil), // 19: alt.v1.CompareBacktestRunsResponse
|
(*CompareBacktestRunsRequest)(nil), // 19: alt.v1.CompareBacktestRunsRequest
|
||||||
(Market)(0), // 20: alt.v1.Market
|
(*CompareBacktestRunsResponse)(nil), // 20: alt.v1.CompareBacktestRunsResponse
|
||||||
(Timeframe)(0), // 21: alt.v1.Timeframe
|
(Market)(0), // 21: alt.v1.Market
|
||||||
(*ErrorInfo)(nil), // 22: alt.v1.ErrorInfo
|
(Timeframe)(0), // 22: alt.v1.Timeframe
|
||||||
(*Quantity)(nil), // 23: alt.v1.Quantity
|
(*ErrorInfo)(nil), // 23: alt.v1.ErrorInfo
|
||||||
(*Price)(nil), // 24: alt.v1.Price
|
(*Quantity)(nil), // 24: alt.v1.Quantity
|
||||||
(*Decimal)(nil), // 25: alt.v1.Decimal
|
(*Price)(nil), // 25: alt.v1.Price
|
||||||
|
(*Decimal)(nil), // 26: alt.v1.Decimal
|
||||||
}
|
}
|
||||||
var file_alt_v1_backtest_proto_depIdxs = []int32{
|
var file_alt_v1_backtest_proto_depIdxs = []int32{
|
||||||
20, // 0: alt.v1.BacktestRunSpec.market:type_name -> alt.v1.Market
|
21, // 0: alt.v1.BacktestRunSpec.market:type_name -> alt.v1.Market
|
||||||
21, // 1: alt.v1.BacktestRunSpec.timeframe:type_name -> alt.v1.Timeframe
|
22, // 1: alt.v1.BacktestRunSpec.timeframe:type_name -> alt.v1.Timeframe
|
||||||
1, // 2: alt.v1.BacktestRun.spec:type_name -> alt.v1.BacktestRunSpec
|
1, // 2: alt.v1.BacktestRunSpec.selector:type_name -> alt.v1.BacktestInputSelector
|
||||||
0, // 3: alt.v1.BacktestRun.status:type_name -> alt.v1.BacktestRunStatus
|
2, // 3: alt.v1.BacktestRun.spec:type_name -> alt.v1.BacktestRunSpec
|
||||||
1, // 4: alt.v1.StartBacktestRequest.spec:type_name -> alt.v1.BacktestRunSpec
|
0, // 4: alt.v1.BacktestRun.status:type_name -> alt.v1.BacktestRunStatus
|
||||||
2, // 5: alt.v1.StartBacktestResponse.run:type_name -> alt.v1.BacktestRun
|
2, // 5: alt.v1.StartBacktestRequest.spec:type_name -> alt.v1.BacktestRunSpec
|
||||||
22, // 6: alt.v1.StartBacktestResponse.error:type_name -> alt.v1.ErrorInfo
|
3, // 6: alt.v1.StartBacktestResponse.run:type_name -> alt.v1.BacktestRun
|
||||||
2, // 7: alt.v1.GetBacktestRunResponse.run:type_name -> alt.v1.BacktestRun
|
23, // 7: alt.v1.StartBacktestResponse.error:type_name -> alt.v1.ErrorInfo
|
||||||
22, // 8: alt.v1.GetBacktestRunResponse.error:type_name -> alt.v1.ErrorInfo
|
3, // 8: alt.v1.GetBacktestRunResponse.run:type_name -> alt.v1.BacktestRun
|
||||||
23, // 9: alt.v1.BacktestTrade.quantity:type_name -> alt.v1.Quantity
|
23, // 9: alt.v1.GetBacktestRunResponse.error:type_name -> alt.v1.ErrorInfo
|
||||||
24, // 10: alt.v1.BacktestTrade.price:type_name -> alt.v1.Price
|
24, // 10: alt.v1.BacktestTrade.quantity:type_name -> alt.v1.Quantity
|
||||||
23, // 11: alt.v1.BacktestPosition.quantity:type_name -> alt.v1.Quantity
|
25, // 11: alt.v1.BacktestTrade.price:type_name -> alt.v1.Price
|
||||||
24, // 12: alt.v1.BacktestPosition.last_price:type_name -> alt.v1.Price
|
24, // 12: alt.v1.BacktestPosition.quantity:type_name -> alt.v1.Quantity
|
||||||
24, // 13: alt.v1.BacktestSummaryMetrics.starting_cash:type_name -> alt.v1.Price
|
25, // 13: alt.v1.BacktestPosition.last_price:type_name -> alt.v1.Price
|
||||||
24, // 14: alt.v1.BacktestSummaryMetrics.ending_equity:type_name -> alt.v1.Price
|
25, // 14: alt.v1.BacktestSummaryMetrics.starting_cash:type_name -> alt.v1.Price
|
||||||
25, // 15: alt.v1.BacktestSummaryMetrics.total_return:type_name -> alt.v1.Decimal
|
25, // 15: alt.v1.BacktestSummaryMetrics.ending_equity:type_name -> alt.v1.Price
|
||||||
24, // 16: alt.v1.BacktestEquityPoint.equity:type_name -> alt.v1.Price
|
26, // 16: alt.v1.BacktestSummaryMetrics.total_return:type_name -> alt.v1.Decimal
|
||||||
24, // 17: alt.v1.BacktestResult.starting_cash:type_name -> alt.v1.Price
|
25, // 17: alt.v1.BacktestEquityPoint.equity:type_name -> alt.v1.Price
|
||||||
24, // 18: alt.v1.BacktestResult.ending_equity:type_name -> alt.v1.Price
|
25, // 18: alt.v1.BacktestResult.starting_cash:type_name -> alt.v1.Price
|
||||||
7, // 19: alt.v1.BacktestResult.trades:type_name -> alt.v1.BacktestTrade
|
25, // 19: alt.v1.BacktestResult.ending_equity:type_name -> alt.v1.Price
|
||||||
8, // 20: alt.v1.BacktestResult.positions:type_name -> alt.v1.BacktestPosition
|
8, // 20: alt.v1.BacktestResult.trades:type_name -> alt.v1.BacktestTrade
|
||||||
9, // 21: alt.v1.BacktestResult.summary:type_name -> alt.v1.BacktestSummaryMetrics
|
9, // 21: alt.v1.BacktestResult.positions:type_name -> alt.v1.BacktestPosition
|
||||||
10, // 22: alt.v1.BacktestResult.equity_curve:type_name -> alt.v1.BacktestEquityPoint
|
10, // 22: alt.v1.BacktestResult.summary:type_name -> alt.v1.BacktestSummaryMetrics
|
||||||
11, // 23: alt.v1.GetBacktestResultResponse.result:type_name -> alt.v1.BacktestResult
|
11, // 23: alt.v1.BacktestResult.equity_curve:type_name -> alt.v1.BacktestEquityPoint
|
||||||
22, // 24: alt.v1.GetBacktestResultResponse.error:type_name -> alt.v1.ErrorInfo
|
12, // 24: alt.v1.GetBacktestResultResponse.result:type_name -> alt.v1.BacktestResult
|
||||||
0, // 25: alt.v1.ListBacktestRunsRequest.status:type_name -> alt.v1.BacktestRunStatus
|
23, // 25: alt.v1.GetBacktestResultResponse.error:type_name -> alt.v1.ErrorInfo
|
||||||
2, // 26: alt.v1.ListBacktestRunsResponse.runs:type_name -> alt.v1.BacktestRun
|
0, // 26: alt.v1.ListBacktestRunsRequest.status:type_name -> alt.v1.BacktestRunStatus
|
||||||
22, // 27: alt.v1.ListBacktestRunsResponse.error:type_name -> alt.v1.ErrorInfo
|
3, // 27: alt.v1.ListBacktestRunsResponse.runs:type_name -> alt.v1.BacktestRun
|
||||||
2, // 28: alt.v1.GetBacktestRunDetailResponse.run:type_name -> alt.v1.BacktestRun
|
23, // 28: alt.v1.ListBacktestRunsResponse.error:type_name -> alt.v1.ErrorInfo
|
||||||
11, // 29: alt.v1.GetBacktestRunDetailResponse.result:type_name -> alt.v1.BacktestResult
|
3, // 29: alt.v1.GetBacktestRunDetailResponse.run:type_name -> alt.v1.BacktestRun
|
||||||
22, // 30: alt.v1.GetBacktestRunDetailResponse.error:type_name -> alt.v1.ErrorInfo
|
12, // 30: alt.v1.GetBacktestRunDetailResponse.result:type_name -> alt.v1.BacktestResult
|
||||||
11, // 31: alt.v1.CompareBacktestRunsResponse.results:type_name -> alt.v1.BacktestResult
|
23, // 31: alt.v1.GetBacktestRunDetailResponse.error:type_name -> alt.v1.ErrorInfo
|
||||||
22, // 32: alt.v1.CompareBacktestRunsResponse.error:type_name -> alt.v1.ErrorInfo
|
12, // 32: alt.v1.CompareBacktestRunsResponse.results:type_name -> alt.v1.BacktestResult
|
||||||
33, // [33:33] is the sub-list for method output_type
|
23, // 33: alt.v1.CompareBacktestRunsResponse.error:type_name -> alt.v1.ErrorInfo
|
||||||
33, // [33:33] is the sub-list for method input_type
|
34, // [34:34] is the sub-list for method output_type
|
||||||
33, // [33:33] is the sub-list for extension type_name
|
34, // [34:34] is the sub-list for method input_type
|
||||||
33, // [33:33] is the sub-list for extension extendee
|
34, // [34:34] is the sub-list for extension type_name
|
||||||
0, // [0:33] is the sub-list for field type_name
|
34, // [34:34] is the sub-list for extension extendee
|
||||||
|
0, // [0:34] is the sub-list for field type_name
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() { file_alt_v1_backtest_proto_init() }
|
func init() { file_alt_v1_backtest_proto_init() }
|
||||||
|
|
@ -1346,7 +1412,7 @@ func file_alt_v1_backtest_proto_init() {
|
||||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_alt_v1_backtest_proto_rawDesc), len(file_alt_v1_backtest_proto_rawDesc)),
|
RawDescriptor: unsafe.Slice(unsafe.StringData(file_alt_v1_backtest_proto_rawDesc), len(file_alt_v1_backtest_proto_rawDesc)),
|
||||||
NumEnums: 1,
|
NumEnums: 1,
|
||||||
NumMessages: 19,
|
NumMessages: 20,
|
||||||
NumExtensions: 0,
|
NumExtensions: 0,
|
||||||
NumServices: 0,
|
NumServices: 0,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -16,12 +16,18 @@ enum BacktestRunStatus {
|
||||||
BACKTEST_RUN_STATUS_CANCELED = 5;
|
BACKTEST_RUN_STATUS_CANCELED = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message BacktestInputSelector {
|
||||||
|
repeated string instrument_ids = 1;
|
||||||
|
repeated string symbols = 2;
|
||||||
|
}
|
||||||
|
|
||||||
message BacktestRunSpec {
|
message BacktestRunSpec {
|
||||||
string strategy_id = 1;
|
string strategy_id = 1;
|
||||||
Market market = 2;
|
Market market = 2;
|
||||||
Timeframe timeframe = 3;
|
Timeframe timeframe = 3;
|
||||||
int64 from_unix_ms = 4;
|
int64 from_unix_ms = 4;
|
||||||
int64 to_unix_ms = 5;
|
int64 to_unix_ms = 5;
|
||||||
|
BacktestInputSelector selector = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BacktestRun {
|
message BacktestRun {
|
||||||
|
|
|
||||||
|
|
@ -22,12 +22,18 @@ const (
|
||||||
RunStatusCanceled RunStatus = "canceled"
|
RunStatusCanceled RunStatus = "canceled"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type InputSelector struct {
|
||||||
|
InstrumentIDs []market.InstrumentID
|
||||||
|
Symbols []string
|
||||||
|
}
|
||||||
|
|
||||||
type RunSpec struct {
|
type RunSpec struct {
|
||||||
StrategyID StrategyID
|
StrategyID StrategyID
|
||||||
Market market.Market
|
Market market.Market
|
||||||
Timeframe market.Timeframe
|
Timeframe market.Timeframe
|
||||||
From time.Time
|
From time.Time
|
||||||
To time.Time
|
To time.Time
|
||||||
|
Selector InputSelector
|
||||||
}
|
}
|
||||||
|
|
||||||
type Run struct {
|
type Run struct {
|
||||||
|
|
|
||||||
|
|
@ -191,7 +191,15 @@ func (f *fakeWorkerClient) ListLiveAuditEvents(ctx context.Context, req *altv1.L
|
||||||
}
|
}
|
||||||
|
|
||||||
func validStart() *altv1.StartBacktestRequest {
|
func validStart() *altv1.StartBacktestRequest {
|
||||||
return &altv1.StartBacktestRequest{Spec: &altv1.BacktestRunSpec{StrategyId: "strat-abc"}}
|
return &altv1.StartBacktestRequest{
|
||||||
|
Spec: &altv1.BacktestRunSpec{
|
||||||
|
StrategyId: "strat-abc",
|
||||||
|
Selector: &altv1.BacktestInputSelector{
|
||||||
|
InstrumentIds: []string{"005930"},
|
||||||
|
Symbols: []string{"AAPL"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleStartBacktestForwards(t *testing.T) {
|
func TestHandleStartBacktestForwards(t *testing.T) {
|
||||||
|
|
@ -207,6 +215,17 @@ func TestHandleStartBacktestForwards(t *testing.T) {
|
||||||
if resp.GetRun().GetId() != "run-1" {
|
if resp.GetRun().GetId() != "run-1" {
|
||||||
t.Errorf("unexpected response run id: %q", resp.GetRun().GetId())
|
t.Errorf("unexpected response run id: %q", resp.GetRun().GetId())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pSel := fake.startReq.GetSpec().GetSelector()
|
||||||
|
if pSel == nil {
|
||||||
|
t.Fatal("expected selector to be forwarded")
|
||||||
|
}
|
||||||
|
if len(pSel.GetInstrumentIds()) != 1 || pSel.GetInstrumentIds()[0] != "005930" {
|
||||||
|
t.Errorf("unexpected forwarded instrument ids: %v", pSel.GetInstrumentIds())
|
||||||
|
}
|
||||||
|
if len(pSel.GetSymbols()) != 1 || pSel.GetSymbols()[0] != "AAPL" {
|
||||||
|
t.Errorf("unexpected forwarded symbols: %v", pSel.GetSymbols())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestHandleStartBacktestRejectsMissingSpec(t *testing.T) {
|
func TestHandleStartBacktestRejectsMissingSpec(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,13 @@ func TestWorkerClient_StartBacktest_Success(t *testing.T) {
|
||||||
defer client.Close()
|
defer client.Close()
|
||||||
|
|
||||||
res, err := client.StartBacktest(ctx, &altv1.StartBacktestRequest{
|
res, err := client.StartBacktest(ctx, &altv1.StartBacktestRequest{
|
||||||
Spec: &altv1.BacktestRunSpec{StrategyId: "strat-abc"},
|
Spec: &altv1.BacktestRunSpec{
|
||||||
|
StrategyId: "strat-abc",
|
||||||
|
Selector: &altv1.BacktestInputSelector{
|
||||||
|
InstrumentIds: []string{"005930"},
|
||||||
|
Symbols: []string{"AAPL"},
|
||||||
|
},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("StartBacktest failed: %v", err)
|
t.Fatalf("StartBacktest failed: %v", err)
|
||||||
|
|
@ -120,6 +126,16 @@ func TestWorkerClient_StartBacktest_Success(t *testing.T) {
|
||||||
if res.GetRun().GetSpec().GetStrategyId() != "strat-abc" {
|
if res.GetRun().GetSpec().GetStrategyId() != "strat-abc" {
|
||||||
t.Errorf("spec did not round-trip, got %q", res.GetRun().GetSpec().GetStrategyId())
|
t.Errorf("spec did not round-trip, got %q", res.GetRun().GetSpec().GetStrategyId())
|
||||||
}
|
}
|
||||||
|
pSel := res.GetRun().GetSpec().GetSelector()
|
||||||
|
if pSel == nil {
|
||||||
|
t.Fatal("expected selector to round-trip")
|
||||||
|
}
|
||||||
|
if len(pSel.GetInstrumentIds()) != 1 || pSel.GetInstrumentIds()[0] != "005930" {
|
||||||
|
t.Errorf("unexpected selector instrument ids: %v", pSel.GetInstrumentIds())
|
||||||
|
}
|
||||||
|
if len(pSel.GetSymbols()) != 1 || pSel.GetSymbols()[0] != "AAPL" {
|
||||||
|
t.Errorf("unexpected selector symbols: %v", pSel.GetSymbols())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWorkerClient_StartPaperTrading_Success(t *testing.T) {
|
func TestWorkerClient_StartPaperTrading_Success(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package socket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
altv1 "git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1"
|
altv1 "git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1"
|
||||||
|
|
@ -48,12 +49,32 @@ func runSpecFromProto(spec *altv1.BacktestRunSpec) (backtest.RunSpec, error) {
|
||||||
return backtest.RunSpec{}, fmt.Errorf("backtest spec: from cannot be after to")
|
return backtest.RunSpec{}, fmt.Errorf("backtest spec: from cannot be after to")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var selector backtest.InputSelector
|
||||||
|
if spec.GetSelector() != nil {
|
||||||
|
pSel := spec.GetSelector()
|
||||||
|
for _, id := range pSel.GetInstrumentIds() {
|
||||||
|
trimmed := strings.TrimSpace(id)
|
||||||
|
if trimmed == "" {
|
||||||
|
return backtest.RunSpec{}, fmt.Errorf("backtest spec: selector: instrument_id cannot be blank")
|
||||||
|
}
|
||||||
|
selector.InstrumentIDs = append(selector.InstrumentIDs, market.InstrumentID(trimmed))
|
||||||
|
}
|
||||||
|
for _, sym := range pSel.GetSymbols() {
|
||||||
|
trimmed := strings.TrimSpace(sym)
|
||||||
|
if trimmed == "" {
|
||||||
|
return backtest.RunSpec{}, fmt.Errorf("backtest spec: selector: symbol cannot be blank")
|
||||||
|
}
|
||||||
|
selector.Symbols = append(selector.Symbols, trimmed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return backtest.RunSpec{
|
return backtest.RunSpec{
|
||||||
StrategyID: backtest.StrategyID(spec.GetStrategyId()),
|
StrategyID: backtest.StrategyID(spec.GetStrategyId()),
|
||||||
Market: mkt,
|
Market: mkt,
|
||||||
Timeframe: timeframe,
|
Timeframe: timeframe,
|
||||||
From: from,
|
From: from,
|
||||||
To: to,
|
To: to,
|
||||||
|
Selector: selector,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -142,12 +163,24 @@ func runStatusToProto(s backtest.RunStatus) altv1.BacktestRunStatus {
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSpecToProto(spec backtest.RunSpec) *altv1.BacktestRunSpec {
|
func runSpecToProto(spec backtest.RunSpec) *altv1.BacktestRunSpec {
|
||||||
|
var pSel *altv1.BacktestInputSelector
|
||||||
|
if len(spec.Selector.InstrumentIDs) > 0 || len(spec.Selector.Symbols) > 0 {
|
||||||
|
insts := make([]string, len(spec.Selector.InstrumentIDs))
|
||||||
|
for i, id := range spec.Selector.InstrumentIDs {
|
||||||
|
insts[i] = string(id)
|
||||||
|
}
|
||||||
|
pSel = &altv1.BacktestInputSelector{
|
||||||
|
InstrumentIds: insts,
|
||||||
|
Symbols: spec.Selector.Symbols,
|
||||||
|
}
|
||||||
|
}
|
||||||
return &altv1.BacktestRunSpec{
|
return &altv1.BacktestRunSpec{
|
||||||
StrategyId: string(spec.StrategyID),
|
StrategyId: string(spec.StrategyID),
|
||||||
Market: marketToProto(spec.Market),
|
Market: marketToProto(spec.Market),
|
||||||
Timeframe: timeframeToProto(spec.Timeframe),
|
Timeframe: timeframeToProto(spec.Timeframe),
|
||||||
FromUnixMs: timeToUnixMs(spec.From),
|
FromUnixMs: timeToUnixMs(spec.From),
|
||||||
ToUnixMs: timeToUnixMs(spec.To),
|
ToUnixMs: timeToUnixMs(spec.To),
|
||||||
|
Selector: pSel,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,10 @@ func TestRunSpecFromProtoValid(t *testing.T) {
|
||||||
Timeframe: altv1.Timeframe_TIMEFRAME_DAILY,
|
Timeframe: altv1.Timeframe_TIMEFRAME_DAILY,
|
||||||
FromUnixMs: from.UnixMilli(),
|
FromUnixMs: from.UnixMilli(),
|
||||||
ToUnixMs: to.UnixMilli(),
|
ToUnixMs: to.UnixMilli(),
|
||||||
|
Selector: &altv1.BacktestInputSelector{
|
||||||
|
InstrumentIds: []string{"005930"},
|
||||||
|
Symbols: []string{"AAPL"},
|
||||||
|
},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
|
@ -39,6 +43,12 @@ func TestRunSpecFromProtoValid(t *testing.T) {
|
||||||
if !spec.To.Equal(to) {
|
if !spec.To.Equal(to) {
|
||||||
t.Errorf("to mismatch: got %v want %v", spec.To, to)
|
t.Errorf("to mismatch: got %v want %v", spec.To, to)
|
||||||
}
|
}
|
||||||
|
if len(spec.Selector.InstrumentIDs) != 1 || spec.Selector.InstrumentIDs[0] != "005930" {
|
||||||
|
t.Errorf("selector instrument ids mismatch: got %v", spec.Selector.InstrumentIDs)
|
||||||
|
}
|
||||||
|
if len(spec.Selector.Symbols) != 1 || spec.Selector.Symbols[0] != "AAPL" {
|
||||||
|
t.Errorf("selector symbols mismatch: got %v", spec.Selector.Symbols)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunSpecFromProtoRejectsInvalid(t *testing.T) {
|
func TestRunSpecFromProtoRejectsInvalid(t *testing.T) {
|
||||||
|
|
@ -56,6 +66,8 @@ func TestRunSpecFromProtoRejectsInvalid(t *testing.T) {
|
||||||
{"missing from", &altv1.BacktestRunSpec{StrategyId: "s", Market: altv1.Market_MARKET_KR, Timeframe: altv1.Timeframe_TIMEFRAME_DAILY, ToUnixMs: validTo}},
|
{"missing from", &altv1.BacktestRunSpec{StrategyId: "s", Market: altv1.Market_MARKET_KR, Timeframe: altv1.Timeframe_TIMEFRAME_DAILY, ToUnixMs: validTo}},
|
||||||
{"missing to", &altv1.BacktestRunSpec{StrategyId: "s", Market: altv1.Market_MARKET_KR, Timeframe: altv1.Timeframe_TIMEFRAME_DAILY, FromUnixMs: validFrom}},
|
{"missing to", &altv1.BacktestRunSpec{StrategyId: "s", Market: altv1.Market_MARKET_KR, Timeframe: altv1.Timeframe_TIMEFRAME_DAILY, FromUnixMs: validFrom}},
|
||||||
{"inverted range", &altv1.BacktestRunSpec{StrategyId: "s", Market: altv1.Market_MARKET_KR, Timeframe: altv1.Timeframe_TIMEFRAME_DAILY, FromUnixMs: validTo, ToUnixMs: validFrom}},
|
{"inverted range", &altv1.BacktestRunSpec{StrategyId: "s", Market: altv1.Market_MARKET_KR, Timeframe: altv1.Timeframe_TIMEFRAME_DAILY, FromUnixMs: validTo, ToUnixMs: validFrom}},
|
||||||
|
{"blank instrument id", &altv1.BacktestRunSpec{StrategyId: "s", Market: altv1.Market_MARKET_KR, Timeframe: altv1.Timeframe_TIMEFRAME_DAILY, FromUnixMs: validFrom, ToUnixMs: validTo, Selector: &altv1.BacktestInputSelector{InstrumentIds: []string{" "}}}},
|
||||||
|
{"blank symbol", &altv1.BacktestRunSpec{StrategyId: "s", Market: altv1.Market_MARKET_KR, Timeframe: altv1.Timeframe_TIMEFRAME_DAILY, FromUnixMs: validFrom, ToUnixMs: validTo, Selector: &altv1.BacktestInputSelector{Symbols: []string{""}}}},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
@ -122,6 +134,10 @@ func TestRunToProto(t *testing.T) {
|
||||||
Timeframe: market.TimeframeMin5,
|
Timeframe: market.TimeframeMin5,
|
||||||
From: time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC),
|
From: time.Date(2026, 5, 1, 0, 0, 0, 0, time.UTC),
|
||||||
To: time.Date(2026, 5, 15, 0, 0, 0, 0, time.UTC),
|
To: time.Date(2026, 5, 15, 0, 0, 0, 0, time.UTC),
|
||||||
|
Selector: backtest.InputSelector{
|
||||||
|
InstrumentIDs: []market.InstrumentID{"AAPL"},
|
||||||
|
Symbols: []string{"MSFT"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Status: backtest.RunStatusRunning,
|
Status: backtest.RunStatusRunning,
|
||||||
CreatedAt: created,
|
CreatedAt: created,
|
||||||
|
|
@ -144,6 +160,17 @@ func TestRunToProto(t *testing.T) {
|
||||||
if got.GetUpdatedAtUnixMs() != updated.UnixMilli() {
|
if got.GetUpdatedAtUnixMs() != updated.UnixMilli() {
|
||||||
t.Errorf("updated mismatch: %d", got.GetUpdatedAtUnixMs())
|
t.Errorf("updated mismatch: %d", got.GetUpdatedAtUnixMs())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pSel := got.GetSpec().GetSelector()
|
||||||
|
if pSel == nil {
|
||||||
|
t.Fatal("selector should not be nil")
|
||||||
|
}
|
||||||
|
if len(pSel.GetInstrumentIds()) != 1 || pSel.GetInstrumentIds()[0] != "AAPL" {
|
||||||
|
t.Errorf("instrument ids mismatch: got %v", pSel.GetInstrumentIds())
|
||||||
|
}
|
||||||
|
if len(pSel.GetSymbols()) != 1 || pSel.GetSymbols()[0] != "MSFT" {
|
||||||
|
t.Errorf("symbols mismatch: got %v", pSel.GetSymbols())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRunToProtoZeroTimeMapsToZero(t *testing.T) {
|
func TestRunToProtoZeroTimeMapsToZero(t *testing.T) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue