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.
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.10.dist-info}/METADATA +11 -1
  114. ksxt-0.0.10.dist-info/RECORD +119 -0
  115. {ksxt-0.0.8.dist-info → ksxt-0.0.10.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.10.dist-info}/LICENSE.txt +0 -0
  141. {ksxt-0.0.8.dist-info → ksxt-0.0.10.dist-info}/top_level.txt +0 -0
ksxt/koreainvest.py CHANGED
@@ -1,1147 +1,501 @@
1
1
  import json
2
- from datetime import datetime, timedelta
3
2
  import os
4
- import pandas as pd
5
- from typing import Any, Dict, Optional, Union
3
+ import time
4
+ from datetime import datetime, timedelta
5
+ from typing import Any, Dict, Literal, Optional
6
6
 
7
- from ksxt.base.rest_exchange import RestExchange
8
- from ksxt.base.types import Entry
7
+ import pytz
9
8
 
9
+ from ksxt.api.koreainvest import ImplicitAPI
10
+ from ksxt.base.rest_exchange import RestExchange
10
11
  from ksxt.market.manager import MarketManager
12
+ from ksxt.parser.koreainvest import KoreaInvestParser
11
13
 
14
+ import ksxt.models
12
15
 
13
- class ImplicitAPI:
14
- ################################################################################################
15
- # KRX API
16
- ################################################################################################
17
- fetchTicker = Entry('fetch_ticker_price', 'stock', 'feeder', 'public', 'GET', {})
18
- fetchOHLCV = Entry('fetch_ohlcv_stock', 'stock', 'feeder', 'public', 'GET', {})
19
- fetchIsHoliday = Entry('fetch_calendar_holiday', 'stock', 'feeder', 'public', 'GET', {})
20
- fetchScreenerList = Entry('fetch_screener_list', 'stock', 'feeder', 'private', 'GET', {})
21
- fetchScreener = Entry('fetch_screener', 'stock', 'feeder', 'private', 'GET', {})
22
-
23
- fetchBalance = Entry('fetch_balance', 'stock', 'broker', 'private', 'GET', {})
24
- fetchCash = Entry('fetch_cash', 'stock', 'broker', 'private', 'GET', {})
25
- sendOrderEntry = Entry('send_order_entry', 'stock', 'broker', 'private', 'POST', {})
26
- sendOrderExit = Entry('send_order_entry', 'stock', 'broker', 'private', 'POST', {})
27
- sendModifyOrder = Entry('send_modify_order', 'stock', 'broker', 'private', 'POST', {})
28
- sendCancelOrder = Entry('send_cancel_order', 'stock', 'broker', 'private', 'POST', {})
29
-
30
- fetchOpenedOrder = Entry('fetch_opened_order', 'stock', 'broker', 'private', 'GET', {})
31
- fetchClosedOrder = Entry('fetch_closed_order_short', 'stock', 'broker', 'private', 'GET', {})
32
-
33
- ################################################################################################
34
- # US API
35
- ################################################################################################
36
- fetchTickerForUS = Entry('fetch_ticker_price', 'oversea_stock', 'feeder', 'public', 'GET', {})
37
- fetchOHLCVforUS = Entry('fetch_ohlcv_stock', 'oversea_stock', 'feeder', 'public', 'GET', {})
38
- fetchScreenerListForUS = Entry('fetch_screener_list', 'oversea_stock', 'feeder', 'private', 'GET', {})
39
- fetchScreenerForUS = Entry('fetch_screener', 'oversea_stock', 'feeder', 'private', 'GET', {})
40
-
41
- fetchBalanceForUS = Entry('fetch_balance', 'oversea_stock', 'broker', 'private', 'GET', {})
42
- fetchCashForUS = Entry('fetch_cash', 'oversea_stock', 'broker', 'private', 'GET', {})
43
-
44
- sendOrderEntryForUS = Entry('send_order_entry', 'oversea_stock', 'broker', 'private', 'POST', {})
45
- sendOrderExitForUS = Entry('send_order_exit', 'oversea_stock', 'broker', 'private', 'POST', {})
46
- sendModifyOrderForUS = Entry('send_modify_order', 'oversea_stock', 'broker', 'private', 'POST', {})
47
- sendCancelOrderForUS = Entry('send_cancel_order', 'oversea_stock', 'broker', 'private', 'POST', {})
48
-
49
- fetchOpenedOrderForUS = Entry('fetch_opened_order', 'oversea_stock', 'broker', 'private', 'GET', {})
50
- fetchClosedOrderForUS = Entry('fetch_closed_order', 'oversea_stock', 'broker', 'private', 'GET', {})
51
16
 
52
17
  class KoreaInvest(RestExchange, ImplicitAPI):
53
- def __init__(self, config: Dict=None) -> None:
54
- super().__init__(config=config)
55
-
56
- def describe(self) -> Dict:
57
- result = self.deep_extend(super(KoreaInvest, self).describe(), {
58
- 'id': 'KIS',
59
- 'name': 'KoreaInvestment',
60
- 'countries': ['KR', 'US'],
61
- 'version': 'v1',
62
- 'rateLimit': 1000,
63
- 'urls': {
64
- 'logo': '',
65
- 'api': {
66
- 'token': 'https://{hostname}/oauth2/tokenP',
67
- 'public': 'https://{hostname}',
68
- 'private': 'https://{hostname}',
69
- },
70
- 'www': 'https://securities.koreainvestment.com',
71
- 'doc': 'https://apiportal.koreainvestment.com/apiservice/oauth2#L_5c87ba63-740a-4166-93ac-803510bb9c02',
72
- 'fees': '',
73
- },
74
- })
75
-
76
- return result
77
-
78
- # region _____
79
- def set_token(self):
80
- url = self.implode_hostname(self.urls['api']['token'], self.apis['rest']['hostname'])
81
- request_headers = self.prepare_request_headers()
82
-
83
- body = {
84
- "grant_type":"client_credentials",
85
- "appkey":self.open_key,
86
- "appsecret":self.secret_key
87
- }
88
-
89
- body = json.dumps(body, separators=(',', ':'))
90
-
91
- res = self.fetch(url=url, method='POST', headers=request_headers, body=body)
92
- self.token = res['access_token']
93
- self.token_expired = res['access_token_token_expired']
94
-
95
- import logging
96
- logging.info('set token')
97
- logging.info(self.token)
98
- logging.info(self.token_expired)
99
-
100
- def sign(self, path, market, module, api: Any = 'public', method='GET', headers: Optional[Any] = None, body: Optional[Any] = None, params: Optional[Dict] = {}, config={}):
101
- host_url = self.implode_hostname(self.urls['api'][api], self.apis[self.type]['hostname'])
102
- folder = self.apis[self.type][market][module]['foldername']
103
- destination = self.apis[self.type][market][module][path]['url']
104
- url = host_url + '/' + folder + '/' + self.version + '/' + destination
105
-
106
- tr_id = self.apis['rest'][market][module][path]['tr']
107
- if headers is None:
108
- headers = {}
109
- headers.update(
110
- {
111
- "content-type":"application/json",
112
- "authorization": f"Bearer {self.token}",
113
- "appKey": self.open_key,
114
- "appSecret": self.secret_key,
115
- "tr_id": tr_id
116
- }
117
- )
118
-
119
- if method.upper() == 'POST':
18
+ def __init__(self, config: Dict = None) -> None:
19
+ super().__init__(config, "koreainvest.toml")
20
+ self.parser = KoreaInvestParser()
21
+ self.timezone = pytz.timezone("Asia/Seoul")
22
+
23
+ def is_activate(self, path, security_type) -> bool:
24
+ mode = "dev" if self.is_dev == True else "app"
25
+
26
+ tr_id = self.apis[self.type][security_type][path][mode]["tr_id"]
27
+
28
+ if security_type != "token" and not bool(tr_id):
29
+ return False
30
+
31
+ return super().is_activate(path=path, security_type=security_type)
32
+
33
+ # @RestExchange.check_token
34
+ def sign(
35
+ self,
36
+ path,
37
+ security_type,
38
+ method_type,
39
+ api_type: Any = "public",
40
+ headers: Optional[Any] = None,
41
+ body: Optional[Any] = None,
42
+ params: Optional[Any] = None,
43
+ config={},
44
+ ):
45
+ mode = "dev" if self.is_dev == True else "app"
46
+
47
+ host_url = self.apis[self.type][mode]["hostname"]
48
+ destination = self.apis[self.type][security_type][path]["url"]
49
+ version = self.apis[self.type]["version"]
50
+ params["version"] = version
51
+ destination = self.implode_params(destination, params)
52
+
53
+ url = f"{host_url}/{destination}"
54
+
55
+ tr_id = self.apis[self.type][security_type][path][mode]["tr_id"]
56
+ authorization_token = f"Bearer {self.token}"
57
+
58
+ if api_type == "private":
59
+ if headers is None:
60
+ headers = {}
61
+ headers.update(
62
+ {
63
+ "content-type": "application/json; charset=utf-8",
64
+ "authorization": authorization_token,
65
+ "appkey": self.open_key,
66
+ "appsecret": self.secret_key,
67
+ "tr_id": tr_id,
68
+ "custtype": "P",
69
+ }
70
+ )
71
+
72
+ if method_type.upper() == "POST":
120
73
  body = json.dumps(params)
121
74
  params = {}
122
75
 
123
- return {'url': url, 'method': method, 'headers': headers, 'body': body, 'params': params}
124
- # endregion ____
76
+ return {"url": url, "method": method_type, "headers": headers, "body": body, "params": params}
125
77
 
126
- # region public feeder
127
- @RestExchange.check_token
128
- def fetch_markets(self, market_name: str):
129
- db_path = os.path.join(os.getcwd(), f".ksxt-cache_market.{datetime.now().strftime('%Y%m%d')}.db")
130
- manager = MarketManager(db_path=db_path)
131
- manager._init()
132
- if market_name.lower() == 'kospi':
133
- result = manager.kospi.all()
134
- base_market = 'KRW'
135
- elif market_name.lower() == 'kosdaq':
136
- result = manager.kosdaq.all()
137
- base_market = 'KRW'
138
- elif market_name.lower() == 'nyse':
139
- result = manager.nyse.all()
140
- base_market = 'USD'
141
- elif market_name.lower() == 'nasdaq':
142
- result = manager.nasdaq.all()
143
- base_market = 'USD'
144
- elif market_name.lower() == 'amex':
145
- result = manager.amex.all()
146
- base_market = 'USD'
147
- else:
148
- return self.get_error_response(error_code='market_error', error_message=f'{market_name} market is not yet supported.')
149
-
150
- df = pd.DataFrame(result)
151
-
152
- return self.parse_market(response=df, base_market=base_market)
153
-
154
- def parse_market(self, response: pd.DataFrame, base_market: str= 'KRW'):
155
- result = self.get_common_successful_response(response=response)
156
-
157
- if base_market == 'KRW':
158
- result.update({
159
- # 종목 코드
160
- 'symbol': response['mksc_shrn_iscd'],
161
- # 종목 이름
162
- 'name': response['hts_kor_isnm'],
163
- # 거래소 정보 (KOSPI, KOSDAQ)
164
- 'exchange': response['excd'],
165
- # 거래 통화
166
- 'currency': response['curr'],
167
- })
168
- elif base_market == 'USD':
169
- result.update({
170
- # 종목 코드
171
- 'symbol': response['symb'],
172
- # 종목 이름
173
- 'name': response['enam'],
174
- # 거래소 정보 (NYSE, NASDAQ, AMEX)
175
- 'exchange': response['excd'],
176
- # 거래 통화
177
- 'currency': response['curr'],
178
- })
179
- else:
180
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
78
+ def create_token(self) -> ksxt.models.KsxtTokenResponse:
79
+ params = {"grant_type": "client_credentials", "appkey": self.open_key, "appsecret": self.secret_key}
181
80
 
182
- return result
183
-
184
- @RestExchange.check_token
185
- def fetch_security(self, symbol: str, base_market: str = 'KRW'):
186
- db_path = os.path.join(os.getcwd(), f".ksxt-cache_market.{datetime.now().strftime('%Y%m%d')}.db")
187
- manager = MarketManager(db_path=db_path)
188
- manager._init()
81
+ common_header = self.create_common_header(request_params=params)
189
82
 
190
- response = manager.stock(symbol)
83
+ response = self.public_post_generate_token(self.extend(params))
191
84
 
192
- return self.parse_security(response=response, base_market=base_market)
193
-
194
- def parse_security(self, response, base_market: str = 'KRW'):
195
- result = self.get_common_successful_response(response=response)
196
-
197
- if base_market == 'KRW':
198
- result.update({
199
- # 종목 코드
200
- 'symbol': response.mksc_shrn_iscd,
201
- # 종목 이름
202
- 'name': response.hts_kor_isnm,
203
- # 거래소 정보 (KOSPI, KOSDAQ)
204
- 'exchange': response.excd,
205
- # 거래 통화
206
- 'currency': response.curr,
207
- })
208
- elif base_market == 'USD':
209
- result.update({
210
- # 종목 코드
211
- 'symbol': response.symb,
212
- # 종목 이름
213
- 'name': response.enam,
214
- # 거래소 정보 (NYSE, NASDAQ, AMEX)
215
- 'exchange': response.excd,
216
- # 거래 통화
217
- 'currency': response.curr,
218
- })
219
- else:
220
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
221
-
222
- return result
223
-
85
+ common_response = self.get_common_response(response=response)
86
+ if common_response.success != "0":
87
+ return ksxt.models.KsxtTokenResponse(header=common_header, response=common_response, info=None)
224
88
 
225
- @RestExchange.check_token
226
- def fetch_ticker(self, symbol: str, base_market: str= 'KRW'):
227
- if base_market == 'KRW':
228
- params = {
229
- "FID_COND_MRKT_DIV_CODE":"J",
230
- "FID_INPUT_ISCD": symbol
231
- }
232
- response = self.fetchTicker(self.extend(params))
233
- elif base_market == 'USD':
234
- market_code = self.get_market_code_in_feeder(symbol=symbol, base_market=base_market)
235
- params = {
236
- "AUTH": "",
237
- "EXCD": market_code,
238
- "SYMB": symbol
239
- }
240
- response = self.fetchTickerForUS(self.extend(params))
241
- else:
242
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
243
-
244
- return self.parse_ticker(response=response, base_market=base_market)
245
-
246
- def parse_ticker(self, response: dict, base_market: str= 'KRW'):
247
- data = self.safe_value(response, 'output')
248
- if data is None:
249
- return response
250
-
251
- result = self.get_common_successful_response(response=response)
252
-
253
- if base_market == 'KRW':
254
- result.update({
255
- # 종목 코드
256
- 'symbol': self.safe_value(data, 'stck_shrn_iscd'),
257
- # 현재가
258
- 'price': self.safe_value(data, 'stck_prpr'),
259
- })
260
- elif base_market == 'USD':
261
- symbol = self.safe_value(data, 'rsym')
262
- symbol = symbol[4:]
263
- result.update({
264
- # 중목 코드
265
- 'symbol': symbol,
266
- # 현재가
267
- 'price': self.safe_value(data, 'last')
268
- })
269
- else:
270
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
271
-
272
- return result
273
-
274
- @RestExchange.check_token
275
- def fetch_historical_data(self, symbol: str, time_frame: str, start: Optional[str] = None, end: Optional[str] = None, base_market: str= 'KRW'):
276
- limit = 100
89
+ parsed_info = self.parser.parse_token(response=response)
277
90
 
278
- if end is None:
279
- end = KoreaInvest.now(base_market=base_market)
91
+ self.save_token(self.open_key, parsed_info.access_token, expired=parsed_info.expired_datetime)
280
92
 
281
- if start is None:
93
+ return ksxt.models.KsxtTokenResponse(header=common_header, response=common_response, info=parsed_info)
282
94
 
283
- if time_frame == 'D':
284
- start = end - timedelta(days=limit)
285
- elif time_frame == 'W':
286
- start = end - timedelta(weeks=limit)
287
- elif time_frame == "M":
288
- start = end - timedelta(days=limit * 30)
289
- elif time_frame == 'Y':
290
- start = end - timedelta(days=limit * 365)
291
- else:
292
- start = end
95
+ def get_common_response(self, response):
96
+ if "error_code" in response:
97
+ return self.create_common_response(
98
+ success="1",
99
+ msg_code=self.safe_string(response, "error_code"),
100
+ msg=self.safe_string(response, "error_description"),
101
+ info=response,
102
+ )
293
103
 
294
- if base_market == 'KRW':
295
- params = {
296
- "FID_COND_MRKT_DIV_CODE": "J",
297
- "FID_INPUT_ISCD": symbol,
298
- "FID_INPUT_DATE_1": start.strftime('%Y%m%d'),
299
- "FID_INPUT_DATE_2": end.strftime('%Y%m%d'),
300
- "FID_PERIOD_DIV_CODE": time_frame,
301
- "FID_ORG_ADJ_PRC":"1",
302
- }
104
+ if "rt_cd" in response and response["rt_cd"] != "0":
105
+ return self.create_common_response(
106
+ success="1",
107
+ msg_code=self.safe_string(response, "msg_cd"),
108
+ msg=self.safe_string(response, "msg1"),
109
+ info=response,
110
+ )
303
111
 
304
- response = self.fetchOHLCV(self.extend(params))
305
- elif base_market == 'USD':
306
- if time_frame == 'D':
307
- gubn = '0'
308
- elif time_frame == 'W':
309
- gubn = '1'
310
- elif time_frame == 'M':
311
- gubn = '2'
312
- else:
313
- return self.get_error_response(error_code='time frame error', error_message=f'{time_frame} time-frame is not supported.')
314
-
315
- market_code = self.get_market_code_in_feeder(symbol=symbol, base_market=base_market)
112
+ if "response" in response and response["response"]["success"] != "0":
113
+ return self.create_common_response(
114
+ success="1",
115
+ msg_code=self.safe_string(response["response"], "code"),
116
+ msg=self.safe_string(response["response"], "message"),
117
+ info=response,
118
+ )
316
119
 
317
- params = {
318
- "AUTH": "",
319
- "EXCD": market_code,
320
- "SYMB": symbol,
321
- "GUBN": gubn,
322
- "BYMD": end.strftime('%Y%m%d'),
323
- "MODP": "1",
324
- "KEYB": ""
325
- }
120
+ return self.create_common_response(
121
+ success="0",
122
+ msg_code=self.safe_string(response, "msg_cd"),
123
+ msg=self.safe_string(response, "msg1"),
124
+ info=response,
125
+ )
326
126
 
327
- response = self.fetchOHLCVforUS(self.extend(params))
328
- else:
329
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
127
+ # @RestExchange.check_token
128
+ # def fetch_markets(self, market_name: str) -> ksxt.models.KsxtMarketResponse:
129
+ # params = {}
330
130
 
331
- return self.parse_historical_data(response=response, base_market=base_market)
332
-
333
- def parse_historical_data(self, response: dict, base_market: str= 'KRW'):
334
- data = self.safe_value(response, 'output1')
335
- data_ohlcv = self.safe_value(response, 'output2')
336
- if data is None or data_ohlcv is None:
337
- return response
338
-
339
- result = self.get_common_successful_response(response=response)
340
-
341
- ohlcv = [self.parse_ohlcva(_, base_market) for _ in data_ohlcv]
342
- sorted_ohlcv = self.sort_by(ohlcv, 0)
343
-
344
- result.update({
345
- # 종목코드
346
- 'symbol': self.safe_value(data, 'stck_shrn_iscd'),
347
- # ohlcv
348
- 'history' : sorted_ohlcv
349
- })
350
-
351
- return result
352
-
353
- def parse_ohlcv(self, ohlcv, base_market: str= 'KRW'):
354
- # convert datetime to timestamp
355
- ts = self.safe_string(ohlcv, 'stck_bsop_date')
356
-
357
- if base_market == 'KRW':
358
- return [
359
- # timestamp
360
- ts,
361
- # open
362
- self.safe_number(ohlcv, 'stck_oprc'),
363
- # high
364
- self.safe_number(ohlcv, 'stck_hgpr'),
365
- # low
366
- self.safe_number(ohlcv, 'stck_lwpr'),
367
- # close
368
- self.safe_number(ohlcv, 'stck_clpr'),
369
- # volume
370
- self.safe_number(ohlcv, 'acml_vol'),
371
- ]
372
- elif base_market == 'USD':
373
- return [
374
- # timestamp
375
- ts,
376
- # open
377
- self.safe_number(ohlcv, 'ovrs_nmix_oprc'),
378
- # high
379
- self.safe_number(ohlcv, 'ovrs_nmix_hgpr'),
380
- # low
381
- self.safe_number(ohlcv, 'ovrs_nmix_lwpr'),
382
- # close
383
- self.safe_number(ohlcv, 'ovrs_nmix_prpr'),
384
- # volume
385
- self.safe_number(ohlcv, 'acml_vol'),
386
- ]
387
- else:
388
- return []
389
-
390
- def parse_ohlcva(self, ohlcva, base_market: str= 'KRW'):
391
- if base_market == 'KRW':
392
- # convert datetime to timestamp
393
- ts = self.safe_string(ohlcva, 'stck_bsop_date')
394
-
395
- return [
396
- # timestamp
397
- ts,
398
- # open
399
- self.safe_number(ohlcva, 'stck_oprc'),
400
- # high
401
- self.safe_number(ohlcva, 'stck_hgpr'),
402
- # low
403
- self.safe_number(ohlcva, 'stck_lwpr'),
404
- # close
405
- self.safe_number(ohlcva, 'stck_clpr'),
406
- # volume
407
- self.safe_number(ohlcva, 'acml_vol'),
408
- # amount
409
- self.safe_number(ohlcva, 'acml_tr_pbmn')
410
- ]
411
- elif base_market == 'USD':
412
- # convert datetime to timestamp
413
- ts = self.safe_string(ohlcva, 'xymd')
414
-
415
- return [
416
- # timestamp
417
- ts,
418
- # open
419
- self.safe_number(ohlcva, 'open'),
420
- # high
421
- self.safe_number(ohlcva, 'high'),
422
- # low
423
- self.safe_number(ohlcva, 'low'),
424
- # close
425
- self.safe_number(ohlcva, 'clos'),
426
- # volume
427
- self.safe_number(ohlcva, 'tvol'),
428
- # amount
429
- self.safe_number(ohlcva, 'tamt')
430
- ]
431
- else:
432
- return []
433
-
434
- @RestExchange.check_token
435
- def fetch_is_holiday(self, dt: datetime, base_market: str= 'KRW'):
436
- params = {
437
- "BASS_DT": dt.strftime('%Y%m%d'),
438
- "CTX_AREA_NK": '',
439
- "CTX_AREA_FK": ''
440
- }
131
+ # common_header = self.create_common_header(request_params=params)
441
132
 
442
- if base_market == 'KRW':
443
- response = self.fetchIsHoliday(self.extend(params))
444
- if response['response']['success'] != '0':
445
- return response
446
-
447
- result = self.parse_is_holiday(response=response)
133
+ # # TODO from Database
134
+ # response = None
448
135
 
449
- return self.safe_boolean(result['holiday'], dt.strftime('%Y%m%d'))
450
- else:
451
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
452
-
453
- def parse_is_holiday(self, response: dict):
454
- data = self.safe_value(response, 'output')
455
- if data is None:
456
- return response
457
-
458
- info = {}
459
- for _ in data:
460
- info.update(self._parse_is_holiday(_))
461
-
462
- return {
463
- 'response': {
464
- # 성공 실패 여부
465
- 'success' : self.safe_string(response, 'rt_cd'),
466
- # 응답코드
467
- 'code': self.safe_string(response, 'msg_cd'),
468
- # 응답메세지
469
- 'message': self.safe_string(response, 'msg1'),
470
- },
471
- # 원본 데이터
472
- 'info': response,
473
-
474
- # 휴장일 정보
475
- 'holiday': info
476
- }
477
-
478
- def _parse_is_holiday(self, info):
479
- return {
480
- # 날짜 (YYYYMMDD) : 개장일 여부 (Y/N)
481
- self.safe_string(info, 'bass_dt') : (not self.safe_boolean(info, 'opnd_yn')),
482
- }
136
+ # common_response = self.get_common_response(response=response)
137
+ # if common_response.success != "0":
138
+ # return ksxt.models.KsxtMarketResponse(header=common_header, response=common_response, info=None)
483
139
 
484
-
485
- # endregion public feeder
140
+ # parsed_info = self.parser.parse_markets(response=response, base_market="KRW")
141
+
142
+ # return ksxt.models.KsxtMarketResponse(header=common_header, response=common_response, info=parsed_info)
486
143
 
487
- # region private feeder
488
144
  @RestExchange.check_token
489
- def fetch_balance(self, acc_num: str, base_market: str= 'KRW'):
490
- if base_market == 'KRW':
145
+ def fetch_balance(self, acc_num: str, base_market: str = "KRW") -> ksxt.models.KsxtBalanceResponse:
146
+ if base_market == "KRW":
491
147
  params = {
492
148
  "CANO": acc_num[:8],
493
149
  "ACNT_PRDT_CD": acc_num[-2:],
494
- "AFHR_FLPR_YN": 'N',
495
- "OFL_YN": '',
496
- "INQR_DVSN": '01',
497
- "UNPR_DVSN": '01',
150
+ "AFHR_FLPR_YN": "N",
151
+ "OFL_YN": "",
152
+ "INQR_DVSN": "01",
153
+ "UNPR_DVSN": "01",
498
154
  "FUND_STTL_ICLD_YN": "N",
499
- "FNCG_AMT_AUTO_RDPT_YN": 'N',
500
- "PRCS_DVSN": '01',
501
- "CTX_AREA_FK100": '',
502
- "CTX_AREA_NK100": ''
155
+ "FNCG_AMT_AUTO_RDPT_YN": "N",
156
+ "PRCS_DVSN": "01",
157
+ "CTX_AREA_FK100": "",
158
+ "CTX_AREA_NK100": "",
503
159
  }
160
+ else:
161
+ assert ValueError(f"{base_market} is not valid value")
504
162
 
505
- response = self.fetchBalance(self.extend(params))
506
- elif base_market == 'USD':
507
- market_code = self.get_market_code_in_feeder(symbol='ALL', base_market=base_market)
508
- params = {
509
- "CANO": acc_num[:8],
510
- "ACNT_PRDT_CD": acc_num[-2:],
511
- "OVRS_EXCG_CD": market_code,
512
- "TR_CRCY_CD": 'USD',
513
- "CTX_AREA_FK200": '',
514
- "CTX_AREA_NK200": ''
515
- }
163
+ common_header = self.create_common_header(request_params=params)
516
164
 
517
- response = self.fetchBalanceForUS(self.extend(params))
518
- else:
519
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
165
+ response = self.private_get_fetch_balance(self.extend(params))
166
+
167
+ common_response = self.get_common_response(response=response)
168
+ if common_response.success != "0":
169
+ return ksxt.models.KsxtBalanceResponse(header=common_header, response=common_response, info=None)
170
+
171
+ parsed_info = self.parser.parse_balance(response=response, base_market=base_market)
172
+
173
+ return ksxt.models.KsxtBalanceResponse(header=common_header, response=common_response, info=parsed_info)
520
174
 
521
- return self.parse_balance(response=response, base_market=base_market)
522
-
523
- def parse_balance(self, response: dict, base_market: str= 'KRW'):
524
- data = self.safe_value(response, 'output1')
525
- if data is None:
526
- return response
527
-
528
- result = self.get_common_successful_response(response=response)
529
-
530
- balances = [self._parse_balance(_, base_market) for _ in data]
531
- sorted_balances = self.sort_by(balances, 'symbol')
532
-
533
- result.update({
534
- 'balance': sorted_balances
535
- })
536
-
537
- return result
538
-
539
- def _parse_balance(self, balance, base_market: str= 'KRW'):
540
- if base_market == 'KRW':
541
- total = self.safe_number(balance, 'hldg_qty')
542
- free = self.safe_number(balance, 'ord_psbl_qty')
543
-
544
- return {
545
- 'symbol': self.safe_string(balance, 'pdno'),
546
- 'name': self.safe_string(balance, 'prdt_name'),
547
- 'position': 'long',
548
- 'price': self.safe_number(balance, 'pchs_avg_pric'),
549
- 'qty':{
550
- 'total': total,
551
- 'free': free,
552
- 'used': total - free,
553
- },
554
- 'amount': self.safe_number(balance, 'pchs_amt'),
555
- }
556
- elif base_market == 'USD':
557
- total = self.safe_number(balance, 'ovrs_cblc_qty')
558
- free = self.safe_number(balance, 'ord_psbl_qty')
559
-
560
- return {
561
- 'symbol': self.safe_string(balance, 'ovrs_pdno'),
562
- 'name': self.safe_string(balance, 'ovrs_item_name'),
563
- 'position': 'long',
564
- 'price': self.safe_number(balance, 'pchs_avg_pric'),
565
- 'qty':{
566
- 'total': total,
567
- 'free': free,
568
- 'used': total - free,
569
- },
570
- 'amount': self.safe_number(balance, 'frcr_pchs_amt1'),
571
- }
572
- else:
573
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
574
-
575
175
  @RestExchange.check_token
576
- def fetch_cash(self, acc_num: str, base_market: str= 'KRW'):
577
- if base_market == 'KRW':
176
+ def fetch_cash(self, acc_num: str, base_market: str = "KRW") -> ksxt.models.KsxtCashResponse:
177
+ if base_market == "KRW":
578
178
  params = {
579
179
  "CANO": acc_num[:8],
580
180
  "ACNT_PRDT_CD": acc_num[-2:],
581
- "AFHR_FLPR_YN": 'N',
582
- "OFL_YN": '',
583
- "INQR_DVSN": '01',
584
- "UNPR_DVSN": '01',
181
+ "AFHR_FLPR_YN": "N",
182
+ "OFL_YN": "",
183
+ "INQR_DVSN": "01",
184
+ "UNPR_DVSN": "01",
585
185
  "FUND_STTL_ICLD_YN": "N",
586
- "FNCG_AMT_AUTO_RDPT_YN": 'N',
587
- "PRCS_DVSN": '01',
588
- "CTX_AREA_FK100": '',
589
- "CTX_AREA_NK100": ''
186
+ "FNCG_AMT_AUTO_RDPT_YN": "N",
187
+ "PRCS_DVSN": "01",
188
+ "CTX_AREA_FK100": "",
189
+ "CTX_AREA_NK100": "",
590
190
  }
191
+ else:
192
+ assert ValueError(f"{base_market} is not valid value")
591
193
 
592
- response = self.fetchCash(self.extend(params))
593
- elif base_market == 'USD':
594
- params = {
595
- "CANO": acc_num[:8],
596
- "ACNT_PRDT_CD": acc_num[-2:],
597
- # 01: 원화, 02: 외화
598
- "WCRC_FRCR_DVSN_CD": "02",
599
- "NATN_CD": "840",
600
- "TR_MKET_CD": "00",
601
- "INQR_DVSN_CD": "00"
602
- }
194
+ common_header = self.create_common_header(request_params=params)
195
+
196
+ response = self.private_get_fetch_cash(self.extend(params))
197
+
198
+ common_response = self.get_common_response(response=response)
199
+ if common_response.success != "0":
200
+ return ksxt.models.KsxtCashResponse(header=common_header, response=common_response, info=None)
603
201
 
604
- response = self.fetchCashForUS(self.extend(params))
202
+ parsed_info = self.parser.parse_cash(response=response, base_market=base_market)
203
+
204
+ return ksxt.models.KsxtCashResponse(header=common_header, response=common_response, info=parsed_info)
205
+
206
+ @RestExchange.check_token
207
+ def fetch_orderbook(self, symbol: str, base_market: str = "KRW") -> ksxt.models.KsxtSingleOrderBookResponse:
208
+ if base_market == "KRW":
209
+ params = {"FID_COND_MRKT_DIV_CODE": "J", "FID_INPUT_ISCD": symbol}
605
210
  else:
606
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
607
-
608
- return self.parse_cash(response=response, base_market=base_market)
609
-
610
- def parse_cash(self, response:dict, base_market: str= 'KRW'):
611
- result = self.get_common_successful_response(response=response)
612
- data = self.safe_value(response, 'output2')
613
- if data is None:
614
- return response
615
-
616
- if base_market == 'KRW':
617
- data = data[0]
618
-
619
- result.update({
620
- # 정산금액 (KR: D+2 예수금)
621
- 'cash': self.safe_number(data, 'prvs_rcdl_excc_amt')
622
- })
623
- elif base_market == 'USD':
624
- data = next(filter(lambda x: x['crcy_cd'] == base_market, data), None)
625
- if data is None:
626
- return response
627
-
628
- result.update({
629
- # 정산금액 (US: D+3 예수금)
630
- # TODO : 실전투자에서 D+3 예수금을 정상적으로 조회하는지 검증 필요
631
- 'cash': self.safe_number(data, 'frcr_dncl_amt_2')
632
- })
633
-
634
- return result
635
-
211
+ assert ValueError(f"{base_market} is not valid value")
212
+
213
+ common_header = self.create_common_header(request_params=params)
214
+
215
+ response = self.private_get_fetch_orderbook(self.extend(params))
216
+
217
+ common_response = self.get_common_response(response=response)
218
+ if common_response.success != "0":
219
+ return ksxt.models.KsxtSingleOrderBookResponse(header=common_header, response=common_response, info=None)
220
+
221
+ parsed_info = self.parser.parse_orderbook(response=response, base_market=base_market)
222
+
223
+ return ksxt.models.KsxtSingleOrderBookResponse(header=common_header, response=common_response, info=parsed_info)
224
+
636
225
  @RestExchange.check_token
637
- def fetch_screener_list(self, user_id, base_market: str= 'KRW'):
638
- params = {
639
- "USER_ID": user_id
640
- }
226
+ def fetch_security(self, symbol: str, base_market: str = "KRW") -> ksxt.models.KsxtSecurityResponse:
227
+ if base_market == "KRW":
228
+ params = {"PRDT_TYPE_CD": "300", "PDNO": symbol}
229
+ else:
230
+ assert ValueError(f"{base_market} is not valid value")
231
+
232
+ common_header = self.create_common_header(request_params=params)
641
233
 
642
- if base_market == 'KRW':
643
- response = self.fetchScreenerList(self.extend(params))
644
- return response
234
+ response = self.private_get_fetch_security_info(self.extend(params))
235
+
236
+ common_response = self.get_common_response(response=response)
237
+ if common_response.success != "0":
238
+ return ksxt.models.KsxtSecurityResponse(header=common_header, response=common_response, info=None)
239
+
240
+ parsed_info = self.parser.parse_security(response=response, base_market=base_market)
241
+
242
+ return ksxt.models.KsxtSecurityResponse(header=common_header, response=common_response, info=parsed_info)
243
+
244
+ @RestExchange.check_token
245
+ def fetch_ticker(self, symbol: str, base_market: str = "KRW") -> ksxt.models.KsxtTickerResponse:
246
+ if base_market == "KRW":
247
+ params = {"FID_COND_MRKT_DIV_CODE": "J", "FID_INPUT_ISCD": symbol}
645
248
  else:
646
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
647
-
249
+ assert ValueError(f"{base_market} is not valid value")
250
+
251
+ common_header = self.create_common_header(request_params=params)
252
+
253
+ response = self.private_get_fetch_ticker_price(self.extend(params))
254
+
255
+ common_response = self.get_common_response(response=response)
256
+ if common_response.success != "0":
257
+ return ksxt.models.KsxtTickerResponse(header=common_header, response=common_response, info=None)
258
+
259
+ parsed_info = self.parser.parse_ticker(response=response, base_market=base_market)
260
+
261
+ return ksxt.models.KsxtTickerResponse(header=common_header, response=common_response, info=parsed_info)
262
+
648
263
  @RestExchange.check_token
649
- def fetch_screener(self, user_id: str, screen_id: str, base_market: str= 'KRW'):
650
- if base_market == 'KRW':
651
- params = {
652
- "USER_ID": user_id,
653
- "SEQ" : screen_id
654
- }
264
+ def fetch_historical_data_index(
265
+ self, symbol: str, time_frame: str, start: str | None = None, end: str | None = None, base_market: str = "KRW"
266
+ ) -> ksxt.models.KsxtHistoricalDataResponse:
267
+ if time_frame.endswith("D"):
268
+ param_code = "D"
269
+ elif time_frame.endswith("W") or time_frame.endswith("w"):
270
+ param_code = "W"
271
+ elif time_frame.endswith("M"):
272
+ param_code = "M"
273
+ elif time_frame.endswith("Y"):
274
+ param_code = "Y"
275
+ else:
276
+ assert ValueError(f"{time_frame} is not valid value")
277
+
278
+ if start is None:
279
+ start = self.now(base_market) - timedelta(days=50)
280
+ if end is None:
281
+ end = self.now(base_market)
655
282
 
656
- response = self.fetchScreener(self.extend(params))
657
- elif base_market == 'USD':
658
- market_code = self.get_market_code_in_feeder(symbol='ALL', base_market=base_market)
283
+ if base_market == "KRW":
659
284
  params = {
660
- "AUTH": "",
661
- "EXCD": market_code,
662
- "CO_YN_PRICECUR": 1
285
+ "FID_COND_MRKT_DIV_CODE": "U",
286
+ "FID_INPUT_ISCD": symbol,
287
+ "FID_INPUT_DATE_1": start.strftime('%Y%m%d'),
288
+ "FID_INPUT_DATE_2": end.strftime('%Y%m%d'),
289
+ "FID_PERIOD_DIV_CODE": param_code
663
290
  }
664
- response = self.fetchScreenerForUS(self.extend(params))
665
-
666
- # FIXME : when implement parsing logic remove below logic.
667
- response = self.get_common_successful_response(response=response)
668
291
  else:
669
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
670
-
671
- # FIXME: screener parsing logic
292
+ assert ValueError(f"{base_market} is not valid value")
672
293
 
673
- return response
674
- # endregion private feeder
294
+ common_header = self.create_common_header(request_params=params)
675
295
 
676
- # region broker
677
- @RestExchange.check_token
678
- def create_order(self, acc_num: str, symbol: str, ticket_type: str, price: float, qty: float, otype: str, base_market: str= 'KRW'):
679
- if base_market == 'KRW':
680
- if otype.upper() == 'limit'.upper():
681
- order_dvsn = '00'
682
- elif otype.upper() == 'market'.upper():
683
- order_dvsn = '01'
684
-
685
- body = {
686
- "CANO": acc_num[:8],
687
- "ACNT_PRDT_CD": acc_num[-2:],
688
- "PDNO": symbol,
689
- "ORD_DVSN": order_dvsn,
690
- "ORD_QTY": str(qty), # string type 으로 설정
691
- "ORD_UNPR": str(price), # string type 으로 설정
692
- }
296
+ response = self.private_get_fetch_index_ohlcv(self.extend(params))
693
297
 
694
- if ticket_type == 'entry_long':
695
- response = self.sendOrderEntry(self.extend(body))
696
- elif ticket_type == 'exit_long':
697
- response = self.sendOrderExit(self.extend(body))
698
- else:
699
- return
700
- elif base_market == 'USD':
701
- if otype.upper() == 'limit'.upper():
702
- order_dvsn = '00'
703
- elif otype.upper() == 'market'.upper():
704
- # 미국장은 시장가를 세부적으로 구분하여 지원함. -> 시장가 거래를 우선 지원하지 않는다.
705
- # https://apiportal.koreainvestment.com/apiservice/apiservice-overseas-stock#L_e4a7e5fd-eed5-4a85-93f0-f46b804dae5f
706
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
707
-
708
- if ticket_type == 'entry_long':
709
- sell_type = ''
710
- elif ticket_type == 'exit_long':
711
- sell_type = '00'
712
- else:
713
- return
298
+ common_response = self.get_common_response(response=response)
299
+ if common_response.success != "0":
300
+ return ksxt.models.KsxtHistoricalDataResponse(header=common_header, response=common_response, info=None)
714
301
 
715
- market_code = self.get_market_code_in_broker(symbol=symbol, base_market=base_market)
716
- body = {
717
- "CANO": acc_num[:8],
718
- "ACNT_PRDT_CD": acc_num[-2:],
719
- "OVRS_EXCG_CD": market_code,
720
- "PDNO": symbol,
721
- "ORD_DVSN": order_dvsn,
722
- "ORD_QTY": str(qty), # string type 으로 설정
723
- "SLL_TYPE": sell_type,
724
- "OVRS_ORD_UNPR": str(price), # string type 으로 설정
725
- "ORD_SVR_DVSN_CD": "0"
726
- }
302
+ parsed_info = self.parser.parse_historical_index_data(response=response, symbol=symbol, base_market=base_market)
727
303
 
728
- if ticket_type == 'entry_long':
729
- response = self.sendOrderEntryForUS(self.extend(body))
730
- elif ticket_type == 'exit_long':
731
- response = self.sendOrderExitForUS(self.extend(body))
732
- else:
733
- return
734
- else:
735
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
304
+ return ksxt.models.KsxtHistoricalDataResponse(header=common_header, response=common_response, info=parsed_info)
736
305
 
737
- return self.parse_order_response(response=response, base_market=base_market)
306
+ @RestExchange.check_token
307
+ def fetch_historical_data(
308
+ self, symbol: str, time_frame: str, start: str | None = None, end: str | None = None, base_market: str = "KRW"
309
+ ) -> ksxt.models.KsxtHistoricalDataResponse:
310
+ if time_frame.endswith("D"):
311
+ param_code = "D"
312
+ elif time_frame.endswith("W") or time_frame.endswith("w"):
313
+ param_code = "W"
314
+ elif time_frame.endswith("M"):
315
+ param_code = "M"
316
+ elif time_frame.endswith("Y"):
317
+ param_code = "Y"
318
+ else:
319
+ assert ValueError(f"{time_frame} is not valid value")
738
320
 
321
+ if start is None:
322
+ start = self.now(base_market) - timedelta(days=100)
323
+ if end is None:
324
+ end = self.now(base_market)
325
+
326
+ if base_market == "KRW":
327
+ params = {
328
+ "FID_COND_MRKT_DIV_CODE": "J",
329
+ "FID_INPUT_ISCD": symbol,
330
+ "FID_INPUT_DATE_1": start.strftime("%Y%m%d"),
331
+ "FID_INPUT_DATE_2": end.strftime("%Y%m%d"),
332
+ "FID_PERIOD_DIV_CODE": param_code,
333
+ "FID_ORG_ADJ_PRC": "0",
334
+ }
335
+ else:
336
+ assert ValueError(f"{base_market} is not valid value")
337
+
338
+ if time_frame.endswith("m"):
339
+ common_header = self.create_common_header(request_params=params)
340
+ response = self.private_get_fetch_security_ohlcv_minute(self.extend(params))
341
+ elif time_frame.endswith("D"):
342
+ common_header = self.create_common_header(request_params=params)
343
+ response = self.private_get_fetch_security_ohlcv_day(self.extend(params))
344
+ elif time_frame.endswith("W"):
345
+ common_header = self.create_common_header(request_params=params)
346
+ response = self.private_get_fetch_security_ohlcv_week(self.extend(params))
347
+ elif time_frame.endswith("M"):
348
+ common_header = self.create_common_header(request_params=params)
349
+ response = self.private_get_fetch_security_ohlcv_month(self.extend(params))
350
+ elif time_frame.endswith("Y"):
351
+ common_header = self.create_common_header(request_params=params)
352
+ response = self.private_get_fetch_security_ohlcv_year(self.extend(params))
353
+
354
+ common_response = self.get_common_response(response=response)
355
+ if common_response.success != "0":
356
+ return ksxt.models.KsxtHistoricalDataResponse(header=common_header, response=common_response, info=None)
357
+
358
+ parsed_info = self.parser.parse_historical_data(response=response, symbol=symbol, base_market=base_market)
359
+
360
+ return ksxt.models.KsxtHistoricalDataResponse(header=common_header, response=common_response, info=parsed_info)
361
+
739
362
  @RestExchange.check_token
740
- def cancel_order(self, acc_num: str, order_id: str, symbol: Optional[str] = '', qty: float = 0, *args, base_market: str= 'KRW'):
741
- if base_market == 'KRW':
742
- body = {
363
+ def modify_order(
364
+ self,
365
+ acc_num: str,
366
+ order_id: str,
367
+ price: float,
368
+ qty: float,
369
+ *args,
370
+ symbol: str | None = "",
371
+ base_market: str = "KRW",
372
+ ) -> ksxt.models.KsxtModifyOrderResponse:
373
+ if base_market == "KRW":
374
+ params = {
743
375
  "CANO": acc_num[:8],
744
376
  "ACNT_PRDT_CD": acc_num[-2:],
745
377
  "KRX_FWDG_ORD_ORGNO": "",
746
- "ORGN_ODNO":str(order_id),
747
- "RVSE_CNCL_DVSN_CD":"02",
748
- "ORD_DVSN":"00",
749
- "ORD_QTY":str(qty),
750
- "ORD_UNPR":str(0),
378
+ "ORGN_ODNO": str(order_id),
379
+ "RVSE_CNCL_DVSN_CD": "01",
380
+ "ORD_DVSN": "00",
381
+ "ORD_QTY": str(qty),
382
+ "ORD_UNPR": str(price),
751
383
  "QTY_ALL_ORD_YN": "N",
752
384
  }
385
+ else:
386
+ assert ValueError(f"{base_market} is not valid value")
753
387
 
754
- # 수량 미입력시 전량 취소
755
- if qty == 0:
756
- body['QTY_ALL_ORD_YN'] = 'Y'
757
-
758
- response = self.sendCancelOrder(self.extend(body))
759
- elif base_market == 'USD':
760
- if qty == 0:
761
- return self.get_error_response(error_code='qty_error', error_message=f'{base_market} cancel order need to set qty.')
762
-
763
- market_code = self.get_market_code_in_broker(symbol=symbol, base_market=base_market)
764
- body = {
765
- "CANO": acc_num[:8],
766
- "ACNT_PRDT_CD": acc_num[-2:],
767
- "OVRS_EXCG_CD": market_code,
768
- "PDNO": symbol,
769
- "ORGN_ODNO":str(order_id),
770
- "RVSE_CNCL_DVSN_CD":"02",
771
- "ORD_QTY":str(qty),
772
- "OVRS_ORD_UNPR":str(0),
773
- }
388
+ common_header = self.create_common_header(request_params=params)
389
+ response = self.private_post_send_modify_order(self.extend(params))
390
+
391
+ common_response = self.get_common_response(response=response)
392
+ if common_response.success != "0":
393
+ return ksxt.models.KsxtModifyOrderResponse(header=common_header, response=common_response, info=None)
394
+
395
+ parsed_info = self.parser.parse_modify_order(response=response, base_market=base_market)
396
+
397
+ return ksxt.models.KsxtModifyOrderResponse(header=common_header, response=common_response, info=parsed_info)
774
398
 
775
- response = self.sendCancelOrderForUS(self.extend(body))
776
- else:
777
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
778
-
779
- return self.parse_order_response(response=response, base_market=base_market)
780
-
781
399
  @RestExchange.check_token
782
- def modify_order(self, acc_num: str, order_id: str, price: float, qty: float, *args, symbol: Optional[str] = '', base_market: str= 'KRW'):
783
- if base_market == 'KRW':
784
- body = {
400
+ def cancel_order(
401
+ self, acc_num: str, order_id: str, symbol: str | None = "", qty: float = 0, *args, base_market: str = "KRW"
402
+ ) -> ksxt.models.KsxtCancelOrderResponse:
403
+ if base_market == "KRW":
404
+ params = {
785
405
  "CANO": acc_num[:8],
786
406
  "ACNT_PRDT_CD": acc_num[-2:],
787
407
  "KRX_FWDG_ORD_ORGNO": "",
788
- "ORGN_ODNO":str(order_id),
789
- "RVSE_CNCL_DVSN_CD":"01",
790
- "ORD_DVSN":"00",
791
- "ORD_QTY":str(qty),
792
- "ORD_UNPR":str(price),
408
+ "ORGN_ODNO": str(order_id),
409
+ "RVSE_CNCL_DVSN_CD": "02",
410
+ "ORD_DVSN": "00",
411
+ "ORD_QTY": str(qty),
412
+ "ORD_UNPR": str(0),
793
413
  "QTY_ALL_ORD_YN": "N",
794
414
  }
415
+ else:
416
+ assert ValueError(f"{base_market} is not valid value")
795
417
 
796
- # 수량 미입력시 전량 수정
797
- if qty == 0:
798
- body['QTY_ALL_ORD_YN'] = 'Y'
418
+ common_header = self.create_common_header(request_params=params)
419
+ response = self.private_post_send_cancel_order(self.extend(params))
799
420
 
800
- response = self.sendModifyOrder(self.extend(body))
801
- elif base_market == 'USD':
802
- market_code = self.get_market_code_in_broker(symbol=symbol, base_market=base_market)
803
- body = {
804
- "CANO": acc_num[:8],
805
- "ACNT_PRDT_CD": acc_num[-2:],
806
- "OVRS_EXCG_CD": market_code,
807
- "PDNO": symbol,
808
- "ORGN_ODNO":str(order_id),
809
- "RVSE_CNCL_DVSN_CD":"01",
810
- "ORD_QTY":str(qty),
811
- "OVRS_ORD_UNPR":str(price),
812
- }
421
+ common_response = self.get_common_response(response=response)
422
+ if common_response.success != "0":
423
+ return ksxt.models.KsxtCancelOrderResponse(header=common_header, response=common_response, info=None)
813
424
 
814
- response = self.sendModifyOrderForUS(self.extend(body))
815
- else:
816
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
817
-
818
- return self.parse_order_response(response=response, base_market=base_market)
819
-
820
- def parse_order_response(self, response: dict, base_market: str='KRW'):
821
- result = self.get_common_successful_response(response=response)
822
-
823
- data = response['output']
824
- time = self.safe_string(data, 'ORD_TMD')
825
- today = datetime.today()
826
- dt = datetime.combine(today, datetime.strptime(time, '%H%M%S').time())
827
-
828
- result.update({
829
- # 주문 날짜 (YYYY-mm-DD HH:MM:SS)
830
- 'datetime': datetime.strftime(dt, '%Y-%m-%d %H:%M:%S'),
831
- # 주문번호
832
- 'order_id': self.safe_string(data, 'ODNO')
833
- })
834
-
835
- return result
836
-
837
- @RestExchange.check_token
838
- def fetch_open_order(self, acc_num: str, symbol: Optional[str] = '', start: Optional[str] = None, end: Optional[str] = None, base_market: str= 'KRW'):
839
- if start is None:
840
- start = KoreaInvest.now(base_market=base_market)
841
-
842
- if end is None:
843
- end = KoreaInvest.now(base_market=base_market)
425
+ parsed_info = self.parser.parse_cancel_order(response=response, base_market=base_market)
844
426
 
845
- if base_market == 'KRW':
846
- params = {
847
- 'CANO': acc_num[:8],
848
- 'ACNT_PRDT_CD': acc_num[-2:],
849
- 'INQR_STRT_DT': start.strftime('%Y%m%d'),
850
- 'INQR_END_DT' : end.strftime('%Y%m%d'),
851
- 'SLL_BUY_DVSN_CD' : '00',
852
- 'INQR_DVSN': '00',
853
- 'PDNO': symbol,
854
- 'CCLD_DVSN': '02',
855
- 'ORD_GNO_BRNO': '',
856
- 'ODNO': '',
857
- 'INQR_DVSN_3': '00',
858
- 'INQR_DVSN_1': '',
859
- 'CTX_AREA_FK100': '',
860
- 'CTX_AREA_NK100': ''
861
- }
862
-
863
- response = self.fetchOpenedOrder(self.extend(params))
864
- elif base_market == 'USD':
865
- market_code = self.get_market_code_in_broker('ALL', base_market=base_market)
866
- params = {
867
- 'CANO': acc_num[:8],
868
- 'ACNT_PRDT_CD': acc_num[-2:],
869
- 'PDNO': symbol if symbol is not None else '%',
870
- 'OVRS_EXCG_CD': market_code,
871
- 'SORT_SQN': 'DS', # DS : 정순, AS : 역순
872
- 'CTX_AREA_NK200': '',
873
- 'CTX_AREA_FK200': ''
874
- }
875
-
876
- response = self.fetchOpenedOrderForUS(self.extend(params))
877
- else:
878
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
427
+ return ksxt.models.KsxtCancelOrderResponse(header=common_header, response=common_response, info=parsed_info)
879
428
 
880
- return self.parse_open_order(response=response, base_market=base_market)
881
-
882
- def parse_open_order(self, response: dict, base_market: str='KRW'):
883
- if base_market == 'KRW':
884
- data = self.safe_value(response, 'output1')
885
- elif base_market == 'USD':
886
- data = self.safe_value(response, 'output')
887
- else:
888
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
889
-
890
- if data is None:
891
- return response
892
-
893
- result = self.get_common_successful_response(response=response)
894
-
895
- orders = [self.parse_open_order_history(_, base_market) for _ in data]
896
- sorted_orders = self.sort_by(orders, 'datetime')
897
-
898
- result.update({
899
- # 주문 정보
900
- 'orders': sorted_orders
901
- })
902
-
903
- return result
904
-
905
- def parse_open_order_history(self, order: dict, base_market: str='KRW'):
906
- date = self.safe_string(order, 'ord_dt')
907
- time = self.safe_string(order, 'ord_tmd')
908
- dt = datetime.combine(datetime.strptime(date, '%Y%m%d'), datetime.strptime(time, '%H%M%S').time())
909
-
910
- position = self.safe_string(order, 'sll_buy_dvsn_cd')
911
- if position == '01':
912
- position = 'exit_long'
913
- elif position == '02':
914
- position = 'entry_long'
915
- else:
916
- position = None
917
-
918
- result = {
919
- # 주문 날짜 (YYYY-mm-DD HH:MM:SS)
920
- 'datetime': datetime.strftime(dt, '%Y-%m-%d %H:%M:%S'),
921
- # 주문번호
922
- 'order_id': self.safe_string(order, 'odno'),
923
- # 원주문번호
924
- 'org_order_id': self.safe_string(order, 'orgn_odno'),
925
- # long or short
926
- 'position': position,
927
- # 종목코드
928
- 'symbol': self.safe_string(order, 'pdno'),
429
+ @RestExchange.check_token
430
+ def create_order(
431
+ self,
432
+ acc_num: str,
433
+ symbol: str,
434
+ ticket_type: Literal["EntryLong"] | Literal["EntryShort"] | Literal["ExitLong"] | Literal["ExitShort"],
435
+ otype: Literal["limit"] | Literal["market"],
436
+ price: float | None = 0,
437
+ qty: float | None = 0,
438
+ amount: float | None = 0,
439
+ base_market: str = "KRW",
440
+ ) -> ksxt.models.KsxtCreateOrderResponse:
441
+ if otype.lower() == "limit":
442
+ order_dvsn = "00"
443
+ elif otype.lower() == "market":
444
+ order_dvsn = "01"
445
+ params = {
446
+ "CANO": acc_num[:8],
447
+ "ACNT_PRDT_CD": acc_num[-2:],
448
+ "PDNO": symbol,
449
+ "ORD_DVSN": order_dvsn,
450
+ "ORD_QTY": str(qty), # string type 으로 설정
451
+ "ORD_UNPR": str(price), # string type 으로 설정
929
452
  }
930
453
 
931
- if base_market == 'KRW':
932
- result.update({
933
- # 주문구분
934
- 'order_type': self.safe_string(order, 'ord_dvsn_cd'),
935
- # 주문단가
936
- 'price': self.safe_number(order, 'ord_unpr'),
937
- 'qty': {
938
- # 주문수량
939
- 'total': self.safe_number(order, 'ord_qty'),
940
- # 체결수량
941
- 'used': self.safe_number(order, 'tot_ccld_qty'),
942
- # 잔여수량
943
- 'free': self.safe_number(order, 'rmn_qty')
944
- }
945
- })
946
- elif base_market == 'USD':
947
- result.update({
948
- # 주문구분
949
- 'order_type': 'limit',
950
- # 주문단가
951
- 'price': self.safe_number(order, 'ft_ord_unpr3'),
952
- 'qty': {
953
- # 주문수량
954
- 'total': self.safe_number(order, 'ft_ord_qty'),
955
- # 체결수량
956
- 'used': self.safe_number(order, 'ft_ccld_qty'),
957
- # 잔여수량
958
- 'free': self.safe_number(order, 'nccs_qty')
959
- }
960
- })
961
-
962
- return result
963
-
964
- @RestExchange.check_token
965
- def fetch_closed_order(self, acc_num: str, symbol: Optional[str] = '', start: Optional[str] = None, end: Optional[str] = None, base_market: str= 'KRW'):
966
- if start is None:
967
- start = KoreaInvest.now(base_market=base_market)
968
-
969
- if end is None:
970
- end = KoreaInvest.now(base_market=base_market)
454
+ common_header = self.create_common_header(request_params=params)
971
455
 
972
- if base_market == 'KRW':
973
- params = {
974
- 'CANO': acc_num[:8],
975
- 'ACNT_PRDT_CD': acc_num[-2:],
976
- 'INQR_STRT_DT': start.strftime('%Y%m%d'),
977
- 'INQR_END_DT' : end.strftime('%Y%m%d'),
978
- 'SLL_BUY_DVSN_CD' : '00',
979
- 'INQR_DVSN': '00',
980
- 'PDNO': symbol,
981
- 'CCLD_DVSN': '01',
982
- 'ORD_GNO_BRNO': '',
983
- 'ODNO': '',
984
- 'INQR_DVSN_3': '00',
985
- 'INQR_DVSN_1': '',
986
- 'CTX_AREA_FK100': '',
987
- 'CTX_AREA_NK100': ''
988
- }
989
-
990
- response = self.fetchClosedOrder(self.extend(params))
991
- elif base_market == 'USD':
992
- market_code = self.get_market_code_in_broker('ALL', base_market=base_market)
993
- params = {
994
- 'CANO': acc_num[:8],
995
- 'ACNT_PRDT_CD': acc_num[-2:],
996
- 'PDNO': symbol if symbol is not None else '%',
997
- 'ORD_STRT_DT': start.strftime('%Y%m%d'),
998
- 'ORD_END_DT' : end.strftime('%Y%m%d'),
999
- 'SLL_BUY_DVSN' : '00',
1000
- 'CCLD_NCCS_DVSN' : '01' if not self.is_dev else '00',
1001
- 'OVRS_EXCG_CD': market_code,
1002
- 'SORT_SQN': 'DS', # DS : 정순, AS : 역순
1003
- 'ORD_DT': '',
1004
- 'ORD_GNO_BRNO': '',
1005
- 'ODNO': '',
1006
- 'CTX_AREA_NK200': '',
1007
- 'CTX_AREA_FK200': ''
1008
- }
456
+ if ticket_type == "EntryLong":
457
+ response = self.private_post_send_order_entry(self.extend(params))
458
+ elif ticket_type == "ExitLong":
459
+ response = self.private_post_send_order_exit(self.extend(params))
1009
460
 
1010
- response = self.fetchClosedOrderForUS(self.extend(params))
461
+ common_response = self.get_common_response(response=response)
462
+ if common_response.success != "0":
463
+ return ksxt.models.KsxtCreateOrderResponse(header=common_header, response=common_response, info=None)
1011
464
 
1012
- return self.parse_closed_order(response=response, base_market=base_market)
1013
-
1014
- def parse_closed_order(self, response: dict, base_market: str='KRW'):
1015
- if base_market == 'KRW':
1016
- data = self.safe_value(response, 'output1')
1017
- elif base_market == 'USD':
1018
- data = self.safe_value(response, 'output')
1019
- else:
1020
- return self.get_error_response(error_code='market_error', error_message=f'{base_market} market is not yet supported.')
1021
-
1022
- if data is None:
1023
- return response
1024
-
1025
- result = self.get_common_successful_response(response=response)
1026
-
1027
- orders = [self.parse_closed_order_history(_, base_market) for _ in data]
1028
- sorted_orders = self.sort_by(orders, 'datetime')
1029
-
1030
- result.update({
1031
- # 주문 정보
1032
- 'orders': sorted_orders
1033
- })
1034
-
1035
- return result
1036
-
1037
- def parse_closed_order_history(self, order: dict, base_market: str='KRW'):
1038
- date = self.safe_string(order, 'ord_dt')
1039
- time = self.safe_string(order, 'ord_tmd')
1040
- dt = datetime.combine(datetime.strptime(date, '%Y%m%d'), datetime.strptime(time, '%H%M%S').time())
1041
-
1042
- position = self.safe_string(order, 'sll_buy_dvsn_cd')
1043
- if position == '01':
1044
- position = 'exit_long'
1045
- elif position == '02':
1046
- position = 'entry_long'
1047
- else:
1048
- position = None
1049
-
1050
- result = {
1051
- # 주문 날짜 (YYYY-mm-DD HH:MM:SS)
1052
- 'datetime': datetime.strftime(dt, '%Y-%m-%d %H:%M:%S'),
1053
- # 주문번호
1054
- 'order_id': self.safe_string(order, 'odno'),
1055
- # 원주문번호
1056
- 'org_order_id': self.safe_string(order, 'orgn_odno'),
1057
- # long or short
1058
- 'position': position,
1059
- # 종목코드
1060
- 'symbol': self.safe_string(order, 'pdno'),
1061
- }
465
+ parsed_info = self.parser.parse_create_order(response=response, base_market=base_market)
1062
466
 
1063
- if base_market == 'KRW':
1064
- result.update({
1065
- # 주문구분
1066
- 'order_type': self.safe_string(order, 'ord_dvsn_cd'),
1067
- # 주문단가
1068
- 'price': self.safe_number(order, 'ord_unpr'),
1069
- # 체결수량
1070
- 'qty': self.safe_number(order, 'tot_ccld_qty')
1071
- })
1072
-
1073
- elif base_market == 'USD':
1074
- result.update({
1075
- # 주문구분
1076
- 'order_type': 'limit',
1077
- # 주문단가
1078
- 'price': self.safe_number(order, 'ft_ccld_unpr3'),
1079
- # 체결수량
1080
- 'qty': self.safe_number(order, 'ft_ccld_qty')
1081
- })
1082
-
1083
- return result
1084
-
1085
- # endregion broker
1086
-
1087
- def get_common_successful_response(self, response) -> dict:
1088
- if type(response) == dict:
1089
- return {
1090
- 'response': {
1091
- # 성공 실패 여부
1092
- 'success' : self.safe_string(response, 'rt_cd'),
1093
- # 응답코드
1094
- 'code': self.safe_string(response, 'msg_cd'),
1095
- # 응답메세지
1096
- 'message': self.safe_string(response, 'msg1'),
1097
- },
1098
- # 원본 데이터
1099
- 'info': response,
1100
- }
1101
- else:
1102
- return {
1103
- 'response': {
1104
- # 성공 실패 여부
1105
- 'success' : '0',
1106
- # 응답코드
1107
- 'code': 'success',
1108
- # 응답메세지
1109
- 'message': '',
1110
- },
1111
- # 원본 데이터
1112
- 'info': response
1113
- }
467
+ return ksxt.models.KsxtCreateOrderResponse(header=common_header, response=common_response, info=parsed_info)
468
+
469
+ @RestExchange.check_token
470
+ def get_market_code_in_feeder(self, symbol: str, base_market: str = "KRW"):
471
+ if base_market == "KRW":
472
+ return ""
473
+ elif base_market == "USD":
474
+ if symbol.upper() == "ALL":
475
+ return "NASD"
1114
476
 
1115
- def get_market_code_in_feeder(self, symbol: str, base_market: str= 'KRW'):
1116
- if base_market == 'KRW':
1117
- return ''
1118
- elif base_market == 'USD':
1119
- if symbol.upper() == 'ALL':
1120
- return 'NASD'
1121
-
1122
477
  response = self.fetch_security(symbol=symbol, base_market=base_market)
1123
- return response['exchange']
478
+ return response["exchange"]
1124
479
  else:
1125
- return ''
1126
-
1127
- def get_market_code_in_broker(self, symbol: str, base_market: str= 'KRW'):
1128
- if base_market == 'KRW':
1129
- return ''
1130
- elif base_market == 'USD':
1131
- if symbol.upper() == 'ALL':
1132
- return 'NASD'
1133
-
480
+ return ""
481
+
482
+ @RestExchange.check_token
483
+ def get_market_code_in_broker(self, symbol: str, base_market: str = "KRW"):
484
+ if base_market == "KRW":
485
+ return ""
486
+ elif base_market == "USD":
487
+ if symbol.upper() == "ALL":
488
+ return "NASD"
489
+
1134
490
  response = self.fetch_security(symbol=symbol, base_market=base_market)
1135
- exname = response['exchange']
1136
- if exname == 'NYS':
1137
- return 'NYSE'
1138
- elif exname == 'NAS':
1139
- return 'NASD'
1140
- elif exname == 'AMS':
1141
- return 'AMEX'
491
+ exname = response["exchange"]
492
+ if exname == "NYS":
493
+ return "NYSE"
494
+ elif exname == "NAS":
495
+ return "NASD"
496
+ elif exname == "AMS":
497
+ return "AMEX"
1142
498
  else:
1143
- return ''
1144
- else:
1145
- return ''
1146
-
1147
-
499
+ return ""
500
+ else:
501
+ return ""