docs(proofline): approve KIS balance pagination spec
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
---
|
||||
id: "dcy-0001"
|
||||
status: confirmed
|
||||
---
|
||||
|
||||
# KIS 잔고조회 페이지네이션 완전성
|
||||
|
||||
## Problem
|
||||
|
||||
`trex-core`의 `KISBroker.get_balance()`는 KIS 국내주식 잔고조회 응답을 한 번만 요청하고 첫 페이지의 `output1`만 반환한다. 실전 계좌는 호출당 최대 50건, 모의 계좌는 최대 20건을 반환하므로 보유 row가 이 한도를 넘으면 성공 응답이어도 후속 페이지의 종목이 누락된 불완전한 broker snapshot이 만들어진다.
|
||||
|
||||
이 snapshot을 사용하는 두 reconciliation 경로는 서로 독립적으로 잘못된 결과를 낼 수 있다. `reconcile_positions()`는 portfolio-hub 수량과 누락된 broker 수량을 비교해 `case_code=C` 경고를 만들 수 있고, `reconcile_core_holdings()`는 holding 종목을 broker flat으로 오인해 슬롯을 `error`로 격리할 수 있다. 문제는 reconciliation 정책 자체가 아니라 두 소비자에게 전달되는 KIS 잔고 snapshot의 완전성이다.
|
||||
|
||||
기대 상태는 KIS가 지시하는 모든 연속 페이지를 성공적으로 읽고 병합한 경우에만 완전한 잔고를 반환하고, 페이지 체인이 불완전하거나 진행하지 않으면 부분 잔고를 정상 결과로 반환하지 않는 것이다.
|
||||
|
||||
## Evidence
|
||||
|
||||
- GitHub issue #53, `https://github.com/fourblocks2020/trex-core/issues/53` (OPEN, 2026-08-06T14:54:08Z 갱신)은 운영 계좌에서 첫 페이지 `output1=50`, 후속 페이지 `output1=9`를 관찰했고, 두 번째 페이지의 7개 양수 보유 종목이 첫 페이지 전용 구현에서 누락됐다고 기록한다. 두 요청 모두 HTTP `200`, `rt_cd=0`이어서 인증·권한·KIS 업무 오류와 구별된다.
|
||||
- `trex_core/adapters/broker/kis.py:626-689`의 `KISBroker.get_balance()`는 최초 query에 빈 `CTX_AREA_FK100`/`CTX_AREA_NK100`을 설정한 뒤 `httpx.get()`을 한 번만 호출하고 그 응답의 `output1`만 변환해 반환한다. 응답의 `ctx_area_fk100`, `ctx_area_nk100`과 response header `tr_cont`를 읽는 경로가 없다.
|
||||
- 저장소의 KIS 계약 문서 `docs/broker/kis/rest/국내주식/주문/계좌/015_v1_국내주식-006_주식잔고조회.md:18-20,38,59-60,68,78-80`은 실전 최대 50건·모의 최대 20건, 최초 요청의 빈 `tr_cont`, 후속 요청의 `tr_cont=N`, 이전 응답 continuation key 재사용, response header `tr_cont`의 `F/M`(다음 데이터 있음) 및 `D/E`(마지막 데이터)를 명시한다.
|
||||
- 이슈 원문의 “첫 요청에 `tr_cont=F`”와 “`KIOK0510` 등으로 마지막 페이지 판정”은 위 계약과 다르다. 최초 요청은 공백/미지정이어야 하고, 후속 페이지 존재와 종료의 정본은 `msg_cd`가 아니라 response header `tr_cont`다. `KIOK0500`/`KIOK0510`은 운영 관찰 증거로만 취급한다.
|
||||
- `trex_core/application/reconciliation.py:100-199`의 `reconcile_core_holdings()`는 반환된 balance map에 holding 종목이 없으면 `broker_qty=0`으로 판단해 `holding → error`를 수행한다. `trex_core/application/reconciliation.py:202-280`의 `reconcile_positions()`는 별도로 portfolio-hub 수량과 broker 수량을 비교하고 불일치 시 `case_code=C` 경고를 기록한다.
|
||||
- 현재 tree `bdf9761b2d64166abfcf1ab5699800c37c076f04`에서 `.venv/bin/python -m pytest -q tests/adapters/broker/test_kis.py tests/application/test_holding_divergence.py tests/application/test_reconciliation.py`를 실행한 결과는 `33 passed, 1 skipped`였다. 기존 `get_balance()` 테스트는 실패 응답, 정상 빈 응답, 토큰 발급 실패를 다루지만 다중 페이지, continuation 진행, 중간 페이지 실패는 검증하지 않는다.
|
||||
|
||||
## Scope
|
||||
|
||||
- AC admission 분류는 새로운 장기 active 동작을 정의하는 `create`다. 현재 ProofLine AC inventory는 비어 있고, 이 Line은 KIS 잔고 snapshot 완전성에 대한 독립 PASS/FAIL 계약을 새로 만든다.
|
||||
- `KISBroker.get_balance()`는 최초 요청에서 continuation key를 비우고 `tr_cont`를 공백/미지정으로 유지하며, response header `tr_cont`가 `F` 또는 `M`이면 직전 응답의 `ctx_area_fk100`/`ctx_area_nk100`을 다음 query에 전달하고 후속 요청 header에 `tr_cont=N`을 사용한다.
|
||||
- response header `tr_cont`가 `D` 또는 `E`일 때 페이지 순회를 종료한다. `msg_cd` 값은 진단 정보일 뿐 pagination 제어 계약으로 사용하지 않는다.
|
||||
- 모든 성공 페이지의 `output1`을 읽고 동일 종목의 양수 `hldg_qty`를 종목별로 합산하며, 기존의 0수량 row 제외 동작을 유지한다.
|
||||
- 최초 페이지를 포함한 어느 페이지에서든 HTTP, KIS `rt_cd`, JSON shape, row shape 또는 continuation 계약이 실패하면 이미 모은 부분 결과를 반환하지 않고 기존의 정제된 `BrokerBalanceError` 경계로 실패한다.
|
||||
- response header가 다음 페이지를 요구하지만 continuation key가 비어 있거나 이전과 동일해 진행하지 않는 경우 무한 반복하지 않고 fail-closed 한다.
|
||||
- 단일 페이지, 실전/모의 page-size와 무관한 synthetic 다중 페이지, 종목별 병합, 중간 페이지 실패, 빈/반복 continuation key, 정상 빈 잔고를 단위 테스트로 검증한다.
|
||||
- 다중 페이지 결과를 사용하는 기존 reconciliation에서 후속 페이지 보유 종목이 `broker_qty=0`으로 누락되지 않는 회귀 경계를 검증한다.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- portfolio-hub 저장소 또는 API 변경
|
||||
- `case_code=B/C` 분류, warning persistence, `holding → error`를 포함한 reconciliation 정책 변경
|
||||
- KIS 인증정보, token cache, broker provider 또는 account ownership projection 변경
|
||||
- 잔고조회 외 주문·체결·시세 API의 pagination 일반화
|
||||
- `INQR_DVSN`, 보유수량 0 row 제외 등 이 Line과 무관한 기존 KIS query·정규화 정책 변경
|
||||
- 운영계좌에 대한 주문, 자동 슬롯 복구, 기존 warning/error 상태의 데이터 수정
|
||||
- 배포, release 또는 운영환경 검증
|
||||
|
||||
## Risks and Unknowns
|
||||
|
||||
### Risks
|
||||
|
||||
- 첫 페이지가 성공한 뒤 후속 페이지가 실패했을 때 부분 snapshot을 반환하면 현재 결함이 다른 형태로 유지된다. 완화책은 전체 페이지 체인이 성공한 경우에만 결과를 반환하는 atomic read 경계다.
|
||||
- KIS가 “다음 데이터 있음”을 표시하면서 빈 key나 동일 key를 반복하면 무한 호출이 발생할 수 있다. 완화책은 key 존재성과 진행성을 검증하고 위반 시 fail-closed 하는 것이다.
|
||||
- response body의 `msg_cd`를 종료 조건으로 고정하면 KIS 메시지 코드 변화에 취약하다. 완화책은 저장소에 기록된 공식 계약대로 response header `tr_cont`만 pagination 제어에 사용하고 메시지 코드는 진단용으로 한정하는 것이다.
|
||||
- 동일 종목이 여러 row 또는 페이지에 걸쳐 나타날 수 있으므로 마지막 row 덮어쓰기는 수량을 축소할 수 있다. 완화책은 유효한 양수 수량을 종목별로 합산하고 해당 경계를 테스트하는 것이다.
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
id: "line-0001"
|
||||
---
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
id: "req-0001"
|
||||
status: approved
|
||||
discovery: "dcy-0001"
|
||||
criteria:
|
||||
create:
|
||||
- "ac-0001"
|
||||
- "ac-0002"
|
||||
- "ac-0003"
|
||||
update: []
|
||||
retire: []
|
||||
satisfy: []
|
||||
---
|
||||
|
||||
# KIS 잔고조회 페이지네이션 완전성
|
||||
|
||||
## Objective
|
||||
|
||||
KIS 국내주식 잔고조회에서 단일 응답 한도를 넘는 계좌도 모든 연속 페이지를 완전하게 읽어 종목별 broker 잔고 snapshot을 반환하고, 불완전한 페이지 체인을 정상 잔고로 소비하지 않도록 한다. 이를 통해 후속 페이지 보유종목이 reconciliation에서 broker flat 또는 수량 불일치로 오인되지 않게 한다.
|
||||
|
||||
## Scope
|
||||
|
||||
- `KISBroker.get_balance()`의 국내주식 잔고조회에 KIS response header와 continuation key에 따른 연속조회 동작을 적용한다.
|
||||
- 모든 성공 페이지의 유효한 `output1` 보유수량을 종목별 snapshot으로 집계한다.
|
||||
- 페이지 체인의 완전성과 진행성을 검증하고 불완전한 결과를 기존 broker read error 경계로 차단한다.
|
||||
- KIS adapter의 단일·다중 페이지 및 오류 경계, 기존 account ownership projection, 두 reconciliation 소비 경로의 회귀를 repository-local 자동화 테스트로 검증한다.
|
||||
|
||||
## Constraints
|
||||
|
||||
- 최초 요청은 `tr_cont`를 공백/미지정으로 유지하고 continuation key를 비우며, 후속 요청만 `tr_cont=N`과 직전 응답의 key를 사용한다.
|
||||
- 다음 페이지와 종료 판정은 response header `tr_cont`의 `F/M` 및 `D/E` 계약을 따르며 `msg_cd`를 제어 흐름의 정본으로 사용하지 않는다.
|
||||
- 하나의 `get_balance()` 호출 동안 정규화한 `(ctx_area_fk100, ctx_area_nk100)` pair의 방문 이력을 추적하고, 이미 방문한 pair가 즉시 또는 비인접하게 다시 나타나면 실패해야 한다.
|
||||
- 어느 페이지에서든 실패하면 누적된 부분 잔고를 반환하거나 broker-derived reconciliation 판단·warning/slot write에 사용하지 않는다.
|
||||
- row가 object가 아니거나, `hldg_qty`가 누락되거나 정수 변환 불가능하거나, 양수 수량의 `pdno`가 비어 있으면 malformed response로 실패한다. `hldg_qty <= 0` row는 기존 normalization대로 제외한다.
|
||||
- pagination 계약 위반은 기존 `BrokerBalanceError` allow-list 안의 정제된 오류로 매핑하며 새 public error taxonomy를 추가하지 않는다.
|
||||
- 정상적인 단일 페이지, 검증된 빈 잔고, 0 이하 수량 row 제외, account ownership projection 및 정제된 `BrokerBalanceError` 동작을 보존한다.
|
||||
- 실계좌 주문, 운영 데이터 수정 또는 외부 저장소 변경 없이 repository-local fixture로 검증 가능해야 한다.
|
||||
|
||||
## Non-Goals
|
||||
|
||||
- portfolio-hub 저장소 또는 API 변경
|
||||
- reconciliation의 warning 분류, 상태 전이 또는 자동 복구 정책 변경
|
||||
- KIS credential, token cache, broker provider 또는 account ownership projection 변경
|
||||
- 잔고조회 외 KIS API의 pagination 일반화
|
||||
- `INQR_DVSN`을 포함한 이 Line과 무관한 query·정규화 정책 변경
|
||||
- 운영계좌 주문, 기존 warning/error 데이터 수정, 배포 또는 release
|
||||
Reference in New Issue
Block a user