feat: backtest data collection infrastructure and agent-tooling phase add
This commit is contained in:
parent
6201708081
commit
d1bfc28d34
20 changed files with 1810 additions and 127 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
## 전체 목표
|
||||
|
||||
ALT는 개인용 quant system workspace로, 한국장 일봉 MVP에서 시작해 시장 데이터 수집, 정규화, 백테스트, 결과 분석, command-first 운영 흐름, 페이퍼 트레이딩, 미국장 확장, 실거래 경계, Flutter 운영 UI까지 단계적으로 확장한다. Go 서비스와 CLI/YAML workflow는 뒤집지 않는 운영 코어를 담당하고, Flutter client는 충분히 표면화된 운영 항목을 web/mobile/desktop 단일 UI 표면으로 정리하는 후순위 단계로 둔다. proto-socket과 ALT protobuf contracts가 런타임 사이의 통신 기준이 된다.
|
||||
ALT는 개인용 quant system workspace로, 한국장 일봉 MVP에서 시작해 시장 데이터 수집, 정규화, 백테스트, 결과 분석, command-first 운영 흐름, 페이퍼 트레이딩, 미국장 확장, 실거래 경계, Flutter 운영 UI까지 단계적으로 확장한다. Go 서비스와 CLI/YAML workflow는 뒤집지 않는 운영 코어를 담당하고, Flutter client는 충분히 표면화된 운영 항목을 web/mobile/desktop 단일 UI 표면으로 정리하는 후순위 단계로 둔다. 에이전트는 ALT의 주식 분석/백테스트 코어가 아니라 별도 도구/분석 보조 표면으로 정의하며, `../iop` backend와 `../agent-shell` frontend 연동은 용도 정의 이후 별도 Phase에서 다룬다. proto-socket과 ALT protobuf contracts가 ALT 런타임 사이의 통신 기준이 된다.
|
||||
|
||||
## Phase 흐름
|
||||
|
||||
|
|
@ -24,7 +24,10 @@ ALT는 개인용 quant system workspace로, 한국장 일봉 MVP에서 시작해
|
|||
- 요약: paper trading, 미국장 확장, 주문/리스크/포트폴리오, 실거래 adapter 경계를 MVP 이후 단계로 연다.
|
||||
- [진행중] 백테스트 루프
|
||||
- 경로: `agent-roadmap/phase/backtest-loop/PHASE.md`
|
||||
- 요약: 일봉 기반 strategy execution, backtest lifecycle, result persistence, repeatable verification을 만든다.
|
||||
- 요약: 선택 종목의 자동/일별 수집, 정제 데이터 저장, 재현 가능한 backtest dataset과 lifecycle verification을 만든다.
|
||||
- [스케치] 에이전트 도구화
|
||||
- 경로: `agent-roadmap/phase/agent-tooling/PHASE.md`
|
||||
- 요약: 에이전트를 ALT 코어가 아니라 도구/분석 보조층으로 쓰기 위한 용도, 책임 경계, `../iop` backend와 `../agent-shell` frontend 연결 후보를 정리한다.
|
||||
- [보류] 운영 UI
|
||||
- 경로: `agent-roadmap/phase/operator-ui/PHASE.md`
|
||||
- 요약: command/headless workflow와 거래 확장 기능이 충분히 표면화된 뒤 Flutter operator console과 push notification UI boundary를 연다.
|
||||
|
|
|
|||
29
agent-roadmap/phase/agent-tooling/PHASE.md
Normal file
29
agent-roadmap/phase/agent-tooling/PHASE.md
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
# Phase: 에이전트 도구화
|
||||
|
||||
## 상태
|
||||
|
||||
[스케치]
|
||||
|
||||
## 목표
|
||||
|
||||
에이전트를 ALT의 주식 분석/백테스트 코어로 직접 넣지 않고, 개발·분석·운영 보조 도구로 어떤 용도에서 쓸지 먼저 정의한다. 현재 확실한 전제는 `../iop`를 agent backend로, `../agent-shell`을 frontend로 사용하는 것이며, ALT는 데이터 수집·정제·백테스트·도메인 판단 코어를 별도로 유지한다.
|
||||
|
||||
## Milestone 흐름
|
||||
|
||||
완료된 Milestone은 archive 경로를 가리키고, 검토중, 진행중, 계획, 스케치 또는 보류 Milestone은 이 Phase 하위 `milestones/` 경로를 가리킨다.
|
||||
완료, 검토중, 진행중, 계획, 스케치 순서로 두어 아래로 갈수록 미래 작업에 가까워지게 정렬한다.
|
||||
스케치 Milestone은 아직 구현 가능한 계획이 아니므로 계획 Milestone보다 아래에 둔다.
|
||||
|
||||
- [스케치] Agent Usage Definition Sketch
|
||||
- 경로: `agent-roadmap/phase/agent-tooling/milestones/agent-usage-definition-sketch.md`
|
||||
- 요약: 에이전트를 어떤 용도와 경계의 도구로 사용할지 먼저 정의하고, 주식 분석 코어와 분리한다.
|
||||
- [스케치] IOP Agent Shell Bridge Sketch
|
||||
- 경로: `agent-roadmap/phase/agent-tooling/milestones/iop-agent-shell-bridge-sketch.md`
|
||||
- 요약: `../iop` backend와 `../agent-shell` frontend를 ALT와 느슨하게 연결하기 위한 프로젝트 역할, 호출 방향, 계약 후보를 정리한다.
|
||||
|
||||
## Phase 경계
|
||||
|
||||
- 이 Phase는 에이전트의 용도, 책임 경계, 프로젝트 간 연결 후보를 다룬다.
|
||||
- 주식 분석 알고리즘, 백테스트 인프라, 데이터 수집/정제 코어는 ALT의 별도 Phase와 Milestone에서 다룬다.
|
||||
- 에이전트가 생성한 산출물은 명시적 검토 전까지 자동 투자 판단, 주문, 백테스트 결과 확정 근거로 쓰지 않는다.
|
||||
- `../iop`와 `../agent-shell` 연결은 먼저 local/dev topology와 계약 후보를 정리한 뒤 구현 Milestone으로 승격한다.
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
# Milestone: Agent Usage Definition Sketch
|
||||
|
||||
## 위치
|
||||
|
||||
- Roadmap: `agent-roadmap/ROADMAP.md`
|
||||
- Phase: `agent-roadmap/phase/agent-tooling/PHASE.md`
|
||||
|
||||
## 목표
|
||||
|
||||
에이전트를 특정 finance/stock 분석 기능으로 미리 도입하지 않고, ALT와 주변 프로젝트에서 어떤 용도의 도구로 사용할지 정의한다. 에이전트는 주식 분석 코어, 백테스트 코어, 데이터 수집 코어가 아니라 `../iop` backend와 `../agent-shell` frontend를 통해 사용될 수 있는 보조 도구층으로 먼저 정리한다.
|
||||
|
||||
## 상태
|
||||
|
||||
[스케치]
|
||||
|
||||
## 승격 조건
|
||||
|
||||
- [ ] 에이전트 사용 용도를 개발 보조, 운영 보조, 분석 보조, 문서/리포트 보조, 툴 orchestration 등 범주 단위로 정리한다.
|
||||
- [ ] ALT 코어와 에이전트 도구층 사이의 책임 경계와 금지 경계를 정한다.
|
||||
- [ ] `../iop` backend와 `../agent-shell` frontend가 맡을 역할을 ALT 관점에서 정리한다.
|
||||
- [ ] 에이전트가 ALT 데이터/도구에 접근할 때의 read/write, persistence, review 기준 후보를 정한다.
|
||||
- [ ] 후속 구현 Milestone을 iop-agent-shell bridge, ALT tool gateway, agent artifact policy 중 필요한 단위로 나눈다.
|
||||
|
||||
## 구현 잠금
|
||||
|
||||
- 상태: 잠금
|
||||
- 결정 필요: 아래 체크리스트
|
||||
- [ ] 에이전트를 우선 어떤 용도 범주부터 사용할지 결정한다.
|
||||
- [ ] 에이전트가 ALT에 직접 연결될지, `../iop`를 통해서만 연결될지 결정한다.
|
||||
- [ ] 에이전트 산출물을 임시 세션 산출물로 둘지, 정제된 일부만 ALT DB에 남길지 결정한다.
|
||||
- [ ] 에이전트가 사용할 수 있는 ALT tool/API/CLI의 read/write 경계를 결정한다.
|
||||
|
||||
## 범위
|
||||
|
||||
- 에이전트 용도 범주와 비목표 정의
|
||||
- ALT 코어와 agent tooling 사이의 책임 경계 정리
|
||||
- `../iop` backend, `../agent-shell` frontend 사용 전제 기록
|
||||
- 에이전트 산출물의 임시/영구 보존 후보와 human review 기준 정리
|
||||
- 후속 구현 Milestone 후보 분리
|
||||
|
||||
## 기능
|
||||
|
||||
### Epic: [purpose] Agent purpose map
|
||||
|
||||
에이전트를 특정 주식 분석 기능으로 고정하지 않고, 여러 프로젝트에서 사용할 수 있는 도구 용도 범주로 정의한다.
|
||||
|
||||
- [ ] [usage-taxonomy] 에이전트 사용 용도 범주와 각 범주의 비목표를 정리한다.
|
||||
- [ ] [core-boundary] 주식 분석, 백테스트, 데이터 정제 코어와 에이전트 도구층의 책임을 분리한다.
|
||||
- [ ] [fixed-assumptions] `../iop` backend와 `../agent-shell` frontend 사용 전제를 명시한다.
|
||||
|
||||
### Epic: [policy] Tooling boundary policy
|
||||
|
||||
에이전트가 ALT의 데이터와 도구를 사용할 때 필요한 접근, 저장, 검토 기준을 스케치한다.
|
||||
|
||||
- [ ] [access-policy] 에이전트의 ALT tool/API/CLI 접근 후보와 read/write 경계를 정리한다.
|
||||
- [ ] [artifact-policy] 에이전트 산출물 중 DB에 남길 정제 결과와 임시 산출물을 구분하는 후보 기준을 정리한다.
|
||||
- [ ] [next-milestones] iop-agent-shell bridge와 ALT tool gateway 등 후속 구현 단위를 제안한다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
||||
- 상태: 없음
|
||||
- 요청일: 없음
|
||||
- 완료 근거: 아직 스케치 상태이며 승격 조건이 충족되지 않았다.
|
||||
- 리뷰 필요:
|
||||
- [ ] 사용자가 완료 결과를 확인했다
|
||||
- [ ] archive 이동을 승인했다
|
||||
- 리뷰 코멘트: 없음
|
||||
|
||||
## 범위 제외
|
||||
|
||||
- finance agent skillset 도입
|
||||
- 주식 분석 로직, 전략 판단 로직, 백테스트 인프라 구현
|
||||
- `../iop` 또는 `../agent-shell` 코드 변경
|
||||
- ALT worker/API/client에 agent runtime 직접 삽입
|
||||
- 자동 투자 판단, 주문 제출, 실거래 execution
|
||||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `../iop/`, `../agent-shell/`, `services/worker/`, `apps/cli/`, `packages/contracts/`
|
||||
- 표준선(선택): agent는 ALT 코어를 대체하지 않고 도구/분석/운영 보조층으로 둔다.
|
||||
- 표준선(선택): 주식 분석 기능은 agent가 아니라 ALT 코어 또는 별도 분석 모듈에서 설계될 수 있다.
|
||||
- 표준선(선택): 에이전트 산출물은 자동 실행 근거가 아니라 검토 가능한 보조 산출물로 취급한다.
|
||||
- 선행 작업: 없음
|
||||
- 후속 작업: IOP Agent Shell Bridge Sketch, ALT Tool Gateway 후보
|
||||
- 확인 필요:
|
||||
- 첫 용도 범주
|
||||
- ALT 연결 방식
|
||||
- 산출물 저장 기준
|
||||
- read/write 접근 경계
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
# Milestone: IOP Agent Shell Bridge Sketch
|
||||
|
||||
## 위치
|
||||
|
||||
- Roadmap: `agent-roadmap/ROADMAP.md`
|
||||
- Phase: `agent-roadmap/phase/agent-tooling/PHASE.md`
|
||||
|
||||
## 목표
|
||||
|
||||
`../iop`를 agent backend로, `../agent-shell`을 frontend로 사용할 때 ALT와 어떻게 느슨하게 연결할지 정리한다. 이 마일스톤은 바로 구현을 확정하지 않고, 프로젝트 역할, 호출 방향, local/dev topology, 최소 계약 후보를 먼저 스케치한다.
|
||||
|
||||
## 상태
|
||||
|
||||
[스케치]
|
||||
|
||||
## 승격 조건
|
||||
|
||||
- [ ] `../iop`, `../agent-shell`, ALT 사이의 프로젝트 역할과 소유 경계를 정한다.
|
||||
- [ ] 호출 방향을 `agent-shell -> iop -> ALT tool/API/CLI`, `ALT -> iop`, 또는 별도 gateway 중 하나로 좁힌다.
|
||||
- [ ] local/dev 실행 topology와 secret/env 전달 경계를 정한다.
|
||||
- [ ] 최소 연결 계약 후보를 CLI, proto-socket/API, MCP/tool gateway, file artifact handoff 중에서 비교한다.
|
||||
- [ ] 구현 Milestone으로 나눌 경우 bridge scaffold, ALT tool gateway, artifact handoff 중 필요한 단위를 정한다.
|
||||
|
||||
## 구현 잠금
|
||||
|
||||
- 상태: 잠금
|
||||
- 결정 필요: 아래 체크리스트
|
||||
- [ ] 기본 호출 방향과 소유 프로젝트를 결정한다.
|
||||
- [ ] ALT와 연결할 transport/tool boundary를 결정한다.
|
||||
- [ ] local secret/env 전달 방식을 결정한다.
|
||||
- [ ] bridge의 첫 smoke scenario를 결정한다.
|
||||
|
||||
## 범위
|
||||
|
||||
- `../iop` backend와 `../agent-shell` frontend의 ALT 연동 후보 정리
|
||||
- 프로젝트 간 역할, 호출 방향, 실행 topology 스케치
|
||||
- ALT tool/API/CLI 연결 방식 후보 비교
|
||||
- 산출물 handoff와 review boundary 후보 정리
|
||||
- 후속 구현 Milestone 후보 분리
|
||||
|
||||
## 기능
|
||||
|
||||
### Epic: [topology] Project topology
|
||||
|
||||
세 프로젝트가 어떤 방향으로 연결되는지, 어느 프로젝트가 어떤 책임을 갖는지 정리한다.
|
||||
|
||||
- [ ] [project-roles] `../iop`, `../agent-shell`, ALT의 역할과 소유 경계를 정리한다.
|
||||
- [ ] [call-flow] 후보 호출 방향과 장단점을 비교한다.
|
||||
- [ ] [local-dev] local/dev 실행 topology와 env/secret 전달 후보를 정리한다.
|
||||
|
||||
### Epic: [bridge] Bridge contract candidates
|
||||
|
||||
실제 연결 전에 최소 계약과 handoff 방식을 비교한다.
|
||||
|
||||
- [ ] [interface-options] CLI, API/proto-socket, MCP/tool gateway, file artifact handoff 후보를 비교한다.
|
||||
- [ ] [review-boundary] agent 산출물이 ALT DB 또는 운영 판단으로 넘어가기 전 review boundary를 정리한다.
|
||||
- [ ] [implementation-split] bridge scaffold와 ALT tool gateway 등 후속 구현 단위를 제안한다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
||||
- 상태: 없음
|
||||
- 요청일: 없음
|
||||
- 완료 근거: 아직 스케치 상태이며 승격 조건이 충족되지 않았다.
|
||||
- 리뷰 필요:
|
||||
- [ ] 사용자가 완료 결과를 확인했다
|
||||
- [ ] archive 이동을 승인했다
|
||||
- 리뷰 코멘트: 없음
|
||||
|
||||
## 범위 제외
|
||||
|
||||
- `../iop`, `../agent-shell`, ALT 코드 구현
|
||||
- agent skill 내용 설계
|
||||
- 주식 분석/백테스트 판단 로직 설계
|
||||
- production 배포, 인증, 장기 운영 모니터링 구현
|
||||
- 자동 주문 또는 실거래 execution
|
||||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `../iop/`, `../agent-shell/`, `apps/cli/`, `services/api/`, `services/worker/`, `packages/contracts/`
|
||||
- 표준선(선택): bridge는 ALT 코어를 뒤집지 않고 외부 도구층에서 ALT의 headless/API 경계를 사용하는 방향을 우선 검토한다.
|
||||
- 표준선(선택): 연결 계약은 먼저 local/dev smoke가 가능한 최소 단위로 둔다.
|
||||
- 선행 작업: Agent Usage Definition Sketch
|
||||
- 후속 작업: IOP Agent Shell Bridge Implementation, ALT Tool Gateway 후보
|
||||
- 확인 필요:
|
||||
- 호출 방향
|
||||
- transport/tool boundary
|
||||
- secret/env 전달 방식
|
||||
- 첫 smoke scenario
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
## 목표
|
||||
|
||||
정규화된 일봉 데이터를 기준으로 strategy execution, run lifecycle, result persistence, 기본 분석 표면을 만든다. ALT 확장의 중심축이 되는 반복 가능한 백테스트 루프를 안정화한다.
|
||||
사용자가 정한 종목군을 자동으로 수집하고 매일 갱신할 수 있는 데이터 파이프라인을 기준으로, 정제 데이터 저장, strategy execution, run lifecycle, result persistence, 기본 분석 표면을 만든다. ALT 확장의 중심축이 되는 반복 가능한 백테스트 인프라와 실행 루프를 안정화한다.
|
||||
|
||||
## Milestone 흐름
|
||||
|
||||
|
|
@ -19,12 +19,14 @@
|
|||
- [완료] Backtest Analysis Surface
|
||||
- 경로: `agent-roadmap/archive/phase/backtest-loop/milestones/backtest-analysis-surface.md`
|
||||
- 요약: backtest 결과를 operator가 비교하고 해석할 수 있는 기본 분석 표면을 만든다.
|
||||
- [스케치] Finance Agent Research Sidecar Sketch
|
||||
- 경로: `agent-roadmap/phase/backtest-loop/milestones/finance-agent-research-sidecar-sketch.md`
|
||||
- 요약: Anthropic finance agents 계열 skillset을 ALT 코어가 아니라 리서치/전략 가설/백테스트 해석 보조 sidecar 후보로 검토한다.
|
||||
- [진행중] Backtest Data Collection Infrastructure
|
||||
- 경로: `agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md`
|
||||
- 요약: 선택 종목을 자동/일별로 수집하고 정제된 백테스트 입력 데이터만 DB에 남기는 headless 인프라를 만든다.
|
||||
|
||||
## Phase 경계
|
||||
|
||||
- 이 Phase는 deterministic backtest와 기본 result analysis에 집중한다.
|
||||
- 선택 종목 수집, 일별 갱신, 정제 데이터 저장은 백테스트 실행 가능성의 선행 인프라로 이 Phase에서 다룬다.
|
||||
- 에이전트 도입, 에이전트 skillset, `../iop`/`../agent-shell` 연결은 이 Phase의 책임이 아니며 `agent-tooling` Phase에서 별도로 정의한다.
|
||||
- paper/live order routing, 실시간 전략 실행, 고급 리스크 모델 전체는 거래 확장 Phase에서 다룬다.
|
||||
- worker가 backtest 실행을 소유하고 API는 실행 요청과 조회 경계를 담당한다.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
# Milestone: Backtest Data Collection Infrastructure
|
||||
|
||||
## 위치
|
||||
|
||||
- Roadmap: `agent-roadmap/ROADMAP.md`
|
||||
- Phase: `agent-roadmap/phase/backtest-loop/PHASE.md`
|
||||
|
||||
## 목표
|
||||
|
||||
사용자가 정한 종목군을 대상으로 자동 수집과 일별 갱신을 수행하고, 백테스트에 필요한 정제 데이터만 DB에 남기는 headless 인프라를 만든다. 이 마일스톤은 에이전트 도입이 아니라 백테스팅을 실제로 반복하기 위한 데이터 준비, freshness 확인, 실행 입력 안정화에 집중한다.
|
||||
|
||||
## 상태
|
||||
|
||||
[진행중]
|
||||
|
||||
## 승격 조건
|
||||
|
||||
- 없음
|
||||
|
||||
## 구현 잠금
|
||||
|
||||
- 상태: 해제
|
||||
- 결정 필요: 없음
|
||||
|
||||
## 범위
|
||||
|
||||
- 사용자가 정한 종목군/watchlist를 수집 대상으로 삼는 설정 경계
|
||||
- 가능한 넓은 provider/data 접근을 수용할 수 있는 수집 adapter 경계
|
||||
- 매일 갱신 가능한 collection job과 실패/누락 재시도 흐름
|
||||
- 백테스트에 필요한 정제 데이터만 DB에 남기는 persistence 기준
|
||||
- headless CLI/log/YAML 또는 동등한 운영 경로에서 freshness와 readiness를 확인하는 표면
|
||||
- 저장된 데이터에서 재현 가능한 backtest input range를 선택하는 준비 경계
|
||||
|
||||
## 기능
|
||||
|
||||
### Epic: [universe] Selected universe collection
|
||||
|
||||
사용자가 지정한 종목군을 수집 대상으로 삼고, 매일 반복 가능한 수집 단위로 관리한다.
|
||||
|
||||
- [ ] [watchlist-config] 선택 종목군을 수집 대상으로 등록하고 갱신할 수 있는 headless 설정 경계를 만든다.
|
||||
- [x] [daily-collect] 선택 종목군에 대해 일별 수집 job을 실행하고 재실행할 수 있는 worker/CLI 경계를 만든다.
|
||||
- [ ] [freshness-status] 수집 성공, 실패, 누락, 최신 일자를 operator가 확인할 수 있는 상태 출력을 만든다.
|
||||
|
||||
### Epic: [dataset] Backtest-ready dataset
|
||||
|
||||
수집한 원천 데이터를 백테스트 입력으로 사용할 수 있는 정제 데이터로 저장한다.
|
||||
|
||||
- [ ] [provider-boundary] KIS/KRX/DART/US provider 등 확장 가능한 data source adapter 경계를 정리한다.
|
||||
- [ ] [normalized-store] 백테스트에 필요한 정제 필드만 DB에 저장하고 원천 payload 전체 저장은 기본값에서 제외한다.
|
||||
- [ ] [gap-handling] 결측, 휴장일, 중복, provider 지연을 백테스트 입력에서 식별할 수 있게 한다.
|
||||
|
||||
### Epic: [backtest-ready] Repeatable execution input
|
||||
|
||||
정제 데이터가 실제 백테스트 실행 입력으로 반복 사용될 수 있게 연결한다.
|
||||
|
||||
- [ ] [input-selector] 저장된 정제 데이터에서 종목군, 기간, frequency를 선택해 backtest run input으로 넘기는 경계를 만든다.
|
||||
- [ ] [headless-smoke] 샘플 종목군 수집부터 기본 backtest 실행까지 headless smoke로 검증한다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
||||
- 상태: 없음
|
||||
- 요청일: 없음
|
||||
- 완료 근거: 일부 기능 Task만 완료되었으며 전체 마일스톤 완료 조건은 아직 충족되지 않았다.
|
||||
- 리뷰 필요:
|
||||
- [ ] 사용자가 완료 결과를 확인했다
|
||||
- [ ] archive 이동을 승인했다
|
||||
- 리뷰 코멘트: 없음
|
||||
|
||||
## 범위 제외
|
||||
|
||||
- 에이전트 도입, 에이전트 skill 설계, agent runtime 연결
|
||||
- 주식 분석/전략 판단 로직 자체의 설계
|
||||
- Flutter 운영 화면 구현
|
||||
- paper/live order routing과 실거래 execution
|
||||
- 외부 provider credential 값이나 개인 secret 문서화
|
||||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `services/worker/`, `apps/cli/`, `packages/domain/`, `packages/contracts/`, `deployments/local/`
|
||||
- 표준선(선택): 백테스트 인프라는 먼저 화면 없이 CLI, terminal output, YAML scenario, log, test fixture로 검증한다.
|
||||
- 표준선(선택): 데이터 접근 범위는 좁히지 않고 확장 가능한 adapter 경계로 열어두되, DB에는 백테스트와 운영 판단에 필요한 정제 데이터만 남긴다.
|
||||
- 표준선(선택): agent는 이 마일스톤의 구현 수단이나 주식 분석 코어가 아니다.
|
||||
- 완료 근거(daily-collect): `import_daily_bars`가 API -> worker -> importer로 전달되고 CLI scenario에서 실행/재실행 가능한 경계가 이미 구현되어 있으며, 2026-06-14에 `go test ./packages/domain/... ./services/worker/internal/jobs ./services/worker/internal/marketdata/importer ./services/worker/internal/socket ./services/api/internal/socket ./services/api/internal/workerclient ./apps/cli/internal/operator`가 통과했다.
|
||||
- 선행 작업: Backtest Engine Baseline, Backtest Analysis Surface
|
||||
- 후속 작업: Backtest Scenario Automation, Data Quality Monitoring, Agent Tooling Bridge 후보
|
||||
- 확인 필요: 없음
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
# Milestone: Finance Agent Research Sidecar Sketch
|
||||
|
||||
## 위치
|
||||
|
||||
- Roadmap: `agent-roadmap/ROADMAP.md`
|
||||
- Phase: `agent-roadmap/phase/backtest-loop/PHASE.md`
|
||||
|
||||
## 목표
|
||||
|
||||
Anthropic finance agents 계열 skillset을 ALT에 즉시 도입하지 않고, 리서치 메모, 전략 가설, 백테스트 결과 해석을 돕는 sidecar 후보로 검토한다. ALT의 Go worker, protobuf contract, Flutter operator surface는 deterministic core로 유지하고, finance agent는 수치 출처와 사람 검토가 필요한 보조 산출물 생성 경계로만 다룬다.
|
||||
|
||||
## 상태
|
||||
|
||||
[스케치]
|
||||
|
||||
## 승격 조건
|
||||
|
||||
- [ ] 첫 적용 목적을 strategy hypothesis note, backtest commentary, universe idea generation, earnings review 중 하나로 좁힌다.
|
||||
- [ ] 실행 위치를 개인 Claude plugin, 로컬 agent-ops skill, Claude Managed Agent/API sidecar, 또는 미도입 중 하나로 결정한다.
|
||||
- [ ] 데이터/커넥터 경계를 KIS/KRX/DART/US provider, read-only MCP, 로컬 secret, cloud retention 기준으로 정리한다.
|
||||
- [ ] research artifact가 instrument, watchlist, strategy, backtest run 중 무엇에 연결되는지 저장/계약 후보를 정한다.
|
||||
- [ ] 수치 출처, 미출처 표시, untrusted document 처리, human sign-off, 자동 주문 금지 원칙을 완료 기준으로 승격할 수 있게 정리한다.
|
||||
- [ ] 후속 구현 Milestone 후보를 artifact model, worker sidecar, operator display, US earnings workflow 중 필요한 단위로 나눈다.
|
||||
|
||||
## 구현 잠금
|
||||
|
||||
- 상태: 잠금
|
||||
- 결정 필요: 아래 체크리스트
|
||||
- [ ] ALT에서 finance agent 보조층의 첫 사용 사례를 무엇으로 둘지 결정한다.
|
||||
- [ ] Claude/Cowork plugin처럼 개인 도구로만 쓸지, ALT worker/API 경계와 연결할지 결정한다.
|
||||
- [ ] 외부 MCP/data provider 구독, API key, 데이터 보존 위치를 허용할지 결정한다.
|
||||
- [ ] agent 산출물을 ALT 저장소에 남길지, 임시 operator note로만 둘지 결정한다.
|
||||
|
||||
## 범위
|
||||
|
||||
- Anthropic finance agents 발표와 marketplace skillset의 ALT 적용 후보 정리
|
||||
- `market-researcher`, `earnings-reviewer`, `model-builder`, `financial-analysis` 계열 중 ALT와 맞는 subset 선별
|
||||
- research artifact, strategy hypothesis, backtest commentary 후보 산출물 정의
|
||||
- worker/API/client core와 분리되는 sidecar 책임 경계 정리
|
||||
- compliance, source citation, human review, no-trade-execution guardrail 정리
|
||||
- `[계획]` 승격 시 필요한 후속 구현 Milestone 후보 작성
|
||||
|
||||
## 필수 기능
|
||||
|
||||
### Epic: [agent-fit] Finance agent fit review
|
||||
|
||||
ALT의 개인 quant workspace 맥락에서 finance agent skillset의 도입 목적과 범위 적합성을 정리한다.
|
||||
|
||||
- [ ] [skill-subset] ALT에 맞는 skillset과 맞지 않는 skillset을 분리한다.
|
||||
- [ ] [use-case] 첫 적용 후보와 보류 후보를 구분한다.
|
||||
- [ ] [guardrails] 투자 조언, 자동 주문, 출처 없는 수치, untrusted document 실행을 막는 원칙을 정리한다.
|
||||
|
||||
### Epic: [sidecar-boundary] Research sidecar boundary
|
||||
|
||||
finance agent를 ALT core가 아니라 보조 분석 layer로 둘 때 필요한 런타임, 데이터, 산출물 경계를 정리한다.
|
||||
|
||||
- [ ] [runtime-options] 개인 plugin, 로컬 skill, Managed Agent/API sidecar 선택지를 비교한다.
|
||||
- [ ] [data-boundary] 외부 data provider와 local secret, read-only connector 기준을 정리한다.
|
||||
- [ ] [artifact-shape] research artifact가 instrument, strategy, backtest run과 연결되는 최소 shape를 제안한다.
|
||||
- [ ] [handoff-plan] Backtest Analysis Surface, Flutter Operator Console, US Market Expansion 중 어느 후속 흐름과 연결할지 제안한다.
|
||||
|
||||
## 완료 기준
|
||||
|
||||
- [ ] finance agent 도입, 보류, 미도입을 판단할 기준이 문서화되어 있다.
|
||||
- [ ] `[계획]` 승격을 위한 첫 사용 사례, runtime 위치, data boundary, review guardrail이 정리되어 있다.
|
||||
- [ ] ALT core에 직접 agent runtime을 넣지 않는 범위 제외가 명확하다.
|
||||
- [ ] 후속 구현 Milestone 후보가 필요 단위로 분리되어 있다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
||||
- 상태: 없음
|
||||
- 요청일: 없음
|
||||
- 완료 근거: 아직 스케치 상태이며 승격 조건과 완료 기준이 충족되지 않았다.
|
||||
- 리뷰 필요:
|
||||
- [ ] 사용자가 완료 결과를 확인했다
|
||||
- [ ] archive 이동을 승인했다
|
||||
- 리뷰 코멘트: 없음
|
||||
|
||||
## 범위 제외
|
||||
|
||||
- Claude plugin 설치 또는 Managed Agent 배포
|
||||
- 외부 MCP/data provider credential 설정
|
||||
- 실제 리서치 노트, Excel 모델, 투자 메모 생성
|
||||
- 자동 투자 판단, 주문 제출, 실거래 execution
|
||||
- `packages/contracts`, `services/api`, `services/worker`, `apps/client` 코드 변경
|
||||
|
||||
## 작업 컨텍스트
|
||||
|
||||
- 관련 경로: `agent-roadmap/phase/backtest-loop/`, `services/worker/`, `packages/contracts/`, `apps/client/`
|
||||
- 표준선(선택): ALT core는 deterministic market data, backtest, operator contract를 우선하고 finance agent는 출처 검증과 사람 검토가 필요한 sidecar로 둔다.
|
||||
- 표준선(선택): Anthropic finance agents의 유효성은 analyst workflow reference로만 보고, quant strategy 수익성이나 실거래 안정성의 근거로 사용하지 않는다.
|
||||
- 표준선(선택): 모든 agent 산출물은 투자 조언이나 주문 지시가 아니라 검토 대기 draft로 취급한다.
|
||||
- 선행 작업: Backtest Analysis Surface
|
||||
- 후속 작업: Research Artifact Model, Finance Agent Worker Sidecar, Operator Research Notes, US Earnings Workflow 후보
|
||||
- 확인 필요:
|
||||
- 첫 사용 사례 우선순위
|
||||
- 실행 위치와 외부 provider 허용 범위
|
||||
- 산출물 저장 여부와 human review 방식
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
<!-- task=m-backtest-data-collection-infrastructure/01_watchlist_config plan=0 tag=CLI -->
|
||||
|
||||
# Code Review Reference - CLI
|
||||
|
||||
> **[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`.
|
||||
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-06-14
|
||||
task=m-backtest-data-collection-infrastructure/01_watchlist_config, plan=0, tag=CLI
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md`
|
||||
- Task ids:
|
||||
- `watchlist-config`: 선택 종목군을 수집 대상으로 등록하고 갱신할 수 있는 headless 설정 경계를 만든다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-local-G05.md` -> `code_review_local_G05_N.log`, `PLAN-local-G05.md` -> `plan_local_G05_M.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-backtest-data-collection-infrastructure/01_watchlist_config/`로 이동한다.
|
||||
4. PASS이고 task group이 `m-backtest-data-collection-infrastructure`이면 완료 이벤트 메타데이터를 보고한다. roadmap 수정이나 `update-roadmap` 직접 호출은 런타임 책임이다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [CLI-1] Named Universe Schema | [x] |
|
||||
| [CLI-2] Scenario Fixtures Use Named Universes | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] Scenario에 named universe 설정 타입과 `request.universe` 참조를 추가하고 validation/materialization을 구현한다.
|
||||
- [x] KR/US market data scenario fixture가 inline symbols 대신 named universe를 사용하도록 갱신하고 runner 테스트로 API 전달 값을 고정한다.
|
||||
- [x] `go test ./apps/cli/internal/operator ./apps/cli/internal/cli`를 실행한다.
|
||||
- [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_{review_lane}_GNN_N.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다.
|
||||
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
|
||||
- [ ] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [ ] PASS이면 active task 디렉터리를 archive로 이동한다.
|
||||
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고한다.
|
||||
- [x] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.md`를 작성하고 `complete.log`를 작성하지 않는다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
expected jsonl 출력(테스트 통과를 위해 확인한 결과)에 Request 구조체의 신규 필드인 `universe`가 marshal되어 들어가지 않으므로, `expected/*.jsonl` 파일들에 변경이 불필요하였습니다. 그 외에는 계획과 완전히 일치하게 구현하였습니다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
1. `Scenario.Validate` 메서드의 시작 부분에서 named universes의 중복 여부 및 필수 필드(Name, Provider, SelectorKind, Symbols) 검증을 추가하여 조기에 에러를 반환할 수 있도록 하였습니다.
|
||||
2. `applyUniverseDefaults` 헬퍼 함수를 통해 step validation 이전에 `request.universe` 참조를 해석하고 필요한 필드들(provider, selector_kind, market, venue, name, symbols)을 materialize해주었습니다. 이를 통해 기존의 `validateRequest` 등 세부 필드 검증 로직을 전혀 수정하지 않고 재사용할 수 있었습니다.
|
||||
3. `request.universe` 참조와 inline 설정 필드가 공존하는 경우 conflict 에러를 반환하게 하여 잘못된 설정을 방지하였습니다.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- named universe validation이 inline import validation을 약화하지 않았는지 확인한다.
|
||||
- universe 참조가 loaded scenario 안에서 materialize되어 기존 runner/API request 경로를 그대로 쓰는지 확인한다.
|
||||
- JSONL expected output이 불필요하게 바뀌지 않았는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### CLI-1 중간 검증
|
||||
|
||||
```bash
|
||||
$ go test ./apps/cli/internal/operator
|
||||
=== RUN TestValidateImportDailyBarsUniverseReference
|
||||
--- PASS: TestValidateImportDailyBarsUniverseReference (0.00s)
|
||||
=== RUN TestValidateRejectsDuplicateUniverseName
|
||||
--- PASS: TestValidateRejectsDuplicateUniverseName (0.00s)
|
||||
=== RUN TestValidateRejectsMissingUniverseReference
|
||||
--- PASS: TestValidateRejectsMissingUniverseReference (0.00s)
|
||||
=== RUN TestValidateRejectsUniverseInlineConflict
|
||||
--- PASS: TestValidateRejectsUniverseInlineConflict (0.00s)
|
||||
PASS
|
||||
ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.199s
|
||||
```
|
||||
|
||||
### CLI-2 중간 검증
|
||||
|
||||
```bash
|
||||
$ go test ./apps/cli/internal/operator
|
||||
ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.193s
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```bash
|
||||
$ go test ./apps/cli/internal/operator ./apps/cli/internal/cli
|
||||
ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.180s
|
||||
ok git.toki-labs.com/toki/alt/apps/cli/internal/cli 0.007s
|
||||
```
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
### 2026-06-14 local-G05
|
||||
|
||||
- 종합 판정: FAIL
|
||||
|
||||
#### 차원별 평가
|
||||
|
||||
| 차원 | 평가 | 근거 |
|
||||
|------|------|------|
|
||||
| correctness | Fail | `Validate()`가 universe materialization 뒤에도 `Request.Universe`를 남겨 재호출 시 자체 materialized inline fields를 conflict로 오인한다. |
|
||||
| completeness | Fail | 계획된 `runner_market_test.go` fixture materialization/API request assertion 보강이 누락되었다. |
|
||||
| test coverage | Fail | fixture 기반 runner 경로가 `market`, `venue`, `name`, `symbols` 전달을 고정하지 못한다. |
|
||||
| API contract | Pass | 현재 runner request 매핑 자체는 materialized request 필드를 protobuf 요청으로 전달한다. |
|
||||
| code quality | Pass | 범위 밖 debug print, dead code, formatting drift는 발견하지 못했다. |
|
||||
| plan deviation | Fail | 계획의 CLI-2 수정 파일/테스트 보강 항목 중 `runner_market_test.go`가 실제 변경되지 않았다. |
|
||||
| verification trust | Pass | `go test ./apps/cli/internal/operator ./apps/cli/internal/cli`를 리뷰 중 재실행했고 통과했다. |
|
||||
|
||||
#### 발견된 문제
|
||||
|
||||
- Required: `apps/cli/internal/operator/scenario.go:467`에서 inline conflict를 검사한 뒤 `apps/cli/internal/operator/scenario.go:477`부터 universe 값을 `Step.Request`에 채우지만 `Request.Universe`를 그대로 남긴다. 그 결과 `ParseScenario`로 이미 materialize된 scenario에 `Validate()`를 다시 호출하면, 사용자가 inline field를 쓰지 않았는데도 `provider`, `selector_kind`, `market`, `venue`, `name`, `symbols`가 채워져 있어 conflict error가 난다. `Validate()`가 재호출되어도 성공하도록 materialization을 idempotent하게 만들고, universe-backed scenario를 `ParseScenario`한 뒤 `sc.Validate()`가 다시 통과하는 테스트를 추가한다.
|
||||
- Required: `apps/cli/internal/operator/runner_market_test.go:143`의 fixture 기반 API request assertion이 `provider`와 날짜만 확인한다. 계획은 `agent-task/m-backtest-data-collection-infrastructure/01_watchlist_config/PLAN-local-G05.md:223`에서 loaded fixture가 기존 API request와 동일한 `provider/venue/symbols`를 보내도록 `runner_market_test.go`를 보강하라고 했고, `scenario.go`는 `Name`까지 materialize한다. `TestRunScenarioMarketDataStatusImportsBeforeReads` 또는 별도 fixture runner test에서 `selector_kind`, `market`, `venue`, `name`, `symbols`까지 assert해 named universe materialization이 API request까지 보존되는지 고정한다.
|
||||
|
||||
#### 다음 단계
|
||||
|
||||
- FAIL 후속 `PLAN-local-G06.md`와 `CODE_REVIEW-local-G06.md`를 작성한다.
|
||||
|
|
@ -0,0 +1,163 @@
|
|||
<!-- task=m-backtest-data-collection-infrastructure/01_watchlist_config plan=1 tag=REVIEW_CLI -->
|
||||
|
||||
# Code Review Reference - REVIEW_CLI
|
||||
|
||||
> **[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.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-06-14
|
||||
task=m-backtest-data-collection-infrastructure/01_watchlist_config, plan=1, tag=REVIEW_CLI
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md`
|
||||
- Task ids:
|
||||
- `watchlist-config`: 선택 종목군을 수집 대상으로 등록하고 갱신할 수 있는 headless 설정 경계를 만든다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-local-G06.md` -> `code_review_local_G06_N.log`, `PLAN-local-G06.md` -> `plan_local_G06_M.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-backtest-data-collection-infrastructure/01_watchlist_config/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
|
||||
4. PASS이고 task group이 `m-backtest-data-collection-infrastructure`이면 완료 이벤트 메타데이터를 보고한다. roadmap 수정이나 `update-roadmap` 직접 호출은 런타임 책임이다.
|
||||
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [REVIEW_CLI-1] Idempotent Universe Materialization | [x] |
|
||||
| [REVIEW_CLI-2] Runner Fixture Request Assertions | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] `Scenario.Validate()`의 named universe materialization을 재호출해도 실패하지 않도록 idempotent하게 고친다.
|
||||
- [x] loaded KR/US fixture runner 테스트가 materialized import request의 `selector_kind`, `market`, `venue`, `name`, `symbols`까지 API 요청으로 전달되는지 검증하게 보강한다.
|
||||
- [x] `go test ./apps/cli/internal/operator ./apps/cli/internal/cli`를 실행한다.
|
||||
- [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_{review_lane}_GNN_N.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_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/{task_name}/`를 `agent-task/archive/YYYY/MM/{task_name}/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [x] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/{task_group}/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다. `agent-task/m-backtest-data-collection-infrastructure/02+01_freshness_status/`가 남아 parent를 유지했다.
|
||||
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-{build_lane}-GNN.md`와 `CODE_REVIEW-{review_lane}-GNN.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로 이동한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
계획에서 요구하는 사항들과 완벽히 조화롭게 수행되었으며, 임의로 설계를 변경하거나 대체 검증 명령을 사용하지 않았습니다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
1. **Idempotence 보장**: `applyUniverseDefaults` 함수에서 materialization 적용 완료 후에 `step.Request.Universe`의 값을 빈 문자열(`""`)로 설정하도록 보장하였습니다. 이로 인해 동일한 시나리오 상에서 `Validate()` 메서드가 두 번 이상 재호출되더라도 materialized 필드들로 인해 inline conflict 판정이 발생하지 않도록 멱등성을 구축하였습니다.
|
||||
2. **US/KR runner assertions 확장**: `runner_market_test.go` 내부 테스트들(`TestRunScenarioMarketDataStatusUS` 및 `TestRunScenarioMarketDataStatusImportsBeforeReads`)에서 mock API 서버가 수신한 `ImportDailyBarsRequest` 메시지들의 `selector_kind`, `market`, `venue`, `name`, `symbols` 등 주요 구체화 필드가 client runner 수준까지 온전히 전파되고 전달되는지 엄격하게 검증하도록 assert 로직을 확장 보완하였습니다.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `Validate()`가 universe-backed scenario를 materialize한 뒤 재호출되어도 false conflict를 만들지 않는지 확인한다.
|
||||
- universe와 inline import field가 같이 작성된 YAML은 계속 validation error인지 확인한다.
|
||||
- loaded KR/US fixtures가 API request의 selector_kind, market, venue, name, symbols까지 보존하는지 확인한다.
|
||||
- 변경이 `apps/cli/internal/operator` 범위를 벗어나지 않았는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### REVIEW_CLI-1 중간 검증
|
||||
|
||||
```bash
|
||||
$ go test ./apps/cli/internal/operator
|
||||
=== RUN TestValidateImportDailyBarsUniverseReference
|
||||
--- PASS: TestValidateImportDailyBarsUniverseReference (0.00s)
|
||||
=== RUN TestValidateRejectsDuplicateUniverseName
|
||||
--- PASS: TestValidateRejectsDuplicateUniverseName (0.00s)
|
||||
=== RUN TestValidateRejectsMissingUniverseReference
|
||||
--- PASS: TestValidateRejectsMissingUniverseReference (0.00s)
|
||||
=== RUN TestValidateRejectsUniverseInlineConflict
|
||||
--- PASS: TestValidateRejectsUniverseInlineConflict (0.00s)
|
||||
=== RUN TestValidateImportDailyBarsUniverseReferenceIsIdempotent
|
||||
--- PASS: TestValidateImportDailyBarsUniverseReferenceIsIdempotent (0.00s)
|
||||
PASS
|
||||
ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.232s
|
||||
```
|
||||
|
||||
### REVIEW_CLI-2 중간 검증
|
||||
|
||||
```bash
|
||||
$ go test ./apps/cli/internal/operator
|
||||
ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.181s
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```bash
|
||||
$ go test ./apps/cli/internal/operator ./apps/cli/internal/cli
|
||||
ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.181s
|
||||
ok git.toki-labs.com/toki/alt/apps/cli/internal/cli 0.005s
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT - BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
> If anything is blank, go back and fill it in before saving this file.
|
||||
> Leave review-agent-only sections unchanged.
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
### 2026-06-14 local-G06
|
||||
|
||||
- 종합 판정: PASS
|
||||
|
||||
#### 차원별 평가
|
||||
|
||||
| 차원 | 평가 | 근거 |
|
||||
|------|------|------|
|
||||
| correctness | Pass | `applyUniverseDefaults`가 성공 materialization 뒤 `Request.Universe`를 clear해 `Validate()` 재호출 false conflict가 해소되었다. |
|
||||
| completeness | Pass | G05 Required 두 건인 validation idempotence와 runner fixture request assertion 보강을 모두 반영했다. |
|
||||
| test coverage | Pass | idempotence regression test와 KR/US loaded fixture API request assertions가 추가되었다. |
|
||||
| API contract | Pass | runner는 materialized `Request` 필드를 기존 `ImportDailyBarsRequest` contract로 전달하며 contract/schema 변경은 없다. |
|
||||
| code quality | Pass | debug print, dead code, unrelated source change는 발견하지 못했다. |
|
||||
| plan deviation | Pass | 후속 계획 범위 안에서만 변경되었고 대체 검증 없이 계획된 명령을 실행했다. |
|
||||
| verification trust | Pass | 리뷰 중 `go test ./apps/cli/internal/operator ./apps/cli/internal/cli`를 재실행했고 통과했다. |
|
||||
|
||||
#### 발견된 문제
|
||||
|
||||
없음
|
||||
|
||||
#### 다음 단계
|
||||
|
||||
- PASS 종결: active plan/review를 log로 아카이브하고 `complete.log` 작성 후 task directory를 archive로 이동한다.
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
# Complete - m-backtest-data-collection-infrastructure/01_watchlist_config
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-06-14
|
||||
|
||||
## 요약
|
||||
|
||||
Watchlist named universe 설정 경계를 2회 리뷰 루프로 완료했으며 최종 판정은 PASS다.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_local_G05_0.log` | `code_review_local_G05_0.log` | FAIL | `Validate()` 재호출 false conflict와 runner fixture API request assertion 공백으로 follow-up 필요 |
|
||||
| `plan_local_G06_1.log` | `code_review_local_G06_1.log` | PASS | universe materialization idempotence와 KR/US fixture request assertion 보강이 통과 |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- CLI scenario schema에 top-level named universe 설정과 `request.universe` 참조를 추가했다.
|
||||
- `import_daily_bars` step validation 전에 named universe를 provider/selector/market/venue/name/symbols request fields로 materialize한다.
|
||||
- successful materialization 후 `Request.Universe`를 clear해 `Scenario.Validate()` 재호출이 false conflict를 만들지 않도록 했다.
|
||||
- KR/US/smoke market data fixtures가 inline watchlist 대신 named universe를 참조하도록 갱신했다.
|
||||
- scenario validation tests와 runner fixture API request assertions로 materialized fields가 기존 protobuf request 경계까지 보존되는지 고정했다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `go test ./apps/cli/internal/operator ./apps/cli/internal/cli` - PASS; `ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.189s`, `ok git.toki-labs.com/toki/alt/apps/cli/internal/cli 0.006s`
|
||||
|
||||
## Roadmap Completion
|
||||
|
||||
- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md`
|
||||
- Completed task ids:
|
||||
- `watchlist-config`: PASS; evidence=`agent-task/archive/2026/06/m-backtest-data-collection-infrastructure/01_watchlist_config/plan_local_G06_1.log`, `agent-task/archive/2026/06/m-backtest-data-collection-infrastructure/01_watchlist_config/code_review_local_G06_1.log`; verification=`go test ./apps/cli/internal/operator ./apps/cli/internal/cli`
|
||||
- Not completed task ids: 없음
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,258 @@
|
|||
<!-- task=m-backtest-data-collection-infrastructure/01_watchlist_config plan=0 tag=CLI -->
|
||||
|
||||
# Plan - CLI Watchlist Config
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성은 필수다. 구현 후 검증을 실행하고 실제 stdout/stderr를 붙인 뒤 active 파일을 그대로 둔 채 리뷰 준비 상태로 보고한다. 종결, log rename, `complete.log`, archive 이동은 code-review 전용이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 범위 충돌이 필요하면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 기록하고 멈춘다. 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 증거 공백은 사용자 리뷰 요청이 아니다.
|
||||
|
||||
## 배경
|
||||
|
||||
`universe` Epic의 `daily-collect`는 기존 `import_daily_bars` 경계로 완료 처리되었다. 남은 첫 병목은 선택 종목군이 각 scenario step의 inline symbols로 흩어져 있어 등록/갱신 가능한 headless 설정 경계가 없다는 점이다. 이 작업은 CLI scenario YAML 안에 named universe 설정을 추가하고, import step이 그 설정을 참조해 동일한 provider/selector/market/venue/symbols를 재사용하게 만든다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
|
||||
구현 중 사용자 전용 차단이 생기면 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 구현 에이전트는 채팅에서 직접 질문하지 않고, code-review가 `USER_REVIEW.md` 작성 여부를 판단한다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md`
|
||||
- Task ids:
|
||||
- `watchlist-config`: 선택 종목군을 수집 대상으로 등록하고 갱신할 수 있는 headless 설정 경계를 만든다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-roadmap/current.md`
|
||||
- `agent-roadmap/phase/backtest-loop/PHASE.md`
|
||||
- `agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md`
|
||||
- `agent-ops/rules/project/rules.md`
|
||||
- `agent-ops/rules/project/domain/operations/rules.md`
|
||||
- `agent-ops/rules/project/domain/domain-model/rules.md`
|
||||
- `agent-ops/rules/project/domain/worker/rules.md`
|
||||
- `agent-ops/rules/project/domain/api/rules.md`
|
||||
- `agent-ops/rules/project/domain/contracts/rules.md`
|
||||
- `agent-test/local/rules.md`
|
||||
- `agent-test/local/operations-smoke.md`
|
||||
- `agent-test/local/domain-model-smoke.md`
|
||||
- `agent-test/local/worker-smoke.md`
|
||||
- `agent-test/local/api-smoke.md`
|
||||
- `agent-test/local/contracts-smoke.md`
|
||||
- `apps/cli/go.mod`
|
||||
- `apps/cli/internal/operator/scenario.go`
|
||||
- `apps/cli/internal/operator/scenario_test.go`
|
||||
- `apps/cli/internal/operator/runner.go`
|
||||
- `apps/cli/internal/operator/runner_market_test.go`
|
||||
- `apps/cli/internal/operator/client.go`
|
||||
- `apps/cli/internal/operator/client_test.go`
|
||||
- `apps/cli/internal/operator/output.go`
|
||||
- `apps/cli/internal/operator/parser_map.go`
|
||||
- `apps/cli/internal/operator/parser_map_test.go`
|
||||
- `apps/cli/internal/cli/cli.go`
|
||||
- `apps/cli/internal/cli/cli_test.go`
|
||||
- `apps/cli/testdata/operator/market_data_status_query.yaml`
|
||||
- `apps/cli/testdata/operator/us_market_data_status_query.yaml`
|
||||
- `apps/cli/testdata/operator/kis_daily_import_smoke.yaml`
|
||||
- `apps/cli/testdata/operator/expected/market_data_status_query.jsonl`
|
||||
- `apps/cli/testdata/operator/expected/us_market_data_status_query.jsonl`
|
||||
- `apps/cli/testdata/operator/expected/kis_daily_import_smoke.jsonl`
|
||||
- `apps/cli/testdata/operator/headless_validation.md`
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
- `test_env=local`.
|
||||
- `agent-test/local/rules.md`를 읽었다. 여러 도메인 후보 중 이 plan의 실제 변경은 `apps/cli/**`라 `operations-smoke`를 적용한다.
|
||||
- 읽은 profile: `agent-test/local/operations-smoke.md`. 관련 경계 확인을 위해 `domain-model-smoke`, `worker-smoke`, `api-smoke`, `contracts-smoke`도 읽었지만 이 plan은 해당 source를 변경하지 않는다.
|
||||
- 적용 명령: `go test ./apps/cli/internal/operator ./apps/cli/internal/cli`.
|
||||
- `<확인 필요>` 값 없음. 새 외부 dependency 없음. `apps/cli/go.mod`에 `gopkg.in/yaml.v3`가 이미 있다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- 기존 `scenario_test.go`는 inline `import_daily_bars` 필수 필드와 날짜 검증을 덮지만 named universe 참조를 덮지 않는다.
|
||||
- 기존 `runner_market_test.go`는 inline request가 API로 전달되는지 덮지만 universe 참조가 materialized 되는지는 덮지 않는다.
|
||||
- 이 plan에서 named universe parsing, duplicate name rejection, missing reference rejection, import request materialization 테스트를 추가한다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- renamed/removed symbol 없음. 새 struct/field/action helper만 추가한다.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
- split 정책을 먼저 평가했다. 공유 task group은 `agent-task/m-backtest-data-collection-infrastructure/`다.
|
||||
- `01_watchlist_config`: 독립 선행 작업. predecessor 없음.
|
||||
- `02+01_freshness_status`: `01_watchlist_config`의 named universe 설정을 전제로 구현한다.
|
||||
- 이 plan은 CLI scenario schema와 fixture 변경만 다루므로 단일 plan이 적절하다. worker/API/contracts 변경은 split gate에 해당하지만 이 plan 범위에서 제외한다.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- protobuf schema와 generated contract는 변경하지 않는다. 기존 `ImportDailyBarsRequest` 필드에 맞춰 scenario request를 materialize한다.
|
||||
- API/worker/importer는 변경하지 않는다. `daily-collect` 경계는 이미 완료 처리되었고, 이 작업은 headless 설정 경계만 만든다.
|
||||
- Flutter 화면, provider live credential, scheduling loop는 범위 밖이다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
- `local-G05`: CLI schema/fixture/test 변경으로 범위가 명확하고 검증이 빠르며, 새 storage/protocol 위험이 없다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] Scenario에 named universe 설정 타입과 `request.universe` 참조를 추가하고 validation/materialization을 구현한다.
|
||||
- [ ] KR/US market data scenario fixture가 inline symbols 대신 named universe를 사용하도록 갱신하고 runner 테스트로 API 전달 값을 고정한다.
|
||||
- [ ] `go test ./apps/cli/internal/operator ./apps/cli/internal/cli`를 실행한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [CLI-1] Named Universe Schema
|
||||
|
||||
#### 문제
|
||||
|
||||
`apps/cli/internal/operator/scenario.go:241`의 `Request`는 import step마다 provider/selector/symbols를 직접 들고 있고, `apps/cli/internal/operator/scenario.go:330`의 `Scenario`에는 shared universe 설정 공간이 없다. `apps/cli/internal/operator/scenario.go:597`의 import validation도 inline fields만 허용한다.
|
||||
|
||||
```go
|
||||
// apps/cli/internal/operator/scenario.go:241
|
||||
type Request struct {
|
||||
// Market filters a list_instruments query ("", "kr", "us", "unspecified").
|
||||
Market string `yaml:"market"`
|
||||
...
|
||||
// Symbols is the explicit list of tickers/symbols to import.
|
||||
Symbols []string `yaml:"symbols"`
|
||||
```
|
||||
|
||||
```go
|
||||
// apps/cli/internal/operator/scenario.go:330
|
||||
type Scenario struct {
|
||||
Name string `yaml:"name"`
|
||||
Timeout Duration `yaml:"timeout"`
|
||||
Steps []Step `yaml:"steps"`
|
||||
}
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`UniverseConfig`와 `Scenario.Universes`를 추가하고 `Request.Universe`를 추가한다. `Scenario.Validate`는 먼저 universe name 중복/필수 필드를 검증한 뒤, `import_daily_bars` step이 `request.universe`를 지정하면 inline request에 provider/selector_kind/market/venue/name/symbols를 materialize한다. 날짜 범위는 step별 실행 창이므로 request에 남긴다. inline 필드와 universe 필드가 충돌하면 validation error로 처리한다.
|
||||
|
||||
```go
|
||||
type UniverseConfig struct {
|
||||
Name string `yaml:"name"`
|
||||
Provider string `yaml:"provider"`
|
||||
SelectorKind string `yaml:"selector_kind"`
|
||||
Market string `yaml:"market"`
|
||||
Venue string `yaml:"venue"`
|
||||
Symbols []string `yaml:"symbols"`
|
||||
}
|
||||
|
||||
type Request struct {
|
||||
Universe string `yaml:"universe"`
|
||||
...
|
||||
}
|
||||
|
||||
type Scenario struct {
|
||||
Name string `yaml:"name"`
|
||||
Timeout Duration `yaml:"timeout"`
|
||||
Universes []UniverseConfig `yaml:"universes"`
|
||||
Steps []Step `yaml:"steps"`
|
||||
}
|
||||
```
|
||||
|
||||
`Validate` loop는 `for i := range s.Steps`로 바꾸고 `applyUniverseDefaults(&s.Steps[i], universeByName)`를 호출한 다음 기존 `validateRequest`를 재사용한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/cli/internal/operator/scenario.go`: `UniverseConfig`, `Request.Universe`, universe index/validation/materialization helper 추가.
|
||||
- [ ] `apps/cli/internal/operator/scenario_test.go`: valid universe import, duplicate universe name, missing universe reference, inline conflict, materialized request assertions 추가.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- `TestValidateImportDailyBarsUniverseReference`: named universe를 참조한 import step이 provider/selector/market/venue/symbols를 채우는지 확인.
|
||||
- `TestValidateRejectsDuplicateUniverseName`: 같은 name 두 번 선언 시 실패.
|
||||
- `TestValidateRejectsMissingUniverseReference`: 없는 universe 참조 시 실패.
|
||||
- `TestValidateRejectsUniverseInlineConflict`: universe 참조와 inline symbols/provider 등이 같이 있으면 실패.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test ./apps/cli/internal/operator
|
||||
```
|
||||
|
||||
기대 결과: exit code 0.
|
||||
|
||||
### [CLI-2] Scenario Fixtures Use Named Universes
|
||||
|
||||
#### 문제
|
||||
|
||||
대표 market data fixtures는 모두 inline watchlist를 반복한다. 예: `apps/cli/testdata/operator/market_data_status_query.yaml:17`의 import step은 provider/selector/symbols를 직접 선언한다.
|
||||
|
||||
```yaml
|
||||
# apps/cli/testdata/operator/market_data_status_query.yaml:17
|
||||
- id: import_kr_daily_bars
|
||||
action: import_daily_bars
|
||||
request:
|
||||
provider: kis
|
||||
selector_kind: watchlist
|
||||
market: kr
|
||||
venue: krx
|
||||
symbols: ["005930"]
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
KR/US fixtures에 top-level `universes`를 추가하고 import step은 `request.universe`를 참조하게 바꾼다. runner output은 기존과 동일해야 한다. `runner_market_test.go`의 existing fake API assertions를 유지하면서 loaded scenario가 materialized request를 보내는지 확인한다.
|
||||
|
||||
```yaml
|
||||
universes:
|
||||
- name: kr-core
|
||||
provider: kis
|
||||
selector_kind: watchlist
|
||||
market: kr
|
||||
venue: krx
|
||||
symbols: ["005930"]
|
||||
steps:
|
||||
- id: import_kr_daily_bars
|
||||
action: import_daily_bars
|
||||
request:
|
||||
universe: kr-core
|
||||
from_yyyymmdd: "20240527"
|
||||
to_yyyymmdd: "20240528"
|
||||
```
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/cli/testdata/operator/market_data_status_query.yaml`: KR universe 추가.
|
||||
- [ ] `apps/cli/testdata/operator/us_market_data_status_query.yaml`: US universe 추가.
|
||||
- [ ] `apps/cli/testdata/operator/kis_daily_import_smoke.yaml`: smoke universe 추가.
|
||||
- [ ] `apps/cli/internal/operator/runner_market_test.go`: loaded fixture가 기존 API request와 동일한 provider/venue/symbols를 보내는지 유지/보강.
|
||||
- [ ] `apps/cli/testdata/operator/expected/*.jsonl`: output이 바뀌지 않아야 하며 변경이 필요하면 이유를 review에 기록.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 기존 `TestRunScenarioMarketDataStatusImportsBeforeReads`를 유지하고, materialized universe request가 `kis`, `20240527..20240528`, expected symbols를 보내는지 확인한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test ./apps/cli/internal/operator
|
||||
```
|
||||
|
||||
기대 결과: exit code 0.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|---|---|
|
||||
| `apps/cli/internal/operator/scenario.go` | CLI-1 |
|
||||
| `apps/cli/internal/operator/scenario_test.go` | CLI-1 |
|
||||
| `apps/cli/internal/operator/runner_market_test.go` | CLI-2 |
|
||||
| `apps/cli/testdata/operator/market_data_status_query.yaml` | CLI-2 |
|
||||
| `apps/cli/testdata/operator/us_market_data_status_query.yaml` | CLI-2 |
|
||||
| `apps/cli/testdata/operator/kis_daily_import_smoke.yaml` | CLI-2 |
|
||||
| `apps/cli/testdata/operator/expected/market_data_status_query.jsonl` | CLI-2 |
|
||||
| `apps/cli/testdata/operator/expected/us_market_data_status_query.jsonl` | CLI-2 |
|
||||
| `apps/cli/testdata/operator/expected/kis_daily_import_smoke.jsonl` | CLI-2 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
go test ./apps/cli/internal/operator ./apps/cli/internal/cli
|
||||
```
|
||||
|
||||
기대 결과: exit code 0. Go test cache output은 허용한다. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,155 @@
|
|||
<!-- task=m-backtest-data-collection-infrastructure/01_watchlist_config plan=1 tag=REVIEW_CLI -->
|
||||
|
||||
# Plan - REVIEW_CLI Watchlist Config Follow-up
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성은 필수다. 구현 후 검증을 실행하고 실제 stdout/stderr를 붙인 뒤 active 파일을 그대로 둔 채 리뷰 준비 상태로 보고한다. 종결, log rename, `complete.log`, archive 이동은 code-review 전용이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 범위 충돌이 필요하면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 기록하고 멈춘다. 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 증거 공백은 사용자 리뷰 요청이 아니다.
|
||||
|
||||
## 배경
|
||||
|
||||
이 follow-up은 `code_review_local_G05_0.log`의 Required 이슈만 처리한다. named universe 구현 자체는 기본 테스트를 통과했지만, `Scenario.Validate()`가 materialization 뒤 재호출에 취약하고, fixture 기반 runner 테스트가 API request의 universe-derived 필드를 충분히 고정하지 못한다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md`
|
||||
- Task ids:
|
||||
- `watchlist-config`: 선택 종목군을 수집 대상으로 등록하고 갱신할 수 있는 headless 설정 경계를 만든다.
|
||||
- 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-roadmap/current.md`
|
||||
- `agent-ops/skills/common/router.md`
|
||||
- `agent-ops/skills/common/code-review/SKILL.md`
|
||||
- `agent-ops/rules/project/domain/operations/rules.md`
|
||||
- `agent-test/local/rules.md`
|
||||
- `agent-test/local/operations-smoke.md`
|
||||
- `agent-task/m-backtest-data-collection-infrastructure/01_watchlist_config/plan_local_G05_0.log`
|
||||
- `agent-task/m-backtest-data-collection-infrastructure/01_watchlist_config/code_review_local_G05_0.log`
|
||||
- `apps/cli/internal/operator/scenario.go`
|
||||
- `apps/cli/internal/operator/scenario_test.go`
|
||||
- `apps/cli/internal/operator/runner.go`
|
||||
- `apps/cli/internal/operator/runner_market_test.go`
|
||||
- `apps/cli/internal/operator/client_test.go`
|
||||
- `apps/cli/testdata/operator/market_data_status_query.yaml`
|
||||
- `apps/cli/testdata/operator/us_market_data_status_query.yaml`
|
||||
- `apps/cli/testdata/operator/kis_daily_import_smoke.yaml`
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
- `test_env=local`.
|
||||
- 변경 범위는 `apps/cli/**`이므로 operations 도메인 규칙과 local Go test를 적용한다.
|
||||
- 적용 명령: `go test ./apps/cli/internal/operator ./apps/cli/internal/cli`.
|
||||
- 외부 서비스, secret, Docker infra 필요 없음.
|
||||
|
||||
### 실패 요약
|
||||
|
||||
- `scenario.go`는 universe materialization 후 `Request.Universe`를 그대로 둔다. 이미 materialize된 scenario에 `Validate()`를 다시 호출하면 helper가 자체 채운 inline fields를 사용자 inline conflict로 오인한다.
|
||||
- `runner_market_test.go`는 loaded fixture에서 import request가 실제 API로 나갔는지 확인하지만 `provider`와 날짜만 보며, named universe가 제공하는 `selector_kind`, `market`, `venue`, `name`, `symbols`는 고정하지 않는다.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- protobuf schema, API/worker service, YAML fixture 구조 자체는 변경하지 않는다.
|
||||
- 이번 follow-up은 validation idempotence와 runner test coverage만 보완한다.
|
||||
- milestone-linked task group이므로 PASS 시 code-review가 runtime completion metadata를 보고한다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
- `local-G06`: 명확한 Required 이슈 두 개이며 로컬 unit test로 재현/검증 가능하다. 범위는 `apps/cli/internal/operator` 테스트와 validation helper에 제한된다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `Scenario.Validate()`의 named universe materialization을 재호출해도 실패하지 않도록 idempotent하게 고친다.
|
||||
- [ ] loaded KR/US fixture runner 테스트가 materialized import request의 `selector_kind`, `market`, `venue`, `name`, `symbols`까지 API 요청으로 전달되는지 검증하게 보강한다.
|
||||
- [ ] `go test ./apps/cli/internal/operator ./apps/cli/internal/cli`를 실행한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [REVIEW_CLI-1] Idempotent Universe Materialization
|
||||
|
||||
#### 문제
|
||||
|
||||
`apps/cli/internal/operator/scenario.go:467`의 conflict check는 `Request.Universe`가 있는 상태에서 inline import field가 하나라도 채워져 있으면 실패한다. 같은 helper가 `apps/cli/internal/operator/scenario.go:477`부터 provider/selector/market/venue/name/symbols를 채우지만 `Request.Universe`를 비우지 않으므로, `ParseScenario` 뒤 `sc.Validate()`를 재호출하면 실제 사용자 inline conflict가 없는데도 실패한다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
성공적으로 universe를 materialize한 뒤 `step.Request.Universe = ""`로 정리해 validated scenario가 기존 inline import request와 같은 상태가 되게 한다. user-authored conflict는 materialization 전 검사에서 계속 실패해야 한다.
|
||||
|
||||
```go
|
||||
step.Request.Provider = u.Provider
|
||||
step.Request.SelectorKind = u.SelectorKind
|
||||
step.Request.Market = u.Market
|
||||
step.Request.Venue = u.Venue
|
||||
step.Request.Name = u.Name
|
||||
step.Request.Symbols = append([]string(nil), u.Symbols...)
|
||||
step.Request.Universe = ""
|
||||
```
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/cli/internal/operator/scenario.go`: successful materialization 후 `Request.Universe`를 clear한다.
|
||||
- [ ] `apps/cli/internal/operator/scenario_test.go`: universe-backed scenario를 `ParseScenario`한 뒤 `sc.Validate()`를 다시 호출해도 성공하는 regression test를 추가한다.
|
||||
- [ ] 기존 `TestValidateRejectsUniverseInlineConflict`가 계속 실패하는지 유지한다.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- `TestValidateImportDailyBarsUniverseReferenceIsIdempotent`: `ParseScenario` 성공 후 `sc.Validate()` 재호출이 nil을 반환하는지 확인한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test ./apps/cli/internal/operator
|
||||
```
|
||||
|
||||
기대 결과: exit code 0.
|
||||
|
||||
### [REVIEW_CLI-2] Runner Fixture Request Assertions
|
||||
|
||||
#### 문제
|
||||
|
||||
`apps/cli/internal/operator/runner_market_test.go:143`은 `market_data_status_query.yaml` fixture를 load한 뒤 fake API가 받은 import request를 확인하지만 `provider`와 날짜만 assert한다. named universe는 `selector_kind`, `market`, `venue`, `name`, `symbols`도 materialize하므로 이 값들이 runner/API request 경계까지 보존되는지 테스트가 잠가야 한다.
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
KR fixture runner test를 확장하고, US fixture runner test도 import request assertion을 추가한다. fake API가 이미 `lastImportDailyBarsReq()`를 제공하므로 새 test infrastructure는 필요 없다.
|
||||
|
||||
```go
|
||||
if imp.GetSelectorKind() != "watchlist" { ... }
|
||||
if imp.GetMarket() != altv1.Market_MARKET_KR { ... }
|
||||
if imp.GetVenue() != altv1.Venue_VENUE_KRX { ... }
|
||||
if imp.GetName() != "kr-core" { ... }
|
||||
if !equalStrings(imp.GetSymbols(), []string{"005930"}) { ... }
|
||||
```
|
||||
|
||||
US fixture는 `MARKET_US`, `VENUE_NASDAQ`, `name=us-core`, `symbols=[AAPL]`을 확인한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/cli/internal/operator/runner_market_test.go`: KR loaded fixture import request assertion을 `selector_kind`, `market`, `venue`, `name`, `symbols`까지 확장한다.
|
||||
- [ ] `apps/cli/internal/operator/runner_market_test.go`: US loaded fixture import request assertion을 추가한다.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- 기존 `TestRunScenarioMarketDataStatusImportsBeforeReads` 확장.
|
||||
- 기존 `TestRunScenarioMarketDataStatusUS` 또는 새 US-specific test에서 API request fields 확인.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test ./apps/cli/internal/operator
|
||||
```
|
||||
|
||||
기대 결과: exit code 0.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
go test ./apps/cli/internal/operator ./apps/cli/internal/cli
|
||||
```
|
||||
|
||||
기대 결과: exit code 0. Go test cache output은 허용한다. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다.
|
||||
|
|
@ -0,0 +1,127 @@
|
|||
<!-- task=m-backtest-data-collection-infrastructure/02+01_freshness_status plan=0 tag=CLI -->
|
||||
|
||||
# Code Review Reference - CLI
|
||||
|
||||
> **[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`.
|
||||
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-06-14
|
||||
task=m-backtest-data-collection-infrastructure/02+01_freshness_status, plan=0, tag=CLI
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md`
|
||||
- Task ids:
|
||||
- `freshness-status`: 수집 성공, 실패, 누락, 최신 일자를 operator가 확인할 수 있는 상태 출력을 만든다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
|
||||
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.
|
||||
|
||||
1. 판정을 append한다.
|
||||
2. `CODE_REVIEW-local-G06.md` -> `code_review_local_G06_N.log`, `PLAN-local-G06.md` -> `plan_local_G06_M.log`로 아카이브한다.
|
||||
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-backtest-data-collection-infrastructure/02+01_freshness_status/`로 이동한다.
|
||||
4. PASS이고 task group이 `m-backtest-data-collection-infrastructure`이면 완료 이벤트 메타데이터를 보고한다. roadmap 수정이나 `update-roadmap` 직접 호출은 런타임 책임이다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [CLI-1] Freshness Scenario Action | [ ] |
|
||||
| [CLI-2] Freshness Runner And Output | [ ] |
|
||||
| [CLI-3] Freshness Fixtures And Handoff | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `collection_freshness` scenario action과 validation을 추가한다.
|
||||
- [ ] Runner가 named universe를 기준으로 instruments/bars를 조회해 success/error/missing/latest 상태를 계산한다.
|
||||
- [ ] Text/JSONL output과 fixtures/headless handoff 문서에 freshness 상태 키를 반영한다.
|
||||
- [ ] `go test ./apps/cli/internal/operator ./apps/cli/internal/cli`를 실행한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
|
||||
- [ ] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [ ] active `CODE_REVIEW-*-G??.md`를 `code_review_{review_lane}_GNN_N.log`로 아카이브한다.
|
||||
- [ ] active `PLAN-*-G??.md`를 `plan_{build_lane}_GNN_M.log`로 아카이브한다.
|
||||
- [ ] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [ ] PASS이면 active task 디렉터리를 archive로 이동한다.
|
||||
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- implementation 전에 `01_watchlist_config` complete log가 존재했는지 확인한다.
|
||||
- freshness 계산이 existing API reads만 사용하고 worker/API/contracts를 침범하지 않았는지 확인한다.
|
||||
- typed error, missing symbol, empty bars, latest date가 text/JSONL 모두에서 안정적으로 출력되는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
|
||||
|
||||
### CLI-1 중간 검증
|
||||
|
||||
```bash
|
||||
$ go test ./apps/cli/internal/operator
|
||||
(output)
|
||||
```
|
||||
|
||||
### CLI-2 중간 검증
|
||||
|
||||
```bash
|
||||
$ go test ./apps/cli/internal/operator
|
||||
(output)
|
||||
```
|
||||
|
||||
### CLI-3 중간 검증
|
||||
|
||||
```bash
|
||||
$ go test ./apps/cli/internal/operator
|
||||
(output)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
|
||||
```bash
|
||||
$ go test ./apps/cli/internal/operator ./apps/cli/internal/cli
|
||||
(output)
|
||||
```
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
_리뷰 에이전트가 PASS/WARN/FAIL 판정을 append한다._
|
||||
|
|
@ -0,0 +1,291 @@
|
|||
<!-- task=m-backtest-data-collection-infrastructure/02+01_freshness_status plan=0 tag=CLI -->
|
||||
|
||||
# Plan - CLI Freshness Status
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성은 필수다. 구현 전 `agent-task/m-backtest-data-collection-infrastructure/01_watchlist_config/complete.log` 또는 archive의 같은 predecessor complete log가 있는지 확인한다. 구현 후 검증을 실행하고 실제 stdout/stderr를 붙인 뒤 active 파일을 그대로 둔 채 리뷰 준비 상태로 보고한다. 종결, log rename, `complete.log`, archive 이동은 code-review 전용이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 범위 충돌이 필요하면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 기록하고 멈춘다. 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다.
|
||||
|
||||
## 배경
|
||||
|
||||
`daily-collect`는 실행/재실행 경계가 준비되었지만 operator가 선택 종목군의 성공, 실패, 누락, 최신 일자를 한 번에 확인하는 출력은 아직 없다. 이 작업은 named universe 설정을 전제로 기존 API의 `ListInstruments`와 `ListBars` 응답을 CLI runner에서 조합해 headless freshness 상태 이벤트를 만든다. 새 protobuf/API/worker storage 변경은 이 plan 범위가 아니다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
|
||||
구현 중 사용자 전용 차단이 생기면 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 구현 에이전트는 채팅에서 직접 질문하지 않고, code-review가 `USER_REVIEW.md` 작성 여부를 판단한다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md`
|
||||
- Task ids:
|
||||
- `freshness-status`: 수집 성공, 실패, 누락, 최신 일자를 operator가 확인할 수 있는 상태 출력을 만든다.
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-roadmap/current.md`
|
||||
- `agent-roadmap/phase/backtest-loop/PHASE.md`
|
||||
- `agent-roadmap/phase/backtest-loop/milestones/backtest-data-collection-infrastructure.md`
|
||||
- `agent-ops/rules/project/rules.md`
|
||||
- `agent-ops/rules/project/domain/operations/rules.md`
|
||||
- `agent-ops/rules/project/domain/domain-model/rules.md`
|
||||
- `agent-ops/rules/project/domain/worker/rules.md`
|
||||
- `agent-ops/rules/project/domain/api/rules.md`
|
||||
- `agent-ops/rules/project/domain/contracts/rules.md`
|
||||
- `agent-test/local/rules.md`
|
||||
- `agent-test/local/operations-smoke.md`
|
||||
- `agent-test/local/domain-model-smoke.md`
|
||||
- `agent-test/local/worker-smoke.md`
|
||||
- `agent-test/local/api-smoke.md`
|
||||
- `agent-test/local/contracts-smoke.md`
|
||||
- `apps/cli/go.mod`
|
||||
- `apps/cli/internal/operator/scenario.go`
|
||||
- `apps/cli/internal/operator/scenario_test.go`
|
||||
- `apps/cli/internal/operator/runner.go`
|
||||
- `apps/cli/internal/operator/runner_market_test.go`
|
||||
- `apps/cli/internal/operator/client.go`
|
||||
- `apps/cli/internal/operator/client_test.go`
|
||||
- `apps/cli/internal/operator/output.go`
|
||||
- `apps/cli/internal/operator/parser_map.go`
|
||||
- `apps/cli/internal/operator/parser_map_test.go`
|
||||
- `apps/cli/internal/cli/cli.go`
|
||||
- `apps/cli/internal/cli/cli_test.go`
|
||||
- `packages/contracts/proto/alt/v1/market.proto`
|
||||
- `services/worker/internal/socket/market_mapping.go`
|
||||
- `services/worker/internal/socket/market_mapping_test.go`
|
||||
- `services/worker/internal/storage/ports.go`
|
||||
- `apps/cli/testdata/operator/market_data_status_query.yaml`
|
||||
- `apps/cli/testdata/operator/us_market_data_status_query.yaml`
|
||||
- `apps/cli/testdata/operator/expected/market_data_status_query.jsonl`
|
||||
- `apps/cli/testdata/operator/expected/us_market_data_status_query.jsonl`
|
||||
- `apps/cli/testdata/operator/headless_validation.md`
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
- `test_env=local`.
|
||||
- `agent-test/local/rules.md`를 읽었다. 실제 source 변경은 `apps/cli/**`라 `operations-smoke`를 적용한다.
|
||||
- 읽은 profile: `agent-test/local/operations-smoke.md`. 관련 경계 검토를 위해 domain/worker/api/contracts smoke도 읽었지만, 이 plan은 worker/API/contracts를 수정하지 않는다.
|
||||
- 적용 명령: `go test ./apps/cli/internal/operator ./apps/cli/internal/cli`.
|
||||
- `<확인 필요>` 값 없음. 새 외부 dependency 없음.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- 기존 `runner_market_test.go`는 import, list instruments, list bars를 개별/순서로 검증하지만 universe별 missing/latest 상태 계산은 덮지 않는다.
|
||||
- 기존 `output.go`는 import count와 generic count만 출력하고 `freshness_status`, `latest_yyyymmdd`, `missing_count`, `missing_symbols` 같은 operator-facing 상태 키를 출력하지 않는다.
|
||||
- 이 plan에서 freshness action success, missing symbol, typed error propagation, JSONL/text rendering 테스트를 추가한다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
- renamed/removed symbol 없음. 새 action, request/expect fields, output fields, helper functions만 추가한다.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
- split 정책을 먼저 평가했다. 공유 task group은 `agent-task/m-backtest-data-collection-infrastructure/`다.
|
||||
- `01_watchlist_config`: predecessor이며 named universe 설정을 제공한다.
|
||||
- `02+01_freshness_status`: 이 plan. directory name상 predecessor index `01`에 의존한다.
|
||||
- predecessor 확인: active `agent-task/m-backtest-data-collection-infrastructure/01_watchlist_config/complete.log` 없음. archive 후보도 없음. 구현 시작 전 `01`이 PASS되어 complete log가 생겨야 한다.
|
||||
- 이 plan은 CLI runner/output 변경만 다룬다. storage migration이나 protobuf 추가는 별도 split gate로 보류한다.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
- 새 protobuf/API/worker endpoint를 만들지 않는다. `packages/contracts/proto/alt/v1/market.proto:9`의 `Instrument.provider_symbols`와 `market.proto:29`의 `Bar.timestamp_unix_ms`로 충분히 latest/missing 상태를 계산할 수 있다.
|
||||
- 실패 상태는 existing typed `ErrorInfo`를 CLI output에 표면화한다. durable import journal이나 retry scheduler는 별도 Milestone/Task 후보로 남긴다.
|
||||
- Flutter 화면, provider live credentials, DB migration은 범위 밖이다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
- `local-G06`: CLI runner에 다단계 API read orchestration과 output contract가 추가되지만, protocol/storage 변경 없이 focused Go tests로 검증 가능하다.
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
- 이 plan의 directory `02+01_freshness_status`는 predecessor `01_watchlist_config` 완료에 의존한다.
|
||||
- 구현 시작 전 active 또는 archived `m-backtest-data-collection-infrastructure/01_watchlist_config/complete.log`를 확인한다.
|
||||
- predecessor가 없으면 구현하지 말고 review stub의 `사용자 리뷰 요청`이 아니라 "predecessor missing" 진행 불가 상태로 보고한다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] `collection_freshness` scenario action과 validation을 추가한다.
|
||||
- [ ] Runner가 named universe를 기준으로 instruments/bars를 조회해 success/error/missing/latest 상태를 계산한다.
|
||||
- [ ] Text/JSONL output과 fixtures/headless handoff 문서에 freshness 상태 키를 반영한다.
|
||||
- [ ] `go test ./apps/cli/internal/operator ./apps/cli/internal/cli`를 실행한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [CLI-1] Freshness Scenario Action
|
||||
|
||||
#### 문제
|
||||
|
||||
`apps/cli/internal/operator/scenario.go:23`의 action set에는 import/read/backtest/paper/live actions만 있고 freshness 상태 action이 없다. `apps/cli/internal/operator/scenario.go:432`의 `list_bars` validation은 단일 instrument만 다룬다.
|
||||
|
||||
```go
|
||||
// apps/cli/internal/operator/scenario.go:23
|
||||
const (
|
||||
// ActionHello validates a basic API connection handshake.
|
||||
ActionHello Action = "hello"
|
||||
// ActionImportDailyBars imports market data daily bars.
|
||||
ActionImportDailyBars Action = "import_daily_bars"
|
||||
```
|
||||
|
||||
```go
|
||||
// apps/cli/internal/operator/scenario.go:432
|
||||
case ActionListBars:
|
||||
if step.Request.InstrumentID == "" {
|
||||
return fmt.Errorf("step %q: list_bars requires request.instrument_id", step.ID)
|
||||
}
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
새 action 이름은 `collection_freshness`로 둔다. request는 `universe`, `timeframe`, `from_unix_ms`, `to_unix_ms`를 요구한다. `expect`에는 `freshness_status`, `missing_count`, `latest_yyyymmdd`를 선택적으로 추가한다. `watchlist-config`가 materialize한 universe를 기준으로 provider/market/symbols를 사용할 수 있어야 한다.
|
||||
|
||||
```go
|
||||
const (
|
||||
ActionCollectionFreshness Action = "collection_freshness"
|
||||
)
|
||||
|
||||
type Expect struct {
|
||||
...
|
||||
FreshnessStatus string `yaml:"freshness_status"`
|
||||
MissingCount *int `yaml:"missing_count"`
|
||||
LatestYYYYMMDD string `yaml:"latest_yyyymmdd"`
|
||||
}
|
||||
```
|
||||
|
||||
Validation은 `request.universe`, supported timeframe, non-zero ordered window를 확인하고, `expect.freshness_status`는 `fresh`, `missing`, `error` 중 하나만 허용한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/cli/internal/operator/scenario.go`: action constant/map, request/expect fields, validation 추가.
|
||||
- [ ] `apps/cli/internal/operator/scenario_test.go`: missing universe/timeframe/window, invalid expect status, valid freshness scenario 테스트 추가.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- `TestValidateCollectionFreshness`와 invalid matrix cases 추가.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test ./apps/cli/internal/operator
|
||||
```
|
||||
|
||||
기대 결과: exit code 0.
|
||||
|
||||
### [CLI-2] Freshness Runner And Output
|
||||
|
||||
#### 문제
|
||||
|
||||
`apps/cli/internal/operator/runner.go:218`의 `runStep` switch는 `list_instruments`와 `list_bars`를 개별 step으로만 처리한다. `apps/cli/internal/operator/output.go:23`의 `StepEvent`에는 import counts는 있지만 freshness-specific fields가 없다.
|
||||
|
||||
```go
|
||||
// apps/cli/internal/operator/runner.go:231
|
||||
case ActionListInstruments:
|
||||
resp, err := client.ListInstruments(stepCtx, &altv1.ListInstrumentsRequest{
|
||||
Market: marketByName[step.Request.Market],
|
||||
})
|
||||
```
|
||||
|
||||
```go
|
||||
// apps/cli/internal/operator/output.go:41
|
||||
// Import-specific output fields
|
||||
Provider string
|
||||
InstrumentCount int
|
||||
BarCount int
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
`runCollectionFreshness` helper를 추가한다. 흐름은 다음 순서다.
|
||||
|
||||
1. `ListInstruments(market, provider)`를 호출한다.
|
||||
2. universe symbols를 provider symbol 또는 instrument symbol로 instrument id에 매칭한다.
|
||||
3. 매칭된 instrument마다 `ListBars(instrument_id, timeframe, from, to)`를 호출한다.
|
||||
4. typed error가 오면 `freshness_status=error`와 error code/message를 출력한다.
|
||||
5. symbol이 없거나 bar가 없으면 missing으로 기록한다.
|
||||
6. 모든 bar timestamp 중 max를 `latest_yyyymmdd`로 출력한다.
|
||||
7. missing_count가 0이면 `freshness_status=fresh`, 아니면 `missing`으로 출력한다.
|
||||
|
||||
`StepEvent`와 `Writer.WriteStep`에 `Universe`, `FreshnessStatus`, `LatestYYYYMMDD`, `MissingCount`, `MissingSymbols`를 추가한다. Text output은 `missing_symbols=A,B`처럼 comma-separated로, JSONL은 `missing_symbols` array로 출력한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/cli/internal/operator/runner.go`: `ActionCollectionFreshness` case, helper, expectation evaluation 추가.
|
||||
- [ ] `apps/cli/internal/operator/output.go`: freshness fields와 text/jsonl rendering 추가.
|
||||
- [ ] `apps/cli/internal/operator/runner_market_test.go`: fresh, missing instrument, empty bars, typed error, call order 테스트 추가. fake API에 per-instrument bars response를 지원한다.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- `TestRunScenarioCollectionFreshnessFresh`: all symbols present, latest date 출력.
|
||||
- `TestRunScenarioCollectionFreshnessMissingSymbol`: missing_count/missing_symbols 출력.
|
||||
- `TestRunScenarioCollectionFreshnessTypedError`: typed ErrorInfo가 `status=error`로 표면화.
|
||||
- JSONL rendering assertion은 expected fixture 또는 direct writer assertion으로 고정한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test ./apps/cli/internal/operator
|
||||
```
|
||||
|
||||
기대 결과: exit code 0.
|
||||
|
||||
### [CLI-3] Freshness Fixtures And Handoff
|
||||
|
||||
#### 문제
|
||||
|
||||
현재 `apps/cli/testdata/operator/market_data_status_query.yaml:29` 이후에는 `list_instruments`와 단일 `list_bars` count만 있어 operator가 누락 symbol과 최신 일자를 한 줄로 볼 수 없다. handoff matrix도 `count` 중심이다.
|
||||
|
||||
```yaml
|
||||
# apps/cli/testdata/operator/market_data_status_query.yaml:29
|
||||
- id: list_kr_instruments
|
||||
action: list_instruments
|
||||
...
|
||||
- id: list_daily_bars
|
||||
action: list_bars
|
||||
```
|
||||
|
||||
#### 해결 방법
|
||||
|
||||
KR/US status fixtures에 `collection_freshness` step을 추가한다. expected JSONL에는 `freshness_status`, `latest_yyyymmdd`, `missing_count` 키를 추가한다. `headless_validation.md`의 market data status rows도 expected output keys와 protobuf/view-model field 설명을 갱신한다.
|
||||
|
||||
#### 수정 파일 및 체크리스트
|
||||
|
||||
- [ ] `apps/cli/testdata/operator/market_data_status_query.yaml`: freshness step 추가.
|
||||
- [ ] `apps/cli/testdata/operator/us_market_data_status_query.yaml`: freshness step 추가.
|
||||
- [ ] `apps/cli/testdata/operator/expected/market_data_status_query.jsonl`: expected keys 갱신.
|
||||
- [ ] `apps/cli/testdata/operator/expected/us_market_data_status_query.jsonl`: expected keys 갱신.
|
||||
- [ ] `apps/cli/testdata/operator/headless_validation.md`: matrix keys와 field 설명 갱신.
|
||||
|
||||
#### 테스트 작성
|
||||
|
||||
- Existing fixture loading/running tests가 expected freshness output을 확인하도록 갱신한다.
|
||||
|
||||
#### 중간 검증
|
||||
|
||||
```bash
|
||||
go test ./apps/cli/internal/operator
|
||||
```
|
||||
|
||||
기대 결과: exit code 0.
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|---|---|
|
||||
| `apps/cli/internal/operator/scenario.go` | CLI-1 |
|
||||
| `apps/cli/internal/operator/scenario_test.go` | CLI-1 |
|
||||
| `apps/cli/internal/operator/runner.go` | CLI-2 |
|
||||
| `apps/cli/internal/operator/output.go` | CLI-2 |
|
||||
| `apps/cli/internal/operator/runner_market_test.go` | CLI-2, CLI-3 |
|
||||
| `apps/cli/testdata/operator/market_data_status_query.yaml` | CLI-3 |
|
||||
| `apps/cli/testdata/operator/us_market_data_status_query.yaml` | CLI-3 |
|
||||
| `apps/cli/testdata/operator/expected/market_data_status_query.jsonl` | CLI-3 |
|
||||
| `apps/cli/testdata/operator/expected/us_market_data_status_query.jsonl` | CLI-3 |
|
||||
| `apps/cli/testdata/operator/headless_validation.md` | CLI-3 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
go test ./apps/cli/internal/operator ./apps/cli/internal/cli
|
||||
```
|
||||
|
||||
기대 결과: exit code 0. Go test cache output은 허용한다. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -82,12 +82,13 @@ func TestRunScenarioMarketDataStatus(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestRunScenarioMarketDataStatusUS(t *testing.T) {
|
||||
url := startFakeAPIServer(t, &fakeAPI{
|
||||
api := &fakeAPI{
|
||||
helloCaps: []string{"market-read"},
|
||||
importDailyBarsResp: &altv1.ImportDailyBarsResponse{Provider: "kis", InstrumentCount: 1, BarCount: 42},
|
||||
instResp: &altv1.ListInstrumentsResponse{Instruments: []*altv1.Instrument{{Id: "NASDAQ:AAPL"}}},
|
||||
barsResp: &altv1.ListBarsResponse{Bars: []*altv1.Bar{{InstrumentId: "NASDAQ:AAPL"}}},
|
||||
})
|
||||
}
|
||||
url := startFakeAPIServer(t, api)
|
||||
sc, err := LoadScenario(fixture(t, "us_market_data_status_query.yaml"))
|
||||
if err != nil {
|
||||
t.Fatalf("load scenario: %v", err)
|
||||
|
|
@ -117,6 +118,30 @@ func TestRunScenarioMarketDataStatusUS(t *testing.T) {
|
|||
t.Errorf("output %q missing %q", out, want)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify imported US request fields
|
||||
imp := api.lastImportDailyBarsReq()
|
||||
if imp == nil {
|
||||
t.Fatal("server did not receive an import_daily_bars request")
|
||||
}
|
||||
if imp.GetProvider() != "kis" || imp.GetFromYyyymmdd() != "20240528" || imp.GetToYyyymmdd() != "20240529" {
|
||||
t.Errorf("import request = %+v, want kis 20240528..20240529", imp)
|
||||
}
|
||||
if imp.GetSelectorKind() != "watchlist" {
|
||||
t.Errorf("selector_kind = %q, want watchlist", imp.GetSelectorKind())
|
||||
}
|
||||
if imp.GetMarket() != altv1.Market_MARKET_US {
|
||||
t.Errorf("market = %v, want MARKET_US", imp.GetMarket())
|
||||
}
|
||||
if imp.GetVenue() != altv1.Venue_VENUE_NASDAQ {
|
||||
t.Errorf("venue = %v, want VENUE_NASDAQ", imp.GetVenue())
|
||||
}
|
||||
if imp.GetName() != "us-core" {
|
||||
t.Errorf("name = %q, want us-core", imp.GetName())
|
||||
}
|
||||
if !equalStrings(imp.GetSymbols(), []string{"AAPL"}) {
|
||||
t.Errorf("symbols = %v, want [AAPL]", imp.GetSymbols())
|
||||
}
|
||||
}
|
||||
|
||||
// TestRunScenarioMarketDataStatusImportsBeforeReads proves the command-first
|
||||
|
|
@ -148,6 +173,21 @@ func TestRunScenarioMarketDataStatusImportsBeforeReads(t *testing.T) {
|
|||
if imp.GetProvider() != "kis" || imp.GetFromYyyymmdd() != "20240527" || imp.GetToYyyymmdd() != "20240528" {
|
||||
t.Errorf("import request = %+v, want kis 20240527..20240528", imp)
|
||||
}
|
||||
if imp.GetSelectorKind() != "watchlist" {
|
||||
t.Errorf("selector_kind = %q, want watchlist", imp.GetSelectorKind())
|
||||
}
|
||||
if imp.GetMarket() != altv1.Market_MARKET_KR {
|
||||
t.Errorf("market = %v, want MARKET_KR", imp.GetMarket())
|
||||
}
|
||||
if imp.GetVenue() != altv1.Venue_VENUE_KRX {
|
||||
t.Errorf("venue = %v, want VENUE_KRX", imp.GetVenue())
|
||||
}
|
||||
if imp.GetName() != "kr-core" {
|
||||
t.Errorf("name = %q, want kr-core", imp.GetName())
|
||||
}
|
||||
if !equalStrings(imp.GetSymbols(), []string{"005930"}) {
|
||||
t.Errorf("symbols = %v, want [005930]", imp.GetSymbols())
|
||||
}
|
||||
|
||||
// Call order: import precedes both status reads.
|
||||
wantOrder := []string{"import_daily_bars", "list_instruments", "list_bars"}
|
||||
|
|
|
|||
|
|
@ -241,6 +241,9 @@ type Expect struct {
|
|||
// Request carries the per-action parameters for market and backtest steps. Fields not
|
||||
// used by a given action are ignored; the runner reads only what each action needs.
|
||||
type Request struct {
|
||||
// Universe targets a named universe config defined at the scenario root.
|
||||
Universe string `yaml:"universe"`
|
||||
|
||||
// Market filters a list_instruments query ("", "kr", "us", "unspecified").
|
||||
Market string `yaml:"market"`
|
||||
// InstrumentID identifies the instrument for a list_bars query.
|
||||
|
|
@ -327,11 +330,23 @@ type Step struct {
|
|||
SaveAs string `yaml:"save_as"`
|
||||
}
|
||||
|
||||
// UniverseConfig defines a named selection of instruments that can be referenced
|
||||
// by multiple steps in a scenario instead of repeating raw tickers inline.
|
||||
type UniverseConfig struct {
|
||||
Name string `yaml:"name"`
|
||||
Provider string `yaml:"provider"`
|
||||
SelectorKind string `yaml:"selector_kind"`
|
||||
Market string `yaml:"market"`
|
||||
Venue string `yaml:"venue"`
|
||||
Symbols []string `yaml:"symbols"`
|
||||
}
|
||||
|
||||
// Scenario is the top-level operator scenario document.
|
||||
type Scenario struct {
|
||||
Name string `yaml:"name"`
|
||||
Timeout Duration `yaml:"timeout"`
|
||||
Steps []Step `yaml:"steps"`
|
||||
Name string `yaml:"name"`
|
||||
Timeout Duration `yaml:"timeout"`
|
||||
Universes []UniverseConfig `yaml:"universes"`
|
||||
Steps []Step `yaml:"steps"`
|
||||
}
|
||||
|
||||
// LoadScenario reads a scenario file and returns it after strict decode and
|
||||
|
|
@ -370,8 +385,46 @@ func (s *Scenario) Validate() error {
|
|||
return fmt.Errorf("scenario %q has no steps", s.Name)
|
||||
}
|
||||
|
||||
universeByName := make(map[string]UniverseConfig)
|
||||
for _, u := range s.Universes {
|
||||
if u.Name == "" {
|
||||
return fmt.Errorf("universe name is required")
|
||||
}
|
||||
if _, ok := universeByName[u.Name]; ok {
|
||||
return fmt.Errorf("duplicate universe name %q", u.Name)
|
||||
}
|
||||
if u.Provider == "" {
|
||||
return fmt.Errorf("universe %q: provider is required", u.Name)
|
||||
}
|
||||
if !validProviders[u.Provider] {
|
||||
return fmt.Errorf("universe %q: unsupported provider %q", u.Name, u.Provider)
|
||||
}
|
||||
if u.SelectorKind == "" {
|
||||
return fmt.Errorf("universe %q: selector_kind is required", u.Name)
|
||||
}
|
||||
if !validSelectorKinds[u.SelectorKind] {
|
||||
return fmt.Errorf("universe %q: unsupported selector_kind %q", u.Name, u.SelectorKind)
|
||||
}
|
||||
if len(u.Symbols) == 0 {
|
||||
return fmt.Errorf("universe %q: symbols is required", u.Name)
|
||||
}
|
||||
for _, sym := range u.Symbols {
|
||||
if strings.TrimSpace(sym) == "" {
|
||||
return fmt.Errorf("universe %q: symbols cannot contain empty or blank values", u.Name)
|
||||
}
|
||||
}
|
||||
if !validMarkets[u.Market] {
|
||||
return fmt.Errorf("universe %q: unsupported market %q", u.Name, u.Market)
|
||||
}
|
||||
if u.Venue != "" && !validVenues[u.Venue] {
|
||||
return fmt.Errorf("universe %q: unsupported venue %q", u.Name, u.Venue)
|
||||
}
|
||||
universeByName[u.Name] = u
|
||||
}
|
||||
|
||||
seen := make(map[string]bool, len(s.Steps))
|
||||
for i, step := range s.Steps {
|
||||
for i := range s.Steps {
|
||||
step := &s.Steps[i]
|
||||
if step.ID == "" {
|
||||
return fmt.Errorf("step %d: id is required", i)
|
||||
}
|
||||
|
|
@ -384,16 +437,55 @@ func (s *Scenario) Validate() error {
|
|||
return fmt.Errorf("step %q: unknown action %q", step.ID, step.Action)
|
||||
}
|
||||
|
||||
if err := validateRequest(step); err != nil {
|
||||
if err := applyUniverseDefaults(step, universeByName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateExpect(step); err != nil {
|
||||
|
||||
if err := validateRequest(*step); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateExpect(*step); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyUniverseDefaults(step *Step, universeByName map[string]UniverseConfig) error {
|
||||
if step.Action != ActionImportDailyBars {
|
||||
return nil
|
||||
}
|
||||
if step.Request.Universe == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
u, ok := universeByName[step.Request.Universe]
|
||||
if !ok {
|
||||
return fmt.Errorf("step %q: referenced universe %q not found", step.ID, step.Request.Universe)
|
||||
}
|
||||
|
||||
// Check inline conflicts
|
||||
if step.Request.Provider != "" ||
|
||||
step.Request.SelectorKind != "" ||
|
||||
step.Request.Market != "" ||
|
||||
step.Request.Venue != "" ||
|
||||
step.Request.Name != "" ||
|
||||
len(step.Request.Symbols) > 0 {
|
||||
return fmt.Errorf("step %q: cannot specify both universe reference %q and inline import fields", step.ID, step.Request.Universe)
|
||||
}
|
||||
|
||||
// Materialize fields
|
||||
step.Request.Provider = u.Provider
|
||||
step.Request.SelectorKind = u.SelectorKind
|
||||
step.Request.Market = u.Market
|
||||
step.Request.Venue = u.Venue
|
||||
step.Request.Name = u.Name
|
||||
step.Request.Symbols = append([]string(nil), u.Symbols...)
|
||||
step.Request.Universe = ""
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateExpect applies dry-run checks to a step expectation so a mistyped
|
||||
// expect.status or a misused error_code fails validation (exit code 2) before
|
||||
// any socket is opened, instead of being silently treated as a success
|
||||
|
|
|
|||
|
|
@ -727,3 +727,158 @@ steps:
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateImportDailyBarsUniverseReference(t *testing.T) {
|
||||
yamlText := `
|
||||
name: valid_universe_ref
|
||||
universes:
|
||||
- name: kr-core
|
||||
provider: kis
|
||||
selector_kind: watchlist
|
||||
market: kr
|
||||
venue: krx
|
||||
symbols: ["005930"]
|
||||
steps:
|
||||
- id: step1
|
||||
action: import_daily_bars
|
||||
request:
|
||||
universe: kr-core
|
||||
from_yyyymmdd: "20240527"
|
||||
to_yyyymmdd: "20240528"
|
||||
expect:
|
||||
status: ok
|
||||
`
|
||||
sc, err := ParseScenario([]byte(yamlText))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if len(sc.Steps) != 1 {
|
||||
t.Fatalf("steps = %d, want 1", len(sc.Steps))
|
||||
}
|
||||
step := sc.Steps[0]
|
||||
if step.Request.Provider != "kis" {
|
||||
t.Errorf("provider = %q, want kis", step.Request.Provider)
|
||||
}
|
||||
if step.Request.SelectorKind != "watchlist" {
|
||||
t.Errorf("selector_kind = %q, want watchlist", step.Request.SelectorKind)
|
||||
}
|
||||
if step.Request.Market != "kr" {
|
||||
t.Errorf("market = %q, want kr", step.Request.Market)
|
||||
}
|
||||
if step.Request.Venue != "krx" {
|
||||
t.Errorf("venue = %q, want krx", step.Request.Venue)
|
||||
}
|
||||
if len(step.Request.Symbols) != 1 || step.Request.Symbols[0] != "005930" {
|
||||
t.Errorf("symbols = %v, want [005930]", step.Request.Symbols)
|
||||
}
|
||||
if step.Request.Name != "kr-core" {
|
||||
t.Errorf("name = %q, want kr-core", step.Request.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRejectsDuplicateUniverseName(t *testing.T) {
|
||||
yamlText := `
|
||||
name: dup_universe
|
||||
universes:
|
||||
- name: kr-core
|
||||
provider: kis
|
||||
selector_kind: watchlist
|
||||
symbols: ["005930"]
|
||||
- name: kr-core
|
||||
provider: kis
|
||||
selector_kind: watchlist
|
||||
symbols: ["000660"]
|
||||
steps:
|
||||
- id: step1
|
||||
action: hello
|
||||
`
|
||||
_, err := ParseScenario([]byte(yamlText))
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "duplicate universe name") {
|
||||
t.Errorf("error = %v, want duplicate universe name", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRejectsMissingUniverseReference(t *testing.T) {
|
||||
yamlText := `
|
||||
name: missing_universe_ref
|
||||
universes:
|
||||
- name: kr-core
|
||||
provider: kis
|
||||
selector_kind: watchlist
|
||||
symbols: ["005930"]
|
||||
steps:
|
||||
- id: step1
|
||||
action: import_daily_bars
|
||||
request:
|
||||
universe: kr-other
|
||||
from_yyyymmdd: "20240527"
|
||||
to_yyyymmdd: "20240528"
|
||||
`
|
||||
_, err := ParseScenario([]byte(yamlText))
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "referenced universe \"kr-other\" not found") {
|
||||
t.Errorf("error = %v, want referenced universe not found", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRejectsUniverseInlineConflict(t *testing.T) {
|
||||
yamlText := `
|
||||
name: inline_conflict
|
||||
universes:
|
||||
- name: kr-core
|
||||
provider: kis
|
||||
selector_kind: watchlist
|
||||
symbols: ["005930"]
|
||||
steps:
|
||||
- id: step1
|
||||
action: import_daily_bars
|
||||
request:
|
||||
universe: kr-core
|
||||
provider: kis
|
||||
from_yyyymmdd: "20240527"
|
||||
to_yyyymmdd: "20240528"
|
||||
`
|
||||
_, err := ParseScenario([]byte(yamlText))
|
||||
if err == nil {
|
||||
t.Fatal("expected error, got nil")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "cannot specify both universe reference") {
|
||||
t.Errorf("error = %v, want cannot specify both universe reference", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateImportDailyBarsUniverseReferenceIsIdempotent(t *testing.T) {
|
||||
yamlText := `
|
||||
name: valid_universe_ref
|
||||
universes:
|
||||
- name: kr-core
|
||||
provider: kis
|
||||
selector_kind: watchlist
|
||||
market: kr
|
||||
venue: krx
|
||||
symbols: ["005930"]
|
||||
steps:
|
||||
- id: step1
|
||||
action: import_daily_bars
|
||||
request:
|
||||
universe: kr-core
|
||||
from_yyyymmdd: "20240527"
|
||||
to_yyyymmdd: "20240528"
|
||||
expect:
|
||||
status: ok
|
||||
`
|
||||
sc, err := ParseScenario([]byte(yamlText))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error on ParseScenario: %v", err)
|
||||
}
|
||||
|
||||
// Validate again to check idempotence
|
||||
if err := sc.Validate(); err != nil {
|
||||
t.Errorf("unexpected error on second Validate call: %v", err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,14 +1,17 @@
|
|||
name: kis_daily_import_smoke
|
||||
timeout: 5s
|
||||
universes:
|
||||
- name: kr-smoke
|
||||
provider: kis
|
||||
selector_kind: watchlist
|
||||
market: kr
|
||||
venue: krx
|
||||
symbols: ["005930"]
|
||||
steps:
|
||||
- id: import_samsung
|
||||
action: import_daily_bars
|
||||
request:
|
||||
provider: kis
|
||||
selector_kind: watchlist
|
||||
market: kr
|
||||
venue: krx
|
||||
symbols: ["005930"]
|
||||
universe: kr-smoke
|
||||
from_yyyymmdd: "20240527"
|
||||
to_yyyymmdd: "20240528"
|
||||
expect:
|
||||
|
|
|
|||
|
|
@ -7,6 +7,13 @@
|
|||
# offline.
|
||||
name: market_data_status_query
|
||||
timeout: 5s
|
||||
universes:
|
||||
- name: kr-core
|
||||
provider: kis
|
||||
selector_kind: watchlist
|
||||
market: kr
|
||||
venue: krx
|
||||
symbols: ["005930"]
|
||||
steps:
|
||||
- id: hello
|
||||
action: hello
|
||||
|
|
@ -17,11 +24,7 @@ steps:
|
|||
- id: import_kr_daily_bars
|
||||
action: import_daily_bars
|
||||
request:
|
||||
provider: kis
|
||||
selector_kind: watchlist
|
||||
market: kr
|
||||
venue: krx
|
||||
symbols: ["005930"]
|
||||
universe: kr-core
|
||||
from_yyyymmdd: "20240527"
|
||||
to_yyyymmdd: "20240528"
|
||||
expect:
|
||||
|
|
|
|||
|
|
@ -2,6 +2,13 @@
|
|||
# then reads instruments and bars.
|
||||
name: us_market_data_status_query
|
||||
timeout: 5s
|
||||
universes:
|
||||
- name: us-core
|
||||
provider: kis
|
||||
selector_kind: watchlist
|
||||
market: us
|
||||
venue: nasdaq
|
||||
symbols: ["AAPL"]
|
||||
steps:
|
||||
- id: hello
|
||||
action: hello
|
||||
|
|
@ -12,11 +19,7 @@ steps:
|
|||
- id: import_us_daily_bars
|
||||
action: import_daily_bars
|
||||
request:
|
||||
provider: kis
|
||||
selector_kind: watchlist
|
||||
market: us
|
||||
venue: nasdaq
|
||||
symbols: ["AAPL"]
|
||||
universe: us-core
|
||||
from_yyyymmdd: "20240528"
|
||||
to_yyyymmdd: "20240529"
|
||||
expect:
|
||||
|
|
|
|||
Loading…
Reference in a new issue