ksxt 0.0.8__py3-none-any.whl → 0.0.10__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.
- ksxt/__init__.py +3 -1
- ksxt/__pycache__/__init__.cpython-312.pyc +0 -0
- ksxt/__pycache__/bithumb.cpython-312.pyc +0 -0
- ksxt/__pycache__/koreainvest.cpython-312.pyc +0 -0
- ksxt/__pycache__/upbit.cpython-312.pyc +0 -0
- ksxt/api/__init__.py +26 -0
- ksxt/api/__pycache__/__init__.cpython-312.pyc +0 -0
- ksxt/api/__pycache__/bithumb.cpython-312.pyc +0 -0
- ksxt/api/__pycache__/koreainvest.cpython-312.pyc +0 -0
- ksxt/api/__pycache__/upbit.cpython-312.pyc +0 -0
- ksxt/api/auto/api_generator.py +54 -0
- ksxt/api/auto/bithumb.py +35 -0
- ksxt/api/auto/koreainvest.py +49 -0
- ksxt/api/auto/upbit.py +39 -0
- ksxt/api/bithumb.py +42 -0
- ksxt/api/koreainvest.py +40 -0
- ksxt/api/upbit.py +54 -0
- ksxt/async_/__init__.py +4 -0
- ksxt/async_/__pycache__/__init__.cpython-312.pyc +0 -0
- ksxt/async_/__pycache__/bithumb.cpython-312.pyc +0 -0
- ksxt/async_/__pycache__/koreainvest.cpython-312.pyc +0 -0
- ksxt/async_/__pycache__/upbit.cpython-312.pyc +0 -0
- ksxt/async_/base/__init__.py +0 -0
- ksxt/async_/base/__pycache__/__init__.cpython-312.pyc +0 -0
- ksxt/async_/base/__pycache__/async_exchange.cpython-312.pyc +0 -0
- ksxt/async_/base/__pycache__/throttler.cpython-312.pyc +0 -0
- ksxt/async_/base/async_exchange.py +232 -0
- ksxt/async_/base/throttler.py +63 -0
- ksxt/async_/bithumb.py +455 -0
- ksxt/async_/koreainvest.py +849 -0
- ksxt/async_/upbit.py +488 -0
- ksxt/base/__pycache__/__init__.cpython-312.pyc +0 -0
- ksxt/base/__pycache__/errors.cpython-312.pyc +0 -0
- ksxt/base/__pycache__/exchange.cpython-312.pyc +0 -0
- ksxt/base/__pycache__/rest_exchange.cpython-312.pyc +0 -0
- ksxt/base/__pycache__/types.cpython-312.pyc +0 -0
- ksxt/base/com_exchange.py +2 -2
- ksxt/base/errors.py +10 -0
- ksxt/base/exchange.py +188 -497
- ksxt/base/rest_exchange.py +297 -113
- ksxt/base/types.py +1 -36
- ksxt/bithumb.py +504 -0
- ksxt/config/__init__.py +2 -1
- ksxt/config/__pycache__/__init__.cpython-312.pyc +0 -0
- ksxt/config/bithumb.toml +380 -0
- ksxt/config/koreainvest.toml +312 -0
- ksxt/config/token.toml +7 -0
- ksxt/config/upbit.toml +428 -0
- ksxt/koreainvest.py +409 -1055
- ksxt/market/__pycache__/base.cpython-312.pyc +0 -0
- ksxt/market/__pycache__/db.cpython-312.pyc +0 -0
- ksxt/market/__pycache__/logging.cpython-312.pyc +0 -0
- ksxt/market/__pycache__/manager.cpython-312.pyc +0 -0
- ksxt/market/__pycache__/markets.cpython-312.pyc +0 -0
- ksxt/market/base.py +50 -50
- ksxt/market/db.py +5 -4
- ksxt/market/krx/__pycache__/kosdaq.cpython-312.pyc +0 -0
- ksxt/market/krx/__pycache__/kospi.cpython-312.pyc +0 -0
- ksxt/market/krx/__pycache__/stock.cpython-312.pyc +0 -0
- ksxt/market/krx/kosdaq.py +150 -147
- ksxt/market/krx/kospi.py +179 -175
- ksxt/market/krx/stock.py +136 -134
- ksxt/market/logging.py +4 -4
- ksxt/market/manager.py +10 -12
- ksxt/market/markets.py +1 -1
- ksxt/market/us/__pycache__/amex.cpython-312.pyc +0 -0
- ksxt/market/us/__pycache__/nasdaq.cpython-312.pyc +0 -0
- ksxt/market/us/__pycache__/nyse.cpython-312.pyc +0 -0
- ksxt/market/us/__pycache__/stock.cpython-312.pyc +0 -0
- ksxt/market/us/amex.py +31 -31
- ksxt/market/us/nasdaq.py +31 -31
- ksxt/market/us/nyse.py +31 -31
- ksxt/market/us/stock.py +20 -28
- ksxt/models/__init__.py +16 -0
- ksxt/models/__pycache__/__init__.cpython-312.pyc +0 -0
- ksxt/models/__pycache__/balance.cpython-312.pyc +0 -0
- ksxt/models/__pycache__/cash.cpython-312.pyc +0 -0
- ksxt/models/__pycache__/common.cpython-312.pyc +0 -0
- ksxt/models/__pycache__/error.cpython-312.pyc +0 -0
- ksxt/models/__pycache__/historical.cpython-312.pyc +0 -0
- ksxt/models/__pycache__/market.cpython-312.pyc +0 -0
- ksxt/models/__pycache__/order.cpython-312.pyc +0 -0
- ksxt/models/__pycache__/orderbook.cpython-312.pyc +0 -0
- ksxt/models/__pycache__/ticker.cpython-312.pyc +0 -0
- ksxt/models/__pycache__/token.cpython-312.pyc +0 -0
- ksxt/models/__pycache__/transaction.cpython-312.pyc +0 -0
- ksxt/models/balance.py +30 -0
- ksxt/models/cash.py +15 -0
- ksxt/models/common.py +31 -0
- ksxt/models/error.py +13 -0
- ksxt/models/historical.py +26 -0
- ksxt/models/market.py +81 -0
- ksxt/models/order.py +42 -0
- ksxt/models/orderbook.py +32 -0
- ksxt/models/ticker.py +25 -0
- ksxt/models/token.py +14 -0
- ksxt/models/transaction.py +79 -0
- ksxt/parser/__pycache__/bithumb.cpython-312.pyc +0 -0
- ksxt/parser/__pycache__/koreainvest.cpython-312.pyc +0 -0
- ksxt/parser/__pycache__/parser.cpython-312.pyc +0 -0
- ksxt/parser/__pycache__/upbit.cpython-312.pyc +0 -0
- ksxt/parser/bithumb.py +300 -0
- ksxt/parser/koreainvest.py +323 -0
- ksxt/parser/parser.py +114 -0
- ksxt/parser/upbit.py +308 -0
- ksxt/upbit.py +499 -0
- ksxt/utils/__pycache__/safer.cpython-312.pyc +0 -0
- ksxt/utils/__pycache__/sorter.cpython-312.pyc +0 -0
- ksxt/utils/__pycache__/timer.cpython-312.pyc +0 -0
- ksxt/utils/safer.py +48 -0
- ksxt/utils/sorter.py +8 -0
- ksxt/utils/timer.py +47 -0
- {ksxt-0.0.8.dist-info → ksxt-0.0.10.dist-info}/METADATA +11 -1
- ksxt-0.0.10.dist-info/RECORD +119 -0
- {ksxt-0.0.8.dist-info → ksxt-0.0.10.dist-info}/WHEEL +1 -1
- ksxt/__pycache__/__init__.cpython-39.pyc +0 -0
- ksxt/__pycache__/koreainvest.cpython-39.pyc +0 -0
- ksxt/base/__pycache__/__init__.cpython-39.pyc +0 -0
- ksxt/base/__pycache__/exchange.cpython-39.pyc +0 -0
- ksxt/base/__pycache__/rest_exchange.cpython-39.pyc +0 -0
- ksxt/base/__pycache__/restexchange.cpython-39.pyc +0 -0
- ksxt/base/__pycache__/types.cpython-39.pyc +0 -0
- ksxt/base/api_response.py +0 -68
- ksxt/config/__pycache__/__init__.cpython-39.pyc +0 -0
- ksxt/config/tr_app.json +0 -381
- ksxt/config/tr_dev.json +0 -446
- ksxt/market/__pycache__/base.cpython-39.pyc +0 -0
- ksxt/market/__pycache__/db.cpython-39.pyc +0 -0
- ksxt/market/__pycache__/logging.cpython-39.pyc +0 -0
- ksxt/market/__pycache__/manager.cpython-39.pyc +0 -0
- ksxt/market/__pycache__/markets.cpython-39.pyc +0 -0
- ksxt/market/krx/__pycache__/kosdaq.cpython-39.pyc +0 -0
- ksxt/market/krx/__pycache__/kospi.cpython-39.pyc +0 -0
- ksxt/market/krx/__pycache__/stock.cpython-39.pyc +0 -0
- ksxt/market/us/__pycache__/amex.cpython-39.pyc +0 -0
- ksxt/market/us/__pycache__/nasdaq.cpython-39.pyc +0 -0
- ksxt/market/us/__pycache__/nyse.cpython-39.pyc +0 -0
- ksxt/market/us/__pycache__/stock.cpython-39.pyc +0 -0
- ksxt-0.0.8.dist-info/RECORD +0 -49
- {ksxt-0.0.8.dist-info → ksxt-0.0.10.dist-info}/LICENSE.txt +0 -0
- {ksxt-0.0.8.dist-info → ksxt-0.0.10.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
|
23
|
+
timeout = 10000 # milliseconds = seconds * 1000
|
24
|
+
synchronous = True
|
21
25
|
|
22
26
|
required_credentials = {
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
|
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
|
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
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
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
|
-
|
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=
|
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=
|
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
|
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
|
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
|
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=
|
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=
|
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=
|
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(
|
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
|
-
|
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(
|
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
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
523
|
-
|
524
|
-
|
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
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
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
|
604
|
-
|
605
|
-
|
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
|
-
|
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
|
-
|
304
|
+
"response": {
|
668
305
|
# 성공 실패 여부
|
669
|
-
|
306
|
+
"success": response_code,
|
670
307
|
# 응답코드
|
671
|
-
|
308
|
+
"code": msg_code,
|
672
309
|
# 응답메세지
|
673
|
-
|
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 =
|
679
|
-
if base_market ==
|
680
|
-
return datetime.now(tz=pytz.timezone(
|
681
|
-
elif base_market ==
|
682
|
-
return datetime.now(tz=pytz.timezone(
|
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
|
-
|
749
|
-
|
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,
|
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(
|
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
|