feat: own current KIS daily fill contract in Core

This commit is contained in:
jordan
2026-07-29 21:46:42 +09:00
parent b47d7418e8
commit 56b35b257b
12 changed files with 365 additions and 54 deletions
@@ -0,0 +1,33 @@
# BAT-063 Design
- Status: confirmed
- Batch: BAT-063
## Architecture Summary
- KIS 일별 체결조회 거래 ID와 공통 파라미터는 `KISBroker`의 private helper가
한 번만 생성한다.
- 실전은 `TTTC0081R`, 모의는 `VTTC0081R`을 사용한다.
- 일반 주문번호 조회와 결과불명 주문 탐색 모두
`EXCG_ID_DVSN_CD=ALL`을 전달한다.
- 체결·부분체결·취소·거부·결과불명 해석과 폴링 간격은 그대로 유지한다.
## Changed Areas
- Module: `trex_core/adapters/broker/kis.py`
- Tests: `tests/adapters/broker/test_kis.py`,
`tests/adapters/broker/test_venue.py`
- Docs: `docs/interface-contract.md`, `docs/runtime-flows.md`
## Key Decisions
1. sidecar가 거래 ID를 바꾸지 않아도 Core 단독으로 KRX·NXT 체결을 조회한다.
2. 거래소별 분기 대신 `ALL` 하나로 주문번호와 결과불명 후보를 찾는다.
3. 공개 `BrokerPort.poll_fill` 시그니처는 변경하지 않는다.
4. 구형 거래 ID fallback을 두지 않는다. 공식 명세가 구형 거래 ID 중단 가능성을
명시하기 때문이다.
## Architecture Impact
- KIS broker transport
- 주문 상태 해석과 도메인·애플리케이션 경계에는 영향 없음
@@ -0,0 +1,39 @@
# BAT-063 Implementation
- Status: completed
- Batch: BAT-063
## Plan Summary
- KIS 일별 체결조회 요청 계약을 Core의 단일 private helper로 모으고 일반 주문과
결과불명 주문 조회가 동일한 신형 거래 ID와 전체 거래소 조건을 사용하게 한다.
## Changed Files
- `trex_core/adapters/broker/kis.py`
- `tests/adapters/broker/test_kis.py`
- `docs/interface-contract.md`
- `docs/runtime-flows.md`
- `docs/srs/Integration/req-118_kis-broker-venue-parameterization.md`
- `docs/batches/bat-063_20260729_kis-daily-fill-all/*`
## Execution Log
1. 날짜·종목·주문번호를 받는 `_daily_fill_request`를 추가했다.
2. 실전 `TTTC0081R`, 모의 `VTTC0081R`, `EXCG_ID_DVSN_CD=ALL`을 한 곳에
고정했다.
3. 일반 `poll_fill`과 결과불명 주문 복구가 같은 helper를 사용하도록 바꿨다.
4. 기존 응답 파싱, 상태 판정, 공개 포트와 재시도 주기는 변경하지 않았다.
5. 일반·결과불명·실전·모의 요청 계약을 단위검사로 고정했다.
## Validation
- KIS broker 집중검사: 59 passed, 1 skipped.
- Core 전체 회귀: 922 passed, 51 skipped.
- 변경 파일 Ruff: PASS.
- 공용 가상 인증은 커밋 후 깨끗한 작업트리에서 1~7단계로 실행한다.
## Remaining Risks
- 가상 인증은 실전 KIS 응답이나 주문 가능 상태를 증명하지 않는다.
- 이 배치는 실주문, 계정 설정, 주문 게이트, 배포를 변경하지 않는다.
@@ -0,0 +1,29 @@
# BAT-063: KIS 전체 거래소 일별 체결조회
- Status: in-progress
- Profile: standard
- Source Discovery: REQ-118 CHG-20260729-01
- Source REQ: REQ-118
- Included REQ: REQ-118 CHG-20260729-01
- Excluded REQ: 없음
## Scope
### In Scope
- 일반 주문과 결과불명 주문의 일별 체결조회 거래 ID를 KIS 신형 계약으로 전환
- `EXCG_ID_DVSN_CD=ALL`을 통한 KRX·NXT 통합 체결조회
- 요청 계약 단위검사, 전체 회귀, 가상거래소 주문 수명주기 인증
- 인터페이스·런타임 문서 동기화
### Out of Scope
- 주문 제출·취소·수량·가격·전략 신호 변경
- 실주문 생성 또는 계좌·주문 게이트 변경
- KIS sidecar 배포와 제거
## Notes
- KIS 공식 예제와 저장소 내 KIS REST 명세가 신형 거래 ID와 `ALL`을 동일하게
요구한다.
- Core 인증 통과 뒤 docker-local sidecar 축소를 별도 저장소에서 수행한다.
@@ -0,0 +1,34 @@
# BAT-063 Planning
- Status: confirmed
- Batch: BAT-063
- Profile: standard
## Included REQ
- REQ-118 CHG-20260729-01 (Approved): KIS 전체 거래소 일별 체결조회
## Delivery Plan
1. KIS 공식 거래 ID·거래소 파라미터 계약을 테스트로 고정한다.
2. 일반 주문과 결과불명 주문 조회에 하나의 요청 생성 함수를 적용한다.
3. 기존 체결·취소·거부·결과불명 상태 해석을 변경하지 않는다.
4. 집중검사, 전체 회귀, 가상 주문 수명주기 1~7단계를 순서대로 검증한다.
5. 문서와 검증 증거를 동기화하고 Draft PR로 제출한다.
## Dependencies
- Internal: `KISBroker.poll_fill`, `_poll_unknown_submission`
- External: KIS `inquire-daily-ccld` 신형 요청 계약
## Design Gate
- Design Required Before Implementation: yes
- Reason: 실전 주문 체결조회 경로이므로 요청 생성 책임과 불변 동작을 먼저 고정한다.
## Risks
- 결과불명 주문 조회만 구형 계약에 남을 수 있다.
- 대응: 일반·결과불명 두 경로의 거래 ID와 `ALL`을 별도 회귀검사로 고정한다.
- 체결 상태 해석이 바뀔 수 있다.
- 대응: 요청 생성만 변경하고 응답 파싱 함수와 공개 포트 시그니처를 보존한다.
@@ -0,0 +1,37 @@
# BAT-063 Verification
- Status: pending
- Batch: BAT-063
- Verified By: delivery-runner
## Acceptance Mapping
| AC | 결과 | 근거 |
| --- | --- | --- |
| REQ-118 AC-4 일반 조회 | PASS | `test_poll_fill_uses_current_daily_fill_contract_for_all_venues` |
| REQ-118 AC-4 결과불명 조회 | PASS | `test_unknown_submission_resolves_from_recent_daily_orders` |
| 실전·모의 거래 ID | PASS | `test_daily_fill_request_uses_current_live_and_mock_contract` |
| 기존 주문·취소·상태 해석 | PASS | KIS broker 집중검사와 Core 전체 회귀 |
| 가상 주문 수명주기 | PENDING | 공용 인증 1~7단계 실행 전 |
## Evidence
- Test:
- KIS broker 집중검사: 59 passed, 1 skipped.
- Core 전체: 922 passed, 51 skipped.
- 변경 파일 Ruff: PASS.
- Certification:
- 공용 인증 산출물 경로는 실행 후 기록한다.
## Result
- Ready for Release: false
- Blocking Issues:
- 깨끗한 커밋 기준 공용 가상 인증 1~7단계가 아직 실행 전이다.
## Safety Classification
- `research_only=true`
- `official_comparable=false`
- `real_order_allowed=false`
- `order_adapter=virtual_locked_replay`
+1
View File
@@ -66,3 +66,4 @@
| BAT-058 | standard | release-candidate | REQ-123 | dcy-038 | [bat-058](./bat-058_20260716_cost-basis-safe-sync-seed/) |
| BAT-059 | standard | release-candidate | REQ-124 | dcy-039 | [bat-059](./bat-059_20260727_intent-venue-fanin/) |
| BAT-062 | standard | release-candidate | REQ-127 | dcy-042 | [bat-062](./bat-062_20260729_shared-account-capital-allocation/) |
| BAT-063 | standard | in-progress | REQ-118 (CHG-20260729-01) | REQ-118 change-req | [bat-063](./bat-063_20260729_kis-daily-fill-all/) |
+7 -2
View File
@@ -463,7 +463,9 @@ get_current_price(symbol: str, market: str = "J") -> Optional[Decimal]
- `submit_order`: body에 `EXCG_ID_DVSN_CD: req.venue` 항상 명시 전송 (이전: 미전송·서버 기본값 의존 → 명시화로 견고성 향상). 실측(2026-07-06): NXT(EXCG=NXT) 주문 rt_cd=0 확인.
- `AUTO` 세션: 08:00~08:49:59 NXT, 09:00:30~15:29:59 KRX, 15:30~20:00 NXT. 그 밖의 시간은 `auto_venue_session_closed`로 fail-closed 한다.
- `cancel_order`: body에 `EXCG_ID_DVSN_CD: venue` 명시 전송. 실측(2026-07-06): NXT 취소 rt_cd=0 확인.
- `poll_fill`: 구 TR(`TTTC8001R`/`VTTC8001R`) 유지. NXT 체결 포함 여부 미확인(OPEN ITEM — 운영 스모크 필요). 확인 후 미포함이면 `TTTC0081R`(EXCG=ALL) 전환 별도 batch 처리.
- `poll_fill`: KIS 신형 일별주문체결조회 실전 `TTTC0081R`, 모의
`VTTC0081R`을 사용하고 `EXCG_ID_DVSN_CD=ALL`을 전달한다. 일반 주문번호
조회와 결과불명 주문 탐색이 같은 계약으로 KRX·NXT 체결을 확인한다.
## `FillStatus` 확장 (BAT-006 / REQ-018)
@@ -635,7 +637,10 @@ trex-core가 `source="trex-core"`, `severity="critical"`로 발행하는 초기
- 2026-06-21 :: BAT-049 (dcy-033) — 알람 채널 `trex.alerts` (topic, durable) 신규 추가. 알람 envelope 표준 JSON 스키마(7필드: source/severity/code/title/fields/occurred_at/schema_version, schema_version="1"), routing key `alarm.{severity}.{source}`, 발행자(모든 모듈)/소비자(trex-notifier), mandatory=False best-effort 정책. trex-core CRITICAL code 10종 초기 배선. 기존 wire 계약 불변(가산·격리). (REQ-098~101)
- 2026-06-10 :: BAT-031 (dcy-021 포지션 사이징) — 매수 intent 가 **holding 슬롯 대상이면 증액**(qty+price 추가 매수; M 캡 미적용); 매도 intent 에 **`qty`(부분 매도, 선택)** 추가 — 지정 시 `min(qty, 브로커잔고)` 부분 매도(잔량 holding), 미지정 시 전량(→inactive). 상태머신 13 전이(holding→pending_buy). avg_price 증액 가중평균·부분 매도 cost basis 유지. (REQ-066/057/061/062)
- 2026-07-04 :: BAT-055 (dcy-001/002 v3 FR-8, REQ-116) — `trex.slots.req` additive 확장: 요청에 선택적 `strategy: str` 필터 추가(exact-match, `filter` AND 조합, `available_slots` 불변 — REQ-073 AC-6); 응답 슬롯 행에 `strategy_name`(null)/`strategy_params`({})/`markets`([]) 3필드 추가(PortfolioItem/hub 정본, safe default). 기존 소비자(orb·tsl) backward compatible(unknown 필드 무시). REQ-074 AC-5 additive 재도입(dcy-025 OQ-4 후속 확정). PortfolioItem에 `strategy_name`/`markets` 필드 추가 + hub_read.py 파싱 확장.
- 2026-07-06 :: BAT-056 (dcy-002 FR-6, REQ-117/118) — `OrderRequest.venue: str = "KRX"` additive 추가(기존 위치 인수 순서 불변 — AMQP wire 계약 영향 없음, 브로커 어댑터 계층 DTO까지만). KIS 어댑터: submit_order/cancel_order body에 `EXCG_ID_DVSN_CD` 항상 명시 전송(기존 미전송 → KRX 명시화 견고성 향상), cancel_order/get_current_price 시그니처 venue/market 파라미터 추가(기본값 KRX/"J" — 기존 호출자 무영향). poll_fill TR 유지(NXT 체결 포함 여부 미확인 open item). paper broker 시그니처 정합(venue/market 무시). 실측 근거: 2026-07-06 NXT 주문/취소 rt_cd=0 확인.
- 2026-07-29 :: BAT-063 (REQ-118 CHG-20260729-01) — `poll_fill`과
결과불명 주문 탐색을 KIS 신형 일별주문체결조회
(`TTTC0081R`/`VTTC0081R`, `EXCG_ID_DVSN_CD=ALL`)로 단일화했다.
- 2026-07-06 :: BAT-056 (dcy-002 FR-6, REQ-117/118) — `OrderRequest.venue: str = "KRX"` additive 추가(기존 위치 인수 순서 불변 — AMQP wire 계약 영향 없음, 브로커 어댑터 계층 DTO까지만). KIS 어댑터: submit_order/cancel_order body에 `EXCG_ID_DVSN_CD` 항상 명시 전송(기존 미전송 → KRX 명시화 견고성 향상), cancel_order/get_current_price 시그니처 venue/market 파라미터 추가(기본값 KRX/"J" — 기존 호출자 무영향). paper broker 시그니처 정합(venue/market 무시). 실측 근거: 2026-07-06 NXT 주문/취소 rt_cd=0 확인.
## REQ-123 Contract Addendum: cost-basis canonical parsing & sync diagnostics
+4 -1
View File
@@ -459,7 +459,10 @@ trex-notifier (단일 소비자) → 텔레그램 등 전달
| delivery_mode | 2 (persistent) | 1 (non-persistent) |
| 소비자 | 전략 모듈(strategy_id 필터) | trex-notifier 단독 |
| 발행 재시도 | 재연결 후 1회 retry | **없음** (best-effort) |
- 2026-07-06 :: BAT-056 (dcy-002 FR-6, REQ-117/118) — 브로커 집행 venue 전달 경로 명시: `OrderRequest.venue`(기본 KRX) → KIS 어댑터 `EXCG_ID_DVSN_CD` 항상 명시 전송(submit_order/cancel_order). `get_current_price` market 파라미터 추가(기본 J). poll_fill TR 유지(NXT 체결 open item). 기존 orb/tsl 경로 무영향.
- 2026-07-29 :: BAT-063 (REQ-118 CHG-20260729-01) — 일반 주문과
결과불명 주문의 `poll_fill`이 신형 일별주문체결조회와
`EXCG_ID_DVSN_CD=ALL`을 사용해 KRX·NXT 체결을 함께 확인한다.
- 2026-07-06 :: BAT-056 (dcy-002 FR-6, REQ-117/118) — 브로커 집행 venue 전달 경로 명시: `OrderRequest.venue`(기본 KRX) → KIS 어댑터 `EXCG_ID_DVSN_CD` 항상 명시 전송(submit_order/cancel_order). `get_current_price` market 파라미터 추가(기본 J). 기존 orb/tsl 경로 무영향.
## Flow: `operator sync` (REQ-105/REQ-123, dcy-034 / BAT-051)
@@ -1,6 +1,6 @@
# req-118: KIS 브로커 어댑터 venue 파라미터화 (EXCG_ID_DVSN_CD)
- Status: Implemented
- Status: Approved
- Type: Integration
- Priority: High
- Owner: trex-core-dev
@@ -9,7 +9,8 @@
- KIS 브로커 어댑터(`adapters/broker/kis.py`)에서 주문 거래소구분(`EXCG_ID_DVSN_CD`)을 `OrderRequest.venue`로부터 파라미터화하여 NXT(대체거래소) 주문·취소를 지원한다.
- 동시에 `submit_order`에서 기존 EXCG 미전송 관행을 `"KRX"` 명시 전송으로 변경하여 서버 기본값 의존을 제거한다(동작 고정, 실측상 서버 기본값=KRX로 동일하나 명시화가 더 견고함).
- `poll_fill`의 구 TR(`TTTC8001R`) NXT 체결 포함 여부를 확인하고, 미포함 시 신 TR(`TTTC0081R`, EXCG=ALL)으로 전환하여 NXT 체결 조회를 보장한다.
- `poll_fill`은 신 TR(`TTTC0081R`/`VTTC0081R`)과 `EXCG_ID_DVSN_CD=ALL`을
사용하여 KRX·NXT 체결 조회를 보장한다.
- `get_current_price`의 시장구분(`FID_COND_MRKT_DIV_CODE`) `"J"` 하드코딩을 파라미터로 전환한다(기본 `"J"`, `"UN"` 옵션).
- 이 모든 변경은 venue 미지정(기본 KRX) 경로에서 orb/tsl 기존 동작과 byte-동일하게 유지되는 additive 설계다.
@@ -17,7 +18,9 @@
1. `submit_order`: `body`에 `"EXCG_ID_DVSN_CD": req.venue` 항상 명시 전송한다. `req.venue` 기본값은 `"KRX"`이므로 venue 미지정 호출에서도 `EXCG_ID_DVSN_CD=KRX`가 전송된다.
2. `cancel_order`: 취소 요청 `body`에 `"EXCG_ID_DVSN_CD": <원주문 venue>` 를 전송한다. 원주문 venue는 cancel_order 시그니처에 `venue: str = "KRX"` 파라미터를 추가하거나, 호출자가 원주문 venue를 보존·전달하는 방식으로 구현한다(설계 방식은 구현 batch에 위임 — 의미론 확정: 취소는 원주문 venue와 동일한 EXCG를 사용해야 한다).
3. `poll_fill`: NXT 체결 조회를 보장한다. 구 TR `TTTC8001R`의 NXT 체결 포함 여부를 구현 시점에 확인(AC-4의 확인 방법 참조)하고, 미포함이 확인되면 신 TR `TTTC0081R`(EXCG 파라미터 `ALL` 또는 venue 지정)으로 전환한다. 확인 불가 시 Notes의 fallback 방침을 따른다.
3. `poll_fill`: 일반 주문과 결과불명 주문 조회 모두 실전 `TTTC0081R`, 모의
`VTTC0081R`을 사용하고 `EXCG_ID_DVSN_CD=ALL`을 전달하여 KRX·NXT 체결을
하나의 조회로 확인한다.
4. `get_current_price`: 시그니처에 `market: str = "J"` 파라미터를 추가하고, `FID_COND_MRKT_DIV_CODE`를 해당 값으로 전달한다. 기본 `"J"` 유지로 기존 safety-sell 경로 무영향. 호출자가 `"UN"` 지정 가능.
5. orb/tsl 무영향: venue 미지정 경로에서 submit_order 페이로드는 기존 대비 `EXCG_ID_DVSN_CD="KRX"` 필드 추가만 다르며, 주문 수락 동작이 동일함을 회귀 테스트로 확인한다.
@@ -26,7 +29,9 @@
1. Given `OrderRequest(..., venue="KRX")`(또는 venue 미지정 기본값)로 `submit_order` 호출 시, When KIS HTTP 요청이 생성되면, Then 요청 body에 `"EXCG_ID_DVSN_CD": "KRX"`가 포함된다.
2. Given `OrderRequest(..., venue="NXT")`로 `submit_order` 호출 시, When KIS HTTP 요청이 생성되면, Then 요청 body에 `"EXCG_ID_DVSN_CD": "NXT"`가 포함된다.
3. Given NXT 주문의 broker_order_id와 `venue="NXT"`로 `cancel_order` 호출 시, When KIS HTTP 요청이 생성되면, Then 요청 body에 `"EXCG_ID_DVSN_CD": "NXT"`가 포함된다.
4. Given `poll_fill` 구현 시, When 구 TR(`TTTC8001R`)의 NXT 체결 포함 여부를 확인하는 과정(KIS 개발자 센터 문서 조회 또는 NXT 체결 건 직접 조회 실측)을 수행하면, Then 그 결과를 구현 문서(batch design 또는 코드 주석)에 명시하고, 미포함이 확인된 경우 신 TR `TTTC0081R`으로 전환한다.
4. Given 일반 주문번호 또는 결과불명 주문 메타데이터를 조회할 때, When
`inquire-daily-ccld` 요청을 생성하면, Then 실전은 `TTTC0081R`, 모의는
`VTTC0081R`을 사용하고 `EXCG_ID_DVSN_CD=ALL`을 전달한다.
5. Given `get_current_price(symbol, market="J")` 기본 호출(또는 market 미지정) 시, When KIS HTTP 요청이 생성되면, Then `FID_COND_MRKT_DIV_CODE=J`가 전달된다(기존 동작 동일).
6. Given `get_current_price(symbol, market="UN")` 호출 시, When KIS HTTP 요청이 생성되면, Then `FID_COND_MRKT_DIV_CODE=UN`이 전달된다.
7. Given 기존 orb/tsl가 venue 미지정으로 `submit_order`를 호출하는 경로, When 단위 테스트를 실행하면, Then 기존 submit_order 관련 테스트 전체가 통과한다(회귀 없음). 페이로드 차이는 `EXCG_ID_DVSN_CD="KRX"` 추가뿐임을 fixture 검사로 확인한다.
@@ -49,8 +54,13 @@
- **고위험 경로**: `submit_order`·`cancel_order`·`poll_fill`은 라이브 주문 직결 경로다. worktree+PR+QC(opus) 플로 필수.
- **cancel_order 설계 위임**: 취소 시 venue 전달 방식(cancel_order 시그니처 추가 vs. execution context 보존)은 구현 batch design에서 확정한다. 본 REQ는 의미론만 확정한다 — "취소 요청은 원주문 venue와 동일한 EXCG_ID_DVSN_CD를 사용해야 한다."
- **poll_fill TR 전환 판단**: 구 TR(`TTTC8001R`) NXT 체결 포함 여부가 구현 시점에 확인 불가능하면, 신 TR(`TTTC0081R`)으로 즉시 전환하는 것을 기본 방침으로 한다(보수적 접근). 신 TR로 전환 시 모의(VTS) 지원 여부가 미확인이므로, mock 모드에서의 fallback(구 TR 유지)을 설계에 포함한다.
- **모의(VTS) NXT 미확인**: VTS(모의투자)에서 NXT EXCG_ID_DVSN_CD 동작이 확인되지 않았다. 검증은 단위 테스트(HTTP 목킹) + 실전 소액 스모크(운영 협의)로 진행한다. 모의 환경 검증은 선행 조건이 아니며, 배포 전 운영팀과 스모크 계획을 협의한다.
- **poll_fill TR 계약**: KIS 공식 `주식일별주문체결조회[v1_국내주식-005]`
기준 3개월 이내 실전은 `TTTC0081R`, 모의는 `VTTC0081R`이며 거래소
구분 `ALL`은 전체 거래소 조회다. 구형 `TTTC8001R`/`VTTC8001R`은
사전고지 없이 중단될 수 있으므로 사용하지 않는다.
- **모의(VTS) 범위**: 모의투자는 KRX만 제공되지만 동일 신형
`VTTC0081R` 요청 형식과 `ALL` 파라미터를 유지해 실전·가상 계약을
분기시키지 않는다.
- **토큰 캐시**: 실측(2026-07-06)에서 토큰 재요청 1분 rate-limit 확인. 현행 `kis.py` 토큰 캐시 방식과 정합 — 신 TR 전환 시에도 토큰 캐시 재사용 경로를 유지해야 한다.
- **SOR**: `"SOR"` 값 전달은 설계 경로에서 차단하지 않되, 현재 권고하지 않는다(체결거래소 식별·정정취소 EXCG 일치 보장이 미확인).
- 출처 Discovery: trex-tvr-v1 `dcy-002_20260704_execution-cadence-slot-fetch.md` FR-6, confirmed 2026-07-06.
@@ -59,6 +69,51 @@
## Change Log
### CHG-20260729-01
- Date: 2026-07-29
- Author: Codex — 형님 승인 `남은 작업 진행하자`
#### Change Summary
- KIS 공식 신형 일별주문체결조회 계약을 Core가 직접 소유하도록
`TTTC0081R`/`VTTC0081R`과 `EXCG_ID_DVSN_CD=ALL`을 `poll_fill` 및
결과불명 주문 조회에 적용한다.
#### Intent
- NXT 체결 조회를 Docker sidecar의 거래 ID 변환에 의존하지 않고 Core KIS
transport에서 완결해 주문·체결·취소 거래시장 정본을 Core 하나로 단일화한다.
#### Acceptance Criteria Delta
- Updated: AC-4를 공식 명세 확인 단계에서 신형 거래 ID와 전체 거래소 조회를
직접 검증하는 실행 기준으로 변경한다.
- Added: 일반 주문과 결과불명 주문 조회가 같은 신형 계약을 사용하는 회귀검사.
- Removed: 구형 거래 ID 유지와 sidecar 변환 의존.
#### Impacted Area Delta
- Modules: `trex_core/adapters/broker/kis.py`
- Tests: `tests/adapters/broker/test_kis.py`,
`tests/adapters/broker/test_venue.py`
- Docs: `docs/interface-contract.md`, `docs/runtime-flows.md`,
`docs/batches/bat-063_20260729_kis-daily-fill-all/`
#### Delivery Trace Delta
- Batches: BAT-063 신규
- Releases: 미할당
#### Revalidation Impact
- Reverification Needed: yes
- Existing Implementation Invalidated: yes — 구형 거래 ID와 NXT 포함 여부 미확인
상태는 최신 KIS 공식 계약 및 sidecar 제거 목표와 맞지 않는다.
- Status Recommendation: Implemented → Approved, BAT-063 검증 후 Implemented 복귀
#### Validation Plan
- 일반·결과불명 `poll_fill` 요청이 실전 `TTTC0081R`, 모의
`VTTC0081R`, `EXCG_ID_DVSN_CD=ALL`을 사용하는 단위검사를 통과한다.
- 기존 KRX/NXT 주문·취소·결과불명·재시작 회귀와 Core 전체 테스트를 통과한다.
- 가상거래소 `logic`부터 `ledger_reconcile`까지 1~7단계 인증을 통과한다.
- FROZEN 영향: 저장소 내 FROZEN 목록이 없으며 공개 포트 시그니처는 변경하지 않는다.
- BT 영향: 전략 신호·가격·수량·자본 정책은 변경하지 않으므로 수익률 BT 대상이
아니다. 주문 수명주기 가상 인증으로 회귀를 검증한다.
### CHG-20260707-01
- Date: 2026-07-07
- Author: 팀장(Claude) — confirm-req-implemented
+5 -1
View File
@@ -198,7 +198,7 @@
| REQ-115 | 그날-시작 장중 무중단 보장 — 그날 고정 인증정보 캐시 + portfolio-hub 장애 비중단 | Non-Functional | Approved | High | trex-core-dev | [req-115](./Non-Functional/req-115_daystart-intraday-resilience-credential-cache.md) |
| REQ-116 | `trex.slots.req` 전략 필드 노출 + `strategy` 요청 필터 (additive) | Interface | Implemented | High | trex-core-dev | [req-116](./Interface/req-116_slots-req-strategy-field-and-filter.md) |
| REQ-117 | OrderRequest venue additive 필드 (KRX/NXT 거래소구분 포트 DTO) | Integration | Implemented | High | trex-core-dev | [req-117](./Integration/req-117_order-request-venue-field.md) |
| REQ-118 | KIS 브로커 어댑터 venue 파라미터화 — EXCG_ID_DVSN_CD 전송 + NXT 지원 | Integration | Implemented | High | trex-core-dev | [req-118](./Integration/req-118_kis-broker-venue-parameterization.md) |
| REQ-118 | KIS 브로커 어댑터 venue 파라미터화 — EXCG_ID_DVSN_CD 전송 + NXT 지원 | Integration | Approved | High | trex-core-dev | [req-118](./Integration/req-118_kis-broker-venue-parameterization.md) |
| REQ-119 | fetched credential 공통 broker provider와 operator CLI truth 정합 | Integration | Implemented | High | trex-core-dev | [req-119](./Integration/req-119_fetched-credential-broker-provider-cli-parity.md) |
| REQ-120 | KIS balance 인증 실패 fail-loud 및 broker/token lifecycle 정책 | Exception | Implemented | High | trex-core-dev | [req-120](./Exception/req-120_kis-balance-auth-failure-fail-loud-lifecycle-policy.md) |
| REQ-121 | managed non-holding divergence 분류와 slot 존재 조회 계약 | Data | Implemented | High | trex-core-dev | [req-121](./Data/req-121_managed-non-holding-divergence-classification-query.md) |
@@ -216,6 +216,10 @@
- 2026-07-16 :: REQ-123 **신규 생성(Proposed, draft-req, dcy-038)**: portfolio-hub canonical `cost_basis_per_unit` consumer parse, legacy alias precedence, WAC unavailable fail-closed sync/no-write, dry-run diagnostics, response-contract/regression tests, observability 및 rollback/release gate. portfolio-hub API/schema/auth·token/config·DB migration/direct edit·live sync·배포 제외.
- 2026-07-15 :: REQ-119~122 **`Approved` → `Implemented`** (`confirm-req-implemented`, dcy-037/BAT-057): PR #42 merged to `main` (94e1489). Independent QC PASS-WITH-NOTES (acceptance blocker 0); 806 passed/47 skipped/1 deselected. `sync --live`, deployment, and production remediation remain separate release-approval work.
- 2026-07-14 :: REQ-119~122 **승인(Approved, confirm-req, dcy-037)**: lead 검토로 각 REQ의 owner·priority·scope·검증 가능 AC와 Notes의 non-blocking 설계 제약을 확인했다. #39/#40/#41은 다음 `suggest-batch-reqs`에서 단일 emergency batch 후보이며, `sync --live`는 여전히 별도 release approval gate다.
- 2026-07-29 :: REQ-118 CHG-20260729-01 **Implemented → Approved**:
KIS 공식 신형 일별주문체결조회(`TTTC0081R`/`VTTC0081R`,
`EXCG_ID_DVSN_CD=ALL`)를 일반·결과불명 `poll_fill`에 적용하고 BAT-063
검증 후 Implemented로 복귀한다.
- 2026-07-06 :: REQ-117/118 **신규 생성(Implemented, draft-req, trex-tvr-v1 dcy-002 FR-6)**: KIS 브로커 venue 파라미터화 — REQ-117 OrderRequest.venue additive 필드(기본 KRX, 포트 DTO 계층), REQ-118 KIS 어댑터 EXCG_ID_DVSN_CD 전송 + cancel venue 의미론 + poll_fill NXT 체결 보장 + get_current_price market 파라미터화. 실측 근거: 2026-07-06 실전 NXT 주문/취소 rt_cd=0. 고위험(라이브 주문 경로) — worktree+PR+QC(opus) 필수. confirm-req 대기.
- 2026-06-28 :: REQ-112/114 **`Approved` → `Implemented`**, REQ-113/115 **`Approved` 유지(confirm-req-implemented, dcy-036/BAT-053)**: BAT-053 PR #34 머지(01655ab). QC PASS-WITH-NOTES(opus, blocker 0). 745 passed/0 회귀. REQ-112(fetch 포트+어댑터+stub AC-1~8 완료), REQ-114(fail-closed 게이트 AC-1~9, BuyIntent 차단/SellIntent 통과 비대칭·CRITICAL 알람·opt-in off 비활성 완료). REQ-113/115 스캐폴드 완료 — KISBroker 런타임 재조립(REQ-113 AC-2, REQ-115 AC-1) M3 후속 batch 연기.
- 2026-06-28 :: REQ-112~115 **`Proposed` → `Approved`(confirm-req 팀장 승인)**: dcy-036 FR/NFR 추적·AC testable·blocker 0. 진입 fail-closed/청산·DISPATCHED 통과 비대칭(REQ-114 AC-4/5), 그날 고정 캐시·ledger 무손실(REQ-115), TradingContext 원자 read(REQ-112), opt-in 회귀차단(REQ-113 AC-1). OQ-4(trex-notifier) Deferred. 선행 의존 portfolio-hub dcy-010(머지완료). 다음=suggest-batch-reqs.
+82 -5
View File
@@ -85,6 +85,72 @@ def test_live_base_url_requires_allow_live():
assert b.name() == "kis-live" and b._tr_submit("buy") == "TTTC0012U"
@pytest.mark.parametrize(
("base_url", "allow_live", "expected_tr_id"),
[
(_MOCK_URL, False, "VTTC0081R"),
("https://openapi.koreainvestment.com:9443", True, "TTTC0081R"),
],
)
def test_daily_fill_request_uses_current_all_market_contract(
base_url,
allow_live,
expected_tr_id,
):
broker = KISBroker(
"12345678-01",
app_key="k",
app_secret="s",
base_url=base_url,
allow_live=allow_live,
)
tr_id, params = broker._daily_fill_request(
"20260729",
symbol="005930",
order_number="1234",
)
assert tr_id == expected_tr_id
assert params["EXCG_ID_DVSN_CD"] == "ALL"
assert params["PDNO"] == "005930"
assert params["ODNO"] == "1234"
def test_poll_fill_uses_current_all_market_contract(monkeypatch):
broker = KISBroker(
"12345678-01",
app_key="k",
app_secret="s",
base_url=_MOCK_URL,
)
seen = {}
monkeypatch.setattr(
broker,
"_headers",
lambda tr_id: seen.setdefault("tr_id", tr_id) and {},
)
class Response:
status_code = 200
def json(self):
return {"rt_cd": "0", "output1": []}
def fake_get(*args, **kwargs):
seen["params"] = kwargs["params"]
return Response()
monkeypatch.setattr("trex_core.adapters.broker.kis.httpx.get", fake_get)
status = broker.poll_fill("91258:1234", side="buy", symbol="005930")
assert status.state == "accepted"
assert seen["tr_id"] == "VTTC0081R"
assert seen["params"]["EXCG_ID_DVSN_CD"] == "ALL"
assert seen["params"]["ODNO"] == "1234"
def _kis_env():
keys = (
"TREX_KIS_BROKER_BASE_URL",
@@ -261,7 +327,12 @@ def test_submit_gateway_5xx_blocks_automatic_duplicate(monkeypatch):
def test_submit_unknown_is_resolved_by_unique_daily_order(monkeypatch):
b = KISBroker("12345678-01", app_key="k", app_secret="s", base_url=_MOCK_URL)
monkeypatch.setattr(b, "_headers", lambda _tr: {})
seen_tr_ids = []
monkeypatch.setattr(
b,
"_headers",
lambda tr_id: seen_tr_ids.append(tr_id) or {},
)
monkeypatch.setattr(
"trex_core.adapters.broker.kis.httpx.post",
lambda *a, **kw: (_ for _ in ()).throw(httpx.ReadTimeout("timed out")),
@@ -300,15 +371,21 @@ def test_submit_unknown_is_resolved_by_unique_daily_order(monkeypatch):
def json(self):
return {"rt_cd": "0", "output1": [row]}
monkeypatch.setattr(
"trex_core.adapters.broker.kis.httpx.get",
lambda *a, **kw: Response(),
)
seen_params = {}
def fake_get(*args, **kwargs):
seen_params.update(kwargs["params"])
return Response()
monkeypatch.setattr("trex_core.adapters.broker.kis.httpx.get", fake_get)
status = b.poll_fill(ack.broker_order_id, side="buy", symbol="005930")
assert status.state == "accepted"
assert status.raw["resolved_broker_order_id"] == "91258:0012345678"
assert seen_tr_ids[-1] == "VTTC0081R"
assert seen_params["EXCG_ID_DVSN_CD"] == "ALL"
assert seen_params["PDNO"] == "005930"
def test_submit_unknown_confirmed_absent_becomes_retryable_reject(monkeypatch):
+33 -39
View File
@@ -334,6 +334,34 @@ class KISBroker:
raw=payload,
)
def _daily_fill_request(
self,
trading_date: str,
*,
symbol: str = "",
order_number: str = "",
) -> tuple[str, dict[str, str]]:
"""Build the current KIS daily-fill contract for every order venue."""
cano, product = self._account()
tr_id = "VTTC0081R" if self._mock else "TTTC0081R"
return tr_id, {
"CANO": cano,
"ACNT_PRDT_CD": product,
"INQR_STRT_DT": trading_date,
"INQR_END_DT": trading_date,
"SLL_BUY_DVSN_CD": "00",
"INQR_DVSN": "00",
"PDNO": symbol,
"CCLD_DVSN": "00",
"ORD_GNO_BRNO": "",
"ODNO": order_number,
"INQR_DVSN_3": "00",
"INQR_DVSN_1": "",
"CTX_AREA_FK100": "",
"CTX_AREA_NK100": "",
"EXCG_ID_DVSN_CD": "ALL",
}
def poll_fill(self, broker_order_id: str, *, side: str, symbol: str) -> FillStatus:
if broker_order_id.startswith(_SUBMISSION_UNKNOWN_PREFIX):
return self._poll_unknown_submission(broker_order_id, side=side, symbol=symbol)
@@ -346,29 +374,9 @@ class KISBroker:
# Set the normal gate before I/O so concurrent execution ticks cannot
# open duplicate KIS inquiries for the same order.
self._fill_poll_not_before[broker_order_id] = now + _FILL_POLL_MIN_INTERVAL_SECONDS
cano, product = self._account()
odno = broker_order_id.split(":")[-1]
today = datetime.now(tz=_KST).strftime("%Y%m%d")
# REQ-118 AC-4 OPEN ITEM: TTTC8001R NXT 체결 포함 여부 미확인 (웹 접근 불가 환경,
# KIS 개발자 센터 문서 조회 불가). 보수 구현: 기존 TR 유지. KRX 경로 동작 절대 불변.
# 향후 NXT 체결 미포함 실측 확인 시 TTTC0081R(EXCG=ALL) 전환 별도 batch 처리.
tr_id = "VTTC8001R" if self._mock else "TTTC8001R"
params = {
"CANO": cano,
"ACNT_PRDT_CD": product,
"INQR_STRT_DT": today,
"INQR_END_DT": today,
"SLL_BUY_DVSN_CD": "00",
"INQR_DVSN": "00",
"PDNO": "",
"CCLD_DVSN": "00",
"ORD_GNO_BRNO": "",
"ODNO": odno,
"INQR_DVSN_3": "00",
"INQR_DVSN_1": "",
"CTX_AREA_FK100": "",
"CTX_AREA_NK100": "",
}
tr_id, params = self._daily_fill_request(today, order_number=odno)
try:
resp = httpx.get(
f"{self._base_url}/uapi/domestic-stock/v1/trading/inquire-daily-ccld",
@@ -430,26 +438,12 @@ class KISBroker:
self._fill_poll_not_before[broker_order_id] = (
now_monotonic + _FILL_POLL_MIN_INTERVAL_SECONDS
)
cano, product = self._account()
submitted_at = datetime.fromisoformat(str(metadata["submitted_at"])).astimezone(_KST)
today = submitted_at.strftime("%Y%m%d")
tr_id = "VTTC8001R" if self._mock else "TTTC8001R"
params = {
"CANO": cano,
"ACNT_PRDT_CD": product,
"INQR_STRT_DT": today,
"INQR_END_DT": today,
"SLL_BUY_DVSN_CD": "00",
"INQR_DVSN": "00",
"PDNO": str(metadata["symbol"]),
"CCLD_DVSN": "00",
"ORD_GNO_BRNO": "",
"ODNO": "",
"INQR_DVSN_3": "00",
"INQR_DVSN_1": "",
"CTX_AREA_FK100": "",
"CTX_AREA_NK100": "",
}
tr_id, params = self._daily_fill_request(
today,
symbol=str(metadata["symbol"]),
)
try:
resp = httpx.get(
f"{self._base_url}/uapi/domestic-stock/v1/trading/inquire-daily-ccld",