kiwoomRest 0.0.1__py3-none-any.whl
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.
- kiwoomRest/__init__.py +6 -0
- kiwoomRest/rest_api.py +445 -0
- kiwoomRest/tr_code_to_path.py +205 -0
- kiwoomrest-0.0.1.dist-info/METADATA +225 -0
- kiwoomrest-0.0.1.dist-info/RECORD +7 -0
- kiwoomrest-0.0.1.dist-info/WHEEL +4 -0
- kiwoomrest-0.0.1.dist-info/licenses/LICENSE +22 -0
kiwoomRest/__init__.py
ADDED
kiwoomRest/rest_api.py
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
############################################################################
|
|
2
|
+
# 키움증권 REST API 비동기 클라이언트
|
|
3
|
+
# 키움증권 가이드 참고: https://openapi.kiwoom.com/guide/apiguide
|
|
4
|
+
############################################################################
|
|
5
|
+
|
|
6
|
+
import asyncio
|
|
7
|
+
import aiohttp
|
|
8
|
+
import json
|
|
9
|
+
import time
|
|
10
|
+
|
|
11
|
+
BASE_URL_REAL = "https://api.kiwoom.com"
|
|
12
|
+
BASE_URL_SIMUL = "https://mockapi.kiwoom.com"
|
|
13
|
+
WSS_URL_REAL = "wss://api.kiwoom.com:10000/api/dostk/websocket"
|
|
14
|
+
WSS_URL_SIMULATION = "wss://mockapi.kiwoom.com:10000/api/dostk/websocket"
|
|
15
|
+
|
|
16
|
+
from .tr_code_to_path import tr_code_to_path
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ResponseData:
|
|
20
|
+
def __init__(self,
|
|
21
|
+
api_id: str
|
|
22
|
+
) -> None:
|
|
23
|
+
self.api_id = api_id
|
|
24
|
+
self.path = str()
|
|
25
|
+
self.return_code = int(-1)
|
|
26
|
+
self.return_msg = str()
|
|
27
|
+
self.cont_yn = str()
|
|
28
|
+
self.next_key = str()
|
|
29
|
+
self.body = {}
|
|
30
|
+
# additional variables
|
|
31
|
+
self.inputs = dict()
|
|
32
|
+
self.in_cont_yn = str()
|
|
33
|
+
self.in_next_key = str()
|
|
34
|
+
self.request_time = 0.0
|
|
35
|
+
self.elapsed_ms = 0.0
|
|
36
|
+
|
|
37
|
+
def __str__(self) -> str:
|
|
38
|
+
return f"ResponseData(api_id={self.api_id}, path={self.path}, return_code={self.return_code}, return_msg={self.return_msg}, cont_yn={self.cont_yn}, next_key={self.next_key}, body={self.body})"
|
|
39
|
+
|
|
40
|
+
class KwRestApi(object):
|
|
41
|
+
|
|
42
|
+
class _event_signal:
|
|
43
|
+
class _slot:
|
|
44
|
+
def __init__(self, func):
|
|
45
|
+
self.func = func
|
|
46
|
+
self.is_coroutine = asyncio.iscoroutinefunction(func)
|
|
47
|
+
def __eq__(self, other):
|
|
48
|
+
return self.func == other
|
|
49
|
+
def __init__(self):
|
|
50
|
+
self.__slots: list[KwRestApi._event_signal._slot] = []
|
|
51
|
+
def connect(self, func):
|
|
52
|
+
# check callable
|
|
53
|
+
if not hasattr(func, "__call__") :
|
|
54
|
+
raise ValueError("slot must be callable")
|
|
55
|
+
# check exist
|
|
56
|
+
exist_slot = next((s for s in self.__slots if s.func == func), None)
|
|
57
|
+
if exist_slot:
|
|
58
|
+
return
|
|
59
|
+
# add slot
|
|
60
|
+
self.__slots.append(self._slot(func))
|
|
61
|
+
def disconnect(self, func):
|
|
62
|
+
exist_slot = next((s for s in self.__slots if s.func == func), None)
|
|
63
|
+
if exist_slot:
|
|
64
|
+
self.__slots.remove(exist_slot)
|
|
65
|
+
def disconnect_all(self):
|
|
66
|
+
self.__slots.clear()
|
|
67
|
+
async def emit_signal(self, *args):
|
|
68
|
+
for slot in self.__slots:
|
|
69
|
+
if slot.is_coroutine:
|
|
70
|
+
await slot.func(*args)
|
|
71
|
+
else:
|
|
72
|
+
slot.func(*args)
|
|
73
|
+
|
|
74
|
+
class _asyncNode:
|
|
75
|
+
def __init__(self, hashid):
|
|
76
|
+
self.__event = asyncio.Event()
|
|
77
|
+
self.hashid = hashid
|
|
78
|
+
self.jsondata = {}
|
|
79
|
+
|
|
80
|
+
def set(self):
|
|
81
|
+
self.__event.set()
|
|
82
|
+
|
|
83
|
+
async def wait(self, timeout=None):
|
|
84
|
+
if timeout is None:
|
|
85
|
+
await self.__event.wait()
|
|
86
|
+
return True
|
|
87
|
+
try:
|
|
88
|
+
await asyncio.wait_for(self.__event.wait(), timeout)
|
|
89
|
+
return True
|
|
90
|
+
except asyncio.TimeoutError:
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
def __init__(self):
|
|
94
|
+
super().__init__()
|
|
95
|
+
|
|
96
|
+
self._access_token = ""
|
|
97
|
+
self._http = None
|
|
98
|
+
self._websocket = None
|
|
99
|
+
self._base_url:str = ""
|
|
100
|
+
self._connected:bool = False
|
|
101
|
+
self._is_simulation:bool = False
|
|
102
|
+
self._last_message:str = ""
|
|
103
|
+
self._timeout = 5
|
|
104
|
+
self._last_response_value = None
|
|
105
|
+
|
|
106
|
+
# 이벤트 핸들러
|
|
107
|
+
self._on_message = self._event_signal()
|
|
108
|
+
self._on_realtime = self._event_signal()
|
|
109
|
+
|
|
110
|
+
# 웹소켓 비동기 요청 노드
|
|
111
|
+
self._ws_asyncnode:KwRestApi._asyncNode | None = None
|
|
112
|
+
|
|
113
|
+
@property
|
|
114
|
+
def connected(self) -> bool:
|
|
115
|
+
"""로그인 연결상태.
|
|
116
|
+
True: 연결됨, False: 연결안됨
|
|
117
|
+
|
|
118
|
+
A readonly property.
|
|
119
|
+
"""
|
|
120
|
+
return self._connected
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def is_simulation(self) -> bool:
|
|
124
|
+
"""서버모드
|
|
125
|
+
True: 모의투자, False: 실투자
|
|
126
|
+
|
|
127
|
+
A readonly property.
|
|
128
|
+
"""
|
|
129
|
+
return self._is_simulation
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def last_message(self) -> str:
|
|
133
|
+
"""last error message.
|
|
134
|
+
|
|
135
|
+
A readonly property.
|
|
136
|
+
"""
|
|
137
|
+
return self._last_message
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def on_message(self) :
|
|
141
|
+
"""메시지 수신 이벤트 핸들러, 웹소켓 오류/끊김 시 발생
|
|
142
|
+
on_message(msg:str)
|
|
143
|
+
"""
|
|
144
|
+
return self._on_message
|
|
145
|
+
|
|
146
|
+
@property
|
|
147
|
+
def on_realtime(self) :
|
|
148
|
+
"""실시간 데이터 수신 이벤트 핸들러
|
|
149
|
+
on_realtime(realdata:dict)
|
|
150
|
+
"""
|
|
151
|
+
return self._on_realtime
|
|
152
|
+
|
|
153
|
+
@property
|
|
154
|
+
def last_response(self) -> ResponseData | None:
|
|
155
|
+
"""마지막 요청 응답값
|
|
156
|
+
ResponseValue: 응답값
|
|
157
|
+
"""
|
|
158
|
+
return self._last_response_value
|
|
159
|
+
|
|
160
|
+
@property
|
|
161
|
+
def access_token(self) -> str:
|
|
162
|
+
"""access token
|
|
163
|
+
access_token: access token
|
|
164
|
+
"""
|
|
165
|
+
return self._access_token
|
|
166
|
+
|
|
167
|
+
@property
|
|
168
|
+
def timeout(self) -> int:
|
|
169
|
+
"""timeout
|
|
170
|
+
timeout: timeout
|
|
171
|
+
"""
|
|
172
|
+
return self._timeout
|
|
173
|
+
|
|
174
|
+
@timeout.setter
|
|
175
|
+
def timeout(self, value: int) -> None:
|
|
176
|
+
"""timeout setter
|
|
177
|
+
value: timeout
|
|
178
|
+
"""
|
|
179
|
+
if value <= 0:
|
|
180
|
+
raise ValueError("timeout must be greater than 0")
|
|
181
|
+
self._timeout = value
|
|
182
|
+
|
|
183
|
+
async def close(self) -> None:
|
|
184
|
+
"""연결 종료"""
|
|
185
|
+
self._connected = False
|
|
186
|
+
if self._websocket and not self._websocket.closed:
|
|
187
|
+
await self._websocket.close()
|
|
188
|
+
if self._http and not self._http.closed:
|
|
189
|
+
await self._http.close()
|
|
190
|
+
|
|
191
|
+
async def login(self, appkey:str, secretkey:str, is_simulation:bool = False) -> bool:
|
|
192
|
+
'''
|
|
193
|
+
로그인 요청
|
|
194
|
+
appkey: 앱키
|
|
195
|
+
secretkey: 앱시크릿키
|
|
196
|
+
is_simulation: 모의투자 로그인 시 True, 실투자 로그인 시 False (default)
|
|
197
|
+
return: True: 성공, False: 실패, 실패시 last_message에 실패사유가 저장됨
|
|
198
|
+
'''
|
|
199
|
+
if self._connected :
|
|
200
|
+
self._last_message = "aleady connected"
|
|
201
|
+
return True
|
|
202
|
+
|
|
203
|
+
if appkey == "" or secretkey == "":
|
|
204
|
+
self._last_message = "appkey or secretkey is empty"
|
|
205
|
+
return False
|
|
206
|
+
|
|
207
|
+
self._is_simulation = is_simulation
|
|
208
|
+
self._base_url = BASE_URL_SIMUL if self._is_simulation else BASE_URL_REAL
|
|
209
|
+
|
|
210
|
+
timeout = aiohttp.ClientTimeout(total=10) # 10초 타임아웃
|
|
211
|
+
self._http = aiohttp.ClientSession(timeout=timeout)
|
|
212
|
+
|
|
213
|
+
# 토큰 가져오기
|
|
214
|
+
self._connected = True
|
|
215
|
+
inputs = {'grant_type': 'client_credentials', 'appkey': appkey, 'secretkey': secretkey}
|
|
216
|
+
response = await self.request("oauth2", inputs, path="/oauth2/token")
|
|
217
|
+
if response.return_code == 0:
|
|
218
|
+
# 토큰발급 성공
|
|
219
|
+
token = response.body["token"]
|
|
220
|
+
self._http.headers["Authorization"] = f"Bearer {token}"
|
|
221
|
+
self._access_token = token
|
|
222
|
+
# 웹소켓 연결/인증요청
|
|
223
|
+
try:
|
|
224
|
+
websocket = await self._http.ws_connect(WSS_URL_SIMULATION if self._is_simulation else WSS_URL_REAL)
|
|
225
|
+
if not websocket.closed:
|
|
226
|
+
self._websocket = websocket
|
|
227
|
+
asyncio.create_task(self._websocket_listen())
|
|
228
|
+
response = await self.realtime({"trnm":"LOGIN", "token" : self._access_token})
|
|
229
|
+
if response.return_code == 0:
|
|
230
|
+
# 인증성공
|
|
231
|
+
self._last_message = "Login success"
|
|
232
|
+
return True
|
|
233
|
+
else:
|
|
234
|
+
self._last_message = response.return_msg
|
|
235
|
+
else:
|
|
236
|
+
self._last_message = "websocket connection failed."
|
|
237
|
+
except Exception as e:
|
|
238
|
+
self._last_message = str(e)
|
|
239
|
+
else:
|
|
240
|
+
self._last_message = response.return_msg
|
|
241
|
+
await self.close()
|
|
242
|
+
return False
|
|
243
|
+
|
|
244
|
+
async def request(self
|
|
245
|
+
, api_id:str
|
|
246
|
+
, indatas:dict
|
|
247
|
+
,*
|
|
248
|
+
, cont_yn:str="N"
|
|
249
|
+
, next_key:str=""
|
|
250
|
+
, path:str=None
|
|
251
|
+
) -> ResponseData:
|
|
252
|
+
'''
|
|
253
|
+
TR/실시간 요청
|
|
254
|
+
api_id: TR코드(api-id)
|
|
255
|
+
indatas: 입력데이터(dictionary)
|
|
256
|
+
cont_yn: TR연속요청여부
|
|
257
|
+
next_key: TR연속요청키
|
|
258
|
+
path: 요청경로(생략시 자동으로 결정)
|
|
259
|
+
return: ResponseValue, 성공시 return_code = 0, 그외 실패, 실패시 return_msg에 실패사유가 저장됨
|
|
260
|
+
|
|
261
|
+
example:
|
|
262
|
+
# 삼성전자 종목정보 조회
|
|
263
|
+
inputs = {
|
|
264
|
+
"stk_cd" : "005930"
|
|
265
|
+
}
|
|
266
|
+
response = await api.request("ka10001", inputs)
|
|
267
|
+
print(response)
|
|
268
|
+
|
|
269
|
+
# 조건검색식 목록 조회
|
|
270
|
+
inputs = {
|
|
271
|
+
"trnm" : "CNSRLST"
|
|
272
|
+
}
|
|
273
|
+
response = await api.request("ka10171", inputs)
|
|
274
|
+
print(response)
|
|
275
|
+
'''
|
|
276
|
+
result = ResponseData(api_id)
|
|
277
|
+
result.in_cont_yn = cont_yn
|
|
278
|
+
result.in_next_key = next_key
|
|
279
|
+
result.inputs = indatas
|
|
280
|
+
|
|
281
|
+
if not path:
|
|
282
|
+
path = tr_code_to_path.get(api_id, None)
|
|
283
|
+
if not path:
|
|
284
|
+
self._last_message = "Not supported tr_code-path"
|
|
285
|
+
result.return_msg = self._last_message
|
|
286
|
+
return result
|
|
287
|
+
|
|
288
|
+
result.path = path
|
|
289
|
+
|
|
290
|
+
self._last_response_value = result
|
|
291
|
+
if not self._connected:
|
|
292
|
+
self._last_message = "Not connected"
|
|
293
|
+
result.return_msg = self._last_message
|
|
294
|
+
return result
|
|
295
|
+
|
|
296
|
+
# check if websocket request
|
|
297
|
+
if path.endswith("/websocket"):
|
|
298
|
+
result.request_time = time.time()
|
|
299
|
+
start_time = time.perf_counter_ns()
|
|
300
|
+
ws_result = await self._ws_request(indatas)
|
|
301
|
+
result.elapsed_ms = (time.perf_counter_ns() - start_time) / 1000000
|
|
302
|
+
if ws_result != None:
|
|
303
|
+
result.return_code = ws_result.get("return_code", -1)
|
|
304
|
+
result.return_msg = ws_result.get("return_msg", "")
|
|
305
|
+
result.body = ws_result
|
|
306
|
+
return result
|
|
307
|
+
else:
|
|
308
|
+
result.return_code = -1
|
|
309
|
+
result.return_msg = self._last_message
|
|
310
|
+
return result
|
|
311
|
+
|
|
312
|
+
headers = dict()
|
|
313
|
+
headers["api-id"] = api_id
|
|
314
|
+
headers["cont-yn"] = cont_yn
|
|
315
|
+
headers["next-key"] = next_key
|
|
316
|
+
|
|
317
|
+
try:
|
|
318
|
+
result.request_time = time.time()
|
|
319
|
+
start_time = time.perf_counter_ns()
|
|
320
|
+
response = await self._http.post(self._base_url + path, headers=headers, json=indatas)
|
|
321
|
+
response_data:dict = await response.json()
|
|
322
|
+
result.elapsed_ms = (time.perf_counter_ns() - start_time) / 1000000
|
|
323
|
+
if response.status != 200:
|
|
324
|
+
self._last_message = f"Request failed. {response.status}"
|
|
325
|
+
result.return_msg = self._last_message
|
|
326
|
+
return result
|
|
327
|
+
result.cont_yn = response.headers.get("cont-yn", "N")
|
|
328
|
+
result.next_key = response.headers.get("next-key", "")
|
|
329
|
+
result.body = response_data
|
|
330
|
+
result.return_code = result.body.get("return_code", -1)
|
|
331
|
+
self._last_message = result.body.get("return_msg", "")
|
|
332
|
+
result.return_msg = self._last_message
|
|
333
|
+
except Exception as e:
|
|
334
|
+
self._last_message = str(e)
|
|
335
|
+
result.return_msg = self._last_message
|
|
336
|
+
|
|
337
|
+
return result
|
|
338
|
+
|
|
339
|
+
def realtime(self, indatas:dict):
|
|
340
|
+
'''
|
|
341
|
+
# ex: 삼성전자, SK하이닉스 실시간 체결 시세 등록
|
|
342
|
+
inputs = {
|
|
343
|
+
"trnm" : "REG",
|
|
344
|
+
"grp_no" : "1",
|
|
345
|
+
"refresh" : "1",
|
|
346
|
+
"data" : [{
|
|
347
|
+
" item" : [ "005930", "000660" ],
|
|
348
|
+
" type" : [ "0B" ] # 우선호가시세 까지 함께 등록할 경우 ["0B", "0C" ]로 설정
|
|
349
|
+
}]
|
|
350
|
+
}
|
|
351
|
+
response = await api.realtime(inputs)
|
|
352
|
+
print(response)
|
|
353
|
+
|
|
354
|
+
# 실시간 시세 해제 시 "REG" -> "REMOVE" 로 변경
|
|
355
|
+
inputs["trnm"] = "REMOVE"
|
|
356
|
+
response = await api.realtime(inputs)
|
|
357
|
+
'''
|
|
358
|
+
return self.request("realtime", indatas, path="/api/dostk/websocket", cont_yn="")
|
|
359
|
+
|
|
360
|
+
async def ws_sendmessage(self, text:str) -> bool:
|
|
361
|
+
'''
|
|
362
|
+
웹소켓 메시지 전송
|
|
363
|
+
text: 전송할 메시지
|
|
364
|
+
return: True: 성공, False: 실패, 실패시 last_message에 실패사유가 저장됨
|
|
365
|
+
'''
|
|
366
|
+
if not self._connected or self._websocket.closed:
|
|
367
|
+
self._last_message = "Not connected"
|
|
368
|
+
return False
|
|
369
|
+
await self._websocket.send_str(text)
|
|
370
|
+
return True
|
|
371
|
+
|
|
372
|
+
async def _ws_request(self, indatas:dict) -> dict | None:
|
|
373
|
+
# check connection
|
|
374
|
+
if not self._connected:
|
|
375
|
+
self._last_message = "Not connected"
|
|
376
|
+
return None
|
|
377
|
+
|
|
378
|
+
# check async node
|
|
379
|
+
if self._ws_asyncnode != None:
|
|
380
|
+
# already requested
|
|
381
|
+
self._last_message = "Already requested"
|
|
382
|
+
return None
|
|
383
|
+
|
|
384
|
+
trnm = indatas.get("trnm", None)
|
|
385
|
+
if trnm == None:
|
|
386
|
+
self._last_message = "trnm is not define."
|
|
387
|
+
return None
|
|
388
|
+
|
|
389
|
+
# create async node
|
|
390
|
+
node = KwRestApi._asyncNode(trnm)
|
|
391
|
+
self._ws_asyncnode = node
|
|
392
|
+
|
|
393
|
+
# send message
|
|
394
|
+
try:
|
|
395
|
+
await self._websocket.send_json(indatas)
|
|
396
|
+
# wait for response
|
|
397
|
+
if not await self._ws_asyncnode.wait(self.timeout):
|
|
398
|
+
# timeout
|
|
399
|
+
self._last_message = "Timeout"
|
|
400
|
+
self._ws_asyncnode = None
|
|
401
|
+
return None
|
|
402
|
+
except Exception as e:
|
|
403
|
+
self._last_message = f"websocket request error. {e}"
|
|
404
|
+
self._ws_asyncnode = None
|
|
405
|
+
return None
|
|
406
|
+
|
|
407
|
+
# set response and return
|
|
408
|
+
self._ws_asyncnode = None
|
|
409
|
+
return node.jsondata
|
|
410
|
+
|
|
411
|
+
async def _websocket_listen(self):
|
|
412
|
+
async for msg in self._websocket:
|
|
413
|
+
if msg.type == aiohttp.WSMsgType.TEXT:
|
|
414
|
+
json_text = msg.data
|
|
415
|
+
try:
|
|
416
|
+
jsondata = json.loads(json_text)
|
|
417
|
+
trnm = jsondata.get("trnm", None)
|
|
418
|
+
if trnm != None:
|
|
419
|
+
# proc PING
|
|
420
|
+
if trnm == "PING":
|
|
421
|
+
await self._websocket.send_str(json_text)
|
|
422
|
+
continue
|
|
423
|
+
# check async node
|
|
424
|
+
if self._ws_asyncnode != None and self._ws_asyncnode.hashid == trnm:
|
|
425
|
+
# setting jsondata, set event
|
|
426
|
+
self._ws_asyncnode.jsondata = jsondata
|
|
427
|
+
self._ws_asyncnode.set()
|
|
428
|
+
continue
|
|
429
|
+
# raise event
|
|
430
|
+
await self._on_realtime.emit_signal(jsondata)
|
|
431
|
+
except Exception as e:
|
|
432
|
+
self._last_message = f"websocket exception. {e}"
|
|
433
|
+
await self._on_message.emit_signal(self._last_message)
|
|
434
|
+
continue
|
|
435
|
+
|
|
436
|
+
elif msg.type == aiohttp.WSMsgType.CLOSED:
|
|
437
|
+
break
|
|
438
|
+
|
|
439
|
+
elif msg.type == aiohttp.WSMsgType.ERROR:
|
|
440
|
+
self._last_message = f"websocket error. {msg}"
|
|
441
|
+
await self._on_message.emit_signal(self._last_message)
|
|
442
|
+
|
|
443
|
+
if self._connected:
|
|
444
|
+
self._last_message = f"websocket closed."
|
|
445
|
+
await self._on_message.emit_signal(self._last_message)
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
tr_code_to_path:dict = {
|
|
2
|
+
|
|
3
|
+
## 국내주식
|
|
4
|
+
|
|
5
|
+
# 종목정보
|
|
6
|
+
"ka10001" : "/api/dostk/stkinfo",
|
|
7
|
+
"ka10002" : "/api/dostk/stkinfo",
|
|
8
|
+
"ka10003" : "/api/dostk/stkinfo",
|
|
9
|
+
"ka10013" : "/api/dostk/stkinfo",
|
|
10
|
+
"ka10015" : "/api/dostk/stkinfo",
|
|
11
|
+
"ka10016" : "/api/dostk/stkinfo",
|
|
12
|
+
"ka10017" : "/api/dostk/stkinfo",
|
|
13
|
+
"ka10018" : "/api/dostk/stkinfo",
|
|
14
|
+
"ka10019" : "/api/dostk/stkinfo",
|
|
15
|
+
"ka10024" : "/api/dostk/stkinfo",
|
|
16
|
+
"ka10025" : "/api/dostk/stkinfo",
|
|
17
|
+
"ka10026" : "/api/dostk/stkinfo",
|
|
18
|
+
"ka10028" : "/api/dostk/stkinfo",
|
|
19
|
+
"ka10043" : "/api/dostk/stkinfo",
|
|
20
|
+
"ka10052" : "/api/dostk/stkinfo",
|
|
21
|
+
"ka10054" : "/api/dostk/stkinfo",
|
|
22
|
+
"ka10055" : "/api/dostk/stkinfo",
|
|
23
|
+
"ka10058" : "/api/dostk/stkinfo",
|
|
24
|
+
"ka10059" : "/api/dostk/stkinfo",
|
|
25
|
+
"ka10061" : "/api/dostk/stkinfo",
|
|
26
|
+
"ka10084" : "/api/dostk/stkinfo",
|
|
27
|
+
"ka10095" : "/api/dostk/stkinfo",
|
|
28
|
+
"ka10099" : "/api/dostk/stkinfo",
|
|
29
|
+
"ka10100" : "/api/dostk/stkinfo",
|
|
30
|
+
"ka10101" : "/api/dostk/stkinfo",
|
|
31
|
+
"ka10102" : "/api/dostk/stkinfo",
|
|
32
|
+
"ka90003" : "/api/dostk/stkinfo",
|
|
33
|
+
"ka90004" : "/api/dostk/stkinfo",
|
|
34
|
+
"ka90012" : "/api/dostk/stkinfo",
|
|
35
|
+
|
|
36
|
+
# 시세
|
|
37
|
+
"ka10004" : "/api/dostk/mrkcond",
|
|
38
|
+
"ka10005" : "/api/dostk/mrkcond",
|
|
39
|
+
"ka10006" : "/api/dostk/mrkcond",
|
|
40
|
+
"ka10007" : "/api/dostk/mrkcond",
|
|
41
|
+
"ka10011" : "/api/dostk/mrkcond",
|
|
42
|
+
"ka10044" : "/api/dostk/mrkcond",
|
|
43
|
+
"ka10045" : "/api/dostk/mrkcond",
|
|
44
|
+
"ka10046" : "/api/dostk/mrkcond",
|
|
45
|
+
"ka10047" : "/api/dostk/mrkcond",
|
|
46
|
+
"ka10063" : "/api/dostk/mrkcond",
|
|
47
|
+
"ka10066" : "/api/dostk/mrkcond",
|
|
48
|
+
"ka10078" : "/api/dostk/mrkcond",
|
|
49
|
+
"ka10086" : "/api/dostk/mrkcond",
|
|
50
|
+
"ka10087" : "/api/dostk/mrkcond",
|
|
51
|
+
"ka90005" : "/api/dostk/mrkcond",
|
|
52
|
+
"ka90006" : "/api/dostk/mrkcond",
|
|
53
|
+
"ka90007" : "/api/dostk/mrkcond",
|
|
54
|
+
"ka90008" : "/api/dostk/mrkcond",
|
|
55
|
+
"ka90010" : "/api/dostk/mrkcond",
|
|
56
|
+
"ka90013" : "/api/dostk/mrkcond",
|
|
57
|
+
|
|
58
|
+
# 기관/외국인
|
|
59
|
+
"ka10008" : "/api/dostk/frgnistt",
|
|
60
|
+
"ka10009" : "/api/dostk/frgnistt",
|
|
61
|
+
"ka10131" : "/api/dostk/frgnistt",
|
|
62
|
+
|
|
63
|
+
# 업종
|
|
64
|
+
"ka10010" : "/api/dostk/sect",
|
|
65
|
+
"ka10051" : "/api/dostk/sect",
|
|
66
|
+
"ka20001" : "/api/dostk/sect",
|
|
67
|
+
"ka20002" : "/api/dostk/sect",
|
|
68
|
+
"ka20003" : "/api/dostk/sect",
|
|
69
|
+
"ka20009" : "/api/dostk/sect",
|
|
70
|
+
|
|
71
|
+
# 순위정보
|
|
72
|
+
"ka10020" : "/api/dostk/rkinfo",
|
|
73
|
+
"ka10021" : "/api/dostk/rkinfo",
|
|
74
|
+
"ka10022" : "/api/dostk/rkinfo",
|
|
75
|
+
"ka10023" : "/api/dostk/rkinfo",
|
|
76
|
+
"ka10027" : "/api/dostk/rkinfo",
|
|
77
|
+
"ka10029" : "/api/dostk/rkinfo",
|
|
78
|
+
"ka10030" : "/api/dostk/rkinfo",
|
|
79
|
+
"ka10031" : "/api/dostk/rkinfo",
|
|
80
|
+
"ka10032" : "/api/dostk/rkinfo",
|
|
81
|
+
"ka10033" : "/api/dostk/rkinfo",
|
|
82
|
+
"ka10034" : "/api/dostk/rkinfo",
|
|
83
|
+
"ka10035" : "/api/dostk/rkinfo",
|
|
84
|
+
"ka10036" : "/api/dostk/rkinfo",
|
|
85
|
+
"ka10037" : "/api/dostk/rkinfo",
|
|
86
|
+
"ka10038" : "/api/dostk/rkinfo",
|
|
87
|
+
"ka10039" : "/api/dostk/rkinfo",
|
|
88
|
+
"ka10040" : "/api/dostk/rkinfo",
|
|
89
|
+
"ka10042" : "/api/dostk/rkinfo",
|
|
90
|
+
"ka10053" : "/api/dostk/rkinfo",
|
|
91
|
+
"ka10062" : "/api/dostk/rkinfo",
|
|
92
|
+
"ka10065" : "/api/dostk/rkinfo",
|
|
93
|
+
"ka10069" : "/api/dostk/rkinfo",
|
|
94
|
+
"ka10098" : "/api/dostk/rkinfo",
|
|
95
|
+
"ka90009" : "/api/dostk/rkinfo",
|
|
96
|
+
|
|
97
|
+
# ELW
|
|
98
|
+
"ka10048" : "/api/dostk/elw",
|
|
99
|
+
"ka10050" : "/api/dostk/elw",
|
|
100
|
+
"ka30001" : "/api/dostk/elw",
|
|
101
|
+
"ka30002" : "/api/dostk/elw",
|
|
102
|
+
"ka30003" : "/api/dostk/elw",
|
|
103
|
+
"ka30004" : "/api/dostk/elw",
|
|
104
|
+
"ka30005" : "/api/dostk/elw",
|
|
105
|
+
"ka30009" : "/api/dostk/elw",
|
|
106
|
+
"ka30010" : "/api/dostk/elw",
|
|
107
|
+
"ka30011" : "/api/dostk/elw",
|
|
108
|
+
"ka30012" : "/api/dostk/elw",
|
|
109
|
+
|
|
110
|
+
# 차트
|
|
111
|
+
"ka10060" : "/api/dostk/chart",
|
|
112
|
+
"ka10064" : "/api/dostk/chart",
|
|
113
|
+
"ka10079" : "/api/dostk/chart",
|
|
114
|
+
"ka10080" : "/api/dostk/chart",
|
|
115
|
+
"ka10081" : "/api/dostk/chart",
|
|
116
|
+
"ka10082" : "/api/dostk/chart",
|
|
117
|
+
"ka10083" : "/api/dostk/chart",
|
|
118
|
+
"ka10094" : "/api/dostk/chart",
|
|
119
|
+
"ka20004" : "/api/dostk/chart",
|
|
120
|
+
"ka20005" : "/api/dostk/chart",
|
|
121
|
+
"ka20006" : "/api/dostk/chart",
|
|
122
|
+
"ka20007" : "/api/dostk/chart",
|
|
123
|
+
"ka20008" : "/api/dostk/chart",
|
|
124
|
+
"ka20019" : "/api/dostk/chart",
|
|
125
|
+
|
|
126
|
+
# 계좌
|
|
127
|
+
"ka10072" : "/api/dostk/acnt",
|
|
128
|
+
"ka10073" : "/api/dostk/acnt",
|
|
129
|
+
"ka10074" : "/api/dostk/acnt",
|
|
130
|
+
"ka10075" : "/api/dostk/acnt",
|
|
131
|
+
"ka10076" : "/api/dostk/acnt",
|
|
132
|
+
"ka10077" : "/api/dostk/acnt",
|
|
133
|
+
"ka10085" : "/api/dostk/acnt",
|
|
134
|
+
"ka10088" : "/api/dostk/acnt",
|
|
135
|
+
"ka10170" : "/api/dostk/acnt",
|
|
136
|
+
"kt00001" : "/api/dostk/acnt",
|
|
137
|
+
"kt00002" : "/api/dostk/acnt",
|
|
138
|
+
"kt00003" : "/api/dostk/acnt",
|
|
139
|
+
"kt00004" : "/api/dostk/acnt",
|
|
140
|
+
"kt00005" : "/api/dostk/acnt",
|
|
141
|
+
"kt00007" : "/api/dostk/acnt",
|
|
142
|
+
"kt00008" : "/api/dostk/acnt",
|
|
143
|
+
"kt00009" : "/api/dostk/acnt",
|
|
144
|
+
"kt00010" : "/api/dostk/acnt",
|
|
145
|
+
"kt00011" : "/api/dostk/acnt",
|
|
146
|
+
"kt00012" : "/api/dostk/acnt",
|
|
147
|
+
"kt00013" : "/api/dostk/acnt",
|
|
148
|
+
"kt00015" : "/api/dostk/acnt",
|
|
149
|
+
"kt00016" : "/api/dostk/acnt",
|
|
150
|
+
"kt00017" : "/api/dostk/acnt",
|
|
151
|
+
"kt00018" : "/api/dostk/acnt",
|
|
152
|
+
|
|
153
|
+
# ETF
|
|
154
|
+
"ka40001" : "/api/dostk/etf",
|
|
155
|
+
"ka40002" : "/api/dostk/etf",
|
|
156
|
+
"ka40003" : "/api/dostk/etf",
|
|
157
|
+
"ka40004" : "/api/dostk/etf",
|
|
158
|
+
"ka40006" : "/api/dostk/etf",
|
|
159
|
+
"ka40007" : "/api/dostk/etf",
|
|
160
|
+
"ka40008" : "/api/dostk/etf",
|
|
161
|
+
"ka40009" : "/api/dostk/etf",
|
|
162
|
+
"ka40010" : "/api/dostk/etf",
|
|
163
|
+
|
|
164
|
+
# 테마
|
|
165
|
+
"ka90001" : "/api/dostk/thme",
|
|
166
|
+
"ka90002" : "/api/dostk/thme",
|
|
167
|
+
|
|
168
|
+
# 주문
|
|
169
|
+
"kt10000" : "/api/dostk/ordr",
|
|
170
|
+
"kt10001" : "/api/dostk/ordr",
|
|
171
|
+
"kt10002" : "/api/dostk/ordr",
|
|
172
|
+
"kt10003" : "/api/dostk/ordr",
|
|
173
|
+
|
|
174
|
+
# 실시간시세
|
|
175
|
+
"00" : "/api/dostk/websocket",
|
|
176
|
+
"04" : "/api/dostk/websocket",
|
|
177
|
+
"0A" : "/api/dostk/websocket",
|
|
178
|
+
"0B" : "/api/dostk/websocket",
|
|
179
|
+
"0C" : "/api/dostk/websocket",
|
|
180
|
+
"0D" : "/api/dostk/websocket",
|
|
181
|
+
"0E" : "/api/dostk/websocket",
|
|
182
|
+
"0F" : "/api/dostk/websocket",
|
|
183
|
+
"0G" : "/api/dostk/websocket",
|
|
184
|
+
"0H" : "/api/dostk/websocket",
|
|
185
|
+
"0J" : "/api/dostk/websocket",
|
|
186
|
+
"0U" : "/api/dostk/websocket",
|
|
187
|
+
"0g" : "/api/dostk/websocket",
|
|
188
|
+
"0m" : "/api/dostk/websocket",
|
|
189
|
+
"0s" : "/api/dostk/websocket",
|
|
190
|
+
"0u" : "/api/dostk/websocket",
|
|
191
|
+
"0w" : "/api/dostk/websocket",
|
|
192
|
+
"1h" : "/api/dostk/websocket",
|
|
193
|
+
|
|
194
|
+
# 조건검색
|
|
195
|
+
"ka10171" : "/api/dostk/websocket",
|
|
196
|
+
"ka10172" : "/api/dostk/websocket",
|
|
197
|
+
"ka10173" : "/api/dostk/websocket",
|
|
198
|
+
"ka10174" : "/api/dostk/websocket",
|
|
199
|
+
|
|
200
|
+
# 신용주문
|
|
201
|
+
"kt10006" : "/api/dostk/crdordr",
|
|
202
|
+
"kt10007" : "/api/dostk/crdordr",
|
|
203
|
+
"kt10008" : "/api/dostk/crdordr",
|
|
204
|
+
"kt10009" : "/api/dostk/crdordr",
|
|
205
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: kiwoomRest
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: package for Kiwoom RestAPI
|
|
5
|
+
Project-URL: Homepage, https://github.com/teranum/python-packages/tree/master/kiwoomRest
|
|
6
|
+
Author-email: teranum <teranum@gmail.com>
|
|
7
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.8
|
|
11
|
+
Requires-Dist: aiohttp
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
# kiwoomRest Package
|
|
15
|
+
|
|
16
|
+
키움증권 REST API 간편이용 패키지.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
pip install kiwoomRest
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Usage
|
|
25
|
+
모든 요청은 비동기로 처리되며, 요청에 대한 응답은 await 키워드를 사용하여 받을 수 있습니다.
|
|
26
|
+
<BR/>
|
|
27
|
+
Samples: https://github.com/teranum/kiwoom-restapi-samples
|
|
28
|
+
|
|
29
|
+
### 로그인 요청은 반드시 먼저 수행되어야 하며, 로그인이 성공하면 다른 요청을 수행할 수 있습니다.
|
|
30
|
+
```python
|
|
31
|
+
import asyncio
|
|
32
|
+
from kiwoomRest import KwRestApi
|
|
33
|
+
from app_keys import appkey, secretkey # app_keys.py 파일에 appkey, secretkey 변수를 정의하고 사용하세요
|
|
34
|
+
|
|
35
|
+
async def main():
|
|
36
|
+
# Kiwoom API 객체 생성
|
|
37
|
+
api = KwRestApi()
|
|
38
|
+
|
|
39
|
+
# 실시간 이벤트 핸들러 등록
|
|
40
|
+
api.on_realtime.connect(lambda realdatas: print(f"on_realtime: {realdatas}"))
|
|
41
|
+
|
|
42
|
+
# 로그인
|
|
43
|
+
ret = await api.login(appkey, secretkey, is_simulation=False) # 실거래서버 사용, 모의투자서버 사용시 is_simulation=True
|
|
44
|
+
if not ret:
|
|
45
|
+
print(api.last_message)
|
|
46
|
+
return
|
|
47
|
+
print("Login success")
|
|
48
|
+
|
|
49
|
+
# 다른 작업...
|
|
50
|
+
await asyncio.sleep(5)
|
|
51
|
+
|
|
52
|
+
# 연결 종료
|
|
53
|
+
await api.close()
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
asyncio.run(main())
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 조회/연속 요청
|
|
60
|
+
```python
|
|
61
|
+
# 종목정보 리스트 조회
|
|
62
|
+
inputs = {
|
|
63
|
+
"mrkt_tp": "0" # 시장구분: 0:코스피, 10:코스닥, 3:ELW, 8:ETF, 30:K-OTC, 50:코넥스, 5:신주인수권, 4:뮤추얼펀드, 6:리츠, 9:하이일드
|
|
64
|
+
}
|
|
65
|
+
response = await api.request("ka10099", inputs)
|
|
66
|
+
|
|
67
|
+
# 주식 종목정보 조회
|
|
68
|
+
inputs = {
|
|
69
|
+
"stk_cd" : "005930" # 종목코드: 삼성전자
|
|
70
|
+
}
|
|
71
|
+
response = await api.request("ka10001", inputs)
|
|
72
|
+
|
|
73
|
+
# 주식 차트 조회
|
|
74
|
+
inputs = {
|
|
75
|
+
"stk_cd": "005930", # 종목코드: 삼성전자
|
|
76
|
+
"base_dt": "00000000", # 기준일자: 00000000(현재일자)
|
|
77
|
+
"upd_stkpc_tp": "1" # 수정주가구분: 0(미적용), 1(적용)
|
|
78
|
+
}
|
|
79
|
+
response = await api.request("ka10081", inputs)
|
|
80
|
+
|
|
81
|
+
# 연속조회
|
|
82
|
+
if response.cont_yn == "Y":
|
|
83
|
+
response = await api.request("t8410", request, cont_yn=response.cont_yn, next_key=response.next_key)
|
|
84
|
+
|
|
85
|
+
# response 출력
|
|
86
|
+
if response.return_code == 0:
|
|
87
|
+
print("조회 성공")
|
|
88
|
+
print(response.body)
|
|
89
|
+
else:
|
|
90
|
+
print(f"조회 실패: {response.return_msg}")
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 조건검색 조회/실시간
|
|
95
|
+
```python
|
|
96
|
+
# 서버저장조건 리스트조회
|
|
97
|
+
inputs = {
|
|
98
|
+
"trnm" : "CNSRLST" # CNSRLST 고정값
|
|
99
|
+
}
|
|
100
|
+
response = await api.request("ka10171", inputs)
|
|
101
|
+
|
|
102
|
+
# 조건검색 요청 일반
|
|
103
|
+
inputs = {
|
|
104
|
+
"trnm" : "CNSRREQ", # CNSRREQ 고정값
|
|
105
|
+
"seq" : "4", # 조건검색식 일련번호
|
|
106
|
+
"search_type" : "0", # 0:조건검색
|
|
107
|
+
"stex_tp" : "K", # K:KRX
|
|
108
|
+
"cont_yn" : "N", # 연속조회여부 (Y:연속조회, N:단순조회)
|
|
109
|
+
"next_key" : "" # 연속조회키 (연속조회여부가 'Y'인 경우 필수 세팅)
|
|
110
|
+
}
|
|
111
|
+
response = await api.request("ka10172", inputs)
|
|
112
|
+
|
|
113
|
+
# 조건검색 실시간 검색 등록
|
|
114
|
+
api.on_realtime.connect(print) # 실시간 이벤트 핸들러 등록
|
|
115
|
+
inputs = {
|
|
116
|
+
"trnm" : "CNSRREQ", # CNSRREQ 고정값
|
|
117
|
+
"seq" : "4", # 조건검색식 일련번호
|
|
118
|
+
"search_type" : "1", # 1: 조건검색+실시간조건검색
|
|
119
|
+
"stex_tp" : "K" # K:KRX
|
|
120
|
+
}
|
|
121
|
+
response = await api.realtime(inputs)
|
|
122
|
+
|
|
123
|
+
if response.return_code != 0:
|
|
124
|
+
print(f"실시간검색 등록실패: {response.return_msg}")
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
print("실시간검색 등록성공")
|
|
128
|
+
print(response.body)
|
|
129
|
+
|
|
130
|
+
await asyncio.sleep(60) # 60초동안 유효, 후에 중지
|
|
131
|
+
|
|
132
|
+
# 조건검색 실시간 해제
|
|
133
|
+
inputs = {
|
|
134
|
+
"trnm" : "CNSRCLR", # CNSRCLR 고정값
|
|
135
|
+
"seq" : "4", # 조건검색식 일련번호
|
|
136
|
+
}
|
|
137
|
+
response = await api.realtime(inputs)
|
|
138
|
+
print("실시간검색 해제")
|
|
139
|
+
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### 웹소켓 실시간 시세 등록/해제
|
|
143
|
+
```python
|
|
144
|
+
api.on_realtime.connect(on_realtime) # 실시간 이벤트 핸들러 연결
|
|
145
|
+
|
|
146
|
+
# 삼성전자 실시간 체결 시세 등록
|
|
147
|
+
inputs = {
|
|
148
|
+
"trnm" : "REG",
|
|
149
|
+
"grp_no" : "1",
|
|
150
|
+
"refresh" : "1",
|
|
151
|
+
"data" : [{
|
|
152
|
+
" item" : ["005930"], # 삼성전자. or SK하이닉스 함께 등록할 경우 ["005930", "000660"] 로 설정
|
|
153
|
+
" type" : ["0B"] # 체결시세. or 우선호가시세 함께 등록할 경우 ["0B", "0C"]로 설정
|
|
154
|
+
}]
|
|
155
|
+
}
|
|
156
|
+
response = await api.realtime(inputs)
|
|
157
|
+
|
|
158
|
+
# 60초후 실시간 시세 중지
|
|
159
|
+
await asyncio.sleep(60)
|
|
160
|
+
|
|
161
|
+
# 실시간 시세 해제 시 "REG" -> "REMOVE" 로 변경
|
|
162
|
+
inputs["trnm"] = "REMOVE"
|
|
163
|
+
response = await api.realtime(inputs)
|
|
164
|
+
|
|
165
|
+
def on_realtime(realdatas):
|
|
166
|
+
print(f"실시간이벤트: {realdatas}")
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## 프로퍼티, 메소드, 이벤트
|
|
171
|
+
```python
|
|
172
|
+
# 프로퍼티
|
|
173
|
+
connected -> bool: 연결여부 (연결: True, 미연결: False)
|
|
174
|
+
is_simulation -> bool: 모의투자 여부 (모의투자: True, 실거래: False))
|
|
175
|
+
access_token -> str: 발급된 엑세스토큰
|
|
176
|
+
last_message -> str: 마지막 메시지, 로그인 또는 요청 실패시 사유 저장
|
|
177
|
+
|
|
178
|
+
# 메소드
|
|
179
|
+
login(appkey:str, secretkey:str, is_simulation:bool = False) -> bool: 로그인
|
|
180
|
+
appkey:str - 앱키
|
|
181
|
+
secretkey:str - 앱시크릿키
|
|
182
|
+
is_simulation:bool - 모의서버 사용여부 (모의서버: True, 실거래서버: False), 기본값: False
|
|
183
|
+
reutrn: bool - 로그인 성공여부 (성공: True, 실패: False), 실패시 last_message에 실패 사유 저장
|
|
184
|
+
|
|
185
|
+
request(api_id:str, indatas:dict, *, cont_yn:str='N', next_key:str='0', path:str=None) -> ResponseData: TR요청
|
|
186
|
+
api_id:str - api-id (TR코드)
|
|
187
|
+
indatas:dict - 요청 데이터
|
|
188
|
+
* - cont_yn, next_key, path는 옵션(기본값으로 설정됨)
|
|
189
|
+
cont_yn:str - 연속조회여부 (연속조회: 'Y', 단순조회: 'N'), 기본값: 'N'
|
|
190
|
+
next_key:str - 연속조회키 (연속조회여부가 'Y'인 경우 필수 세팅), 기본값: '0'
|
|
191
|
+
path:str - PATH경로, 기본값: None(자동으로 설정 됨), 설정 필요시 URL값으로 세팅 ex) '/api/dostk/stkinfo'
|
|
192
|
+
return: ResponseData - 응답 데이터, 요청 성공 여부는 return_code로 확인 가능 (0: 성공, 그 외: 실패)
|
|
193
|
+
|
|
194
|
+
realtime(indatas:dict) -> ResponseData: 실시간 등록/해제
|
|
195
|
+
indatas:dict - 실시간 요청 데이터
|
|
196
|
+
return: ResponseData - 응답 데이터, 요청 성공 여부는 return_code로 확인 가능 (0: 성공, 그 외: 실패)
|
|
197
|
+
|
|
198
|
+
close() -> None: 연결 종료
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
# 이벤트:
|
|
202
|
+
on_message(msg:str): 메시지 수신 이벤트 (웹소켓 오류시 발생)
|
|
203
|
+
msg - 메시지
|
|
204
|
+
- ex.1 ) 'websocket exception. {e}'
|
|
205
|
+
- ex.2 ) 'websocket closed. {msg}'
|
|
206
|
+
- ex.3 ) 'websocket error. {msg}'
|
|
207
|
+
|
|
208
|
+
on_realtime(realdatas:dict): 실시간 수신 이벤트 (실시간 데이터 수신시 발생)
|
|
209
|
+
realdatas - 실시간 데이터
|
|
210
|
+
|
|
211
|
+
이벤트 핸들러 연결 : api 객체 생성시 연결
|
|
212
|
+
api = KwRestApi()
|
|
213
|
+
api.on_message.connect(print)
|
|
214
|
+
api.on_realtime.connect(print)
|
|
215
|
+
|
|
216
|
+
# 응답데이터 : ResponseData
|
|
217
|
+
return_code -> int: 응답코드 (0: 성공, 그 외: 실패)
|
|
218
|
+
return_msg -> str: 응답메시지 (성공/실패 메시지)
|
|
219
|
+
body -> dict: 응답데이터 (조회결과 데이터)
|
|
220
|
+
cont_yn -> str: 연속조회여부 (연속조회 있을 경우: 'Y'로 세팅됨)
|
|
221
|
+
next_key -> str: 연속조회키
|
|
222
|
+
|
|
223
|
+
api_id -> str: 요청 api-id (TR코드)
|
|
224
|
+
path -> str: 요청 PATH경로(URL)
|
|
225
|
+
```
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
kiwoomRest/__init__.py,sha256=QGusLIiQqiPWAlKruu-G6T6G0STyyhG2jJyOaXCspJU,120
|
|
2
|
+
kiwoomRest/rest_api.py,sha256=W-cCUFTg1p5TIJR1d6bn391A_M9hgZYmLkkiTf6tZ0A,15975
|
|
3
|
+
kiwoomRest/tr_code_to_path.py,sha256=dpy5YqXyOiYGQgaW4tSo50_8ApKUSkpbYqCGOpZACGs,6781
|
|
4
|
+
kiwoomrest-0.0.1.dist-info/METADATA,sha256=gFxhsYYDg-03SmW-BPXz9-Att2Gx0VnXJPzLv6UGn2w,8136
|
|
5
|
+
kiwoomrest-0.0.1.dist-info/WHEEL,sha256=C2FUgwZgiLbznR-k0b_5k3Ai_1aASOXDss3lzCUsUug,87
|
|
6
|
+
kiwoomrest-0.0.1.dist-info/licenses/LICENSE,sha256=RGh27_uzSrWGoN1S0od88T_hkD8HqGDOBJCbBOccXKg,1087
|
|
7
|
+
kiwoomrest-0.0.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 teranum
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
22
|
+
|