diff --git a/docs/batches/bat-063_20260729_kis-daily-fill-all/design.md b/docs/batches/bat-063_20260729_kis-daily-fill-all/design.md new file mode 100644 index 0000000..16758be --- /dev/null +++ b/docs/batches/bat-063_20260729_kis-daily-fill-all/design.md @@ -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 +- 주문 상태 해석과 도메인·애플리케이션 경계에는 영향 없음 diff --git a/docs/batches/bat-063_20260729_kis-daily-fill-all/implementation.md b/docs/batches/bat-063_20260729_kis-daily-fill-all/implementation.md new file mode 100644 index 0000000..bc8d318 --- /dev/null +++ b/docs/batches/bat-063_20260729_kis-daily-fill-all/implementation.md @@ -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 응답이나 주문 가능 상태를 증명하지 않는다. +- 이 배치는 실주문, 계정 설정, 주문 게이트, 배포를 변경하지 않는다. diff --git a/docs/batches/bat-063_20260729_kis-daily-fill-all/index.md b/docs/batches/bat-063_20260729_kis-daily-fill-all/index.md new file mode 100644 index 0000000..b639325 --- /dev/null +++ b/docs/batches/bat-063_20260729_kis-daily-fill-all/index.md @@ -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 축소를 별도 저장소에서 수행한다. diff --git a/docs/batches/bat-063_20260729_kis-daily-fill-all/planning.md b/docs/batches/bat-063_20260729_kis-daily-fill-all/planning.md new file mode 100644 index 0000000..46145a3 --- /dev/null +++ b/docs/batches/bat-063_20260729_kis-daily-fill-all/planning.md @@ -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`을 별도 회귀검사로 고정한다. +- 체결 상태 해석이 바뀔 수 있다. + - 대응: 요청 생성만 변경하고 응답 파싱 함수와 공개 포트 시그니처를 보존한다. diff --git a/docs/batches/bat-063_20260729_kis-daily-fill-all/verification.md b/docs/batches/bat-063_20260729_kis-daily-fill-all/verification.md new file mode 100644 index 0000000..5656925 --- /dev/null +++ b/docs/batches/bat-063_20260729_kis-daily-fill-all/verification.md @@ -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` diff --git a/docs/batches/index.md b/docs/batches/index.md index a7be732..0c69696 100644 --- a/docs/batches/index.md +++ b/docs/batches/index.md @@ -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/) | diff --git a/docs/interface-contract.md b/docs/interface-contract.md index b1f6a97..ebaa721 100644 --- a/docs/interface-contract.md +++ b/docs/interface-contract.md @@ -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 diff --git a/docs/runtime-flows.md b/docs/runtime-flows.md index 6afedbd..996e393 100644 --- a/docs/runtime-flows.md +++ b/docs/runtime-flows.md @@ -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) diff --git a/docs/srs/Integration/req-118_kis-broker-venue-parameterization.md b/docs/srs/Integration/req-118_kis-broker-venue-parameterization.md index 360432a..07d4cbf 100644 --- a/docs/srs/Integration/req-118_kis-broker-venue-parameterization.md +++ b/docs/srs/Integration/req-118_kis-broker-venue-parameterization.md @@ -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 diff --git a/docs/srs/index.md b/docs/srs/index.md index 3a50292..1abc7bd 100644 --- a/docs/srs/index.md +++ b/docs/srs/index.md @@ -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. diff --git a/tests/adapters/broker/test_kis.py b/tests/adapters/broker/test_kis.py index b751939..9c711c6 100644 --- a/tests/adapters/broker/test_kis.py +++ b/tests/adapters/broker/test_kis.py @@ -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): diff --git a/trex_core/adapters/broker/kis.py b/trex_core/adapters/broker/kis.py index ea90966..d2a4e15 100644 --- a/trex_core/adapters/broker/kis.py +++ b/trex_core/adapters/broker/kis.py @@ -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",