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/upbit.py ADDED
@@ -0,0 +1,499 @@
1
+ import hashlib
2
+ import json
3
+ import time
4
+ import uuid
5
+ from datetime import datetime
6
+ from typing import Any, Dict, List, Literal, Optional
7
+ from urllib.parse import urlencode
8
+
9
+ import jwt
10
+ import pytz
11
+
12
+ from ksxt.api.upbit import ImplicitAPI
13
+ from ksxt.parser.upbit import UpbitParser
14
+ from ksxt.base.rest_exchange import RestExchange
15
+ import ksxt.models
16
+
17
+
18
+ class Upbit(RestExchange, ImplicitAPI):
19
+ def __init__(self, config: Dict = None) -> None:
20
+ super().__init__(config, "upbit.toml")
21
+ self.parser = UpbitParser()
22
+ self.timezone = pytz.timezone("Asia/Seoul")
23
+
24
+ def safe_symbol(self, base_market: str, security: str) -> str:
25
+ # If security already contains a hyphen, assume it's correctly formatted
26
+ if "-" in security:
27
+ return security
28
+
29
+ # Otherwise, assume security is just the symbol and format it with base_market
30
+ return f"{base_market}-{security}"
31
+
32
+ def safe_security(self, symbol: str) -> str:
33
+ if symbol.find("-") < 0:
34
+ return symbol
35
+ return symbol.split("-")[1]
36
+
37
+ def sign(
38
+ self,
39
+ path,
40
+ security_type,
41
+ method_type,
42
+ api_type: Any = "public",
43
+ headers: Optional[Any] = None,
44
+ body: Optional[Any] = None,
45
+ params: Optional[Any] = None,
46
+ config={},
47
+ ):
48
+ host_url = self.apis[self.type]["hostname"]
49
+ destination = self.apis[self.type][security_type][path]["url"]
50
+ version = self.apis[self.type]["version"]
51
+ destination = self.implode_params(destination, params)
52
+
53
+ url = host_url + "/" + version + "/" + destination
54
+
55
+ if api_type == "private":
56
+ payload = {
57
+ "access_key": self.open_key,
58
+ "nonce": str(uuid.uuid4()),
59
+ }
60
+
61
+ if params is not None and len(params) > 0:
62
+ m = hashlib.sha512()
63
+ m.update(urlencode(params).encode())
64
+ payload.update({"query_hash": m.hexdigest(), "query_hash_alg": "SHA512"})
65
+
66
+ jwt_token = jwt.encode(payload=payload, key=self.secret_key)
67
+ authorization_token = f"Bearer {jwt_token}"
68
+
69
+ if headers is None:
70
+ headers = {}
71
+ headers.update({"content-type": "application/json", "Authorization": authorization_token})
72
+
73
+ if method_type.upper() == "POST":
74
+ body = json.dumps(params)
75
+ params = {}
76
+
77
+ return {"url": url, "method": method_type, "headers": headers, "body": body, "params": params}
78
+
79
+ def get_common_response(self, response):
80
+ if "error" in response:
81
+ return self.create_common_response(
82
+ success="1",
83
+ msg_code=self.safe_string(response["error"], "name"),
84
+ msg=self.safe_string(response["error"], "message"),
85
+ info=response,
86
+ )
87
+
88
+ return self.create_common_response(
89
+ success="0",
90
+ msg_code=self.safe_string(response, "name"),
91
+ msg=self.safe_string(response, "message"),
92
+ info=response,
93
+ )
94
+
95
+ # if ('status' in response and self.safe_string(response, 'status') == '0000') or ('status' not in response and 'error' not in response):
96
+ # return self.create_common_response(
97
+ # success='0',
98
+ # msg_code=self.safe_string(response, 'status'),
99
+ # msg=self.safe_string(response, 'message'),
100
+ # info=response
101
+ # )
102
+ # else:
103
+ # return self.create_common_response(
104
+ # success='1',
105
+ # msg_code=self.safe_string(response, 'status'),
106
+ # msg=self.safe_string(response, 'message'),
107
+ # info=response
108
+ # )
109
+
110
+ def fetch_markets(self, base_market: str = "KRW") -> ksxt.models.KsxtMarketResponse:
111
+ params = {"isDetails": "True"}
112
+
113
+ common_header = self.create_common_header(request_params=params)
114
+
115
+ response = self.public_get_fetch_markets(self.extend(params))
116
+
117
+ common_response = self.get_common_response(response=response)
118
+ if common_response.success != "0":
119
+ return ksxt.models.KsxtMarketResponse(header=common_header, response=common_response, info=None)
120
+
121
+ parsed_info = self.parser.parse_markets(response=response, base_market=base_market)
122
+
123
+ return ksxt.models.KsxtMarketResponse(header=common_header, response=common_response, info=parsed_info)
124
+
125
+ def fetch_historical_data(
126
+ self, symbol: str, time_frame: str, start: str | None = None, end: str | None = None, base_market: str = "KRW"
127
+ ) -> ksxt.models.KsxtHistoricalDataResponse:
128
+ params = {"market": self.safe_symbol(base_market, symbol), "count": 200}
129
+
130
+ # TODO : time_frame 을 어떻게 고정시킬까? 우리는 분봉, 일봉, 주봉, 월봉 만 지원한다고 가정하면?
131
+ if time_frame.endswith("m"):
132
+ # TODO : parse number
133
+ period = 1
134
+ params.update({"unit": period})
135
+
136
+ common_header = self.create_common_header(request_params=params)
137
+ response = self.public_get_fetch_security_ohlcv_minute(self.extend(params))
138
+
139
+ elif time_frame.endswith("D"):
140
+ common_header = self.create_common_header(request_params=params)
141
+ response = self.public_get_fetch_security_ohlcv_day(self.extend(params))
142
+
143
+ elif time_frame.endswith("W"):
144
+ common_header = self.create_common_header(request_params=params)
145
+ response = self.public_get_fetch_security_ohlcv_week(self.extend(params))
146
+
147
+ elif time_frame.endswith("M"):
148
+ common_header = self.create_common_header(request_params=params)
149
+ response = self.public_get_fetch_security_ohlcv_month(self.extend(params))
150
+
151
+ common_response = self.get_common_response(response=response)
152
+ if common_response.success != "0":
153
+ return ksxt.models.KsxtHistoricalDataResponse(header=common_header, response=common_response, info=None)
154
+
155
+ parsed_info = self.parser.parse_historical_data(response=response, symbol=symbol, base_market=base_market)
156
+
157
+ return ksxt.models.KsxtHistoricalDataResponse(header=common_header, response=common_response, info=parsed_info)
158
+
159
+ def fetch_ticker(self, symbol: str, base_market: str = "KRW") -> ksxt.models.KsxtTickerResponse:
160
+ params = {"markets": self.safe_symbol(base_market, symbol)} # 다중 조회 시, 콤마로 구분 ex. 'KRW-BTC, BTC-ETH'
161
+
162
+ common_header = self.create_common_header(request_params=params)
163
+
164
+ response = self.public_get_fetch_ticker_price(self.extend(params))
165
+
166
+ common_response = self.get_common_response(response=response)
167
+ if common_response.success != "0":
168
+ return ksxt.models.KsxtTickerResponse(header=common_header, response=common_response, info=None)
169
+
170
+ parsed_info = self.parser.parse_ticker(response=response, base_market=base_market)
171
+
172
+ return ksxt.models.KsxtTickerResponse(header=common_header, response=common_response, info=parsed_info)
173
+
174
+ def fetch_tickers(self, symbols: List[str], base_market: str = "KRW") -> ksxt.models.KsxtTickersResponse:
175
+ symbol = ",".join([self.safe_symbol(base_market, symbol) for symbol in symbols])
176
+ params = {"markets": symbol}
177
+
178
+ common_header = self.create_common_header(request_params=params)
179
+
180
+ response = self.public_get_fetch_tickers_price(self.extend(params))
181
+
182
+ common_response = self.get_common_response(response=response)
183
+
184
+ # 실패 시 오류 응답 반환
185
+ if common_response.success != "0":
186
+ return ksxt.models.KsxtTickersResponse(header=common_header, response=common_response, info=[])
187
+
188
+ # 데이터 파싱
189
+ parsed_info = self.parser.parse_tickers(response, base_market)
190
+
191
+ return ksxt.models.KsxtTickersResponse(header=common_header, response=common_response, info=parsed_info)
192
+
193
+ def fetch_orderbook(self, symbol: str, base_market: str = "KRW") -> ksxt.models.KsxtSingleOrderBookResponse:
194
+ params = {"markets": self.safe_symbol(base_market, symbol), "level": 0}
195
+
196
+ common_header = self.create_common_header(request_params=params)
197
+
198
+ response = self.public_get_fetch_orderbook(self.extend(params))
199
+
200
+ common_response = self.get_common_response(response=response)
201
+ if common_response.success != "0":
202
+ return ksxt.models.KsxtSingleOrderBookResponse(header=common_header, response=common_response, info=None)
203
+
204
+ parsed_info = self.parser.parse_orderbook(response=response, base_market=base_market)
205
+
206
+ return ksxt.models.KsxtSingleOrderBookResponse(header=common_header, response=common_response, info=parsed_info)
207
+
208
+ def fetch_orderbooks(self, symbols: List[str], base_market: str = "KRW") -> ksxt.models.KsxtMultiOrderBookResponse:
209
+ symbol = ",".join([self.safe_symbol(base_market, symbol) for symbol in symbols])
210
+ params = {"markets": symbol}
211
+
212
+ common_header = self.create_common_header(request_params=params)
213
+
214
+ response = self.public_get_fetch_orderbooks(self.extend(params))
215
+
216
+ common_response = self.get_common_response(response=response)
217
+
218
+ # 실패 시 오류 응답 반환
219
+ if common_response.success != "0":
220
+ return ksxt.models.KsxtMultiOrderBookResponse(header=common_header, response=common_response, info=None)
221
+
222
+ # 데이터 파싱
223
+ parsed_info = self.parser.parse_orderbooks(response, base_market)
224
+
225
+ return ksxt.models.KsxtMultiOrderBookResponse(header=common_header, response=common_response, info=parsed_info)
226
+
227
+ def fetch_balance(self, acc_num: str, base_market: str = "KRW") -> ksxt.models.KsxtBalanceResponse:
228
+ params = {}
229
+
230
+ common_header = self.create_common_header(request_params=params)
231
+
232
+ response = self.private_get_fetch_balance(self.extend(params))
233
+
234
+ common_response = self.get_common_response(response=response)
235
+ if common_response.success != "0":
236
+ return ksxt.models.KsxtBalanceResponse(header=common_header, response=common_response, info=None)
237
+
238
+ parsed_info = self.parser.parse_balance(response=response, base_market=base_market)
239
+
240
+ return ksxt.models.KsxtBalanceResponse(header=common_header, response=common_response, info=parsed_info)
241
+
242
+ def fetch_cash(self, acc_num: str, base_market: str = "KRW") -> ksxt.models.KsxtCashResponse:
243
+ params = {}
244
+
245
+ common_header = self.create_common_header(request_params=params)
246
+
247
+ response = self.private_get_fetch_cash(self.extend(params))
248
+
249
+ common_response = self.get_common_response(response=response)
250
+ if common_response.success != "0":
251
+ return ksxt.models.KsxtCashResponse(header=common_header, response=common_response, info=None)
252
+
253
+ parsed_info = self.parser.parse_cash(response=response, base_market=base_market)
254
+
255
+ return ksxt.models.KsxtCashResponse(header=common_header, response=common_response, info=parsed_info)
256
+
257
+ def fetch_security(self, symbol: str, base_market: str = "KRW") -> ksxt.models.KsxtSecurityResponse:
258
+ params = {"market": self.safe_symbol(base_market, symbol)}
259
+
260
+ common_header = self.create_common_header(request_params=params)
261
+
262
+ response = self.private_get_fetch_security_info(self.extend(params))
263
+
264
+ common_response = self.get_common_response(response=response)
265
+ if common_response.success != "0":
266
+ return ksxt.models.KsxtSecurityResponse(header=common_header, response=common_response, info=None)
267
+
268
+ parsed_info = self.parser.parse_security(response=response, base_market=base_market)
269
+
270
+ return ksxt.models.KsxtSecurityResponse(header=common_header, response=common_response, info=parsed_info)
271
+
272
+ def fetch_trade_fee(self, symbol: Optional[str] = "", base_market: str = "KRW") -> ksxt.models.KsxtTradeFeeResponse:
273
+ params = {"market": self.safe_symbol(base_market, symbol)}
274
+
275
+ common_header = self.create_common_header(request_params=params)
276
+
277
+ response = self.private_get_fetch_trade_fee(self.extend(params))
278
+
279
+ common_response = self.get_common_response(response=response)
280
+ if common_response.success != "0":
281
+ return ksxt.models.KsxtTradeFeeResponse(header=common_header, response=common_response, info=None)
282
+
283
+ parsed_info = self.parser.parse_trade_fee(response=response, base_market=base_market)
284
+ return ksxt.models.KsxtTradeFeeResponse(header=common_header, response=common_response, info=parsed_info)
285
+
286
+ def fetch_closed_order_detail(
287
+ self, acc_num: str, order_ids: List[str], base_market: str = "KRW"
288
+ ) -> ksxt.models.KsxtOpenOrderResponse:
289
+ params = {
290
+ "uuids": order_ids,
291
+ }
292
+
293
+ common_header = self.create_common_header(request_params=params)
294
+
295
+ response = self.private_get_fetch_closed_order_detail(self.extend(params))
296
+
297
+ common_response = self.get_common_response(response=response)
298
+
299
+ # 실패 시 오류 응답 반환
300
+ if common_response.success != "0":
301
+ return ksxt.models.KsxtOpenOrderResponse(header=common_header, response=common_response, info=None)
302
+
303
+ # 데이터 파싱
304
+ parsed_info = self.parser.parse_closed_order_history(response, base_market)
305
+
306
+ return ksxt.models.KsxtOpenOrderResponse(header=common_header, response=common_response, info=parsed_info)
307
+
308
+ def fetch_open_order_detail(
309
+ self, acc_num: str, order_ids: List[str], base_market: str = "KRW"
310
+ ) -> ksxt.models.KsxtOpenOrderResponse:
311
+ params = {
312
+ "uuids": order_ids,
313
+ }
314
+
315
+ common_header = self.create_common_header(request_params=params)
316
+
317
+ response = self.private_get_fetch_opened_order_detail(self.extend(params))
318
+
319
+ common_response = self.get_common_response(response=response)
320
+
321
+ # 실패 시 오류 응답 반환
322
+ if common_response.success != "0":
323
+ return ksxt.models.KsxtOpenOrderResponse(header=common_header, response=common_response, info=None)
324
+
325
+ # 데이터 파싱
326
+ parsed_info = self.parser.parse_open_order_history(response, base_market)
327
+
328
+ return ksxt.models.KsxtOpenOrderResponse(header=common_header, response=common_response, info=parsed_info)
329
+
330
+ def fetch_closed_order(
331
+ self,
332
+ acc_num: str,
333
+ symbol: Optional[str] = "",
334
+ start: Optional[str] = None,
335
+ end: Optional[str] = None,
336
+ base_market: str = "KRW",
337
+ ) -> ksxt.models.KsxtClosedOrderResponse:
338
+ params = {"state": "done"}
339
+
340
+ if bool(symbol):
341
+ params.update({"market": self.safe_symbol(base_market, symbol)})
342
+
343
+ common_header = self.create_common_header(request_params=params)
344
+
345
+ response = self.private_get_fetch_closed_order(self.extend(params))
346
+
347
+ common_response = self.get_common_response(response=response)
348
+ if common_response.success != "0":
349
+ return ksxt.models.KsxtClosedOrderResponse(header=common_header, response=common_response, info=None)
350
+
351
+ parsed_info = self.parser.parse_closed_order_history(response=response, base_market=base_market)
352
+
353
+ return ksxt.models.KsxtClosedOrderResponse(header=common_header, response=common_response, info=parsed_info)
354
+
355
+ def fetch_open_order(
356
+ self,
357
+ acc_num: str,
358
+ symbol: str | None = "",
359
+ start: str | None = None,
360
+ end: str | None = None,
361
+ base_market: str = "KRW",
362
+ ) -> ksxt.models.KsxtOpenOrderResponse:
363
+ params = {"state": "wait"}
364
+
365
+ if bool(symbol):
366
+ params.update({"market": self.safe_symbol(base_market, symbol)})
367
+
368
+ common_header = self.create_common_header(request_params=params)
369
+
370
+ response = self.private_get_fetch_opened_order(self.extend(params))
371
+
372
+ common_response = self.get_common_response(response=response)
373
+ if common_response.success != "0":
374
+ return ksxt.models.KsxtOpenOrderResponse(header=common_header, response=common_response, info=None)
375
+
376
+ parsed_info = self.parser.parse_open_order_history(response=response, base_market=base_market)
377
+
378
+ return ksxt.models.KsxtOpenOrderResponse(header=common_header, response=common_response, info=parsed_info)
379
+
380
+ def fetch_withdrawal_history(
381
+ self, acc_num: str, base_market: str = "KRW"
382
+ ) -> ksxt.models.KsxtWithdrawalHistoryResponse:
383
+ params = {"currency": base_market, "state": "DONE"}
384
+
385
+ common_header = self.create_common_header(request_params=params)
386
+
387
+ response = self.private_get_fetch_withdrawal_history(self.extend(params))
388
+
389
+ common_response = self.get_common_response(response=response)
390
+ if common_response.success != "0":
391
+ return ksxt.models.KsxtWithdrawalHistoryResponse(header=common_header, response=common_response, info=None)
392
+
393
+ parsed_info = self.parser.parse_withdrawal_history(response=response, base_market=base_market)
394
+
395
+ return ksxt.models.KsxtWithdrawalHistoryResponse(
396
+ header=common_header, response=common_response, info=parsed_info
397
+ )
398
+
399
+ def fetch_deposit_history(self, acc_num: str, base_market: str = "KRW") -> ksxt.models.KsxtDepositHistoryResponse:
400
+ params = {"currency": base_market, "state": "ACCEPTED"}
401
+
402
+ common_header = self.create_common_header(request_params=params)
403
+
404
+ response = self.private_get_fetch_deposit_history(self.extend(params))
405
+
406
+ common_response = self.get_common_response(response=response)
407
+ if common_response.success != "0":
408
+ return ksxt.models.KsxtDepositHistoryResponse(header=common_header, response=common_response, info=None)
409
+
410
+ parsed_info = self.parser.parse_deposit_history(response=response, base_market=base_market)
411
+
412
+ return ksxt.models.KsxtDepositHistoryResponse(header=common_header, response=common_response, info=parsed_info)
413
+
414
+ def create_order(
415
+ self,
416
+ acc_num: str,
417
+ symbol: str,
418
+ ticket_type: Literal["EntryLong", "EntryShort", "ExitLong", "ExitShort"],
419
+ otype: Literal["limit", "market"],
420
+ price: Optional[float] = 0,
421
+ qty: Optional[float] = 0,
422
+ amount: Optional[float] = 0,
423
+ base_market: str = "KRW",
424
+ ) -> ksxt.models.KsxtCreateOrderResponse:
425
+
426
+ params = {
427
+ "market": self.safe_symbol(base_market, symbol),
428
+ }
429
+
430
+ if ticket_type == "EntryLong":
431
+ order_side = "bid"
432
+
433
+ if otype == "limit":
434
+ params.update({"side": order_side, "price": str(price), "volume": str(qty), "ord_type": "limit"})
435
+
436
+ response = self.private_post_send_order_entry(self.extend(params))
437
+ elif otype == "market":
438
+ params.update(
439
+ {
440
+ "side": order_side,
441
+ "price": amount,
442
+ "ord_type": "price",
443
+ }
444
+ )
445
+
446
+ response = self.private_post_send_order_entry_market(self.extend(params))
447
+ elif ticket_type == "ExitLong":
448
+ order_side = "ask"
449
+ params = {
450
+ "market": self.safe_symbol(base_market, symbol),
451
+ "side": order_side,
452
+ }
453
+
454
+ if otype == "limit":
455
+ params.update({"price": str(price), "volume": str(qty), "ord_type": "limit"})
456
+
457
+ response = self.private_post_send_order_exit(self.extend(params))
458
+
459
+ elif otype == "market":
460
+ params.update({"ord_type": "market", "volume": str(qty)})
461
+
462
+ response = self.private_post_send_order_exit_market(self.extend(params))
463
+ else:
464
+ raise ValueError(
465
+ f"Unsupported ticket_type '{ticket_type}'. {__class__.__name__} supports only 'EntryLong' and 'ExitLong'."
466
+ )
467
+
468
+ common_header = self.create_common_header(request_params=params)
469
+ common_response = self.get_common_response(response=response)
470
+ if common_response.success != "0":
471
+ return ksxt.models.KsxtCreateOrderResponse(header=common_header, response=common_response, info=None)
472
+
473
+ parsed_info = self.parser.parse_create_order(response=response, base_market=base_market)
474
+
475
+ return ksxt.models.KsxtCreateOrderResponse(header=common_header, response=common_response, info=parsed_info)
476
+
477
+ def cancel_order(
478
+ self,
479
+ acc_num: str,
480
+ order_id: str,
481
+ symbol: str | None = "",
482
+ qty: float = 0,
483
+ *args,
484
+ base_market: str = "KRW",
485
+ **kwargs,
486
+ ) -> ksxt.models.KsxtCancelOrderResponse:
487
+ params = {"uuid": order_id}
488
+
489
+ common_header = self.create_common_header(request_params=params)
490
+
491
+ response = self.private_delete_send_cancel_order(self.extend(params))
492
+
493
+ common_response = self.get_common_response(response=response)
494
+ if common_response.success != "0":
495
+ return ksxt.models.KsxtCancelOrderResponse(header=common_header, response=common_response, info=None)
496
+
497
+ parsed_info = self.parser.parse_cancel_order(response=response, base_market=base_market)
498
+
499
+ return ksxt.models.KsxtCancelOrderResponse(header=common_header, response=common_response, info=parsed_info)
ksxt/utils/safer.py ADDED
@@ -0,0 +1,48 @@
1
+ from distutils.util import strtobool
2
+
3
+
4
+ @staticmethod
5
+ def key_exists(dictionary, key):
6
+ if hasattr(dictionary, "__getitem__") and not isinstance(dictionary, str):
7
+ if isinstance(dictionary, list) and type(key) is not int:
8
+ return False
9
+ try:
10
+ value = dictionary[key]
11
+ return value is not None and value != ""
12
+ except LookupError:
13
+ return False
14
+ return False
15
+
16
+
17
+ @staticmethod
18
+ def safe_value(dictionary, key, default_value=None):
19
+ return dictionary[key] if key_exists(dictionary, key) else default_value
20
+
21
+
22
+ @staticmethod
23
+ def safe_string(dictionary, key, default_value=""):
24
+ return str(dictionary[key]) if key_exists(dictionary, key) else default_value
25
+
26
+
27
+ @staticmethod
28
+ def safe_number(dictionary, key, default_value=0):
29
+ value = safe_string(dictionary, key)
30
+ if value == "":
31
+ return default_value
32
+
33
+ try:
34
+ return float(value)
35
+ except Exception:
36
+ return default_value
37
+
38
+
39
+ @staticmethod
40
+ def safe_boolean(dictionary, key, default_value=False):
41
+ value = safe_string(dictionary, key)
42
+ if value == "":
43
+ return default_value
44
+
45
+ try:
46
+ return bool(strtobool(value))
47
+ except Exception:
48
+ return default_value
ksxt/utils/sorter.py ADDED
@@ -0,0 +1,8 @@
1
+ import collections
2
+
3
+
4
+ @staticmethod
5
+ def sort_by(array, key, descending=False):
6
+ return sorted(
7
+ array, key=lambda k: getattr(k, key, None) if getattr(k, key, None) is not None else "", reverse=descending
8
+ )
ksxt/utils/timer.py ADDED
@@ -0,0 +1,47 @@
1
+ from datetime import datetime
2
+ import time
3
+
4
+ import pytz
5
+
6
+
7
+ @staticmethod
8
+ def now(base_market: str = "KRW"):
9
+ if base_market == "KRW":
10
+ return datetime.now(tz=pytz.timezone("Asia/Seoul"))
11
+ elif base_market == "USD":
12
+ return datetime.now(tz=pytz.timezone("US/Eastern"))
13
+ else:
14
+ return datetime.now(tz=pytz.utc)
15
+
16
+
17
+ @staticmethod
18
+ def seconds():
19
+ return int(time.time())
20
+
21
+
22
+ @staticmethod
23
+ def milliseconds():
24
+ return int(time.time() * 1_000)
25
+
26
+
27
+ @staticmethod
28
+ def create_datetime_with_today(hhmmss: str) -> datetime:
29
+ """
30
+ hhmmss 형식의 시간 문자열을 받아 오늘 날짜를 포함한 datetime 객체를 생성합니다.
31
+
32
+ Parameters:
33
+ hhmmss (str): "hhmmss" 형식의 시간 문자열 (예: "123045")
34
+
35
+ Returns:
36
+ datetime: 오늘 날짜와 주어진 시간이 포함된 datetime 객체
37
+ """
38
+ # 현재 날짜를 가져옴
39
+ today = datetime.now()
40
+
41
+ # hhmmss 문자열을 datetime 객체로 변환
42
+ time_part = datetime.strptime(hhmmss, "%H%M%S").time()
43
+
44
+ # 현재 날짜와 주어진 시간을 합쳐 datetime 객체 생성
45
+ combined_datetime = datetime.combine(today, time_part)
46
+
47
+ return combined_datetime
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: ksxt
3
- Version: 0.0.8
3
+ Version: 0.0.9
4
4
  License: MIT License
5
5
 
6
6
  Copyright © 2023 AMOSA
@@ -24,6 +24,16 @@ License: MIT License
24
24
  SOFTWARE.
25
25
  Description-Content-Type: text/markdown
26
26
  License-File: LICENSE.txt
27
+ Requires-Dist: requests
28
+ Requires-Dist: pandas
29
+ Requires-Dist: sqlalchemy
30
+ Requires-Dist: yarl
31
+ Requires-Dist: aiohttp
32
+ Requires-Dist: toml
33
+ Requires-Dist: pyjwt
34
+ Requires-Dist: pydantic
35
+ Requires-Dist: black
36
+ Requires-Dist: pytz
27
37
 
28
38
  # ksxt
29
39
  - Krx Stock eXchange Trading library