ksxt 1.0.0__tar.gz → 1.0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {ksxt-1.0.0/src/ksxt.egg-info → ksxt-1.0.1}/PKG-INFO +1 -1
- {ksxt-1.0.0 → ksxt-1.0.1}/pyproject.toml +1 -1
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/__pycache__/koreainvest.cpython-312.pyc +0 -0
- ksxt-1.0.1/src/ksxt/async_/__pycache__/koreainvest.cpython-312.pyc +0 -0
- ksxt-1.0.1/src/ksxt/async_/base/__pycache__/async_exchange.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/async_/base/async_exchange.py +35 -42
- ksxt-1.0.1/src/ksxt/async_/koreainvest.py +481 -0
- ksxt-1.0.1/src/ksxt/base/__pycache__/exchange.cpython-312.pyc +0 -0
- ksxt-1.0.1/src/ksxt/base/__pycache__/rate_limiter.cpython-312.pyc +0 -0
- ksxt-1.0.1/src/ksxt/base/__pycache__/rest_exchange.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/base/exchange.py +5 -0
- ksxt-1.0.1/src/ksxt/base/rate_limiter.py +88 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/base/rest_exchange.py +52 -26
- ksxt-1.0.1/src/ksxt/config/__init__.py +5 -0
- ksxt-1.0.1/src/ksxt/config/__pycache__/__init__.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/config/bithumb.toml +2 -1
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/config/upbit.toml +6 -1
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/koreainvest.py +6 -26
- {ksxt-1.0.0 → ksxt-1.0.1/src/ksxt.egg-info}/PKG-INFO +1 -1
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt.egg-info/SOURCES.txt +2 -1
- ksxt-1.0.0/src/ksxt/async_/__pycache__/koreainvest.cpython-312.pyc +0 -0
- ksxt-1.0.0/src/ksxt/async_/base/__pycache__/async_exchange.cpython-312.pyc +0 -0
- ksxt-1.0.0/src/ksxt/async_/base/throttler.py +0 -63
- ksxt-1.0.0/src/ksxt/async_/koreainvest.py +0 -849
- ksxt-1.0.0/src/ksxt/base/__pycache__/exchange.cpython-312.pyc +0 -0
- ksxt-1.0.0/src/ksxt/base/__pycache__/rest_exchange.cpython-312.pyc +0 -0
- ksxt-1.0.0/src/ksxt/config/__init__.py +0 -3
- ksxt-1.0.0/src/ksxt/config/__pycache__/__init__.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/LICENSE.txt +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/MANIFEST.in +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/README.md +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/setup.cfg +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/__init__.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/__pycache__/__init__.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/__pycache__/bithumb.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/__pycache__/upbit.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/api/__init__.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/api/__pycache__/__init__.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/api/__pycache__/bithumb.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/api/__pycache__/koreainvest.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/api/__pycache__/upbit.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/api/auto/api_generator.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/api/auto/bithumb.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/api/auto/koreainvest.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/api/auto/upbit.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/api/bithumb.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/api/koreainvest.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/api/upbit.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/async_/__init__.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/async_/__pycache__/__init__.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/async_/__pycache__/bithumb.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/async_/__pycache__/upbit.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/async_/base/__init__.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/async_/base/__pycache__/__init__.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/async_/base/__pycache__/throttler.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/async_/bithumb.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/async_/upbit.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/base/__init__.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/base/__pycache__/__init__.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/base/__pycache__/errors.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/base/__pycache__/types.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/base/com_exchange.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/base/errors.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/base/types.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/bithumb.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/config/koreainvest.toml +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/config/token.toml +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/__pycache__/base.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/__pycache__/db.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/__pycache__/logging.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/__pycache__/manager.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/__pycache__/markets.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/base.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/db.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/krx/__pycache__/kosdaq.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/krx/__pycache__/kospi.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/krx/__pycache__/stock.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/krx/kosdaq.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/krx/kospi.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/krx/stock.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/logging.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/manager.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/markets.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/us/__pycache__/amex.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/us/__pycache__/nasdaq.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/us/__pycache__/nyse.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/us/__pycache__/stock.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/us/amex.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/us/nasdaq.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/us/nyse.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/market/us/stock.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/__init__.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/__pycache__/__init__.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/__pycache__/balance.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/__pycache__/cash.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/__pycache__/common.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/__pycache__/error.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/__pycache__/historical.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/__pycache__/market.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/__pycache__/order.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/__pycache__/orderbook.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/__pycache__/ticker.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/__pycache__/token.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/__pycache__/transaction.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/balance.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/cash.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/common.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/error.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/historical.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/market.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/order.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/orderbook.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/ticker.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/token.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/models/transaction.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/parser/__pycache__/bithumb.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/parser/__pycache__/koreainvest.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/parser/__pycache__/parser.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/parser/__pycache__/upbit.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/parser/bithumb.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/parser/koreainvest.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/parser/parser.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/parser/upbit.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/sample/symbol_sync.ipynb +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/upbit.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/utils/__pycache__/safer.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/utils/__pycache__/sorter.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/utils/__pycache__/timer.cpython-312.pyc +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/utils/safer.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/utils/sorter.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt/utils/timer.py +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt.egg-info/dependency_links.txt +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt.egg-info/requires.txt +0 -0
- {ksxt-1.0.0 → ksxt-1.0.1}/src/ksxt.egg-info/top_level.txt +0 -0
Binary file
|
Binary file
|
@@ -1,19 +1,14 @@
|
|
1
1
|
import asyncio
|
2
|
+
import time
|
2
3
|
import aiohttp
|
3
|
-
import json
|
4
|
-
import os
|
5
|
-
import platform
|
6
|
-
import tomllib
|
7
4
|
from datetime import datetime
|
8
|
-
from pathlib import Path
|
9
5
|
from typing import Any, Dict, Literal, Optional
|
10
6
|
|
11
|
-
import yarl
|
12
7
|
|
13
|
-
from ksxt.async_.base.throttler import Throttler
|
14
8
|
from ksxt.base.errors import NotSupportedError
|
9
|
+
from ksxt.base.rate_limiter import RateLimiterContext
|
15
10
|
from ksxt.base.rest_exchange import RestExchange
|
16
|
-
from ksxt.config import
|
11
|
+
from ksxt.config import VALID_METHODS
|
17
12
|
import ksxt.models
|
18
13
|
|
19
14
|
|
@@ -23,22 +18,26 @@ class AsyncExchange(RestExchange):
|
|
23
18
|
def __init__(self, config: Dict = None, filename: str = None):
|
24
19
|
super().__init__(config, filename)
|
25
20
|
|
26
|
-
self.asyncio_loop =
|
27
|
-
self.session
|
28
|
-
self.throttle = Throttler({}, self.asyncio_loop)
|
21
|
+
self.asyncio_loop = None
|
22
|
+
self.session: aiohttp.ClientSession = None
|
29
23
|
|
30
24
|
async def initialize(self):
|
31
25
|
if self.asyncio_loop is None:
|
32
26
|
self.asyncio_loop = asyncio.get_event_loop()
|
33
|
-
|
27
|
+
|
28
|
+
if self.session is None or (
|
29
|
+
self.session_last_used and (time.time() - self.session_last_used > self.session_lifetime)
|
30
|
+
):
|
31
|
+
if self.session:
|
32
|
+
await self.session.close()
|
34
33
|
self.session = aiohttp.ClientSession()
|
35
|
-
|
36
|
-
self.throttle = Throttler({}, self.asyncio_loop)
|
34
|
+
self.session_last_used = time.time()
|
37
35
|
|
38
36
|
async def close(self):
|
39
37
|
if self.session:
|
40
38
|
await self.session.close()
|
41
39
|
self.session = None
|
40
|
+
self.session_last_used = None
|
42
41
|
|
43
42
|
async def __aenter__(self):
|
44
43
|
await self.initialize()
|
@@ -47,49 +46,43 @@ class AsyncExchange(RestExchange):
|
|
47
46
|
async def __aexit__(self, *args):
|
48
47
|
await self.close()
|
49
48
|
|
50
|
-
def
|
51
|
-
|
52
|
-
|
53
|
-
else:
|
54
|
-
tr_config_filename = filename
|
55
|
-
|
56
|
-
config_path = os.path.join(CONFIG_DIR, tr_config_filename)
|
49
|
+
async def fetch(self, url, method="GET", headers=None, body=None, params=None):
|
50
|
+
# Ensure that resources are initialized before the request
|
51
|
+
await self.initialize()
|
57
52
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
encoding="utf-8",
|
62
|
-
) as f:
|
63
|
-
c = json.load(f)
|
64
|
-
return {"apis": c[self.name]}
|
53
|
+
method_lower = method.lower()
|
54
|
+
if method_lower not in VALID_METHODS:
|
55
|
+
raise ValueError(f"Invalid HTTP method: {method}")
|
65
56
|
|
66
|
-
|
67
|
-
with open(config_path, mode="rb") as f:
|
68
|
-
c = tomllib.load(f)
|
69
|
-
return c
|
57
|
+
session_method = getattr(self.session, method.lower())
|
70
58
|
|
71
|
-
|
72
|
-
|
73
|
-
request_body = str(body).encode() if body else None
|
74
|
-
request_params = params
|
59
|
+
# TODO : Set rate limiters value when config load
|
60
|
+
api_name = ""
|
75
61
|
|
76
|
-
|
62
|
+
if api_name and api_name in self.rate_limiters:
|
63
|
+
await self.rate_limiters[api_name].async_acquire()
|
77
64
|
|
78
65
|
try:
|
79
66
|
async with session_method(
|
80
67
|
url,
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
params=request_params,
|
68
|
+
headers=headers,
|
69
|
+
data=str(body).encode() if body else None,
|
70
|
+
params=params,
|
85
71
|
timeout=aiohttp.ClientTimeout(total=int(self.timeout / 1000)),
|
86
72
|
) as response:
|
87
73
|
http_response = await response.text(errors="replace")
|
88
74
|
json_response = self.parse_json(http_response)
|
89
75
|
return json_response
|
90
|
-
except asyncio.TimeoutError as e:
|
76
|
+
except (asyncio.TimeoutError, aiohttp.ClientError) as e:
|
91
77
|
details = f"{self.id} {method} {url}"
|
92
78
|
raise TimeoutError(details) from e
|
79
|
+
finally:
|
80
|
+
if (
|
81
|
+
api_name
|
82
|
+
and api_name in self.rate_limiters
|
83
|
+
and isinstance(self.rate_limiters[api_name], RateLimiterContext)
|
84
|
+
):
|
85
|
+
self.rate_limiters[api_name].release()
|
93
86
|
|
94
87
|
async def fetch2(
|
95
88
|
self, path, security_type, params={}, headers: Optional[Any] = None, body: Optional[Any] = None, config={}
|
@@ -0,0 +1,481 @@
|
|
1
|
+
from datetime import datetime, timedelta
|
2
|
+
import json
|
3
|
+
import os
|
4
|
+
import time
|
5
|
+
from typing import Any, Dict, Literal, Optional
|
6
|
+
|
7
|
+
import pytz
|
8
|
+
|
9
|
+
from ksxt.api.koreainvest import ImplicitAPI
|
10
|
+
from ksxt.async_.base.async_exchange import AsyncExchange
|
11
|
+
from ksxt.market.manager import MarketManager
|
12
|
+
from ksxt.parser.koreainvest import KoreaInvestParser
|
13
|
+
|
14
|
+
import ksxt.models
|
15
|
+
|
16
|
+
|
17
|
+
class KoreaInvest(AsyncExchange, ImplicitAPI):
|
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
|
+
def sign(
|
34
|
+
self,
|
35
|
+
path,
|
36
|
+
security_type,
|
37
|
+
method_type,
|
38
|
+
api_type: Any = "public",
|
39
|
+
headers: Optional[Any] = None,
|
40
|
+
body: Optional[Any] = None,
|
41
|
+
params: Optional[Any] = None,
|
42
|
+
config={},
|
43
|
+
):
|
44
|
+
mode = "dev" if self.is_dev == True else "app"
|
45
|
+
|
46
|
+
host_url = self.apis[self.type][mode]["hostname"]
|
47
|
+
destination = self.apis[self.type][security_type][path]["url"]
|
48
|
+
version = self.apis[self.type]["version"]
|
49
|
+
params["version"] = version
|
50
|
+
destination = self.implode_params(destination, params)
|
51
|
+
|
52
|
+
url = f"{host_url}/{destination}"
|
53
|
+
|
54
|
+
tr_id = self.apis[self.type][security_type][path][mode]["tr_id"]
|
55
|
+
authorization_token = f"Bearer {self.token}"
|
56
|
+
|
57
|
+
if api_type == "private":
|
58
|
+
if headers is None:
|
59
|
+
headers = {}
|
60
|
+
headers.update(
|
61
|
+
{
|
62
|
+
"content-type": "application/json",
|
63
|
+
"authorization": authorization_token,
|
64
|
+
"appkey": self.open_key,
|
65
|
+
"appsecret": self.secret_key,
|
66
|
+
"tr_id": tr_id,
|
67
|
+
"custtype": "P",
|
68
|
+
}
|
69
|
+
)
|
70
|
+
|
71
|
+
if method_type.upper() == "POST":
|
72
|
+
body = json.dumps(params)
|
73
|
+
params = {}
|
74
|
+
|
75
|
+
return {"url": url, "method": method_type, "headers": headers, "body": body, "params": params}
|
76
|
+
|
77
|
+
async def create_token(self) -> ksxt.models.KsxtTokenResponse:
|
78
|
+
params = {"grant_type": "client_credentials", "appkey": self.open_key, "appsecret": self.secret_key}
|
79
|
+
|
80
|
+
common_header = self.create_common_header(request_params=params)
|
81
|
+
|
82
|
+
response = await self.public_post_generate_token(self.extend(params))
|
83
|
+
|
84
|
+
common_response = self.get_common_response(response=response)
|
85
|
+
if common_response.success != "0":
|
86
|
+
return ksxt.models.KsxtTokenResponse(header=common_header, response=common_response, info=None)
|
87
|
+
|
88
|
+
parsed_info = self.parser.parse_token(response=response)
|
89
|
+
|
90
|
+
self.save_token(self.open_key, parsed_info.access_token, expired=parsed_info.expired_datetime)
|
91
|
+
|
92
|
+
return ksxt.models.KsxtTokenResponse(header=common_header, response=common_response, info=parsed_info)
|
93
|
+
|
94
|
+
def get_common_response(self, response):
|
95
|
+
if "error_code" in response:
|
96
|
+
return self.create_common_response(
|
97
|
+
success="1",
|
98
|
+
msg_code=self.safe_string(response, "error_code"),
|
99
|
+
msg=self.safe_string(response, "error_description"),
|
100
|
+
info=response,
|
101
|
+
)
|
102
|
+
|
103
|
+
if "rt_cd" in response and response["rt_cd"] != "0":
|
104
|
+
return self.create_common_response(
|
105
|
+
success="1",
|
106
|
+
msg_code=self.safe_string(response, "msg_cd"),
|
107
|
+
msg=self.safe_string(response, "msg1"),
|
108
|
+
info=response,
|
109
|
+
)
|
110
|
+
|
111
|
+
if "response" in response and response["response"]["success"] != "0":
|
112
|
+
return self.create_common_response(
|
113
|
+
success="1",
|
114
|
+
msg_code=self.safe_string(response["response"], "code"),
|
115
|
+
msg=self.safe_string(response["response"], "message"),
|
116
|
+
info=response,
|
117
|
+
)
|
118
|
+
|
119
|
+
return self.create_common_response(
|
120
|
+
success="0",
|
121
|
+
msg_code=self.safe_string(response, "msg_cd"),
|
122
|
+
msg=self.safe_string(response, "msg1"),
|
123
|
+
info=response,
|
124
|
+
)
|
125
|
+
|
126
|
+
@AsyncExchange.check_token
|
127
|
+
async def fetch_balance(self, acc_num: str, base_market: str = "KRW") -> ksxt.models.KsxtBalanceResponse:
|
128
|
+
if base_market == "KRW":
|
129
|
+
params = {
|
130
|
+
"CANO": acc_num[:8],
|
131
|
+
"ACNT_PRDT_CD": acc_num[-2:],
|
132
|
+
"AFHR_FLPR_YN": "N",
|
133
|
+
"OFL_YN": "",
|
134
|
+
"INQR_DVSN": "01",
|
135
|
+
"UNPR_DVSN": "01",
|
136
|
+
"FUND_STTL_ICLD_YN": "N",
|
137
|
+
"FNCG_AMT_AUTO_RDPT_YN": "N",
|
138
|
+
"PRCS_DVSN": "01",
|
139
|
+
"CTX_AREA_FK100": "",
|
140
|
+
"CTX_AREA_NK100": "",
|
141
|
+
}
|
142
|
+
else:
|
143
|
+
assert ValueError(f"{base_market} is not valid value")
|
144
|
+
|
145
|
+
common_header = self.create_common_header(request_params=params)
|
146
|
+
|
147
|
+
response = await self.private_get_fetch_balance(self.extend(params))
|
148
|
+
|
149
|
+
common_response = self.get_common_response(response=response)
|
150
|
+
if common_response.success != "0":
|
151
|
+
return ksxt.models.KsxtBalanceResponse(header=common_header, response=common_response, info=None)
|
152
|
+
|
153
|
+
parsed_info = self.parser.parse_balance(response=response, base_market=base_market)
|
154
|
+
|
155
|
+
return ksxt.models.KsxtBalanceResponse(header=common_header, response=common_response, info=parsed_info)
|
156
|
+
|
157
|
+
@AsyncExchange.check_token
|
158
|
+
async def fetch_cash(self, acc_num: str, base_market: str = "KRW") -> ksxt.models.KsxtCashResponse:
|
159
|
+
if base_market == "KRW":
|
160
|
+
params = {
|
161
|
+
"CANO": acc_num[:8],
|
162
|
+
"ACNT_PRDT_CD": acc_num[-2:],
|
163
|
+
"AFHR_FLPR_YN": "N",
|
164
|
+
"OFL_YN": "",
|
165
|
+
"INQR_DVSN": "01",
|
166
|
+
"UNPR_DVSN": "01",
|
167
|
+
"FUND_STTL_ICLD_YN": "N",
|
168
|
+
"FNCG_AMT_AUTO_RDPT_YN": "N",
|
169
|
+
"PRCS_DVSN": "01",
|
170
|
+
"CTX_AREA_FK100": "",
|
171
|
+
"CTX_AREA_NK100": "",
|
172
|
+
}
|
173
|
+
else:
|
174
|
+
assert ValueError(f"{base_market} is not valid value")
|
175
|
+
|
176
|
+
common_header = self.create_common_header(request_params=params)
|
177
|
+
|
178
|
+
response = await self.private_get_fetch_cash(self.extend(params))
|
179
|
+
|
180
|
+
common_response = self.get_common_response(response=response)
|
181
|
+
if common_response.success != "0":
|
182
|
+
return ksxt.models.KsxtCashResponse(header=common_header, response=common_response, info=None)
|
183
|
+
|
184
|
+
parsed_info = self.parser.parse_cash(response=response, base_market=base_market)
|
185
|
+
|
186
|
+
return ksxt.models.KsxtCashResponse(header=common_header, response=common_response, info=parsed_info)
|
187
|
+
|
188
|
+
@AsyncExchange.check_token
|
189
|
+
async def fetch_orderbook(self, symbol: str, base_market: str = "KRW") -> ksxt.models.KsxtSingleOrderBookResponse:
|
190
|
+
if base_market == "KRW":
|
191
|
+
params = {"FID_COND_MRKT_DIV_CODE": "J", "FID_INPUT_ISCD": symbol}
|
192
|
+
else:
|
193
|
+
assert ValueError(f"{base_market} is not valid value")
|
194
|
+
|
195
|
+
common_header = self.create_common_header(request_params=params)
|
196
|
+
|
197
|
+
response = await self.private_get_fetch_orderbook(self.extend(params))
|
198
|
+
|
199
|
+
common_response = self.get_common_response(response=response)
|
200
|
+
if common_response.success != "0":
|
201
|
+
return ksxt.models.KsxtSingleOrderBookResponse(header=common_header, response=common_response, info=None)
|
202
|
+
|
203
|
+
parsed_info = self.parser.parse_orderbook(response=response, base_market=base_market)
|
204
|
+
|
205
|
+
return ksxt.models.KsxtSingleOrderBookResponse(header=common_header, response=common_response, info=parsed_info)
|
206
|
+
|
207
|
+
@AsyncExchange.check_token
|
208
|
+
async def fetch_security(self, symbol: str, base_market: str = "KRW") -> ksxt.models.KsxtSecurityResponse:
|
209
|
+
if base_market == "KRW":
|
210
|
+
params = {"PRDT_TYPE_CD": "300", "PDNO": symbol}
|
211
|
+
else:
|
212
|
+
assert ValueError(f"{base_market} is not valid value")
|
213
|
+
|
214
|
+
common_header = self.create_common_header(request_params=params)
|
215
|
+
|
216
|
+
response = await self.private_get_fetch_security_info(self.extend(params))
|
217
|
+
|
218
|
+
common_response = self.get_common_response(response=response)
|
219
|
+
if common_response.success != "0":
|
220
|
+
return ksxt.models.KsxtSecurityResponse(header=common_header, response=common_response, info=None)
|
221
|
+
|
222
|
+
parsed_info = self.parser.parse_security(response=response, base_market=base_market)
|
223
|
+
|
224
|
+
return ksxt.models.KsxtSecurityResponse(header=common_header, response=common_response, info=parsed_info)
|
225
|
+
|
226
|
+
@AsyncExchange.check_token
|
227
|
+
async def fetch_ticker(self, symbol: str, base_market: str = "KRW") -> ksxt.models.KsxtTickerResponse:
|
228
|
+
if base_market == "KRW":
|
229
|
+
params = {"FID_COND_MRKT_DIV_CODE": "J", "FID_INPUT_ISCD": symbol}
|
230
|
+
else:
|
231
|
+
assert ValueError(f"{base_market} is not valid value")
|
232
|
+
|
233
|
+
common_header = self.create_common_header(request_params=params)
|
234
|
+
|
235
|
+
response = await self.private_get_fetch_ticker_price(self.extend(params))
|
236
|
+
|
237
|
+
common_response = self.get_common_response(response=response)
|
238
|
+
if common_response.success != "0":
|
239
|
+
return ksxt.models.KsxtTickerResponse(header=common_header, response=common_response, info=None)
|
240
|
+
|
241
|
+
parsed_info = self.parser.parse_ticker(response=response, base_market=base_market)
|
242
|
+
|
243
|
+
return ksxt.models.KsxtTickerResponse(header=common_header, response=common_response, info=parsed_info)
|
244
|
+
|
245
|
+
@AsyncExchange.check_token
|
246
|
+
async def fetch_historical_data_index(
|
247
|
+
self, symbol: str, time_frame: str, start: str | None = None, end: str | None = None, base_market: str = "KRW"
|
248
|
+
) -> ksxt.models.KsxtHistoricalDataResponse:
|
249
|
+
if time_frame.endswith("D"):
|
250
|
+
param_code = "D"
|
251
|
+
elif time_frame.endswith("W") or time_frame.endswith("w"):
|
252
|
+
param_code = "W"
|
253
|
+
elif time_frame.endswith("M"):
|
254
|
+
param_code = "M"
|
255
|
+
elif time_frame.endswith("Y"):
|
256
|
+
param_code = "Y"
|
257
|
+
else:
|
258
|
+
assert ValueError(f"{time_frame} is not valid value")
|
259
|
+
|
260
|
+
if start is None:
|
261
|
+
start = self.now(base_market) - timedelta(days=50)
|
262
|
+
if end is None:
|
263
|
+
end = self.now(base_market)
|
264
|
+
|
265
|
+
if base_market == "KRW":
|
266
|
+
params = {
|
267
|
+
"FID_COND_MRKT_DIV_CODE": "U",
|
268
|
+
"FID_INPUT_ISCD": symbol,
|
269
|
+
"FID_INPUT_DATE_1": start.strftime("%Y%m%d"),
|
270
|
+
"FID_INPUT_DATE_2": end.strftime("%Y%m%d"),
|
271
|
+
"FID_PERIOD_DIV_CODE": param_code,
|
272
|
+
}
|
273
|
+
else:
|
274
|
+
assert ValueError(f"{base_market} is not valid value")
|
275
|
+
|
276
|
+
common_header = self.create_common_header(request_params=params)
|
277
|
+
|
278
|
+
response = await self.private_get_fetch_index_ohlcv(self.extend(params))
|
279
|
+
|
280
|
+
common_response = self.get_common_response(response=response)
|
281
|
+
if common_response.success != "0":
|
282
|
+
return ksxt.models.KsxtHistoricalDataResponse(header=common_header, response=common_response, info=None)
|
283
|
+
|
284
|
+
parsed_info = self.parser.parse_historical_index_data(response=response, symbol=symbol, base_market=base_market)
|
285
|
+
|
286
|
+
return ksxt.models.KsxtHistoricalDataResponse(header=common_header, response=common_response, info=parsed_info)
|
287
|
+
|
288
|
+
@AsyncExchange.check_token
|
289
|
+
async def fetch_historical_data(
|
290
|
+
self, symbol: str, time_frame: str, start: str | None = None, end: str | None = None, base_market: str = "KRW"
|
291
|
+
) -> ksxt.models.KsxtHistoricalDataResponse:
|
292
|
+
if time_frame.endswith("D"):
|
293
|
+
param_code = "D"
|
294
|
+
elif time_frame.endswith("W") or time_frame.endswith("w"):
|
295
|
+
param_code = "W"
|
296
|
+
elif time_frame.endswith("M"):
|
297
|
+
param_code = "M"
|
298
|
+
elif time_frame.endswith("Y"):
|
299
|
+
param_code = "Y"
|
300
|
+
else:
|
301
|
+
assert ValueError(f"{time_frame} is not valid value")
|
302
|
+
|
303
|
+
if start is None:
|
304
|
+
start = self.now(base_market) - timedelta(days=100)
|
305
|
+
if end is None:
|
306
|
+
end = self.now(base_market)
|
307
|
+
|
308
|
+
if base_market == "KRW":
|
309
|
+
params = {
|
310
|
+
"FID_COND_MRKT_DIV_CODE": "J",
|
311
|
+
"FID_INPUT_ISCD": symbol,
|
312
|
+
"FID_INPUT_DATE_1": start.strftime("%Y%m%d"),
|
313
|
+
"FID_INPUT_DATE_2": end.strftime("%Y%m%d"),
|
314
|
+
"FID_PERIOD_DIV_CODE": param_code,
|
315
|
+
"FID_ORG_ADJ_PRC": "0",
|
316
|
+
}
|
317
|
+
else:
|
318
|
+
assert ValueError(f"{base_market} is not valid value")
|
319
|
+
|
320
|
+
if time_frame.endswith("m"):
|
321
|
+
common_header = self.create_common_header(request_params=params)
|
322
|
+
response = await self.private_get_fetch_security_ohlcv_minute(self.extend(params))
|
323
|
+
elif time_frame.endswith("D"):
|
324
|
+
common_header = self.create_common_header(request_params=params)
|
325
|
+
response = await self.private_get_fetch_security_ohlcv_day(self.extend(params))
|
326
|
+
elif time_frame.endswith("W"):
|
327
|
+
common_header = self.create_common_header(request_params=params)
|
328
|
+
response = await self.private_get_fetch_security_ohlcv_week(self.extend(params))
|
329
|
+
elif time_frame.endswith("M"):
|
330
|
+
common_header = self.create_common_header(request_params=params)
|
331
|
+
response = await self.private_get_fetch_security_ohlcv_month(self.extend(params))
|
332
|
+
elif time_frame.endswith("Y"):
|
333
|
+
common_header = self.create_common_header(request_params=params)
|
334
|
+
response = await self.private_get_fetch_security_ohlcv_year(self.extend(params))
|
335
|
+
|
336
|
+
common_response = self.get_common_response(response=response)
|
337
|
+
if common_response.success != "0":
|
338
|
+
return ksxt.models.KsxtHistoricalDataResponse(header=common_header, response=common_response, info=None)
|
339
|
+
|
340
|
+
parsed_info = self.parser.parse_historical_data(response=response, symbol=symbol, base_market=base_market)
|
341
|
+
|
342
|
+
return ksxt.models.KsxtHistoricalDataResponse(header=common_header, response=common_response, info=parsed_info)
|
343
|
+
|
344
|
+
@AsyncExchange.check_token
|
345
|
+
async def modify_order(
|
346
|
+
self,
|
347
|
+
acc_num: str,
|
348
|
+
order_id: str,
|
349
|
+
price: float,
|
350
|
+
qty: float,
|
351
|
+
*args,
|
352
|
+
symbol: str | None = "",
|
353
|
+
base_market: str = "KRW",
|
354
|
+
) -> ksxt.models.KsxtModifyOrderResponse:
|
355
|
+
if base_market == "KRW":
|
356
|
+
params = {
|
357
|
+
"CANO": acc_num[:8],
|
358
|
+
"ACNT_PRDT_CD": acc_num[-2:],
|
359
|
+
"KRX_FWDG_ORD_ORGNO": "",
|
360
|
+
"ORGN_ODNO": str(order_id),
|
361
|
+
"RVSE_CNCL_DVSN_CD": "01",
|
362
|
+
"ORD_DVSN": "00",
|
363
|
+
"ORD_QTY": str(qty),
|
364
|
+
"ORD_UNPR": str(price),
|
365
|
+
"QTY_ALL_ORD_YN": "N",
|
366
|
+
}
|
367
|
+
else:
|
368
|
+
assert ValueError(f"{base_market} is not valid value")
|
369
|
+
|
370
|
+
common_header = self.create_common_header(request_params=params)
|
371
|
+
response = await self.private_post_send_modify_order(self.extend(params))
|
372
|
+
|
373
|
+
common_response = self.get_common_response(response=response)
|
374
|
+
if common_response.success != "0":
|
375
|
+
return ksxt.models.KsxtModifyOrderResponse(header=common_header, response=common_response, info=None)
|
376
|
+
|
377
|
+
parsed_info = self.parser.parse_modify_order(response=response, base_market=base_market)
|
378
|
+
|
379
|
+
return ksxt.models.KsxtModifyOrderResponse(header=common_header, response=common_response, info=parsed_info)
|
380
|
+
|
381
|
+
@AsyncExchange.check_token
|
382
|
+
async def cancel_order(
|
383
|
+
self, acc_num: str, order_id: str, symbol: str | None = "", qty: float = 0, *args, base_market: str = "KRW"
|
384
|
+
) -> ksxt.models.KsxtCancelOrderResponse:
|
385
|
+
if base_market == "KRW":
|
386
|
+
params = {
|
387
|
+
"CANO": acc_num[:8],
|
388
|
+
"ACNT_PRDT_CD": acc_num[-2:],
|
389
|
+
"KRX_FWDG_ORD_ORGNO": "",
|
390
|
+
"ORGN_ODNO": str(order_id),
|
391
|
+
"RVSE_CNCL_DVSN_CD": "02",
|
392
|
+
"ORD_DVSN": "00",
|
393
|
+
"ORD_QTY": str(qty),
|
394
|
+
"ORD_UNPR": str(0),
|
395
|
+
"QTY_ALL_ORD_YN": "N",
|
396
|
+
}
|
397
|
+
else:
|
398
|
+
assert ValueError(f"{base_market} is not valid value")
|
399
|
+
|
400
|
+
common_header = self.create_common_header(request_params=params)
|
401
|
+
response = await self.private_post_send_cancel_order(self.extend(params))
|
402
|
+
|
403
|
+
common_response = self.get_common_response(response=response)
|
404
|
+
if common_response.success != "0":
|
405
|
+
return ksxt.models.KsxtCancelOrderResponse(header=common_header, response=common_response, info=None)
|
406
|
+
|
407
|
+
parsed_info = self.parser.parse_cancel_order(response=response, base_market=base_market)
|
408
|
+
|
409
|
+
return ksxt.models.KsxtCancelOrderResponse(header=common_header, response=common_response, info=parsed_info)
|
410
|
+
|
411
|
+
@AsyncExchange.check_token
|
412
|
+
async def create_order(
|
413
|
+
self,
|
414
|
+
acc_num: str,
|
415
|
+
symbol: str,
|
416
|
+
ticket_type: Literal["EntryLong"] | Literal["EntryShort"] | Literal["ExitLong"] | Literal["ExitShort"],
|
417
|
+
otype: Literal["limit"] | Literal["market"],
|
418
|
+
price: float | None = 0,
|
419
|
+
qty: float | None = 0,
|
420
|
+
amount: float | None = 0,
|
421
|
+
base_market: str = "KRW",
|
422
|
+
) -> ksxt.models.KsxtCreateOrderResponse:
|
423
|
+
if otype.lower() == "limit":
|
424
|
+
order_dvsn = "00"
|
425
|
+
elif otype.lower() == "market":
|
426
|
+
order_dvsn = "01"
|
427
|
+
params = {
|
428
|
+
"CANO": acc_num[:8],
|
429
|
+
"ACNT_PRDT_CD": acc_num[-2:],
|
430
|
+
"PDNO": symbol,
|
431
|
+
"ORD_DVSN": order_dvsn,
|
432
|
+
"ORD_QTY": str(qty), # string type 으로 설정
|
433
|
+
"ORD_UNPR": str(price), # string type 으로 설정
|
434
|
+
}
|
435
|
+
|
436
|
+
common_header = self.create_common_header(request_params=params)
|
437
|
+
|
438
|
+
if ticket_type == "EntryLong":
|
439
|
+
response = await self.private_post_send_order_entry(self.extend(params))
|
440
|
+
elif ticket_type == "ExitLong":
|
441
|
+
response = await self.private_post_send_order_exit(self.extend(params))
|
442
|
+
|
443
|
+
common_response = self.get_common_response(response=response)
|
444
|
+
if common_response.success != "0":
|
445
|
+
return ksxt.models.KsxtCreateOrderResponse(header=common_header, response=common_response, info=None)
|
446
|
+
|
447
|
+
parsed_info = self.parser.parse_create_order(response=response, base_market=base_market)
|
448
|
+
|
449
|
+
return ksxt.models.KsxtCreateOrderResponse(header=common_header, response=common_response, info=parsed_info)
|
450
|
+
|
451
|
+
def get_market_code_in_feeder(self, symbol: str, base_market: str = "KRW"):
|
452
|
+
if base_market == "KRW":
|
453
|
+
return ""
|
454
|
+
elif base_market == "USD":
|
455
|
+
if symbol.upper() == "ALL":
|
456
|
+
return "NASD"
|
457
|
+
|
458
|
+
response = self.fetch_security(symbol=symbol, base_market=base_market)
|
459
|
+
return response["exchange"]
|
460
|
+
else:
|
461
|
+
return ""
|
462
|
+
|
463
|
+
def get_market_code_in_broker(self, symbol: str, base_market: str = "KRW"):
|
464
|
+
if base_market == "KRW":
|
465
|
+
return ""
|
466
|
+
elif base_market == "USD":
|
467
|
+
if symbol.upper() == "ALL":
|
468
|
+
return "NASD"
|
469
|
+
|
470
|
+
response = self.fetch_security(symbol=symbol, base_market=base_market)
|
471
|
+
exname = response["exchange"]
|
472
|
+
if exname == "NYS":
|
473
|
+
return "NYSE"
|
474
|
+
elif exname == "NAS":
|
475
|
+
return "NASD"
|
476
|
+
elif exname == "AMS":
|
477
|
+
return "AMEX"
|
478
|
+
else:
|
479
|
+
return ""
|
480
|
+
else:
|
481
|
+
return ""
|
Binary file
|
Binary file
|
Binary file
|