6.9 KiB
6.9 KiB
id, status
| id | status |
|---|---|
| dcy-0001 | 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개 양수 보유 종목이 첫 페이지 전용 구현에서 누락됐다고 기록한다. 두 요청 모두 HTTP200,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 headertr_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 headertr_cont의F/M(다음 데이터 있음) 및D/E(마지막 데이터)를 명시한다. - 이슈 원문의 “첫 요청에
tr_cont=F”와 “KIOK0510등으로 마지막 페이지 판정”은 위 계약과 다르다. 최초 요청은 공백/미지정이어야 하고, 후속 페이지 존재와 종료의 정본은msg_cd가 아니라 response headertr_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 headertr_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 headertr_cont만 pagination 제어에 사용하고 메시지 코드는 진단용으로 한정하는 것이다. - 동일 종목이 여러 row 또는 페이지에 걸쳐 나타날 수 있으므로 마지막 row 덮어쓰기는 수량을 축소할 수 있다. 완화책은 유효한 양수 수량을 종목별로 합산하고 해당 경계를 테스트하는 것이다.