Skip to content

v2.11.0 — API 파라미터 감사 (Tranche B)#28

Merged
gejyn14 merged 32 commits into
mainfrom
feature/v2.11.0-api-params
Jul 19, 2026
Merged

v2.11.0 — API 파라미터 감사 (Tranche B)#28
gejyn14 merged 32 commits into
mainfrom
feature/v2.11.0-api-params

Conversation

@gejyn14

@gejyn14 gejyn14 commented Jul 19, 2026

Copy link
Copy Markdown
Owner

키움 스펙 대비 잘못 전송되던 API 파라미터를 교정하고, 자유 텍스트 옵션을 human-readable enum으로 전환했습니다. 15개 API(ka10066, ka10131, ka10063, kt20016, kt20017, ka10005, ka90005, ka90010, ka10030, ka10032, ka10038, ka30002, ka10043, ust20000, ust20001)가 대상입니다.

테스트 757 → 895, ruff clean.

Breaking

  • stock daily --type 제거 — 주/월 조회는 stock chart 사용
  • market rank volume --include-managed--stock-condition (15종 종목필터, boolean이 아니었음)
  • market elw broker-top --issuer, stock trader-analysis --broker 필수화
  • credit inquiry가 종목코드를 위치 인자로 받음
  • 미국 지정가 계열 주문은 --price 누락 시 전송 전 exit 1
  • 자유 텍스트였던 옵션 다수가 enum으로 축소 (raw 숫자 코드는 HumanChoice로 계속 동작)
  • stock investor consecutive --amount-qty 2 거부 — 스펙에 없는 값이었음

검토

브랜치 전체를 세 갈래로 리뷰했습니다: 태스크 간 상호작용, 스펙 대비 전송값, CHANGELOG 정확성. 모든 리뷰는 프로덕션 코드를 되돌려 테스트가 실제로 실패하는지 확인하는 방식으로 검증했습니다.

전송값 검토에서 kwcli가 배포하는 arguments.csv가 이 브랜치의 값 매핑과 문자 단위로 일치함을 확인했습니다. 값 오류는 나오지 않았습니다.

리뷰에서 나온 7건을 수정했습니다. 가장 중요한 건 --amount-qty 2 제거가 breaking으로 공시되지 않은 채 Non-breaking 항목이 정반대 내용을 주장하던 문제였습니다. HumanChoice 고정 테스트가 order 그룹을 훑지 않아 금현물 주문 2건을 놓치고 있던 것도 함께 고쳤습니다(53 → 55, CLI 트리 전체 순회로 변경).

다음 릴리스로 미룬 것

  • 국내 지정가 주문의 ord_uv 가드 — 국내도 빈 문자열을 전송하지만 서버가 거부하므로 잘못된 주문은 나가지 않습니다. exit 1 대신 exit 2가 반환되는 차이입니다. kwcli의 order_price_policies.csv에 근거가 있어 다음 릴리스에 넣습니다.
  • --exchange 3곳 정리 (ka10030, ka90006, ka90007) — 세 스펙 모두 3:통합을 지원하지만 아직 KRX/NXT만 받습니다. Tranche E에서 한 번에 처리하려고 의도적으로 남겼습니다. 그래서 이번 릴리스는 rank volumerank amount--exchange all 지원 여부에서 서로 다릅니다.

🤖 Generated with Claude Code

gejyn14 and others added 30 commits July 19, 2026 19:47
--trade의 기본값이 실제로는 순매도(2) 데이터를 반환하고 있었다. 스펙(ka10066
Request Body)은 trde_tp를 0:순매수, 1:매수, 2:매도로 정의하는데 기존
Choice(["1","2"])/default="2"는 진짜 순매수 코드 0을 아예 받을 수 없었고
help는 반대로 "2=순매수"라고 안내했다.

- --trade: HumanChoice(TRDE_TP_NET_BUY_BUY_SELL), default="net-buy"
  (net-buy=0/buy=1/sell=2)
- --amount-qty: HumanChoice(AMT_QTY_TP_STD) (amount=1/quantity=2, 값은
  변경 없음 — presentation만 전환)
- --market: MARKET_ALL로 전환, 스펙에 있는 000:전체(all) 추가 지원
- --exchange: EXCHANGE_ALL이 이미 스펙(1:KRX,2:NXT,3:통합)과 일치해 변경 없음

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
…euse guard (ka10066)

Review round 1 on the ka10066 매매구분 fix:
- CHANGELOG wrongly claimed raw numeric --trade/--amount-qty codes were no
  longer valid. HumanChoice.convert() backward-compat-allows raw codes by
  design; verified live that --trade 0/1/2 and --amount-qty 1/2 all still
  work. Reframed the entry: E-side name conversion is non-breaking, the
  only real breaking change is --trade's default flipping trde_tp 2->0.
- Renamed AMT_QTY_TP_STD -> AMT_QTY_TP_1_2 so the name itself encodes the
  wire codes, since ka10051/ka10131 use an identical {amount,quantity} key
  set with different codes (0/1) and nothing in "STD" would have stopped
  a future misreuse. Reserved AMT_QTY_TP_0_1 in a comment for that pair.
- Added the missing --market kosdaq coverage test for after-close.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
amt_qty_tp 스펙은 0:금액,1:수량인데 기존 Choice(["1","2"])/default="1"은
한 칸 밀려 있어 기본값이 실제로는 수량을 반환했고 스펙에 없는 값 2도
선택 가능했다. AMT_QTY_TP_0_1(Task 8이 예약해 둔 이름)로 교체하고
default="amount"(wire "0")로 정정.

같은 자리의 다른 4개 옵션도 함께 정리(Tranche B+E는 한 사이트에서 둘 다
처리):
- --period(dt): 자유 텍스트 -> HumanChoice(recent/3d/5d/10d/20d/120d/range),
  0이 "시작/종료일자로 조회" 모드 전환임을 스펙에서 확인, 기본값 wire "5" 불변
- --net-type(netslmt_tp): 자유 텍스트 -> HumanChoice({"net-buy":"2"}) (스펙상
  고정값 1개), wire "2" 불변
- --stock-sector(stk_inds_tp): Choice(["0","1"]) -> HumanChoice(stock/sector),
  wire "0" 불변
- --exchange(stex_tp): 이미 EXCHANGE_ALL로 스펙과 일치, 변경 없음

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
자유 입력이던 --period가 열거형이 되면서 이전에 그대로 전송되던 스펙 미정의
값(예: --period 7)이 이제 전송 전에 거부된다. 하위호환 절에 이 축소를 적지
않아 리뷰에서 지적됐다 — 이름 추가는 하위호환이지만 값 제한은 아니다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
ka10063 요청 바디 6개 필드 중 5개가 스펙과 어긋나 있었다:
- invsr 기본값이 ka10058 invsr_tp 코드북을 복붙한 스펙 밖 값 "1000"
  (스펙 Length=1) -> INTRADAY_INVESTOR + HumanChoice, default="foreign"(6)
- mrkt_tp가 MARKET_TWO(kospi/kosdaq)라 스펙의 000:전체를 못 씀 -> MARKET_ALL
- amt_qty_tp가 자유 텍스트(스펙상 값 1개뿐) -> AMT_QTY_TP_COMBINED enum
- frgn_all/smtm_netprps_tp가 원시 0/1 노출 -> 공용 CHECK_YES_NO
- stex_tp(EXCHANGE_ALL)는 이미 스펙과 일치해 손대지 않음

TDD로 진행: invsr=="6" 단언 테스트를 먼저 작성해 기존 "1000"으로 실패하는
것을 확인한 뒤 수정. 15개 테스트 추가(796 = 781 + 15), ruff clean.
fix(stock) 커밋과 분리해 breaking 두 건(invsr 기본값 1000->6, amount-qty
자유텍스트->enum)과 non-breaking human-readable 이름 추가를 구분 기록.
…O 개명 (ka10063)

리뷰 발견 2건 수정:

1. CHANGELOG가 --investor-type을 (a) 기본값 변경으로만 breaking
   처리했지만, 이 옵션은 원래 type= 없는 자유 텍스트였으므로 (c)
   자유텍스트->enum narrowing이기도 하다. 실측 회귀 2건을 기록:
   --investor-type 1000(이전 기본값)과 --investor-type 9 둘 다
   이전엔 그대로 전송됐지만 지금은 exit 1. 하위호환 문단의 과잉
   주장("숫자 코드를 직접 넘기던 스크립트는 그대로 동작합니다")도
   새 매핑에 있는 값(스펙이 정의한 코드)으로 범위를 좁혔다 —
   --amount-qty가 이미 받고 있던 처리와 동일하게 맞춤.

2. _constants.py의 CHECK_YES_NO를 CHECK_YES_1_NO_0으로 개명.
   이름에 wire 값(1/0)과 극성을 박아 넣어 AMT_QTY_TP_1_2/
   AMT_QTY_TP_0_1 명명 관례와 맞추고, stock.py에 남아있는 무관한
   click.Choice(["0","1"]) 6곳(indc_tp 등)이 이 이름에 이끌려
   재사용되는 것을 막았다. 순수 rename — 전송값/기본값 변경 없음.

pytest tests/test_stock.py -q: 56 passed. ruff check kiwoom_cli/: All
checks passed. 두 회귀 모두 실제 CLI 실행으로 재확인함(추측 아님).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
credit inquiry(kt20017)는 필수 stk_cd를 받을 인자가 없어 {}만 전송해
호출이 원천적으로 성립하지 않았다. 종목코드를 위치 인자로 추가해
{"stk_cd": code}를 전송한다.

credit available(kt20016)은 필수 mrkt_deal_tp를 누락했다. --market
(CREDIT_MARKET: all=%, kospi=1, kosdaq=0 — MARKET_KOSPI_KOSDAQ와 극성
반대, 항상 전송), --grade(CREDIT_GRADE, 항상 전송), --code(선택,
미지정 시 stk_cd 키 자체를 생략)를 추가했다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
README의 stock daily --type week 예제를 stock chart week로 교체하고,
CHANGELOG [Unreleased]에 이 릴리스의 유일한 진짜 breaking 변경을 기재.
--market이 형제 API(ka90007)에서 복붙된 "0"/"1"을 보내고 있었다. ka90005/
ka90010의 mrkt_tp는 10자리 P-코드이고 값이 stex_tp와 연동된다
(docs/미국 REST API 문서.xlsx). --market을 kospi/kosdaq Choice로 바꾸고
PROGRAM_MARKET_BY_EXCHANGE[market][stex_tp]로 해석하도록 수정했다.

--exchange도 KRX/NXT만 받던 것을 EXCHANGE_ALL(all=3 포함)로 확장했다 —
stex_tp=3(통합) 분기가 실제로 존재하는데 이전에는 지정할 방법이 없었다.

두 API 스펙 시트가 코스닥+통합 코드를 다르게 적고 있다(ka90005:
P101_AL02, ka90010: P001_AL02) — 워크북/키움 공식 GitHub 전체에서
동일하게 나타나는 모순이라 키움 소스로는 판정 불가. 사용자 결정으로
코스닥 계열 접두사(P101_)에 맞춰 두 API 모두 P101_AL02로 통일했다
(_constants.py의 PROGRAM_MARKET_BY_EXCHANGE 주석에 근거 기록).

amt_qty_tp/min_tic_tp도 원시 코드 옵션이었던 것을 HumanChoice로 전환
(AMT_QTY_TP_1_2 재사용, MIN_TIC_TP 신설).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
ka10030(rank volume)/ka10032(rank amount)의 mang_stk_incls 극성 반전을
바로잡고 ka10030은 15종 종목필터를 온전히 노출하는 --stock-condition으로
분리했다. ka10038(broker-by-stock)은 --period/--from·--to 동시 지정 시
기간(dt) 우선이 조용히 이기던 것을 막고 스펙대로 --from/--to일 때 dt 키를
아예 제외하도록 고쳤다. ka30002(elw broker-top)의 --issuer 기본값이
자릿수부터 틀린(12자리, 필드는 3자리) 무효값이었던 것을 제거하고 필수
옵션으로 바꿨다. 두 명령의 나머지 자유 텍스트 옵션도 HumanChoice로 전환.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
ka10030/ka10032 mang_stk_incls 극성 반전, --stock-condition 신설,
ka10038 --period/--from·--to 상호배타, ka30002 --issuer 필수화,
enum 전환 목록을 CHANGELOG에 기록. 모든 주장은 CLI 실행으로 확인.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
… requires both --from/--to

Two review findings on 11d1486/3964b3f:

- _constants.py: STOCK_CONDITION's name translates to 종목조건 (Korean name
  of the raw-text stk_cnd field used by ~10 market.py commands), but the
  constant actually holds ka10030's mang_stk_incls codes. Extended the
  comment to warn a future stk_cnd->HumanChoice conversion off reusing this
  constant. No behavior change.

- market.py rank_broker_by_stock (ka10038): has_range = bool(strt_dt or
  end_dt) let a lone --from (or --to) through, sending
  {strt_dt: "...", end_dt: "", dt: <key excluded>} — an under-specified
  request. The spec's blank-dt rule only applies when both start and end
  date are given. Added a guard requiring --from and --to together,
  exiting 1 (INVALID_INPUT) otherwise. Added tests for each one-sided case
  asserting exit 1 and no request sent. Updated CHANGELOG Fixed/Breaking
  entries, wording verified against actual CLI output.
qry_dt_tp 기본값이 "0"(기간으로 조회)이라 --from/--to가 required=True인데도
API가 그 필수 날짜를 무시하고 있었고, mmcm_cd(Required=Y)는 기본값이 빈
문자열이었다. --date-type 기본값을 "1"(start-end)로 바꾸고 --broker를
필수로 승격했다. --date-type/--pot/--sort/--days 네 옵션도 HumanChoice로
전환(하위호환 raw 코드 유지). dt 코드북은 ka10038의 off-by-one 코드북과
값이 다르므로(5일=5, 정상) 절대 합치지 말 것을 상수 주석에 명시.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
The Breaking bullet claimed --date-type/--pot/--sort/--days were free
text and got narrowed to an enum. Verified against 857fc98: all four
already carried click.Choice with numeric codes, and --date-type 9
fails identically before/after (exit 1). The accepted set only
widened (human-readable names added alongside existing codes), which
the Non-breaking section already documents accurately — so the false
bullet is just deleted.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
지정가 계열(limit/vwap-limit/twap-limit/loc/stop-limit)에서 --price 없이
주문하면 ord_uv=""로 조용히 전송되고 있었다. ust20000/ust20001 스펙:
"trde_tp가 00(지정가),30(LOC)...인 경우 필수 입력". US_MARKET_TYPES의
여집합으로 US_LIMIT_TYPES를 신설하고, _validate_us_type에 반대 방향 가드를
추가했다. 국내(kt10000/kt10001)는 조건부 필수 문구가 스펙에 없어 대상에서
제외했다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Breaking: --price 없이 지정가 계열을 호출하던 스크립트는 이제 exit 1 —
다만 그 호출은 원래 정상 동작한 적이 없었다(ord_uv="" 전송). 국내 경로는
스펙 근거 부족으로 의도적으로 그대로 두었다는 점도 함께 기록.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
여섯 개 작업이 각자 이어붙인 Fixed/Breaking/Non-breaking 헤딩이 8번
반복되던 것을 각 1개로 병합했다. 내용 변경 없이 순서만 정리했다.

- Non-breaking인데 Breaking에 있던 두 항목을 이동:
  credit inquiry/available의 CREDIT_MARKET 극성 설명(자체 결론이 이미
  "하위호환 깨짐 아님"), program time/daily-trend의 --exchange all
  추가(순수 확장)
- bool(strt_dt or end_dt)를 버그로 서술한 Fixed 항목 삭제 — 같은
  Unreleased 구간 안에서 도입되고(11d1486) 세 커밋 뒤에 고쳐진(857fc98)
  릴리스 내부 리그레션이라, 배포된 적 없는 버그를 배포된 것처럼 적어
  독자를 오도했다. 옆의 Breaking 항목이 실제 사용자 영향(--from/--to
  한쪽만 주면 exit 1)을 이미 정확히 설명한다
- 미국 --price 가드 항목에서 "(컨트롤러가 워크북에서 직접 확인)" 제거,
  영향받는 명령 목록에 --dry-run 추가(order buy NVDA 10 --type limit
  --dry-run도 이제 exit 1)

CLI로 직접 실행해 확인: --exchange all 추가는 기본 호출 body 불변,
credit 커맨드 둘 다 이전엔 옵션이 하나도 없었음, --dry-run이 가격
가드보다 먼저 걸림.
여집합으로 정의하는 방식은 실수를 막지 못하고 비대칭으로 만들 뿐이다.
새 주문유형을 US_MARKET_TYPES에 추가하는 걸 잊으면 조용히 지정가
계열로 분류돼 --price를 요구하게 된다 — 즉 시장가 계열 신규 유형이
정당한 무가격 주문을 거부당하는 쪽으로만 실패한다. 주문 경로에서는
이 방향이 의도한 선택(과잉 거부가 가격 누락 주문 전송보다 안전)이라는
점을 주석에 명시했다. 정의 자체는 변경하지 않았다 — 동작 변화 없음.
이전 Choice 목록 ["1","2"]에서 2는 실제로 amt_qty_tp="2"로 전송됐지만
새 AMT_QTY_TP_0_1에는 없는 값이라 이제 exit 1이다. 값 집합이 줄었으므로
옵션이 이미 Choice였더라도 breaking이다.

Non-breaking 쪽에서 "숫자 코드는 그대로 동작한다"고 단언하던 문장도
스펙이 정의한 코드로 한정해 새 breaking 항목과 어긋나지 않게 고쳤다.
program time-trend/daily-trend의 --unit·--tick-type과 investor consecutive의
--period·--net-type은 fe289c7 시점에 전부 type= 없는 자유 텍스트였다.
매핑 밖 값이 이제 거부되므로 non-breaking이 아니라 breaking이다.

Non-breaking 쪽에 꼬리말로 붙어 있던 축소 고백 두 군데를 걷어내고,
기존 breaking 축소 목록에 항목 하나로 합쳤다.
"dt 키를 빼고 빈 문자열인 채로 전송했다"는 서술은 브랜치 중간 커밋
11d1486의 상태였고 857fc98에서 고쳐져 릴리스된 적이 없다. fe289c7의
실제 코드는 날짜와 함께 dt="1"을 항상 보내 기간 조회가 이기고 있었다.
결론(breaking)은 그대로 두고 이전 동작 설명만 고쳤다.
account/market/stock만 손으로 나열하다 보니 order 그룹이 통째로 빠져
금현물 주문의 --order-type(GOLD_ORDER_TYPES) 2건이 집계에서 누락돼 있었다.
루트 cli부터 내려가도록 바꿔 그룹이 새로 생겨도 자동으로 잡히게 했고,
실제 측정값 55로 고정했다. 개수만 맞으면 통과하는 것을 막으려고 금현물
두 건은 이름으로도 못 박았다.

매핑 레지스트리 테스트에도 데코레이터에 물려 있는데 빠져 있던 13개를
채웠다 (GOLD_ORDER_TYPES, CREDIT_MARKET, AMT_QTY_TP_0_1, AMT_QTY_TP_1_2,
INTRADAY_INVESTOR, MIN_TIC_TP 등).
배포되는 주석에 task 번호와 docs/superpowers/plans/... 경로가 남아 있었다.
그 플랜 저장소는 비공개라 읽는 사람에게 아무 의미가 없다.

내용은 그대로 두고 참조만 걷어냈다. AMT_QTY_TP_0_1의 경우 사라진 C4 링크
자리에 왜 합치면 안 되는지(키 집합이 같아 조용히 통과하고 극성만 뒤집힌
값이 나간다)를 직접 적었다. 절대 합치지 말 것 경고는 전부 유지.
ka10030 시트가 500 코드를 "500만주이상"으로 적어 이웃 항목들의 산술
패턴과 어긋나는 탓에 500k라는 이름을 "보수적 선택"으로만 적어 뒀었다.
키움 공식 kwcli의 maps/arguments.csv가 rankings today-volume의 같은
필드를 9개 값 전부 우리와 동일하게(500k=500 포함) 매핑하고 있어 확인됐다.

CHANGELOG에도 같은 유보 문구가 있어 함께 고쳤다.
두 맵을 서로 바꿔 끼워도 기존 891개 테스트가 전부 통과했다. 공유 키의
전송값이 전부 같아 잘못된 값이 나가지는 않지만, 형제 쌍마다 분리를
고정해 온 이 브랜치의 방식과 어긋나 비어 있던 자리다.

유일하게 드러나는 차이인 ka10030 전용 200/300으로 양쪽을 고정했다.
rank volume은 200k/300k를 받아 200/300을 보내야 하고, elw broker-top은
같은 값을 exit 1로 거부해야 한다. 맵을 맞바꾸면 4개 전부 실패한다.
gejyn14 and others added 2 commits July 19, 2026 22:58
국내 지정가 주문의 ord_uv 가드 관련 서술을 교정했습니다. kwcli가 배포하는
order_price_policies.csv에 국내 지정가 --price required가 명시돼 있어,
"근거가 없다"는 기존 설명은 사실과 달랐습니다. 가드 자체는 다음 릴리스로
미룹니다 — 서버가 빈 값을 거부하므로 잘못된 주문이 나가지는 않습니다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
README의 금현물 매수 예제가 --type market을 쓰고 있었습니다. 금현물에는
시장가가 없어 v0.2.1부터 동작하지 않던 예제입니다. 지정가로 바꾸고 이유를
주석으로 남겼습니다.

Excel은 v2.9.0 이후 갱신이 없어 61개 커밋만큼 밀려 있었습니다.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@gejyn14
gejyn14 merged commit 6c76b21 into main Jul 19, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant