kwcli 1.0.0__tar.gz → 1.0.3__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. {kwcli-1.0.0 → kwcli-1.0.3}/PKG-INFO +42 -77
  2. {kwcli-1.0.0 → kwcli-1.0.3}/README.md +40 -75
  3. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/core/auth.py +13 -2
  4. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/core/client.py +13 -2
  5. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/core/errors.py +55 -2
  6. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/core/runtime.py +86 -11
  7. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/core/secrets.py +23 -0
  8. kwcli-1.0.3/kiwoom/core/settings.py +114 -0
  9. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/core/types.py +12 -0
  10. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/core/ws_client.py +2 -4
  11. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/README.md +36 -55
  12. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/commands/mapped.py +17 -1
  13. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/commands/spec.py +30 -2
  14. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/commands/streams.py +6 -0
  15. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/maps/README.md +5 -6
  16. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/maps/api_commands.csv +42 -42
  17. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/maps/arguments.csv +10 -10
  18. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/output.py +9 -3
  19. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/registry.py +2 -0
  20. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/setup.py +3 -18
  21. {kwcli-1.0.0 → kwcli-1.0.3}/pyproject.toml +1 -1
  22. kwcli-1.0.0/kiwoom/core/settings.py +0 -64
  23. {kwcli-1.0.0 → kwcli-1.0.3}/LICENSE.md +0 -0
  24. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/__init__.py +0 -0
  25. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/_data/kiwoom_api_spec.json +0 -0
  26. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/core/__init__.py +0 -0
  27. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/core/platform_paths.py +0 -0
  28. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/core/profiles.py +0 -0
  29. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/core/token_store.py +0 -0
  30. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/realtime/__init__.py +0 -0
  31. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/realtime/decoders.py +0 -0
  32. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/realtime/events.py +0 -0
  33. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/realtime/packets.py +0 -0
  34. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/realtime/schemas.py +0 -0
  35. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/realtime/stream.py +0 -0
  36. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom/specs.py +0 -0
  37. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/__init__.py +0 -0
  38. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/__main__.py +0 -0
  39. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/argument_maps.py +0 -0
  40. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/arguments.py +0 -0
  41. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/auth_context.py +0 -0
  42. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/banner.py +0 -0
  43. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/commands/__init__.py +0 -0
  44. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/commands/auth.py +0 -0
  45. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/commands/exchange.py +0 -0
  46. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/commands/groups.py +0 -0
  47. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/commands/orders.py +0 -0
  48. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/commands/stocks.py +0 -0
  49. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/doctor.py +0 -0
  50. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/errors.py +0 -0
  51. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/executor/__init__.py +0 -0
  52. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/executor/condition.py +0 -0
  53. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/executor/rest.py +0 -0
  54. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/executor/waits.py +0 -0
  55. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/executor/websocket.py +0 -0
  56. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/main.py +0 -0
  57. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/maps/coupled_arguments.csv +0 -0
  58. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/maps/order_confirmation_commands.csv +0 -0
  59. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/maps/order_confirmation_fields.csv +0 -0
  60. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/maps/order_price_policies.csv +0 -0
  61. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/maps/order_value_labels.csv +0 -0
  62. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/maps/positional_arguments.csv +0 -0
  63. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/order_confirmation.py +0 -0
  64. {kwcli-1.0.0 → kwcli-1.0.3}/kiwoom_cli/safety.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: kwcli
3
- Version: 1.0.0
3
+ Version: 1.0.3
4
4
  Summary: Kiwoom OpenAPI toolkit
5
5
  License-File: LICENSE.md
6
6
  Requires-Python: >=3.13
@@ -11,19 +11,13 @@ Requires-Dist: requests>=2.33.1
11
11
  Requires-Dist: websockets>=15.0.1
12
12
  Description-Content-Type: text/markdown
13
13
 
14
- # Kiwoom CLI
14
+ # 키움증권 CLI (Kiwoom CLI)
15
15
 
16
- `kiwoomcli`는 키움증권 OpenAPI를 위한 커맨드라인 클라이언트입니다. 터미널에서
17
- 키움 OpenAPI 데이터를 조회하고, 인증 상태를 관리하며, 안전장치가 적용된 주문
18
- 작업을 수행해야 하는 사람과 AI 에이전트를 위한 **탐색(discovery) 중심**의 운영
19
- 도구입니다.
16
+ `kiwoomcli`는 키움증권 OpenAPI를 위한 커맨드라인 클라이언트입니다. 터미널에서 키움 OpenAPI 데이터를 조회하고, 인증 상태를 관리하며, 안전장치가 적용된 주문 작업을 수행해야 하는 사람과 AI 에이전트를 위한 **탐색(discovery) 중심**의 운영 도구입니다.
20
17
 
21
18
  배포 패키지 이름은 `kwcli` 이며, 설치 후 사용하는 콘솔 명령은 `kiwoomcli` 입니다.
22
19
 
23
- > **키움증권 공식 프로젝트**
24
- > 본 프로젝트는 **키움증권(Kiwoom Securities)이 공식적으로 제공·관리**하는 도구입니다.
25
- > PyPI 패키지 [`kwcli`](https://pypi.org/project/kwcli/)는 키움증권이 게시한 공식 배포본입니다.
26
- > 키움 REST API 공식 포털: <https://openapi.kiwoom.com> (API 가이드: <https://openapi.kiwoom.com/guide/apiguide>)
20
+ > **키움증권 공식 프로젝트** 본 프로젝트는 **키움증권(Kiwoom Securities)이 공식적으로 제공·관리**하는 도구입니다. PyPI 패키지 [`kwcli`](https://pypi.org/project/kwcli/)는 키움증권이 게시한 공식 배포본입니다. 키움 REST API 공식 포털: <https://openapi.kiwoom.com> (API 가이드: <https://openapi.kiwoom.com/guide/apiguide>)
27
21
 
28
22
  ## 설치
29
23
 
@@ -43,9 +37,7 @@ kiwoomcli setup
43
37
 
44
38
  ## 빠른 시작
45
39
 
46
- 일상적인 흐름은 **먼저 탐색하고, 그다음 명령 하나를 실행**하는 것입니다. 어떤
47
- 명령이든 `-h`를 붙이면 실시간으로 매핑된 계약 정보(요약 / 동작 / 예시 / OpenAPI
48
- 매핑)를 볼 수 있습니다.
40
+ 일상적인 흐름은 **먼저 탐색하고, 그다음 명령 하나를 실행**하는 것입니다. 어떤 명령이든 `-h`를 붙이면 실시간으로 매핑된 계약 정보(요약 / 동작 / 예시 / OpenAPI 매핑)를 볼 수 있습니다.
49
41
 
50
42
  ```sh
51
43
  kiwoomcli setup # 온보딩: 별칭, demo/real, 키, 검증
@@ -60,19 +52,9 @@ kiwoomcli domestic orders buy --code 005930 --qty 1 --price 70000 --order-type l
60
52
 
61
53
  ## 인증과 프로필
62
54
 
63
- - `kiwoomcli setup`은 온보딩 초기화 명령입니다. 환경 사전 점검(OS 자격 증명
64
- 저장소 사용 가능 여부와 PATH 모호성 경고)을 수행하고, 계정 별칭을 만들며,
65
- `demo`/`real`을 선택하고, App Key / Secret을 저장한 뒤, 안전한 읽기 전용
66
- 호출로 검증하고, 현재 프로필을 지정한 다음 준비 상태 요약을 출력합니다.
67
- 비대화형 셸에서는 멈추지 않고 안내 메시지와 함께 실패합니다(`--mode` 전달).
68
- - `kiwoomcli doctor`는 읽기 전용 진단 명령입니다. 어떤 인증 컨텍스트가
69
- 선택됐는지(우선순위 `--profile`/`KIWOOM_PROFILE` > `--mode`/`KIWOOM_MODE` >
70
- 현재 프로필), 프로필별 자격 증명/토큰 상태, 권장 조치를 보여줍니다.
71
- - `--profile NAME`은 저장된 계정 별칭(OS 자격 증명 저장소의 App Key / Secret)을
72
- 사용합니다. `--mode demo|real`은 저장된 별칭을 쓰지 않으며, mode 단독 실행에는
73
- 환경변수가 필요합니다. 이때 환경변수 이름은 모드별로 다릅니다 —
74
- `real`은 `APP_KEY` / `APP_SECRET`, `demo`는 `APP_KEY_MOCK` / `APP_SECRET_MOCK`.
75
- 자격 증명이 없을 때는 진입 방식에 맞는 해결책을 함께 안내합니다.
55
+ - `kiwoomcli setup`은 온보딩 초기화 명령입니다. 환경을 먼저 점검(OS 자격 증명 저장소 사용 가능 여부, PATH 중복 경고)한 뒤, 계정 별칭 입력 → `demo`/`real` 선택 → App Key/Secret 저장 → 안전한 읽기 전용 호출로 검증 → 현재 프로필 지정 순서로 진행하고, 마지막에 준비 상태 요약을 보여줍니다. 비대화형 셸에서는 멈추지 않고 안내 메시지와 함께 실패합니다(`--mode` 전달).
56
+ - `kiwoomcli doctor`는 읽기 전용 진단 명령입니다. 어떤 인증 컨텍스트가 선택됐는지(우선순위 `--profile`/`KIWOOM_PROFILE` > `--mode`/`KIWOOM_MODE` > 현재 프로필), 프로필별 자격 증명/토큰 상태, 권장 조치를 보여줍니다.
57
+ - `--profile NAME`은 저장된 계정 별칭(OS 자격 증명 저장소의 App Key / Secret)을 사용합니다. `--mode demo|real`은 저장된 별칭을 쓰지 않으며, mode 단독 실행에는 환경변수가 필요합니다. 이때 환경변수 이름은 모드별로 다릅니다 — `real`은 `APP_KEY` / `APP_SECRET`, `demo`는 `APP_KEY_MOCK` / `APP_SECRET_MOCK`. 자격 증명이 없을 때는 진입 방식에 맞는 해결책을 함께 안내합니다.
76
58
 
77
59
  자주 쓰는 인증 명령:
78
60
 
@@ -88,13 +70,10 @@ kiwoomcli auth remove <alias>
88
70
  kiwoomcli auth export [--profile NAME | --mode demo|real] [--dir DIR] [--yes]
89
71
  ```
90
72
 
91
- - `auth clear`는 비밀 정보만 삭제합니다(기본은 토큰 캐시, `--all`을 주면 OS에
92
- 저장된 App Key / Secret까지). 별칭 등록 자체는 유지됩니다.
93
- - `auth remove <alias>`는 계정을 완전히 등록 해제합니다(프로필 항목, 토큰 캐시,
94
- 저장된 자격 증명 모두).
73
+ - `auth clear`는 비밀 정보만 삭제합니다(기본은 토큰 캐시, `--all`을 주면 OS에 저장된 App Key / Secret까지). 별칭 등록 자체는 유지됩니다.
74
+ - `auth remove <alias>`는 계정을 완전히 등록 해제합니다(프로필 항목, 토큰 캐시, 저장된 자격 증명 모두).
95
75
  - `auth revoke`는 서버에서 현재 토큰을 폐기하고 로컬 토큰 캐시도 삭제합니다.
96
- - `auth export`는 저장된 App Key/Secret을 화면에 출력하지 않고 `.env` 파일로
97
- 내보냅니다. 비대화형 환경에서는 `--yes`가 필요합니다.
76
+ - `auth export`는 저장된 App Key/Secret을 화면에 출력하지 않고 `.env` 파일로 내보냅니다. 비대화형 환경에서는 `--yes`가 필요합니다.
98
77
 
99
78
  ## 명령 그룹
100
79
 
@@ -107,8 +86,7 @@ kiwoomcli spec groups [--format pretty|json|yaml]
107
86
  kiwoomcli spec apis [--group <text>] [--limit N] [--format pretty|json|yaml]
108
87
  ```
109
88
 
110
- 국내 리소스 명령은 주제별 그룹으로 나뉩니다. 대표 예시는 아래와 같습니다(어떤
111
- 명령이든 `-h`를 붙이면 전체 옵션 계약을 볼 수 있습니다).
89
+ 국내 리소스 명령은 주제별 그룹으로 나뉩니다. 대표 예시는 아래와 같습니다(어떤 명령이든 `-h`를 붙이면 전체 옵션 계약을 볼 수 있습니다).
112
90
 
113
91
  | 그룹 | 예시 |
114
92
  | --- | --- |
@@ -144,13 +122,9 @@ kiwoomcli spec apis [--group <text>] [--limit N] [--format pretty|json|yaml]
144
122
  | `exchange` | `kiwoomcli overseas exchange rate --direction krw-to-usd` |
145
123
  | `streams` | `kiwoomcli overseas streams trades --exchange NASDAQ --code AAPL --count 1` |
146
124
 
147
- 전체 API 337개(OAuth 2, 국내주식 206, 미국주식 129)가 로컬 스펙과 CLI
148
- 맵에 포함됩니다. 정확한 옵션은 각 명령의 `-h` 출력으로 확인하세요.
125
+ 전체 API 337개(OAuth 2, 국내주식 206, 미국주식 129)가 로컬 스펙과 CLI 맵에 포함됩니다. 정확한 옵션은 각 명령의 `-h` 출력으로 확인하세요.
149
126
 
150
- 미국주식 `--exchange` 값의 대소문자는 명령별 API 계약을 따릅니다. 종목·시세·
151
- 호가·캔들·계좌·스트림은 `AMEX|NASDAQ|NYSE`, 순위는
152
- `all|nyse|nasdaq|amex`, 업종의 `period-returns`는
153
- `ALL|NYSE|AMEX|NASDAQ`을 사용합니다. 항상 해당 명령의 `-h`를 확인하세요.
127
+ 미국주식 `--exchange` 값의 대소문자는 명령별 API 계약을 따릅니다. 종목·시세· 호가·캔들·계좌·스트림은 `AMEX|NASDAQ|NYSE`, 순위는 `all|nyse|nasdaq|amex`, 업종의 `period-returns`는 `ALL|NYSE|AMEX|NASDAQ`을 사용합니다. 항상 해당 명령의 `-h`를 확인하세요.
154
128
 
155
129
  ## 출력 형식
156
130
 
@@ -167,34 +141,25 @@ kiwoomcli spec apis [--group <text>] [--limit N] [--format pretty|json|yaml]
167
141
  - `json`은 에이전트가 파싱하기 좋은 한 줄 압축 출력입니다.
168
142
  - `jsonl`은 한 줄에 JSON 레코드 하나씩 출력합니다(목록 행이나 스트림에 유용).
169
143
  - `yaml`은 YAML 형식으로 출력합니다.
170
- - `--pages`는 REST 연속조회 페이지 수입니다. 기본값은 1이며, 0은 서버가
171
- 제공하는 모든 페이지를 조회합니다. `spec` 명령에는 적용되지 않습니다.
144
+ - `--pages`는 REST 연속조회 페이지 수입니다. 기본값은 1이며, 0은 서버가 제공하는 모든 페이지를 조회합니다. `spec` 명령에는 적용되지 않습니다.
172
145
  - REST 명령의 `--named`는 응답 필드 코드를 스펙의 한글명으로 변환합니다.
173
146
 
174
- 안전장치가 적용된 계좌/주문 조회와 스트림에서는 출력 계층이 계좌 식별자를
175
- 마스킹합니다. 다만 주문번호(`ord_no`, `orig_ord_no` 등)는 `orders modify`/`cancel`
176
- 에 필요하므로 **마스킹하지 않습니다**. 마스킹은 `pretty`, `json`, `jsonl`, `yaml`
177
- 전반에서 동일하게 적용됩니다.
147
+ 안전장치가 적용된 계좌/주문 조회와 스트림에서는 출력 계층이 계좌 식별자를 마스킹합니다. 다만 주문번호(`ord_no`, `orig_ord_no` 등)는 `orders modify`/`cancel` 에 필요하므로 **마스킹하지 않습니다**. 마스킹은 `pretty`, `json`, `jsonl`, `yaml` 전반에서 동일하게 적용됩니다.
178
148
 
179
149
  ## 스트리밍 (WebSocket)
180
150
 
181
- 스트림 명령은 포그라운드에서 실행되며 키움 서버 메시지를 출력합니다. 유한한
182
- 실행을 원하면 `--count`/`--duration`을 사용하세요.
151
+ 스트림 명령은 포그라운드에서 실행되며 키움 서버 메시지를 출력합니다. 유한한 실행을 원하면 `--count`/`--duration`을 사용하세요.
183
152
 
184
153
  ```sh
185
154
  kiwoomcli domestic streams trades --code 005930 --count 1 --named --format json
186
155
  kiwoomcli overseas streams trades --exchange NASDAQ --code AAPL --count 1 --named --format json
187
156
  ```
188
157
 
189
- - `--count`는 `REAL` 데이터 메시지만 셉니다. `REG`/`REMOVE`/`SYSTEM`은 제어
190
- 메시지입니다.
191
- - `--check`는 유한한 등록/수집을 수행하며, `REAL` 틱이 오지 않아도 정상 종료
192
- 합니다.
193
- - 스트림 명령의 `--named`는 키움 스펙 기반 내장 스키마로 `REAL` 프레임 FID를
194
- 변환합니다. 알 수 없는 FID는 `unknown` 아래에 보존됩니다.
158
+ - `--count`는 `REAL` 데이터 메시지만 셉니다. `REG`/`REMOVE`/`SYSTEM`은 제어 메시지입니다.
159
+ - `--check`는 유한한 등록/수집을 수행하며, `REAL` 틱이 오지 않아도 정상 종료합니다.
160
+ - 스트림 명령의 `--named`는 키움 스펙 기반 내장 스키마로 `REAL` 프레임 FID를 변환합니다. 알 수 없는 FID는 `unknown` 아래에 보존됩니다.
195
161
 
196
- 장시간 구독을 저장하려면 파일로 출력하고 OS 도구로 프로세스를 백그라운드로
197
- 돌리세요(CLI는 자체 작업 관리자를 제공하지 않습니다).
162
+ 장시간 구독을 저장하려면 파일로 출력하고 OS 도구로 프로세스를 백그라운드로 돌리세요(CLI는 자체 작업 관리자를 제공하지 않습니다).
198
163
 
199
164
  ```sh
200
165
  # 계속 수신하며 이벤트를 파일에 추가 기록
@@ -207,30 +172,21 @@ nohup kiwoomcli domestic streams trades --codes 005930,000660 --watch --output t
207
172
  Start-Process kiwoomcli -ArgumentList 'domestic streams trades --codes 005930,000660 --watch --output trades.jsonl'
208
173
  ```
209
174
 
210
- 조건검색식의 생성과 수정은 키움 영웅문 HTS에서 합니다. CLI는 HTS에 이미 저장된
211
- 조건식을 목록 조회, 선택, 조회, 구독, 해제만 합니다.
175
+ 조건검색식의 생성과 수정은 키움 영웅문 HTS에서 합니다. CLI는 HTS에 이미 저장된 조건식을 목록 조회, 선택, 조회, 구독, 해제만 합니다.
212
176
 
213
177
  ## 주문 안전장치
214
178
 
215
- 국내·미국주식 주문 쓰기 명령(`orders buy/sell/modify/cancel`,
216
- `credit-*`, `gold-*`)과 미국주식 환전 신청(`overseas exchange request`)에는
217
- 안전장치가 적용됩니다.
179
+ 국내·미국주식 주문 쓰기 명령(`orders buy/sell/modify/cancel`, `credit-*`, `gold-*`)과 미국주식 환전 신청(`overseas exchange request`)에는 안전장치가 적용됩니다.
218
180
 
219
- - `--confirm`이 없으면 짧은 미전송 주문 요약을 출력하고 주문 API를 호출하지
220
- 않습니다.
181
+ - `--confirm`이 없으면 짧은 미전송 주문 요약을 출력하고 주문 API를 호출하지 않습니다.
221
182
  - `--confirm`이 있으면 실제 엔드포인트로 전송합니다.
222
- - `overseas exchange request`도 `--confirm`이 없으면 미전송 환전 확인만
223
- 출력하며, `--confirm`이 있어야 실제 환전 API를 호출합니다.
224
- - 주문 유형별 가격 규칙은 전송 경로 이전에 검증됩니다. 예를 들어
225
- `--order-type limit`에는 `--price`가 필요하고, `--order-type market`에는
226
- `--price`를 넣으면 안 됩니다. 잘못된 주문 식별자는 전송 전에 보고됩니다.
227
- - 주문 쓰기는 `--profile`/`--mode`로 인증 대상을 선택합니다. 먼저 데모(모의투자)
228
- 서버에서 검증하세요.
183
+ - `overseas exchange request`도 `--confirm`이 없으면 미전송 환전 확인만 출력하며, `--confirm`이 있어야 실제 환전 API를 호출합니다.
184
+ - 주문 유형별 가격 규칙은 전송 경로 이전에 검증됩니다. 예를 들어 `--order-type limit`에는 `--price`가 필요하고, `--order-type market`에는 `--price`를 넣으면 안 됩니다. 잘못된 주문 식별자는 전송 전에 보고됩니다.
185
+ - 주문 쓰기는 `--profile`/`--mode`로 인증 대상을 선택합니다. 먼저 데모(모의투자) 서버에서 검증하세요.
229
186
 
230
187
  ## 용어
231
188
 
232
- 안정적인 사용자 대상 옵션 이름이 키움 원본 필드명을 숨깁니다. 구체적인 코드
233
- 종류는 명령 맥락에 따라 결정됩니다.
189
+ 각 옵션 이름은 키움 원본 필드명 대신 안정적인 사용자 대상 이름으로 노출됩니다. 구체적으로 어떤 코드를 넣어야 하는지는 명령마다 다릅니다.
234
190
 
235
191
  | 개념 | 옵션 | 예시 |
236
192
  | --- | --- | --- |
@@ -253,7 +209,16 @@ Start-Process kiwoomcli -ArgumentList 'domestic streams trades --codes 005930,00
253
209
  ## 안전 유의사항
254
210
 
255
211
  - 개발과 검증 단계에서는 `demo` 모드를 우선 사용하세요.
256
- - 자격 증명, 토큰, 계좌번호, 정제되지 않은 실제 호출 출력은 절대 로그로 남기거나
257
- 커밋하지 마세요.
258
- - 자격 증명·네트워크·계좌 안전 제약으로 실제 호출이 막히면, CLI는 결과를
259
- 지어내지 않고 차단됨/미실행으로 보고합니다.
212
+ - 자격 증명, 토큰, 계좌번호, 정제되지 않은 실제 호출 출력은 절대 로그로 남기거나 커밋하지 마세요.
213
+ - 자격 증명·네트워크·계좌 안전 제약으로 실제 호출이 막히면, CLI는 결과를 지어내지 않고 차단됨/미실행으로 보고합니다.
214
+
215
+ ## 알려진 제약
216
+
217
+ - 국내 주문 취소(`domestic orders cancel`)는 `--qty`로 수량 일부만 취소할 수 있지만, 해외 주문 취소(`overseas orders cancel`)는 **전량만** 가능합니다. 해외 주문 정정(`overseas orders modify`)은 수량은 바꿀 수 없고 **가격만** 바꿀 수 있습니다(원주문이 STOP 주문이면 `--stop-price` 필수).
218
+ - `domestic orders list-open`의 `--exchange`는 주문용 값인 `SOR`을 받지 않습니다 — `ALL`로 조회하세요. `overseas accounts open-orders`는 `--exchange`를 `--code` 없이 받지 않습니다(서버 제약) — 종목을 지정하지 않는 미체결 조회는 거래소 없이 전체로 조회하세요.
219
+ - 너무 짧은 간격으로 연속 조회하면 유량 제한(429) 오류가 날 수 있습니다. 반복 호출에는 간격을 두세요.
220
+ - 모의투자(demo) 계좌에서는 환율 조회, 주문가능금액 조회가 거절됩니다(실전 계좌에서만 가능).
221
+ - 계좌마다 거래할 수 있는 시장이 다를 수 있습니다. 해당 계좌가 다루지 않는 시장은 주문이 거절되거나 조회 결과가 오류 없이 빈 목록으로 돌아올 수 있으므로, 결과가 비어 있다면 먼저 `--profile`/`--mode`로 선택한 계좌를 확인하세요. 장 운영 시간이 아닐 때 국내 주문을 시도하면 이와는 다른 이유(장 종료)로 거절됩니다.
222
+ - 현재가 조회(`domestic quotes price`) 응답에는 전일대비 값이 직접 포함되어 있지 않습니다 — 현재가와 전일 종가의 차이로 직접 계산해야 합니다.
223
+ - `auth list`/`auth status`는 `--format json`을 지원하지 않고 사람이 읽는 텍스트만 출력합니다.
224
+ - 일부 계좌 조회 명령(`accounts valuation`/`accounts holdings`)에는 `--named`가 없어, 응답 필드가 키움 원본 코드로 나옵니다.
@@ -1,16 +1,10 @@
1
- # Kiwoom CLI
1
+ # 키움증권 CLI (Kiwoom CLI)
2
2
 
3
- `kiwoomcli`는 키움증권 OpenAPI를 위한 커맨드라인 클라이언트입니다. 터미널에서
4
- 키움 OpenAPI 데이터를 조회하고, 인증 상태를 관리하며, 안전장치가 적용된 주문
5
- 작업을 수행해야 하는 사람과 AI 에이전트를 위한 **탐색(discovery) 중심**의 운영
6
- 도구입니다.
3
+ `kiwoomcli`는 키움증권 OpenAPI를 위한 커맨드라인 클라이언트입니다. 터미널에서 키움 OpenAPI 데이터를 조회하고, 인증 상태를 관리하며, 안전장치가 적용된 주문 작업을 수행해야 하는 사람과 AI 에이전트를 위한 **탐색(discovery) 중심**의 운영 도구입니다.
7
4
 
8
5
  배포 패키지 이름은 `kwcli` 이며, 설치 후 사용하는 콘솔 명령은 `kiwoomcli` 입니다.
9
6
 
10
- > **키움증권 공식 프로젝트**
11
- > 본 프로젝트는 **키움증권(Kiwoom Securities)이 공식적으로 제공·관리**하는 도구입니다.
12
- > PyPI 패키지 [`kwcli`](https://pypi.org/project/kwcli/)는 키움증권이 게시한 공식 배포본입니다.
13
- > 키움 REST API 공식 포털: <https://openapi.kiwoom.com> (API 가이드: <https://openapi.kiwoom.com/guide/apiguide>)
7
+ > **키움증권 공식 프로젝트** 본 프로젝트는 **키움증권(Kiwoom Securities)이 공식적으로 제공·관리**하는 도구입니다. PyPI 패키지 [`kwcli`](https://pypi.org/project/kwcli/)는 키움증권이 게시한 공식 배포본입니다. 키움 REST API 공식 포털: <https://openapi.kiwoom.com> (API 가이드: <https://openapi.kiwoom.com/guide/apiguide>)
14
8
 
15
9
  ## 설치
16
10
 
@@ -30,9 +24,7 @@ kiwoomcli setup
30
24
 
31
25
  ## 빠른 시작
32
26
 
33
- 일상적인 흐름은 **먼저 탐색하고, 그다음 명령 하나를 실행**하는 것입니다. 어떤
34
- 명령이든 `-h`를 붙이면 실시간으로 매핑된 계약 정보(요약 / 동작 / 예시 / OpenAPI
35
- 매핑)를 볼 수 있습니다.
27
+ 일상적인 흐름은 **먼저 탐색하고, 그다음 명령 하나를 실행**하는 것입니다. 어떤 명령이든 `-h`를 붙이면 실시간으로 매핑된 계약 정보(요약 / 동작 / 예시 / OpenAPI 매핑)를 볼 수 있습니다.
36
28
 
37
29
  ```sh
38
30
  kiwoomcli setup # 온보딩: 별칭, demo/real, 키, 검증
@@ -47,19 +39,9 @@ kiwoomcli domestic orders buy --code 005930 --qty 1 --price 70000 --order-type l
47
39
 
48
40
  ## 인증과 프로필
49
41
 
50
- - `kiwoomcli setup`은 온보딩 초기화 명령입니다. 환경 사전 점검(OS 자격 증명
51
- 저장소 사용 가능 여부와 PATH 모호성 경고)을 수행하고, 계정 별칭을 만들며,
52
- `demo`/`real`을 선택하고, App Key / Secret을 저장한 뒤, 안전한 읽기 전용
53
- 호출로 검증하고, 현재 프로필을 지정한 다음 준비 상태 요약을 출력합니다.
54
- 비대화형 셸에서는 멈추지 않고 안내 메시지와 함께 실패합니다(`--mode` 전달).
55
- - `kiwoomcli doctor`는 읽기 전용 진단 명령입니다. 어떤 인증 컨텍스트가
56
- 선택됐는지(우선순위 `--profile`/`KIWOOM_PROFILE` > `--mode`/`KIWOOM_MODE` >
57
- 현재 프로필), 프로필별 자격 증명/토큰 상태, 권장 조치를 보여줍니다.
58
- - `--profile NAME`은 저장된 계정 별칭(OS 자격 증명 저장소의 App Key / Secret)을
59
- 사용합니다. `--mode demo|real`은 저장된 별칭을 쓰지 않으며, mode 단독 실행에는
60
- 환경변수가 필요합니다. 이때 환경변수 이름은 모드별로 다릅니다 —
61
- `real`은 `APP_KEY` / `APP_SECRET`, `demo`는 `APP_KEY_MOCK` / `APP_SECRET_MOCK`.
62
- 자격 증명이 없을 때는 진입 방식에 맞는 해결책을 함께 안내합니다.
42
+ - `kiwoomcli setup`은 온보딩 초기화 명령입니다. 환경을 먼저 점검(OS 자격 증명 저장소 사용 가능 여부, PATH 중복 경고)한 뒤, 계정 별칭 입력 → `demo`/`real` 선택 → App Key/Secret 저장 → 안전한 읽기 전용 호출로 검증 → 현재 프로필 지정 순서로 진행하고, 마지막에 준비 상태 요약을 보여줍니다. 비대화형 셸에서는 멈추지 않고 안내 메시지와 함께 실패합니다(`--mode` 전달).
43
+ - `kiwoomcli doctor`는 읽기 전용 진단 명령입니다. 어떤 인증 컨텍스트가 선택됐는지(우선순위 `--profile`/`KIWOOM_PROFILE` > `--mode`/`KIWOOM_MODE` > 현재 프로필), 프로필별 자격 증명/토큰 상태, 권장 조치를 보여줍니다.
44
+ - `--profile NAME`은 저장된 계정 별칭(OS 자격 증명 저장소의 App Key / Secret)을 사용합니다. `--mode demo|real`은 저장된 별칭을 쓰지 않으며, mode 단독 실행에는 환경변수가 필요합니다. 이때 환경변수 이름은 모드별로 다릅니다 — `real`은 `APP_KEY` / `APP_SECRET`, `demo`는 `APP_KEY_MOCK` / `APP_SECRET_MOCK`. 자격 증명이 없을 때는 진입 방식에 맞는 해결책을 함께 안내합니다.
63
45
 
64
46
  자주 쓰는 인증 명령:
65
47
 
@@ -75,13 +57,10 @@ kiwoomcli auth remove <alias>
75
57
  kiwoomcli auth export [--profile NAME | --mode demo|real] [--dir DIR] [--yes]
76
58
  ```
77
59
 
78
- - `auth clear`는 비밀 정보만 삭제합니다(기본은 토큰 캐시, `--all`을 주면 OS에
79
- 저장된 App Key / Secret까지). 별칭 등록 자체는 유지됩니다.
80
- - `auth remove <alias>`는 계정을 완전히 등록 해제합니다(프로필 항목, 토큰 캐시,
81
- 저장된 자격 증명 모두).
60
+ - `auth clear`는 비밀 정보만 삭제합니다(기본은 토큰 캐시, `--all`을 주면 OS에 저장된 App Key / Secret까지). 별칭 등록 자체는 유지됩니다.
61
+ - `auth remove <alias>`는 계정을 완전히 등록 해제합니다(프로필 항목, 토큰 캐시, 저장된 자격 증명 모두).
82
62
  - `auth revoke`는 서버에서 현재 토큰을 폐기하고 로컬 토큰 캐시도 삭제합니다.
83
- - `auth export`는 저장된 App Key/Secret을 화면에 출력하지 않고 `.env` 파일로
84
- 내보냅니다. 비대화형 환경에서는 `--yes`가 필요합니다.
63
+ - `auth export`는 저장된 App Key/Secret을 화면에 출력하지 않고 `.env` 파일로 내보냅니다. 비대화형 환경에서는 `--yes`가 필요합니다.
85
64
 
86
65
  ## 명령 그룹
87
66
 
@@ -94,8 +73,7 @@ kiwoomcli spec groups [--format pretty|json|yaml]
94
73
  kiwoomcli spec apis [--group <text>] [--limit N] [--format pretty|json|yaml]
95
74
  ```
96
75
 
97
- 국내 리소스 명령은 주제별 그룹으로 나뉩니다. 대표 예시는 아래와 같습니다(어떤
98
- 명령이든 `-h`를 붙이면 전체 옵션 계약을 볼 수 있습니다).
76
+ 국내 리소스 명령은 주제별 그룹으로 나뉩니다. 대표 예시는 아래와 같습니다(어떤 명령이든 `-h`를 붙이면 전체 옵션 계약을 볼 수 있습니다).
99
77
 
100
78
  | 그룹 | 예시 |
101
79
  | --- | --- |
@@ -131,13 +109,9 @@ kiwoomcli spec apis [--group <text>] [--limit N] [--format pretty|json|yaml]
131
109
  | `exchange` | `kiwoomcli overseas exchange rate --direction krw-to-usd` |
132
110
  | `streams` | `kiwoomcli overseas streams trades --exchange NASDAQ --code AAPL --count 1` |
133
111
 
134
- 전체 API 337개(OAuth 2, 국내주식 206, 미국주식 129)가 로컬 스펙과 CLI
135
- 맵에 포함됩니다. 정확한 옵션은 각 명령의 `-h` 출력으로 확인하세요.
112
+ 전체 API 337개(OAuth 2, 국내주식 206, 미국주식 129)가 로컬 스펙과 CLI 맵에 포함됩니다. 정확한 옵션은 각 명령의 `-h` 출력으로 확인하세요.
136
113
 
137
- 미국주식 `--exchange` 값의 대소문자는 명령별 API 계약을 따릅니다. 종목·시세·
138
- 호가·캔들·계좌·스트림은 `AMEX|NASDAQ|NYSE`, 순위는
139
- `all|nyse|nasdaq|amex`, 업종의 `period-returns`는
140
- `ALL|NYSE|AMEX|NASDAQ`을 사용합니다. 항상 해당 명령의 `-h`를 확인하세요.
114
+ 미국주식 `--exchange` 값의 대소문자는 명령별 API 계약을 따릅니다. 종목·시세· 호가·캔들·계좌·스트림은 `AMEX|NASDAQ|NYSE`, 순위는 `all|nyse|nasdaq|amex`, 업종의 `period-returns`는 `ALL|NYSE|AMEX|NASDAQ`을 사용합니다. 항상 해당 명령의 `-h`를 확인하세요.
141
115
 
142
116
  ## 출력 형식
143
117
 
@@ -154,34 +128,25 @@ kiwoomcli spec apis [--group <text>] [--limit N] [--format pretty|json|yaml]
154
128
  - `json`은 에이전트가 파싱하기 좋은 한 줄 압축 출력입니다.
155
129
  - `jsonl`은 한 줄에 JSON 레코드 하나씩 출력합니다(목록 행이나 스트림에 유용).
156
130
  - `yaml`은 YAML 형식으로 출력합니다.
157
- - `--pages`는 REST 연속조회 페이지 수입니다. 기본값은 1이며, 0은 서버가
158
- 제공하는 모든 페이지를 조회합니다. `spec` 명령에는 적용되지 않습니다.
131
+ - `--pages`는 REST 연속조회 페이지 수입니다. 기본값은 1이며, 0은 서버가 제공하는 모든 페이지를 조회합니다. `spec` 명령에는 적용되지 않습니다.
159
132
  - REST 명령의 `--named`는 응답 필드 코드를 스펙의 한글명으로 변환합니다.
160
133
 
161
- 안전장치가 적용된 계좌/주문 조회와 스트림에서는 출력 계층이 계좌 식별자를
162
- 마스킹합니다. 다만 주문번호(`ord_no`, `orig_ord_no` 등)는 `orders modify`/`cancel`
163
- 에 필요하므로 **마스킹하지 않습니다**. 마스킹은 `pretty`, `json`, `jsonl`, `yaml`
164
- 전반에서 동일하게 적용됩니다.
134
+ 안전장치가 적용된 계좌/주문 조회와 스트림에서는 출력 계층이 계좌 식별자를 마스킹합니다. 다만 주문번호(`ord_no`, `orig_ord_no` 등)는 `orders modify`/`cancel` 에 필요하므로 **마스킹하지 않습니다**. 마스킹은 `pretty`, `json`, `jsonl`, `yaml` 전반에서 동일하게 적용됩니다.
165
135
 
166
136
  ## 스트리밍 (WebSocket)
167
137
 
168
- 스트림 명령은 포그라운드에서 실행되며 키움 서버 메시지를 출력합니다. 유한한
169
- 실행을 원하면 `--count`/`--duration`을 사용하세요.
138
+ 스트림 명령은 포그라운드에서 실행되며 키움 서버 메시지를 출력합니다. 유한한 실행을 원하면 `--count`/`--duration`을 사용하세요.
170
139
 
171
140
  ```sh
172
141
  kiwoomcli domestic streams trades --code 005930 --count 1 --named --format json
173
142
  kiwoomcli overseas streams trades --exchange NASDAQ --code AAPL --count 1 --named --format json
174
143
  ```
175
144
 
176
- - `--count`는 `REAL` 데이터 메시지만 셉니다. `REG`/`REMOVE`/`SYSTEM`은 제어
177
- 메시지입니다.
178
- - `--check`는 유한한 등록/수집을 수행하며, `REAL` 틱이 오지 않아도 정상 종료
179
- 합니다.
180
- - 스트림 명령의 `--named`는 키움 스펙 기반 내장 스키마로 `REAL` 프레임 FID를
181
- 변환합니다. 알 수 없는 FID는 `unknown` 아래에 보존됩니다.
145
+ - `--count`는 `REAL` 데이터 메시지만 셉니다. `REG`/`REMOVE`/`SYSTEM`은 제어 메시지입니다.
146
+ - `--check`는 유한한 등록/수집을 수행하며, `REAL` 틱이 오지 않아도 정상 종료합니다.
147
+ - 스트림 명령의 `--named`는 키움 스펙 기반 내장 스키마로 `REAL` 프레임 FID를 변환합니다. 알 수 없는 FID는 `unknown` 아래에 보존됩니다.
182
148
 
183
- 장시간 구독을 저장하려면 파일로 출력하고 OS 도구로 프로세스를 백그라운드로
184
- 돌리세요(CLI는 자체 작업 관리자를 제공하지 않습니다).
149
+ 장시간 구독을 저장하려면 파일로 출력하고 OS 도구로 프로세스를 백그라운드로 돌리세요(CLI는 자체 작업 관리자를 제공하지 않습니다).
185
150
 
186
151
  ```sh
187
152
  # 계속 수신하며 이벤트를 파일에 추가 기록
@@ -194,30 +159,21 @@ nohup kiwoomcli domestic streams trades --codes 005930,000660 --watch --output t
194
159
  Start-Process kiwoomcli -ArgumentList 'domestic streams trades --codes 005930,000660 --watch --output trades.jsonl'
195
160
  ```
196
161
 
197
- 조건검색식의 생성과 수정은 키움 영웅문 HTS에서 합니다. CLI는 HTS에 이미 저장된
198
- 조건식을 목록 조회, 선택, 조회, 구독, 해제만 합니다.
162
+ 조건검색식의 생성과 수정은 키움 영웅문 HTS에서 합니다. CLI는 HTS에 이미 저장된 조건식을 목록 조회, 선택, 조회, 구독, 해제만 합니다.
199
163
 
200
164
  ## 주문 안전장치
201
165
 
202
- 국내·미국주식 주문 쓰기 명령(`orders buy/sell/modify/cancel`,
203
- `credit-*`, `gold-*`)과 미국주식 환전 신청(`overseas exchange request`)에는
204
- 안전장치가 적용됩니다.
166
+ 국내·미국주식 주문 쓰기 명령(`orders buy/sell/modify/cancel`, `credit-*`, `gold-*`)과 미국주식 환전 신청(`overseas exchange request`)에는 안전장치가 적용됩니다.
205
167
 
206
- - `--confirm`이 없으면 짧은 미전송 주문 요약을 출력하고 주문 API를 호출하지
207
- 않습니다.
168
+ - `--confirm`이 없으면 짧은 미전송 주문 요약을 출력하고 주문 API를 호출하지 않습니다.
208
169
  - `--confirm`이 있으면 실제 엔드포인트로 전송합니다.
209
- - `overseas exchange request`도 `--confirm`이 없으면 미전송 환전 확인만
210
- 출력하며, `--confirm`이 있어야 실제 환전 API를 호출합니다.
211
- - 주문 유형별 가격 규칙은 전송 경로 이전에 검증됩니다. 예를 들어
212
- `--order-type limit`에는 `--price`가 필요하고, `--order-type market`에는
213
- `--price`를 넣으면 안 됩니다. 잘못된 주문 식별자는 전송 전에 보고됩니다.
214
- - 주문 쓰기는 `--profile`/`--mode`로 인증 대상을 선택합니다. 먼저 데모(모의투자)
215
- 서버에서 검증하세요.
170
+ - `overseas exchange request`도 `--confirm`이 없으면 미전송 환전 확인만 출력하며, `--confirm`이 있어야 실제 환전 API를 호출합니다.
171
+ - 주문 유형별 가격 규칙은 전송 경로 이전에 검증됩니다. 예를 들어 `--order-type limit`에는 `--price`가 필요하고, `--order-type market`에는 `--price`를 넣으면 안 됩니다. 잘못된 주문 식별자는 전송 전에 보고됩니다.
172
+ - 주문 쓰기는 `--profile`/`--mode`로 인증 대상을 선택합니다. 먼저 데모(모의투자) 서버에서 검증하세요.
216
173
 
217
174
  ## 용어
218
175
 
219
- 안정적인 사용자 대상 옵션 이름이 키움 원본 필드명을 숨깁니다. 구체적인 코드
220
- 종류는 명령 맥락에 따라 결정됩니다.
176
+ 각 옵션 이름은 키움 원본 필드명 대신 안정적인 사용자 대상 이름으로 노출됩니다. 구체적으로 어떤 코드를 넣어야 하는지는 명령마다 다릅니다.
221
177
 
222
178
  | 개념 | 옵션 | 예시 |
223
179
  | --- | --- | --- |
@@ -240,7 +196,16 @@ Start-Process kiwoomcli -ArgumentList 'domestic streams trades --codes 005930,00
240
196
  ## 안전 유의사항
241
197
 
242
198
  - 개발과 검증 단계에서는 `demo` 모드를 우선 사용하세요.
243
- - 자격 증명, 토큰, 계좌번호, 정제되지 않은 실제 호출 출력은 절대 로그로 남기거나
244
- 커밋하지 마세요.
245
- - 자격 증명·네트워크·계좌 안전 제약으로 실제 호출이 막히면, CLI는 결과를
246
- 지어내지 않고 차단됨/미실행으로 보고합니다.
199
+ - 자격 증명, 토큰, 계좌번호, 정제되지 않은 실제 호출 출력은 절대 로그로 남기거나 커밋하지 마세요.
200
+ - 자격 증명·네트워크·계좌 안전 제약으로 실제 호출이 막히면, CLI는 결과를 지어내지 않고 차단됨/미실행으로 보고합니다.
201
+
202
+ ## 알려진 제약
203
+
204
+ - 국내 주문 취소(`domestic orders cancel`)는 `--qty`로 수량 일부만 취소할 수 있지만, 해외 주문 취소(`overseas orders cancel`)는 **전량만** 가능합니다. 해외 주문 정정(`overseas orders modify`)은 수량은 바꿀 수 없고 **가격만** 바꿀 수 있습니다(원주문이 STOP 주문이면 `--stop-price` 필수).
205
+ - `domestic orders list-open`의 `--exchange`는 주문용 값인 `SOR`을 받지 않습니다 — `ALL`로 조회하세요. `overseas accounts open-orders`는 `--exchange`를 `--code` 없이 받지 않습니다(서버 제약) — 종목을 지정하지 않는 미체결 조회는 거래소 없이 전체로 조회하세요.
206
+ - 너무 짧은 간격으로 연속 조회하면 유량 제한(429) 오류가 날 수 있습니다. 반복 호출에는 간격을 두세요.
207
+ - 모의투자(demo) 계좌에서는 환율 조회, 주문가능금액 조회가 거절됩니다(실전 계좌에서만 가능).
208
+ - 계좌마다 거래할 수 있는 시장이 다를 수 있습니다. 해당 계좌가 다루지 않는 시장은 주문이 거절되거나 조회 결과가 오류 없이 빈 목록으로 돌아올 수 있으므로, 결과가 비어 있다면 먼저 `--profile`/`--mode`로 선택한 계좌를 확인하세요. 장 운영 시간이 아닐 때 국내 주문을 시도하면 이와는 다른 이유(장 종료)로 거절됩니다.
209
+ - 현재가 조회(`domestic quotes price`) 응답에는 전일대비 값이 직접 포함되어 있지 않습니다 — 현재가와 전일 종가의 차이로 직접 계산해야 합니다.
210
+ - `auth list`/`auth status`는 `--format json`을 지원하지 않고 사람이 읽는 텍스트만 출력합니다.
211
+ - 일부 계좌 조회 명령(`accounts valuation`/`accounts holdings`)에는 `--named`가 없어, 응답 필드가 키움 원본 코드로 나옵니다.
@@ -328,8 +328,19 @@ class KiwoomAuth:
328
328
 
329
329
  @staticmethod
330
330
  def _credential_fingerprint(credentials: CredentialSet) -> str:
331
- raw = f"{credentials.appkey}:{credentials.secretkey}".encode("utf-8")
332
- return hashlib.sha256(raw).hexdigest()
331
+ return credential_fingerprint(credentials)
332
+
333
+
334
+ def credential_fingerprint(credentials: CredentialSet) -> str:
335
+ """Non-reversible identity of an appkey/secret pair.
336
+
337
+ Stored beside a cached token so a token issued for one key pair is never
338
+ reused after the credentials change (`_load_valid_token`), and used by the
339
+ runtime to seed a pre-issued token (`KIWOOM_ACCESS_TOKEN`) with the
340
+ fingerprint the auth object will check it against.
341
+ """
342
+ raw = f"{credentials.appkey}:{credentials.secretkey}".encode("utf-8")
343
+ return hashlib.sha256(raw).hexdigest()
333
344
 
334
345
 
335
346
  def _safe_json(response: requests.Response) -> dict:
@@ -6,10 +6,11 @@ import requests
6
6
 
7
7
  from kiwoom.core.auth import KiwoomAuth, get_base_url
8
8
  from kiwoom.core.errors import (
9
- AUTH_RETRY_RETURN_CODES,
10
9
  AuthenticationError,
11
10
  HTTPRequestError,
11
+ auth_retry_code,
12
12
  normalize_return_code,
13
+ raise_for_error_code,
13
14
  )
14
15
  from kiwoom.core.types import Continuation, KiwoomResponse
15
16
 
@@ -81,7 +82,9 @@ class KiwoomClient:
81
82
  }
82
83
 
83
84
  return_code = normalize_return_code(data.get("return_code"))
84
- if return_code in AUTH_RETRY_RETURN_CODES and retry_on_auth_failure:
85
+ # 키움은 만료/무효 토큰을 top-level return_code=8005로도, return_code=3 +
86
+ # return_msg "[8005:...]"로도 돌려준다 — 어느 쪽이든 한 번 재발급 후 재시도.
87
+ if retry_on_auth_failure and auth_retry_code(return_code, data.get("return_msg")) is not None:
85
88
  self.auth.recover_from_auth_failure()
86
89
  return self.request(
87
90
  api_id=resolved_api_id,
@@ -97,6 +100,14 @@ class KiwoomClient:
97
100
  if return_code not in (None, 0):
98
101
  message = f"{message} (return_code={data['return_code']})"
99
102
  raise HTTPRequestError(response.status_code, message)
103
+
104
+ # 키움은 업무 오류도 HTTP 200으로 내려보내고 실패 여부는 본문 return_code에만 담는다.
105
+ if return_code not in (None, 0):
106
+ raise_for_error_code(
107
+ return_code,
108
+ str(data.get("return_msg") or "API 요청에 실패했습니다."),
109
+ )
110
+
100
111
  return _build_response(data, response.headers)
101
112
 
102
113
  def fetch_page(
@@ -1,9 +1,31 @@
1
+ import re
1
2
  from pathlib import Path
2
3
 
3
4
  # Kiwoom auth-expiry return codes that warrant a one-shot credential
4
5
  # recovery + retry (shared by the REST client and the WebSocket client).
5
6
  AUTH_RETRY_RETURN_CODES = frozenset({8005, 8031, 8103})
6
7
 
8
+ # Kiwoom often reports a generic top-level ``return_code`` (e.g. 3 = "인증에
9
+ # 실패했습니다") and puts the specific code inside ``return_msg`` as
10
+ # ``[8005:Token이 유효하지 않습니다]`` (REST) or ``CODE=8005`` (WebSocket).
11
+ _EMBEDDED_CODE_RE = re.compile(r"\[(\d{3,5}):|CODE=(\d{3,5})")
12
+
13
+
14
+ def embedded_return_code(return_msg: object) -> int | None:
15
+ """Return the specific code embedded in a Kiwoom ``return_msg``, if any."""
16
+ match = _EMBEDDED_CODE_RE.search(str(return_msg or ""))
17
+ if match is None:
18
+ return None
19
+ return int(match.group(1) or match.group(2))
20
+
21
+
22
+ def auth_retry_code(return_code: int | None, return_msg: object) -> int | None:
23
+ """The auth-expiry code (top-level or embedded) that warrants recovery, else None."""
24
+ if return_code in AUTH_RETRY_RETURN_CODES:
25
+ return return_code
26
+ embedded = embedded_return_code(return_msg)
27
+ return embedded if embedded in AUTH_RETRY_RETURN_CODES else None
28
+
7
29
 
8
30
  def normalize_return_code(value: object) -> int | None:
9
31
  """Normalize a Kiwoom ``return_code`` payload value.
@@ -137,6 +159,13 @@ class DeviceAuthenticationError(AuthenticationError):
137
159
  self.return_msg = return_msg
138
160
 
139
161
 
162
+ class DemoUnsupportedError(APIError):
163
+ """Raised when the API exists but is not served in demo (모의투자) mode."""
164
+
165
+ def __init__(self, return_code: int, return_msg: str):
166
+ super().__init__(return_code, f"모의투자에서 지원하지 않는 API입니다. ({return_msg})")
167
+
168
+
140
169
  INPUT_VALIDATION_CODES = {
141
170
  1501,
142
171
  1504,
@@ -151,15 +180,35 @@ INPUT_VALIDATION_CODES = {
151
180
  1687,
152
181
  8020,
153
182
  }
154
- RATE_LIMIT_CODES = {1700}
155
- SYMBOL_NOT_FOUND_CODES = {1901, 1902}
183
+ RATE_LIMIT_CODES = {1700, 1701, 1702}
184
+ SYMBOL_NOT_FOUND_CODES = {1901, 1902, 1903}
156
185
  INVALID_CREDENTIAL_CODES = {8001, 8002, 8011, 8012}
157
186
  INVALID_TOKEN_CODES = {8003, 8005, 8006, 8009, 8015, 8016}
158
187
  MODE_MISMATCH_CODES = {8030, 8031}
159
188
  DEVICE_AUTH_CODES = {8010, 8040, 8050, 8103}
189
+ DEMO_UNSUPPORTED_CODES = {8104}
190
+
191
+
192
+ _CLASSIFIED_CODES = (
193
+ INPUT_VALIDATION_CODES
194
+ | RATE_LIMIT_CODES
195
+ | SYMBOL_NOT_FOUND_CODES
196
+ | INVALID_CREDENTIAL_CODES
197
+ | INVALID_TOKEN_CODES
198
+ | MODE_MISMATCH_CODES
199
+ | DEVICE_AUTH_CODES
200
+ | DEMO_UNSUPPORTED_CODES
201
+ )
160
202
 
161
203
 
162
204
  def raise_for_error_code(return_code: int, return_msg: str) -> None:
205
+ # A generic top-level code (3, 5, ...) with a specific code embedded in the
206
+ # message is classified by the embedded code, so callers get the same
207
+ # error class either way; the message keeps Kiwoom's original text.
208
+ if return_code not in _CLASSIFIED_CODES:
209
+ embedded = embedded_return_code(return_msg)
210
+ if embedded in _CLASSIFIED_CODES:
211
+ return_code = embedded
163
212
  if return_code in INPUT_VALIDATION_CODES:
164
213
  raise InputValidationError(return_code, return_msg)
165
214
  if return_code in RATE_LIMIT_CODES:
@@ -174,4 +223,8 @@ def raise_for_error_code(return_code: int, return_msg: str) -> None:
174
223
  raise ModeMismatchError(return_code, return_msg)
175
224
  if return_code in DEVICE_AUTH_CODES:
176
225
  raise DeviceAuthenticationError(return_code, return_msg)
226
+ if return_code in DEMO_UNSUPPORTED_CODES:
227
+ raise DemoUnsupportedError(return_code, return_msg)
228
+ # 1999(예기치 못한 오류)·8200(법인 미지원)처럼 분류해도 대응이 달라지지 않는 코드는
229
+ # 원문 메시지를 그대로 노출하는 APIError로 남긴다.
177
230
  raise APIError(return_code, return_msg)