ksxt 0.0.8__py3-none-any.whl → 0.0.9__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.
Files changed (141) hide show
  1. ksxt/__init__.py +3 -1
  2. ksxt/__pycache__/__init__.cpython-312.pyc +0 -0
  3. ksxt/__pycache__/bithumb.cpython-312.pyc +0 -0
  4. ksxt/__pycache__/koreainvest.cpython-312.pyc +0 -0
  5. ksxt/__pycache__/upbit.cpython-312.pyc +0 -0
  6. ksxt/api/__init__.py +26 -0
  7. ksxt/api/__pycache__/__init__.cpython-312.pyc +0 -0
  8. ksxt/api/__pycache__/bithumb.cpython-312.pyc +0 -0
  9. ksxt/api/__pycache__/koreainvest.cpython-312.pyc +0 -0
  10. ksxt/api/__pycache__/upbit.cpython-312.pyc +0 -0
  11. ksxt/api/auto/api_generator.py +54 -0
  12. ksxt/api/auto/bithumb.py +35 -0
  13. ksxt/api/auto/koreainvest.py +49 -0
  14. ksxt/api/auto/upbit.py +39 -0
  15. ksxt/api/bithumb.py +42 -0
  16. ksxt/api/koreainvest.py +40 -0
  17. ksxt/api/upbit.py +54 -0
  18. ksxt/async_/__init__.py +4 -0
  19. ksxt/async_/__pycache__/__init__.cpython-312.pyc +0 -0
  20. ksxt/async_/__pycache__/bithumb.cpython-312.pyc +0 -0
  21. ksxt/async_/__pycache__/koreainvest.cpython-312.pyc +0 -0
  22. ksxt/async_/__pycache__/upbit.cpython-312.pyc +0 -0
  23. ksxt/async_/base/__init__.py +0 -0
  24. ksxt/async_/base/__pycache__/__init__.cpython-312.pyc +0 -0
  25. ksxt/async_/base/__pycache__/async_exchange.cpython-312.pyc +0 -0
  26. ksxt/async_/base/__pycache__/throttler.cpython-312.pyc +0 -0
  27. ksxt/async_/base/async_exchange.py +232 -0
  28. ksxt/async_/base/throttler.py +63 -0
  29. ksxt/async_/bithumb.py +455 -0
  30. ksxt/async_/koreainvest.py +849 -0
  31. ksxt/async_/upbit.py +488 -0
  32. ksxt/base/__pycache__/__init__.cpython-312.pyc +0 -0
  33. ksxt/base/__pycache__/errors.cpython-312.pyc +0 -0
  34. ksxt/base/__pycache__/exchange.cpython-312.pyc +0 -0
  35. ksxt/base/__pycache__/rest_exchange.cpython-312.pyc +0 -0
  36. ksxt/base/__pycache__/types.cpython-312.pyc +0 -0
  37. ksxt/base/com_exchange.py +2 -2
  38. ksxt/base/errors.py +10 -0
  39. ksxt/base/exchange.py +188 -497
  40. ksxt/base/rest_exchange.py +297 -113
  41. ksxt/base/types.py +1 -36
  42. ksxt/bithumb.py +504 -0
  43. ksxt/config/__init__.py +2 -1
  44. ksxt/config/__pycache__/__init__.cpython-312.pyc +0 -0
  45. ksxt/config/bithumb.toml +380 -0
  46. ksxt/config/koreainvest.toml +312 -0
  47. ksxt/config/token.toml +7 -0
  48. ksxt/config/upbit.toml +428 -0
  49. ksxt/koreainvest.py +409 -1055
  50. ksxt/market/__pycache__/base.cpython-312.pyc +0 -0
  51. ksxt/market/__pycache__/db.cpython-312.pyc +0 -0
  52. ksxt/market/__pycache__/logging.cpython-312.pyc +0 -0
  53. ksxt/market/__pycache__/manager.cpython-312.pyc +0 -0
  54. ksxt/market/__pycache__/markets.cpython-312.pyc +0 -0
  55. ksxt/market/base.py +50 -50
  56. ksxt/market/db.py +5 -4
  57. ksxt/market/krx/__pycache__/kosdaq.cpython-312.pyc +0 -0
  58. ksxt/market/krx/__pycache__/kospi.cpython-312.pyc +0 -0
  59. ksxt/market/krx/__pycache__/stock.cpython-312.pyc +0 -0
  60. ksxt/market/krx/kosdaq.py +150 -147
  61. ksxt/market/krx/kospi.py +179 -175
  62. ksxt/market/krx/stock.py +136 -134
  63. ksxt/market/logging.py +4 -4
  64. ksxt/market/manager.py +10 -12
  65. ksxt/market/markets.py +1 -1
  66. ksxt/market/us/__pycache__/amex.cpython-312.pyc +0 -0
  67. ksxt/market/us/__pycache__/nasdaq.cpython-312.pyc +0 -0
  68. ksxt/market/us/__pycache__/nyse.cpython-312.pyc +0 -0
  69. ksxt/market/us/__pycache__/stock.cpython-312.pyc +0 -0
  70. ksxt/market/us/amex.py +31 -31
  71. ksxt/market/us/nasdaq.py +31 -31
  72. ksxt/market/us/nyse.py +31 -31
  73. ksxt/market/us/stock.py +20 -28
  74. ksxt/models/__init__.py +16 -0
  75. ksxt/models/__pycache__/__init__.cpython-312.pyc +0 -0
  76. ksxt/models/__pycache__/balance.cpython-312.pyc +0 -0
  77. ksxt/models/__pycache__/cash.cpython-312.pyc +0 -0
  78. ksxt/models/__pycache__/common.cpython-312.pyc +0 -0
  79. ksxt/models/__pycache__/error.cpython-312.pyc +0 -0
  80. ksxt/models/__pycache__/historical.cpython-312.pyc +0 -0
  81. ksxt/models/__pycache__/market.cpython-312.pyc +0 -0
  82. ksxt/models/__pycache__/order.cpython-312.pyc +0 -0
  83. ksxt/models/__pycache__/orderbook.cpython-312.pyc +0 -0
  84. ksxt/models/__pycache__/ticker.cpython-312.pyc +0 -0
  85. ksxt/models/__pycache__/token.cpython-312.pyc +0 -0
  86. ksxt/models/__pycache__/transaction.cpython-312.pyc +0 -0
  87. ksxt/models/balance.py +30 -0
  88. ksxt/models/cash.py +15 -0
  89. ksxt/models/common.py +31 -0
  90. ksxt/models/error.py +13 -0
  91. ksxt/models/historical.py +26 -0
  92. ksxt/models/market.py +81 -0
  93. ksxt/models/order.py +42 -0
  94. ksxt/models/orderbook.py +32 -0
  95. ksxt/models/ticker.py +25 -0
  96. ksxt/models/token.py +14 -0
  97. ksxt/models/transaction.py +79 -0
  98. ksxt/parser/__pycache__/bithumb.cpython-312.pyc +0 -0
  99. ksxt/parser/__pycache__/koreainvest.cpython-312.pyc +0 -0
  100. ksxt/parser/__pycache__/parser.cpython-312.pyc +0 -0
  101. ksxt/parser/__pycache__/upbit.cpython-312.pyc +0 -0
  102. ksxt/parser/bithumb.py +300 -0
  103. ksxt/parser/koreainvest.py +323 -0
  104. ksxt/parser/parser.py +114 -0
  105. ksxt/parser/upbit.py +308 -0
  106. ksxt/upbit.py +499 -0
  107. ksxt/utils/__pycache__/safer.cpython-312.pyc +0 -0
  108. ksxt/utils/__pycache__/sorter.cpython-312.pyc +0 -0
  109. ksxt/utils/__pycache__/timer.cpython-312.pyc +0 -0
  110. ksxt/utils/safer.py +48 -0
  111. ksxt/utils/sorter.py +8 -0
  112. ksxt/utils/timer.py +47 -0
  113. {ksxt-0.0.8.dist-info → ksxt-0.0.9.dist-info}/METADATA +11 -1
  114. ksxt-0.0.9.dist-info/RECORD +119 -0
  115. {ksxt-0.0.8.dist-info → ksxt-0.0.9.dist-info}/WHEEL +1 -1
  116. ksxt/__pycache__/__init__.cpython-39.pyc +0 -0
  117. ksxt/__pycache__/koreainvest.cpython-39.pyc +0 -0
  118. ksxt/base/__pycache__/__init__.cpython-39.pyc +0 -0
  119. ksxt/base/__pycache__/exchange.cpython-39.pyc +0 -0
  120. ksxt/base/__pycache__/rest_exchange.cpython-39.pyc +0 -0
  121. ksxt/base/__pycache__/restexchange.cpython-39.pyc +0 -0
  122. ksxt/base/__pycache__/types.cpython-39.pyc +0 -0
  123. ksxt/base/api_response.py +0 -68
  124. ksxt/config/__pycache__/__init__.cpython-39.pyc +0 -0
  125. ksxt/config/tr_app.json +0 -381
  126. ksxt/config/tr_dev.json +0 -446
  127. ksxt/market/__pycache__/base.cpython-39.pyc +0 -0
  128. ksxt/market/__pycache__/db.cpython-39.pyc +0 -0
  129. ksxt/market/__pycache__/logging.cpython-39.pyc +0 -0
  130. ksxt/market/__pycache__/manager.cpython-39.pyc +0 -0
  131. ksxt/market/__pycache__/markets.cpython-39.pyc +0 -0
  132. ksxt/market/krx/__pycache__/kosdaq.cpython-39.pyc +0 -0
  133. ksxt/market/krx/__pycache__/kospi.cpython-39.pyc +0 -0
  134. ksxt/market/krx/__pycache__/stock.cpython-39.pyc +0 -0
  135. ksxt/market/us/__pycache__/amex.cpython-39.pyc +0 -0
  136. ksxt/market/us/__pycache__/nasdaq.cpython-39.pyc +0 -0
  137. ksxt/market/us/__pycache__/nyse.cpython-39.pyc +0 -0
  138. ksxt/market/us/__pycache__/stock.cpython-39.pyc +0 -0
  139. ksxt-0.0.8.dist-info/RECORD +0 -49
  140. {ksxt-0.0.8.dist-info → ksxt-0.0.9.dist-info}/LICENSE.txt +0 -0
  141. {ksxt-0.0.8.dist-info → ksxt-0.0.9.dist-info}/top_level.txt +0 -0
ksxt/base/exchange.py CHANGED
@@ -2,12 +2,15 @@ import collections
2
2
  from distutils.util import strtobool
3
3
  import json
4
4
  from datetime import datetime
5
+ import time
5
6
  import pytz
6
7
  from typing import Dict, Optional
7
8
 
8
9
  from requests import Session
10
+ import urllib.parse as _urlencode
9
11
 
10
12
  from ksxt.base.types import IndexType
13
+ from ksxt.models.common import CommonResponse, CommonResponseHeader
11
14
 
12
15
 
13
16
  class Exchange:
@@ -17,19 +20,20 @@ class Exchange:
17
20
  is_dev = False
18
21
 
19
22
  session = None
20
- timeout = 10000 # milliseconds = seconds * 1000
23
+ timeout = 10000 # milliseconds = seconds * 1000
24
+ synchronous = True
21
25
 
22
26
  required_credentials = {
23
- 'open_key': False,
24
- 'secret_key': False,
25
- 'uid': False,
26
- 'login': False,
27
- 'password': False,
28
- 'token': False
27
+ "open_key": False,
28
+ "secret_key": False,
29
+ "uid": False,
30
+ "login": False,
31
+ "password": False,
32
+ "token": False,
29
33
  }
30
34
 
31
35
  def __init__(self):
32
- if not self.session:
36
+ if not self.session and self.synchronous:
33
37
  self.session = Session()
34
38
 
35
39
  def __del__(self):
@@ -52,30 +56,7 @@ class Exchange:
52
56
  """
53
57
  pass
54
58
 
55
- def parse_market(self, response: dict, base_market: str= 'KRW'):
56
- return {
57
- 'response': {
58
- # 성공 실패 여부
59
- 'success' : 0,
60
- # 응답코드
61
- 'code': '',
62
- # 응답메세지
63
- 'message': '',
64
- },
65
- # 원본 데이터
66
- 'info': response,
67
-
68
- # 종목 코드
69
- 'symbol': self.safe_value(response, ''),
70
- # 종목 이름
71
- 'name': self.safe_value(response, ''),
72
- # 거래소 정보 (KOSPI, KOSDAQ, NYSE, NASDAQ, AMEX, .....)
73
- 'exchange': self.safe_value(response, ''),
74
- # 거래 통화
75
- 'currency': self.safe_value(response, ''),
76
- }
77
-
78
- def fetch_security(self, symbol: str, base_market: str = 'KRW'):
59
+ def fetch_security(self, symbol: str, base_market: str = "KRW"):
79
60
  """
80
61
  종목 정보 조회
81
62
 
@@ -85,30 +66,7 @@ class Exchange:
85
66
  """
86
67
  pass
87
68
 
88
- def parse_security(self, response, base_market: str = 'KRW'):
89
- return {
90
- 'response': {
91
- # 성공 실패 여부
92
- 'success' : 0,
93
- # 응답코드
94
- 'code': '',
95
- # 응답메세지
96
- 'message': '',
97
- },
98
- # 원본 데이터
99
- 'info': response,
100
-
101
- # 종목 코드
102
- 'symbol': self.safe_value(response, ''),
103
- # 종목 이름
104
- 'name': self.safe_value(response, ''),
105
- # 거래소 정보 (KOSPI, KOSDAQ, NYSE, NASDAQ, AMEX, .....)
106
- 'exchange': self.safe_value(response, ''),
107
- # 거래 통화
108
- 'currency': self.safe_value(response, ''),
109
- }
110
-
111
- def fetch_ticker(self, symbol: str, base_market: str= 'KRW'):
69
+ def fetch_ticker(self, symbol: str, base_market: str = "KRW"):
112
70
  """
113
71
  시세 정보 조회
114
72
 
@@ -118,26 +76,14 @@ class Exchange:
118
76
  """
119
77
  pass
120
78
 
121
- def parse_ticker(self, response: dict, base_market: str= 'KRW'):
122
- return {
123
- 'response': {
124
- # 성공 실패 여부
125
- 'success' : 0,
126
- # 응답코드
127
- 'code': '',
128
- # 응답메세지
129
- 'message': '',
130
- },
131
- # 원본 데이터
132
- 'info': response,
133
-
134
- # 종목 코드
135
- 'symbol': self.safe_value(response, ''),
136
- # 현재가
137
- 'price': self.safe_value(response, ''),
138
- }
139
-
140
- def fetch_historical_data(self, symbol: str, time_frame: str, start: Optional[str] = None, end: Optional[str] = None, base_market: str= 'KRW'):
79
+ def fetch_historical_data(
80
+ self,
81
+ symbol: str,
82
+ time_frame: str,
83
+ start: Optional[str] = None,
84
+ end: Optional[str] = None,
85
+ base_market: str = "KRW",
86
+ ):
141
87
  """
142
88
  과거 봉 정보 조회
143
89
 
@@ -150,98 +96,13 @@ class Exchange:
150
96
  """
151
97
  pass
152
98
 
153
- def parse_historical_data(self, response: dict, base_market: str= 'KRW'):
154
- return {
155
- 'response': {
156
- # 성공 실패 여부
157
- 'success' : 0,
158
- # 응답코드
159
- 'code': '',
160
- # 응답메세지
161
- 'message': '',
162
- },
163
- # 원본 데이터
164
- 'info': response,
165
-
166
- 'history': [
167
- {
168
- # 종목코드
169
- 'symbol': self.safe_value(response, ''),
170
- # 날짜
171
- 'date': self.safe_string(response, ''),
172
- # 시가
173
- 'open': self.safe_value(response, ''),
174
- # 고가
175
- 'high': self.safe_value(response, ''),
176
- # 저가
177
- 'low': self.safe_value(response, ''),
178
- # 종가
179
- 'close': self.safe_value(response, ''),
180
- # 거래량
181
- 'volume': self.safe_value(response, ''),
182
- # 거래대금
183
- 'amount': self.safe_value(response, '')
184
- },
185
- {
186
- # 종목코드
187
- 'symbol': self.safe_value(response, ''),
188
- # 날짜
189
- 'date': self.safe_string(response, ''),
190
- # 시가
191
- 'open': self.safe_value(response, ''),
192
- # 고가
193
- 'high': self.safe_value(response, ''),
194
- # 저가
195
- 'low': self.safe_value(response, ''),
196
- # 종가
197
- 'close': self.safe_value(response, ''),
198
- # 거래량
199
- 'volume': self.safe_value(response, ''),
200
- # 거래대금
201
- 'amount': self.safe_value(response, '')
202
- },
203
- ]
204
-
205
- }
206
-
207
- def parse_ohlcv(self, ohlcv, base_market: str= 'KRW'):
208
- if isinstance(ohlcv, list):
209
- return [
210
- self.safe_string(ohlcv, 0), # timestamp
211
- self.safe_number(ohlcv, 1), # open
212
- self.safe_number(ohlcv, 2), # high
213
- self.safe_number(ohlcv, 3), # low
214
- self.safe_number(ohlcv, 4), # close
215
- self.safe_number(ohlcv, 5), # volume
216
- ]
217
- return ohlcv
218
-
219
- def parse_ohlcva(self, ohlcva, base_market: str= 'KRW'):
220
- if isinstance(ohlcva, list):
221
- return [
222
- self.safe_string(ohlcva, 0), # timestamp
223
- self.safe_number(ohlcva, 1), # open
224
- self.safe_number(ohlcva, 2), # high
225
- self.safe_number(ohlcva, 3), # low
226
- self.safe_number(ohlcva, 4), # close
227
- self.safe_number(ohlcva, 5), # volume
228
- self.safe_number(ohlcva, 6), # amount
229
- ]
230
- return ohlcva
231
-
232
99
  def resample(self, df, timeframe: str, offset):
233
- ohlcv = {
234
- 'open': 'first',
235
- 'high': 'max',
236
- 'low': 'min',
237
- 'close': 'last',
238
- 'volume': 'sum'
239
- }
240
-
100
+ ohlcv = {"open": "first", "high": "max", "low": "min", "close": "last", "volume": "sum"}
101
+
241
102
  result = df.resample(timeframe.upper(), offset=offset).apply(ohlcv)
242
103
  return result
243
-
244
- def fetch_is_holiday(self, dt: datetime, base_market: str= 'KRW'):
104
+
105
+ def fetch_is_holiday(self, dt: datetime, base_market: str = "KRW"):
245
106
  """
246
107
  휴장일 조회
247
108
 
@@ -251,29 +112,10 @@ class Exchange:
251
112
  """
252
113
  pass
253
114
 
254
- def parse_is_holiday(self, response: dict):
255
- return {
256
- 'response': {
257
- # 성공 실패 여부
258
- 'success' : 0,
259
- # 응답코드
260
- 'code': '',
261
- # 응답메세지
262
- 'message': '',
263
- },
264
- # 원본 데이터
265
- 'info': response,
266
-
267
- # 날짜 (YYYYMMDD)
268
- 'date': self.safe_string(response, ''),
269
- # 개장일 여부 (Y/N)
270
- 'is_open': self.safe_boolean(response, ''),
271
- }
272
-
273
115
  # endregion public feeder
274
116
 
275
117
  # region private feeder
276
- def fetch_user_info(self, base_market: str= 'KRW'):
118
+ def fetch_user_info(self, base_market: str = "KRW"):
277
119
  """
278
120
  회원 정보 조회
279
121
 
@@ -281,8 +123,18 @@ class Exchange:
281
123
  base_market (str, optional): Market 구분 코드. Defaults to 'KRW'.
282
124
  """
283
125
  pass
284
-
285
- def fetch_balance(self, acc_num: str, base_market: str= 'KRW'):
126
+
127
+ def fetch_trade_fee(self, symbol: str, base_market: str = "KRW"):
128
+ """
129
+ 종목의 거래 수수료 정보 조회
130
+
131
+ Args:
132
+ symbol (str): 종목코드
133
+ base_market (str, optional): Market 구분 코드. Defaults to 'KRW'.
134
+ """
135
+ pass
136
+
137
+ def fetch_balance(self, acc_num: str, base_market: str = "KRW"):
286
138
  """
287
139
  보유 자산 조회
288
140
 
@@ -292,103 +144,7 @@ class Exchange:
292
144
  """
293
145
  pass
294
146
 
295
- def parse_balance(self, response: dict, base_market: str= 'KRW'):
296
- return {
297
- 'response': {
298
- # 성공 실패 여부
299
- 'success' : 0,
300
- # 응답코드
301
- 'code': '',
302
- # 응답메세지
303
- 'message': '',
304
- },
305
- # 원본 데이터
306
- 'info': response,
307
-
308
- 'balance': [
309
- {
310
- # 종목코드
311
- 'symbol': self.safe_string(response, ''),
312
- # 종목명
313
- 'name': self.safe_string(response, ''),
314
- # Long or Short
315
- 'position': self.safe_string(response, ''),
316
- # 매입평균가격
317
- 'price': self.safe_number(response, ''),
318
- 'qty': {
319
- # 매입 수량
320
- 'total': self.safe_number(response, ''),
321
- # 주문 가능 수량
322
- 'free': self.safe_number(response, ''),
323
- # 기주문 수량 (total - free)
324
- 'used': self.safe_number(response, '') - self.safe_number(response, '')
325
- },
326
- # 매입금액
327
- 'amount': self.safe_number(response, '')
328
- },
329
- {
330
- # 종목코드
331
- 'symbol': self.safe_string(response, ''),
332
- # 종목명
333
- 'name': self.safe_string(response, ''),
334
- # Long or Short
335
- 'position': self.safe_string(response, ''),
336
- # 매입평균가격
337
- 'price': self.safe_number(response, ''),
338
- 'qty': {
339
- # 매입 수량
340
- 'total': self.safe_number(response, ''),
341
- # 주문 가능 수량
342
- 'free': self.safe_number(response, ''),
343
- # 기주문 수량 (total - free)
344
- 'used': self.safe_number(response, '') - self.safe_number(response, '')
345
- },
346
- # 매입금액
347
- 'amount': self.safe_number(response, '')
348
- }
349
- ]
350
- }
351
-
352
- def _parse_balance(self, balance, base_market: str= 'KRW'):
353
- if isinstance(balance, list):
354
- return {
355
- 'symbol': self.safe_string(balance, 0),
356
- 'name': self.safe_string(balance, 1),
357
- 'position': self.safe_string(balance, 2),
358
- 'price': self.safe_number(balance, 3),
359
- 'qty':
360
- {
361
- # 매입 수량
362
- 'total': self.safe_number(balance, 4),
363
- # 주문 가능 수량
364
- 'free': self.safe_number(balance, 5),
365
- # 기주문 수량 (total - free)
366
- 'used': self.safe_number(balance, 4) - self.safe_number(balance, 5)
367
- },
368
- 'amount': self.safe_number(balance, 6),
369
- }
370
-
371
- if isinstance(balance, dict):
372
- return {
373
- 'symbol': self.safe_string(balance, ''),
374
- 'name': self.safe_string(balance, ''),
375
- 'position': self.safe_string(balance, ''),
376
- 'price': self.safe_number(balance, ''),
377
- 'qty':
378
- {
379
- # 매입 수량
380
- 'total': self.safe_number(balance, ''),
381
- # 주문 가능 수량
382
- 'free': self.safe_number(balance, ''),
383
- # 기주문 수량 (total - free)
384
- 'used': self.safe_number(balance, '') - self.safe_number(balance, '')
385
- },
386
- 'amount': self.safe_number(balance, ''),
387
- }
388
-
389
- return balance
390
-
391
- def fetch_cash(self, acc_num: str, base_market: str= 'KRW'):
147
+ def fetch_cash(self, acc_num: str, base_market: str = "KRW"):
392
148
  """
393
149
  예수금 조회
394
150
 
@@ -398,24 +154,7 @@ class Exchange:
398
154
  """
399
155
  pass
400
156
 
401
- def parse_cash(self, response: dict, base_market: str= 'KRW'):
402
- return {
403
- 'response': {
404
- # 성공 실패 여부
405
- 'success' : 0,
406
- # 응답코드
407
- 'code': '',
408
- # 응답메세지
409
- 'message': '',
410
- },
411
- # 원본 데이터
412
- 'info': response,
413
-
414
- # 정산금액 (KR: D+2 예수금)
415
- 'cash': self.safe_number(response, '')
416
- }
417
-
418
- def fetch_screener_list(self, base_market: str= 'KRW'):
157
+ def fetch_screener_list(self, base_market: str = "KRW"):
419
158
  """
420
159
  조건식 리스트 조회
421
160
 
@@ -423,8 +162,8 @@ class Exchange:
423
162
  _type_: 조건식 리스트
424
163
  """
425
164
  pass
426
-
427
- def fetch_screener(self, screen_id: str, base_market: str= 'KRW'):
165
+
166
+ def fetch_screener(self, screen_id: str, base_market: str = "KRW"):
428
167
  """
429
168
  조건식 조회 결과
430
169
 
@@ -433,8 +172,8 @@ class Exchange:
433
172
  base_market (str, optional): Market 구분 코드. Defaults to 'KRW'.
434
173
  """
435
174
  pass
436
-
437
- def fetch_deposit_history(self, acc_num: str, base_market: str= 'KRW'):
175
+
176
+ def fetch_deposit_history(self, acc_num: str, base_market: str = "KRW"):
438
177
  """
439
178
  입금 내역 조회
440
179
 
@@ -443,8 +182,8 @@ class Exchange:
443
182
  base_market (str, optional): Market 구분 코드. Defaults to 'KRW'.
444
183
  """
445
184
  pass
446
-
447
- def fetch_withdrawal_history(self, acc_num: str, base_market: str= 'KRW'):
185
+
186
+ def fetch_withdrawal_history(self, acc_num: str, base_market: str = "KRW"):
448
187
  """
449
188
  출금 내역 조회
450
189
 
@@ -453,25 +192,39 @@ class Exchange:
453
192
  base_market (str, optional): Market 구분 코드. Defaults to 'KRW'.
454
193
  """
455
194
  pass
195
+
456
196
  # endregion private feeder
457
197
 
458
198
  # region broker
459
- def create_order(self, acc_num: str, symbol: str, ticket_type: str, price: float, qty: float, otype: str, base_market: str= 'KRW'):
199
+ def create_order(
200
+ self,
201
+ acc_num: str,
202
+ symbol: str,
203
+ ticket_type: str,
204
+ price: float,
205
+ qty: float,
206
+ amount: float,
207
+ otype: str,
208
+ base_market: str = "KRW",
209
+ ):
460
210
  """
461
211
  주문 발행
462
212
 
463
213
  Args:
464
214
  acc_num (str): 계좌정보(계좌번호, 지갑정보)
465
215
  symbol (str): 종목정보(종목코드)
466
- ticket_type (str): EntryLong, EntryShort, ExitLong, ExitShort, ...
467
- price (float): 가격
468
- qty (float): 수량
469
- otype (str): 시장가, 지정가, ...
216
+ ticket_type (str): EntryLong, EntryShort, ExitLong, ExitShort, ...
217
+ price (float): 가격 (지정가 시 필수)
218
+ qty (float): 수량 (매도 시 필수.)
219
+ amount (float): 총금액 (시장가 매수 시 필수)
220
+ otype (str): market(시장가), limit(지정가), ...
470
221
  base_market (str, optional): Market 구분 코드. Defaults to 'KRW'.
471
222
  """
472
223
  pass
473
224
 
474
- def cancel_order(self, acc_num: str, order_id: str, symbol: Optional[str] = '', qty: float = 0, *args, base_market: str= 'KRW'):
225
+ def cancel_order(
226
+ self, acc_num: str, order_id: str, symbol: Optional[str] = "", qty: float = 0, *args, base_market: str = "KRW"
227
+ ):
475
228
  """
476
229
  미체결 주문 취소
477
230
 
@@ -483,45 +236,15 @@ class Exchange:
483
236
  base_market (str, optional): Market 구분 코드. Defaults to 'KRW'.
484
237
  """
485
238
  pass
486
-
487
- def modify_order(self, acc_num: str, order_id: str, price: float, qty: float, *args, symbol: Optional[str] = '', base_market: str= 'KRW'):
488
- """
489
- 미체결 주문 정정
490
-
491
- Args:
492
- acc_num (str): 계좌정보(계좌번호, 지갑정보)
493
- order_id (str): 주문 정보(주문 id)
494
- price (float): 가격
495
- qty (float): 수량
496
- symbol (str, optional): 종목정보(종목코드). Defaults to ''.
497
- base_market (str, optional): Market 구분 코드. Defaults to 'KRW'.
498
- """
499
- pass
500
-
501
- def parse_order_response(self, response: dict, base_market: str='KRW'):
502
- time = self.safe_string(response, '')
503
- today = datetime.today()
504
- dt = datetime.combine(today, datetime.strptime(time, '%H%M%S').time())
505
-
506
- return {
507
- 'response': {
508
- # 성공 실패 여부
509
- 'success' : 0,
510
- # 응답코드
511
- 'code': '',
512
- # 응답메세지
513
- 'message': '',
514
- },
515
- # 원본 데이터
516
- 'info': response,
517
239
 
518
- # 주문 날짜 (YYYY-mm-DD HH:MM:SS)
519
- 'datetime': datetime.strftime(dt, '%Y-%m-%d %H:%M:%S'),
520
- # 주문번호
521
- 'order_id': self.safe_string(response, ''),
522
- }
523
-
524
- def fetch_open_order(self, acc_num: str, symbol: Optional[str] = '', start: Optional[str] = None, end: Optional[str] = None, base_market: str= 'KRW'):
240
+ def fetch_open_order(
241
+ self,
242
+ acc_num: str,
243
+ symbol: Optional[str] = "",
244
+ start: Optional[str] = None,
245
+ end: Optional[str] = None,
246
+ base_market: str = "KRW",
247
+ ):
525
248
  """
526
249
  미체결 주문 내역 조회
527
250
 
@@ -534,60 +257,14 @@ class Exchange:
534
257
  """
535
258
  pass
536
259
 
537
- def parse_open_order(self, response: dict, base_market: str='KRW'):
538
- data = self.safe_value(response, '')
539
- if data is None:
540
- return response
541
-
542
- orders = [self.parse_open_order_history(_) for _ in data]
543
- sorted_orders = self.sort_by(orders, 0)
544
-
545
- return {
546
- 'response': {
547
- # 성공 실패 여부
548
- 'success' : 0,
549
- # 응답코드
550
- 'code': '',
551
- # 응답메세지
552
- 'message': '',
553
- },
554
- # 원본 데이터
555
- 'info': response,
556
- # 주문 정보
557
- 'orders': sorted_orders
558
- }
559
-
560
- def parse_open_order_history(self, order:dict, base_market: str='KRW'):
561
- date = self.safe_string(order, '')
562
- time = self.safe_string(order, '')
563
- dt = datetime.combine(datetime.strptime(date, '%Y%m%d'), datetime.strptime(time, '%H%M%S').time())
564
-
565
- return {
566
- # 주문 날짜 (YYYY-mm-DD HH:MM:SS)
567
- 'datetime': datetime.strftime(dt, '%Y-%m-%d %H:%M:%S'),
568
- # 주문번호
569
- 'order_id': self.safe_string(order, ''),
570
- # 원주문번호
571
- 'org_order_id': self.safe_string(order, ''),
572
- # 주문구분
573
- 'order_type': self.safe_string(order, ''),
574
- # long or short
575
- 'position': self.safe_string(order, ''),
576
- # 종목코드
577
- 'symbol': self.safe_string(order, ''),
578
- # 주문단가
579
- 'price': self.safe_number(order, ''),
580
- 'qty': {
581
- # 주문수량
582
- 'total': self.safe_number(order, ''),
583
- # 체결수량
584
- 'used': self.safe_number(order, ''),
585
- # 잔여수량
586
- 'free': self.safe_number(order, '')
587
- }
588
- }
589
-
590
- def fetch_closed_order(self, acc_num: str, symbol: Optional[str] = '', start: Optional[str] = None, end: Optional[str] = None, base_market: str= 'KRW'):
260
+ def fetch_closed_order(
261
+ self,
262
+ acc_num: str,
263
+ symbol: Optional[str] = "",
264
+ start: Optional[str] = None,
265
+ end: Optional[str] = None,
266
+ base_market: str = "KRW",
267
+ ):
591
268
  """
592
269
  체결 주문 내역 조회
593
270
 
@@ -600,54 +277,9 @@ class Exchange:
600
277
  """
601
278
  pass
602
279
 
603
- def parse_closed_order(self, response: dict, base_market: str='KRW'):
604
- data = self.safe_value(response, '')
605
- if data is None:
606
- return response
607
-
608
- orders = [self.parse_closed_order_history(_) for _ in data]
609
- sorted_orders = self.sort_by(orders, 0)
610
-
611
- return {
612
- 'response': {
613
- # 성공 실패 여부
614
- 'success' : 0,
615
- # 응답코드
616
- 'code': '',
617
- # 응답메세지
618
- 'message': '',
619
- },
620
- # 원본 데이터
621
- 'info': response,
622
- # 주문 정보
623
- 'orders': sorted_orders
624
- }
625
-
626
- def parse_closed_order_history(self, order:dict, base_market: str='KRW'):
627
- date = self.safe_string(order, '')
628
- time = self.safe_string(order, '')
629
- dt = datetime.combine(datetime.strptime(date, '%Y%m%d'), datetime.strptime(time, '%H%M%S').time())
630
-
631
- return {
632
- # 주문 날짜 (YYYY-mm-DD HH:MM:SS)
633
- 'datetime': datetime.strftime(dt, '%Y-%m-%d %H:%M:%S'),
634
- # 주문번호
635
- 'order_id': self.safe_string(order, ''),
636
- # 원주문번호
637
- 'org_order_id': self.safe_string(order, ''),
638
- # 주문구분
639
- 'order_type': self.safe_string(order, ''),
640
- # long or short
641
- 'position': self.safe_string(order, ''),
642
- # 종목코드
643
- 'symbol': self.safe_string(order, ''),
644
- # 평균가
645
- 'price': self.safe_number(order, ''),
646
- # 체결수량
647
- 'qty': self.safe_number(order, '')
648
- }
649
-
650
- def reserve_order(self, acc_num:str, symbol: str, price: float, qty: float, target_date: str, base_market: str= 'KRW'):
280
+ def reserve_order(
281
+ self, acc_num: str, symbol: str, price: float, qty: float, target_date: str, base_market: str = "KRW"
282
+ ):
651
283
  """
652
284
  예약 주문 발행
653
285
 
@@ -660,29 +292,75 @@ class Exchange:
660
292
  base_market (str, optional): Market 구분 코드. Defaults to 'KRW'.
661
293
  """
662
294
  pass
295
+
663
296
  # endregion broker
664
297
 
665
- def get_error_response(self, error_code, error_message):
298
+ # TODO : @abstractmethod로 제약을 하는게 나을까?는 고민 필요.
299
+ def get_success_response(self, result):
300
+ pass
301
+
302
+ def get_success_response(self, response_code, msg_code, msg, org_data):
666
303
  return {
667
- 'response': {
304
+ "response": {
668
305
  # 성공 실패 여부
669
- 'success' : '-1',
306
+ "success": response_code,
670
307
  # 응답코드
671
- 'code': error_code,
308
+ "code": msg_code,
672
309
  # 응답메세지
673
- 'message': error_message,
674
- }
310
+ "message": msg,
311
+ # 원본 데이터
312
+ "info": org_data,
313
+ },
314
+ "header": {},
675
315
  }
676
-
316
+
317
+ def get_error_response(self, error_code, error_message, org_data=None):
318
+ return {
319
+ "response": {
320
+ # 성공 실패 여부
321
+ "success": "-1",
322
+ # 응답코드
323
+ "code": error_code,
324
+ # 응답메세지
325
+ "message": error_message,
326
+ # 원본 데이터
327
+ "info": org_data,
328
+ },
329
+ "header": {},
330
+ }
331
+
332
+ def update_success_response_header(self, response: dict, header: dict):
333
+ response["header"].update(header)
334
+ return response
335
+
336
+ def update_success_response_body(self, response: dict, body: dict):
337
+ response.update(body)
338
+ return response
339
+
340
+ def create_common_response(self, success: str, msg_code: str, msg: str, info: dict) -> CommonResponse:
341
+ return CommonResponse(success=success, msg_code=msg_code, msg=msg, info=info)
342
+
343
+ def create_common_header(self, request_params: dict) -> CommonResponseHeader:
344
+ return CommonResponseHeader(request_params=request_params)
345
+
346
+ # region utils
677
347
  @staticmethod
678
- def now(base_market: str = 'KRW'):
679
- if base_market == 'KRW':
680
- return datetime.now(tz=pytz.timezone('Asia/Seoul'))
681
- elif base_market == 'USD':
682
- return datetime.now(tz=pytz.timezone('US/Eastern'))
348
+ def now(base_market: str = "KRW"):
349
+ if base_market == "KRW":
350
+ return datetime.now(tz=pytz.timezone("Asia/Seoul"))
351
+ elif base_market == "USD":
352
+ return datetime.now(tz=pytz.timezone("US/Eastern"))
683
353
  else:
684
354
  return datetime.now(tz=pytz.utc)
685
355
 
356
+ @staticmethod
357
+ def seconds():
358
+ return int(time.time())
359
+
360
+ @staticmethod
361
+ def milliseconds():
362
+ return int(time.time() * 1_000)
363
+
686
364
  @staticmethod
687
365
  def set_attr(self, attrs):
688
366
  for key in attrs:
@@ -699,12 +377,12 @@ class Exchange:
699
377
  result = collections.OrderedDict()
700
378
  else:
701
379
  result = {}
702
-
380
+
703
381
  for arg in args:
704
382
  result.update(arg)
705
383
 
706
384
  return result
707
-
385
+
708
386
  return {}
709
387
 
710
388
  @staticmethod
@@ -720,16 +398,7 @@ class Exchange:
720
398
  result = arg
721
399
 
722
400
  return result
723
-
724
- @staticmethod
725
- def implode_params(string, params):
726
- if isinstance(params, dict):
727
- for key in params:
728
- if not isinstance(params[key], list):
729
- string = string.replace('{' + key + '}', str(params[key]))
730
401
 
731
- return string
732
-
733
402
  @staticmethod
734
403
  def omit(d, *args):
735
404
  if isinstance(d, dict):
@@ -744,51 +413,67 @@ class Exchange:
744
413
  del result[arg]
745
414
  return result
746
415
  return d
747
-
748
- def implode_hostname(self, url: str, hostname):
749
- return self.implode_params(url, {'hostname': hostname})
416
+
417
+ @staticmethod
418
+ def implode_params(string, params):
419
+ if isinstance(params, dict):
420
+ for key in params:
421
+ if not isinstance(params[key], list):
422
+ string = string.replace("{" + key + "}", str(params[key]))
423
+ return string
424
+
425
+ @staticmethod
426
+ def urlencode(params={}, doseq=False):
427
+ new_params = params.copy()
428
+ for key, value in params.items():
429
+ if isinstance(value, bool):
430
+ new_params[key] = "true" if value else "false"
431
+ return _urlencode.urlencode(new_params, doseq, quote_via=_urlencode.quote)
432
+
433
+ def calculate_rate_limiter_cost(self, api, method, path, params, config={}):
434
+ return self.safe_value(config, "cost", 1)
750
435
 
751
436
  def parse_json(self, http_response):
752
437
  return json.loads(http_response, parse_float=str, parse_int=str)
753
-
438
+
754
439
  # region safe method
755
440
  @staticmethod
756
441
  def key_exists(dictionary, key):
757
- if hasattr(dictionary, '__getitem__') and not isinstance(dictionary, str):
442
+ if hasattr(dictionary, "__getitem__") and not isinstance(dictionary, str):
758
443
  if isinstance(dictionary, list) and type(key) is not int:
759
444
  return False
760
445
  try:
761
446
  value = dictionary[key]
762
- return value is not None and value != ''
447
+ return value is not None and value != ""
763
448
  except LookupError:
764
449
  return False
765
450
  return False
766
-
451
+
767
452
  @staticmethod
768
453
  def safe_value(dictionary, key, default_value=None):
769
454
  return dictionary[key] if Exchange.key_exists(dictionary, key) else default_value
770
-
455
+
771
456
  @staticmethod
772
- def safe_string(dictionary, key, default_value=''):
457
+ def safe_string(dictionary, key, default_value=""):
773
458
  return str(dictionary[key]) if Exchange.key_exists(dictionary, key) else default_value
774
-
459
+
775
460
  @staticmethod
776
461
  def safe_number(dictionary, key, default_value=0):
777
462
  value = Exchange.safe_string(dictionary, key)
778
- if value == '':
463
+ if value == "":
779
464
  return default_value
780
-
465
+
781
466
  try:
782
467
  return float(value)
783
468
  except Exception:
784
469
  return default_value
785
-
470
+
786
471
  @staticmethod
787
472
  def safe_boolean(dictionary, key, default_value=False):
788
473
  value = Exchange.safe_string(dictionary, key)
789
- if value == '':
474
+ if value == "":
790
475
  return default_value
791
-
476
+
792
477
  try:
793
478
  return bool(strtobool(value))
794
479
  except Exception:
@@ -806,7 +491,11 @@ class Exchange:
806
491
 
807
492
  @staticmethod
808
493
  def sort_by_2(array, key1, key2, descending=False):
809
- return sorted(array, key=lambda k: (k[key1] if k[key1] is not None else "", k[key2] if k[key2] is not None else ""), reverse=descending)
494
+ return sorted(
495
+ array,
496
+ key=lambda k: (k[key1] if k[key1] is not None else "", k[key2] if k[key2] is not None else ""),
497
+ reverse=descending,
498
+ )
810
499
 
811
500
  @staticmethod
812
501
  def index_by(array, key):
@@ -819,7 +508,7 @@ class Exchange:
819
508
  k = element[key]
820
509
  result[k] = element
821
510
  return result
822
-
511
+
823
512
  @staticmethod
824
513
  def to_array(value):
825
514
  return list(value.values()) if type(value) is dict else value
@@ -830,10 +519,12 @@ class Exchange:
830
519
  # return all of them if no values were passed
831
520
  if values is None or not values:
832
521
  return self.index_by(objects, key) if indexed else objects
833
-
522
+
834
523
  results = []
835
524
  for i in range(0, len(objects)):
836
525
  if self.in_array(objects[i][key], values):
837
526
  results.append(objects[i])
838
-
839
- return self.index_by(results, key) if indexed else results
527
+
528
+ return self.index_by(results, key) if indexed else results
529
+
530
+ # endregion