refactor: organize operator-ui phase and update roadmap structures

- Move flutter operator console and push notification milestones to operator-ui phase
- Archive kis-live-data-collection-pipeline milestone
- Add command-first operator workflow milestone
- Add operator-ui phase definition
- Add paper-trading-command-workflow milestone
- Update phase documentation and dependencies
This commit is contained in:
toki 2026-06-03 18:10:52 +09:00
parent 316db7de50
commit ee01990117
18 changed files with 357 additions and 75 deletions

View file

@ -2,7 +2,7 @@
## 전체 목표
ALT는 개인용 quant system workspace로, 한국장 일봉 MVP에서 시작해 시장 데이터 수집, 정규화, 백테스트, 결과 분석, Flutter 운영 클라이언트, 페이퍼 트레이딩, 미국장 확장, 실거래 경계까지 단계적으로 확장한다. Go 서비스는 뒤집지 않는 운영 코어를 담당하고, Flutter client는 web/mobile/desktop 단일 UI 표면을 담당하며, proto-socket과 ALT protobuf contracts가 런타임 사이의 통신 기준이 된다.
ALT는 개인용 quant system workspace로, 한국장 일봉 MVP에서 시작해 시장 데이터 수집, 정규화, 백테스트, 결과 분석, command-first 운영 흐름, 페이퍼 트레이딩, 미국장 확장, 실거래 경계, Flutter 운영 UI까지 단계적으로 확장한다. Go 서비스와 CLI/YAML workflow는 뒤집지 않는 운영 코어를 담당하고, Flutter client는 충분히 표면화된 운영 항목을 web/mobile/desktop 단일 UI 표면으로 정리하는 후순위 단계로 둔다. proto-socket과 ALT protobuf contracts가 런타임 사이의 통신 기준이 된다.
## Phase 흐름
@ -21,10 +21,13 @@ ALT는 개인용 quant system workspace로, 한국장 일봉 MVP에서 시작해
- 요약: 일봉 기반 strategy execution, backtest lifecycle, result persistence, repeatable verification을 만든다.
- [진행중] 운영 표면
- 경로: `agent-roadmap/phase/operator-surface/PHASE.md`
- 요약: Flutter client에서 백테스트와 market data 상태를 조회하고 실행할 수 있는 operator console을 만든다. client integration 복제 작업은 별도 프로젝트 산출물로 다룬다.
- 요약: Flutter 실화면 전에 CLI/YAML/log 중심 command-first operator workflow로 KIS data, storage status, backtest 실행/결과 확인을 반복 가능하게 만든다.
- [계획] 거래 확장
- 경로: `agent-roadmap/phase/trading-expansion/PHASE.md`
- 요약: paper trading, 미국장 확장, 주문/리스크/포트폴리오, 실거래 adapter 경계를 MVP 이후 단계로 연다.
- [보류] 운영 UI
- 경로: `agent-roadmap/phase/operator-ui/PHASE.md`
- 요약: command/headless workflow와 거래 확장 기능이 충분히 표면화된 뒤 Flutter operator console과 push notification UI boundary를 연다.
## 로딩 정책

View file

@ -11,14 +11,14 @@ Flutter 실화면 구현 전에 한국투자증권(KIS) API를 실제 provider
## 상태
[검토중]
[완료]
## 구현 잠금
- 상태: 해제
- 결정 필요: 아래 체크리스트
- [x] KIS live smoke에 사용할 credential 주입 방식을 정한다. 원문 secret, secret-store 항목 이름, 계좌번호는 tracked 문서에 기록하지 않는다. 기준: SOPS + age 단일 원본, `bin/kis-sops-env` command-lifetime env 주입.
- [x] live smoke 대상 범위를 정한다: 국내주식 일봉 `inquire-daily-itemchartprice` 단일 symbol/date range부터 시작한다. 기준: paper `005930`, `20240527~20240528`.
- [x] live smoke 대상 범위를 정한다: 국내주식 일봉 `inquire-daily-itemchartprice` 단일 symbol/date range부터 시작한다. 기준: paper/real `005930`, `20240527~20240528`.
- [x] live 호출 결과를 commit 가능한 sanitized fixture/evidence로 남기는 기준을 정한다. 기준: 데이터 적재가 아니라 테스트 evidence만 남기며, raw payload/header/token/key/account는 저장하지 않고 symbol/date/bar_count/msg_cd/result 같은 요약만 기록한다.
## 범위
@ -39,22 +39,22 @@ Flutter 실화면 구현 전에 한국투자증권(KIS) API를 실제 provider
fixture/mock 기반 adapter를 실제 KIS provider 호출과 worker import pipeline으로 확장한다.
- [x] [credential-boundary] KIS credential은 command lifetime 동안만 주입하고, access token은 runtime에서 발급한다. 검증: SOPS + age 단일 원본과 `bin/kis-sops-env` command-lifetime env 주입을 사용하고, tracked 파일에 raw secret, secret-store 항목 이름, 계좌번호를 기록하지 않는다.
- [x] [auth-client] KIS auth/token client가 국내주식 일봉 호출에 필요한 access token을 얻고 만료/오류 상태를 분류한다. 검증: fake auth response unit test, credential 없는 unavailable test, SOPS 주입 live-gated paper smoke가 통과한다.
- [x] [auth-client] KIS auth/token client가 국내주식 일봉 호출에 필요한 access token을 얻고 만료/오류 상태를 분류한다. 검증: fake auth response unit test, credential 없는 unavailable test, SOPS 주입 live-gated paper/real smoke가 통과한다.
- [x] [daily-rest-client] KIS `inquire-daily-itemchartprice` REST client가 request parameter, response decode, error/quota/malformed 상태를 처리한다. 검증: 로컬 KIS 공식 샘플 cache, sanitized fixture 기반 test, quota/malformed/error-redaction test가 통과한다.
- [x] [live-provider] worker market data importer가 KIS live provider를 선택해 일봉 데이터를 fetch할 수 있다. 검증: fake HTTP test가 provider request, normalized bars, importer storage upsert를 확인하고, `alt-worker`가 KIS importer handler를 등록한다.
- [x] [live-smoke] credential이 준비된 환경에서 단일 symbol/date range KIS live smoke를 실행하고 sanitized evidence를 남긴다. 검증: 실제 KIS paper 호출 결과를 raw secret 없이 요약 evidence로 기록하고, 실패 시 auth/quota/unavailable 원인이 grep 가능한 상태로 남는다.
- [x] [live-smoke] credential이 준비된 환경에서 단일 symbol/date range KIS live smoke를 실행하고 sanitized evidence를 남긴다. 검증: 실제 KIS paper/real 호출 결과를 raw secret 없이 요약 evidence로 기록하고, 실패 시 auth/quota/unavailable 원인이 grep 가능한 상태로 남는다.
- [x] [backtest-input] KIS live로 수집한 normalized daily bars가 backtest 입력 조회 adapter 경계에서 사용 가능하다. 검증: `StorageBarSource`와 KIS fake HTTP -> importer -> store -> `StorageBarSource` query test가 통과한다.
- [x] [operator-handoff] Flutter Operator Console MVP로 넘길 provider readiness와 market data status key를 정리한다. 검증: 실행 명령, secret 주입 전제, sanitized evidence key, unavailable/auth/quota/malformed/provider 상태를 문서화한다.
## 완료 리뷰
- 상태: 요청
- 상태: 승인
- 요청일: 2026-06-03
- 완료 근거: SOPS 주입 기반 KIS paper 일봉 smoke, worker KIS auth/daily/live-provider, import/storage/`StorageBarSource` 입력 조회 evidence가 준비됐다.
- 완료 근거: SOPS 주입 기반 KIS paper/real 일봉 smoke, worker KIS auth/daily/live-provider, import/storage/`StorageBarSource` 입력 조회 evidence가 준비됐다.
- 리뷰 필요:
- [ ] 사용자가 완료 결과를 확인했다
- [ ] archive 이동을 승인했다
- 리뷰 코멘트: 모든 기능 Task는 완료 후보이며, 사용자 최종 확인 전까지 활성 마일스톤으로 유지한다.
- [x] 사용자가 완료 결과를 확인했다
- [x] archive 이동을 승인했다
- 리뷰 코멘트: 2026-06-03 사용자 요청에 따라 현재 마일스톤을 종료하고 archive로 이동했다.
## 범위 제외
@ -77,7 +77,9 @@ fixture/mock 기반 adapter를 실제 KIS provider 호출과 worker import pipel
- 표준선(선택): 원격 field secret boundary는 SOPS + age를 agent runtime secret의 단일 원본으로 둔다. code-server 컨테이너에는 age private key나 평문 secret 파일을 두지 않고, SOPS 복호화 값을 command lifetime 동안만 환경변수로 주입해 smoke를 실행한다.
- 표준선(선택): SOPS/age key 위치와 host 경로 같은 환경 세부는 private testing rule에만 두고 tracked roadmap에는 raw secret, 계좌번호, secret-store 항목 이름을 기록하지 않는다.
- 최근 evidence: 2026-06-03 원격 host에서 `KIS_ACTIVE_ENV=paper`와 SOPS 주입으로 `bin/kis-paper-daily-smoke --symbol 005930 --start 20240527 --end 20240528` 실행 성공. Sanitized output: `KIS paper daily smoke ok`, `name=삼성전자`, `bar_count=2`, `first_date=20240527`, `last_date=20240528`, `msg_cd=MCA00000`.
- 최근 evidence: 2026-06-03 원격 host에서 `KIS_ACTIVE_ENV=real`와 SOPS 주입으로 `bin/kis-paper-daily-smoke --symbol 005930 --start 20240527 --end 20240528` 실행 성공. Sanitized output: `KIS real daily smoke ok`, `name=삼성전자`, `bar_count=2`, `first_date=20240527`, `last_date=20240528`, `msg_cd=MCA00000`.
- 최근 evidence: 2026-06-03 원격 host에서 `KIS_ACTIVE_ENV=paper`, `KIS_LIVE_SMOKE=1`, SOPS 주입으로 `go test ./services/worker/internal/providers/kis -run TestLiveProviderPaperSmoke -count=1 -v` 실행 성공. Sanitized output: `PASS`, `TestLiveProviderPaperSmoke`.
- 최근 evidence: 2026-06-03 원격 host에서 `KIS_ACTIVE_ENV=real`, `KIS_REAL_LIVE_SMOKE=1`, SOPS 주입으로 `go test ./services/worker/internal/providers/kis -run TestLiveProviderRealSmoke -count=1 -v` 실행 성공. Sanitized output: `PASS`, `TestLiveProviderRealSmoke`.
- 선행 작업: Korea Daily Data Foundation, Backtest Engine Baseline, Operator Client API/Core Integration Validation
- 후속 작업: Flutter Operator Console MVP
- 확인 필요: 없음

View file

@ -7,9 +7,9 @@
## 활성 Milestone
- [검토중] KIS Live Data Collection Pipeline
- [계획] Command-First Operator Workflow
- Phase: `agent-roadmap/phase/operator-surface/PHASE.md`
- 경로: `agent-roadmap/phase/operator-surface/milestones/kis-live-data-collection-pipeline.md`
- 경로: `agent-roadmap/phase/operator-surface/milestones/command-first-operator-workflow.md`
## 선택 규칙

View file

@ -6,7 +6,7 @@
## 목표
Flutter client를 ALT의 공식 operator surface로 삼아 market data 상태와 backtest 실행 및 결과를 조회할 수 있게 한다. 먼저 API 서버 중심의 proto-socket 내부 통신 rail을 정리하고, 화면 없이 CLI/YAML/log/test fixture로 운영 흐름을 검증한 뒤 wireframe이 준비된 경우에만 Flutter 실화면 구현으로 넘어간다. 이 Phase에서는 web/mobile/desktop 단일 UI 표면을 유지하면서 API와 generated contract 기준을 맞추고, push notification은 별도 후속 Milestone으로 분리한다.
Flutter 실화면 전에 API 서버 중심 proto-socket rail과 CLI/YAML/log/test fixture 기반 command-first operator workflow를 안정화한다. KIS live data, worker storage, market status, backtest 실행/결과 확인을 화면 없이 반복 가능하게 만들고, 운영 항목과 상태 key가 충분히 표면화되면 후순위 `운영 UI` Phase로 넘긴다.
## Milestone 흐름
@ -28,23 +28,18 @@ Flutter client를 ALT의 공식 operator surface로 삼아 market data 상태와
- [완료] Operator Client API/Core Integration Validation
- 경로: `agent-roadmap/archive/phase/operator-surface/milestones/operator-client-api-core-integration.md`
- 요약: Flutter 실화면 전에 client core/provider/repository 경계에서 API socket 연결과 기본 market/backtest 호출을 검증했다.
- [계획] KIS Live Data Collection Pipeline
- 경로: `agent-roadmap/phase/operator-surface/milestones/kis-live-data-collection-pipeline.md`
- [완료] KIS Live Data Collection Pipeline
- 경로: `agent-roadmap/archive/phase/operator-surface/milestones/kis-live-data-collection-pipeline.md`
- 요약: Flutter 실화면 전에 KIS 실 API 기반 한국장 일봉 수집, worker import/storage, `StorageBarSource` 입력 조회 경계까지 검증한다.
- [보류] Flutter Operator Console MVP
- 경로: `agent-roadmap/phase/operator-surface/milestones/flutter-operator-console-mvp.md`
- 요약: headless 운영 검증과 화면 wireframe 승인 뒤에만 Flutter에서 market data, backtest run, result를 조회하고 실행 요청할 수 있는 MVP 화면을 구현한다.
- [스케치] Flutter Push Notification Boundary
- 경로: `agent-roadmap/phase/operator-surface/milestones/flutter-push-notification-boundary.md`
- 요약: push notification을 operator console 본작업에서 분리하고, Firebase/platform channel 등 외부 protocol 경계를 후속 작업으로 정리한다.
- [계획] Command-First Operator Workflow
- 경로: `agent-roadmap/phase/operator-surface/milestones/command-first-operator-workflow.md`
- 요약: Flutter 실화면을 보류하고 KIS live data, worker storage, market status, backtest 실행/결과 확인을 CLI/YAML/log 중심의 반복 가능한 operator workflow로 묶는다.
## Phase 경계
- 이 Phase는 Flutter client의 운영 화면과 API/client 통신 표면에 집중한다.
- 이 Phase는 Flutter 화면이 아니라 API/client 통신 rail, CLI/YAML/log 기반 운영 command surface, UI handoff evidence에 집중한다.
- 별도 TypeScript web app, 고급 charting 전체, mobile app store 배포 설정은 제외한다.
- API 서버를 operator-facing control plane으로 두고, client와 worker는 API를 기준으로 proto-socket rail에 연결한다.
- UI는 generated/mapped contracts와 proto-socket client layer를 기준으로 API와 통신한다.
- 운영 기능은 화면 구현 전에 CLI, terminal output, YAML scenario, log, test fixture 같은 headless 경로로 구성과 결과를 먼저 검증한다.
- Flutter 실화면 구현은 headless 검증 완료와 wireframe 준비/승인을 구현 잠금 해제 조건으로 둔다.
- client skeleton과 bootstrap convention은 외부 client integration 프로젝트 산출물을 추후 반영하되, Riverpod/go_router와 API client boundary는 이 Phase의 Flutter 표면 안에서 명시한다.
- push notification은 현재 operator console 범위에서 제외하고 `Flutter Push Notification Boundary` Milestone으로 분리한다.
- Flutter 실화면 구현은 후순위 `운영 UI` Phase에서만 다룬다.
- client skeleton과 bootstrap convention, Riverpod/go_router, push notification은 `운영 UI` Phase에서 다룬다.

View file

@ -0,0 +1,70 @@
# Milestone: Command-First Operator Workflow
## 위치
- Roadmap: `agent-roadmap/ROADMAP.md`
- Phase: `agent-roadmap/phase/operator-surface/PHASE.md`
## 목표
Flutter 실화면을 보류한 상태에서 KIS live data, worker storage, market status, backtest 실행/결과 확인을 CLI/YAML/로그 중심의 반복 가능한 operator workflow로 묶는다. 화면에 올릴 기능이 충분히 표면화될 때까지 command-first 운영을 기본 경로로 유지하고, 나중에 UI MVP가 가져갈 contract와 evidence key를 축적한다.
## 상태
[계획]
## 구현 잠금
- 상태: 해제
- 결정 필요: 없음
## 범위
- KIS paper/real daily smoke와 provider readiness command handoff
- KIS live daily import를 worker storage까지 연결하는 command 또는 scenario 경계
- storage 기반 market data status 조회 command/YAML scenario
- imported daily bars를 사용한 backtest start/poll/result command workflow
- command output의 machine-readable status/evidence key 정리
- UI MVP로 넘길 operator workflow handoff 갱신
## 기능
### Epic: [cmd-operator] Command-first operator workflow
화면 없이도 운영자가 KIS 데이터 수집, 상태 조회, 백테스트 실행/결과 확인을 반복할 수 있는 command path를 만든다.
- [ ] [kis-smoke-runbook] KIS paper/real daily smoke와 Go live-provider smoke 실행 경로가 command runbook으로 정리되어 있다. 검증: secret 원문 없이 readiness/smoke/evidence key가 문서화되고 smoke command가 dry failure와 live-gated success 경로를 구분한다.
- [ ] [live-import-command] KIS live daily bars를 worker storage에 import하는 command 또는 YAML scenario가 있다. 검증: fake/provider test와 필요한 integration smoke가 import result와 storage upsert를 확인한다.
- [ ] [market-status-scenario] imported market data를 CLI/YAML로 조회할 수 있다. 검증: `alt operator scenario validate/run` 또는 동등한 command가 instrument/bar count와 typed unavailable/error key를 출력한다.
- [ ] [backtest-command-flow] imported daily bars를 입력으로 backtest start/poll/result를 command workflow에서 확인할 수 있다. 검증: start/poll/result scenario가 run id, terminal status, result summary key를 출력한다.
- [ ] [operator-handoff] command-first workflow가 UI MVP로 넘길 화면 후보와 아직 화면으로 올리지 않을 항목을 분리한다. 검증: handoff 문서가 command, expected output key, 반복 운영 여부, UI defer 사유를 함께 기록하고, 충분히 표면화되지 않은 기능은 화면 후보에서 제외한다.
## 완료 리뷰
- 상태: 없음
- 요청일: 없음
- 완료 근거: 아직 기능 Task와 검증이 충족되지 않았다.
- 리뷰 필요:
- [ ] 사용자가 완료 결과를 확인했다
- [ ] archive 이동을 승인했다
- 리뷰 코멘트: 없음
## 범위 제외
- Flutter 실화면 구현
- wireframe 확정
- push notification
- paper trading 주문/포지션 모델
- 실거래 주문 API
- production 자동 수집 스케줄링
- secret 원문, 계좌번호, token의 tracked 문서 기록
## 작업 컨텍스트
- 관련 경로: `apps/cli/`, `apps/cli/testdata/operator/`, `bin/`, `services/api/`, `services/worker/`, `docs/`
- 표준선(선택): 현재 운영은 화면보다 CLI/YAML/log/test fixture를 우선한다. Flutter MVP는 command workflow에서 반복 운영 항목, 상태 key, 우선순위가 충분히 표면화되어 정리 가능한 뒤에 다시 연다.
- 표준선(선택): command output은 사람이 읽을 수 있으면서도 `jsonl`/status key처럼 script가 grep 가능한 형태를 유지한다.
- 표준선(선택): KIS secret은 SOPS + age command-lifetime env 주입 경계만 사용하고 tracked 문서에는 sanitized evidence만 남긴다.
- 선행 작업: Operator Headless Workflow Validation, Operator Client API/Core Integration Validation, KIS Live Data Collection Pipeline
- 후속 작업: Paper Trading Readiness, Paper Trading Command Workflow, US Market Expansion, Live Trading Boundary, Flutter Operator Console MVP
- 확인 필요: 없음

View file

@ -0,0 +1,29 @@
# Phase: 운영 UI
## 상태
[보류]
## 목표
command/headless workflow, paper trading, 시장 확장, 실거래 경계 후보처럼 운영 기능이 충분히 표면화된 뒤 Flutter client에 올릴 화면 후보를 정리하고 구현한다. UI는 기능 탐색의 시작점이 아니라 반복 운영 항목, 상태 key, 우선순위, wireframe 승인까지 준비된 뒤 여는 후순위 표면으로 둔다.
## Milestone 흐름
완료된 Milestone은 archive 경로를 가리키고, 검토중, 진행중, 계획, 스케치 또는 보류 Milestone은 이 Phase 하위 `milestones/` 경로를 가리킨다.
완료, 검토중, 진행중, 계획, 보류, 스케치 순서로 두어 아래로 갈수록 미래 작업에 가까워지게 정렬한다.
- [보류] Flutter Operator Console MVP
- 경로: `agent-roadmap/phase/operator-ui/milestones/flutter-operator-console-mvp.md`
- 요약: command/headless workflow에서 충분히 표면화된 운영 항목과 승인된 wireframe을 기준으로 Flutter에서 market data, backtest, paper trading 후보를 조회하고 실행 요청할 수 있는 MVP 화면을 구현한다.
- [스케치] Flutter Push Notification Boundary
- 경로: `agent-roadmap/phase/operator-ui/milestones/flutter-push-notification-boundary.md`
- 요약: push notification을 operator console 본작업에서 분리하고, Firebase/platform channel 등 외부 protocol 경계를 후속 작업으로 정리한다.
## Phase 경계
- 이 Phase는 Flutter client 실화면, navigation, dashboard/control surface, push notification UI boundary를 다룬다.
- command-first workflow, paper trading command workflow, 시장 확장, live trading boundary가 충분히 표면화되기 전에는 실화면 구현을 시작하지 않는다.
- 화면 구현은 wireframe 또는 동등한 화면 정의 산출물과 사용자 승인 기준이 준비된 뒤에만 연다.
- UI는 generated/mapped contracts와 proto-socket client layer를 기준으로 API만 바라본다.
- 별도 TypeScript web app, 고급 charting 전체, mobile app store 배포 설정은 제외한다.

View file

@ -3,11 +3,11 @@
## 위치
- Roadmap: `agent-roadmap/ROADMAP.md`
- Phase: `agent-roadmap/phase/operator-surface/PHASE.md`
- Phase: `agent-roadmap/phase/operator-ui/PHASE.md`
## 목표
승인된 `Flutter Operator Console UX Plan``Operator Headless Workflow Validation` 결과를 기준으로 Flutter client에 operator dashboard MVP를 구현한다. 화면은 headless 검증으로 확인된 운영 흐름과 준비된 wireframe을 따라가며, terminal UI나 고급 charting이 아니라 반복 운영에 필요한 조회/실행 표면을 우선한다.
승인된 `Flutter Operator Console UX Plan`, command/headless workflow, 거래 확장 결과를 기준으로 Flutter client에 operator dashboard MVP를 구현한다. 화면은 충분히 표면화된 운영 흐름과 준비된 wireframe을 따라가며, terminal UI나 고급 charting이 아니라 반복 운영에 필요한 조회/실행 표면을 우선한다.
## 상태
@ -19,7 +19,8 @@
- 결정 필요: 아래 체크리스트
- [x] `Operator Headless Workflow Validation`이 완료되어 화면 없이 대표 운영 흐름을 검증했다.
- [x] `Operator Client API/Core Integration Validation`이 완료되어 Flutter 실화면 없이 client core에서 기본 API 호출을 검증했다.
- [ ] `KIS Live Data Collection Pipeline`이 완료되어 실제 KIS provider 수집과 worker import/storage/`StorageBarSource` 입력 조회 경계가 검증되었다.
- [x] `KIS Live Data Collection Pipeline`이 완료되어 실제 KIS provider 수집과 worker import/storage/`StorageBarSource` 입력 조회 경계가 검증되었다.
- [ ] command-first workflow에서 반복 운영할 기능, 상태 key, 우선순위가 충분히 표면화되어 화면 후보로 정리되었다.
- [ ] 화면 wireframe 또는 동등한 화면 정의 산출물이 준비되었고 사용자가 승인했다.
## 범위
@ -69,6 +70,8 @@ headless 검증과 wireframe 승인을 거친 운영 흐름만 실제 Flutter
- 관련 경로: `apps/client/`, `packages/contracts/`, `services/api/`
- 표준선(선택): `go_router`는 화면 이동/URL 구조, Riverpod은 API client/socket state/feature state 주입을 담당한다. UI는 `AltSocketClient` provider와 generated/mapped contracts boundary를 통해 API만 바라본다.
- 선행 작업: Flutter Operator Console UX Plan, Operator Headless Workflow Validation, Operator Client API/Core Integration Validation, KIS Live Data Collection Pipeline, wireframe 승인
- 표준선(선택): 화면 작업은 command-first workflow에서 운영 항목, 상태 key, 우선순위가 충분히 표면화되어 정리 가능한 뒤 다시 연다.
- 표준선(선택): paper trading 화면은 `Paper Trading Command Workflow`에서 반복 운영 항목과 evidence key가 정리된 뒤에만 MVP 범위 후보로 올린다.
- 선행 작업: Flutter Operator Console UX Plan, Operator Headless Workflow Validation, Operator Client API/Core Integration Validation, KIS Live Data Collection Pipeline, Command-First Operator Workflow, Paper Trading Readiness, Paper Trading Command Workflow, US Market Expansion, Live Trading Boundary, wireframe 승인
- 후속 작업: Flutter Push Notification Boundary
- 확인 필요: headless 검증과 client API/core 연결 검증은 완료되었고, KIS live data collection pipeline과 wireframe 승인 전에는 구현 잠금을 유지한다.
- 확인 필요: headless 검증, client API/core 연결 검증, KIS live data collection pipeline은 완료되었고, command-first workflow에서 화면 후보가 충분히 정리되고 wireframe이 승인되기 전에는 구현 잠금을 유지한다.

View file

@ -3,7 +3,7 @@
## 위치
- Roadmap: `agent-roadmap/ROADMAP.md`
- Phase: `agent-roadmap/phase/operator-surface/PHASE.md`
- Phase: `agent-roadmap/phase/operator-ui/PHASE.md`
## 목표

View file

@ -16,6 +16,9 @@
- [계획] Paper Trading Readiness
- 경로: `agent-roadmap/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 account/order/fill/position/risk 흐름을 Flutter 화면 없이 CLI/YAML/log 중심의 반복 가능한 command workflow로 검증한다.
- [계획] US Market Expansion
- 경로: `agent-roadmap/phase/trading-expansion/milestones/us-market-expansion.md`
- 요약: 한국장 MVP 구조 위에 미국장 데이터를 추가한다.

View file

@ -66,8 +66,8 @@
## 작업 컨텍스트
- 관련 경로: `services/worker/`, `services/api/`, `apps/client/`, `packages/domain/`
- 표준선(선택): 실거래는 MVP 이후, paper trading 안정화 이후에만 연다.
- 선행 작업: Paper Trading Readiness
- 후속 작업: 없음
- 관련 경로: `services/worker/`, `services/api/`, `apps/cli/`, `packages/domain/`
- 표준선(선택): 실거래는 paper trading 안정화 이후에만 경계를 열며, UI는 live boundary 후보까지 command/headless로 정리된 뒤 후순위 Phase에서 다룬다.
- 선행 작업: Paper Trading Readiness, Paper Trading Command Workflow, US Market Expansion
- 후속 작업: Flutter Operator Console MVP
- 확인 필요: paper 안정화 기준, live provider, 주문 유형, 리스크 한도

View file

@ -0,0 +1,69 @@
# Milestone: Paper Trading Command Workflow
## 위치
- Roadmap: `agent-roadmap/ROADMAP.md`
- Phase: `agent-roadmap/phase/trading-expansion/PHASE.md`
## 목표
`Paper Trading Readiness`에서 정의한 paper account, virtual order/fill, position, risk guard를 Flutter 화면 없이 CLI/YAML/log 중심의 반복 가능한 command workflow로 검증한다. paper trading 기능이 충분히 표면화되고 운영자가 어떤 상태와 액션을 반복해서 보는지 정리된 뒤에만 UI 후보로 넘긴다.
## 상태
[계획]
## 구현 잠금
- 상태: 잠금
- 결정 필요: 아래 체크리스트
- [ ] `Paper Trading Readiness`가 완료되어 paper account/order/fill/position/risk 경계가 준비되었다.
- [ ] command workflow에서 검증할 첫 paper trading scenario 범위를 정한다.
## 범위
- paper account/portfolio 상태 조회 command
- virtual order submit/cancel/fill simulation command 또는 YAML scenario
- position/risk guard 상태 조회 command
- paper execution loop smoke
- paper trading command output의 machine-readable status/evidence key 정리
- UI MVP로 넘길 paper trading 화면 후보와 보류 항목 분리
## 기능
### Epic: [paper-cmd] Paper trading command workflow
paper trading을 화면 없이 반복 검증하고, 나중에 UI로 올릴 운영 항목을 command evidence로 먼저 정리한다.
- [ ] [paper-status-command] paper account, portfolio, position 상태를 command로 조회할 수 있다. 검증: status command 또는 scenario가 account/position/risk key를 출력한다.
- [ ] [paper-order-command] virtual order submit/cancel/fill simulation을 command로 실행할 수 있다. 검증: order lifecycle scenario가 order id, status transition, fill summary를 출력한다.
- [ ] [paper-risk-command] 최소 risk guard 결과를 command workflow에서 확인할 수 있다. 검증: 허용/차단 case가 typed status/error key로 구분된다.
- [ ] [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 사유를 기록한다.
## 완료 리뷰
- 상태: 없음
- 요청일: 없음
- 완료 근거: 아직 기능 Task와 검증이 충족되지 않았다.
- 리뷰 필요:
- [ ] 사용자가 완료 결과를 확인했다
- [ ] archive 이동을 승인했다
- 리뷰 코멘트: 없음
## 범위 제외
- Flutter 실화면 구현
- 실거래 주문 제출
- broker 계좌 잔고 실시간 동기화
- 고급 리스크 엔진
- production 자동 execution
## 작업 컨텍스트
- 관련 경로: `apps/cli/`, `apps/cli/testdata/operator/`, `services/worker/`, `services/api/`, `packages/domain/`
- 표준선(선택): paper trading은 실거래 전 검증 경계이며, 먼저 command workflow로 반복 운영성과 상태 key를 확인한다.
- 표준선(선택): UI는 command workflow에서 반복되는 status/action/evidence key가 정리된 뒤에만 후보로 올린다.
- 선행 작업: Paper Trading Readiness
- 후속 작업: US Market Expansion, Live Trading Boundary, Flutter Operator Console MVP
- 확인 필요: Paper Trading Readiness 완료 후 첫 command scenario 범위

View file

@ -26,7 +26,7 @@
- virtual order and fill model
- strategy execution scheduling
- risk guard skeleton
- API/client monitoring surface
- API/command monitoring surface
## 필수 기능
@ -38,7 +38,7 @@
- [ ] [virtual-orders] 가상 주문과 체결 모델이 있다.
- [ ] [risk-guard] 최소 risk guard skeleton이 있다.
- [ ] [strategy-run] 전략을 paper execution loop에서 실행할 수 있다.
- [ ] [paper-monitor] client에서 paper state를 모니터링할 수 있다.
- [ ] [paper-monitor] API/command workflow에서 paper state를 모니터링할 수 있다.
## 완료 기준
@ -65,8 +65,8 @@
## 작업 컨텍스트
- 관련 경로: `services/worker/`, `services/api/`, `apps/client/`, `packages/domain/`
- 표준선(선택): 실거래는 MVP 이후이며 paper trading 안정화 뒤에 연다.
- 선행 작업: Flutter Operator Console
- 후속 작업: Live Trading Boundary
- 관련 경로: `services/worker/`, `services/api/`, `apps/cli/`, `packages/domain/`
- 표준선(선택): 실거래는 paper trading 안정화 뒤에 연며, UI는 paper command workflow로 반복 운영 항목과 상태 key가 정리된 뒤에만 연다.
- 선행 작업: Command-First Operator Workflow
- 후속 작업: Paper Trading Command Workflow, US Market Expansion, Live Trading Boundary, Flutter Operator Console MVP
- 확인 필요: paper order/fill 정밀도와 데이터 시간 단위

View file

@ -66,6 +66,6 @@
- 관련 경로: `services/worker/`, `packages/domain/market/`, `packages/contracts/proto/alt/v1/market.proto`
- 표준선(선택): 시장 추가는 provider adapter와 market metadata 확장으로 처리하고 백테스트 코어를 복제하지 않는다.
- 선행 작업: Korea Daily Data Foundation, Backtest Engine Baseline
- 후속 작업: Live Trading Boundary
- 선행 작업: Korea Daily Data Foundation, Backtest Engine Baseline, Command-First Operator Workflow, Paper Trading Command Workflow
- 후속 작업: Live Trading Boundary, Flutter Operator Console MVP
- 확인 필요: 미국장 universe와 provider 우선순위

View file

@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Run a minimal KIS paper daily-price smoke without printing secrets."""
"""Run a minimal KIS daily-price smoke without printing secrets."""
from __future__ import annotations
@ -12,7 +12,22 @@ import urllib.parse
import urllib.request
PAPER_BASE_URL = "https://openapivts.koreainvestment.com:29443"
ENV_CONFIG = {
"paper": {
"base_url": "https://openapivts.koreainvestment.com:29443",
"app_key": "KIS_PAPER_APP_KEY",
"app_secret": "KIS_PAPER_APP_SECRET",
"cano": "KIS_PAPER_CANO",
"product": "KIS_PAPER_ACNT_PRDT_CD",
},
"real": {
"base_url": "https://openapi.koreainvestment.com:9443",
"app_key": "KIS_REAL_APP_KEY",
"app_secret": "KIS_REAL_APP_SECRET",
"cano": "KIS_REAL_CANO",
"product": "KIS_REAL_ACNT_PRDT_CD",
},
}
TOKEN_PATH = "/oauth2/tokenP"
DAILY_PATH = "/uapi/domestic-stock/v1/quotations/inquire-daily-itemchartprice"
DAILY_TR_ID = "FHKST03010100"
@ -77,7 +92,7 @@ def request_json(
def parse_args(argv: list[str]) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Run a KIS paper daily item chart price smoke.",
description="Run a KIS daily item chart price smoke.",
)
parser.add_argument("--symbol", default=os.environ.get("KIS_SMOKE_SYMBOL", "005930"))
parser.add_argument("--start", default=os.environ.get("KIS_SMOKE_START", "20240527"))
@ -101,15 +116,18 @@ def main(argv: list[str]) -> int:
raise SmokeError("--symbol must be a numeric KRX symbol")
active_env = os.environ.get("KIS_ACTIVE_ENV", "")
if active_env != "paper":
raise SmokeError('KIS_ACTIVE_ENV must be "paper" for this smoke')
env_config = ENV_CONFIG.get(active_env)
if env_config is None:
raise SmokeError('KIS_ACTIVE_ENV must be "paper" or "real" for this smoke')
app_key = required_env("KIS_PAPER_APP_KEY")
app_secret = required_env("KIS_PAPER_APP_SECRET")
required_env("KIS_PAPER_CANO")
product = required_env("KIS_PAPER_ACNT_PRDT_CD")
app_key = required_env(env_config["app_key"])
app_secret = required_env(env_config["app_secret"])
account_no = required_env(env_config["cano"])
product = required_env(env_config["product"])
if product != "01":
raise SmokeError("KIS_PAPER_ACNT_PRDT_CD must be 01 for domestic stock paper smoke")
raise SmokeError(f"{env_config['product']} must be 01 for domestic stock smoke")
base_url = env_config["base_url"]
static_secrets = [app_key, app_secret, account_no]
base_headers = {
"Content-Type": "application/json; charset=utf-8",
@ -118,7 +136,7 @@ def main(argv: list[str]) -> int:
}
token_payload = request_json(
"POST",
PAPER_BASE_URL + TOKEN_PATH,
base_url + TOKEN_PATH,
headers=base_headers,
body={
"grant_type": "client_credentials",
@ -126,18 +144,18 @@ def main(argv: list[str]) -> int:
"appsecret": app_secret,
},
timeout=args.timeout,
secrets=[app_key, app_secret],
secrets=static_secrets,
)
access_token = token_payload.get("access_token", "")
if access_token == "":
raise SmokeError(
"token response missing access_token: "
+ redact(json.dumps(token_payload, ensure_ascii=False), [app_key, app_secret])
+ redact(json.dumps(token_payload, ensure_ascii=False), static_secrets)
)
query_payload = request_json(
"GET",
PAPER_BASE_URL + DAILY_PATH,
base_url + DAILY_PATH,
headers={
**base_headers,
"authorization": f"Bearer {access_token}",
@ -156,13 +174,13 @@ def main(argv: list[str]) -> int:
"FID_ORG_ADJ_PRC": "0",
},
timeout=args.timeout,
secrets=[app_key, app_secret, access_token],
secrets=static_secrets + [access_token],
)
if query_payload.get("rt_cd") != "0":
raise SmokeError(
"KIS daily query failed: "
+ redact(json.dumps(query_payload, ensure_ascii=False), [app_key, app_secret, access_token])
+ redact(json.dumps(query_payload, ensure_ascii=False), static_secrets + [access_token])
)
output1 = query_payload.get("output1") or {}
@ -178,7 +196,10 @@ def main(argv: list[str]) -> int:
if not dates:
raise SmokeError("KIS daily rows did not include stck_bsop_date")
print("KIS paper daily smoke ok")
print(f"KIS {active_env} daily smoke ok")
print(f"env={active_env}")
print("provider=kis")
print("endpoint=inquire-daily-itemchartprice")
print(f"symbol={args.symbol}")
print(f"name={output1.get('hts_kor_isnm', '')}")
print(f"bar_count={len(output2)}")
@ -193,5 +214,5 @@ if __name__ == "__main__":
try:
raise SystemExit(main(sys.argv[1:]))
except SmokeError as err:
print(f"KIS paper daily smoke failed: {err}", file=sys.stderr)
print(f"KIS daily smoke failed: {err}", file=sys.stderr)
raise SystemExit(1)

View file

@ -56,7 +56,7 @@ echo "KIS SOPS secrets are ready"
'
```
## 5. 모의테스트 실행 준비 확인
## 5. 모의/실전 테스트 실행 준비 확인
모의테스트 command는 `KIS_ACTIVE_ENV=paper`를 일반 환경변수로 주고, secret은 SOPS에서 주입한다.
@ -73,7 +73,24 @@ echo "paper smoke env is ready"
'
```
첫 모의 smoke는 삼성전자 `005930` 일봉 조회로 실행한다. 토큰과 secret은 출력하지 않고 결과 요약만 출력한다.
실전 테스트 command는 `KIS_ACTIVE_ENV=real`을 일반 환경변수로 주고, secret은 SOPS에서 주입한다.
```bash
KIS_ACTIVE_ENV=real \
SOPS_AGE_KEY_FILE="$HOME/.config/sops/age/keys.txt" \
bin/kis-sops-env bash -lc '
test "$KIS_ACTIVE_ENV" = "real"
test -n "$KIS_REAL_APP_KEY"
test -n "$KIS_REAL_APP_SECRET"
test -n "$KIS_REAL_CANO"
test -n "$KIS_REAL_ACNT_PRDT_CD"
echo "real smoke env is ready"
'
```
첫 smoke는 삼성전자 `005930` 일봉 조회로 실행한다. 토큰과 secret은 출력하지 않고 결과 요약만 출력한다.
모의:
```bash
KIS_ACTIVE_ENV=paper \
@ -84,7 +101,20 @@ bin/kis-sops-env bin/kis-paper-daily-smoke \
--end 20240528
```
실전:
```bash
KIS_ACTIVE_ENV=real \
SOPS_AGE_KEY_FILE="$HOME/.config/sops/age/keys.txt" \
bin/kis-sops-env bin/kis-paper-daily-smoke \
--symbol 005930 \
--start 20240527 \
--end 20240528
```
## 6. 확인 체크리스트
- [ ] SOPS에는 실전/모의 8개 값만 있다.
- [ ] secret 값이 문서, 로그, 테스트 fixture에 출력되지 않았다.
- [ ] 모의 일봉 smoke가 통과했다.
- [ ] 실전 일봉 smoke가 통과했다.

View file

@ -54,7 +54,9 @@ echo "KIS SOPS secrets are ready"
'
```
## 모의테스트 준비 확인 명령
## 모의/실전 테스트 준비 확인 명령
모의:
```bash
KIS_ACTIVE_ENV=paper \
@ -69,7 +71,24 @@ echo "paper smoke env is ready"
'
```
## 모의 일봉 smoke 명령
실전:
```bash
KIS_ACTIVE_ENV=real \
SOPS_AGE_KEY_FILE="$HOME/.config/sops/age/keys.txt" \
bin/kis-sops-env bash -lc '
test "$KIS_ACTIVE_ENV" = "real"
test -n "$KIS_REAL_APP_KEY"
test -n "$KIS_REAL_APP_SECRET"
test -n "$KIS_REAL_CANO"
test -n "$KIS_REAL_ACNT_PRDT_CD"
echo "real smoke env is ready"
'
```
## 일봉 smoke 명령
모의:
```bash
KIS_ACTIVE_ENV=paper \
@ -80,8 +99,21 @@ bin/kis-sops-env bin/kis-paper-daily-smoke \
--end 20240528
```
실전:
```bash
KIS_ACTIVE_ENV=real \
SOPS_AGE_KEY_FILE="$HOME/.config/sops/age/keys.txt" \
bin/kis-sops-env bin/kis-paper-daily-smoke \
--symbol 005930 \
--start 20240527 \
--end 20240528
```
## Go live-provider smoke 명령
모의:
```bash
KIS_ACTIVE_ENV=paper \
KIS_LIVE_SMOKE=1 \
@ -92,6 +124,18 @@ bin/kis-sops-env /opt/homebrew/bin/go test ./services/worker/internal/providers/
-v
```
실전:
```bash
KIS_ACTIVE_ENV=real \
KIS_REAL_LIVE_SMOKE=1 \
SOPS_AGE_KEY_FILE="$HOME/.config/sops/age/keys.txt" \
bin/kis-sops-env /opt/homebrew/bin/go test ./services/worker/internal/providers/kis \
-run TestLiveProviderRealSmoke \
-count=1 \
-v
```
## Sanitized evidence 기준
이 기준은 데이터 적재 정책이 아니라 테스트 evidence 기준이다. Raw KIS payload, request/response header, token, key, 계좌번호는 저장하지 않는다.
@ -99,7 +143,7 @@ bin/kis-sops-env /opt/homebrew/bin/go test ./services/worker/internal/providers/
Tracked 문서나 handoff에는 아래 요약만 남긴다.
```text
env=paper
env=paper|real
provider=kis
endpoint=inquire-daily-itemchartprice
symbol=005930
@ -128,8 +172,10 @@ SOPS 입력과 확인 명령이 끝난 뒤 agent에게는 아래만 전달한다
```text
SOPS KIS real/paper runtime secrets are filled.
Paper smoke env readiness check passed.
Real smoke env readiness check passed.
Go KIS paper live-provider smoke passed.
First paper smoke target:
Go KIS real live-provider smoke passed.
First daily smoke target:
- market: KR
- symbol: 005930
- date range: 20240527~20240528

View file

@ -1,6 +1,6 @@
// Package kis decodes and normalizes Korea Investment & Securities (KIS) daily
// chart provider payloads into ALT domain market types. It works against mock
// fixtures only; live HTTP and credential wiring belong to a later milestone.
// chart provider payloads into ALT domain market types, with live REST client
// wiring kept behind explicit runtime configuration.
package kis
import (

View file

@ -412,10 +412,21 @@ func TestLiveProviderPaperSmoke(t *testing.T) {
if os.Getenv("KIS_LIVE_SMOKE") != "1" {
t.Skip("set KIS_LIVE_SMOKE=1 with SOPS-injected KIS paper env to run live smoke")
}
if os.Getenv("KIS_ACTIVE_ENV") != "paper" {
t.Fatal("KIS_ACTIVE_ENV must be paper for this live smoke")
}
runLiveProviderSmoke(t, EnvironmentPaper)
}
func TestLiveProviderRealSmoke(t *testing.T) {
if os.Getenv("KIS_REAL_LIVE_SMOKE") != "1" {
t.Skip("set KIS_REAL_LIVE_SMOKE=1 with SOPS-injected KIS real env to run live smoke")
}
runLiveProviderSmoke(t, EnvironmentReal)
}
func runLiveProviderSmoke(t *testing.T, env Environment) {
t.Helper()
if os.Getenv("KIS_ACTIVE_ENV") != string(env) {
t.Fatalf("KIS_ACTIVE_ENV must be %s for this live smoke", env)
}
provider := NewLiveProvider(NewClient(ConfigFromEnv()))
seoul := time.FixedZone("Asia/Seoul", 9*60*60)
items, err := provider.FetchDailyBars(context.Background(), importer.DailyBarRequest{
@ -429,7 +440,7 @@ func TestLiveProviderPaperSmoke(t *testing.T) {
To: time.Date(2024, 5, 28, 0, 0, 0, 0, seoul),
})
if err != nil {
t.Fatalf("paper live provider smoke: %v", err)
t.Fatalf("%s live provider smoke: %v", env, err)
}
if len(items) != 1 {
t.Fatalf("items: got %d, want 1", len(items))