feat: US market expansion & asset type instrument support
- Add asset_type column to instrument table (migration 000003) - Extend market domain types with asset_type and related fields - Update proto definitions for common and market messages - Regenerate Dart and Go protobuf code - Add shared query surface for US market data - Add KIS US daily itemchartprice provider and tests - Update backtest and operator components for US market support - Update socket market mapping for new asset types - Move paper-trading-command-workflow to archive - Add agent-task for m-us-market-expansion (subtasks 02+01, 03+01, 04+02,03)
This commit is contained in:
parent
bdbf594132
commit
f552dfc3ef
39 changed files with 2203 additions and 98 deletions
|
|
@ -11,7 +11,7 @@
|
|||
|
||||
## 상태
|
||||
|
||||
[진행중]
|
||||
[완료]
|
||||
|
||||
## 구현 잠금
|
||||
|
||||
|
|
@ -37,20 +37,23 @@
|
|||
paper trading을 화면 없이 반복 검증하고, 나중에 UI로 올릴 운영 항목을 command evidence로 먼저 정리한다.
|
||||
|
||||
- [x] [paper-status-command] paper account, portfolio, position 상태를 command로 조회할 수 있다. 검증: status command 또는 scenario가 account_id, cash, position_count, risk key를 출력한다.
|
||||
- [ ] [paper-order-command] virtual order submit/cancel/fill simulation을 command로 실행할 수 있다. 검증: order lifecycle scenario가 order id, status transition, fill summary를 출력한다.
|
||||
- [x] [paper-order-command] virtual order submit/cancel/fill simulation을 command로 실행할 수 있다. 검증: order lifecycle scenario가 order id, status transition, fill summary를 출력한다.
|
||||
- [x] [paper-risk-command] 최소 risk guard 결과를 command workflow에서 확인할 수 있다. 검증: 허용/차단 case가 typed status/error key로 구분된다.
|
||||
- [x] [paper-loop-smoke] paper execution loop를 fixture 또는 fake market data로 smoke 실행할 수 있다. 검증: loop command가 run id, terminal status, position/equity summary를 출력한다.
|
||||
- [ ] [paper-ui-handoff] paper trading에서 화면에 올릴 후보와 아직 command로만 둘 항목을 분리한다. 검증: handoff 문서가 command, expected output key, 반복 운영 여부, UI defer 사유를 기록한다.
|
||||
- [x] [paper-ui-handoff] paper trading에서 화면에 올릴 후보와 아직 command로만 둘 항목을 분리한다. 검증: handoff 문서가 command, expected output key, 반복 운영 여부, UI defer 사유를 기록한다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
||||
- 상태: 없음
|
||||
- 요청일: 없음
|
||||
- 완료 근거: 아직 기능 Task와 검증이 충족되지 않았다.
|
||||
- 상태: 승인됨
|
||||
- 요청일: 2026-06-06
|
||||
- 완료 근거:
|
||||
- 모든 기능 Task가 완료 로그의 `Roadmap Completion` Task id와 매칭되어 `[x]`로 동기화되었다.
|
||||
- `paper-order-command`는 `agent-task/archive/2026/06/m-paper-trading-command-workflow/03+02_order_lifecycle/complete.log` 기준 PASS다.
|
||||
- `paper-ui-handoff`는 `agent-task/archive/2026/06/m-paper-trading-command-workflow/04+01,02,03_ui_handoff/complete.log` 기준 PASS다.
|
||||
- 리뷰 필요:
|
||||
- [ ] 사용자가 완료 결과를 확인했다
|
||||
- [ ] archive 이동을 승인했다
|
||||
- 리뷰 코멘트: 없음
|
||||
- [x] 사용자가 완료 결과를 확인했다
|
||||
- [x] archive 이동을 승인했다
|
||||
- 리뷰 코멘트: 사용자 요청으로 현 마일스톤을 완료 처리하고 archive로 이동한다.
|
||||
|
||||
## 범위 제외
|
||||
|
||||
|
|
@ -65,16 +68,18 @@ paper trading을 화면 없이 반복 검증하고, 나중에 UI로 올릴 운
|
|||
- 관련 경로: `apps/cli/`, `apps/cli/testdata/operator/`, `services/worker/`, `services/api/`, `packages/domain/`
|
||||
- 표준선(선택): paper trading은 실거래 전 검증 경계이며, 먼저 command workflow로 반복 운영성과 상태 key를 확인한다.
|
||||
- 표준선(선택): UI는 command workflow에서 반복되는 status/action/evidence key가 정리된 뒤에만 후보로 올린다.
|
||||
- 현재 작업: order lifecycle과 UI handoff subtask가 active review 상태다.
|
||||
- 현재 작업: 사용자 완료 승인에 따라 archive 이동까지 완료됐다.
|
||||
- 직접 처리 완료:
|
||||
- [x] `paper-status-command`: status output에 `account_id`, `cash`, `position_count`, `risk` key를 고정했고 2026-06-05 local `bin/test`가 통과했다.
|
||||
- [x] `paper-loop-smoke`: `agent-task/archive/2026/06/m-paper-trading-command-workflow/01_loop_smoke/complete.log` 기준 PASS. 검증: `go test -count=1 ./apps/cli/internal/operator`, `PATH="/tmp/protoc293/bin:$PATH" bin/test`.
|
||||
- [x] `paper-risk-command`: `agent-task/archive/2026/06/m-paper-trading-command-workflow/02+01_risk_command/complete.log` 기준 PASS. 검증: `PATH="/tmp/protoc293/bin:$PATH" bin/contracts-check`, `go test ./services/worker/...`, `go test ./services/api/...`, `go test ./apps/cli/internal/operator`, `PATH="/tmp/protoc293/bin:$PATH" bin/test`.
|
||||
- [x] `paper-order-command`: `agent-task/archive/2026/06/m-paper-trading-command-workflow/03+02_order_lifecycle/complete.log` 기준 PASS. 검증: `go test -count=1 ./services/worker/internal/papertrading ./services/worker/internal/socket`, `go test -count=1 ./apps/cli/internal/operator`, `PATH="/tmp/protoc293/bin:$PATH" bin/test`.
|
||||
- [x] `paper-ui-handoff`: `agent-task/archive/2026/06/m-paper-trading-command-workflow/04+01,02,03_ui_handoff/complete.log` 기준 PASS. 검증: `go test -count=1 ./apps/cli/internal/operator`, `bin/test`, `git diff --check`.
|
||||
- 큰 작업 plan:
|
||||
- 완료: `agent-task/archive/2026/06/m-paper-trading-command-workflow/01_loop_smoke/complete.log`
|
||||
- 완료: `agent-task/archive/2026/06/m-paper-trading-command-workflow/02+01_risk_command/complete.log`
|
||||
- 활성: `agent-task/m-paper-trading-command-workflow/03+02_order_lifecycle/PLAN-cloud-G09.md`
|
||||
- 활성: `agent-task/m-paper-trading-command-workflow/04+01,02,03_ui_handoff/PLAN-local-G04.md`
|
||||
- 완료: `agent-task/archive/2026/06/m-paper-trading-command-workflow/03+02_order_lifecycle/complete.log`
|
||||
- 완료: `agent-task/archive/2026/06/m-paper-trading-command-workflow/04+01,02,03_ui_handoff/complete.log`
|
||||
- 진행 순서:
|
||||
1. status 조회 scenario로 account_id, cash, position_count, risk key를 고정한다.
|
||||
2. paper execution loop smoke로 run id, terminal status, position/equity summary를 확인한다.
|
||||
|
|
@ -16,10 +16,10 @@
|
|||
- [완료] Paper Trading Readiness
|
||||
- 경로: `agent-roadmap/archive/phase/trading-expansion/milestones/paper-trading-readiness.md`
|
||||
- 요약: 실거래 전에 전략 실행, 가상 주문, 포지션, 리스크 흐름을 paper trading 경계로 검증한다.
|
||||
- [진행중] Paper Trading Command Workflow
|
||||
- 경로: `agent-roadmap/phase/trading-expansion/milestones/paper-trading-command-workflow.md`
|
||||
- [완료] Paper Trading Command Workflow
|
||||
- 경로: `agent-roadmap/archive/phase/trading-expansion/milestones/paper-trading-command-workflow.md`
|
||||
- 요약: paper trading account/order/fill/position/risk 흐름을 Flutter 화면 없이 CLI/YAML/log 중심의 반복 가능한 command workflow로 검증한다.
|
||||
- [계획] US Market Expansion
|
||||
- [진행중] US Market Expansion
|
||||
- 경로: `agent-roadmap/phase/trading-expansion/milestones/us-market-expansion.md`
|
||||
- 요약: 한국장 MVP 구조 위에 미국장 데이터를 추가한다.
|
||||
- [보류] Live Trading Boundary
|
||||
|
|
|
|||
|
|
@ -11,40 +11,36 @@
|
|||
|
||||
## 상태
|
||||
|
||||
[계획]
|
||||
[진행중]
|
||||
|
||||
## 구현 잠금
|
||||
|
||||
- 상태: 잠금
|
||||
- 결정 필요: 아래 체크리스트
|
||||
- [ ] 미국장 MVP universe를 ETF 중심, 개별주 중심, 또는 watchlist 중심 중 무엇으로 둘지 결정한다.
|
||||
- [ ] 미국장 데이터 provider를 KIS 우선으로 고정할지 보조 provider를 함께 고려할지 결정한다.
|
||||
- 상태: 해제
|
||||
- 결정 필요: 없음
|
||||
- 결정 내용:
|
||||
- [x] 미국장 MVP universe는 watchlist 중심으로 시작한다. ETF/개별주는 universe entry의 `asset_type` metadata로 표현하고, 구현은 특정 종목 종류에 고정하지 않는다.
|
||||
- [x] 초기 미국장 데이터 provider는 KIS로 고정한다. 다만 provider adapter/capability 형식은 유지해 Kiwoom 같은 후속 provider가 시장별 지원 범위를 선언할 수 있게 한다.
|
||||
|
||||
## 범위
|
||||
|
||||
- US market instrument mapping
|
||||
- watchlist 기반 US market universe selector
|
||||
- USD/NASDAQ/NYSE calendar handling
|
||||
- daily bar import
|
||||
- 한국장과 동일한 normalized data/query path
|
||||
- backtest compatibility
|
||||
|
||||
## 필수 기능
|
||||
## 기능
|
||||
|
||||
### Epic: [us-market] US market expansion
|
||||
|
||||
미국장 instrument, calendar, daily data, shared query, backtest compatibility 확장을 묶는다.
|
||||
|
||||
- [ ] [us-instruments] 미국장 instrument identity와 provider symbol mapping이 있다.
|
||||
- [ ] [us-calendar] 미국장 timezone/currency/venue 기준이 있다.
|
||||
- [ ] [us-daily-bars] 미국장 일봉 데이터를 normalized bar model로 저장한다.
|
||||
- [ ] [shared-query] 한국장과 미국장이 같은 query/contract pattern을 공유한다.
|
||||
- [ ] [us-backtest] 미국장 daily data로 backtest fixture 또는 smoke가 동작한다.
|
||||
|
||||
## 완료 기준
|
||||
|
||||
- [ ] 한국장 pipeline을 깨지 않고 미국장 데이터를 추가한다.
|
||||
- [ ] market/currency/venue 차이가 domain model과 contracts에 일관되게 반영된다.
|
||||
- [ ] `bin/test`와 관련 worker 검증이 통과한다.
|
||||
- [ ] [us-instruments] 미국장 instrument identity와 provider symbol mapping이 있다. 검증: watchlist fixture가 `market=us`, `venue`, `currency=USD`, `asset_type` metadata와 provider symbol을 분리해 가진다.
|
||||
- [ ] [us-calendar] 미국장 timezone/currency/venue 기준이 있다. 검증: market/currency/venue 차이가 domain model과 contracts에 일관되게 반영된다.
|
||||
- [ ] [us-daily-bars] KIS를 초기 provider로 사용해 미국장 일봉 데이터를 normalized bar model로 저장한다. 검증: 한국장 pipeline을 깨지 않고 provider capability가 미국장 daily bar import를 허용한다.
|
||||
- [ ] [shared-query] 한국장과 미국장이 같은 query/contract pattern을 공유한다. 검증: market selector만 바꿔도 공통 query path를 사용한다.
|
||||
- [ ] [us-backtest] 미국장 daily data로 backtest fixture 또는 smoke가 동작한다. 검증: `bin/test`와 관련 worker 검증이 통과한다.
|
||||
|
||||
## 완료 리뷰
|
||||
|
||||
|
|
@ -66,6 +62,9 @@
|
|||
|
||||
- 관련 경로: `services/worker/`, `packages/domain/market/`, `packages/contracts/proto/alt/v1/market.proto`
|
||||
- 표준선(선택): 시장 추가는 provider adapter와 market metadata 확장으로 처리하고 백테스트 코어를 복제하지 않는다.
|
||||
- 표준선(선택): universe는 watchlist 중심 selector로 두고, ETF/개별주는 instrument metadata의 `asset_type`으로 열어둔다.
|
||||
- 표준선(선택): 초기 provider는 KIS로 고정하지만 provider abstraction은 유지한다. Provider는 `supported_markets`, `supported_venues`, `supported_asset_types`, `capabilities`를 선언하고, Kiwoom 같은 후속 provider는 한국장만 지원하더라도 같은 provider boundary에 붙일 수 있어야 한다.
|
||||
- 표준선(선택): provider와 market은 별도 축이다. KIS가 한국장/미국장을 모두 지원하더라도 `provider=KIS`가 곧 `market=us`를 뜻하지 않으며, Kiwoom 추가 시 `provider=Kiwoom`, `market=kr` 조합처럼 capability matching으로 선택한다.
|
||||
- 선행 작업: Korea Daily Data Foundation, Backtest Engine Baseline, Command-First Operator Workflow, Paper Trading Command Workflow
|
||||
- 후속 작업: Live Trading Boundary, Flutter Operator Console MVP
|
||||
- 확인 필요: 미국장 universe와 provider 우선순위
|
||||
- 확인 필요: 없음
|
||||
|
|
|
|||
|
|
@ -0,0 +1,165 @@
|
|||
<!-- task=m-us-market-expansion/01_market_foundation plan=0 tag=US_FOUNDATION -->
|
||||
|
||||
# Code Review Reference - US_FOUNDATION
|
||||
|
||||
> **[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 is review-agent-only.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-06-06
|
||||
task=m-us-market-expansion/01_market_foundation, plan=0, tag=US_FOUNDATION
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/trading-expansion/milestones/us-market-expansion.md`
|
||||
- Task ids:
|
||||
- `us-instruments`: 미국장 instrument identity와 provider symbol mapping
|
||||
- `us-calendar`: 미국장 timezone/currency/venue 기준
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요. 리뷰 완료는 판정 append, active plan/review log archive, PASS 시 `complete.log` 작성과 archive 이동까지 끝난 상태를 의미합니다. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터만 보고하고 roadmap 수정은 런타임 책임입니다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [US_FOUNDATION-1] Domain Asset Type And Market Metadata | [x] |
|
||||
| [US_FOUNDATION-2] Contract And Storage Asset Type Round Trip | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] [US_FOUNDATION-1] domain market model에 `AssetType`과 market/venue metadata를 추가하고 KR/US fixture vocabulary를 갱신한다. 검증: watchlist fixture가 `market=us`, `venue`, `currency=USD`, `asset_type`, provider symbol을 분리해 가진다.
|
||||
- [x] [US_FOUNDATION-2] contract와 PostgreSQL persistence가 `asset_type`을 round-trip하도록 schema/query/mapping/generated output을 갱신한다. 검증: market/currency/venue/asset_type 차이가 domain model과 contracts에 일관되게 반영된다.
|
||||
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
|
||||
- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
|
||||
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
|
||||
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다.
|
||||
- [x] `.gitignore`의 Agent-Ops 관리 block을 확인한다.
|
||||
- [ ] PASS이면 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [ ] PASS이면 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-us-market-expansion/01_market_foundation/`로 이동한다.
|
||||
- [ ] PASS이고 task group이 `m-us-market-expansion`이면 완료 이벤트 메타데이터를 보고한다.
|
||||
- [ ] PASS split 작업이면 이동 후 빈 active parent 처리 여부를 확인한다.
|
||||
- [x] WARN/FAIL이면 user-review gate 확인 뒤 다음 active plan/review 또는 `USER_REVIEW.md`를 작성한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
- 없음. 계획된 설계 및 구현 방법론을 온전히 따랐음.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- `packages/domain/market/types.go` 내에 `AssetType` (equity, etf)과 `VenueMetadata` 를 추가하고, Venue를 Key로 메타데이터를 리턴하는 `GetVenueMetadata` 헬퍼 함수를 추가함.
|
||||
- `common.proto`와 `market.proto`에 `AssetType` enum 및 `Instrument.asset_type` 필드를 반영하고, `bin/contracts-gen`을 통해 generated code를 갱신함.
|
||||
- Postgres DB migration에 `000003_instrument_asset_type` 마이그레이션(up/down)을 추가하여 `instruments` 테이블에 `asset_type` 컬럼을 생성하고, sqlc query 및 Go mapper/socket mapper 코드를 수정하여 round-trip 매핑이 정상 작동하도록 구현함.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `asset_type`이 provider symbol과 섞이지 않고 별도 field로 round-trip되는지 확인한다.
|
||||
- `.proto`와 generated output이 drift 없이 일치하는지 확인한다.
|
||||
- storage migration이 기존 rows를 깨지 않는 default/null 정책을 갖는지 확인한다.
|
||||
- US fixture가 equity와 ETF를 모두 표현하고 `currency=USD`를 보존하는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### US_FOUNDATION-1 중간 검증
|
||||
```text
|
||||
$ go test -count=1 ./packages/domain/...
|
||||
ok git.toki-labs.com/toki/alt/packages/domain/backtest 0.002s
|
||||
ok git.toki-labs.com/toki/alt/packages/domain/market 0.003s
|
||||
|
||||
$ go test -count=1 ./services/worker/internal/providers/kis
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/providers/kis 0.007s
|
||||
```
|
||||
|
||||
### US_FOUNDATION-2 중간 검증
|
||||
```text
|
||||
$ bin/contracts-gen
|
||||
(exit 0)
|
||||
|
||||
$ bin/contracts-check
|
||||
(exit 0)
|
||||
|
||||
$ go test -count=1 ./packages/contracts/gen/go/...
|
||||
? git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1 [no test files]
|
||||
|
||||
$ go test -count=1 ./services/worker/internal/storage/postgres ./services/worker/internal/socket
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/storage/postgres 0.073s
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/socket 0.061s
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```text
|
||||
$ bin/contracts-gen
|
||||
(exit 0)
|
||||
|
||||
$ bin/contracts-check
|
||||
(exit 0)
|
||||
|
||||
$ go test -count=1 ./packages/domain/...
|
||||
ok git.toki-labs.com/toki/alt/packages/domain/backtest 0.002s
|
||||
ok git.toki-labs.com/toki/alt/packages/domain/market 0.003s
|
||||
|
||||
$ go test -count=1 ./packages/contracts/gen/go/...
|
||||
? git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1 [no test files]
|
||||
|
||||
$ go test -count=1 ./services/worker/internal/storage/postgres ./services/worker/internal/socket ./services/worker/internal/providers/kis
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/storage/postgres 0.067s
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/socket 0.073s
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/providers/kis 0.016s
|
||||
|
||||
$ bin/worker-storage-check
|
||||
Generating worker storage code via sqlc...
|
||||
Generation complete.
|
||||
|
||||
$ bin/test
|
||||
All tests passed!
|
||||
|
||||
$ git diff --check
|
||||
(exit 0)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
- 종합 판정: FAIL
|
||||
- 차원별 평가:
|
||||
- Correctness: Pass
|
||||
- Completeness: Fail
|
||||
- Test coverage: Fail
|
||||
- API contract: Pass
|
||||
- Code quality: Pass
|
||||
- Plan deviation: Fail
|
||||
- Verification trust: Pass
|
||||
- 발견된 문제:
|
||||
- Required: [services/worker/internal/providers/kis/daily_itemchartprice_test.go](/config/workspace/alt/services/worker/internal/providers/kis/daily_itemchartprice_test.go:258)의 `TestProviderSymbolsFixtureVocabulary`는 각 instrument 값이 허용 enum 집합 안에 있는지만 확인합니다. 그래서 [services/worker/testdata/providers/kis/provider_symbols.sample.json](/config/workspace/alt/services/worker/testdata/providers/kis/provider_symbols.sample.json:5)에서 `AAPL`/`SPY` US watchlist 항목이나 `NASDAQ`/`USD`/`etf` fixture row가 제거되어도 KR equity fixture만 남아 있으면 테스트가 계속 통과합니다. `US_FOUNDATION-1`의 통합 검증 조건인 US watchlist, USD, venue, equity/ETF, KIS provider symbol 분리를 실제로 잠그도록 필수 instrument id와 field 값을 명시적으로 assert하세요.
|
||||
- 다음 단계: user-review gate는 트리거하지 않는다. active plan/review를 archive하고 `REVIEW_US_FOUNDATION` follow-up PLAN/CODE_REVIEW를 작성한다.
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
<!-- task=m-us-market-expansion/01_market_foundation plan=1 tag=REVIEW_US_FOUNDATION -->
|
||||
|
||||
# Code Review Reference - REVIEW_US_FOUNDATION
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> The task is NOT complete until every implementation-owned section below is filled in.
|
||||
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
|
||||
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
|
||||
> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts are normal follow-up issues, not user-review blockers by themselves.
|
||||
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record the needed decision in `사용자 리뷰 요청` and stop for code-review.
|
||||
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
|
||||
> Follow the ownership table at the bottom of this file for which sections you own.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-06-06
|
||||
task=m-us-market-expansion/01_market_foundation, plan=1, tag=REVIEW_US_FOUNDATION
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/trading-expansion/milestones/us-market-expansion.md`
|
||||
- Task ids:
|
||||
- `us-instruments`: 미국장 instrument identity와 provider symbol mapping
|
||||
- `us-calendar`: 미국장 timezone/currency/venue 기준
|
||||
- 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-us-market-expansion/01_market_foundation/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다.
|
||||
4. PASS이고 task group이 `m-us-market-expansion`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
|
||||
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [REVIEW_US_FOUNDATION-1] Strengthen US Fixture Vocabulary Assertions | [x] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [x] [REVIEW_US_FOUNDATION-1] KIS provider symbols fixture test가 US watchlist와 `NASDAQ`/`USD`/`equity`/`etf`/KIS provider symbol mapping을 필수 row 단위로 검증하도록 강화한다. 검증: `AAPL`/`SPY` 또는 ETF row가 제거되면 테스트가 실패해야 한다.
|
||||
- [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_local_G06_N.log`로 아카이브한다.
|
||||
- [x] active `PLAN-*-G??.md`를 `plan_local_G06_M.log`로 아카이브한다.
|
||||
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
|
||||
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
|
||||
- [x] PASS이면 active task 디렉터리 `agent-task/m-us-market-expansion/01_market_foundation/`를 `agent-task/archive/YYYY/MM/m-us-market-expansion/01_market_foundation/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
|
||||
- [x] PASS이고 task group이 `m-us-market-expansion`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
|
||||
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-us-market-expansion/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
|
||||
- [ ] 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로 이동한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
계획에 정확히 따라 구현했다. 변경 범위는 단일 worker fixture test와 관련 JSON fixture 검증에 한정된다.
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
- US instrument 검증에 `requiredUS` map을 사용해 `NASDAQ:AAPL`과 `NASDAQ:SPY`의 exact field 값을 검증하도록 했다.
|
||||
- watchlist selector의 US symbol 포함 여부(`AAPL`, `SPY`)도 assert하도록 했다.
|
||||
- enum 허용 범위는 기존과 동일하게 유지하되, US rows에 대해 추가 row-level 검증만 도입했다.
|
||||
- JSON fixture(`provider_symbols.sample.json`)는 assertion과 이미 일치하므로 수정하지 않았다.
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- `TestProviderSymbolsFixtureVocabulary`가 `NASDAQ:AAPL`과 `NASDAQ:SPY`의 exact field 값을 assert하는지 확인한다.
|
||||
- watchlist selector가 US symbols `AAPL`, `SPY`를 포함해야 테스트가 통과하는지 확인한다.
|
||||
- production mapper/schema/generated output이 불필요하게 바뀌지 않았는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### REVIEW_US_FOUNDATION-1 중간 검증
|
||||
```text
|
||||
$ go test -count=1 -run TestProviderSymbolsFixtureVocabulary ./services/worker/internal/providers/kis
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/providers/kis 0.004s
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```text
|
||||
$ go test -count=1 ./services/worker/internal/providers/kis
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/providers/kis 0.012s
|
||||
|
||||
$ go test -count=1 ./services/worker/...
|
||||
ok git.toki-labs.com/toki/alt/services/worker/cmd/alt-worker 0.005s
|
||||
? git.toki-labs.com/toki/alt/services/worker/cmd/alt-worker-data-check [no test files]
|
||||
ok git.toki-labs.com/toki/alt/services/worker/cmd/alt-worker-migrate 0.003s
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/backtest 0.002s
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/config 0.002s
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/contracts 0.013s
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/jobs 0.003s
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/marketdata/datacheck 0.009s
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/marketdata/importer 0.005s
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/papertrading 0.006s
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/providers/kis 0.011s
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/rediskeys 0.006s
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/socket 0.059s
|
||||
? git.toki-labs.com/toki/alt/services/worker/internal/storage [no test files]
|
||||
ok git.toki-labs.com/toki/alt/services/worker/internal/storage/postgres 0.058s
|
||||
? git.toki-labs.com/toki/alt/services/worker/internal/storage/postgres/sqlc [no test files]
|
||||
|
||||
$ bin/test
|
||||
? git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1 [no test files]
|
||||
ok git.toki-labs.com/toki/alt/packages/domain/backtest (cached)
|
||||
ok git.toki-labs.com/toki/alt/packages/domain/market (cached)
|
||||
? git.toki-labs.com/toki/alt/services/api/cmd/alt-api [no test files]
|
||||
[... Go package tests omitted for brevity ...]
|
||||
00:02 +60: All tests passed!
|
||||
|
||||
$ git diff --check
|
||||
(no output - no whitespace errors)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[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.
|
||||
|
||||
## 코드리뷰 결과
|
||||
|
||||
- 종합 판정: PASS
|
||||
- 차원별 평가:
|
||||
- Correctness: Pass
|
||||
- Completeness: Pass
|
||||
- Test coverage: Pass
|
||||
- API contract: Pass
|
||||
- Code quality: Pass
|
||||
- Plan deviation: Pass
|
||||
- Verification trust: Pass
|
||||
- 발견된 문제: 없음
|
||||
- 다음 단계: PASS로 `complete.log`를 작성하고 task directory를 archive로 이동한다.
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
# Complete - m-us-market-expansion/01_market_foundation
|
||||
|
||||
## 완료 일시
|
||||
|
||||
2026-06-06
|
||||
|
||||
## 요약
|
||||
|
||||
US market foundation fixture vocabulary follow-up까지 2회 리뷰 루프로 완료했으며 최종 판정은 PASS다.
|
||||
|
||||
## 루프 이력
|
||||
|
||||
| Plan | Review | Verdict | 메모 |
|
||||
|------|--------|---------|------|
|
||||
| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | FAIL | US watchlist/USD/ETF fixture 존재를 테스트가 정확히 잠그지 못해 follow-up 필요 |
|
||||
| `plan_local_G06_1.log` | `code_review_local_G06_1.log` | PASS | KIS provider symbols fixture test가 required US instruments와 watchlist symbols를 exact assertion으로 검증 |
|
||||
|
||||
## 구현/정리 내용
|
||||
|
||||
- `TestProviderSymbolsFixtureVocabulary`가 `NASDAQ:AAPL`과 `NASDAQ:SPY`의 market, venue, symbol, currency, asset_type, KIS provider symbol을 필수 row 단위로 검증하도록 보강했다.
|
||||
- watchlist selector가 `AAPL`과 `SPY`를 포함해야 통과하도록 검증을 추가했다.
|
||||
- 1차 구현의 domain/contract/storage/socket foundation과 follow-up fixture 검증이 모두 PASS 상태로 정리되었다.
|
||||
|
||||
## 최종 검증
|
||||
|
||||
- `go test -count=1 -run TestProviderSymbolsFixtureVocabulary ./services/worker/internal/providers/kis` - PASS; `ok git.toki-labs.com/toki/alt/services/worker/internal/providers/kis 0.003s`
|
||||
- `go test -count=1 ./services/worker/internal/providers/kis` - PASS; `ok git.toki-labs.com/toki/alt/services/worker/internal/providers/kis 0.015s`
|
||||
- `go test -count=1 ./services/worker/...` - PASS; all worker packages passed, no-test packages reported as `[no test files]`
|
||||
- `bin/test` - PASS; workspace Go and Flutter tests completed with `All tests passed!`
|
||||
- `git diff --check` - PASS; no whitespace errors
|
||||
|
||||
## Roadmap Completion
|
||||
|
||||
- Milestone: `agent-roadmap/phase/trading-expansion/milestones/us-market-expansion.md`
|
||||
- Completed task ids:
|
||||
- `us-instruments`: PASS; evidence=`agent-task/archive/2026/06/m-us-market-expansion/01_market_foundation/plan_local_G06_1.log`, `agent-task/archive/2026/06/m-us-market-expansion/01_market_foundation/code_review_local_G06_1.log`; verification=`go test -count=1 ./services/worker/internal/providers/kis`, `go test -count=1 ./services/worker/...`, `bin/test`
|
||||
- `us-calendar`: PASS; evidence=`agent-task/archive/2026/06/m-us-market-expansion/01_market_foundation/plan_cloud_G07_0.log`, `agent-task/archive/2026/06/m-us-market-expansion/01_market_foundation/code_review_cloud_G07_0.log`; verification=`go test -count=1 ./packages/domain/...`, `bin/test`
|
||||
- Not completed task ids: 없음
|
||||
|
||||
## 잔여 Nit
|
||||
|
||||
- 없음
|
||||
|
||||
## 후속 작업
|
||||
|
||||
- 없음
|
||||
|
|
@ -0,0 +1,248 @@
|
|||
<!-- task=m-us-market-expansion/01_market_foundation plan=0 tag=US_FOUNDATION -->
|
||||
|
||||
# Implementation Plan - US_FOUNDATION
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
구현 완료의 마지막 단계는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 결정, 검증 출력으로 채우는 것이다. 검증을 실행하고 실제 stdout/stderr를 붙이며, active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 구현 중 사용자만 결정할 수 있는 범위 변경, 외부 환경/secret 준비, 또는 계획 충돌이 생기면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 쓰고 멈춘다. 직접 사용자에게 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 재실행으로 모을 수 있는 증거 공백은 사용자 리뷰 요청이 아니다. finalization, log rename, `complete.log`, archive 이동은 code-review 전용이다.
|
||||
|
||||
## 배경
|
||||
|
||||
US Market Expansion은 한국장 market data 구조를 복제하지 않고 market metadata와 provider-neutral instrument model을 확장해야 한다. 현재 `MarketUS`, `VenueNASDAQ`, `VenueNYSE`, `CurrencyUSD` enum은 있지만 instrument에는 ETF/개별주를 구분할 `asset_type`이 없고, persistence/contract도 같은 정보를 보존하지 않는다. 이 선행 작업은 뒤의 KIS US import, shared query, US backtest가 같은 model을 쓰게 하는 foundation이다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
|
||||
구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 템플릿은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`와 동일하며, 구현 에이전트는 채팅으로 직접 질문하지 않는다. code-review가 해당 요청을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/trading-expansion/milestones/us-market-expansion.md`
|
||||
- Task ids:
|
||||
- `us-instruments`: 미국장 instrument identity와 provider symbol mapping
|
||||
- `us-calendar`: 미국장 timezone/currency/venue 기준
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-ops/rules/project/rules.md`
|
||||
- `agent-ops/rules/private/rules.md`
|
||||
- `agent-ops/rules/common/rules-roadmap.md`
|
||||
- `agent-ops/skills/common/router.md`
|
||||
- `agent-ops/skills/common/plan/SKILL.md`
|
||||
- `agent-roadmap/current.md`
|
||||
- `agent-roadmap/phase/trading-expansion/PHASE.md`
|
||||
- `agent-roadmap/phase/trading-expansion/milestones/us-market-expansion.md`
|
||||
- `agent-test/local/rules.md`
|
||||
- `agent-test/local/domain-model-smoke.md`
|
||||
- `agent-test/local/contracts-smoke.md`
|
||||
- `agent-test/local/worker-smoke.md`
|
||||
- `packages/domain/market/types.go`
|
||||
- `packages/domain/market/types_test.go`
|
||||
- `packages/contracts/proto/alt/v1/common.proto`
|
||||
- `packages/contracts/proto/alt/v1/market.proto`
|
||||
- `services/worker/internal/storage/ports.go`
|
||||
- `services/worker/internal/storage/postgres/migrations/000001_worker_backbone.up.sql`
|
||||
- `services/worker/internal/storage/postgres/queries/queries.sql`
|
||||
- `services/worker/internal/storage/postgres/mapping.go`
|
||||
- `services/worker/internal/storage/postgres/store.go`
|
||||
- `services/worker/internal/storage/postgres/mapping_test.go`
|
||||
- `services/worker/internal/storage/postgres/store_test.go`
|
||||
- `services/worker/testdata/providers/kis/provider_symbols.sample.json`
|
||||
- `services/worker/testdata/providers/kis/daily_bars_normalized.expected.json`
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
`test_env=local`. `agent-test/local/rules.md`는 존재하고 읽었다. 적용 profile은 domain-model-smoke, contracts-smoke, worker-smoke다. 적용 명령은 `go test ./packages/domain/...`, `.proto` 변경 시 `bin/contracts-gen`, `bin/contracts-check`, generated Go 변경 시 `go test ./packages/contracts/gen/go/...`, worker storage/schema 변경 시 `go test ./services/worker/...`와 `bin/worker-storage-check`, milestone confidence용 `bin/test`다. storage integration은 `DATABASE_URL` 또는 Docker-backed infra가 없으면 실행 차단/skip 사유를 review에 기록한다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- `Instrument`의 provider symbol round-trip은 storage mapping 일부로만 간접 검증된다. `asset_type` round-trip test가 필요하다.
|
||||
- US venue/currency enum은 contracts에 있지만 `asset_type` contract field는 없다. proto/generated drift 검증이 필요하다.
|
||||
- provider symbol fixture는 KR instruments만 있고 US watchlist/ETF/equity metadata가 없다. fixture vocabulary test 확장이 필요하다.
|
||||
- market timezone 기준은 KIS normalizer에 KST 상수로만 있다. domain-level market/venue metadata test가 필요하다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
Renamed/removed symbol: none. New symbols only: `AssetType`, `AssetTypeEquity`, `AssetTypeETF`, market/venue metadata helpers, provider capability vocabulary.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
Split decision policy를 먼저 평가했다. 공유 domain/contract/storage foundation, provider import, query surface, backtest smoke는 서로 다른 도메인과 risk profile을 가진다. 따라서 shared task group은 `m-us-market-expansion`이고 sibling은 `01_market_foundation`(독립), `02+01_kis_us_daily_bars`(01 의존), `03+01_shared_query_surface`(01 의존), `04+02,03_us_backtest_smoke`(02,03 의존)이다. 이 plan은 독립 선행 작업이라 predecessor 없음.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
이 작업은 domain/contract/storage/fixture foundation만 다룬다. KIS overseas endpoint, live REST client, operator scenario, backtest execution fixture는 후속 split에서 다룬다. 실거래 주문, live trading, Flutter UI는 milestone 범위 제외와 UI gate에 따라 제외한다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
`cloud-G07`: protobuf schema, generated code, storage migration/sqlc, shared domain model이 함께 바뀌는 cross-domain foundation이라 local보다 넓은 contract/storage 리뷰가 필요하다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [US_FOUNDATION-1] domain market model에 `AssetType`과 market/venue metadata를 추가하고 KR/US fixture vocabulary를 갱신한다. 검증: watchlist fixture가 `market=us`, `venue`, `currency=USD`, `asset_type`, provider symbol을 분리해 가진다.
|
||||
- [ ] [US_FOUNDATION-2] contract와 PostgreSQL persistence가 `asset_type`을 round-trip하도록 schema/query/mapping/generated output을 갱신한다. 검증: market/currency/venue/asset_type 차이가 domain model과 contracts에 일관되게 반영된다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [US_FOUNDATION-1] Domain Asset Type And Market Metadata
|
||||
|
||||
문제: [packages/domain/market/types.go](/config/workspace/alt/packages/domain/market/types.go:37)의 `Instrument`는 `Market`, `Venue`, `Currency`, `ProviderSymbols`만 보존해 ETF와 개별주를 열어둘 metadata가 없다. KIS fixture [services/worker/testdata/providers/kis/provider_symbols.sample.json](/config/workspace/alt/services/worker/testdata/providers/kis/provider_symbols.sample.json:1)도 KR instruments만 가진다.
|
||||
|
||||
해결 방법:
|
||||
|
||||
```go
|
||||
// Before packages/domain/market/types.go:37
|
||||
type Instrument struct {
|
||||
ID InstrumentID
|
||||
Market Market
|
||||
Venue Venue
|
||||
Symbol string
|
||||
Name string
|
||||
Currency Currency
|
||||
ProviderSymbols map[string]string
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// After
|
||||
type AssetType string
|
||||
|
||||
const (
|
||||
AssetTypeEquity AssetType = "equity"
|
||||
AssetTypeETF AssetType = "etf"
|
||||
)
|
||||
|
||||
type VenueMetadata struct {
|
||||
Venue Venue
|
||||
Market Market
|
||||
Currency Currency
|
||||
Timezone string
|
||||
}
|
||||
|
||||
type Instrument struct {
|
||||
ID InstrumentID
|
||||
Market Market
|
||||
Venue Venue
|
||||
Symbol string
|
||||
Name string
|
||||
Currency Currency
|
||||
AssetType AssetType
|
||||
ProviderSymbols map[string]string
|
||||
}
|
||||
```
|
||||
|
||||
수정 파일 및 체크리스트:
|
||||
|
||||
- [ ] `packages/domain/market/types.go`: `AssetType`, metadata helper, `Instrument.AssetType` 추가.
|
||||
- [ ] `packages/domain/market/types_test.go`: KR/US venue metadata, asset type vocabulary, KIS provider vocabulary test 추가.
|
||||
- [ ] `services/worker/testdata/providers/kis/provider_symbols.sample.json`: KR instruments에 `asset_type: "equity"` 추가, US watchlist selector와 `NASDAQ:AAPL` equity, `NASDAQ:SPY` ETF fixture 추가.
|
||||
- [ ] `services/worker/internal/providers/kis/daily_itemchartprice_test.go`: fixture view struct와 assertions에 `asset_type` 추가.
|
||||
|
||||
테스트 작성: 작성한다. `TestMarketVenueMetadata`와 `TestProviderSymbolsFixtureVocabulary` 확장으로 US watchlist, USD, NASDAQ/NYSE, equity/etf를 검증한다.
|
||||
|
||||
중간 검증:
|
||||
|
||||
```bash
|
||||
go test -count=1 ./packages/domain/...
|
||||
go test -count=1 ./services/worker/internal/providers/kis
|
||||
```
|
||||
|
||||
### [US_FOUNDATION-2] Contract And Storage Asset Type Round Trip
|
||||
|
||||
문제: [packages/contracts/proto/alt/v1/market.proto](/config/workspace/alt/packages/contracts/proto/alt/v1/market.proto:9)의 `Instrument`는 `provider_symbols = 7`까지만 있고 `asset_type`이 없다. storage schema [services/worker/internal/storage/postgres/migrations/000001_worker_backbone.up.sql](/config/workspace/alt/services/worker/internal/storage/postgres/migrations/000001_worker_backbone.up.sql:1), query [services/worker/internal/storage/postgres/queries/queries.sql](/config/workspace/alt/services/worker/internal/storage/postgres/queries/queries.sql:1), mapping [services/worker/internal/storage/postgres/mapping.go](/config/workspace/alt/services/worker/internal/storage/postgres/mapping.go:54)도 같은 field를 보존하지 않는다.
|
||||
|
||||
해결 방법:
|
||||
|
||||
```proto
|
||||
// Before packages/contracts/proto/alt/v1/market.proto:9
|
||||
message Instrument {
|
||||
string id = 1;
|
||||
Market market = 2;
|
||||
Venue venue = 3;
|
||||
string symbol = 4;
|
||||
string name = 5;
|
||||
Currency currency = 6;
|
||||
map<string, string> provider_symbols = 7;
|
||||
}
|
||||
```
|
||||
|
||||
```proto
|
||||
// After
|
||||
enum AssetType {
|
||||
ASSET_TYPE_UNSPECIFIED = 0;
|
||||
ASSET_TYPE_EQUITY = 1;
|
||||
ASSET_TYPE_ETF = 2;
|
||||
}
|
||||
|
||||
message Instrument {
|
||||
string id = 1;
|
||||
Market market = 2;
|
||||
Venue venue = 3;
|
||||
string symbol = 4;
|
||||
string name = 5;
|
||||
Currency currency = 6;
|
||||
map<string, string> provider_symbols = 7;
|
||||
AssetType asset_type = 8;
|
||||
}
|
||||
```
|
||||
|
||||
수정 파일 및 체크리스트:
|
||||
|
||||
- [ ] `packages/contracts/proto/alt/v1/common.proto` 또는 `market.proto`: `AssetType` enum 추가 위치를 기존 enum ownership에 맞춰 결정한다.
|
||||
- [ ] `packages/contracts/proto/alt/v1/market.proto`: `Instrument.asset_type = 8` 추가.
|
||||
- [ ] `bin/contracts-gen` 실행으로 generated Go/Dart contract를 갱신한다.
|
||||
- [ ] `services/worker/internal/storage/postgres/migrations/000003_instrument_asset_type.up.sql`/`.down.sql`: 기존 rows를 깨지 않도록 `asset_type TEXT NOT NULL DEFAULT ''` 또는 명시 default 정책 추가.
|
||||
- [ ] `services/worker/internal/storage/postgres/queries/queries.sql`: insert/select/update에 `asset_type` 포함.
|
||||
- [ ] `services/worker/internal/storage/postgres/sqlc/*.go`: sqlc generated output 갱신.
|
||||
- [ ] `services/worker/internal/storage/postgres/mapping.go`: domain <-> sqlc row mapping에 `AssetType` 추가.
|
||||
- [ ] `services/worker/internal/socket/market_mapping.go`: domain <-> proto mapping에 `AssetType` 추가.
|
||||
- [ ] `services/worker/internal/storage/postgres/mapping_test.go`: asset_type round-trip test 추가.
|
||||
- [ ] `services/worker/internal/socket/market_mapping_test.go`: proto mapping round-trip test 추가.
|
||||
|
||||
테스트 작성: 작성한다. storage mapping round-trip과 socket proto mapping에서 equity/etf/unspecified를 모두 확인한다.
|
||||
|
||||
중간 검증:
|
||||
|
||||
```bash
|
||||
bin/contracts-gen
|
||||
bin/contracts-check
|
||||
go test -count=1 ./packages/contracts/gen/go/...
|
||||
go test -count=1 ./services/worker/internal/storage/postgres ./services/worker/internal/socket
|
||||
```
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|---|---|
|
||||
| `packages/domain/market/types.go` | US_FOUNDATION-1 |
|
||||
| `packages/domain/market/types_test.go` | US_FOUNDATION-1 |
|
||||
| `services/worker/testdata/providers/kis/provider_symbols.sample.json` | US_FOUNDATION-1 |
|
||||
| `services/worker/internal/providers/kis/daily_itemchartprice_test.go` | US_FOUNDATION-1 |
|
||||
| `packages/contracts/proto/alt/v1/common.proto` | US_FOUNDATION-2 |
|
||||
| `packages/contracts/proto/alt/v1/market.proto` | US_FOUNDATION-2 |
|
||||
| `packages/contracts/gen/**` | US_FOUNDATION-2 |
|
||||
| `services/worker/internal/storage/postgres/migrations/*.sql` | US_FOUNDATION-2 |
|
||||
| `services/worker/internal/storage/postgres/queries/queries.sql` | US_FOUNDATION-2 |
|
||||
| `services/worker/internal/storage/postgres/sqlc/*.go` | US_FOUNDATION-2 |
|
||||
| `services/worker/internal/storage/postgres/mapping.go` | US_FOUNDATION-2 |
|
||||
| `services/worker/internal/storage/postgres/mapping_test.go` | US_FOUNDATION-2 |
|
||||
| `services/worker/internal/socket/market_mapping.go` | US_FOUNDATION-2 |
|
||||
| `services/worker/internal/socket/market_mapping_test.go` | US_FOUNDATION-2 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
bin/contracts-gen
|
||||
bin/contracts-check
|
||||
go test -count=1 ./packages/domain/...
|
||||
go test -count=1 ./packages/contracts/gen/go/...
|
||||
go test -count=1 ./services/worker/internal/storage/postgres ./services/worker/internal/socket ./services/worker/internal/providers/kis
|
||||
bin/worker-storage-check
|
||||
bin/test
|
||||
git diff --check
|
||||
```
|
||||
|
||||
`go test` cache는 허용하지 않는다. `bin/worker-storage-check`가 infra 준비 부족으로 실행 불가하면 `command -v`/환경 확인 출력과 함께 차단 또는 skip 근거를 review에 기록한다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
<!-- task=m-us-market-expansion/01_market_foundation plan=1 tag=REVIEW_US_FOUNDATION -->
|
||||
|
||||
# Implementation Plan - REVIEW_US_FOUNDATION
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
구현 완료의 마지막 단계는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 결정, 검증 출력으로 채우는 것이다. 검증을 실행하고 실제 stdout/stderr를 붙이며, active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 구현 중 사용자만 결정할 수 있는 범위 변경, 외부 환경/secret 준비, 또는 계획 충돌이 생기면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 쓰고 멈춘다. 직접 사용자에게 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 재실행으로 모을 수 있는 증거 공백은 사용자 리뷰 요청이 아니다. finalization, log rename, `complete.log`, archive 이동은 code-review 전용이다.
|
||||
|
||||
## 배경
|
||||
|
||||
이 리뷰는 `US_FOUNDATION` 1차 구현에서 production contract/storage round-trip과 검증 명령은 통과했지만, provider symbols fixture 테스트가 계획의 핵심 조건을 충분히 잠그지 못한 문제를 다룬다. 현재 fixture에는 `AAPL`/`SPY`, `USD`, `equity`/`etf`가 들어 있지만 테스트는 허용 enum 집합만 확인하므로 US/ETF row가 사라져도 통과할 수 있다. 이 follow-up은 fixture vocabulary 검증만 강화한다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
|
||||
구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 템플릿은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`와 동일하며, 구현 에이전트는 채팅으로 직접 질문하지 않는다. code-review가 해당 요청을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/trading-expansion/milestones/us-market-expansion.md`
|
||||
- Task ids:
|
||||
- `us-instruments`: 미국장 instrument identity와 provider symbol mapping
|
||||
- `us-calendar`: 미국장 timezone/currency/venue 기준
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-ops/rules/project/rules.md`
|
||||
- `agent-ops/rules/private/rules.md`
|
||||
- `agent-ops/rules/common/rules-roadmap.md`
|
||||
- `agent-ops/skills/common/router.md`
|
||||
- `agent-ops/skills/common/code-review/SKILL.md`
|
||||
- `agent-ops/skills/common/plan/SKILL.md`
|
||||
- `agent-test/local/rules.md`
|
||||
- `agent-test/local/worker-smoke.md`
|
||||
- `agent-ops/rules/project/domain/worker/rules.md`
|
||||
- `agent-task/m-us-market-expansion/01_market_foundation/plan_cloud_G07_0.log`
|
||||
- `agent-task/m-us-market-expansion/01_market_foundation/code_review_cloud_G07_0.log`
|
||||
- `services/worker/internal/providers/kis/daily_itemchartprice_test.go`
|
||||
- `services/worker/testdata/providers/kis/provider_symbols.sample.json`
|
||||
- `packages/domain/market/types_test.go`
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
`test_env=local`. `agent-test/local/rules.md`와 `agent-test/local/worker-smoke.md`를 읽었다. 적용 명령은 worker test 변경 기준 `go test -count=1 ./services/worker/internal/providers/kis`, 필요 시 `go test -count=1 ./services/worker/...`, milestone confidence용 `bin/test`, whitespace 확인용 `git diff --check`다. 이 follow-up은 `.proto`, generated output, sqlc output을 바꾸지 않는다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- `TestProviderSymbolsFixtureVocabulary`는 venue/currency/asset_type이 허용 목록에 속하는지만 확인한다. `AAPL`/`SPY` US watchlist coverage, `NASDAQ`/`USD`, `equity`와 `etf`의 동시 존재, KIS provider symbol mapping을 필수 row 단위로 검증하지 않는다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
Renamed/removed symbol: none.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
기존 split task `m-us-market-expansion/01_market_foundation` 안의 리뷰 follow-up이다. 변경 범위가 단일 worker fixture test와 관련 JSON fixture 검증에 한정되어 새 subtask split은 만들지 않는다. sibling `02+01_kis_us_daily_bars`, `03+01_shared_query_surface`, `04+02,03_us_backtest_smoke`의 active state는 건드리지 않는다.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
Production `AssetType` domain/contract/storage/socket mapping, migration, generated contract output은 1차 구현과 재검증에서 통과했으므로 이 follow-up 범위에서 제외한다. fixture 내용 자체는 이미 목표 데이터를 담고 있으므로, 테스트를 강화하다가 fixture 불일치가 드러난 경우에만 `provider_symbols.sample.json`을 최소 수정한다. KIS live import, CLI query surface, backtest smoke, roadmap 문서는 제외한다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
`local-G06`: repo 안에서 결정적으로 재현 가능한 test coverage follow-up이고 변경 파일이 bounded worker test/fixture에 한정된다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [REVIEW_US_FOUNDATION-1] KIS provider symbols fixture test가 US watchlist와 `NASDAQ`/`USD`/`equity`/`etf`/KIS provider symbol mapping을 필수 row 단위로 검증하도록 강화한다. 검증: `AAPL`/`SPY` 또는 ETF row가 제거되면 테스트가 실패해야 한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
### [REVIEW_US_FOUNDATION-1] Strengthen US Fixture Vocabulary Assertions
|
||||
|
||||
문제: [services/worker/internal/providers/kis/daily_itemchartprice_test.go](/config/workspace/alt/services/worker/internal/providers/kis/daily_itemchartprice_test.go:258)의 `TestProviderSymbolsFixtureVocabulary`는 fixture instrument가 허용 venue/currency/asset_type 중 하나인지 확인하지만, [services/worker/testdata/providers/kis/provider_symbols.sample.json](/config/workspace/alt/services/worker/testdata/providers/kis/provider_symbols.sample.json:45)의 US rows가 실제로 존재해야 한다는 조건은 확인하지 않는다.
|
||||
|
||||
해결 방법:
|
||||
|
||||
```go
|
||||
// Before services/worker/internal/providers/kis/daily_itemchartprice_test.go:258
|
||||
for i, inst := range fx.Instruments {
|
||||
v := market.Venue(inst.Venue)
|
||||
if v != market.VenueKRX && v != market.VenueNASDAQ && v != market.VenueNYSE {
|
||||
t.Errorf("instrument[%d] unexpected venue: %q", i, inst.Venue)
|
||||
}
|
||||
c := market.Currency(inst.Currency)
|
||||
if c != market.CurrencyKRW && c != market.CurrencyUSD {
|
||||
t.Errorf("instrument[%d] unexpected currency: %q", i, inst.Currency)
|
||||
}
|
||||
at := market.AssetType(inst.AssetType)
|
||||
if at != market.AssetTypeEquity && at != market.AssetTypeETF {
|
||||
t.Errorf("instrument[%d] unexpected asset type: %q", i, inst.AssetType)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// After
|
||||
required := map[string]struct {
|
||||
market market.Market
|
||||
venue market.Venue
|
||||
symbol string
|
||||
currency market.Currency
|
||||
assetType market.AssetType
|
||||
}{
|
||||
"NASDAQ:AAPL": {market.MarketUS, market.VenueNASDAQ, "AAPL", market.CurrencyUSD, market.AssetTypeEquity},
|
||||
"NASDAQ:SPY": {market.MarketUS, market.VenueNASDAQ, "SPY", market.CurrencyUSD, market.AssetTypeETF},
|
||||
}
|
||||
```
|
||||
|
||||
수정 파일 및 체크리스트:
|
||||
|
||||
- [ ] `services/worker/internal/providers/kis/daily_itemchartprice_test.go`: fixture instruments를 id map으로 만들고 `NASDAQ:AAPL`, `NASDAQ:SPY`의 market/venue/symbol/currency/asset_type/provider symbol 값을 정확히 assert한다.
|
||||
- [ ] `services/worker/internal/providers/kis/daily_itemchartprice_test.go`: watchlist selector가 `AAPL`과 `SPY`를 포함하는지 assert한다.
|
||||
- [ ] `services/worker/testdata/providers/kis/provider_symbols.sample.json`: 강화된 assertion과 fixture가 불일치할 때만 최소 수정한다.
|
||||
|
||||
테스트 작성: 작성한다. `TestProviderSymbolsFixtureVocabulary`에 exact US fixture expectations와 watchlist symbol coverage assertion을 추가한다.
|
||||
|
||||
중간 검증:
|
||||
|
||||
```bash
|
||||
go test -count=1 ./services/worker/internal/providers/kis
|
||||
```
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|---|---|
|
||||
| `services/worker/internal/providers/kis/daily_itemchartprice_test.go` | REVIEW_US_FOUNDATION-1 |
|
||||
| `services/worker/testdata/providers/kis/provider_symbols.sample.json` | REVIEW_US_FOUNDATION-1 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./services/worker/internal/providers/kis
|
||||
go test -count=1 ./services/worker/...
|
||||
bin/test
|
||||
git diff --check
|
||||
```
|
||||
|
||||
`go test` cache는 허용하지 않는다. `bin/test`는 workspace confidence 확인이며 일부 Go package가 cache를 출력해도, 앞선 `-count=1` worker 검증이 이 follow-up의 fresh 검증이다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
<!-- task=m-us-market-expansion/02+01_kis_us_daily_bars plan=0 tag=KIS_US_DAILY -->
|
||||
|
||||
# Code Review Reference - KIS_US_DAILY
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
|
||||
> Complete implementation-owned sections, then stop with active files in place and report ready for review. Do not finalize, archive, write `complete.log`, ask the user directly, or call `request_user_input`.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-06-06
|
||||
task=m-us-market-expansion/02+01_kis_us_daily_bars, plan=0, tag=KIS_US_DAILY
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/trading-expansion/milestones/us-market-expansion.md`
|
||||
- Task ids:
|
||||
- `us-daily-bars`: KIS를 초기 provider로 사용해 미국장 일봉 데이터를 normalized bar model로 저장
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다.
|
||||
|
||||
각 항목의 구현을 실제 소스 파일과 대조하고 검증 출력이 코드와 일치하는지 확인한다. PASS이면 완료 이벤트 메타데이터만 보고하고 roadmap 수정은 런타임 책임이다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [KIS_US_DAILY-1] Provider Capability Gate | [ ] |
|
||||
| [KIS_US_DAILY-2] KIS Overseas Daily Normalization | [ ] |
|
||||
| [KIS_US_DAILY-3] Worker Import Path For US Watchlists | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [KIS_US_DAILY-1] provider capability를 도입해 KIS가 KR/US daily bars 지원 범위를 선언하고 unsupported market/venue 조합을 import 전에 거절한다. 검증: 한국장 pipeline을 깨지 않고 provider capability가 미국장 daily bar import를 허용한다.
|
||||
- [ ] [KIS_US_DAILY-2] KIS US daily bar fixture decoder/normalizer를 추가해 USD/NASDAQ/NYSE bar를 normalized model로 만든다.
|
||||
- [ ] [KIS_US_DAILY-3] worker socket/job/importer 경로가 US watchlist import request를 KIS provider로 전달하고 instrument/bar upsert count를 보존한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
|
||||
|
||||
- [ ] 판정을 append한다.
|
||||
- [ ] active review/plan 파일을 `*.log`로 아카이브한다.
|
||||
- [ ] `.gitignore` Agent-Ops block을 확인한다.
|
||||
- [ ] PASS이면 `complete.log`를 작성하고 task directory를 archive로 이동한다.
|
||||
- [ ] PASS이면 완료 이벤트 메타데이터를 보고한다.
|
||||
- [ ] WARN/FAIL이면 다음 active plan/review 또는 `USER_REVIEW.md`를 작성한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- provider capability가 `provider != market` 원칙을 지키는지 확인한다.
|
||||
- KIS US fixture가 endpoint/TR_ID/field mapping을 test로 잠그는지 확인한다.
|
||||
- KR existing daily import tests가 계속 통과하는지 확인한다.
|
||||
- live secret 없이 deterministic fixture/unit 검증만으로 PASS를 주장하는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### KIS_US_DAILY-1 중간 검증
|
||||
```text
|
||||
$ go test -count=1 ./packages/domain/... ./services/worker/internal/jobs
|
||||
(output)
|
||||
```
|
||||
|
||||
### KIS_US_DAILY-2 중간 검증
|
||||
```text
|
||||
$ go test -count=1 ./services/worker/internal/providers/kis
|
||||
(output)
|
||||
```
|
||||
|
||||
### KIS_US_DAILY-3 중간 검증
|
||||
```text
|
||||
$ go test -count=1 ./services/worker/internal/socket ./services/worker/internal/marketdata/importer ./services/worker/internal/jobs
|
||||
(output)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```text
|
||||
$ go test -count=1 ./packages/domain/...
|
||||
(output)
|
||||
|
||||
$ go test -count=1 ./services/worker/internal/providers/kis ./services/worker/internal/marketdata/importer ./services/worker/internal/jobs ./services/worker/internal/socket
|
||||
(output)
|
||||
|
||||
$ go test -count=1 ./services/worker/...
|
||||
(output)
|
||||
|
||||
$ bin/test
|
||||
(output)
|
||||
|
||||
$ git diff --check
|
||||
(output)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
|
|
@ -0,0 +1,180 @@
|
|||
<!-- task=m-us-market-expansion/02+01_kis_us_daily_bars plan=0 tag=KIS_US_DAILY -->
|
||||
|
||||
# Implementation Plan - KIS_US_DAILY
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
구현 완료의 마지막 단계는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 결정, 검증 출력으로 채우는 것이다. `01_market_foundation`의 `complete.log`가 없으면 구현을 시작하지 말고 active review stub에 blocker를 기록한다. 사용자-only 결정이 필요하면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 쓰고 멈춘다. 직접 사용자에게 질문하지 않는다. finalization은 code-review 전용이다.
|
||||
|
||||
## 배경
|
||||
|
||||
초기 US data provider는 KIS로 고정됐지만, provider가 곧 market을 뜻하지 않도록 capability boundary를 유지해야 한다. 현재 KIS daily chart code는 domestic endpoint와 KST timestamp 전제만 갖는다. 이 작업은 KIS의 US daily bar path를 fixture-first로 추가하고, provider capability가 KR/US 지원 범위를 명시하게 한다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
|
||||
구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 후속 에이전트가 docs 확인, fixture 수집, 명령 재실행으로 해결할 수 있는 증거 공백은 사용자 리뷰 요청이 아니다. code-review가 실제 `USER_REVIEW.md` 작성 여부를 결정한다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/trading-expansion/milestones/us-market-expansion.md`
|
||||
- Task ids:
|
||||
- `us-daily-bars`: KIS를 초기 provider로 사용해 미국장 일봉 데이터를 normalized bar model로 저장
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-test/local/rules.md`
|
||||
- `agent-test/local/domain-model-smoke.md`
|
||||
- `agent-test/local/worker-smoke.md`
|
||||
- `packages/domain/market/types.go`
|
||||
- `services/worker/internal/providers/kis/daily_itemchartprice.go`
|
||||
- `services/worker/internal/providers/kis/daily_itemchartprice_test.go`
|
||||
- `services/worker/internal/marketdata/importer/importer.go`
|
||||
- `services/worker/internal/marketdata/importer/importer_test.go`
|
||||
- `services/worker/internal/jobs/marketdata_jobs.go`
|
||||
- `services/worker/internal/jobs/marketdata_jobs_test.go`
|
||||
- `services/worker/internal/socket/market.go`
|
||||
- `services/worker/internal/socket/market_test.go`
|
||||
- `services/worker/testdata/providers/kis/daily_itemchartprice_request.sample.json`
|
||||
- `services/worker/testdata/providers/kis/daily_itemchartprice_response.sample.json`
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
`test_env=local`. worker-smoke와 domain-model-smoke를 적용한다. worker 변경의 기본 검증은 `go test ./services/worker/...`, provider/importer/job/socket focused 검증은 `go test -count=1`로 실행한다. live KIS credential이 필요한 검증은 범위 밖이며 tracked file에 secret을 쓰지 않는다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- KIS normalizer는 domestic response fixture만 검증한다.
|
||||
- importer/job/socket은 provider/selector/date pass-through는 검증하지만 provider capability로 market/venue 지원 여부를 검증하지 않는다.
|
||||
- US daily bars가 USD/NASDAQ timestamp/currency로 저장되는 integration path가 없다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
Renamed/removed symbol: none. New symbols expected: KIS overseas daily request/response structs, KIS daily capability declaration, capability validation helper.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
Shared task group: `m-us-market-expansion`. This subtask is `02+01_kis_us_daily_bars` and depends on `01_market_foundation`. Predecessor status at plan creation: missing active/archived `complete.log`; implementation must wait for `agent-task/m-us-market-expansion/01_market_foundation/complete.log` or matching archived complete log.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
이 작업은 KIS US daily bars와 provider capability만 다룬다. shared query fixtures와 backtest smoke는 후속 split에서 다룬다. live credential smoke와 real REST 호출은 private/env 준비가 필요하므로 fixture/unit 중심으로 제한한다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
`cloud-G07`: external provider shape, capability boundary, worker import pipeline이 연결되어 있고 KIS API fixture 해석 리스크가 있다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [KIS_US_DAILY-1] provider capability를 도입해 KIS가 KR/US daily bars 지원 범위를 선언하고 unsupported market/venue 조합을 import 전에 거절한다. 검증: 한국장 pipeline을 깨지 않고 provider capability가 미국장 daily bar import를 허용한다.
|
||||
- [ ] [KIS_US_DAILY-2] KIS US daily bar fixture decoder/normalizer를 추가해 USD/NASDAQ/NYSE bar를 normalized model로 만든다.
|
||||
- [ ] [KIS_US_DAILY-3] worker socket/job/importer 경로가 US watchlist import request를 KIS provider로 전달하고 instrument/bar upsert count를 보존한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
`02+01_kis_us_daily_bars`는 디렉터리명 기준으로 `01_market_foundation`의 `complete.log`가 필요하다. 추가 hidden dependency를 만들지 않는다.
|
||||
|
||||
### [KIS_US_DAILY-1] Provider Capability Gate
|
||||
|
||||
문제: [services/worker/internal/jobs/marketdata_jobs.go](/config/workspace/alt/services/worker/internal/jobs/marketdata_jobs.go:106)은 `expectedProvider` 문자열만 비교하고, provider가 특정 market/venue/daily-bars capability를 지원하는지 확인하지 않는다. Kiwoom 같은 후속 provider가 KR만 지원해도 같은 boundary에 붙일 방법이 약하다.
|
||||
|
||||
해결 방법:
|
||||
|
||||
```go
|
||||
// Before services/worker/internal/jobs/marketdata_jobs.go:112
|
||||
if market.Provider(p.Provider) != expectedProvider {
|
||||
return fmt.Errorf("daily bar import payload: provider %q does not match expected %q", p.Provider, expectedProvider)
|
||||
}
|
||||
```
|
||||
|
||||
```go
|
||||
// After
|
||||
if err := capability.RequireDailyBars(market.Provider(p.Provider), p.selector()); err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
수정 파일 및 체크리스트:
|
||||
|
||||
- [ ] `packages/domain/market/types.go` 또는 worker provider package: provider capability vocabulary와 matcher 추가.
|
||||
- [ ] `services/worker/internal/providers/kis/*`: KIS capability가 KR/KRX와 US/NASDAQ/NYSE daily bars를 지원한다고 선언.
|
||||
- [ ] `services/worker/internal/jobs/marketdata_jobs.go`: provider mismatch와 unsupported market/venue/capability를 분리해 validation.
|
||||
- [ ] `services/worker/internal/jobs/marketdata_jobs_test.go`: KIS US 허용, unsupported provider/venue 거절, KR 기존 payload 유지 test 추가.
|
||||
|
||||
테스트 작성: 작성한다. Kiwoom 구현은 하지 않지만 test fixture에서 KR-only fake capability가 US를 거절하는 사례를 둔다.
|
||||
|
||||
중간 검증:
|
||||
|
||||
```bash
|
||||
go test -count=1 ./packages/domain/... ./services/worker/internal/jobs
|
||||
```
|
||||
|
||||
### [KIS_US_DAILY-2] KIS Overseas Daily Normalization
|
||||
|
||||
문제: [services/worker/internal/providers/kis/daily_itemchartprice.go](/config/workspace/alt/services/worker/internal/providers/kis/daily_itemchartprice.go:14)는 domestic endpoint/TR_ID만 갖고, [lines 89-97](/config/workspace/alt/services/worker/internal/providers/kis/daily_itemchartprice.go:89)는 KST/KRW 전제를 적용한다.
|
||||
|
||||
해결 방법: fixture-first로 overseas daily request/response structs를 추가한다. endpoint/TR_ID는 구현 시 official KIS docs 또는 captured fixture와 대조해 확정한다. 검색 후보는 `/uapi/overseas-price/v1/quotations/dailyprice`, `HHDFS76240000`이지만, 코드에는 fixture assertion으로 잠근다. timestamp는 domain venue metadata의 timezone을 사용하고 price currency는 instrument currency를 사용한다.
|
||||
|
||||
수정 파일 및 체크리스트:
|
||||
|
||||
- [ ] `services/worker/internal/providers/kis/daily_itemchartprice.go`: domestic/overseas decoder와 normalizer를 분리하거나 공통 `NormalizeDailyBars`가 venue metadata를 사용하도록 확장.
|
||||
- [ ] `services/worker/testdata/providers/kis/*`: US daily request/response/expected normalized fixture 추가.
|
||||
- [ ] `services/worker/internal/providers/kis/daily_itemchartprice_test.go`: USD/NASDAQ/NYSE fixture decode와 normalized bar assertion 추가.
|
||||
|
||||
테스트 작성: 작성한다. AAPL 또는 SPY fixture로 `Instrument.ID=NASDAQ:AAPL`, `Currency=USD`, `AssetType` 보존, timestamp timezone을 검증한다.
|
||||
|
||||
중간 검증:
|
||||
|
||||
```bash
|
||||
go test -count=1 ./services/worker/internal/providers/kis
|
||||
```
|
||||
|
||||
### [KIS_US_DAILY-3] Worker Import Path For US Watchlists
|
||||
|
||||
문제: [services/worker/internal/socket/market.go](/config/workspace/alt/services/worker/internal/socket/market.go:69)은 US enum을 변환할 수 있지만 tests의 `validImportRequest()`는 KR/KRX만 다룬다. importer tests도 KR sample data 중심이다.
|
||||
|
||||
해결 방법: US watchlist request를 socket/job/importer tests에 추가한다. provider가 US instrument와 USD bars를 반환하면 importer가 instrument first, bars after 순서로 storage upsert count를 보존해야 한다.
|
||||
|
||||
수정 파일 및 체크리스트:
|
||||
|
||||
- [ ] `services/worker/internal/socket/market_test.go`: `Market_US` + `Venue_NASDAQ` import conversion test 추가.
|
||||
- [ ] `services/worker/internal/marketdata/importer/importer_test.go`: US sample bars와 asset_type persistence path 추가.
|
||||
- [ ] `services/worker/internal/jobs/marketdata_jobs_test.go`: payload `market=US`, `venue=NASDAQ`, `symbols=["AAPL"]` dispatch test 추가.
|
||||
|
||||
테스트 작성: 작성한다. live provider 호출 없이 fake provider/store로 deterministic count와 request conversion을 검증한다.
|
||||
|
||||
중간 검증:
|
||||
|
||||
```bash
|
||||
go test -count=1 ./services/worker/internal/socket ./services/worker/internal/marketdata/importer ./services/worker/internal/jobs
|
||||
```
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|---|---|
|
||||
| `packages/domain/market/types.go` | KIS_US_DAILY-1 |
|
||||
| `services/worker/internal/providers/kis/daily_itemchartprice.go` | KIS_US_DAILY-1, KIS_US_DAILY-2 |
|
||||
| `services/worker/internal/providers/kis/daily_itemchartprice_test.go` | KIS_US_DAILY-2 |
|
||||
| `services/worker/testdata/providers/kis/*` | KIS_US_DAILY-2 |
|
||||
| `services/worker/internal/jobs/marketdata_jobs.go` | KIS_US_DAILY-1 |
|
||||
| `services/worker/internal/jobs/marketdata_jobs_test.go` | KIS_US_DAILY-1, KIS_US_DAILY-3 |
|
||||
| `services/worker/internal/socket/market_test.go` | KIS_US_DAILY-3 |
|
||||
| `services/worker/internal/marketdata/importer/importer_test.go` | KIS_US_DAILY-3 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./packages/domain/...
|
||||
go test -count=1 ./services/worker/internal/providers/kis ./services/worker/internal/marketdata/importer ./services/worker/internal/jobs ./services/worker/internal/socket
|
||||
go test -count=1 ./services/worker/...
|
||||
bin/test
|
||||
git diff --check
|
||||
```
|
||||
|
||||
`go test` cache는 허용하지 않는다. live KIS credential 검증은 이 plan의 pass condition이 아니다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,104 @@
|
|||
<!-- task=m-us-market-expansion/03+01_shared_query_surface plan=0 tag=US_QUERY -->
|
||||
|
||||
# Code Review Reference - US_QUERY
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is mandatory.**
|
||||
> Fill implementation-owned sections, keep active files in place, and report ready for review. Do not ask the user directly or finalize the loop.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-06-06
|
||||
task=m-us-market-expansion/03+01_shared_query_surface, plan=0, tag=US_QUERY
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/trading-expansion/milestones/us-market-expansion.md`
|
||||
- Task ids:
|
||||
- `shared-query`: 한국장과 미국장이 같은 query/contract pattern을 공유
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다.
|
||||
|
||||
각 항목의 구현과 검증 출력을 대조한다. PASS이면 completion metadata만 보고하고 roadmap update는 런타임에 맡긴다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [US_QUERY-1] API And Worker US Query Contract Tests | [ ] |
|
||||
| [US_QUERY-2] Operator US Headless Query Fixture | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [US_QUERY-1] API/worker socket tests가 `Market_US`와 provider filter로 US instruments를 같은 ListInstruments contract에서 조회하는지 검증한다. 검증: market selector만 바꿔도 공통 query path를 사용한다.
|
||||
- [ ] [US_QUERY-2] CLI headless fixture에 US market data status query를 추가하고 expected JSONL을 잠근다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
- [ ] 판정을 append한다.
|
||||
- [ ] active review/plan 파일을 `*.log`로 아카이브한다.
|
||||
- [ ] PASS이면 `complete.log` 작성 후 archive 이동한다.
|
||||
- [ ] PASS이면 완료 이벤트 메타데이터를 보고한다.
|
||||
- [ ] WARN/FAIL이면 후속 plan/review 또는 `USER_REVIEW.md`를 작성한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- 새 query API를 만들지 않고 기존 contract path를 쓰는지 확인한다.
|
||||
- `market: us`만 바꿔 동일한 operator workflow가 실행되는지 확인한다.
|
||||
- expected JSONL이 실제 runner output과 일치하는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### US_QUERY-1 중간 검증
|
||||
```text
|
||||
$ go test -count=1 ./services/api/internal/socket ./services/worker/internal/socket
|
||||
(output)
|
||||
```
|
||||
|
||||
### US_QUERY-2 중간 검증
|
||||
```text
|
||||
$ go test -count=1 ./apps/cli/internal/operator
|
||||
(output)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```text
|
||||
$ go test -count=1 ./services/api/internal/socket ./services/worker/internal/socket ./apps/cli/internal/operator
|
||||
(output)
|
||||
|
||||
$ go test -count=1 ./services/api/... ./services/worker/...
|
||||
(output)
|
||||
|
||||
$ bin/test
|
||||
(output)
|
||||
|
||||
$ git diff --check
|
||||
(output)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
<!-- task=m-us-market-expansion/03+01_shared_query_surface plan=0 tag=US_QUERY -->
|
||||
|
||||
# Implementation Plan - US_QUERY
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
구현 완료의 마지막 단계는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이다. `01_market_foundation` 완료 전에는 구현하지 않는다. 사용자-only 결정이 필요하면 review stub의 `사용자 리뷰 요청` 섹션에 기록하고 멈춘다. finalization은 code-review 전용이다.
|
||||
|
||||
## 배경
|
||||
|
||||
US instruments와 bars가 들어와도 operator/API/worker read surface가 KR과 다른 경로를 쓰면 milestone의 shared-query 목표를 만족하지 못한다. 현재 API와 worker는 US enum을 대부분 받아들이지만, headless scenario와 focused tests는 KR 중심이다. 이 작업은 market selector만 바꿔 같은 query pattern을 쓰는 것을 fixtures와 tests로 잠근다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
|
||||
구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 에이전트는 사용자에게 직접 질문하지 않는다. code-review가 실제 user-review stop 여부를 판단한다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/trading-expansion/milestones/us-market-expansion.md`
|
||||
- Task ids:
|
||||
- `shared-query`: 한국장과 미국장이 같은 query/contract pattern을 공유
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-test/local/rules.md`
|
||||
- `agent-test/local/api-smoke.md`
|
||||
- `agent-test/local/worker-smoke.md`
|
||||
- `agent-test/local/operations-smoke.md`
|
||||
- `services/api/internal/socket/market.go`
|
||||
- `services/api/internal/socket/market_test.go`
|
||||
- `services/worker/internal/socket/market.go`
|
||||
- `services/worker/internal/socket/market_mapping.go`
|
||||
- `services/worker/internal/socket/market_test.go`
|
||||
- `apps/cli/internal/operator/scenario.go`
|
||||
- `apps/cli/internal/operator/runner.go`
|
||||
- `apps/cli/internal/operator/scenario_test.go`
|
||||
- `apps/cli/internal/operator/runner_market_test.go`
|
||||
- `apps/cli/testdata/operator/market_data_status_query.yaml`
|
||||
- `apps/cli/testdata/operator/expected/market_data_status_query.jsonl`
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
`test_env=local`. api-smoke, worker-smoke, operations-smoke를 적용한다. API socket boundary는 `go test ./services/api/...`, worker socket은 `go test ./services/worker/...`, CLI operator는 package focused `go test -count=1 ./apps/cli/internal/operator`, milestone smoke는 `bin/test`를 적용한다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- API `isValidMarketFilter`와 `isValidVenueFilter`는 US enum을 허용하지만 US import/list forwarding test가 없다.
|
||||
- Worker `filterInstruments`는 provider + market filter를 다루지만 US-only list/query fixture가 약하다.
|
||||
- CLI operator는 이 작업 시작 전 작은 패치로 `nasdaq`/`nyse` venue validation/mapping을 추가했지만, US end-to-end headless scenario fixture는 아직 없다.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
Renamed/removed symbol: none. New fixture/action symbols only.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
Shared task group: `m-us-market-expansion`. This subtask is `03+01_shared_query_surface` and depends on `01_market_foundation`. Predecessor status at plan creation: missing complete.log. It can run in parallel with `02+01_kis_us_daily_bars` only after 01 completes.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
이 작업은 read/query surface와 headless scenario만 다룬다. KIS provider fetch implementation은 02, US backtest result smoke는 04에서 다룬다. Flutter UI는 operational UI gate에 따라 제외한다.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
`local-G06`: API/worker/CLI 경계가 여러 개지만 대부분 existing enum pass-through와 test/fixture 강화이며 deterministic local tests로 리뷰 가능하다.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [US_QUERY-1] API/worker socket tests가 `Market_US`와 provider filter로 US instruments를 같은 ListInstruments contract에서 조회하는지 검증한다. 검증: market selector만 바꿔도 공통 query path를 사용한다.
|
||||
- [ ] [US_QUERY-2] CLI headless fixture에 US market data status query를 추가하고 expected JSONL을 잠근다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
`03+01_shared_query_surface`는 디렉터리명 기준으로 `01_market_foundation`의 `complete.log`가 필요하다. 추가 dependency를 만들지 않는다.
|
||||
|
||||
### [US_QUERY-1] API And Worker US Query Contract Tests
|
||||
|
||||
문제: [services/api/internal/socket/market.go](/config/workspace/alt/services/api/internal/socket/market.go:159)는 US market/venue enum을 허용하지만 test는 KR import fixture 중심이다. [services/worker/internal/socket/market.go](/config/workspace/alt/services/worker/internal/socket/market.go:148)의 `filterInstruments`는 provider+market filter를 지원하지만 US read contract가 충분히 잠겨 있지 않다.
|
||||
|
||||
해결 방법: API forwarding tests와 worker socket tests에 `Market_US`, `Venue_NASDAQ`, `ProviderSymbols["kis"]`, `AssetType`이 포함된 instruments를 추가한다. implementation code는 existing contract path를 우선 재사용하고, 새 request type을 만들지 않는다.
|
||||
|
||||
수정 파일 및 체크리스트:
|
||||
|
||||
- [ ] `services/api/internal/socket/market_test.go`: US `ImportDailyBarsRequest` validation/forwarding test 추가.
|
||||
- [ ] `services/worker/internal/socket/market_test.go`: US list instruments provider filter와 US list bars response mapping test 추가.
|
||||
- [ ] `services/worker/internal/socket/market_mapping_test.go`: asset_type 포함 proto mapping 검증이 01에서 누락됐다면 보강.
|
||||
|
||||
테스트 작성: 작성한다. 새 public contract path가 아니라 기존 path의 US coverage를 추가한다.
|
||||
|
||||
중간 검증:
|
||||
|
||||
```bash
|
||||
go test -count=1 ./services/api/internal/socket ./services/worker/internal/socket
|
||||
```
|
||||
|
||||
### [US_QUERY-2] Operator US Headless Query Fixture
|
||||
|
||||
문제: [apps/cli/internal/operator/scenario.go](/config/workspace/alt/apps/cli/internal/operator/scenario.go:119)와 [runner.go](/config/workspace/alt/apps/cli/internal/operator/runner.go:62)는 US venue string을 받을 수 있어야 한다. 현재 작은 선행 패치로 `nasdaq`/`nyse` mapping은 추가됐지만, [apps/cli/testdata/operator/market_data_status_query.yaml](/config/workspace/alt/apps/cli/testdata/operator/market_data_status_query.yaml:1)은 KR-only status workflow다.
|
||||
|
||||
해결 방법: `us_market_data_status_query.yaml`과 expected JSONL을 추가한다. Steps는 `import_daily_bars` with `market: us`, `venue: nasdaq`, `symbols: ["AAPL"]`, `list_instruments market: us`, `list_bars instrument_id: NASDAQ:AAPL` 순서로 구성한다.
|
||||
|
||||
수정 파일 및 체크리스트:
|
||||
|
||||
- [ ] `apps/cli/testdata/operator/us_market_data_status_query.yaml`: US query fixture 추가.
|
||||
- [ ] `apps/cli/testdata/operator/expected/us_market_data_status_query.jsonl`: expected output 추가.
|
||||
- [ ] `apps/cli/testdata/operator/headless_validation.md`: fixture matrix에 US query row 추가.
|
||||
- [ ] `apps/cli/internal/operator/runner_market_test.go`: fixture run test 추가 또는 기존 fake API test 확장.
|
||||
|
||||
테스트 작성: 작성한다. Fake API로 transport 없이 runner output과 call order를 검증한다.
|
||||
|
||||
중간 검증:
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/cli/internal/operator
|
||||
```
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|---|---|
|
||||
| `services/api/internal/socket/market_test.go` | US_QUERY-1 |
|
||||
| `services/worker/internal/socket/market_test.go` | US_QUERY-1 |
|
||||
| `services/worker/internal/socket/market_mapping_test.go` | US_QUERY-1 |
|
||||
| `apps/cli/testdata/operator/us_market_data_status_query.yaml` | US_QUERY-2 |
|
||||
| `apps/cli/testdata/operator/expected/us_market_data_status_query.jsonl` | US_QUERY-2 |
|
||||
| `apps/cli/testdata/operator/headless_validation.md` | US_QUERY-2 |
|
||||
| `apps/cli/internal/operator/runner_market_test.go` | US_QUERY-2 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./services/api/internal/socket ./services/worker/internal/socket ./apps/cli/internal/operator
|
||||
go test -count=1 ./services/api/... ./services/worker/...
|
||||
bin/test
|
||||
git diff --check
|
||||
```
|
||||
|
||||
`go test` cache는 허용하지 않는다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
<!-- task=m-us-market-expansion/04+02,03_us_backtest_smoke plan=0 tag=US_BACKTEST -->
|
||||
|
||||
# Code Review Reference - US_BACKTEST
|
||||
|
||||
> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is mandatory.**
|
||||
> Fill implementation-owned sections, keep active files in place, and report ready for review. Do not ask the user directly or finalize the loop.
|
||||
|
||||
## 개요
|
||||
|
||||
date=2026-06-06
|
||||
task=m-us-market-expansion/04+02,03_us_backtest_smoke, plan=0, tag=US_BACKTEST
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/trading-expansion/milestones/us-market-expansion.md`
|
||||
- Task ids:
|
||||
- `us-backtest`: 미국장 daily data로 backtest fixture 또는 smoke 동작
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 이 파일을 읽는 리뷰 에이전트에게
|
||||
|
||||
> **[REVIEW AGENT ONLY]** 종결 절차는 코드리뷰 에이전트 전용이다.
|
||||
|
||||
각 항목의 구현과 검증 출력을 대조한다. PASS이면 completion metadata만 보고하고 roadmap update는 런타임에 맡긴다.
|
||||
|
||||
---
|
||||
|
||||
## 구현 항목별 완료 여부
|
||||
|
||||
| 항목 | 완료 여부 |
|
||||
|------|---------|
|
||||
| [US_BACKTEST-1] Storage Bar Source US Selector | [ ] |
|
||||
| [US_BACKTEST-2] Engine USD Fixture | [ ] |
|
||||
| [US_BACKTEST-3] Operator Or Worker Smoke Closure | [ ] |
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [US_BACKTEST-1] `StorageBarSource`가 US market selector로 USD/NASDAQ bars만 반환하는 test를 추가한다. 검증: `bin/test`와 관련 worker 검증이 통과한다.
|
||||
- [ ] [US_BACKTEST-2] Engine fixture가 `MarketUS` daily bars로 USD starting/ending equity를 계산하는 smoke를 추가한다.
|
||||
- [ ] [US_BACKTEST-3] headless/operator backtest smoke가 US market fixture를 실행하거나, worker-only fixture로 충분한 경우 범위 근거를 review에 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 코드리뷰 전용 체크리스트
|
||||
|
||||
- [ ] 판정을 append한다.
|
||||
- [ ] active review/plan 파일을 `*.log`로 아카이브한다.
|
||||
- [ ] PASS이면 `complete.log` 작성 후 archive 이동한다.
|
||||
- [ ] PASS이면 완료 이벤트 메타데이터를 보고한다.
|
||||
- [ ] WARN/FAIL이면 후속 plan/review 또는 `USER_REVIEW.md`를 작성한다.
|
||||
|
||||
## 계획 대비 변경 사항
|
||||
|
||||
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
|
||||
|
||||
## 주요 설계 결정
|
||||
|
||||
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
|
||||
|
||||
## 사용자 리뷰 요청
|
||||
|
||||
- 상태: 없음
|
||||
- 사유 유형: 없음
|
||||
- 결정 필요: 없음
|
||||
- 차단 근거: 없음
|
||||
- 실행한 검증/명령: 없음
|
||||
- 자동 후속 불가 이유: 없음
|
||||
- 재개 조건: 없음
|
||||
|
||||
## 리뷰어를 위한 체크포인트
|
||||
|
||||
- US source test가 KR bars를 섞지 않는지 확인한다.
|
||||
- Engine result가 USD starting cash와 USD price를 보존하는지 확인한다.
|
||||
- Operator fixture 생략 시 live provider 의존성 때문에 worker deterministic fixture가 충분하다는 근거가 review에 기록됐는지 확인한다.
|
||||
|
||||
## 검증 결과
|
||||
|
||||
### US_BACKTEST-1 중간 검증
|
||||
```text
|
||||
$ go test -count=1 ./services/worker/internal/backtest
|
||||
(output)
|
||||
```
|
||||
|
||||
### US_BACKTEST-2 중간 검증
|
||||
```text
|
||||
$ go test -count=1 ./services/worker/internal/backtest
|
||||
(output)
|
||||
```
|
||||
|
||||
### US_BACKTEST-3 중간 검증
|
||||
```text
|
||||
$ go test -count=1 ./apps/cli/internal/operator
|
||||
(output)
|
||||
```
|
||||
|
||||
### 최종 검증
|
||||
```text
|
||||
$ go test -count=1 ./services/worker/internal/backtest ./apps/cli/internal/operator
|
||||
(output)
|
||||
|
||||
$ go test -count=1 ./services/worker/...
|
||||
(output)
|
||||
|
||||
$ bin/test
|
||||
(output)
|
||||
|
||||
$ git diff --check
|
||||
(output)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
<!-- task=m-us-market-expansion/04+02,03_us_backtest_smoke plan=0 tag=US_BACKTEST -->
|
||||
|
||||
# Implementation Plan - US_BACKTEST
|
||||
|
||||
## 이 파일을 읽는 구현 에이전트에게
|
||||
|
||||
구현 완료의 마지막 단계는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이다. `02+01_kis_us_daily_bars`와 `03+01_shared_query_surface` 완료 전에는 구현하지 않는다. 사용자-only 차단은 review stub의 `사용자 리뷰 요청` 섹션에만 기록한다. finalization은 code-review 전용이다.
|
||||
|
||||
## 배경
|
||||
|
||||
US daily data가 저장되고 query surface가 열려도 backtest가 실제 USD/NASDAQ data로 smoke되지 않으면 milestone의 마지막 기능을 닫을 수 없다. Engine은 `MarketUS`면 USD starting cash를 쓰지만, tests는 KR fixture 중심이다. 이 작업은 US bars를 backtest source와 engine fixture에 넣어 compatibility를 잠근다.
|
||||
|
||||
## 사용자 리뷰 요청 흐름
|
||||
|
||||
구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자에게 질문하지 않는다. code-review가 user-review stop 여부를 판단한다.
|
||||
|
||||
## Roadmap Targets
|
||||
|
||||
- Milestone: `agent-roadmap/phase/trading-expansion/milestones/us-market-expansion.md`
|
||||
- Task ids:
|
||||
- `us-backtest`: 미국장 daily data로 backtest fixture 또는 smoke 동작
|
||||
- Completion mode: check-on-pass
|
||||
|
||||
## 분석 결과
|
||||
|
||||
### 읽은 파일
|
||||
|
||||
- `agent-test/local/rules.md`
|
||||
- `agent-test/local/worker-smoke.md`
|
||||
- `agent-test/local/operations-smoke.md`
|
||||
- `services/worker/internal/backtest/bar_source.go`
|
||||
- `services/worker/internal/backtest/bar_source_test.go`
|
||||
- `services/worker/internal/backtest/engine.go`
|
||||
- `services/worker/internal/backtest/fixture_test.go`
|
||||
- `services/worker/internal/jobs/backtest_jobs.go`
|
||||
- `services/worker/internal/jobs/backtest_jobs_test.go`
|
||||
- `apps/cli/internal/operator/runner.go`
|
||||
- `apps/cli/internal/operator/runner_backtest_test.go`
|
||||
- `apps/cli/testdata/operator/backtest_result_summary.yaml`
|
||||
- `apps/cli/testdata/operator/expected/backtest_result_summary.jsonl`
|
||||
|
||||
### 테스트 환경 규칙
|
||||
|
||||
`test_env=local`. worker-smoke와 operations-smoke를 적용한다. worker backtest 변경은 `go test ./services/worker/...`, CLI/headless fixture 변경은 `go test -count=1 ./apps/cli/internal/operator`, milestone verification은 `bin/test`다.
|
||||
|
||||
### 테스트 커버리지 공백
|
||||
|
||||
- `StorageBarSource` test는 US instrument를 seed하지만 KR query 결과만 assert한다.
|
||||
- Engine tests는 `MarketKR` starting cash/ending equity만 assert한다. `MarketUS` USD cash path는 code에 있으나 direct fixture test가 없다.
|
||||
- Operator backtest scenario fixtures are KR-centered.
|
||||
|
||||
### 심볼 참조
|
||||
|
||||
Renamed/removed symbol: none. New tests/fixtures only unless source behavior gap is found.
|
||||
|
||||
### 분할 판단
|
||||
|
||||
Shared task group: `m-us-market-expansion`. This subtask is `04+02,03_us_backtest_smoke` and depends on `02+01_kis_us_daily_bars` plus `03+01_shared_query_surface`. Predecessor status at plan creation: both missing complete.log. The transitive foundation dependency is carried by 02/03; no hidden dependency is added.
|
||||
|
||||
### 범위 결정 근거
|
||||
|
||||
이 작업은 deterministic fixture/smoke only. Strategy architecture, live provider smoke, paper trading, Flutter UI는 제외한다. If implementation finds a real engine bug, fix only the US compatibility bug needed for this smoke.
|
||||
|
||||
### 빌드 등급
|
||||
|
||||
`local-G06`: deterministic worker/CLI tests with bounded source impact; no schema or external runtime required if predecessors are complete.
|
||||
|
||||
## 구현 체크리스트
|
||||
|
||||
- [ ] [US_BACKTEST-1] `StorageBarSource`가 US market selector로 USD/NASDAQ bars만 반환하는 test를 추가한다. 검증: `bin/test`와 관련 worker 검증이 통과한다.
|
||||
- [ ] [US_BACKTEST-2] Engine fixture가 `MarketUS` daily bars로 USD starting/ending equity를 계산하는 smoke를 추가한다.
|
||||
- [ ] [US_BACKTEST-3] headless/operator backtest smoke가 US market fixture를 실행하거나, worker-only fixture로 충분한 경우 범위 근거를 review에 기록한다.
|
||||
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
|
||||
|
||||
## 의존 관계 및 구현 순서
|
||||
|
||||
`04+02,03_us_backtest_smoke`는 디렉터리명 기준으로 `02+01_kis_us_daily_bars`와 `03+01_shared_query_surface`의 `complete.log`가 필요하다. 추가 dependency를 만들지 않는다.
|
||||
|
||||
### [US_BACKTEST-1] Storage Bar Source US Selector
|
||||
|
||||
문제: [services/worker/internal/backtest/bar_source.go](/config/workspace/alt/services/worker/internal/backtest/bar_source.go:24)는 market filter로 instruments를 골라 bars를 읽는다. [bar_source_test.go](/config/workspace/alt/services/worker/internal/backtest/bar_source_test.go:60)는 US instrument를 seed하지만 KR query만 assert한다.
|
||||
|
||||
해결 방법: 같은 test 또는 새 test에서 `source.GetBars(..., market.MarketUS, ...)`를 호출하고 `NASDAQ:AAPL`만 반환되는지, USD close currency가 유지되는지 검증한다.
|
||||
|
||||
수정 파일 및 체크리스트:
|
||||
|
||||
- [ ] `services/worker/internal/backtest/bar_source_test.go`: US selector assertion 추가.
|
||||
|
||||
테스트 작성: 작성한다. 기존 in-memory store를 재사용한다.
|
||||
|
||||
중간 검증:
|
||||
|
||||
```bash
|
||||
go test -count=1 ./services/worker/internal/backtest
|
||||
```
|
||||
|
||||
### [US_BACKTEST-2] Engine USD Fixture
|
||||
|
||||
문제: [services/worker/internal/backtest/engine.go](/config/workspace/alt/services/worker/internal/backtest/engine.go:57)는 `MarketUS`면 USD starting cash를 사용하지만 [fixture_test.go](/config/workspace/alt/services/worker/internal/backtest/fixture_test.go:92)는 KR-only bars와 KRW expected result를 검증한다.
|
||||
|
||||
해결 방법: `NASDAQ:AAPL` USD bars와 deterministic strategy를 사용해 `MarketUS` run을 실행하고 starting cash `10000` USD, trades/positions USD price, final equity/summary를 assert한다.
|
||||
|
||||
수정 파일 및 체크리스트:
|
||||
|
||||
- [ ] `services/worker/internal/backtest/fixture_test.go`: US fixture test 추가.
|
||||
- [ ] 필요 시 helper 함수로 KR/US fixture 중복만 최소화한다.
|
||||
|
||||
테스트 작성: 작성한다. Numeric expectation은 KR test처럼 명시한다.
|
||||
|
||||
중간 검증:
|
||||
|
||||
```bash
|
||||
go test -count=1 ./services/worker/internal/backtest
|
||||
```
|
||||
|
||||
### [US_BACKTEST-3] Operator Or Worker Smoke Closure
|
||||
|
||||
문제: [apps/cli/testdata/operator/backtest_result_summary.yaml](/config/workspace/alt/apps/cli/testdata/operator/backtest_result_summary.yaml:1)는 KR scenario다. milestone은 US daily data로 backtest fixture 또는 smoke가 동작해야 한다.
|
||||
|
||||
해결 방법: predecessors가 US import/query fixture를 제공하면 `us_backtest_result_summary.yaml`과 expected JSONL을 추가한다. If the runtime cannot seed US bars without live KIS credentials, keep worker deterministic fixture as completion evidence and record why operator fixture is deferred.
|
||||
|
||||
수정 파일 및 체크리스트:
|
||||
|
||||
- [ ] `apps/cli/testdata/operator/us_backtest_result_summary.yaml`: 가능한 경우 추가.
|
||||
- [ ] `apps/cli/testdata/operator/expected/us_backtest_result_summary.jsonl`: 가능한 경우 추가.
|
||||
- [ ] `apps/cli/internal/operator/runner_backtest_test.go`: US backtest scenario test 추가 또는 worker-only closure 근거 기록.
|
||||
|
||||
테스트 작성: 작성한다. Operator fixture가 live provider를 요구하면 작성하지 않고 worker deterministic smoke를 completion evidence로 남긴다.
|
||||
|
||||
중간 검증:
|
||||
|
||||
```bash
|
||||
go test -count=1 ./apps/cli/internal/operator
|
||||
```
|
||||
|
||||
## 수정 파일 요약
|
||||
|
||||
| 파일 | 항목 |
|
||||
|---|---|
|
||||
| `services/worker/internal/backtest/bar_source_test.go` | US_BACKTEST-1 |
|
||||
| `services/worker/internal/backtest/fixture_test.go` | US_BACKTEST-2 |
|
||||
| `apps/cli/testdata/operator/us_backtest_result_summary.yaml` | US_BACKTEST-3 |
|
||||
| `apps/cli/testdata/operator/expected/us_backtest_result_summary.jsonl` | US_BACKTEST-3 |
|
||||
| `apps/cli/internal/operator/runner_backtest_test.go` | US_BACKTEST-3 |
|
||||
|
||||
## 최종 검증
|
||||
|
||||
```bash
|
||||
go test -count=1 ./services/worker/internal/backtest ./apps/cli/internal/operator
|
||||
go test -count=1 ./services/worker/...
|
||||
bin/test
|
||||
git diff --check
|
||||
```
|
||||
|
||||
`go test` cache는 허용하지 않는다.
|
||||
|
||||
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
|
||||
|
|
@ -64,6 +64,8 @@ var venueByName = map[string]altv1.Venue{
|
|||
"": altv1.Venue_VENUE_UNSPECIFIED,
|
||||
"unspecified": altv1.Venue_VENUE_UNSPECIFIED,
|
||||
"krx": altv1.Venue_VENUE_KRX,
|
||||
"nasdaq": altv1.Venue_VENUE_NASDAQ,
|
||||
"nyse": altv1.Venue_VENUE_NYSE,
|
||||
}
|
||||
|
||||
// currencyByMarket derives the paper account currency from the request market so
|
||||
|
|
|
|||
|
|
@ -320,6 +320,60 @@ func TestRunScenarioImportDailyBarsSuccess(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRunScenarioImportDailyBarsMapsUSVenues(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
venue string
|
||||
want altv1.Venue
|
||||
}{
|
||||
{name: "nasdaq", venue: "nasdaq", want: altv1.Venue_VENUE_NASDAQ},
|
||||
{name: "nyse", venue: "nyse", want: altv1.Venue_VENUE_NYSE},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
api := &fakeAPI{
|
||||
importDailyBarsResp: &altv1.ImportDailyBarsResponse{
|
||||
Provider: "kis",
|
||||
InstrumentCount: 1,
|
||||
BarCount: 1,
|
||||
},
|
||||
}
|
||||
url := startFakeAPIServer(t, api)
|
||||
sc := &Scenario{
|
||||
Name: "import_us_daily_bars",
|
||||
Steps: []Step{{
|
||||
ID: "import1",
|
||||
Action: ActionImportDailyBars,
|
||||
Request: Request{
|
||||
Provider: "kis",
|
||||
SelectorKind: "watchlist",
|
||||
Market: "us",
|
||||
Venue: tt.venue,
|
||||
Symbols: []string{"AAPL"},
|
||||
},
|
||||
Expect: Expect{Status: "ok"},
|
||||
}},
|
||||
}
|
||||
|
||||
out, code := runScenario(t, sc, url)
|
||||
if code != codeOK {
|
||||
t.Fatalf("exit code = %d, want 0 (out=%q)", code, out)
|
||||
}
|
||||
req := api.lastImportDailyBarsReq()
|
||||
if req == nil {
|
||||
t.Fatal("server did not receive an import_daily_bars request")
|
||||
}
|
||||
if req.GetMarket() != altv1.Market_MARKET_US {
|
||||
t.Errorf("market = %v, want MARKET_US", req.GetMarket())
|
||||
}
|
||||
if req.GetVenue() != tt.want {
|
||||
t.Errorf("venue = %v, want %v", req.GetVenue(), tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunScenarioImportDailyBarsExpectedTypedError(t *testing.T) {
|
||||
url := startFakeAPIServer(t, &fakeAPI{
|
||||
importDailyBarsResp: &altv1.ImportDailyBarsResponse{
|
||||
|
|
|
|||
|
|
@ -121,6 +121,8 @@ var validVenues = map[string]bool{
|
|||
"": true,
|
||||
"unspecified": true,
|
||||
"krx": true,
|
||||
"nasdaq": true,
|
||||
"nyse": true,
|
||||
}
|
||||
|
||||
// validTimeframes is the set of bar timeframe strings a list_bars request may
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package operator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -464,6 +465,38 @@ steps:
|
|||
}
|
||||
}
|
||||
|
||||
func TestValidateImportDailyBarsUSVenues(t *testing.T) {
|
||||
for _, venue := range []string{"nasdaq", "nyse"} {
|
||||
t.Run(venue, func(t *testing.T) {
|
||||
yamlText := fmt.Sprintf(`
|
||||
name: valid_us_import
|
||||
timeout: 5s
|
||||
steps:
|
||||
- id: step1
|
||||
action: import_daily_bars
|
||||
request:
|
||||
provider: kis
|
||||
selector_kind: watchlist
|
||||
market: us
|
||||
venue: %s
|
||||
symbols: ["AAPL"]
|
||||
from_yyyymmdd: "20240527"
|
||||
to_yyyymmdd: "20240528"
|
||||
expect:
|
||||
status: ok
|
||||
`, venue)
|
||||
sc, err := ParseScenario([]byte(yamlText))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error parsing valid US import scenario: %v", err)
|
||||
}
|
||||
got := sc.Steps[0].Request.Venue
|
||||
if got != venue {
|
||||
t.Errorf("venue = %q, want %q", got, venue)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidatePaperTradingScenario(t *testing.T) {
|
||||
yamlText := `
|
||||
name: valid_paper
|
||||
|
|
|
|||
|
|
@ -82,6 +82,28 @@ class Currency extends $pb.ProtobufEnum {
|
|||
const Currency._(super.value, super.name);
|
||||
}
|
||||
|
||||
class AssetType extends $pb.ProtobufEnum {
|
||||
static const AssetType ASSET_TYPE_UNSPECIFIED =
|
||||
AssetType._(0, _omitEnumNames ? '' : 'ASSET_TYPE_UNSPECIFIED');
|
||||
static const AssetType ASSET_TYPE_EQUITY =
|
||||
AssetType._(1, _omitEnumNames ? '' : 'ASSET_TYPE_EQUITY');
|
||||
static const AssetType ASSET_TYPE_ETF =
|
||||
AssetType._(2, _omitEnumNames ? '' : 'ASSET_TYPE_ETF');
|
||||
|
||||
static const $core.List<AssetType> values = <AssetType>[
|
||||
ASSET_TYPE_UNSPECIFIED,
|
||||
ASSET_TYPE_EQUITY,
|
||||
ASSET_TYPE_ETF,
|
||||
];
|
||||
|
||||
static final $core.List<AssetType?> _byValue =
|
||||
$pb.ProtobufEnum.$_initByValueList(values, 2);
|
||||
static AssetType? valueOf($core.int value) =>
|
||||
value < 0 || value >= _byValue.length ? null : _byValue[value];
|
||||
|
||||
const AssetType._(super.value, super.name);
|
||||
}
|
||||
|
||||
class Timeframe extends $pb.ProtobufEnum {
|
||||
static const Timeframe TIMEFRAME_UNSPECIFIED =
|
||||
Timeframe._(0, _omitEnumNames ? '' : 'TIMEFRAME_UNSPECIFIED');
|
||||
|
|
|
|||
|
|
@ -61,6 +61,21 @@ final $typed_data.Uint8List currencyDescriptor = $convert.base64Decode(
|
|||
'CghDdXJyZW5jeRIYChRDVVJSRU5DWV9VTlNQRUNJRklFRBAAEhAKDENVUlJFTkNZX0tSVxABEh'
|
||||
'AKDENVUlJFTkNZX1VTRBAC');
|
||||
|
||||
@$core.Deprecated('Use assetTypeDescriptor instead')
|
||||
const AssetType$json = {
|
||||
'1': 'AssetType',
|
||||
'2': [
|
||||
{'1': 'ASSET_TYPE_UNSPECIFIED', '2': 0},
|
||||
{'1': 'ASSET_TYPE_EQUITY', '2': 1},
|
||||
{'1': 'ASSET_TYPE_ETF', '2': 2},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `AssetType`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List assetTypeDescriptor = $convert.base64Decode(
|
||||
'CglBc3NldFR5cGUSGgoWQVNTRVRfVFlQRV9VTlNQRUNJRklFRBAAEhUKEUFTU0VUX1RZUEVfRV'
|
||||
'FVSVRZEAESEgoOQVNTRVRfVFlQRV9FVEYQAg==');
|
||||
|
||||
@$core.Deprecated('Use timeframeDescriptor instead')
|
||||
const Timeframe$json = {
|
||||
'1': 'Timeframe',
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ class Instrument extends $pb.GeneratedMessage {
|
|||
$core.String? name,
|
||||
$0.Currency? currency,
|
||||
$core.Iterable<$core.MapEntry<$core.String, $core.String>>? providerSymbols,
|
||||
$0.AssetType? assetType,
|
||||
}) {
|
||||
final result = create();
|
||||
if (id != null) result.id = id;
|
||||
|
|
@ -38,6 +39,7 @@ class Instrument extends $pb.GeneratedMessage {
|
|||
if (currency != null) result.currency = currency;
|
||||
if (providerSymbols != null)
|
||||
result.providerSymbols.addEntries(providerSymbols);
|
||||
if (assetType != null) result.assetType = assetType;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -68,6 +70,8 @@ class Instrument extends $pb.GeneratedMessage {
|
|||
keyFieldType: $pb.PbFieldType.OS,
|
||||
valueFieldType: $pb.PbFieldType.OS,
|
||||
packageName: const $pb.PackageName('alt.v1'))
|
||||
..aE<$0.AssetType>(8, _omitFieldNames ? '' : 'assetType',
|
||||
enumValues: $0.AssetType.values)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
@$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.')
|
||||
|
|
@ -144,6 +148,15 @@ class Instrument extends $pb.GeneratedMessage {
|
|||
|
||||
@$pb.TagNumber(7)
|
||||
$pb.PbMap<$core.String, $core.String> get providerSymbols => $_getMap(6);
|
||||
|
||||
@$pb.TagNumber(8)
|
||||
$0.AssetType get assetType => $_getN(7);
|
||||
@$pb.TagNumber(8)
|
||||
set assetType($0.AssetType value) => $_setField(8, value);
|
||||
@$pb.TagNumber(8)
|
||||
$core.bool hasAssetType() => $_has(7);
|
||||
@$pb.TagNumber(8)
|
||||
void clearAssetType() => $_clearField(8);
|
||||
}
|
||||
|
||||
class Price extends $pb.GeneratedMessage {
|
||||
|
|
|
|||
|
|
@ -54,6 +54,14 @@ const Instrument$json = {
|
|||
'6': '.alt.v1.Instrument.ProviderSymbolsEntry',
|
||||
'10': 'providerSymbols'
|
||||
},
|
||||
{
|
||||
'1': 'asset_type',
|
||||
'3': 8,
|
||||
'4': 1,
|
||||
'5': 14,
|
||||
'6': '.alt.v1.AssetType',
|
||||
'10': 'assetType'
|
||||
},
|
||||
],
|
||||
'3': [Instrument_ProviderSymbolsEntry$json],
|
||||
};
|
||||
|
|
@ -75,8 +83,9 @@ final $typed_data.Uint8List instrumentDescriptor = $convert.base64Decode(
|
|||
'bWJvbBgEIAEoCVIGc3ltYm9sEhIKBG5hbWUYBSABKAlSBG5hbWUSLAoIY3VycmVuY3kYBiABKA'
|
||||
'4yEC5hbHQudjEuQ3VycmVuY3lSCGN1cnJlbmN5ElIKEHByb3ZpZGVyX3N5bWJvbHMYByADKAsy'
|
||||
'Jy5hbHQudjEuSW5zdHJ1bWVudC5Qcm92aWRlclN5bWJvbHNFbnRyeVIPcHJvdmlkZXJTeW1ib2'
|
||||
'xzGkIKFFByb3ZpZGVyU3ltYm9sc0VudHJ5EhAKA2tleRgBIAEoCVIDa2V5EhQKBXZhbHVlGAIg'
|
||||
'ASgJUgV2YWx1ZToCOAE=');
|
||||
'xzEjAKCmFzc2V0X3R5cGUYCCABKA4yES5hbHQudjEuQXNzZXRUeXBlUglhc3NldFR5cGUaQgoU'
|
||||
'UHJvdmlkZXJTeW1ib2xzRW50cnkSEAoDa2V5GAEgASgJUgNrZXkSFAoFdmFsdWUYAiABKAlSBX'
|
||||
'ZhbHVlOgI4AQ==');
|
||||
|
||||
@$core.Deprecated('Use priceDescriptor instead')
|
||||
const Price$json = {
|
||||
|
|
|
|||
|
|
@ -171,6 +171,55 @@ func (Currency) EnumDescriptor() ([]byte, []int) {
|
|||
return file_alt_v1_common_proto_rawDescGZIP(), []int{2}
|
||||
}
|
||||
|
||||
type AssetType int32
|
||||
|
||||
const (
|
||||
AssetType_ASSET_TYPE_UNSPECIFIED AssetType = 0
|
||||
AssetType_ASSET_TYPE_EQUITY AssetType = 1
|
||||
AssetType_ASSET_TYPE_ETF AssetType = 2
|
||||
)
|
||||
|
||||
// Enum value maps for AssetType.
|
||||
var (
|
||||
AssetType_name = map[int32]string{
|
||||
0: "ASSET_TYPE_UNSPECIFIED",
|
||||
1: "ASSET_TYPE_EQUITY",
|
||||
2: "ASSET_TYPE_ETF",
|
||||
}
|
||||
AssetType_value = map[string]int32{
|
||||
"ASSET_TYPE_UNSPECIFIED": 0,
|
||||
"ASSET_TYPE_EQUITY": 1,
|
||||
"ASSET_TYPE_ETF": 2,
|
||||
}
|
||||
)
|
||||
|
||||
func (x AssetType) Enum() *AssetType {
|
||||
p := new(AssetType)
|
||||
*p = x
|
||||
return p
|
||||
}
|
||||
|
||||
func (x AssetType) String() string {
|
||||
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
|
||||
}
|
||||
|
||||
func (AssetType) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_alt_v1_common_proto_enumTypes[3].Descriptor()
|
||||
}
|
||||
|
||||
func (AssetType) Type() protoreflect.EnumType {
|
||||
return &file_alt_v1_common_proto_enumTypes[3]
|
||||
}
|
||||
|
||||
func (x AssetType) Number() protoreflect.EnumNumber {
|
||||
return protoreflect.EnumNumber(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use AssetType.Descriptor instead.
|
||||
func (AssetType) EnumDescriptor() ([]byte, []int) {
|
||||
return file_alt_v1_common_proto_rawDescGZIP(), []int{3}
|
||||
}
|
||||
|
||||
type Timeframe int32
|
||||
|
||||
const (
|
||||
|
|
@ -207,11 +256,11 @@ func (x Timeframe) String() string {
|
|||
}
|
||||
|
||||
func (Timeframe) Descriptor() protoreflect.EnumDescriptor {
|
||||
return file_alt_v1_common_proto_enumTypes[3].Descriptor()
|
||||
return file_alt_v1_common_proto_enumTypes[4].Descriptor()
|
||||
}
|
||||
|
||||
func (Timeframe) Type() protoreflect.EnumType {
|
||||
return &file_alt_v1_common_proto_enumTypes[3]
|
||||
return &file_alt_v1_common_proto_enumTypes[4]
|
||||
}
|
||||
|
||||
func (x Timeframe) Number() protoreflect.EnumNumber {
|
||||
|
|
@ -220,7 +269,7 @@ func (x Timeframe) Number() protoreflect.EnumNumber {
|
|||
|
||||
// Deprecated: Use Timeframe.Descriptor instead.
|
||||
func (Timeframe) EnumDescriptor() ([]byte, []int) {
|
||||
return file_alt_v1_common_proto_rawDescGZIP(), []int{3}
|
||||
return file_alt_v1_common_proto_rawDescGZIP(), []int{4}
|
||||
}
|
||||
|
||||
type Decimal struct {
|
||||
|
|
@ -481,7 +530,11 @@ const file_alt_v1_common_proto_rawDesc = "" +
|
|||
"\bCurrency\x12\x18\n" +
|
||||
"\x14CURRENCY_UNSPECIFIED\x10\x00\x12\x10\n" +
|
||||
"\fCURRENCY_KRW\x10\x01\x12\x10\n" +
|
||||
"\fCURRENCY_USD\x10\x02*k\n" +
|
||||
"\fCURRENCY_USD\x10\x02*R\n" +
|
||||
"\tAssetType\x12\x1a\n" +
|
||||
"\x16ASSET_TYPE_UNSPECIFIED\x10\x00\x12\x15\n" +
|
||||
"\x11ASSET_TYPE_EQUITY\x10\x01\x12\x12\n" +
|
||||
"\x0eASSET_TYPE_ETF\x10\x02*k\n" +
|
||||
"\tTimeframe\x12\x19\n" +
|
||||
"\x15TIMEFRAME_UNSPECIFIED\x10\x00\x12\x13\n" +
|
||||
"\x0fTIMEFRAME_DAILY\x10\x01\x12\x16\n" +
|
||||
|
|
@ -500,17 +553,18 @@ func file_alt_v1_common_proto_rawDescGZIP() []byte {
|
|||
return file_alt_v1_common_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_alt_v1_common_proto_enumTypes = make([]protoimpl.EnumInfo, 4)
|
||||
var file_alt_v1_common_proto_enumTypes = make([]protoimpl.EnumInfo, 5)
|
||||
var file_alt_v1_common_proto_msgTypes = make([]protoimpl.MessageInfo, 4)
|
||||
var file_alt_v1_common_proto_goTypes = []any{
|
||||
(Market)(0), // 0: alt.v1.Market
|
||||
(Venue)(0), // 1: alt.v1.Venue
|
||||
(Currency)(0), // 2: alt.v1.Currency
|
||||
(Timeframe)(0), // 3: alt.v1.Timeframe
|
||||
(*Decimal)(nil), // 4: alt.v1.Decimal
|
||||
(*ErrorInfo)(nil), // 5: alt.v1.ErrorInfo
|
||||
(*HelloRequest)(nil), // 6: alt.v1.HelloRequest
|
||||
(*HelloResponse)(nil), // 7: alt.v1.HelloResponse
|
||||
(AssetType)(0), // 3: alt.v1.AssetType
|
||||
(Timeframe)(0), // 4: alt.v1.Timeframe
|
||||
(*Decimal)(nil), // 5: alt.v1.Decimal
|
||||
(*ErrorInfo)(nil), // 6: alt.v1.ErrorInfo
|
||||
(*HelloRequest)(nil), // 7: alt.v1.HelloRequest
|
||||
(*HelloResponse)(nil), // 8: alt.v1.HelloResponse
|
||||
}
|
||||
var file_alt_v1_common_proto_depIdxs = []int32{
|
||||
0, // [0:0] is the sub-list for method output_type
|
||||
|
|
@ -530,7 +584,7 @@ func file_alt_v1_common_proto_init() {
|
|||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: unsafe.Slice(unsafe.StringData(file_alt_v1_common_proto_rawDesc), len(file_alt_v1_common_proto_rawDesc)),
|
||||
NumEnums: 4,
|
||||
NumEnums: 5,
|
||||
NumMessages: 4,
|
||||
NumExtensions: 0,
|
||||
NumServices: 0,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ type Instrument struct {
|
|||
Name string `protobuf:"bytes,5,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Currency Currency `protobuf:"varint,6,opt,name=currency,proto3,enum=alt.v1.Currency" json:"currency,omitempty"`
|
||||
ProviderSymbols map[string]string `protobuf:"bytes,7,rep,name=provider_symbols,json=providerSymbols,proto3" json:"provider_symbols,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
|
||||
AssetType AssetType `protobuf:"varint,8,opt,name=asset_type,json=assetType,proto3,enum=alt.v1.AssetType" json:"asset_type,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
|
|
@ -113,6 +114,13 @@ func (x *Instrument) GetProviderSymbols() map[string]string {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (x *Instrument) GetAssetType() AssetType {
|
||||
if x != nil {
|
||||
return x.AssetType
|
||||
}
|
||||
return AssetType_ASSET_TYPE_UNSPECIFIED
|
||||
}
|
||||
|
||||
type Price struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Currency Currency `protobuf:"varint,1,opt,name=currency,proto3,enum=alt.v1.Currency" json:"currency,omitempty"`
|
||||
|
|
@ -705,7 +713,7 @@ var File_alt_v1_market_proto protoreflect.FileDescriptor
|
|||
|
||||
const file_alt_v1_market_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x13alt/v1/market.proto\x12\x06alt.v1\x1a\x13alt/v1/common.proto\"\xdb\x02\n" +
|
||||
"\x13alt/v1/market.proto\x12\x06alt.v1\x1a\x13alt/v1/common.proto\"\x8d\x03\n" +
|
||||
"\n" +
|
||||
"Instrument\x12\x0e\n" +
|
||||
"\x02id\x18\x01 \x01(\tR\x02id\x12&\n" +
|
||||
|
|
@ -714,7 +722,9 @@ const file_alt_v1_market_proto_rawDesc = "" +
|
|||
"\x06symbol\x18\x04 \x01(\tR\x06symbol\x12\x12\n" +
|
||||
"\x04name\x18\x05 \x01(\tR\x04name\x12,\n" +
|
||||
"\bcurrency\x18\x06 \x01(\x0e2\x10.alt.v1.CurrencyR\bcurrency\x12R\n" +
|
||||
"\x10provider_symbols\x18\a \x03(\v2'.alt.v1.Instrument.ProviderSymbolsEntryR\x0fproviderSymbols\x1aB\n" +
|
||||
"\x10provider_symbols\x18\a \x03(\v2'.alt.v1.Instrument.ProviderSymbolsEntryR\x0fproviderSymbols\x120\n" +
|
||||
"\n" +
|
||||
"asset_type\x18\b \x01(\x0e2\x11.alt.v1.AssetTypeR\tassetType\x1aB\n" +
|
||||
"\x14ProviderSymbolsEntry\x12\x10\n" +
|
||||
"\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" +
|
||||
"\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"^\n" +
|
||||
|
|
@ -792,38 +802,40 @@ var file_alt_v1_market_proto_goTypes = []any{
|
|||
(Market)(0), // 11: alt.v1.Market
|
||||
(Venue)(0), // 12: alt.v1.Venue
|
||||
(Currency)(0), // 13: alt.v1.Currency
|
||||
(*Decimal)(nil), // 14: alt.v1.Decimal
|
||||
(Timeframe)(0), // 15: alt.v1.Timeframe
|
||||
(*ErrorInfo)(nil), // 16: alt.v1.ErrorInfo
|
||||
(AssetType)(0), // 14: alt.v1.AssetType
|
||||
(*Decimal)(nil), // 15: alt.v1.Decimal
|
||||
(Timeframe)(0), // 16: alt.v1.Timeframe
|
||||
(*ErrorInfo)(nil), // 17: alt.v1.ErrorInfo
|
||||
}
|
||||
var file_alt_v1_market_proto_depIdxs = []int32{
|
||||
11, // 0: alt.v1.Instrument.market:type_name -> alt.v1.Market
|
||||
12, // 1: alt.v1.Instrument.venue:type_name -> alt.v1.Venue
|
||||
13, // 2: alt.v1.Instrument.currency:type_name -> alt.v1.Currency
|
||||
10, // 3: alt.v1.Instrument.provider_symbols:type_name -> alt.v1.Instrument.ProviderSymbolsEntry
|
||||
13, // 4: alt.v1.Price.currency:type_name -> alt.v1.Currency
|
||||
14, // 5: alt.v1.Price.amount:type_name -> alt.v1.Decimal
|
||||
14, // 6: alt.v1.Quantity.amount:type_name -> alt.v1.Decimal
|
||||
15, // 7: alt.v1.Bar.timeframe:type_name -> alt.v1.Timeframe
|
||||
1, // 8: alt.v1.Bar.open:type_name -> alt.v1.Price
|
||||
1, // 9: alt.v1.Bar.high:type_name -> alt.v1.Price
|
||||
1, // 10: alt.v1.Bar.low:type_name -> alt.v1.Price
|
||||
1, // 11: alt.v1.Bar.close:type_name -> alt.v1.Price
|
||||
2, // 12: alt.v1.Bar.volume:type_name -> alt.v1.Quantity
|
||||
11, // 13: alt.v1.ListInstrumentsRequest.market:type_name -> alt.v1.Market
|
||||
0, // 14: alt.v1.ListInstrumentsResponse.instruments:type_name -> alt.v1.Instrument
|
||||
16, // 15: alt.v1.ListInstrumentsResponse.error:type_name -> alt.v1.ErrorInfo
|
||||
15, // 16: alt.v1.ListBarsRequest.timeframe:type_name -> alt.v1.Timeframe
|
||||
3, // 17: alt.v1.ListBarsResponse.bars:type_name -> alt.v1.Bar
|
||||
16, // 18: alt.v1.ListBarsResponse.error:type_name -> alt.v1.ErrorInfo
|
||||
11, // 19: alt.v1.ImportDailyBarsRequest.market:type_name -> alt.v1.Market
|
||||
12, // 20: alt.v1.ImportDailyBarsRequest.venue:type_name -> alt.v1.Venue
|
||||
16, // 21: alt.v1.ImportDailyBarsResponse.error:type_name -> alt.v1.ErrorInfo
|
||||
22, // [22:22] is the sub-list for method output_type
|
||||
22, // [22:22] is the sub-list for method input_type
|
||||
22, // [22:22] is the sub-list for extension type_name
|
||||
22, // [22:22] is the sub-list for extension extendee
|
||||
0, // [0:22] is the sub-list for field type_name
|
||||
14, // 4: alt.v1.Instrument.asset_type:type_name -> alt.v1.AssetType
|
||||
13, // 5: alt.v1.Price.currency:type_name -> alt.v1.Currency
|
||||
15, // 6: alt.v1.Price.amount:type_name -> alt.v1.Decimal
|
||||
15, // 7: alt.v1.Quantity.amount:type_name -> alt.v1.Decimal
|
||||
16, // 8: alt.v1.Bar.timeframe:type_name -> alt.v1.Timeframe
|
||||
1, // 9: alt.v1.Bar.open:type_name -> alt.v1.Price
|
||||
1, // 10: alt.v1.Bar.high:type_name -> alt.v1.Price
|
||||
1, // 11: alt.v1.Bar.low:type_name -> alt.v1.Price
|
||||
1, // 12: alt.v1.Bar.close:type_name -> alt.v1.Price
|
||||
2, // 13: alt.v1.Bar.volume:type_name -> alt.v1.Quantity
|
||||
11, // 14: alt.v1.ListInstrumentsRequest.market:type_name -> alt.v1.Market
|
||||
0, // 15: alt.v1.ListInstrumentsResponse.instruments:type_name -> alt.v1.Instrument
|
||||
17, // 16: alt.v1.ListInstrumentsResponse.error:type_name -> alt.v1.ErrorInfo
|
||||
16, // 17: alt.v1.ListBarsRequest.timeframe:type_name -> alt.v1.Timeframe
|
||||
3, // 18: alt.v1.ListBarsResponse.bars:type_name -> alt.v1.Bar
|
||||
17, // 19: alt.v1.ListBarsResponse.error:type_name -> alt.v1.ErrorInfo
|
||||
11, // 20: alt.v1.ImportDailyBarsRequest.market:type_name -> alt.v1.Market
|
||||
12, // 21: alt.v1.ImportDailyBarsRequest.venue:type_name -> alt.v1.Venue
|
||||
17, // 22: alt.v1.ImportDailyBarsResponse.error:type_name -> alt.v1.ErrorInfo
|
||||
23, // [23:23] is the sub-list for method output_type
|
||||
23, // [23:23] is the sub-list for method input_type
|
||||
23, // [23:23] is the sub-list for extension type_name
|
||||
23, // [23:23] is the sub-list for extension extendee
|
||||
0, // [0:23] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_alt_v1_market_proto_init() }
|
||||
|
|
|
|||
|
|
@ -27,6 +27,12 @@ enum Currency {
|
|||
CURRENCY_USD = 2;
|
||||
}
|
||||
|
||||
enum AssetType {
|
||||
ASSET_TYPE_UNSPECIFIED = 0;
|
||||
ASSET_TYPE_EQUITY = 1;
|
||||
ASSET_TYPE_ETF = 2;
|
||||
}
|
||||
|
||||
enum Timeframe {
|
||||
TIMEFRAME_UNSPECIFIED = 0;
|
||||
TIMEFRAME_DAILY = 1;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ message Instrument {
|
|||
string name = 5;
|
||||
Currency currency = 6;
|
||||
map<string, string> provider_symbols = 7;
|
||||
AssetType asset_type = 8;
|
||||
}
|
||||
|
||||
message Price {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,48 @@ const (
|
|||
|
||||
type InstrumentID string
|
||||
|
||||
type AssetType string
|
||||
|
||||
const (
|
||||
AssetTypeEquity AssetType = "equity"
|
||||
AssetTypeETF AssetType = "etf"
|
||||
)
|
||||
|
||||
type VenueMetadata struct {
|
||||
Venue Venue
|
||||
Market Market
|
||||
Currency Currency
|
||||
Timezone string
|
||||
}
|
||||
|
||||
func GetVenueMetadata(v Venue) (VenueMetadata, bool) {
|
||||
switch v {
|
||||
case VenueKRX:
|
||||
return VenueMetadata{
|
||||
Venue: VenueKRX,
|
||||
Market: MarketKR,
|
||||
Currency: CurrencyKRW,
|
||||
Timezone: "Asia/Seoul",
|
||||
}, true
|
||||
case VenueNASDAQ:
|
||||
return VenueMetadata{
|
||||
Venue: VenueNASDAQ,
|
||||
Market: MarketUS,
|
||||
Currency: CurrencyUSD,
|
||||
Timezone: "America/New_York",
|
||||
}, true
|
||||
case VenueNYSE:
|
||||
return VenueMetadata{
|
||||
Venue: VenueNYSE,
|
||||
Market: MarketUS,
|
||||
Currency: CurrencyUSD,
|
||||
Timezone: "America/New_York",
|
||||
}, true
|
||||
default:
|
||||
return VenueMetadata{}, false
|
||||
}
|
||||
}
|
||||
|
||||
type Instrument struct {
|
||||
ID InstrumentID
|
||||
Market Market
|
||||
|
|
@ -41,6 +83,7 @@ type Instrument struct {
|
|||
Symbol string
|
||||
Name string
|
||||
Currency Currency
|
||||
AssetType AssetType
|
||||
ProviderSymbols map[string]string
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -41,3 +41,38 @@ func TestUniverseSelectorFields(t *testing.T) {
|
|||
t.Errorf("Symbols length: got %d, want 2", len(sel.Symbols))
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarketVenueMetadata(t *testing.T) {
|
||||
cases := []struct {
|
||||
venue Venue
|
||||
wantOk bool
|
||||
wantMarket Market
|
||||
wantCurrency Currency
|
||||
wantTimezone string
|
||||
}{
|
||||
{VenueKRX, true, MarketKR, CurrencyKRW, "Asia/Seoul"},
|
||||
{VenueNASDAQ, true, MarketUS, CurrencyUSD, "America/New_York"},
|
||||
{VenueNYSE, true, MarketUS, CurrencyUSD, "America/New_York"},
|
||||
{Venue("UNKNOWN"), false, Market(""), Currency(""), ""},
|
||||
}
|
||||
for _, c := range cases {
|
||||
meta, ok := GetVenueMetadata(c.venue)
|
||||
if ok != c.wantOk {
|
||||
t.Errorf("GetVenueMetadata(%q) ok: got %v, want %v", c.venue, ok, c.wantOk)
|
||||
}
|
||||
if ok {
|
||||
if meta.Market != c.wantMarket || meta.Currency != c.wantCurrency || meta.Timezone != c.wantTimezone {
|
||||
t.Errorf("GetVenueMetadata(%q) meta mismatch: got %+v, want Market: %v, Currency: %v, Timezone: %v", c.venue, meta, c.wantMarket, c.wantCurrency, c.wantTimezone)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssetTypeVocabulary(t *testing.T) {
|
||||
if string(AssetTypeEquity) != "equity" {
|
||||
t.Errorf("AssetTypeEquity: got %q, want %q", string(AssetTypeEquity), "equity")
|
||||
}
|
||||
if string(AssetTypeETF) != "etf" {
|
||||
t.Errorf("AssetTypeETF: got %q, want %q", string(AssetTypeETF), "etf")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -205,6 +205,7 @@ type providerSymbolsFixture struct {
|
|||
Symbol string `json:"symbol"`
|
||||
Name string `json:"name"`
|
||||
Currency string `json:"currency"`
|
||||
AssetType string `json:"asset_type"`
|
||||
ProviderSymbols map[string]string `json:"provider_symbols"`
|
||||
} `json:"instruments"`
|
||||
}
|
||||
|
|
@ -224,6 +225,7 @@ func TestProviderSymbolsFixtureVocabulary(t *testing.T) {
|
|||
market.UniverseSelectorExchange: false,
|
||||
market.UniverseSelectorSector: false,
|
||||
}
|
||||
usWatchlistSymbols := map[string]bool{}
|
||||
for i, sel := range fx.Selectors {
|
||||
kind := market.UniverseSelectorKind(sel.Kind)
|
||||
if _, ok := wantKinds[kind]; !ok {
|
||||
|
|
@ -235,6 +237,10 @@ func TestProviderSymbolsFixtureVocabulary(t *testing.T) {
|
|||
t.Errorf("selector[%d] %q has no symbols", i, sel.Kind)
|
||||
}
|
||||
switch kind {
|
||||
case market.UniverseSelectorWatchlist:
|
||||
for _, s := range sel.Symbols {
|
||||
usWatchlistSymbols[s] = true
|
||||
}
|
||||
case market.UniverseSelectorExchange:
|
||||
if market.Venue(sel.Venue) != market.VenueKRX {
|
||||
t.Errorf("exchange selector venue: got %q, want %q", sel.Venue, market.VenueKRX)
|
||||
|
|
@ -251,23 +257,89 @@ func TestProviderSymbolsFixtureVocabulary(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// Verify watchlist contains required US symbols.
|
||||
for _, wanted := range []string{"AAPL", "SPY"} {
|
||||
if !usWatchlistSymbols[wanted] {
|
||||
t.Errorf("watchlist selector missing US symbol %q", wanted)
|
||||
}
|
||||
}
|
||||
|
||||
if len(fx.Instruments) == 0 {
|
||||
t.Fatal("provider symbols fixture has no instruments")
|
||||
}
|
||||
for i, inst := range fx.Instruments {
|
||||
if market.Venue(inst.Venue) != market.VenueKRX {
|
||||
t.Errorf("instrument[%d] venue: got %q, want %q", i, inst.Venue, market.VenueKRX)
|
||||
|
||||
// Required US instruments: key is "venue:symbol", values are the exact fields.
|
||||
requiredUS := map[string]struct {
|
||||
market market.Market
|
||||
venue market.Venue
|
||||
symbol string
|
||||
currency market.Currency
|
||||
assetType market.AssetType
|
||||
}{
|
||||
"NASDAQ:AAPL": {market.MarketUS, market.VenueNASDAQ, "AAPL", market.CurrencyUSD, market.AssetTypeEquity},
|
||||
"NASDAQ:SPY": {market.MarketUS, market.VenueNASDAQ, "SPY", market.CurrencyUSD, market.AssetTypeETF},
|
||||
}
|
||||
|
||||
// Build instrument map for quick lookup by id.
|
||||
instMap := make(map[string]struct {
|
||||
ID string `json:"id"`
|
||||
Market string `json:"market"`
|
||||
Venue string `json:"venue"`
|
||||
Symbol string `json:"symbol"`
|
||||
Name string `json:"name"`
|
||||
Currency string `json:"currency"`
|
||||
AssetType string `json:"asset_type"`
|
||||
ProviderSymbols map[string]string `json:"provider_symbols"`
|
||||
}, len(fx.Instruments))
|
||||
for _, inst := range fx.Instruments {
|
||||
instMap[inst.ID] = inst
|
||||
}
|
||||
|
||||
// Validate US required instruments.
|
||||
for key, want := range requiredUS {
|
||||
inst, ok := instMap[key]
|
||||
if !ok {
|
||||
t.Errorf("required US instrument %q missing from fixture", key)
|
||||
continue
|
||||
}
|
||||
if market.Currency(inst.Currency) != market.CurrencyKRW {
|
||||
t.Errorf("instrument[%d] currency: got %q, want %q", i, inst.Currency, market.CurrencyKRW)
|
||||
if market.Market(inst.Market) != want.market {
|
||||
t.Errorf("instrument[%s] market: got %q, want %q", key, inst.Market, want.market)
|
||||
}
|
||||
if market.Venue(inst.Venue) != want.venue {
|
||||
t.Errorf("instrument[%s] venue: got %q, want %q", key, inst.Venue, want.venue)
|
||||
}
|
||||
if inst.Symbol != want.symbol {
|
||||
t.Errorf("instrument[%s] symbol: got %q, want %q", key, inst.Symbol, want.symbol)
|
||||
}
|
||||
if market.Currency(inst.Currency) != want.currency {
|
||||
t.Errorf("instrument[%s] currency: got %q, want %q", key, inst.Currency, want.currency)
|
||||
}
|
||||
if market.AssetType(inst.AssetType) != want.assetType {
|
||||
t.Errorf("instrument[%s] asset_type: got %q, want %q", key, inst.AssetType, want.assetType)
|
||||
}
|
||||
sym, ok := inst.ProviderSymbols[string(market.ProviderKIS)]
|
||||
if !ok || sym == "" {
|
||||
t.Errorf("instrument[%d] missing kis provider symbol", i)
|
||||
t.Errorf("instrument[%s] missing kis provider symbol", key)
|
||||
continue
|
||||
}
|
||||
if sym != inst.Symbol {
|
||||
t.Errorf("instrument[%d] kis provider symbol %q != symbol %q", i, sym, inst.Symbol)
|
||||
t.Errorf("instrument[%s] kis provider symbol %q != symbol %q", key, sym, inst.Symbol)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate non-US instruments still allow expected enum values.
|
||||
for i, inst := range fx.Instruments {
|
||||
v := market.Venue(inst.Venue)
|
||||
if v != market.VenueKRX && v != market.VenueNASDAQ && v != market.VenueNYSE {
|
||||
t.Errorf("instrument[%d] unexpected venue: %q", i, inst.Venue)
|
||||
}
|
||||
c := market.Currency(inst.Currency)
|
||||
if c != market.CurrencyKRW && c != market.CurrencyUSD {
|
||||
t.Errorf("instrument[%d] unexpected currency: %q", i, inst.Currency)
|
||||
}
|
||||
at := market.AssetType(inst.AssetType)
|
||||
if at != market.AssetTypeEquity && at != market.AssetTypeETF {
|
||||
t.Errorf("instrument[%d] unexpected asset type: %q", i, inst.AssetType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,6 +16,31 @@ func instrumentToProto(inst market.Instrument) *altv1.Instrument {
|
|||
Name: inst.Name,
|
||||
Currency: currencyToProto(inst.Currency),
|
||||
ProviderSymbols: cloneProviderSymbols(inst.ProviderSymbols),
|
||||
AssetType: assetTypeToProto(inst.AssetType),
|
||||
}
|
||||
}
|
||||
|
||||
func assetTypeToProto(at market.AssetType) altv1.AssetType {
|
||||
switch at {
|
||||
case market.AssetTypeEquity:
|
||||
return altv1.AssetType_ASSET_TYPE_EQUITY
|
||||
case market.AssetTypeETF:
|
||||
return altv1.AssetType_ASSET_TYPE_ETF
|
||||
default:
|
||||
return altv1.AssetType_ASSET_TYPE_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
|
||||
func assetTypeFromProto(at altv1.AssetType) (market.AssetType, error) {
|
||||
switch at {
|
||||
case altv1.AssetType_ASSET_TYPE_UNSPECIFIED:
|
||||
return "", nil
|
||||
case altv1.AssetType_ASSET_TYPE_EQUITY:
|
||||
return market.AssetTypeEquity, nil
|
||||
case altv1.AssetType_ASSET_TYPE_ETF:
|
||||
return market.AssetTypeETF, nil
|
||||
default:
|
||||
return "", fmt.Errorf("unsupported asset type %q", at.String())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,12 +10,13 @@ import (
|
|||
|
||||
func TestInstrumentToProto(t *testing.T) {
|
||||
got := instrumentToProto(market.Instrument{
|
||||
ID: "KRX:005930",
|
||||
Market: market.MarketKR,
|
||||
Venue: market.VenueKRX,
|
||||
Symbol: "005930",
|
||||
Name: "Samsung Electronics",
|
||||
Currency: market.CurrencyKRW,
|
||||
ID: "KRX:005930",
|
||||
Market: market.MarketKR,
|
||||
Venue: market.VenueKRX,
|
||||
Symbol: "005930",
|
||||
Name: "Samsung Electronics",
|
||||
Currency: market.CurrencyKRW,
|
||||
AssetType: market.AssetTypeEquity,
|
||||
ProviderSymbols: map[string]string{
|
||||
"kis": "005930",
|
||||
},
|
||||
|
|
@ -33,11 +34,38 @@ func TestInstrumentToProto(t *testing.T) {
|
|||
if got.GetCurrency() != altv1.Currency_CURRENCY_KRW {
|
||||
t.Errorf("currency mismatch: %v", got.GetCurrency())
|
||||
}
|
||||
if got.GetAssetType() != altv1.AssetType_ASSET_TYPE_EQUITY {
|
||||
t.Errorf("asset type mismatch: %v", got.GetAssetType())
|
||||
}
|
||||
if got.GetProviderSymbols()["kis"] != "005930" {
|
||||
t.Errorf("provider symbols mismatch: %+v", got.GetProviderSymbols())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAssetTypeMapping(t *testing.T) {
|
||||
cases := []struct {
|
||||
domain market.AssetType
|
||||
proto altv1.AssetType
|
||||
}{
|
||||
{market.AssetTypeEquity, altv1.AssetType_ASSET_TYPE_EQUITY},
|
||||
{market.AssetTypeETF, altv1.AssetType_ASSET_TYPE_ETF},
|
||||
{market.AssetType(""), altv1.AssetType_ASSET_TYPE_UNSPECIFIED},
|
||||
}
|
||||
for _, c := range cases {
|
||||
gotProto := assetTypeToProto(c.domain)
|
||||
if gotProto != c.proto {
|
||||
t.Errorf("assetTypeToProto(%q): got %v, want %v", c.domain, gotProto, c.proto)
|
||||
}
|
||||
gotDomain, err := assetTypeFromProto(c.proto)
|
||||
if err != nil {
|
||||
t.Errorf("assetTypeFromProto(%v) error: %v", c.proto, err)
|
||||
}
|
||||
if gotDomain != c.domain {
|
||||
t.Errorf("assetTypeFromProto(%v): got %q, want %q", c.proto, gotDomain, c.domain)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstrumentToProtoClonesProviderSymbols(t *testing.T) {
|
||||
symbols := map[string]string{"kis": "005930"}
|
||||
got := instrumentToProto(market.Instrument{ProviderSymbols: symbols})
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ func mapInstrumentToParams(inst market.Instrument) (sqlc.UpsertInstrumentParams,
|
|||
Name: inst.Name,
|
||||
Currency: string(inst.Currency),
|
||||
ProviderSymbols: provBytes,
|
||||
AssetType: string(inst.AssetType),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -87,6 +88,7 @@ func mapRowToInstrument(row sqlc.Instrument) (market.Instrument, error) {
|
|||
Symbol: row.Symbol,
|
||||
Name: row.Name,
|
||||
Currency: market.Currency(row.Currency),
|
||||
AssetType: market.AssetType(row.AssetType),
|
||||
ProviderSymbols: provSymbols,
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -338,3 +338,64 @@ func TestBacktestResultMappingRejectsInvalidDecimal(t *testing.T) {
|
|||
t.Fatal("expected error when mapping result with invalid ending equity decimal, got nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestInstrumentMappingRoundTrip(t *testing.T) {
|
||||
inst := market.Instrument{
|
||||
ID: "NASDAQ:AAPL",
|
||||
Market: market.MarketUS,
|
||||
Venue: market.VenueNASDAQ,
|
||||
Symbol: "AAPL",
|
||||
Name: "Apple Inc.",
|
||||
Currency: market.CurrencyUSD,
|
||||
AssetType: market.AssetTypeEquity,
|
||||
ProviderSymbols: map[string]string{
|
||||
"kis": "AAPL",
|
||||
},
|
||||
}
|
||||
|
||||
params, err := mapInstrumentToParams(inst)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to map instrument to params: %v", err)
|
||||
}
|
||||
|
||||
row := sqlc.Instrument{
|
||||
ID: params.ID,
|
||||
Market: params.Market,
|
||||
Venue: params.Venue,
|
||||
Symbol: params.Symbol,
|
||||
Name: params.Name,
|
||||
Currency: params.Currency,
|
||||
ProviderSymbols: params.ProviderSymbols,
|
||||
AssetType: params.AssetType,
|
||||
}
|
||||
|
||||
mappedBack, err := mapRowToInstrument(row)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to map row back to instrument: %v", err)
|
||||
}
|
||||
|
||||
if mappedBack.ID != inst.ID {
|
||||
t.Errorf("ID mismatch: got %v, want %v", mappedBack.ID, inst.ID)
|
||||
}
|
||||
if mappedBack.Market != inst.Market {
|
||||
t.Errorf("Market mismatch: got %v, want %v", mappedBack.Market, inst.Market)
|
||||
}
|
||||
if mappedBack.Venue != inst.Venue {
|
||||
t.Errorf("Venue mismatch: got %v, want %v", mappedBack.Venue, inst.Venue)
|
||||
}
|
||||
if mappedBack.Symbol != inst.Symbol {
|
||||
t.Errorf("Symbol mismatch: got %v, want %v", mappedBack.Symbol, inst.Symbol)
|
||||
}
|
||||
if mappedBack.Name != inst.Name {
|
||||
t.Errorf("Name mismatch: got %v, want %v", mappedBack.Name, inst.Name)
|
||||
}
|
||||
if mappedBack.Currency != inst.Currency {
|
||||
t.Errorf("Currency mismatch: got %v, want %v", mappedBack.Currency, inst.Currency)
|
||||
}
|
||||
if mappedBack.AssetType != inst.AssetType {
|
||||
t.Errorf("AssetType mismatch: got %v, want %v", mappedBack.AssetType, inst.AssetType)
|
||||
}
|
||||
if mappedBack.ProviderSymbols["kis"] != inst.ProviderSymbols["kis"] {
|
||||
t.Errorf("ProviderSymbols mismatch: got %+v, want %+v", mappedBack.ProviderSymbols, inst.ProviderSymbols)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE instruments DROP COLUMN asset_type;
|
||||
|
|
@ -0,0 +1 @@
|
|||
ALTER TABLE instruments ADD COLUMN asset_type TEXT NOT NULL DEFAULT '';
|
||||
|
|
@ -1,21 +1,22 @@
|
|||
-- name: UpsertInstrument :exec
|
||||
INSERT INTO instruments (id, market, venue, symbol, name, currency, provider_symbols)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
INSERT INTO instruments (id, market, venue, symbol, name, currency, provider_symbols, asset_type)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
market = EXCLUDED.market,
|
||||
venue = EXCLUDED.venue,
|
||||
symbol = EXCLUDED.symbol,
|
||||
name = EXCLUDED.name,
|
||||
currency = EXCLUDED.currency,
|
||||
provider_symbols = EXCLUDED.provider_symbols;
|
||||
provider_symbols = EXCLUDED.provider_symbols,
|
||||
asset_type = EXCLUDED.asset_type;
|
||||
|
||||
-- name: GetInstrument :one
|
||||
SELECT id, market, venue, symbol, name, currency, provider_symbols
|
||||
SELECT id, market, venue, symbol, name, currency, provider_symbols, asset_type
|
||||
FROM instruments
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: ListInstruments :many
|
||||
SELECT id, market, venue, symbol, name, currency, provider_symbols
|
||||
SELECT id, market, venue, symbol, name, currency, provider_symbols, asset_type
|
||||
FROM instruments;
|
||||
|
||||
-- name: UpsertBar :exec
|
||||
|
|
|
|||
|
|
@ -52,4 +52,5 @@ type Instrument struct {
|
|||
Name string
|
||||
Currency string
|
||||
ProviderSymbols []byte
|
||||
AssetType string
|
||||
}
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ func (q *Queries) GetBars(ctx context.Context, arg GetBarsParams) ([]Bar, error)
|
|||
}
|
||||
|
||||
const getInstrument = `-- name: GetInstrument :one
|
||||
SELECT id, market, venue, symbol, name, currency, provider_symbols
|
||||
SELECT id, market, venue, symbol, name, currency, provider_symbols, asset_type
|
||||
FROM instruments
|
||||
WHERE id = $1
|
||||
`
|
||||
|
|
@ -76,6 +76,7 @@ func (q *Queries) GetInstrument(ctx context.Context, id string) (Instrument, err
|
|||
&i.Name,
|
||||
&i.Currency,
|
||||
&i.ProviderSymbols,
|
||||
&i.AssetType,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
|
@ -128,7 +129,7 @@ func (q *Queries) GetRun(ctx context.Context, id string) (BacktestRun, error) {
|
|||
}
|
||||
|
||||
const listInstruments = `-- name: ListInstruments :many
|
||||
SELECT id, market, venue, symbol, name, currency, provider_symbols
|
||||
SELECT id, market, venue, symbol, name, currency, provider_symbols, asset_type
|
||||
FROM instruments
|
||||
`
|
||||
|
||||
|
|
@ -149,6 +150,7 @@ func (q *Queries) ListInstruments(ctx context.Context) ([]Instrument, error) {
|
|||
&i.Name,
|
||||
&i.Currency,
|
||||
&i.ProviderSymbols,
|
||||
&i.AssetType,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -234,15 +236,16 @@ func (q *Queries) UpsertBar(ctx context.Context, arg UpsertBarParams) error {
|
|||
}
|
||||
|
||||
const upsertInstrument = `-- name: UpsertInstrument :exec
|
||||
INSERT INTO instruments (id, market, venue, symbol, name, currency, provider_symbols)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
INSERT INTO instruments (id, market, venue, symbol, name, currency, provider_symbols, asset_type)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
ON CONFLICT (id) DO UPDATE SET
|
||||
market = EXCLUDED.market,
|
||||
venue = EXCLUDED.venue,
|
||||
symbol = EXCLUDED.symbol,
|
||||
name = EXCLUDED.name,
|
||||
currency = EXCLUDED.currency,
|
||||
provider_symbols = EXCLUDED.provider_symbols
|
||||
provider_symbols = EXCLUDED.provider_symbols,
|
||||
asset_type = EXCLUDED.asset_type
|
||||
`
|
||||
|
||||
type UpsertInstrumentParams struct {
|
||||
|
|
@ -253,6 +256,7 @@ type UpsertInstrumentParams struct {
|
|||
Name string
|
||||
Currency string
|
||||
ProviderSymbols []byte
|
||||
AssetType string
|
||||
}
|
||||
|
||||
func (q *Queries) UpsertInstrument(ctx context.Context, arg UpsertInstrumentParams) error {
|
||||
|
|
@ -264,6 +268,7 @@ func (q *Queries) UpsertInstrument(ctx context.Context, arg UpsertInstrumentPara
|
|||
arg.Name,
|
||||
arg.Currency,
|
||||
arg.ProviderSymbols,
|
||||
arg.AssetType,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"selectors": [
|
||||
{
|
||||
"kind": "watchlist",
|
||||
"symbols": ["005930", "000660"]
|
||||
"symbols": ["005930", "000660", "AAPL", "SPY"]
|
||||
},
|
||||
{
|
||||
"kind": "exchange",
|
||||
|
|
@ -24,6 +24,7 @@
|
|||
"symbol": "005930",
|
||||
"name": "Samsung Electronics",
|
||||
"currency": "KRW",
|
||||
"asset_type": "equity",
|
||||
"provider_symbols": {
|
||||
"kis": "005930"
|
||||
}
|
||||
|
|
@ -35,9 +36,34 @@
|
|||
"symbol": "000660",
|
||||
"name": "SK Hynix",
|
||||
"currency": "KRW",
|
||||
"asset_type": "equity",
|
||||
"provider_symbols": {
|
||||
"kis": "000660"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "NASDAQ:AAPL",
|
||||
"market": "US",
|
||||
"venue": "NASDAQ",
|
||||
"symbol": "AAPL",
|
||||
"name": "Apple Inc.",
|
||||
"currency": "USD",
|
||||
"asset_type": "equity",
|
||||
"provider_symbols": {
|
||||
"kis": "AAPL"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "NASDAQ:SPY",
|
||||
"market": "US",
|
||||
"venue": "NASDAQ",
|
||||
"symbol": "SPY",
|
||||
"name": "SPDR S&P 500 ETF Trust",
|
||||
"currency": "USD",
|
||||
"asset_type": "etf",
|
||||
"provider_symbols": {
|
||||
"kis": "SPY"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue