커서가 망쳐놓은 듯

This commit is contained in:
2026-03-17 12:33:30 +09:00
parent 6fc179f598
commit c2b2b711e0
91 changed files with 45391 additions and 2244 deletions

View File

0
kiwoom_rest_api/api.py Normal file
View File

View File

View File

View File

@@ -0,0 +1,109 @@
from datetime import datetime, timedelta
from typing import Dict, Optional, Any
import time
from kiwoom_rest_api.config import get_api_key, get_api_secret, TOKEN_URL
from kiwoom_rest_api.core.sync_client import make_request
class TokenManager:
"""Manages OAuth tokens for Kiwoom API"""
def __init__(self):
self._access_token = None
self._token_expiry = None
self._refresh_token = None
self._refresh_expiry = None
@property
def access_token(self) -> Optional[str]:
"""Get the current access token, refreshing if necessary"""
if self._is_access_token_valid():
return self._access_token
# Try to refresh the token
if self._can_refresh_token():
self._refresh_access_token()
return self._access_token
# Get a new token
self._request_new_token()
return self._access_token
def get_token(self) -> str:
"""Get the current access token (alias for access_token property)"""
return self.access_token
def _is_access_token_valid(self) -> bool:
"""Check if the current access token is valid"""
if not self._access_token or not self._token_expiry:
return False
# Add a small buffer (30 seconds) to avoid edge cases
return datetime.now() < self._token_expiry - timedelta(seconds=30)
def _can_refresh_token(self) -> bool:
"""Check if we can refresh the current token"""
if not self._refresh_token or not self._refresh_expiry:
return False
return datetime.now() < self._refresh_expiry - timedelta(seconds=30)
def _request_new_token(self) -> None:
"""Request a new access token"""
response = make_request(
endpoint=TOKEN_URL,
method="POST",
data={
"grant_type": "client_credentials",
"appkey": get_api_key(),
"secretkey": get_api_secret(),
},
)
self._update_token_info(response)
def _refresh_access_token(self) -> None:
"""Refresh the access token using the refresh token"""
response = make_request(
endpoint=TOKEN_URL,
method="POST",
data={
"grant_type": "refresh_token",
"refresh_token": self._refresh_token,
"appkey": get_api_key(),
"appsecret": get_api_secret(),
},
)
self._update_token_info(response)
def _update_token_info(self, token_response: Dict[str, Any]) -> None:
"""Update token information from the API response"""
self._access_token = token_response.get("token")
# Calculate expiry time
if "expires_in" in token_response:
self._token_expiry = datetime.now() + timedelta(seconds=token_response["expires_in"])
if "expires_dt" in token_response:
self._token_expiry = datetime.strptime(token_response["expires_dt"], "%Y%m%d%H%M%S")
# Update refresh token if provided
refresh_token = token_response.get("refresh_token")
if refresh_token:
self._refresh_token = refresh_token
if "refresh_token_expires_in" in token_response:
self._refresh_expiry = datetime.now() + timedelta(seconds=token_response["refresh_token_expires_in"])
# Convenience functions
def get_access_token() -> str:
"""Get a valid access token"""
manager = TokenManager()
return manager.get_token()
if __name__ == "__main__":
print(get_access_token())

View File

View File

@@ -0,0 +1,88 @@
import os
import typer
import json
from rich import print as rprint # print_json 대신 사용할 수 있음
from rich.pretty import pprint # 객체 예쁘게 출력
# 필요한 클래스 임포트
from kiwoom_rest_api.koreanstock.stockinfo import StockInfo
from kiwoom_rest_api.auth.token import TokenManager
from kiwoom_rest_api.core.base import APIError
# Typer 앱 인스턴스 생성
# no_args_is_help=True: 인자 없이 실행 시 도움말 표시
app = typer.Typer(no_args_is_help=True, add_completion=False)
# --- 앱의 기본 동작 (선택 사항, 도움말 개선 등) ---
@app.callback()
def main_callback(ctx: typer.Context):
"""
키움증권 Open API CLI 도구
"""
# 서브커맨드가 없으면 도움말 표시 (Typer가 기본 처리)
# 여기에 앱 전역 설정을 추가할 수도 있음
pass
# --- ka10001 서브커맨드 정의 ---
@app.command()
def ka10001(
stock_code: str = typer.Argument(..., help="조회할 주식 종목 코드 (예: 005930)"),
api_key: str = typer.Option(
None, "--api-key", "-k",
help="키움증권 API Key (환경 변수 KIWOOM_API_KEY)",
envvar="KIWOOM_API_KEY",
show_envvar=True,
),
api_secret: str = typer.Option(
None, "--api-secret", "-s",
help="키움증권 API Secret (환경 변수 KIWOOM_API_SECRET)",
envvar="KIWOOM_API_SECRET",
show_envvar=True,
),
base_url: str = typer.Option(
"https://api.kiwoom.com",
"--base-url", "-u",
help="API 기본 URL"
),
):
"""
주식 기본 정보 요청 (KA10001) API를 호출합니다.
"""
if not api_key:
typer.secho("오류: API Key가 제공되지 않았습니다.", fg=typer.colors.RED, err=True)
raise typer.Exit(code=1)
if not api_secret:
typer.secho("오류: API Secret이 제공되지 않았습니다.", fg=typer.colors.RED, err=True)
raise typer.Exit(code=1)
typer.echo(f"종목 코드 {stock_code} 요청 시작 (URL: {base_url})")
try:
token_manager = TokenManager()
stock_info = StockInfo(base_url=base_url, token_manager=token_manager, use_async=False)
result = stock_info.basic_stock_information_request_ka10001(stock_code)
typer.echo("\n--- API 응답 ---")
# rich의 pprint 사용 (print_json 대신)
pprint(result, expand_all=True)
typer.echo("----------------")
except APIError as e:
typer.secho(f"\nAPI 오류 (HTTP {e.status_code}): {e.message}", fg=typer.colors.RED, err=True)
if e.error_data:
typer.echo("오류 데이터:", err=True)
pprint(e.error_data, expand_all=True)
raise typer.Exit(code=1)
except Exception as e:
typer.secho(f"\n예상치 못한 오류: {type(e).__name__}", fg=typer.colors.RED, err=True)
typer.secho(f"메시지: {e}", fg=typer.colors.RED, err=True)
raise typer.Exit(code=1)
# --- 다른 서브커맨드 추가 가능 ---
# @app.command()
# def another_command(...):
# ...
# --- 메인 실행 블록 ---
if __name__ == "__main__":
app()

60
kiwoom_rest_api/config.py Normal file
View File

@@ -0,0 +1,60 @@
import os
from typing import Optional
# Base URLs
DEFAULT_BASE_URL = os.environ.get("KIWOOM_DEFAULT_BASE_URL", "https://api.kiwoom.com")
SANDBOX_BASE_URL = os.environ.get("KIWOOM_SANDBOX_BASE_URL", "https://mockapi.kiwoom.com")
# WebSocket URLs
DEFAULT_WS_URL = os.environ.get("KIWOOM_DEFAULT_WS_URL", "wss://api.kiwoom.com:10000")
SANDBOX_WS_URL = os.environ.get("KIWOOM_SANDBOX_WS_URL", "wss://mockapi.kiwoom.com:10000")
WS_ENDPOINT = "/api/dostk/websocket"
# API Credentials
API_KEY = os.environ.get("KIWOOM_API_KEY", "")
API_SECRET = os.environ.get("KIWOOM_API_SECRET", "")
# Authentication
TOKEN_URL = "/oauth2/token"
AUTH_URL = "/oauth2/authorize"
# Timeouts
DEFAULT_TIMEOUT = 30.0 # seconds
WS_TIMEOUT = 10.0 # seconds
# Environment setting
USE_SANDBOX = os.environ.get("KIWOOM_USE_SANDBOX", "false").lower() == "true"
def get_base_url() -> str:
"""Return the base URL based on environment settings"""
if USE_SANDBOX:
return SANDBOX_BASE_URL
return DEFAULT_BASE_URL
def get_ws_url() -> str:
"""Return the WebSocket URL based on environment settings"""
base_ws_url = SANDBOX_WS_URL if USE_SANDBOX else DEFAULT_WS_URL
return f"{base_ws_url}{WS_ENDPOINT}"
def get_api_key() -> str:
"""Return the API key"""
return API_KEY
def get_api_secret() -> str:
"""Return the API secret"""
return API_SECRET
def get_headers(access_token: Optional[str] = None) -> dict:
"""Return common headers for API requests"""
headers = {
"Content-Type": "application/json;charset=UTF-8",
}
if access_token:
headers["Authorization"] = f"Bearer {access_token}"
else:
headers["appkey"] = get_api_key()
headers["appsecret"] = get_api_secret()
return headers

View File

View File

@@ -0,0 +1,49 @@
from typing import Any, Dict, Optional
import httpx
from kiwoom_rest_api.core.base import prepare_request_params, process_response_async
async def make_request_async(
endpoint: str,
method: str = "GET",
params: Optional[Dict[str, Any]] = None,
data: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, Any]] = None,
access_token: Optional[str] = None,
timeout: Optional[float] = None,
**kwargs # Add **kwargs
) -> Dict[str, Any]:
"""Make an asynchronous HTTP request to the Kiwoom API"""
request_params = prepare_request_params(
endpoint=endpoint,
method=method,
params=params,
data=data,
headers=headers,
access_token=access_token,
timeout=timeout,
)
# Handle 'json' data from kwargs
json_data = kwargs.get('json')
if json_data and method in ["POST", "PUT", "PATCH"]:
# Prioritize explicitly passed 'json' data
request_params["json"] = json_data
# If 'data' was also prepared, 'json' takes precedence here.
# Remove 'data' if 'json' is being used to avoid conflicts in httpx
request_params.pop("data", None)
async with httpx.AsyncClient() as client:
# This should return an httpx.Response object
response: httpx.Response = await client.request(
method=request_params["method"],
url=request_params["url"],
params=request_params.get("params"),
json=request_params.get("json"),
data=request_params.get("data"),
headers=request_params["headers"],
timeout=request_params["timeout"],
)
return await process_response_async(response)

View File

@@ -0,0 +1,224 @@
from typing import Any, Dict, Optional, Union
import json
from urllib.parse import urljoin
import httpx
import inspect # Import inspect
from kiwoom_rest_api.config import get_base_url, get_headers, DEFAULT_TIMEOUT
class APIError(Exception):
"""Custom exception for API errors"""
def __init__(self, status_code: int, message: str, error_data: dict = None):
self.status_code = status_code
self.message = message
self.error_data = error_data or {}
super().__init__(f"API Error (HTTP {status_code}): {message}")
def __str__(self):
return f"API Error (HTTP {self.status_code}): {self.message}"
def make_url(endpoint: str) -> str:
"""Create a full URL from an endpoint"""
if endpoint.startswith(('http://', 'https://')):
return endpoint
# Ensure endpoint starts with a forward slash
if not endpoint.startswith('/'):
endpoint = f"/{endpoint}"
print("\n\n## full url ##\n\n", urljoin(get_base_url(), endpoint))
return urljoin(get_base_url(), endpoint)
def process_response(response: Any) -> Dict[str, Any]:
"""Process API response and handle errors"""
if not hasattr(response, 'status_code'):
raise ValueError(f"Invalid response object: {response}")
if 200 <= response.status_code < 300:
if not response.text:
return {}
try:
response_json = response.json()
access_control_expose_headers = response.headers.get("access-control-expose-headers")
if access_control_expose_headers:
access_control_expose_headers = access_control_expose_headers.split(",")
for header in access_control_expose_headers:
response_json[header] = response.headers.get(header)
return response_json
return response_json
except json.JSONDecodeError:
return {"content": response.text}
# Handle error responses
error_message = "Unknown error"
error_data = None
try:
error_data = response.json()
error_message = error_data.get("message", "Unknown error")
except (json.JSONDecodeError, AttributeError):
if response.text:
error_message = response.text
raise APIError(response.status_code, error_message, error_data)
def prepare_request_params(
endpoint: str,
method: str = "GET",
params: Optional[Dict[str, Any]] = None,
data: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, Any]] = None,
access_token: Optional[str] = None,
timeout: Optional[float] = None,
) -> Dict[str, Any]:
"""Prepare request parameters for HTTP request"""
# 헤더 정규화
normalized_headers = {}
if headers:
for key, value in headers.items():
# 모든 헤더 키를 소문자로 변환하여 중복 방지
normalized_headers[key.lower()] = value
# 기본 헤더 설정
default_headers = {
"content-type": "application/json;charset=UTF-8",
}
# API 키 추가
from kiwoom_rest_api.config import get_api_key, get_api_secret
default_headers["appkey"] = get_api_key()
default_headers["appsecret"] = get_api_secret()
# 헤더 병합 (사용자 정의 헤더가 기본 헤더보다 우선)
merged_headers = {**default_headers, **normalized_headers}
# 액세스 토큰 추가
if access_token:
merged_headers["authorization"] = f"Bearer {access_token}"
# URL 구성
from kiwoom_rest_api.config import get_base_url
url = endpoint if endpoint.startswith(("http://", "https://")) else f"{get_base_url()}{endpoint}"
# 요청 파라미터 구성
request_params = {
"url": url,
"method": method,
"headers": merged_headers,
"timeout": timeout or DEFAULT_TIMEOUT,
}
# 쿼리 파라미터 추가
if params:
request_params["params"] = params
# POST/PUT/PATCH 요청용 데이터 추가
if method in ["POST", "PUT", "PATCH"] and data:
if merged_headers.get("content-type", "").startswith("application/json"):
request_params["json"] = data
else:
request_params["data"] = data
return request_params
async def process_response_async(response: httpx.Response) -> Dict[str, Any]:
if not isinstance(response, httpx.Response):
print("ERROR: process_response_async did not receive an httpx.Response object!")
raise TypeError(f"Expected httpx.Response, but got {type(response)}")
# --- 추가 디버깅 ---
json_method = getattr(response, 'json', None)
is_json_coro = inspect.iscoroutinefunction(json_method)
print(f"DEBUG: inspect.iscoroutinefunction(response.json) = {is_json_coro}")
# --- 추가 디버깅 끝 ---
try:
# 성공(200) 응답 처리
if response.status_code == 200:
try:
# 여기서 여전히 TypeError 발생 가능성 있음
json_data = await response.json()
access_control_expose_headers = response.headers.get("access-control-expose-headers")
if access_control_expose_headers:
access_control_expose_headers = access_control_expose_headers.split(",")
for header in access_control_expose_headers:
json_data[header] = response.headers.get(header)
if isinstance(json_data, dict) and str(json_data.get("return_code")) != "0":
error_message = json_data.get("return_msg", "Unknown API error message")
raise APIError(response.status_code, error_message, json_data)
return json_data
except json.JSONDecodeError:
# 여기서도 TypeError 발생 가능성 있음
raw_text_content = await response.text()
error_message = f"Failed to decode JSON response. Content: {raw_text_content[:200]}"
raise APIError(response.status_code, error_message, {"raw_content": raw_text_content})
except TypeError as te: # await 실패 시
print(f"ERROR: TypeError on SUCCESS path await: {te}")
# await 없이 직접 접근 시도 (진단용)
try:
json_data = response.json() # await 없이 호출
access_control_expose_headers = response.headers.get("access-control-expose-headers")
if access_control_expose_headers:
access_control_expose_headers = access_control_expose_headers.split(",")
for header in access_control_expose_headers:
json_data[header] = response.headers.get(header)
if isinstance(json_data, dict) and str(json_data.get("return_code")) != "0":
error_message = json_data.get("return_msg", "Unknown API error message")
raise APIError(response.status_code, error_message, json_data)
return json_data # 성공하면 반환
except Exception as direct_err:
print(f"ERROR: Direct access failed after TypeError: {direct_err}")
raw_text_content = getattr(response, 'text', 'N/A') # text 속성 접근 시도
raise APIError(response.status_code, f"TypeError processing SUCCESS response: {te}. Raw content: {raw_text_content[:200]}", {"raw_content": raw_text_content})
# HTTP 에러(400 등) 처리
else:
error_message = f"HTTP Error {response.status_code}"
error_data = {"status_code": response.status_code}
raw_text_content = "Could not retrieve error content"
# --- 진단: await 없이 text 속성 직접 접근 시도 ---
try:
if hasattr(response, 'text') and isinstance(response.text, str):
print("DEBUG: Accessing response.text directly as attribute.")
raw_text_content = response.text
error_data["raw_content"] = raw_text_content
error_message += f". Content: {raw_text_content[:500]}" # 내용 조금 더 보기
# 텍스트 내용으로 JSON 파싱 시도
try:
error_json = json.loads(raw_text_content)
error_msg1 = error_json.get("msg1", "No msg1 found in error JSON")
error_message = f"HTTP Error {response.status_code}: {error_msg1}" # 에러 메시지 개선
error_data.update(error_json)
except json.JSONDecodeError:
print("DEBUG: Error response body is not JSON.")
else:
print("DEBUG: response.text is not a direct string attribute.")
# 여기서 await response.text()를 시도하면 TypeError 발생 가능성 높음
except Exception as e_diag:
print(f"ERROR: Exception during diagnostic access of response text: {e_diag}")
# --- 진단 끝 ---
# 최종 에러 발생
raise APIError(response.status_code, error_message, error_data)
except httpx.RequestError as e:
# 네트워크 관련 에러
raise APIError(500, f"Request failed: {str(e)}", {"exception": str(e)})

View File

@@ -0,0 +1,56 @@
from typing import Optional
from kiwoom_rest_api.core.sync_client import make_request
from kiwoom_rest_api.core.async_client import make_request_async
class KiwoomBaseAPI:
def __init__(
self,
base_url: str = None,
token_manager=None,
use_async: bool = False,
resource_url: str = ""
):
self.base_url = base_url
self.token_manager = token_manager
self.use_async = use_async
self.resource_url = resource_url
self._request_func = make_request_async if use_async else make_request
def _get_access_token(self) -> Optional[str]:
if self.token_manager:
return self.token_manager.get_token()
return None
async def _get_access_token_async(self) -> Optional[str]:
if self.token_manager and hasattr(self.token_manager, 'get_token_async'):
return await self.token_manager.get_token_async()
return self._get_access_token()
def _make_request(self, method: str, url: str, **kwargs):
headers = kwargs.pop("headers", {})
headers["content-type"] = "application/json;charset=UTF-8"
if self.token_manager:
access_token = self._get_access_token()
headers["Authorization"] = f"Bearer {access_token}"
return make_request(endpoint=url, method=method, headers=headers, **kwargs)
async def _make_request_async(self, method: str, url: str, **kwargs):
headers = kwargs.pop("headers", {})
headers["content-type"] = "application/json;charset=UTF-8"
if self.token_manager:
access_token = await self._get_access_token_async()
headers["Authorization"] = f"Bearer {access_token}"
return await make_request_async(endpoint=url, method=method, headers=headers, **kwargs)
def _execute_request(self, method: str, resource_url: str = None, **kwargs):
# resource_url이 제공되면 임시로 사용, 아니면 기본값 사용
url_resource = resource_url if resource_url is not None else self.resource_url
#url = f"{self.base_url}{url_resource}" if self.base_url else f"/{url_resource}"
if self.base_url:
# base_url 끝의 /와 url_resource 앞의 /를 모두 떼고 중간에 / 하나만 넣음
url = f"{self.base_url.rstrip('/')}/{url_resource.lstrip('/')}"
else:
url = f"/{url_resource.lstrip('/')}"
if self.use_async:
return self._make_request_async(method, url, **kwargs)
return self._make_request(method, url, **kwargs)

View File

@@ -0,0 +1,43 @@
from typing import Any, Dict, Optional
import httpx
from kiwoom_rest_api.core.base import prepare_request_params, process_response
def make_request(
endpoint: str,
method: str = "GET",
params: Optional[Dict[str, Any]] = None,
data: Optional[Dict[str, Any]] = None,
headers: Optional[Dict[str, Any]] = None,
access_token: Optional[str] = None,
timeout: Optional[float] = None,
**kwargs: Any
) -> Dict[str, Any]:
"""Make a synchronous HTTP request to the Kiwoom API"""
request_params = prepare_request_params(
endpoint=endpoint,
method=method,
params=params,
data=data,
headers=headers,
access_token=access_token,
timeout=timeout,
)
# 추가: kwargs에서 json 데이터 처리
if 'json' in kwargs and method in ["POST", "PUT", "PATCH"]:
request_params["json"] = kwargs['json']
with httpx.Client() as client:
response = client.request(
method=request_params["method"],
url=request_params["url"],
params=request_params.get("params"),
json=request_params.get("json"),
headers=request_params["headers"],
timeout=request_params["timeout"],
)
return process_response(response)

View File

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,108 @@
from typing import Dict, Optional, Any
from kiwoom_rest_api.core.sync_client import make_request
def get_per_analysis(
market_code: str = "0",
access_token: Optional[str] = None,
) -> Dict[str, Any]:
"""
PER/PBR/배당수익률 (KA-STOCK-010)
Args:
market_code: 시장분류코드 (0:전체, 1:코스피, 2:코스닥)
access_token: OAuth 액세스 토큰
Returns:
PER/PBR/배당수익률 데이터
"""
endpoint = "/stock/per"
params = {
"FID_COND_MRKT_DIV_CODE": market_code,
}
return make_request(
endpoint=endpoint,
params=params,
access_token=access_token,
)
def get_rapid_price_change(
market_code: str = "0",
sort_code: str = "1",
access_token: Optional[str] = None,
) -> Dict[str, Any]:
"""
급등락 종목 (KA-STOCK-009)
Args:
market_code: 시장분류코드 (0:전체, 1:코스피, 2:코스닥)
sort_code: 정렬구분 (1:급등, 2:급락)
access_token: OAuth 액세스 토큰
Returns:
급등락 종목 데이터
"""
endpoint = "/stock/rapid"
params = {
"FID_COND_MRKT_DIV_CODE": market_code,
"FID_INPUT_ISCD": sort_code,
}
return make_request(
endpoint=endpoint,
params=params,
access_token=access_token,
)
def get_price_ranges(
stock_code: str,
access_token: Optional[str] = None,
) -> Dict[str, Any]:
"""
가격 매물대 조회 (KA-STOCK-012)
Args:
stock_code: 종목코드 (6자리)
access_token: OAuth 액세스 토큰
Returns:
가격 매물대 데이터
"""
endpoint = "/stock/price-ranges"
params = {
"FID_COND_MRKT_DIV_CODE": "J",
"FID_INPUT_ISCD": stock_code,
}
return make_request(
endpoint=endpoint,
params=params,
access_token=access_token,
)
def get_stock_trend(
stock_code: str,
access_token: Optional[str] = None,
) -> Dict[str, Any]:
"""
주가 이격도 추이 (KA-STOCK-011)
Args:
stock_code: 종목코드 (6자리)
access_token: OAuth 액세스 토큰
Returns:
주가 이격도 데이터
"""
endpoint = "/stock/trend"
params = {
"FID_COND_MRKT_DIV_CODE": "J",
"FID_INPUT_ISCD": stock_code,
}
return make_request(
endpoint=endpoint,
params=params,
access_token=access_token,
)

View File

@@ -0,0 +1,739 @@
from kiwoom_rest_api.core.base_api import KiwoomBaseAPI
from typing import Union, Dict, Any, Awaitable
class Chart(KiwoomBaseAPI):
"""한국 주식 섹터 관련 API를 제공하는 클래스"""
def __init__(
self,
base_url: str = None,
token_manager=None,
use_async: bool = False,
resource_url: str = "/api/dostk/chart"
):
"""
Chart 클래스 초기화
Args:
base_url (str, optional): API 기본 URL
token_manager: 토큰 관리자 객체
use_async (bool): 비동기 클라이언트 사용 여부 (기본값: False)
"""
super().__init__(
base_url=base_url,
token_manager=token_manager,
use_async=use_async,
resource_url=resource_url
)
def stockwise_investor_institution_chart_request_ka10060(
self,
dt: str,
stk_cd: str,
amt_qty_tp: str,
trde_tp: str,
unit_tp: str,
cont_yn: str = "N",
next_key: str = ""
) -> dict:
"""
종목별투자자기관별차트요청 (ka10060)
Args:
dt (str): 일자 (YYYYMMDD)
stk_cd (str): 종목코드
amt_qty_tp (str): 금액수량구분 (1:금액, 2:수량)
trde_tp (str): 매매구분 (0:순매수, 1:매수, 2:매도)
unit_tp (str): 단위구분 (1000:천주, 1:단주)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 종목별투자자기관별차트 데이터
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka10060",
}
data = {
"dt": dt,
"stk_cd": stk_cd,
"amt_qty_tp": amt_qty_tp,
"trde_tp": trde_tp,
"unit_tp": unit_tp,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def intraday_investor_trading_chart_request_ka10064(
self,
mrkt_tp: str,
amt_qty_tp: str,
trde_tp: str,
stk_cd: str,
cont_yn: str = "N",
next_key: str = ""
) -> dict:
"""
장중투자자별매매차트요청 (ka10064)
Args:
mrkt_tp (str): 시장구분 (000:전체, 001:코스피, 101:코스닥)
amt_qty_tp (str): 금액수량구분 (1:금액, 2:수량)
trde_tp (str): 매매구분 (0:순매수, 1:매수, 2:매도)
stk_cd (str): 종목코드
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 장중투자자별매매차트 데이터
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka10064",
}
data = {
"mrkt_tp": mrkt_tp,
"amt_qty_tp": amt_qty_tp,
"trde_tp": trde_tp,
"stk_cd": stk_cd,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def stock_tick_chart_request_ka10079(
self,
stk_cd: str,
tic_scope: str,
upd_stkpc_tp: str,
cont_yn: str = "N",
next_key: str = ""
) -> dict:
"""
주식틱차트조회요청 (ka10079)
Args:
stk_cd (str): 종목코드 (거래소별 종목코드 KRX:039490,NXT:039490_NX,SOR:039490_AL)
tic_scope (str): 틱범위 (1:1틱, 3:3틱, 5:5틱, 10:10틱, 30:30틱)
upd_stkpc_tp (str): 수정주가구분 (0 or 1)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 주식틱차트 데이터
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka10079",
}
data = {
"stk_cd": stk_cd,
"tic_scope": tic_scope,
"upd_stkpc_tp": upd_stkpc_tp,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def stock_minute_chart_request_ka10080(
self,
stk_cd: str,
tic_scope: str,
upd_stkpc_tp: str,
cont_yn: str = "N",
next_key: str = ""
) -> dict:
"""
주식분봉차트조회요청 (ka10080)
Args:
stk_cd (str): 종목코드 (거래소별 종목코드 KRX:039490,NXT:039490_NX,SOR:039490_AL)
tic_scope (str): 틱범위 (1:1분, 3:3분, 5:5분, 10:10분, 15:15분, 30:30분, 45:45분, 60:60분)
upd_stkpc_tp (str): 수정주가구분 (0 or 1)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 주식분봉차트 데이터
- stk_cd (str): 종목코드
- stk_min_pole_chart_qry (list): 주식분봉차트조회 데이터 리스트
- cur_prc (str): 현재가
- trde_qty (str): 거래량
- cntr_tm (str): 체결시간
- open_pric (str): 시가
- high_pric (str): 고가
- low_pric (str): 저가
- upd_stkpc_tp (str): 수정주가구분
- upd_rt (str): 수정비율
- bic_inds_tp (str): 대업종구분
- sm_inds_tp (str): 소업종구분
- stk_infr (str): 종목정보
- upd_stkpc_event (str): 수정주가이벤트
- pred_close_pric (str): 전일종가
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka10080",
}
data = {
"stk_cd": stk_cd,
"tic_scope": tic_scope,
"upd_stkpc_tp": upd_stkpc_tp,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def stock_daily_chart_request_ka10081(
self,
stk_cd: str,
base_dt: str,
upd_stkpc_tp: str,
cont_yn: str = "N",
next_key: str = ""
) -> dict:
"""
주식일봉차트조회요청 (ka10081)
Args:
stk_cd (str): 종목코드 (거래소별 종목코드 KRX:039490,NXT:039490_NX,SOR:039490_AL)
base_dt (str): 기준일자 (YYYYMMDD)
upd_stkpc_tp (str): 수정주가구분 (0 or 1)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 주식일봉차트 데이터
- stk_cd (str): 종목코드
- stk_dt_pole_chart_qry (list): 주식일봉차트조회 데이터 리스트
- cur_prc (str): 현재가
- trde_qty (str): 거래량
- trde_prica (str): 거래대금
- dt (str): 일자
- open_pric (str): 시가
- high_pric (str): 고가
- low_pric (str): 저가
- upd_stkpc_tp (str): 수정주가구분
- upd_rt (str): 수정비율
- bic_inds_tp (str): 대업종구분
- sm_inds_tp (str): 소업종구분
- stk_infr (str): 종목정보
- upd_stkpc_event (str): 수정주가이벤트
- pred_close_pric (str): 전일종가
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka10081",
}
data = {
"stk_cd": stk_cd,
"base_dt": base_dt,
"upd_stkpc_tp": upd_stkpc_tp,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def stock_weekly_chart_request_ka10082(
self,
stk_cd: str,
base_dt: str,
upd_stkpc_tp: str,
cont_yn: str = "N",
next_key: str = ""
) -> dict:
"""
주식주봉차트조회요청 (ka10082)
Args:
stk_cd (str): 종목코드 (거래소별 종목코드 KRX:039490,NXT:039490_NX,SOR:039490_AL)
base_dt (str): 기준일자 (YYYYMMDD)
upd_stkpc_tp (str): 수정주가구분 (0 or 1)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 주식주봉차트 데이터
- stk_cd (str): 종목코드
- stk_stk_pole_chart_qry (list): 주식주봉차트조회 데이터 리스트
- cur_prc (str): 현재가
- trde_qty (str): 거래량
- trde_prica (str): 거래대금
- dt (str): 일자
- open_pric (str): 시가
- high_pric (str): 고가
- low_pric (str): 저가
- upd_stkpc_tp (str): 수정주가구분 (1:유상증자, 2:무상증자, 4:배당락, 8:액면분할, 16:액면병합, 32:기업합병, 64:감자, 256:권리락)
- upd_rt (str): 수정비율
- bic_inds_tp (str): 대업종구분
- sm_inds_tp (str): 소업종구분
- stk_infr (str): 종목정보
- upd_stkpc_event (str): 수정주가이벤트
- pred_close_pric (str): 전일종가
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka10082",
}
data = {
"stk_cd": stk_cd,
"base_dt": base_dt,
"upd_stkpc_tp": upd_stkpc_tp,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def stock_monthly_chart_request_ka10083(
self,
stk_cd: str,
base_dt: str,
upd_stkpc_tp: str,
cont_yn: str = "N",
next_key: str = ""
) -> dict:
"""
주식월봉차트조회요청 (ka10083)
Args:
stk_cd (str): 종목코드 (거래소별 종목코드 KRX:039490,NXT:039490_NX,SOR:039490_AL)
base_dt (str): 기준일자 (YYYYMMDD)
upd_stkpc_tp (str): 수정주가구분 (0 or 1)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 주식월봉차트 데이터
- stk_cd (str): 종목코드
- stk_mth_pole_chart_qry (list): 주식월봉차트조회 데이터 리스트
- cur_prc (str): 현재가
- trde_qty (str): 거래량
- trde_prica (str): 거래대금
- dt (str): 일자
- open_pric (str): 시가
- high_pric (str): 고가
- low_pric (str): 저가
- upd_stkpc_tp (str): 수정주가구분 (1:유상증자, 2:무상증자, 4:배당락, 8:액면분할, 16:액면병합, 32:기업합병, 64:감자, 256:권리락)
- upd_rt (str): 수정비율
- bic_inds_tp (str): 대업종구분
- sm_inds_tp (str): 소업종구분
- stk_infr (str): 종목정보
- upd_stkpc_event (str): 수정주가이벤트
- pred_close_pric (str): 전일종가
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka10083",
}
data = {
"stk_cd": stk_cd,
"base_dt": base_dt,
"upd_stkpc_tp": upd_stkpc_tp,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def stock_yearly_chart_request_ka10094(
self,
stk_cd: str,
base_dt: str,
upd_stkpc_tp: str,
cont_yn: str = "N",
next_key: str = ""
) -> dict:
"""
주식년봉차트조회요청 (ka10094)
Args:
stk_cd (str): 종목코드 (거래소별 종목코드 KRX:039490,NXT:039490_NX,SOR:039490_AL)
base_dt (str): 기준일자 (YYYYMMDD)
upd_stkpc_tp (str): 수정주가구분 (0 or 1)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 주식년봉차트 데이터
- stk_cd (str): 종목코드
- stk_yr_pole_chart_qry (list): 주식년봉차트조회 데이터 리스트
- cur_prc (str): 현재가
- trde_qty (str): 거래량
- trde_prica (str): 거래대금
- dt (str): 일자
- open_pric (str): 시가
- high_pric (str): 고가
- low_pric (str): 저가
- upd_stkpc_tp (str): 수정주가구분 (1:유상증자, 2:무상증자, 4:배당락, 8:액면분할, 16:액면병합, 32:기업합병, 64:감자, 256:권리락)
- upd_rt (str): 수정비율
- bic_inds_tp (str): 대업종구분
- sm_inds_tp (str): 소업종구분
- stk_infr (str): 종목정보
- upd_stkpc_event (str): 수정주가이벤트
- pred_close_pric (str): 전일종가
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka10094",
}
data = {
"stk_cd": stk_cd,
"base_dt": base_dt,
"upd_stkpc_tp": upd_stkpc_tp,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def industry_tick_chart_request_ka20004(
self,
inds_cd: str,
tic_scope: str,
cont_yn: str = "N",
next_key: str = ""
) -> dict:
"""
업종틱차트조회요청 (ka20004)
Args:
inds_cd (str): 업종코드
- 001: 종합(KOSPI)
- 002: 대형주
- 003: 중형주
- 004: 소형주
- 101: 종합(KOSDAQ)
- 201: KOSPI200
- 302: KOSTAR
- 701: KRX100
tic_scope (str): 틱범위 (1:1틱, 3:3틱, 5:5틱, 10:10틱, 30:30틱)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 업종틱차트 데이터
- inds_cd (str): 업종코드
- inds_tic_chart_qry (list): 업종틱차트조회 데이터 리스트
- cur_prc (str): 현재가
- trde_qty (str): 거래량
- cntr_tm (str): 체결시간
- open_pric (str): 시가
- high_pric (str): 고가
- low_pric (str): 저가
- bic_inds_tp (str): 대업종구분
- sm_inds_tp (str): 소업종구분
- stk_infr (str): 종목정보
- pred_close_pric (str): 전일종가
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka20004",
}
data = {
"inds_cd": inds_cd,
"tic_scope": tic_scope,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def industry_minute_chart_request_ka20005(
self,
inds_cd: str,
tic_scope: str,
cont_yn: str = "N",
next_key: str = ""
) -> dict:
"""
업종분봉조회요청 (ka20005)
Args:
inds_cd (str): 업종코드
- 001: 종합(KOSPI)
- 002: 대형주
- 003: 중형주
- 004: 소형주
- 101: 종합(KOSDAQ)
- 201: KOSPI200
- 302: KOSTAR
- 701: KRX100
tic_scope (str): 틱범위 (1:1분, 3:3분, 5:5분, 10:10분, 15:15분, 30:30분, 45:45분, 60:60분)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 업종분봉차트 데이터
- inds_cd (str): 업종코드
- inds_min_pole_qry (list): 업종분봉조회 데이터 리스트
- cur_prc (str): 현재가
- trde_qty (str): 거래량
- cntr_tm (str): 체결시간
- open_pric (str): 시가
- high_pric (str): 고가
- low_pric (str): 저가
- bic_inds_tp (str): 대업종구분
- sm_inds_tp (str): 소업종구분
- stk_infr (str): 종목정보
- pred_close_pric (str): 전일종가
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka20005",
}
data = {
"inds_cd": inds_cd,
"tic_scope": tic_scope,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def industry_daily_chart_request_ka20006(
self,
inds_cd: str,
base_dt: str,
cont_yn: str = "N",
next_key: str = ""
) -> dict:
"""
업종일봉조회요청 (ka20006)
Args:
inds_cd (str): 업종코드
- 001: 종합(KOSPI)
- 002: 대형주
- 003: 중형주
- 004: 소형주
- 101: 종합(KOSDAQ)
- 201: KOSPI200
- 302: KOSTAR
- 701: KRX100
base_dt (str): 기준일자 (YYYYMMDD)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 업종일봉차트 데이터
- inds_cd (str): 업종코드
- inds_dt_pole_qry (list): 업종일봉조회 데이터 리스트
- cur_prc (str): 현재가
- trde_qty (str): 거래량
- dt (str): 일자
- open_pric (str): 시가
- high_pric (str): 고가
- low_pric (str): 저가
- trde_prica (str): 거래대금
- bic_inds_tp (str): 대업종구분
- sm_inds_tp (str): 소업종구분
- stk_infr (str): 종목정보
- pred_close_pric (str): 전일종가
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka20006",
}
data = {
"inds_cd": inds_cd,
"base_dt": base_dt,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def industry_weekly_chart_request_ka20007(
self,
inds_cd: str,
base_dt: str,
cont_yn: str = "N",
next_key: str = ""
) -> dict:
"""
업종주봉조회요청 (ka20007)
Args:
inds_cd (str): 업종코드
- 001: 종합(KOSPI)
- 002: 대형주
- 003: 중형주
- 004: 소형주
- 101: 종합(KOSDAQ)
- 201: KOSPI200
- 302: KOSTAR
- 701: KRX100
base_dt (str): 기준일자 (YYYYMMDD)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 업종주봉차트 데이터
- inds_cd (str): 업종코드
- inds_stk_pole_qry (list): 업종주봉조회 데이터 리스트
- cur_prc (str): 현재가
- trde_qty (str): 거래량
- dt (str): 일자
- open_pric (str): 시가
- high_pric (str): 고가
- low_pric (str): 저가
- trde_prica (str): 거래대금
- bic_inds_tp (str): 대업종구분
- sm_inds_tp (str): 소업종구분
- stk_infr (str): 종목정보
- pred_close_pric (str): 전일종가
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka20007",
}
data = {
"inds_cd": inds_cd,
"base_dt": base_dt,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def industry_monthly_chart_request_ka20008(
self,
inds_cd: str,
base_dt: str,
cont_yn: str = "N",
next_key: str = ""
) -> dict:
"""
업종월봉조회요청 (ka20008)
Args:
inds_cd (str): 업종코드
- 001: 종합(KOSPI)
- 002: 대형주
- 003: 중형주
- 004: 소형주
- 101: 종합(KOSDAQ)
- 201: KOSPI200
- 302: KOSTAR
- 701: KRX100
base_dt (str): 기준일자 (YYYYMMDD)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 업종월봉차트 데이터
- inds_cd (str): 업종코드
- inds_mth_pole_qry (list): 업종월봉조회 데이터 리스트
- cur_prc (str): 현재가
- trde_qty (str): 거래량
- dt (str): 일자
- open_pric (str): 시가
- high_pric (str): 고가
- low_pric (str): 저가
- trde_prica (str): 거래대금
- bic_inds_tp (str): 대업종구분
- sm_inds_tp (str): 소업종구분
- stk_infr (str): 종목정보
- pred_close_pric (str): 전일종가
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka20008",
}
data = {
"inds_cd": inds_cd,
"base_dt": base_dt,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def industry_yearly_chart_request_ka20019(
self,
inds_cd: str,
base_dt: str,
cont_yn: str = "N",
next_key: str = ""
) -> dict:
"""
업종년봉조회요청 (ka20019)
Args:
inds_cd (str): 업종코드
- 001: 종합(KOSPI)
- 002: 대형주
- 003: 중형주
- 004: 소형주
- 101: 종합(KOSDAQ)
- 201: KOSPI200
- 302: KOSTAR
- 701: KRX100
base_dt (str): 기준일자 (YYYYMMDD)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 업종년봉차트 데이터
- inds_cd (str): 업종코드
- inds_yr_pole_qry (list): 업종년봉조회 데이터 리스트
- cur_prc (str): 현재가
- trde_qty (str): 거래량
- dt (str): 일자
- open_pric (str): 시가
- high_pric (str): 고가
- low_pric (str): 저가
- trde_prica (str): 거래대금
- bic_inds_tp (str): 대업종구분
- sm_inds_tp (str): 소업종구분
- stk_infr (str): 종목정보
- pred_close_pric (str): 전일종가
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka20019",
}
data = {
"inds_cd": inds_cd,
"base_dt": base_dt,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)

View File

@@ -0,0 +1,291 @@
from kiwoom_rest_api.core.base_api import KiwoomBaseAPI
from typing import Union, Dict, Any, Awaitable
class CreditOrder(KiwoomBaseAPI):
"""한국 주식 신용매매 관련 API를 제공하는 클래스"""
def __init__(
self,
base_url: str = None,
token_manager=None,
use_async: bool = False,
resource_url: str = "/api/dostk/crdordr"
):
"""
CreditOrder 클래스 초기화
Args:
base_url (str, optional): API 기본 URL
token_manager: 토큰 관리자 객체
use_async (bool): 비동기 클라이언트 사용 여부 (기본값: False)
"""
super().__init__(
base_url=base_url,
token_manager=token_manager,
use_async=use_async,
resource_url=resource_url
)
def margin_buy_order_request_kt10006(
self,
dmst_stex_tp: str,
stk_cd: str,
ord_qty: str,
trde_tp: str,
ord_uv: str = "",
cond_uv: str = "",
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""신용 매수주문을 요청합니다.
Args:
dmst_stex_tp (str): 국내거래소구분 (KRX, NXT, SOR)
stk_cd (str): 종목코드
ord_qty (str): 주문수량
trde_tp (str): 매매구분 (0:보통, 3:시장가, 5:조건부지정가, 81:장마감후시간외, 61:장시작전시간외, 62:시간외단일가, 6:최유리지정가, 7:최우선지정가, 10:보통(IOC), 13:시장가(IOC), 16:최유리(IOC), 20:보통(FOK), 23:시장가(FOK), 26:최유리(FOK), 28:스톱지정가, 29:중간가, 30:중간가(IOC), 31:중간가(FOK))
ord_uv (str, optional): 주문단가. Defaults to "".
cond_uv (str, optional): 조건단가. Defaults to "".
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 신용 매수주문 결과
{
"ord_no": str, # 주문번호
"dmst_stex_tp": str, # 국내거래소구분
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.credit_order.margin_buy_order_request_kt10006(
... dmst_stex_tp="KRX",
... stk_cd="005930",
... ord_qty="1",
... ord_uv="2580",
... trde_tp="0"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "kt10006",
}
data = {
"dmst_stex_tp": dmst_stex_tp,
"stk_cd": stk_cd,
"ord_qty": ord_qty,
"ord_uv": ord_uv,
"trde_tp": trde_tp,
"cond_uv": cond_uv,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def margin_sell_order_request_kt10007(
self,
dmst_stex_tp: str,
stk_cd: str,
ord_qty: str,
trde_tp: str,
crd_deal_tp: str,
ord_uv: str = "",
crd_loan_dt: str = "",
cond_uv: str = "",
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""신용 매도주문을 요청합니다.
Args:
dmst_stex_tp (str): 국내거래소구분 (KRX, NXT, SOR)
stk_cd (str): 종목코드
ord_qty (str): 주문수량
trde_tp (str): 매매구분 (0:보통, 3:시장가, 5:조건부지정가, 81:장마감후시간외, 61:장시작전시간외, 62:시간외단일가, 6:최유리지정가, 7:최우선지정가, 10:보통(IOC), 13:시장가(IOC), 16:최유리(IOC), 20:보통(FOK), 23:시장가(FOK), 26:최유리(FOK), 28:스톱지정가, 29:중간가, 30:중간가(IOC), 31:중간가(FOK))
crd_deal_tp (str): 신용거래구분 (33:융자, 99:융자합)
ord_uv (str, optional): 주문단가. Defaults to "".
crd_loan_dt (str, optional): 대출일 YYYYMMDD(융자일경우필수). Defaults to "".
cond_uv (str, optional): 조건단가. Defaults to "".
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 신용 매도주문 결과
{
"ord_no": str, # 주문번호
"dmst_stex_tp": str, # 국내거래소구분
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.credit_order.margin_sell_order_request_kt10007(
... dmst_stex_tp="KRX",
... stk_cd="005930",
... ord_qty="3",
... ord_uv="6450",
... trde_tp="0",
... crd_deal_tp="99"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "kt10007",
}
data = {
"dmst_stex_tp": dmst_stex_tp,
"stk_cd": stk_cd,
"ord_qty": ord_qty,
"ord_uv": ord_uv,
"trde_tp": trde_tp,
"crd_deal_tp": crd_deal_tp,
"crd_loan_dt": crd_loan_dt,
"cond_uv": cond_uv,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def margin_modify_order_request_kt10008(
self,
dmst_stex_tp: str,
orig_ord_no: str,
stk_cd: str,
mdfy_qty: str,
mdfy_uv: str,
mdfy_cond_uv: str = "",
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""신용 정정주문을 요청합니다.
Args:
dmst_stex_tp (str): 국내거래소구분 (KRX, NXT, SOR)
orig_ord_no (str): 원주문번호
stk_cd (str): 종목코드
mdfy_qty (str): 정정수량
mdfy_uv (str): 정정단가
mdfy_cond_uv (str, optional): 정정조건단가. Defaults to "".
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 신용 정정주문 결과
{
"ord_no": str, # 주문번호
"base_orig_ord_no": str, # 모주문번호
"mdfy_qty": str, # 정정수량
"dmst_stex_tp": str, # 국내거래소구분
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.credit_order.margin_modify_order_request_kt10008(
... dmst_stex_tp="KRX",
... orig_ord_no="0000455",
... stk_cd="005930",
... mdfy_qty="1",
... mdfy_uv="2590"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "kt10008",
}
data = {
"dmst_stex_tp": dmst_stex_tp,
"orig_ord_no": orig_ord_no,
"stk_cd": stk_cd,
"mdfy_qty": mdfy_qty,
"mdfy_uv": mdfy_uv,
"mdfy_cond_uv": mdfy_cond_uv,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def margin_cancel_order_request_kt10009(
self,
dmst_stex_tp: str,
orig_ord_no: str,
stk_cd: str,
cncl_qty: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""신용 취소주문을 요청합니다.
Args:
dmst_stex_tp (str): 국내거래소구분 (KRX, NXT, SOR)
orig_ord_no (str): 원주문번호
stk_cd (str): 종목코드
cncl_qty (str): 취소수량 ('0' 입력시 잔량 전부 취소)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 신용 취소주문 결과
{
"ord_no": str, # 주문번호
"base_orig_ord_no": str, # 모주문번호
"cncl_qty": str, # 취소수량
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.credit_order.margin_cancel_order_request_kt10009(
... dmst_stex_tp="KRX",
... orig_ord_no="0001615",
... stk_cd="005930",
... cncl_qty="1"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "kt10009",
}
data = {
"dmst_stex_tp": dmst_stex_tp,
"orig_ord_no": orig_ord_no,
"stk_cd": stk_cd,
"cncl_qty": cncl_qty,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,621 @@
from kiwoom_rest_api.core.base_api import KiwoomBaseAPI
from typing import Union, Dict, Any, Awaitable
class ETF(KiwoomBaseAPI):
"""한국 주식 ETF 관련 API를 제공하는 클래스"""
def __init__(
self,
base_url: str = None,
token_manager=None,
use_async: bool = False,
resource_url: str = "/api/dostk/etf"
):
"""
ETF 클래스 초기화
Args:
base_url (str, optional): API 기본 URL
token_manager: 토큰 관리자 객체
use_async (bool): 비동기 클라이언트 사용 여부 (기본값: False)
"""
super().__init__(
base_url=base_url,
token_manager=token_manager,
use_async=use_async,
resource_url=resource_url
)
def etf_return_rate_request_ka40001(
self,
stock_code: str,
etf_object_index_code: str,
period: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""ETF 수익률을 조회합니다.
Args:
stock_code (str): 종목코드 (6자리)
etf_object_index_code (str): ETF대상지수코드 (3자리)
period (str): 기간
- "0": 1주
- "1": 1달
- "2": 6개월
- "3": 1년
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: ETF 수익률 데이터
{
"etfprft_rt_lst": list, # ETF수익율 리스트
[
{
"etfprft_rt": str, # ETF수익률
"cntr_prft_rt": str, # 체결수익률
"for_netprps_qty": str, # 외인순매수수량
"orgn_netprps_qty": str, # 기관순매수수량
}
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.etf_return_rate_request_ka40001(
... stock_code="069500",
... etf_object_index_code="207",
... period="3"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka40001",
}
data = {
"stk_cd": stock_code,
"etfobjt_idex_cd": etf_object_index_code,
"dt": period,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def etf_stock_info_request_ka40002(
self,
stock_code: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""ETF 종목정보를 조회합니다.
Args:
stock_code (str): 종목코드 (6자리)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: ETF 종목정보 데이터
{
"stk_nm": str, # 종목명
"etfobjt_idex_nm": str, # ETF대상지수명
"wonju_pric": str, # 원주가격
"etftxon_type": str, # ETF과세유형
"etntxon_type": str, # ETN과세유형
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.etf_stock_info_request_ka40002(
... stock_code="069500"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka40002",
}
data = {
"stk_cd": stock_code,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def etf_daily_trend_request_ka40003(
self,
stock_code: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""ETF 일별추이를 조회합니다.
Args:
stock_code (str): 종목코드 (6자리)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: ETF 일별추이 데이터
{
"etfdaly_trnsn": list, # ETF일별추이 리스트
[
{
"cntr_dt": str, # 체결일자
"cur_prc": str, # 현재가
"pre_sig": str, # 대비기호
"pred_pre": str, # 전일대비
"pre_rt": str, # 대비율
"trde_qty": str, # 거래량
"nav": str, # NAV
"acc_trde_prica": str, # 누적거래대금
"navidex_dispty_rt": str, # NAV/지수괴리율
"navetfdispty_rt": str, # NAV/ETF괴리율
"trace_eor_rt": str, # 추적오차율
"trace_cur_prc": str, # 추적현재가
"trace_pred_pre": str, # 추적전일대비
"trace_pre_sig": str, # 추적대비기호
}
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.etf_daily_trend_request_ka40003(
... stock_code="069500"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka40003",
}
data = {
"stk_cd": stock_code,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def etf_overall_market_price_request_ka40004(
self,
tax_type: str = "0",
nav_pre: str = "0",
management_company: str = "0000",
tax_yn: str = "0",
trace_index: str = "0",
exchange_type: str = "1",
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""ETF 전체시세를 조회합니다.
Args:
tax_type (str, optional): 과세유형. Defaults to "0".
- "0": 전체
- "1": 비과세
- "2": 보유기간과세
- "3": 회사형
- "4": 외국
- "5": 비과세해외(보유기간관세)
nav_pre (str, optional): NAV대비. Defaults to "0".
- "0": 전체
- "1": NAV > 전일종가
- "2": NAV < 전일종가
management_company (str, optional): 운용사. Defaults to "0000".
- "0000": 전체
- "3020": KODEX(삼성)
- "3027": KOSEF(키움)
- "3191": TIGER(미래에셋)
- "3228": KINDEX(한국투자)
- "3023": KStar(KB)
- "3022": 아리랑(한화)
- "9999": 기타운용사
tax_yn (str, optional): 과세여부. Defaults to "0".
- "0": 전체
- "1": 과세
- "2": 비과세
trace_index (str, optional): 추적지수. Defaults to "0".
- "0": 전체
exchange_type (str, optional): 거래소구분. Defaults to "1".
- "1": KRX
- "2": NXT
- "3": 통합
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: ETF 전체시세 데이터
{
"etfall_mrpr": list, # ETF전체시세 리스트
[
{
"stk_cd": str, # 종목코드
"stk_cls": str, # 종목분류
"stk_nm": str, # 종목명
"close_pric": str, # 종가
"pre_sig": str, # 대비기호
"pred_pre": str, # 전일대비
"pre_rt": str, # 대비율
"trde_qty": str, # 거래량
"nav": str, # NAV
"trace_eor_rt": str, # 추적오차율
"txbs": str, # 과표기준
"dvid_bf_base": str, # 배당전기준
"pred_dvida": str, # 전일배당금
"trace_idex_nm": str, # 추적지수명
"drng": str, # 배수
"trace_idex_cd": str, # 추적지수코드
"trace_idex": str, # 추적지수
"trace_flu_rt": str, # 추적등락율
}
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.etf_overall_market_price_request_ka40004(
... tax_type="0",
... nav_pre="0",
... management_company="0000",
... tax_yn="0",
... trace_index="0",
... exchange_type="1"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka40004",
}
data = {
"txon_type": tax_type,
"navpre": nav_pre,
"mngmcomp": management_company,
"txon_yn": tax_yn,
"trace_idex": trace_index,
"stex_tp": exchange_type,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def etf_time_segment_trend_request_ka40006(
self,
stock_code: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""ETF 시간대별추이를 조회합니다.
Args:
stock_code (str): 종목코드 (6자리)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: ETF 시간대별추이 데이터
{
"stk_nm": str, # 종목명
"etfobjt_idex_nm": str, # ETF대상지수명
"wonju_pric": str, # 원주가격
"etftxon_type": str, # ETF과세유형
"etntxon_type": str, # ETN과세유형
"etftisl_trnsn": list, # ETF시간대별추이 리스트
[
{
"tm": str, # 시간
"close_pric": str, # 종가
"pre_sig": str, # 대비기호
"pred_pre": str, # 전일대비
"flu_rt": str, # 등락율
"trde_qty": str, # 거래량
"nav": str, # NAV
"trde_prica": str, # 거래대금
"navidex": str, # NAV지수
"navetf": str, # NAVETF
"trace": str, # 추적
"trace_idex": str, # 추적지수
"trace_idex_pred_pre": str, # 추적지수전일대비
"trace_idex_pred_pre_sig": str, # 추적지수전일대비기호
}
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.etf_time_segment_trend_request_ka40006(
... stock_code="069500"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka40006",
}
data = {
"stk_cd": stock_code,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def etf_time_segment_execution_request_ka40007(
self,
stock_code: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""ETF 시간대별체결을 조회합니다.
Args:
stock_code (str): 종목코드 (6자리)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: ETF 시간대별체결 데이터
{
"stk_cls": str, # 종목분류
"stk_nm": str, # 종목명
"etfobjt_idex_nm": str, # ETF대상지수명
"etfobjt_idex_cd": str, # ETF대상지수코드
"objt_idex_pre_rt": str, # 대상지수대비율
"wonju_pric": str, # 원주가격
"etftisl_cntr_array": list, # ETF시간대별체결배열
[
{
"cntr_tm": str, # 체결시간
"cur_prc": str, # 현재가
"pre_sig": str, # 대비기호
"pred_pre": str, # 전일대비
"trde_qty": str, # 거래량
"stex_tp": str, # 거래소구분 (KRX, NXT, 통합)
}
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.etf_time_segment_execution_request_ka40007(
... stock_code="069500"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka40007",
}
data = {
"stk_cd": stock_code,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def etf_datewise_execution_request_ka40008(
self,
stock_code: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""ETF 일자별체결을 조회합니다.
Args:
stock_code (str): 종목코드 (6자리)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: ETF 일자별체결 데이터
{
"cntr_tm": str, # 체결시간
"cur_prc": str, # 현재가
"pre_sig": str, # 대비기호
"pred_pre": str, # 전일대비
"trde_qty": str, # 거래량
"etfnetprps_qty_array": list, # ETF순매수수량배열
[
{
"dt": str, # 일자
"cur_prc_n": str, # 현재가n
"pre_sig_n": str, # 대비기호n
"pred_pre_n": str, # 전일대비n
"acc_trde_qty": str, # 누적거래량
"for_netprps_qty": str, # 외인순매수수량
"orgn_netprps_qty": str, # 기관순매수수량
}
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.etf_datewise_execution_request_ka40008(
... stock_code="069500"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka40008",
}
data = {
"stk_cd": stock_code,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def etf_timewise_execution_request_ka40009(
self,
stock_code: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""ETF 시간대별NAV를 조회합니다.
Args:
stock_code (str): 종목코드 (6자리)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: ETF 시간대별NAV 데이터
{
"etfnavarray": list, # ETFNAV배열
[
{
"nav": str, # NAV
"navpred_pre": str, # NAV전일대비
"navflu_rt": str, # NAV등락율
"trace_eor_rt": str, # 추적오차율
"dispty_rt": str, # 괴리율
"stkcnt": str, # 주식수
"base_pric": str, # 기준가
"for_rmnd_qty": str, # 외인보유수량
"repl_pric": str, # 대용가
"conv_pric": str, # 환산가격
"drstk": str, # DR/주
"wonju_pric": str, # 원주가격
}
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.etf_timewise_execution_request_ka40009(
... stock_code="069500"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka40009",
}
data = {
"stk_cd": stock_code,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def etf_timewise_trend_request_ka40010(
self,
stock_code: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""ETF 시간대별추이를 조회합니다.
Args:
stock_code (str): 종목코드 (6자리)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: ETF 시간대별추이 데이터
{
"etftisl_trnsn": list, # ETF시간대별추이 리스트
[
{
"cur_prc": str, # 현재가
"pre_sig": str, # 대비기호
"pred_pre": str, # 전일대비
"trde_qty": str, # 거래량
"for_netprps": str, # 외인순매수
}
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.etf_timewise_trend_request_ka40010(
... stock_code="069500"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka40010",
}
data = {
"stk_cd": stock_code,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)

View File

@@ -0,0 +1,234 @@
from kiwoom_rest_api.core.base_api import KiwoomBaseAPI
from typing import Union, Dict, Any, Awaitable
class ForeignInstitution(KiwoomBaseAPI):
"""한국 주식 외인 관련 API를 제공하는 클래스"""
def __init__(
self,
base_url: str = None,
token_manager=None,
use_async: bool = False,
resource_url: str = "/api/dostk/frgnistt"
):
"""
ForeignInstitution 클래스 초기화
Args:
base_url (str, optional): API 기본 URL
token_manager: 토큰 관리자 객체
use_async (bool): 비동기 클라이언트 사용 여부 (기본값: False)
"""
super().__init__(
base_url=base_url,
token_manager=token_manager,
use_async=use_async,
resource_url=resource_url
)
def foreign_investor_stockwise_trading_trend_request_ka10008(
self,
stock_code: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""주식 외국인 종목별 매매 동향을 조회합니다.
Args:
stock_code (str): 종목코드 (거래소별 종목코드)
- KRX: 039490
- NXT: 039490_NX
- SOR: 039490_AL
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 주식 외국인 종목별 매매 동향 데이터
{
"stk_frgnr": [
{
"dt": str, # 일자
"close_pric": str, # 종가
"pred_pre": str, # 전일대비
"trde_qty": str, # 거래량
"chg_qty": str, # 변동수량
"poss_stkcnt": str, # 보유주식수
"wght": str, # 비중
"gain_pos_stkcnt": str, # 취득가능주식수
"frgnr_limit": str, # 외국인한도
"frgnr_limit_irds": str, # 외국인한도증감
"limit_exh_rt": str, # 한도소진률
},
...
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.foreign_institution.foreign_investor_stockwise_trading_trend_request_ka10008(
... stock_code="005930"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka10008",
}
data = {
"stk_cd": stock_code,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def institutional_stock_request_ka10009(
self,
stock_code: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""주식 기관 요청을 조회합니다.
Args:
stock_code (str): 종목코드 (거래소별 종목코드)
- KRX: 039490
- NXT: 039490_NX
- SOR: 039490_AL
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 주식 기관 요청 데이터
{
"date": str, # 날짜
"close_pric": str, # 종가
"pre": str, # 대비
"orgn_dt_acc": str, # 기관기간누적
"orgn_daly_nettrde": str, # 기관일별순매매
"frgnr_daly_nettrde": str, # 외국인일별순매매
"frgnr_qota_rt": str, # 외국인지분율
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.foreign_institution.institutional_stock_request_ka10009(
... stock_code="005930"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka10009",
}
data = {
"stk_cd": stock_code,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def institution_foreign_consecutive_trading_status_request_ka10131(
self,
dt: str,
mrkt_tp: str,
netslmt_tp: str = "2",
stk_inds_tp: str = "0",
amt_qty_tp: str = "0",
stex_tp: str = "1",
strt_dt: str = "",
end_dt: str = "",
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""기관외국인연속매매현황을 조회합니다.
Args:
dt (str): 기간 (1:최근일, 3:3일, 5:5일, 10:10일, 20:20일, 120:120일, 0:시작일자/종료일자로 조회)
mrkt_tp (str): 장구분 (001:코스피, 101:코스닥)
netslmt_tp (str, optional): 순매도수구분 (2:순매수(고정값)). Defaults to "2".
stk_inds_tp (str, optional): 종목업종구분 (0:종목(주식),1:업종). Defaults to "0".
amt_qty_tp (str, optional): 금액수량구분 (0:금액, 1:수량). Defaults to "0".
stex_tp (str, optional): 거래소구분 (1:KRX, 2:NXT, 3:통합). Defaults to "1".
strt_dt (str, optional): 시작일자 (YYYYMMDD). Defaults to "".
end_dt (str, optional): 종료일자 (YYYYMMDD). Defaults to "".
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 기관외국인연속매매현황 데이터
{
"orgn_frgnr_cont_trde_prst": [
{
"rank": str, # 순위
"stk_cd": str, # 종목코드
"stk_nm": str, # 종목명
"prid_stkpc_flu_rt": str, # 기간중주가등락률
"orgn_nettrde_amt": str, # 기관순매매금액
"orgn_nettrde_qty": str, # 기관순매매량
"orgn_cont_netprps_dys": str, # 기관계연속순매수일수
"orgn_cont_netprps_qty": str, # 기관계연속순매수량
"orgn_cont_netprps_amt": str, # 기관계연속순매수금액
"frgnr_nettrde_qty": str, # 외국인순매매량
"frgnr_nettrde_amt": str, # 외국인순매매액
"frgnr_cont_netprps_dys": str, # 외국인연속순매수일수
"frgnr_cont_netprps_qty": str, # 외국인연속순매수량
"frgnr_cont_netprps_amt": str, # 외국인연속순매수금액
"nettrde_qty": str, # 순매매량
"nettrde_amt": str, # 순매매액
"tot_cont_netprps_dys": str, # 합계연속순매수일수
"tot_cont_nettrde_qty": str, # 합계연속순매매수량
"tot_cont_netprps_amt": str, # 합계연속순매수금액
},
...
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.foreign_institution.institution_foreign_consecutive_trading_status_request_ka10131(
... dt="1",
... mrkt_tp="001"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka10131",
}
data = {
"dt": dt,
"strt_dt": strt_dt,
"end_dt": end_dt,
"mrkt_tp": mrkt_tp,
"netslmt_tp": netslmt_tp,
"stk_inds_tp": stk_inds_tp,
"amt_qty_tp": amt_qty_tp,
"stex_tp": stex_tp,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)

View File

@@ -0,0 +1,115 @@
from typing import Dict, Optional, Any
from kiwoom_rest_api.core.sync_client import make_request
def get_investor_trend(
stock_code: str,
period: str = "D",
start_date: Optional[str] = None,
end_date: Optional[str] = None,
access_token: Optional[str] = None,
) -> Dict[str, Any]:
"""
투자자별 매매동향 (KA-STOCK-013)
Args:
stock_code: 종목코드 (6자리)
period: 기간분류코드 (D:일봉, W:주봉, M:월봉)
start_date: 조회 시작 날짜 (YYYYMMDD)
end_date: 조회 끝 날짜 (YYYYMMDD)
access_token: OAuth 액세스 토큰
Returns:
투자자별 매매동향 데이터
"""
endpoint = "/stock/investor"
params = {
"FID_COND_MRKT_DIV_CODE": "J",
"FID_INPUT_ISCD": stock_code,
"FID_PERIOD_DIV_CODE": period,
}
if start_date:
params["FID_INPUT_DATE_1"] = start_date
if end_date:
params["FID_INPUT_DATE_2"] = end_date
return make_request(
endpoint=endpoint,
params=params,
access_token=access_token,
)
def get_market_investor_trend(
market_code: str = "0",
period: str = "D",
start_date: Optional[str] = None,
end_date: Optional[str] = None,
access_token: Optional[str] = None,
) -> Dict[str, Any]:
"""
전체 시장별 투자자 매매동향 (KA-STOCK-014)
Args:
market_code: 시장분류코드 (0:전체, 1:코스피, 2:코스닥)
period: 기간분류코드 (D:일봉, W:주봉, M:월봉)
start_date: 조회 시작 날짜 (YYYYMMDD)
end_date: 조회 끝 날짜 (YYYYMMDD)
access_token: OAuth 액세스 토큰
Returns:
시장별 투자자 매매동향 데이터
"""
endpoint = "/stock/investor/market"
params = {
"FID_COND_MRKT_DIV_CODE": market_code,
"FID_PERIOD_DIV_CODE": period,
}
if start_date:
params["FID_INPUT_DATE_1"] = start_date
if end_date:
params["FID_INPUT_DATE_2"] = end_date
return make_request(
endpoint=endpoint,
params=params,
access_token=access_token,
)
def get_program_trading(
market_code: str = "0",
start_date: Optional[str] = None,
end_date: Optional[str] = None,
access_token: Optional[str] = None,
) -> Dict[str, Any]:
"""
프로그램 매매현황 (KA-STOCK-015)
Args:
market_code: 시장분류코드 (0:전체, 1:코스피, 2:코스닥)
start_date: 조회 시작 날짜 (YYYYMMDD)
end_date: 조회 끝 날짜 (YYYYMMDD)
access_token: OAuth 액세스 토큰
Returns:
프로그램 매매현황 데이터
"""
endpoint = "/stock/program"
params = {
"FID_COND_MRKT_DIV_CODE": market_code,
}
if start_date:
params["FID_INPUT_DATE_1"] = start_date
if end_date:
params["FID_INPUT_DATE_2"] = end_date
return make_request(
endpoint=endpoint,
params=params,
access_token=access_token,
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,317 @@
from kiwoom_rest_api.core.base_api import KiwoomBaseAPI
from typing import Union, Dict, Any, Awaitable
class Order(KiwoomBaseAPI):
"""한국 주식 주문 관련 API를 제공하는 클래스"""
def __init__(
self,
base_url: str = None,
token_manager=None,
use_async: bool = False,
resource_url: str = "/api/dostk/ordr"
):
"""
Order 클래스 초기화
Args:
base_url (str, optional): API 기본 URL
token_manager: 토큰 관리자 객체
use_async (bool): 비동기 클라이언트 사용 여부 (기본값: False)
"""
super().__init__(
base_url=base_url,
token_manager=token_manager,
use_async=use_async,
resource_url=resource_url
)
def stock_buy_order_request_kt10000(
self,
dmst_stex_tp: str,
stk_cd: str,
ord_qty: str,
trde_tp: str,
ord_uv: str = "",
cond_uv: str = "",
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""주식 매수주문을 요청합니다.
Args:
dmst_stex_tp (str): 국내거래소구분 (KRX, NXT, SOR)
stk_cd (str): 종목코드
ord_qty (str): 주문수량
trde_tp (str): 매매구분
- 0: 보통
- 3: 시장가
- 5: 조건부지정가
- 81: 장마감후시간외
- 61: 장시작전시간외
- 62: 시간외단일가
- 6: 최유리지정가
- 7: 최우선지정가
- 10: 보통(IOC)
- 13: 시장가(IOC)
- 16: 최유리(IOC)
- 20: 보통(FOK)
- 23: 시장가(FOK)
- 26: 최유리(FOK)
- 28: 스톱지정가
- 29: 중간가
- 30: 중간가(IOC)
- 31: 중간가(FOK)
ord_uv (str, optional): 주문단가. Defaults to "".
cond_uv (str, optional): 조건단가. Defaults to "".
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 주문 응답 데이터
{
"ord_no": str, # 주문번호
"dmst_stex_tp": str, # 국내거래소구분
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.order.stock_buy_order_request_kt10000(
... dmst_stex_tp="KRX",
... stk_cd="005930",
... ord_qty="1",
... trde_tp="3"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "kt10000",
}
data = {
"dmst_stex_tp": dmst_stex_tp,
"stk_cd": stk_cd,
"ord_qty": ord_qty,
"ord_uv": ord_uv,
"trde_tp": trde_tp,
"cond_uv": cond_uv,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def stock_sell_order_request_kt10001(
self,
dmst_stex_tp: str,
stk_cd: str,
ord_qty: str,
trde_tp: str,
ord_uv: str = "",
cond_uv: str = "",
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""주식 매도주문을 요청합니다.
Args:
dmst_stex_tp (str): 국내거래소구분 (KRX, NXT, SOR)
stk_cd (str): 종목코드
ord_qty (str): 주문수량
trde_tp (str): 매매구분
- 0: 보통
- 3: 시장가
- 5: 조건부지정가
- 81: 장마감후시간외
- 61: 장시작전시간외
- 62: 시간외단일가
- 6: 최유리지정가
- 7: 최우선지정가
- 10: 보통(IOC)
- 13: 시장가(IOC)
- 16: 최유리(IOC)
- 20: 보통(FOK)
- 23: 시장가(FOK)
- 26: 최유리(FOK)
- 28: 스톱지정가
- 29: 중간가
- 30: 중간가(IOC)
- 31: 중간가(FOK)
ord_uv (str, optional): 주문단가. Defaults to "".
cond_uv (str, optional): 조건단가. Defaults to "".
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 주문 응답 데이터
{
"ord_no": str, # 주문번호
"dmst_stex_tp": str, # 국내거래소구분
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.order.stock_sell_order_request_kt10001(
... dmst_stex_tp="KRX",
... stk_cd="005930",
... ord_qty="1",
... trde_tp="3"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "kt10001",
}
data = {
"dmst_stex_tp": dmst_stex_tp,
"stk_cd": stk_cd,
"ord_qty": ord_qty,
"ord_uv": ord_uv,
"trde_tp": trde_tp,
"cond_uv": cond_uv,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def stock_modify_order_request_kt10002(
self,
dmst_stex_tp: str,
orig_ord_no: str,
stk_cd: str,
mdfy_qty: str,
mdfy_uv: str,
mdfy_cond_uv: str = "",
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""주식 정정주문을 요청합니다.
Args:
dmst_stex_tp (str): 국내거래소구분 (KRX, NXT, SOR)
orig_ord_no (str): 원주문번호
stk_cd (str): 종목코드
mdfy_qty (str): 정정수량
mdfy_uv (str): 정정단가
mdfy_cond_uv (str, optional): 정정조건단가. Defaults to "".
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 정정주문 응답 데이터
{
"ord_no": str, # 주문번호
"base_orig_ord_no": str, # 모주문번호
"mdfy_qty": str, # 정정수량
"dmst_stex_tp": str, # 국내거래소구분
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.order.stock_modify_order_request_kt10002(
... dmst_stex_tp="KRX",
... orig_ord_no="0000139",
... stk_cd="005930",
... mdfy_qty="1",
... mdfy_uv="199700"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "kt10002",
}
data = {
"dmst_stex_tp": dmst_stex_tp,
"orig_ord_no": orig_ord_no,
"stk_cd": stk_cd,
"mdfy_qty": mdfy_qty,
"mdfy_uv": mdfy_uv,
"mdfy_cond_uv": mdfy_cond_uv,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def stock_cancel_order_request_kt10003(
self,
dmst_stex_tp: str,
orig_ord_no: str,
stk_cd: str,
cncl_qty: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""주식 취소주문을 요청합니다.
Args:
dmst_stex_tp (str): 국내거래소구분 (KRX, NXT, SOR)
orig_ord_no (str): 원주문번호
stk_cd (str): 종목코드
cncl_qty (str): 취소수량 ('0' 입력시 잔량 전부 취소)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 취소주문 응답 데이터
{
"ord_no": str, # 주문번호
"base_orig_ord_no": str, # 모주문번호
"cncl_qty": str, # 취소수량
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.order.stock_cancel_order_request_kt10003(
... dmst_stex_tp="KRX",
... orig_ord_no="0000140",
... stk_cd="005930",
... cncl_qty="1"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "kt10003",
}
data = {
"dmst_stex_tp": dmst_stex_tp,
"orig_ord_no": orig_ord_no,
"stk_cd": stk_cd,
"cncl_qty": cncl_qty,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,507 @@
from kiwoom_rest_api.core.base_api import KiwoomBaseAPI
from typing import Union, Dict, Any, Awaitable
class Sector(KiwoomBaseAPI):
"""한국 주식 섹터 관련 API를 제공하는 클래스"""
def __init__(
self,
base_url: str = None,
token_manager=None,
use_async: bool = False,
resource_url: str = "/api/dostk/sect"
):
"""
Sector 클래스 초기화
Args:
base_url (str, optional): API 기본 URL
token_manager: 토큰 관리자 객체
use_async (bool): 비동기 클라이언트 사용 여부 (기본값: False)
"""
super().__init__(
base_url=base_url,
token_manager=token_manager,
use_async=use_async,
resource_url=resource_url
)
def industry_program_trading_request_ka10010(
self,
stock_code: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""업종프로그램매매를 조회합니다.
Args:
stock_code (str): 종목코드 (거래소별 종목코드)
- KRX: 039490
- NXT: 039490_NX
- SOR: 039490_AL
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 업종프로그램매매 데이터
{
"dfrt_trst_sell_qty": str, # 차익위탁매도수량
"dfrt_trst_sell_amt": str, # 차익위탁매도금액
"dfrt_trst_buy_qty": str, # 차익위탁매수수량
"dfrt_trst_buy_amt": str, # 차익위탁매수금액
"dfrt_trst_netprps_qty": str, # 차익위탁순매수수량
"dfrt_trst_netprps_amt": str, # 차익위탁순매수금액
"ndiffpro_trst_sell_qty": str, # 비차익위탁매도수량
"ndiffpro_trst_sell_amt": str, # 비차익위탁매도금액
"ndiffpro_trst_buy_qty": str, # 비차익위탁매수수량
"ndiffpro_trst_buy_amt": str, # 비차익위탁매수금액
"ndiffpro_trst_netprps_qty": str, # 비차익위탁순매수수량
"ndiffpro_trst_netprps_amt": str, # 비차익위탁순매수금액
"all_dfrt_trst_sell_qty": str, # 전체차익위탁매도수량
"all_dfrt_trst_sell_amt": str, # 전체차익위탁매도금액
"all_dfrt_trst_buy_qty": str, # 전체차익위탁매수수량
"all_dfrt_trst_buy_amt": str, # 전체차익위탁매수금액
"all_dfrt_trst_netprps_qty": str, # 전체차익위탁순매수수량
"all_dfrt_trst_netprps_amt": str, # 전체차익위탁순매수금액
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.industry_program_trading_request_ka10010(
... stock_code="005930"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka10010",
}
data = {
"stk_cd": stock_code,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def industrywise_investor_net_buy_request_ka10051(
self,
mrkt_tp: str,
amt_qty_tp: str,
stex_tp: str,
base_dt: str = "",
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""업종별투자자순매수를 조회합니다.
Args:
mrkt_tp (str): 시장구분 (코스피:0, 코스닥:1)
amt_qty_tp (str): 금액수량구분 (금액:0, 수량:1)
stex_tp (str): 거래소구분 (1:KRX, 2:NXT, 3:통합)
base_dt (str, optional): 기준일자 (YYYYMMDD). Defaults to "".
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 업종별투자자순매수 데이터
{
"inds_netprps": [
{
"inds_cd": str, # 업종코드
"inds_nm": str, # 업종명
"cur_prc": str, # 현재가
"pre_smbol": str, # 대비부호
"pred_pre": str, # 전일대비
"flu_rt": str, # 등락율
"trde_qty": str, # 거래량
"sc_netprps": str, # 증권순매수
"insrnc_netprps": str, # 보험순매수
"invtrt_netprps": str, # 투신순매수
"bank_netprps": str, # 은행순매수
"jnsinkm_netprps": str, # 종신금순매수
"endw_netprps": str, # 기금순매수
"etc_corp_netprps": str, # 기타법인순매수
"ind_netprps": str, # 개인순매수
"frgnr_netprps": str, # 외국인순매수
"native_trmt_frgnr_netprps": str, # 내국인대우외국인순매수
"natn_netprps": str, # 국가순매수
"samo_fund_netprps": str, # 사모펀드순매수
"orgn_netprps": str, # 기관계순매수
},
...
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.industrywise_investor_net_buy_request_ka10051(
... mrkt_tp="0",
... amt_qty_tp="0",
... stex_tp="3",
... base_dt="20241107"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka10051",
}
data = {
"mrkt_tp": mrkt_tp,
"amt_qty_tp": amt_qty_tp,
"base_dt": base_dt,
"stex_tp": stex_tp,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def industry_current_price_request_ka20001(
self,
mrkt_tp: str,
inds_cd: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""업종현재가를 조회합니다.
Args:
mrkt_tp (str): 시장구분 (0:코스피, 1:코스닥, 2:코스피200)
inds_cd (str): 업종코드
- 001: 종합(KOSPI)
- 002: 대형주
- 003: 중형주
- 004: 소형주
- 101: 종합(KOSDAQ)
- 201: KOSPI200
- 302: KOSTAR
- 701: KRX100
- 나머지 업종코드 참고
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 업종현재가 데이터
{
"cur_prc": str, # 현재가
"pred_pre_sig": str, # 전일대비기호
"pred_pre": str, # 전일대비
"flu_rt": str, # 등락률
"trde_qty": str, # 거래량
"trde_prica": str, # 거래대금
"trde_frmatn_stk_num": str, # 거래형성종목수
"trde_frmatn_rt": str, # 거래형성비율
"open_pric": str, # 시가
"high_pric": str, # 고가
"low_pric": str, # 저가
"upl": str, # 상한
"rising": str, # 상승
"stdns": str, # 보합
"fall": str, # 하락
"lst": str, # 하한
"52wk_hgst_pric": str, # 52주최고가
"52wk_hgst_pric_dt": str, # 52주최고가일
"52wk_hgst_pric_pre_rt": str, # 52주최고가대비율
"52wk_lwst_pric": str, # 52주최저가
"52wk_lwst_pric_dt": str, # 52주최저가일
"52wk_lwst_pric_pre_rt": str, # 52주최저가대비율
"inds_cur_prc_tm": [ # 업종현재가_시간별
{
"tm_n": str, # 시간n
"cur_prc_n": str, # 현재가n
"pred_pre_sig_n": str, # 전일대비기호n
"pred_pre_n": str, # 전일대비n
"flu_rt_n": str, # 등락률n
"trde_qty_n": str, # 거래량n
"acc_trde_qty_n": str, # 누적거래량n
"stex_tp": str, # 거래소구분
},
...
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.industry_current_price_request_ka20001(
... mrkt_tp="0",
... inds_cd="001"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka20001",
}
data = {
"mrkt_tp": mrkt_tp,
"inds_cd": inds_cd,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def industrywise_stock_price_request_ka20002(
self,
mrkt_tp: str,
inds_cd: str,
stex_tp: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""업종별주가를 조회합니다.
Args:
mrkt_tp (str): 시장구분 (0:코스피, 1:코스닥, 2:코스피200)
inds_cd (str): 업종코드
- 001: 종합(KOSPI)
- 002: 대형주
- 003: 중형주
- 004: 소형주
- 101: 종합(KOSDAQ)
- 201: KOSPI200
- 302: KOSTAR
- 701: KRX100
- 나머지 업종코드 참고
stex_tp (str): 거래소구분 (1:KRX, 2:NXT, 3:통합)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 업종별주가 데이터
{
"inds_stkpc": [ # 업종별주가
{
"stk_cd": str, # 종목코드
"stk_nm": str, # 종목명
"cur_prc": str, # 현재가
"pred_pre_sig": str, # 전일대비기호
"pred_pre": str, # 전일대비
"flu_rt": str, # 등락률
"now_trde_qty": str, # 현재거래량
"sel_bid": str, # 매도호가
"buy_bid": str, # 매수호가
"open_pric": str, # 시가
"high_pric": str, # 고가
"low_pric": str, # 저가
},
...
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.industrywise_stock_price_request_ka20002(
... mrkt_tp="0",
... inds_cd="001",
... stex_tp="1"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka20002",
}
data = {
"mrkt_tp": mrkt_tp,
"inds_cd": inds_cd,
"stex_tp": stex_tp,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def all_industries_index_request_ka20003(
self,
inds_cd: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""전업종지수를 조회합니다.
Args:
inds_cd (str): 업종코드
- 001: 종합(KOSPI)
- 002: 대형주
- 003: 중형주
- 004: 소형주
- 101: 종합(KOSDAQ)
- 201: KOSPI200
- 302: KOSTAR
- 701: KRX100
- 나머지 업종코드 참고
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 전업종지수 데이터
{
"all_inds_idex": [ # 전업종지수
{
"stk_cd": str, # 종목코드
"stk_nm": str, # 종목명
"cur_prc": str, # 현재가
"pre_sig": str, # 대비기호
"pred_pre": str, # 전일대비
"flu_rt": str, # 등락률
"trde_qty": str, # 거래량
"wght": str, # 비중
"trde_prica": str, # 거래대금
"upl": str, # 상한
"rising": str, # 상승
"stdns": str, # 보합
"fall": str, # 하락
"lst": str, # 하한
"flo_stk_num": str, # 상장종목수
},
...
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.all_industries_index_request_ka20003(
... inds_cd="001"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka20003",
}
data = {
"inds_cd": inds_cd,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def industry_daily_current_price_request_ka20009(
self,
mrkt_tp: str,
inds_cd: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""업종현재가일별을 조회합니다.
Args:
mrkt_tp (str): 시장구분 (0:코스피, 1:코스닥, 2:코스피200)
inds_cd (str): 업종코드
- 001: 종합(KOSPI)
- 002: 대형주
- 003: 중형주
- 004: 소형주
- 101: 종합(KOSDAQ)
- 201: KOSPI200
- 302: KOSTAR
- 701: KRX100
- 나머지 업종코드 참고
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 업종현재가일별 데이터
{
"cur_prc": str, # 현재가
"pred_pre_sig": str, # 전일대비기호
"pred_pre": str, # 전일대비
"flu_rt": str, # 등락률
"trde_qty": str, # 거래량
"trde_prica": str, # 거래대금
"trde_frmatn_stk_num": str, # 거래형성종목수
"trde_frmatn_rt": str, # 거래형성비율
"open_pric": str, # 시가
"high_pric": str, # 고가
"low_pric": str, # 저가
"upl": str, # 상한
"rising": str, # 상승
"stdns": str, # 보합
"fall": str, # 하락
"lst": str, # 하한
"52wk_hgst_pric": str, # 52주최고가
"52wk_hgst_pric_dt": str, # 52주최고가일
"52wk_hgst_pric_pre_rt": str, # 52주최고가대비율
"52wk_lwst_pric": str, # 52주최저가
"52wk_lwst_pric_dt": str, # 52주최저가일
"52wk_lwst_pric_pre_rt": str, # 52주최저가대비율
"inds_cur_prc_daly_rept": [ # 업종현재가_일별반복
{
"dt_n": str, # 일자n
"cur_prc_n": str, # 현재가n
"pred_pre_sig_n": str, # 전일대비기호n
"pred_pre_n": str, # 전일대비n
"flu_rt_n": str, # 등락률n
"acc_trde_qty_n": str, # 누적거래량n
},
...
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.industry_daily_current_price_request_ka20009(
... mrkt_tp="0",
... inds_cd="001"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka20009",
}
data = {
"mrkt_tp": mrkt_tp,
"inds_cd": inds_cd,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)

View File

@@ -0,0 +1,287 @@
from kiwoom_rest_api.core.base_api import KiwoomBaseAPI
from typing import Union, Dict, Any, Awaitable
class SecuritiesLendingAndBorrowing(KiwoomBaseAPI):
"""한국 주식 대차거래 관련 API를 제공하는 클래스"""
def __init__(
self,
base_url: str = None,
token_manager=None,
use_async: bool = False,
resource_url: str = "/api/dostk/slb"
):
"""
SecuritiesLendingAndBorrowing 클래스 초기화
Args:
base_url (str, optional): API 기본 URL
token_manager: 토큰 관리자 객체
use_async (bool): 비동기 클라이언트 사용 여부 (기본값: False)
"""
super().__init__(
base_url=base_url,
token_manager=token_manager,
use_async=use_async,
resource_url=resource_url
)
def stock_lending_trend_request_ka10068(
self,
strt_dt: str = "",
end_dt: str = "",
all_tp: str = "1",
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""대차거래추이를 요청합니다.
Args:
strt_dt (str, optional): 시작일자 (YYYYMMDD). Defaults to "".
end_dt (str, optional): 종료일자 (YYYYMMDD). Defaults to "".
all_tp (str, optional): 전체구분 (1: 전체표시). Defaults to "1".
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 대차거래추이 데이터
{
"dbrt_trde_trnsn": list, # 대차거래추이 리스트
[
{
"dt": str, # 일자
"dbrt_trde_cntrcnt": str, # 대차거래체결주수
"dbrt_trde_rpy": str, # 대차거래상환주수
"dbrt_trde_irds": str, # 대차거래증감
"rmnd": str, # 잔고주수
"remn_amt": str, # 잔고금액
}
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.slb.stock_lending_trend_request_ka10068(
... strt_dt="20250401",
... end_dt="20250430",
... all_tp="1"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka10068",
}
data = {
"strt_dt": strt_dt,
"end_dt": end_dt,
"all_tp": all_tp,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def top10_stock_lending_request_ka10069(
self,
strt_dt: str,
end_dt: str = "",
mrkt_tp: str = "001",
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""대차거래상위10종목을 요청합니다.
Args:
strt_dt (str): 시작일자 (YYYYMMDD 형식)
end_dt (str, optional): 종료일자 (YYYYMMDD 형식). Defaults to "".
mrkt_tp (str, optional): 시장구분 (001:코스피, 101:코스닥). Defaults to "001".
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 대차거래상위10종목 데이터
{
"dbrt_trde_cntrcnt_sum": str, # 대차거래체결주수합
"dbrt_trde_rpy_sum": str, # 대차거래상환주수합
"rmnd_sum": str, # 잔고주수합
"remn_amt_sum": str, # 잔고금액합
"dbrt_trde_cntrcnt_rt": str, # 대차거래체결주수비율
"dbrt_trde_rpy_rt": str, # 대차거래상환주수비율
"rmnd_rt": str, # 잔고주수비율
"remn_amt_rt": str, # 잔고금액비율
"dbrt_trde_upper_10stk": list, # 대차거래상위10종목 리스트
[
{
"stk_nm": str, # 종목명
"stk_cd": str, # 종목코드
"dbrt_trde_cntrcnt": str, # 대차거래체결주수
"dbrt_trde_rpy": str, # 대차거래상환주수
"rmnd": str, # 잔고주수
"remn_amt": str, # 잔고금액
}
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.slb.top10_stock_lending_request_ka10069(
... strt_dt="20241110",
... end_dt="20241125",
... mrkt_tp="001"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka10069",
}
data = {
"strt_dt": strt_dt,
"end_dt": end_dt,
"mrkt_tp": mrkt_tp,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def stockwise_lending_trend_request_ka20068(
self,
stk_cd: str,
strt_dt: str = "",
end_dt: str = "",
all_tp: str = "0",
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""종목별 대차거래추이를 요청합니다.
Args:
stk_cd (str): 종목코드
strt_dt (str, optional): 시작일자 (YYYYMMDD). Defaults to "".
end_dt (str, optional): 종료일자 (YYYYMMDD). Defaults to "".
all_tp (str, optional): 전체구분 (0:종목코드 입력종목만 표시). Defaults to "0".
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 종목별 대차거래추이 데이터
{
"dbrt_trde_trnsn": list, # 대차거래추이 리스트
[
{
"dt": str, # 일자
"dbrt_trde_cntrcnt": str, # 대차거래체결주수
"dbrt_trde_rpy": str, # 대차거래상환주수
"dbrt_trde_irds": str, # 대차거래증감
"rmnd": str, # 잔고주수
"remn_amt": str, # 잔고금액
}
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.slb.stockwise_lending_trend_request_ka20068(
... stk_cd="005930",
... strt_dt="20250401",
... end_dt="20250430",
... all_tp="0"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka20068",
}
data = {
"stk_cd": stk_cd,
"strt_dt": strt_dt,
"end_dt": end_dt,
"all_tp": all_tp,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def stock_lending_details_request_ka90012(
self,
dt: str,
mrkt_tp: str,
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""대차거래내역을 요청합니다.
Args:
dt (str): 일자 (YYYYMMDD 형식)
mrkt_tp (str): 시장구분 (001:코스피, 101:코스닥)
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 대차거래내역 데이터
{
"dbrt_trde_prps": list, # 대차거래내역 리스트
[
{
"stk_nm": str, # 종목명
"stk_cd": str, # 종목코드
"dbrt_trde_cntrcnt": str, # 대차거래체결주수
"dbrt_trde_rpy": str, # 대차거래상환주수
"rmnd": str, # 잔고주수
"remn_amt": str, # 잔고금액
}
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.slb.stock_lending_details_request_ka90012(
... dt="20241101",
... mrkt_tp="101"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka90012",
}
data = {
"dt": dt,
"mrkt_tp": mrkt_tp,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,175 @@
from kiwoom_rest_api.core.base_api import KiwoomBaseAPI
from typing import Union, Dict, Any, Awaitable
class Theme(KiwoomBaseAPI):
"""한국 주식 테마 관련 API를 제공하는 클래스"""
def __init__(
self,
base_url: str = None,
token_manager=None,
use_async: bool = False,
resource_url: str = "/api/dostk/thme"
):
"""
Theme 클래스 초기화
Args:
base_url (str, optional): API 기본 URL
token_manager: 토큰 관리자 객체
use_async (bool): 비동기 클라이언트 사용 여부 (기본값: False)
"""
super().__init__(
base_url=base_url,
token_manager=token_manager,
use_async=use_async,
resource_url=resource_url
)
def theme_group_list_request_ka90001(
self,
qry_tp: str,
date_tp: str,
flu_pl_amt_tp: str,
stex_tp: str,
stk_cd: str = "",
thema_nm: str = "",
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""테마그룹별 조회를 요청합니다.
Args:
qry_tp (str): 검색구분 (0:전체검색, 1:테마검색, 2:종목검색)
date_tp (str): 날짜구분 n일전 (1일 ~ 99일 날짜입력)
flu_pl_amt_tp (str): 등락수익구분 (1:상위기간수익률, 2:하위기간수익률, 3:상위등락률, 4:하위등락률)
stex_tp (str): 거래소구분 (1:KRX, 2:NXT, 3:통합)
stk_cd (str, optional): 검색하려는 종목코드. Defaults to "".
thema_nm (str, optional): 검색하려는 테마명. Defaults to "".
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 테마그룹별 데이터
{
"thema_grp": list, # 테마그룹별 리스트
[
{
"thema_grp_cd": str, # 테마그룹코드
"thema_nm": str, # 테마명
"stk_num": str, # 종목수
"flu_sig": str, # 등락기호
"flu_rt": str, # 등락율
"rising_stk_num": str, # 상승종목수
"fall_stk_num": str, # 하락종목수
"dt_prft_rt": str, # 기간수익률
"main_stk": str, # 주요종목
}
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.theme_group_list_request_ka90001(
... qry_tp="0",
... date_tp="10",
... flu_pl_amt_tp="1",
... stex_tp="1"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka90001",
}
data = {
"qry_tp": qry_tp,
"stk_cd": stk_cd,
"date_tp": date_tp,
"thema_nm": thema_nm,
"flu_pl_amt_tp": flu_pl_amt_tp,
"stex_tp": stex_tp,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)
def theme_component_stocks_request_ka90002(
self,
thema_grp_cd: str,
stex_tp: str,
date_tp: str = "",
cont_yn: str = "N",
next_key: str = "",
) -> dict:
"""테마구성종목 조회를 요청합니다.
Args:
thema_grp_cd (str): 테마그룹코드 번호
stex_tp (str): 거래소구분 (1:KRX, 2:NXT, 3:통합)
date_tp (str, optional): 날짜구분 1일 ~ 99일 날짜입력. Defaults to "".
cont_yn (str, optional): 연속조회여부. Defaults to "N".
next_key (str, optional): 연속조회키. Defaults to "".
Returns:
dict: 테마구성종목 데이터
{
"flu_rt": str, # 등락률
"dt_prft_rt": str, # 기간수익률
"thema_comp_stk": list, # 테마구성종목 리스트
[
{
"stk_cd": str, # 종목코드
"stk_nm": str, # 종목명
"cur_prc": str, # 현재가
"flu_sig": str, # 등락기호
"pred_pre": str, # 전일대비
"flu_rt": str, # 등락율
"acc_trde_qty": str, # 누적거래량
"sel_bid": str, # 매도호가
"sel_req": str, # 매도잔량
"buy_bid": str, # 매수호가
"buy_req": str, # 매수잔량
"dt_prft_rt_n": str, # 기간수익률n
}
],
"return_code": int, # 응답코드
"return_msg": str, # 응답메시지
}
Example:
>>> from kiwoom_rest_api import KiwoomRestAPI
>>> api = KiwoomRestAPI()
>>> result = api.sector.theme_component_stocks_request_ka90002(
... thema_grp_cd="100",
... stex_tp="1",
... date_tp="2"
... )
>>> print(result)
"""
headers = {
"cont-yn": cont_yn,
"next-key": next_key,
"api-id": "ka90002",
}
data = {
"date_tp": date_tp,
"thema_grp_cd": thema_grp_cd,
"stex_tp": stex_tp,
}
return self._execute_request(
"POST",
json=data,
headers=headers,
)

View File

@@ -0,0 +1,107 @@
from typing import Dict, Optional, Any
from kiwoom_rest_api.core.sync_client import make_request
def get_trading_volume(
stock_code: str,
access_token: Optional[str] = None,
) -> Dict[str, Any]:
"""
거래량 급증 종목 (KA-STOCK-008)
Args:
stock_code: 종목코드 (6자리)
access_token: OAuth 액세스 토큰
Returns:
거래량 급증 데이터
"""
endpoint = "/stock/volume"
params = {
"FID_COND_MRKT_DIV_CODE": "J",
"FID_INPUT_ISCD": stock_code,
}
return make_request(
endpoint=endpoint,
params=params,
access_token=access_token,
)
def get_execution_price(
stock_code: str,
access_token: Optional[str] = None,
) -> Dict[str, Any]:
"""
체결가 추이 (KA-STOCK-005)
Args:
stock_code: 종목코드 (6자리)
access_token: OAuth 액세스 토큰
Returns:
체결가 추이 데이터
"""
endpoint = "/stock/execution"
params = {
"FID_COND_MRKT_DIV_CODE": "J",
"FID_INPUT_ISCD": stock_code,
}
return make_request(
endpoint=endpoint,
params=params,
access_token=access_token,
)
def get_orderbook(
stock_code: str,
access_token: Optional[str] = None,
) -> Dict[str, Any]:
"""
호가 정보 조회 (KA-STOCK-006)
Args:
stock_code: 종목코드 (6자리)
access_token: OAuth 액세스 토큰
Returns:
호가 정보 데이터
"""
endpoint = "/stock/orderbook"
params = {
"FID_COND_MRKT_DIV_CODE": "J",
"FID_INPUT_ISCD": stock_code,
}
return make_request(
endpoint=endpoint,
params=params,
access_token=access_token,
)
def get_trading_brokers(
stock_code: str,
access_token: Optional[str] = None,
) -> Dict[str, Any]:
"""
거래원 정보 조회 (KA-STOCK-007)
Args:
stock_code: 종목코드 (6자리)
access_token: OAuth 액세스 토큰
Returns:
거래원 정보 데이터
"""
endpoint = "/stock/broker"
params = {
"FID_COND_MRKT_DIV_CODE": "J",
"FID_INPUT_ISCD": stock_code,
}
return make_request(
endpoint=endpoint,
params=params,
access_token=access_token,
)

325
kiwoom_rest_api/trader.py Normal file
View File

@@ -0,0 +1,325 @@
import time
import json
import datetime
import pandas as pd
import numpy as np
import os
# =========================================================
# [Part 1] 데이터 표준화 (Data Class)
# 증권사마다 주는 데이터 이름이 다르므로, 우리 봇이 쓸 공통 이름으로 정의
# =========================================================
class StockData:
def __init__(self, code, name, price, open_p, high, low, close, volume):
self.code = code
self.name = name
self.current_price = price # 현재가
self.open = open_p # 시가
self.high = high # 고가
self.low = low # 저가
self.close = close # 종가
self.volume = volume # 거래량
class OrderbookData:
def __init__(self, total_bid, total_ask):
self.total_bid = total_bid # 총 매수 잔량 (살 사람)
self.total_ask = total_ask # 총 매도 잔량 (팔 사람)
# =========================================================
# [Part 2] 증권사 연결 인터페이스 (이 부분만 채우세요!)
# =========================================================
class BrokerAPI:
"""
[안내] 여기에 한국투자증권(KIS)이나 다른 API 코드를 연결합니다.
봇 로직은 이 함수들을 호출해서 데이터를 받아옵니다.
"""
def get_rank_list(self):
# [To-Do] 거래대금 상위 종목 리스트 반환 (API 호출)
# return [{"code": "005930", "price": 60000}, ...]
return [] # 테스트용 빈 리스트
def get_ohlcv_limit(self, code, timeframe='3m', limit=100):
# [To-Do] 특정 종목의 캔들(OHLCV) 데이터 반환 (RSI, MA 계산용)
# DataFrame 형태로 반환: columns=['open', 'high', 'low', 'close', 'volume']
return pd.DataFrame()
def get_daily_ohlc_yesterday(self, code):
# [To-Do] 어제 일봉 데이터 반환 (Dynamic K 계산용)
# return StockData(...)
pass
def get_current_data(self, code):
# [To-Do] 현재가 데이터 반환
pass
def get_orderbook(self, code):
# [To-Do] 호가창 데이터 반환
# return OrderbookData(bid, ask)
pass
def buy_market_order(self, code, qty):
# [To-Do] 시장가 매수 주문
print(f"✅ [API] {code} {qty}주 매수 주문 전송 완료")
def sell_market_order(self, code, qty):
# [To-Do] 시장가 매도 주문
print(f"✅ [API] {code} {qty}주 매도 주문 전송 완료")
# =========================================================
# [Part 3] 정훈님의 정글 서바이버 봇 (핵심 로직)
# =========================================================
class JungleSurvivorBot:
def __init__(self, broker_api, budget=100000):
self.api = broker_api # 증권사 API 객체 연결
self.budget = budget # 운용 자금 (10만원)
self.target_list = [] # 감시 종목 리스트
self.portfolio = {} # 보유 종목: {'code': {'buy_price': 1000, 'max_price': 1100, 'qty': 10}}
# --- [전략 파라미터] ---
self.rsi_period = 11 # RSI 기간 (남들 14보다 빠르게)
self.rsi_sell_std = 68 # RSI 매도 기준 (70보다 조금 낮게 선취매도)
self.trailing_gap = 0.01 # 트레일링 스탑 (고점 대비 1% 하락 시 매도)
self.target_profit = 0.035 # 목표 수익률 (3.5%면 무조건 익절)
self.stop_loss = -0.02 # 손절매 (-2%)
# -----------------------------------------------------
# [3-1] 계산기 (Indicators)
# -----------------------------------------------------
def calc_rsi(self, df):
"""RSI 지표 계산"""
delta = df['close'].diff()
gain = (delta.where(delta > 0, 0)).rolling(window=self.rsi_period).mean()
loss = (-delta.where(delta < 0, 0)).rolling(window=self.rsi_period).mean()
rs = gain / loss
return 100 - (100 / (1 + rs))
def calc_ma(self, df, period=20):
"""이동평균선 계산"""
return df['close'].rolling(window=period).mean()
def calc_dynamic_k(self, yesterday_data):
"""
변동성 돌파 K값 실시간 변경 (노이즈 비율 활용)
노이즈가 심하면 K값을 높여서 진입 장벽을 높임
"""
if yesterday_data is None: return 0.5 # 데이터 없으면 기본값
total_range = yesterday_data.high - yesterday_data.low
body_range = abs(yesterday_data.open - yesterday_data.close)
if total_range == 0: return 0.5
noise_ratio = 1 - (body_range / total_range)
# K값은 최소 0.3 ~ 최대 0.9 사이로 제한
return max(0.3, min(noise_ratio, 0.9))
# -----------------------------------------------------
# [3-2] 타겟 선정 (Dynamic Universe)
# -----------------------------------------------------
def update_universe(self):
print("\n🔄 [리스트 갱신] 실시간 주도주 스캔 중...")
raw_list = self.api.get_rank_list()
new_targets = []
for item in raw_list:
code = item['code']
price = item['price']
# [필터] 1. 가격 (2천원 ~ 5만원) / 10만원으로 살 수 있어야 함
if not (2000 <= price <= 50000) or (price > self.budget):
continue
# [필터] 2. 프로그램 매매 추이 (API 제공 시 추가)
# if item['prog_buy'] < 0: continue
new_targets.append(code)
# JSON 파일 저장 (로그용)
with open('target_universe.json', 'w') as f:
json.dump(new_targets, f)
self.target_list = new_targets
print(f"✅ 타겟 갱신 완료: {len(self.target_list)}개 종목 감시 시작")
# -----------------------------------------------------
# [3-3] 매수 판단 로직 (살까 말까?)
# -----------------------------------------------------
def check_buy(self, code):
# 1. 데이터 수집
df_candle = self.api.get_ohlcv_limit(code) # 3분봉
curr_data = self.api.get_current_data(code)
yesterday = self.api.get_daily_ohlc_yesterday(code)
orderbook = self.api.get_orderbook(code)
prog_net_buy = self.api.get_program_net_buy(code) # 프로그램 수급 확인
if prog_net_buy < 0:
print(f"🚫 {code} 패스: 프로그램 매도세 ({prog_net_buy})")
return False
if len(df_candle) < 20: return False # 데이터 부족
current_price = curr_data.current_price
# 2. 지표 계산
ma20 = self.calc_ma(df_candle, 20).iloc[-1]
k_val = self.calc_dynamic_k(yesterday)
# 3. 변동성 돌파 목표가 계산
# 목표가 = 오늘 시가 + (어제 변동폭 * K)
prev_range = yesterday.high - yesterday.low
breakout_price = curr_data.open + (prev_range * k_val)
print(f"🧐 {code} 분석: 현재가 {current_price} | 20선 {ma20:.0f} | 목표가 {breakout_price:.0f} (K:{k_val:.2f})")
# --- [매수 조건 (AND)] ---
# A. 추세: 현재가가 20일선 위에 있는가?
cond_trend = current_price >= ma20
# B. 변동성 돌파: 의미 있는 가격(목표가)을 넘었는가?
cond_breakout = current_price >= breakout_price
# C. 수급 안전판: 매수 잔량이 매도 잔량보다 2배 많은가?
cond_safe = orderbook.total_bid >= (orderbook.total_ask * 2)
if cond_trend and cond_breakout and cond_safe:
print(f"🚀 [매수 신호] {code} 발견! (Trend+Breakout+Safe)")
return True
return False
# -----------------------------------------------------
# [3-4] 매도 판단 로직 (팔까 말까?)
# -----------------------------------------------------
def check_sell(self, code):
if code not in self.portfolio: return False
info = self.portfolio[code]
buy_price = info['buy_price']
max_price = info['max_price'] # 트레일링 스탑용 고점
qty = info['qty']
curr_data = self.api.get_current_data(code)
curr_price = curr_data.current_price
# 고점 갱신 (트레일링 스탑용)
if curr_price > max_price:
self.portfolio[code]['max_price'] = curr_price
max_price = curr_price
# 지표 계산
df_candle = self.api.get_ohlcv_limit(code)
rsi = self.calc_rsi(df_candle).iloc[-1]
# 수익률 계산
profit_rate = (curr_price - buy_price) / buy_price
drop_from_high = (max_price - curr_price) / max_price
print(f"👀 {code} 보유중: 수익률 {profit_rate * 100:.2f}% | 고점대비하락 {drop_from_high * 100:.2f}% | RSI {rsi:.1f}")
# --- [매도 조건 (OR)] ---
# 1. [목표 달성] 3.5% 먹으면 묻지도 따지지도 말고 익절
if profit_rate >= self.target_profit:
print("💰 목표 수익 달성! 매도!")
return True
# 2. [트레일링 스탑] 고점 대비 1% 빠지면 익절/청산
if drop_from_high >= self.trailing_gap and profit_rate > 0:
print("📉 트레일링 스탑 발동! 매도!")
return True
# 3. [조기 퇴근] RSI가 68 넘으면 과열이므로 선취 매도
if rsi >= self.rsi_sell_std:
print("🔥 RSI 과열! 조기 매도!")
return True
# 4. [손절] -2% 살려주세요
if profit_rate <= self.stop_loss:
print("😭 손절매 실행...")
return True
return False
# -----------------------------------------------------
# [3-5] 메인 루프 (실행기)
# -----------------------------------------------------
def run(self):
print("🤖 정글 서바이버 봇 가동 시작!")
# [수정 1] 봇 켜자마자 일단 유니버스(감시 종목)부터 만들고 시작!
# 파일이 없거나 비어있을 수 있으니 강제 갱신 1회 실행
if not os.path.exists('target_universe.json'):
print("📂 타겟 파일이 없어서 새로 만듭니다...")
self.update_universe()
else:
# 파일이 있어도 비어있는지 확인
try:
with open('target_universe.json', 'r') as f:
json.load(f)
except json.JSONDecodeError:
print("📂 타겟 파일이 비어있어서 새로 채웁니다...")
self.update_universe()
while True:
# 1. 현재 시간 체크
now = datetime.datetime.now()
# 2. 유니버스 갱신 (30분마다)
if now.minute % 30 == 0 and now.second < 5:
self.update_universe()
time.sleep(5)
# 3. 매도 감시 (보유 종목 먼저 체크)
sell_list = []
for code in list(self.portfolio.keys()):
if self.check_sell(code):
# 매도 실행
qty = self.portfolio[code]['qty']
self.api.sell_market_order(code, qty)
sell_list.append(code)
# 포트폴리오에서 삭제
for code in sell_list:
del self.portfolio[code]
# 4. 매수 감시 (보유 종목 없을 때 or 자금 남을 때)
if len(self.portfolio) < 3:
# [수정 2] 읽을 때도 안전장치 추가
try:
with open('target_universe.json', 'r') as f:
targets = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
targets = [] # 에러 나면 그냥 빈 리스트로 처리
for code in targets:
if code in self.portfolio: continue # 이미 가진 건 패스
if self.check_buy(code):
# 매수 실행 (예: 10만원어치 계산)
curr_price = self.api.get_current_data(code).current_price
qty = int(self.budget / 3 / curr_price) # 자금의 1/3 투입
if qty > 0:
self.api.buy_market_order(code, qty)
# 포트폴리오 등록
self.portfolio[code] = {
'buy_price': curr_price,
'max_price': curr_price,
'qty': qty
}
break # 한 턴에 하나만 산다 (서버 부하 방지)
time.sleep(1) # 1초 대기
# =========================================================
# 실행부
# =========================================================
if __name__ == "__main__":
# 1. API 객체 생성 (나중에 키움/한투 코드로 교체될 부분)
my_broker = BrokerAPI()
# 2. 봇 생성 및 실행
bot = JungleSurvivorBot(my_broker, budget=100000)
bot.run()

View File

@@ -0,0 +1,321 @@
import asyncio
import json
import logging
from typing import Any, Callable, Dict, List, Optional, Union
import websockets
from websockets.exceptions import ConnectionClosed, WebSocketException
from .config import get_ws_url, WS_TIMEOUT
logger = logging.getLogger(__name__)
class WebSocketError(Exception):
"""Custom exception for WebSocket errors"""
def __init__(self, message: str, error_data: dict = None):
self.message = message
self.error_data = error_data or {}
super().__init__(message)
class RealTimeData:
"""실시간 데이터를 담는 클래스"""
def __init__(self, data: Dict[str, Any]):
self.raw_data = data
self.trnm = data.get('trnm')
self.return_code = data.get('return_code')
self.return_msg = data.get('return_msg')
self.data = data.get('data', [])
class WebSocketClient:
"""키움증권 실시간 웹소켓 클라이언트"""
def __init__(
self,
access_token: str,
ws_url: Optional[str] = None,
auto_reconnect: bool = True,
reconnect_interval: int = 5,
ping_interval: int = 30
):
"""
웹소켓 클라이언트 초기화
Args:
access_token: 액세스 토큰
ws_url: 웹소켓 URL (None이면 설정에서 자동 선택)
auto_reconnect: 자동 재연결 여부
reconnect_interval: 재연결 간격 (초)
ping_interval: PING 간격 (초)
"""
self.access_token = access_token
self.ws_url = ws_url or get_ws_url()
self.auto_reconnect = auto_reconnect
self.reconnect_interval = reconnect_interval
self.ping_interval = ping_interval
self.websocket: Optional[websockets.WebSocketServerProtocol] = None
self.connected = False
self.keep_running = True
self.is_logged_in = False
# 콜백 함수들
self.on_connect: Optional[Callable] = None
self.on_disconnect: Optional[Callable] = None
self.on_login: Optional[Callable] = None
self.on_data: Optional[Callable[[RealTimeData], None]] = None
self.on_error: Optional[Callable[[Exception], None]] = None
# 태스크들
self._receive_task: Optional[asyncio.Task] = None
self._ping_task: Optional[asyncio.Task] = None
async def connect(self) -> None:
"""웹소켓 서버에 연결"""
try:
logger.info(f"웹소켓 서버에 연결 중: {self.ws_url}")
self.websocket = await websockets.connect(
self.ws_url,
ping_interval=self.ping_interval,
ping_timeout=WS_TIMEOUT
)
self.connected = True
logger.info("웹소켓 서버 연결 성공")
if self.on_connect:
await self.on_connect()
except Exception as e:
logger.error(f"웹소켓 연결 실패: {e}")
self.connected = False
if self.on_error:
await self.on_error(e)
raise WebSocketError(f"연결 실패: {e}")
async def login(self) -> None:
"""웹소켓 서버에 로그인"""
if not self.connected:
await self.connect()
login_data = {
'trnm': 'LOGIN',
'token': self.access_token
}
await self.send(login_data)
logger.info("로그인 요청 전송")
async def send(self, message: Union[Dict[str, Any], str]) -> None:
"""메시지 전송"""
if not self.connected:
if self.auto_reconnect:
await self.connect()
else:
raise WebSocketError("웹소켓이 연결되지 않았습니다")
try:
if isinstance(message, dict):
message_str = json.dumps(message)
else:
message_str = message
await self.websocket.send(message_str)
logger.debug(f"메시지 전송: {message_str}")
except Exception as e:
logger.error(f"메시지 전송 실패: {e}")
if self.on_error:
await self.on_error(e)
raise WebSocketError(f"메시지 전송 실패: {e}")
async def register_realtime(
self,
group_no: str = "1",
type_list: List[str] = None,
item_list: List[str] = None,
refresh: str = "1"
) -> None:
"""
실시간 데이터 등록
Args:
group_no: 그룹 번호
type_list: 실시간 항목 리스트 (예: ['04', '0A', '0B'])
item_list: 종목코드 리스트 (빈 리스트면 전체)
refresh: 기존등록유지여부 (0: 기존유지안함, 1: 기존유지)
"""
if type_list is None:
type_list = ['04'] # 기본값: 잔고
if item_list is None:
item_list = [''] # 빈 문자열은 전체 종목
register_data = {
'trnm': 'REG',
'grp_no': group_no,
'refresh': refresh,
'data': [{
'item': item_list,
'type': type_list
}]
}
await self.send(register_data)
logger.info(f"실시간 데이터 등록: {type_list}")
async def unregister_realtime(self, group_no: str = "1") -> None:
"""실시간 데이터 해지"""
unregister_data = {
'trnm': 'REMOVE',
'grp_no': group_no
}
await self.send(unregister_data)
logger.info("실시간 데이터 해지")
async def _handle_message(self, message: str) -> None:
"""메시지 처리"""
try:
data = json.loads(message)
realtime_data = RealTimeData(data)
trnm = realtime_data.trnm
if trnm == 'LOGIN':
if realtime_data.return_code == 0:
self.is_logged_in = True
logger.info("로그인 성공")
if self.on_login:
await self.on_login()
else:
error_msg = realtime_data.return_msg or "로그인 실패"
logger.error(f"로그인 실패: {error_msg}")
if self.on_error:
await self.on_error(WebSocketError(error_msg))
elif trnm == 'PING':
# PING에 PONG으로 응답
await self.send(data)
logger.debug("PING-PONG 응답")
elif trnm == 'REAL':
# 실시간 데이터 수신
logger.debug(f"실시간 데이터 수신: {data}")
if self.on_data:
await self.on_data(realtime_data)
else:
# 기타 응답
logger.debug(f"기타 응답 수신: {data}")
if self.on_data:
await self.on_data(realtime_data)
except json.JSONDecodeError as e:
logger.error(f"JSON 파싱 오류: {e}")
if self.on_error:
await self.on_error(e)
except Exception as e:
logger.error(f"메시지 처리 오류: {e}")
if self.on_error:
await self.on_error(e)
async def _receive_messages(self) -> None:
"""메시지 수신 루프"""
while self.keep_running:
try:
if not self.connected or not self.websocket:
break
message = await self.websocket.recv()
await self._handle_message(message)
except ConnectionClosed:
logger.warning("웹소켓 연결이 종료되었습니다")
self.connected = False
self.is_logged_in = False
if self.on_disconnect:
await self.on_disconnect()
if self.auto_reconnect and self.keep_running:
logger.info(f"{self.reconnect_interval}초 후 재연결을 시도합니다")
await asyncio.sleep(self.reconnect_interval)
try:
await self.connect()
await self.login()
except Exception as e:
logger.error(f"재연결 실패: {e}")
else:
break
except Exception as e:
logger.error(f"메시지 수신 오류: {e}")
if self.on_error:
await self.on_error(e)
async def _ping_loop(self) -> None:
"""PING 루프 (연결 유지)"""
while self.keep_running and self.connected:
try:
await asyncio.sleep(self.ping_interval)
if self.connected and self.websocket:
await self.websocket.ping()
logger.debug("PING 전송")
except Exception as e:
logger.error(f"PING 오류: {e}")
async def start(self) -> None:
"""웹소켓 클라이언트 시작"""
try:
await self.connect()
await self.login()
# 수신 및 PING 태스크 시작
self._receive_task = asyncio.create_task(self._receive_messages())
self._ping_task = asyncio.create_task(self._ping_loop())
logger.info("웹소켓 클라이언트 시작됨")
except Exception as e:
logger.error(f"웹소켓 클라이언트 시작 실패: {e}")
raise
async def stop(self) -> None:
"""웹소켓 클라이언트 중지"""
logger.info("웹소켓 클라이언트 중지 중...")
self.keep_running = False
# 태스크들 취소
if self._receive_task:
self._receive_task.cancel()
if self._ping_task:
self._ping_task.cancel()
# 웹소켓 연결 종료
if self.websocket:
await self.websocket.close()
self.connected = False
self.is_logged_in = False
logger.info("웹소켓 클라이언트 중지됨")
async def run_forever(self) -> None:
"""무한 루프로 실행"""
try:
await self.start()
# 태스크들이 완료될 때까지 대기
if self._receive_task:
await self._receive_task
except asyncio.CancelledError:
logger.info("웹소켓 클라이언트가 취소되었습니다")
except Exception as e:
logger.error(f"웹소켓 클라이언트 실행 오류: {e}")
finally:
await self.stop()
def run_sync(self) -> None:
"""동기적으로 실행 (새로운 이벤트 루프에서)"""
try:
asyncio.run(self.run_forever())
except KeyboardInterrupt:
logger.info("사용자에 의해 중단되었습니다")
except Exception as e:
logger.error(f"동기 실행 오류: {e}")
raise

View File

@@ -0,0 +1,186 @@
"""
키움증권 웹소켓 실시간 데이터 타입 상수
"""
# 실시간 데이터 타입
REALTIME_TYPES = {
'04': '잔고',
'00': '주문체결',
'0A': '주식기세',
'0B': '주식체결',
'0C': '주식우선호가',
'0D': '주식호가잔량',
'0E': '주식시간외호가',
'0F': '주식당일거래원',
'0G': 'ETF NAV',
'0H': '주식예상체결',
'0J': '업종지수',
'0U': '업종등락',
'0g': '주식종목정보',
'0m': 'ELW 이론가',
'0s': '장시작시간',
'0u': 'ELW 지표',
'0w': '종목프로그램매매',
'1h': 'VI발동/해제'
}
# 실시간 데이터 필드 매핑 (잔고 04 기준)
BALANCE_FIELDS = {
'9201': '계좌번호',
'9001': '종목코드',
'917': '신용구분',
'916': '대출일',
'302': '종목명',
'10': '현재가',
'930': '보유수량',
'931': '매입단가',
'932': '총매입가',
'933': '주문가능수량',
'945': '당일순매수량',
'946': '매도/매수구분',
'950': '당일총매도손익',
'951': 'Extra Item',
'27': '매도호가',
'28': '매수호가',
'307': '기준가',
'8019': '손익률',
'957': '신용금액',
'958': '신용이자',
'918': '만기일',
'990': '당일실현손익(유가)',
'991': '당일실현손익율(유가)',
'992': '당일실현손익(신용)',
'993': '당일실현손익율(신용)',
'959': '담보대출수량',
'924': 'Extra Item'
}
# 주식체결 필드 (0B)
STOCK_TRADE_FIELDS = {
'9001': '종목코드',
'900': '종목명',
'10': '현재가',
'11': '전일대비',
'12': '등락율',
'27': '매도호가',
'28': '매수호가',
'15': '거래량',
'13': '누적거래량',
'14': '누적거래대금',
'16': '시가',
'17': '고가',
'18': '저가',
'25': '전일대비구분',
'26': '전일거래량대비',
'29': '거래대금증감',
'30': '전일거래량대비',
'31': '거래회전율',
'32': '거래비용',
'311': '종목코드명',
'567': '체결강도',
'568': '체결구분',
'569': '체결시간',
'570': '체결수량',
'571': '체결가격',
'572': '체결구분',
'573': '체결시간',
'574': '체결수량',
'575': '체결가격'
}
# 주식호가 필드 (0C)
STOCK_QUOTE_FIELDS = {
'9001': '종목코드',
'900': '종목명',
'27': '매도호가1',
'28': '매수호가1',
'29': '매도호가2',
'30': '매수호가2',
'31': '매도호가3',
'32': '매수호가3',
'33': '매도호가4',
'34': '매수호가4',
'35': '매도호가5',
'36': '매수호가5',
'37': '매도호가6',
'38': '매수호가6',
'39': '매도호가7',
'40': '매수호가7',
'41': '매도호가8',
'42': '매수호가8',
'43': '매도호가9',
'44': '매수호가9',
'45': '매도호가10',
'46': '매수호가10',
'47': '매도호가수량1',
'48': '매수호가수량1',
'49': '매도호가수량2',
'50': '매수호가수량2',
'51': '매도호가수량3',
'52': '매수호가수량3',
'53': '매도호가수량4',
'54': '매수호가수량4',
'55': '매도호가수량5',
'56': '매수호가수량5',
'57': '매도호가수량6',
'58': '매수호가수량6',
'59': '매도호가수량7',
'60': '매수호가수량7',
'61': '매도호가수량8',
'62': '매수호가수량8',
'63': '매도호가수량9',
'64': '매수호가수량9',
'65': '매도호가수량10',
'66': '매수호가수량10'
}
# 주식기세 필드 (0A)
STOCK_TREND_FIELDS = {
'9001': '종목코드',
'900': '종목명',
'10': '현재가',
'11': '전일대비',
'12': '등락율',
'27': '매도호가',
'28': '매수호가',
'15': '거래량',
'13': '누적거래량',
'14': '누적거래대금',
'16': '시가',
'17': '고가',
'18': '저가',
'25': '전일대비구분',
'26': '전일거래량대비',
'29': '거래대금증감',
'30': '전일거래량대비',
'31': '거래회전율',
'32': '거래비용',
'311': '종목코드명',
'567': '체결강도',
'568': '체결구분',
'569': '체결시간',
'570': '체결수량',
'571': '체결가격',
'572': '체결구분',
'573': '체결시간',
'574': '체결수량',
'575': '체결가격'
}
# 필드 매핑 딕셔너리
FIELD_MAPPINGS = {
'04': BALANCE_FIELDS,
'0B': STOCK_TRADE_FIELDS,
'0C': STOCK_QUOTE_FIELDS,
'0A': STOCK_TREND_FIELDS
}
def get_field_name(type_code: str, field_code: str) -> str:
"""실시간 데이터 타입과 필드 코드로 필드명을 반환"""
if type_code in FIELD_MAPPINGS:
return FIELD_MAPPINGS[type_code].get(field_code, field_code)
return field_code
def get_type_name(type_code: str) -> str:
"""실시간 데이터 타입 코드로 타입명을 반환"""
return REALTIME_TYPES.get(type_code, type_code)

View File

@@ -0,0 +1,283 @@
import asyncio
import logging
from typing import Any, Callable, Dict, List, Optional, Union
from datetime import datetime
from .websocket import WebSocketClient, RealTimeData, WebSocketError
from .websocket_constants import get_field_name, get_type_name, REALTIME_TYPES
logger = logging.getLogger(__name__)
class RealTimeDataProcessor:
"""실시간 데이터 처리기"""
def __init__(self):
self.data_handlers: Dict[str, Callable] = {}
self.balance_data: Dict[str, Dict] = {} # 계좌별 잔고 데이터
self.stock_data: Dict[str, Dict] = {} # 종목별 시세 데이터
def register_handler(self, type_code: str, handler: Callable):
"""특정 타입의 데이터 핸들러 등록"""
self.data_handlers[type_code] = handler
def process_data(self, realtime_data: RealTimeData) -> Dict[str, Any]:
"""실시간 데이터 처리"""
if realtime_data.trnm != 'REAL':
return {}
processed_data = {}
for item_data in realtime_data.data:
type_code = item_data.get('type', '')
item_code = item_data.get('item', '')
values = item_data.get('values', {})
# 데이터 타입별 처리
if type_code == '04': # 잔고
processed = self._process_balance_data(item_code, values)
self.balance_data[item_code] = processed
processed_data[item_code] = processed
elif type_code in ['0A', '0B', '0C']: # 주식 관련
processed = self._process_stock_data(type_code, item_code, values)
self.stock_data[item_code] = processed
processed_data[item_code] = processed
# 등록된 핸들러 호출
if type_code in self.data_handlers:
try:
self.data_handlers[type_code](processed_data)
except Exception as e:
logger.error(f"데이터 핸들러 오류 ({type_code}): {e}")
return processed_data
def _process_balance_data(self, item_code: str, values: Dict) -> Dict[str, Any]:
"""잔고 데이터 처리"""
processed = {
'종목코드': item_code,
'처리시간': datetime.now().isoformat(),
'데이터타입': '잔고'
}
# 필드 매핑 적용
for field_code, value in values.items():
field_name = get_field_name('04', field_code)
processed[field_name] = value
return processed
def _process_stock_data(self, type_code: str, item_code: str, values: Dict) -> Dict[str, Any]:
"""주식 데이터 처리"""
processed = {
'종목코드': item_code,
'처리시간': datetime.now().isoformat(),
'데이터타입': get_type_name(type_code)
}
# 필드 매핑 적용
for field_code, value in values.items():
field_name = get_field_name(type_code, field_code)
processed[field_name] = value
return processed
def get_balance_data(self, item_code: str = None) -> Dict:
"""잔고 데이터 조회"""
if item_code:
return self.balance_data.get(item_code, {})
return self.balance_data
def get_stock_data(self, item_code: str = None) -> Dict:
"""주식 데이터 조회"""
if item_code:
return self.stock_data.get(item_code, {})
return self.stock_data
class SimpleWebSocketClient:
"""간단한 웹소켓 클라이언트 (사용하기 쉬운 인터페이스)"""
def __init__(
self,
access_token: str,
ws_url: Optional[str] = None,
auto_reconnect: bool = True
):
self.client = WebSocketClient(
access_token=access_token,
ws_url=ws_url,
auto_reconnect=auto_reconnect
)
self.processor = RealTimeDataProcessor()
self._setup_default_handlers()
def _setup_default_handlers(self):
"""기본 핸들러 설정"""
self.client.on_data = self._on_data_received
self.client.on_connect = self._on_connected
self.client.on_login = self._on_logged_in
self.client.on_error = self._on_error
async def _on_data_received(self, realtime_data: RealTimeData):
"""데이터 수신 시 호출"""
processed_data = self.processor.process_data(realtime_data)
if processed_data:
logger.info(f"실시간 데이터 처리 완료: {len(processed_data)}개 항목")
async def _on_connected(self):
"""연결 성공 시 호출"""
logger.info("웹소켓 서버에 연결되었습니다")
async def _on_logged_in(self):
"""로그인 성공 시 호출"""
logger.info("웹소켓 서버 로그인 성공")
async def _on_error(self, error: Exception):
"""오류 발생 시 호출"""
logger.error(f"웹소켓 오류: {error}")
def register_data_handler(self, type_code: str, handler: Callable):
"""데이터 핸들러 등록"""
self.processor.register_handler(type_code, handler)
async def start(self, type_list: List[str] = None, item_list: List[str] = None):
"""웹소켓 클라이언트 시작"""
if type_list is None:
type_list = ['04'] # 기본값: 잔고
await self.client.start()
await self.client.register_realtime(type_list=type_list, item_list=item_list)
async def stop(self):
"""웹소켓 클라이언트 중지"""
await self.client.stop()
def run_sync(self, type_list: List[str] = None, item_list: List[str] = None):
"""동기적으로 실행"""
async def run():
await self.start(type_list, item_list)
await self.client.run_forever()
try:
asyncio.run(run())
except KeyboardInterrupt:
logger.info("사용자에 의해 중단되었습니다")
asyncio.run(self.stop())
def get_balance_data(self, item_code: str = None) -> Dict:
"""잔고 데이터 조회"""
return self.processor.get_balance_data(item_code)
def get_stock_data(self, item_code: str = None) -> Dict:
"""주식 데이터 조회"""
return self.processor.get_stock_data(item_code)
class WebSocketManager:
"""웹소켓 클라이언트 관리자 (여러 클라이언트 관리)"""
def __init__(self):
self.clients: Dict[str, WebSocketClient] = {}
self.tasks: Dict[str, asyncio.Task] = {}
async def add_client(
self,
client_id: str,
access_token: str,
ws_url: Optional[str] = None,
type_list: List[str] = None,
item_list: List[str] = None
) -> WebSocketClient:
"""클라이언트 추가"""
if client_id in self.clients:
raise ValueError(f"클라이언트 ID '{client_id}'가 이미 존재합니다")
client = WebSocketClient(access_token=access_token, ws_url=ws_url)
self.clients[client_id] = client
# 클라이언트 시작
await client.start()
if type_list:
await client.register_realtime(type_list=type_list, item_list=item_list)
# 태스크 생성
task = asyncio.create_task(client.run_forever())
self.tasks[client_id] = task
return client
async def remove_client(self, client_id: str):
"""클라이언트 제거"""
if client_id not in self.clients:
return
# 태스크 취소
if client_id in self.tasks:
self.tasks[client_id].cancel()
del self.tasks[client_id]
# 클라이언트 중지
await self.clients[client_id].stop()
del self.clients[client_id]
async def stop_all(self):
"""모든 클라이언트 중지"""
for client_id in list(self.clients.keys()):
await self.remove_client(client_id)
def get_client(self, client_id: str) -> Optional[WebSocketClient]:
"""클라이언트 조회"""
return self.clients.get(client_id)
def list_clients(self) -> List[str]:
"""클라이언트 목록 조회"""
return list(self.clients.keys())
# 유틸리티 함수들
def create_simple_client(
access_token: str,
ws_url: Optional[str] = None,
type_list: List[str] = None,
item_list: List[str] = None
) -> SimpleWebSocketClient:
"""간단한 웹소켓 클라이언트 생성"""
client = SimpleWebSocketClient(access_token=access_token, ws_url=ws_url)
if type_list:
for type_code in type_list:
if type_code not in REALTIME_TYPES:
logger.warning(f"알 수 없는 실시간 타입: {type_code}")
return client
def format_balance_data(balance_data: Dict) -> str:
"""잔고 데이터를 보기 좋게 포맷팅"""
if not balance_data:
return "잔고 데이터가 없습니다"
lines = []
for item_code, data in balance_data.items():
lines.append(f"종목: {data.get('종목명', item_code)}")
lines.append(f" 현재가: {data.get('현재가', 'N/A')}")
lines.append(f" 보유수량: {data.get('보유수량', 'N/A')}")
lines.append(f" 매입단가: {data.get('매입단가', 'N/A')}")
lines.append(f" 손익률: {data.get('손익률', 'N/A')}%")
lines.append("")
return "\n".join(lines)
def format_stock_data(stock_data: Dict) -> str:
"""주식 데이터를 보기 좋게 포맷팅"""
if not stock_data:
return "주식 데이터가 없습니다"
lines = []
for item_code, data in stock_data.items():
lines.append(f"종목: {data.get('종목명', item_code)}")
lines.append(f" 현재가: {data.get('현재가', 'N/A')}")
lines.append(f" 등락율: {data.get('등락율', 'N/A')}%")
lines.append(f" 거래량: {data.get('거래량', 'N/A')}")
lines.append(f" 매도호가: {data.get('매도호가', 'N/A')}")
lines.append(f" 매수호가: {data.get('매수호가', 'N/A')}")
lines.append("")
return "\n".join(lines)