initial commit
This commit is contained in:
167
한국투자증권(API)/.gitignore
vendored
Normal file
167
한국투자증권(API)/.gitignore
vendored
Normal file
@@ -0,0 +1,167 @@
|
||||
### Python template
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# pdm
|
||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||
#pdm.lock
|
||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||
# in version control.
|
||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||
.pdm.toml
|
||||
.pdm-python
|
||||
.pdm-build/
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
# PyCharm
|
||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
.idea/
|
||||
|
||||
*.lock
|
||||
|
||||
*_logs/
|
||||
77
한국투자증권(API)/MCP/Kis Trading MCP/.dockerignore
Normal file
77
한국투자증권(API)/MCP/Kis Trading MCP/.dockerignore
Normal file
@@ -0,0 +1,77 @@
|
||||
# Git 관련
|
||||
.git
|
||||
.gitignore
|
||||
|
||||
# Python 관련
|
||||
__pycache__
|
||||
*.pyc
|
||||
*.pyo
|
||||
*.pyd
|
||||
.Python
|
||||
env
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
.tox
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.log
|
||||
.git
|
||||
.mypy_cache
|
||||
.pytest_cache
|
||||
.hypothesis
|
||||
|
||||
# 개발 환경
|
||||
.venv
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
.env
|
||||
.env.*
|
||||
!.env.live
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS 관련
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# 프로젝트 특정
|
||||
tmp/
|
||||
logs/
|
||||
*.tmp
|
||||
*.log
|
||||
|
||||
# Docker 관련
|
||||
Dockerfile
|
||||
.dockerignore
|
||||
docker-compose*.yml
|
||||
|
||||
# 문서
|
||||
*.md
|
||||
!README.md
|
||||
|
||||
# 테스트
|
||||
test_*.py
|
||||
*_test.py
|
||||
tests/
|
||||
|
||||
# 기타
|
||||
.env.example
|
||||
env.example
|
||||
|
||||
*.csv
|
||||
*.tmp
|
||||
4
한국투자증권(API)/MCP/Kis Trading MCP/.env.live
Normal file
4
한국투자증권(API)/MCP/Kis Trading MCP/.env.live
Normal file
@@ -0,0 +1,4 @@
|
||||
MCP_TYPE=sse
|
||||
MCP_HOST=0.0.0.0
|
||||
MCP_PORT=3000
|
||||
MCP_PATH=/sse
|
||||
23
한국투자증권(API)/MCP/Kis Trading MCP/.gitignore
vendored
Normal file
23
한국투자증권(API)/MCP/Kis Trading MCP/.gitignore
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
# Python-generated files
|
||||
__pycache__/
|
||||
*.py[oc]
|
||||
build/
|
||||
dist/
|
||||
wheels/
|
||||
*.egg-info
|
||||
|
||||
# Virtual environments
|
||||
.venv
|
||||
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
*.log
|
||||
*_log/
|
||||
*_logs/
|
||||
tmp/
|
||||
|
||||
*.csv
|
||||
!standalone_util/*.csv
|
||||
*.tmp
|
||||
*.db
|
||||
1
한국투자증권(API)/MCP/Kis Trading MCP/.python-version
Normal file
1
한국투자증권(API)/MCP/Kis Trading MCP/.python-version
Normal file
@@ -0,0 +1 @@
|
||||
3.13
|
||||
63
한국투자증권(API)/MCP/Kis Trading MCP/Dockerfile
Normal file
63
한국투자증권(API)/MCP/Kis Trading MCP/Dockerfile
Normal file
@@ -0,0 +1,63 @@
|
||||
# Python 3.13 slim 이미지 사용
|
||||
FROM python:3.13-slim
|
||||
|
||||
# 작업 디렉토리 설정
|
||||
WORKDIR /app
|
||||
|
||||
# 시스템 패키지 업데이트 및 필요한 패키지 설치
|
||||
RUN apt-get update && apt-get install -y \
|
||||
gcc \
|
||||
g++ \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Python 의존성 설치를 위한 uv 설치
|
||||
RUN pip install uv
|
||||
|
||||
# pyproject.toml 복사 (uv.lock이 없을 수 있으므로)
|
||||
COPY pyproject.toml ./
|
||||
|
||||
# uv.lock이 있으면 복사, 없으면 의존성만 설치
|
||||
COPY uv.lock* ./
|
||||
|
||||
# 의존성 설치 (uv.lock이 있으면 frozen, 없으면 일반 설치)
|
||||
RUN if [ -f uv.lock ]; then uv sync --frozen; else uv sync; fi
|
||||
|
||||
# 애플리케이션 코드 복사
|
||||
COPY . .
|
||||
|
||||
# 환경변수 설정
|
||||
ENV ENV=live
|
||||
ENV PYTHONPATH=/app
|
||||
|
||||
# 포트 노출 (HTTP 서버용)
|
||||
EXPOSE 3000
|
||||
|
||||
# 환경변수 정의 (런타임에 설정됨)
|
||||
ENV KIS_APP_KEY=""
|
||||
ENV KIS_APP_SECRET=""
|
||||
ENV KIS_PAPER_APP_KEY=""
|
||||
ENV KIS_PAPER_APP_SECRET=""
|
||||
ENV KIS_HTS_ID=""
|
||||
ENV KIS_ACCT_STOCK=""
|
||||
ENV KIS_ACCT_FUTURE=""
|
||||
ENV KIS_PAPER_STOCK=""
|
||||
ENV KIS_PAPER_FUTURE=""
|
||||
ENV KIS_PROD_TYPE=""
|
||||
ENV KIS_URL_REST=""
|
||||
ENV KIS_URL_REST_PAPER=""
|
||||
ENV KIS_URL_WS=""
|
||||
ENV KIS_URL_WS_PAPER=""
|
||||
|
||||
# 시작 스크립트 생성
|
||||
RUN echo '#!/bin/bash\n\
|
||||
set -e\n\
|
||||
\n\
|
||||
echo "Starting KIS Trade MCP Server..."\n\
|
||||
echo "Environment: $ENV"\n\
|
||||
\n\
|
||||
# MCP 서버 시작 (HTTP 모드)\n\
|
||||
exec uv run python server.py\n\
|
||||
' > /app/start.sh && chmod +x /app/start.sh
|
||||
|
||||
# 시작 스크립트 실행
|
||||
CMD ["/app/start.sh"]
|
||||
391
한국투자증권(API)/MCP/Kis Trading MCP/Readme.md
Normal file
391
한국투자증권(API)/MCP/Kis Trading MCP/Readme.md
Normal file
@@ -0,0 +1,391 @@
|
||||
# 중요 : MCP에 대한 내용을 완전히 숙지하신 뒤 사용해 주십시오.
|
||||
# 이 프로그램을 실행하여 발생한 모든 책임은 사용자 본인에게 있습니다.
|
||||
|
||||
# 한국투자증권 OPEN API MCP 서버 - Docker 설치 가이드
|
||||
|
||||
한국투자증권의 다양한 금융 API를 Docker를 통해 Claude Desktop에서 쉽게 사용할 수 있도록 하는 설치 가이드입니다.
|
||||
|
||||
## 🚀 주요 기능
|
||||
|
||||
### 지원하는 API 카테고리
|
||||
|
||||
| 카테고리 | 개수 | 주요 기능 |
|
||||
|---------|------|----------|
|
||||
| 국내주식 | 74개 | 현재가, 호가, 차트, 잔고, 주문, 순위분석, 시세분석, 종목정보, 실시간시세 등 |
|
||||
| 해외주식 | 34개 | 미국/아시아 주식 시세, 잔고, 주문, 체결내역, 거래량순위, 권리종합 등 |
|
||||
| 국내선물옵션 | 20개 | 선물옵션 시세, 호가, 차트, 잔고, 주문, 야간거래, 실시간체결 등 |
|
||||
| 해외선물옵션 | 19개 | 해외선물 시세, 주문내역, 증거금, 체결추이, 옵션호가 등 |
|
||||
| 국내채권 | 14개 | 채권 시세, 호가, 발행정보, 잔고조회, 주문체결내역 등 |
|
||||
| ETF/ETN | 2개 | NAV 비교추이, 현재가 등 |
|
||||
| ELW | 1개 | ELW 거래량순위 |
|
||||
|
||||
**전체 API 총합계: 166개**
|
||||
|
||||
### 핵심 특징
|
||||
- 🐳 **Docker 컨테이너화**: 완전 격리된 환경에서 안전한 실행
|
||||
- ⚡ **동적 코드 실행**: GitHub에서 실시간으로 API 코드를 다운로드하여 실행
|
||||
- 🔧 **설정 기반**: JSON 파일로 API 설정 및 파라미터 관리
|
||||
- 🛡️ **안전한 실행**: 격리된 임시 환경에서 코드 실행
|
||||
- 🔍 **검증 기능**: API 상세 정보 조회로 파라미터 확인
|
||||
- 🌍 **환경 지원**: 실전/모의 환경 구분 지원
|
||||
- 🔐 **자동 설정**: 서버 시작 시 KIS 인증 설정 자동 생성
|
||||
- 🖥️ **크로스 플랫폼**: Windows, macOS, Linux 모두 지원
|
||||
|
||||
## 📦 Docker 설치 및 설정
|
||||
|
||||
### 📋 Docker 설치
|
||||
|
||||
#### 🚀 빠른 설치 (권장)
|
||||
**공식 Docker Desktop을 사용하세요:**
|
||||
- [Docker Desktop for Mac](https://www.docker.com/products/docker-desktop/)
|
||||
- [Docker Desktop for Windows](https://www.docker.com/products/docker-desktop/)
|
||||
- [Docker Engine for Linux](https://docs.docker.com/engine/install/)
|
||||
|
||||
#### 📋 OS별 간단 가이드
|
||||
|
||||
##### 🍎 **macOS**
|
||||
```bash
|
||||
# Homebrew 사용 (권장)
|
||||
brew install --cask docker
|
||||
|
||||
# 또는 공식 인스톨러 다운로드
|
||||
# https://www.docker.com/products/docker-desktop/
|
||||
```
|
||||
|
||||
##### 🐧 **Linux (Ubuntu/Debian)**
|
||||
```bash
|
||||
# 공식 스크립트 사용 (권장)
|
||||
curl -fsSL https://get.docker.com -o get-docker.sh
|
||||
sudo sh get-docker.sh
|
||||
|
||||
# 사용자를 docker 그룹에 추가
|
||||
sudo usermod -aG docker $USER
|
||||
```
|
||||
|
||||
##### 🪟 **Windows**
|
||||
**⚠️ Windows는 추가 설정이 필요합니다:**
|
||||
|
||||
1. **시스템 요구사항 확인**
|
||||
- Windows 10/11 Pro, Enterprise, Education
|
||||
- WSL2 또는 Hyper-V 지원
|
||||
|
||||
2. **Docker Desktop 설치**
|
||||
- [공식 사이트](https://www.docker.com/products/docker-desktop/)에서 다운로드
|
||||
- 설치 중 "Use WSL 2" 옵션 선택 권장
|
||||
|
||||
3. **설치 후 확인**
|
||||
```cmd
|
||||
docker --version
|
||||
docker run hello-world
|
||||
```
|
||||
|
||||
**Windows 상세 설치 가이드**: [Docker 공식 문서](https://docs.docker.com/desktop/install/windows-install/) 참조
|
||||
|
||||
### 요구사항
|
||||
- Docker 20.10+
|
||||
- 한국투자증권 OPEN API 계정
|
||||
|
||||
### 📋 설치 및 설정 단계
|
||||
|
||||
#### **1단계: 프로젝트 클론**
|
||||
```bash
|
||||
# 프로젝트 클론
|
||||
git clone https://github.com/koreainvestment/open-trading-api.git
|
||||
cd "open-trading-api/MCP/Kis Trading MCP"
|
||||
```
|
||||
|
||||
#### **2단계: 한국투자증권 API 정보 준비**
|
||||
한국투자증권 개발자 센터에서 발급받은 정보를 준비하세요:
|
||||
|
||||
**필수 정보:**
|
||||
- App Key (실전용)
|
||||
- App Secret (실전용)
|
||||
- 계좌 정보들
|
||||
|
||||
**선택 정보:**
|
||||
- App Key (모의용)
|
||||
- App Secret (모의용)
|
||||
|
||||
#### **3단계: Docker 이미지 빌드**
|
||||
```bash
|
||||
# Docker 이미지 빌드
|
||||
docker build -t kis-trade-mcp .
|
||||
|
||||
# 또는 태그와 함께 빌드
|
||||
docker build -t kis-trade-mcp:latest .
|
||||
```
|
||||
|
||||
#### **4단계: Docker 컨테이너 실행**
|
||||
|
||||
**기본 실행:**
|
||||
```bash
|
||||
docker run -d \
|
||||
--name kis-trade-mcp \
|
||||
-p 3000:3000 \
|
||||
-e KIS_APP_KEY="your_app_key" \
|
||||
-e KIS_APP_SECRET="your_app_secret" \
|
||||
-e KIS_PAPER_APP_KEY="your_paper_app_key" \
|
||||
-e KIS_PAPER_APP_SECRET="your_paper_app_secret" \
|
||||
-e KIS_HTS_ID="your_hts_id" \
|
||||
-e KIS_ACCT_STOCK="12345678" \
|
||||
-e KIS_ACCT_FUTURE="87654321" \
|
||||
-e KIS_PAPER_STOCK="11111111" \
|
||||
-e KIS_PAPER_FUTURE="22222222" \
|
||||
-e KIS_PROD_TYPE="01" \
|
||||
kis-trade-mcp
|
||||
```
|
||||
|
||||
#### **5단계: 컨테이너 상태 확인**
|
||||
```bash
|
||||
# 컨테이너 상태 확인
|
||||
docker ps
|
||||
|
||||
# 컨테이너 로그 확인
|
||||
docker logs kis-trade-mcp
|
||||
|
||||
# 실시간 로그 확인
|
||||
docker logs -f kis-trade-mcp
|
||||
|
||||
# HTTP 서버 접근 확인
|
||||
curl http://localhost:3000/sse
|
||||
```
|
||||
|
||||
#### **6단계: HTTP 서버 접근 확인**
|
||||
컨테이너가 정상적으로 실행되면 HTTP 서버에 접근할 수 있습니다:
|
||||
|
||||
```bash
|
||||
# 서버 상태 확인
|
||||
curl http://localhost:3000/sse
|
||||
|
||||
# 또는 브라우저에서 접근
|
||||
# http://localhost:3000/sse
|
||||
```
|
||||
|
||||
### 🔗 Claude Desktop 연동 및 설정
|
||||
|
||||
#### 📝 Claude Desktop 설정
|
||||
Claude Desktop 설정 파일에 MCP 서버를 등록하세요.
|
||||
|
||||
**설정 파일 위치:**
|
||||
- **Linux/Mac**: `~/.claude_desktop_config.json`
|
||||
- **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
|
||||
|
||||
#### 🐧 Linux/Mac 설정
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"kis-trade-mcp": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "mcp-remote", "http://localhost:3000/sse"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 🪟 Windows 설정
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"kis-trade-mcp": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "mcp-remote", "http://localhost:3000/sse"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 💬 사용법 및 질문 예시
|
||||
|
||||
### 기본 사용 패턴
|
||||
|
||||
1. **종목 검색**: 먼저 종목 코드를 찾습니다
|
||||
2. **API 확인**: 사용할 API의 파라미터를 확인합니다
|
||||
3. **API 호출**: 필요한 파라미터와 함께 API를 호출합니다
|
||||
|
||||
### 질문 예시
|
||||
|
||||
**주식 시세 조회:**
|
||||
- "삼성전자(005930) 현재가 시세 조회해줘"
|
||||
- "애플(AAPL) 해외주식 현재 체결가 알려줘"
|
||||
- "삼성전자 종목코드 찾아줘"
|
||||
|
||||
**잔고 및 계좌:**
|
||||
- "국내주식 잔고 조회해줘"
|
||||
- "해외주식 잔고 확인해줘"
|
||||
|
||||
**채권 및 기타:**
|
||||
- "국고채 3년물 호가 정보 조회하는 방법"
|
||||
- "KODEX 200 ETF(069500) NAV 비교추이 확인해줘"
|
||||
|
||||
**모의투자:**
|
||||
- "모의투자로 삼성전자 현재가 조회해줘"
|
||||
- "데모 환경에서 애플 주식 시세 알려줘"
|
||||
|
||||
## 🔧 컨테이너 관리
|
||||
|
||||
### 컨테이너 제어
|
||||
```bash
|
||||
# 컨테이너 시작
|
||||
docker start kis-trade-mcp
|
||||
|
||||
# 컨테이너 중지
|
||||
docker stop kis-trade-mcp
|
||||
|
||||
# 컨테이너 재시작
|
||||
docker restart kis-trade-mcp
|
||||
|
||||
# 컨테이너 제거
|
||||
docker stop kis-trade-mcp
|
||||
docker rm kis-trade-mcp
|
||||
```
|
||||
|
||||
### 컨테이너 내부 접근
|
||||
```bash
|
||||
# 컨테이너 내부 bash 실행
|
||||
docker exec -it kis-trade-mcp /bin/bash
|
||||
|
||||
# 환경변수 확인
|
||||
docker exec kis-trade-mcp env | grep KIS
|
||||
|
||||
# 로그 실시간 확인
|
||||
docker logs -f kis-trade-mcp
|
||||
```
|
||||
|
||||
## 💡 사용 팁
|
||||
|
||||
1. **환경변수 관리**: 민감한 정보는 환경변수로 안전하게 관리
|
||||
2. **로그 모니터링**: `docker logs -f`로 실시간 로그 확인
|
||||
3. **리소스 모니터링**: `docker stats`로 컨테이너 리소스 사용량 확인
|
||||
4. **백업 전략**: 중요한 설정 파일은 정기적으로 백업
|
||||
5. **보안 관리**: 컨테이너 내부에서만 민감한 정보 처리
|
||||
|
||||
## 📝 로깅 및 모니터링
|
||||
|
||||
### 로그 확인
|
||||
```bash
|
||||
# 전체 로그
|
||||
docker logs kis-trade-mcp
|
||||
|
||||
# 최근 100줄
|
||||
docker logs --tail 100 kis-trade-mcp
|
||||
|
||||
# 실시간 로그
|
||||
docker logs -f kis-trade-mcp
|
||||
|
||||
# 특정 시간대 로그
|
||||
docker logs --since "2024-01-01T00:00:00" kis-trade-mcp
|
||||
```
|
||||
|
||||
### 성능 모니터링
|
||||
```bash
|
||||
# 컨테이너 리소스 사용량
|
||||
docker stats kis-trade-mcp
|
||||
|
||||
# 컨테이너 상세 정보
|
||||
docker inspect kis-trade-mcp
|
||||
|
||||
# 프로세스 확인
|
||||
docker exec kis-trade-mcp ps aux
|
||||
```
|
||||
|
||||
## 🛠️ 문제 해결
|
||||
|
||||
### 일반적인 문제들
|
||||
|
||||
**1. 컨테이너가 시작되지 않는 경우**
|
||||
```bash
|
||||
# 로그 확인
|
||||
docker logs kis-trade-mcp
|
||||
|
||||
# 환경변수 확인
|
||||
docker exec kis-trade-mcp env | grep KIS
|
||||
```
|
||||
|
||||
**2. 환경변수 누락**
|
||||
```bash
|
||||
# 컨테이너 재시작
|
||||
docker restart kis-trade-mcp
|
||||
|
||||
# 환경변수 다시 설정하여 실행
|
||||
docker run -d --name kis-trade-mcp -e KIS_APP_KEY="..." ...
|
||||
```
|
||||
|
||||
**3. 메모리 부족**
|
||||
```bash
|
||||
# 메모리 사용량 확인
|
||||
docker stats kis-trade-mcp
|
||||
|
||||
# 컨테이너 리소스 제한 설정
|
||||
docker run -d --name kis-trade-mcp --memory="2g" --cpus="2" ...
|
||||
```
|
||||
|
||||
**4. 네트워크 연결 문제**
|
||||
```bash
|
||||
# 포트 확인
|
||||
docker port kis-trade-mcp
|
||||
|
||||
# 네트워크 연결 테스트
|
||||
curl http://localhost:3000/sse
|
||||
```
|
||||
|
||||
### 디버깅 명령어
|
||||
```bash
|
||||
# 컨테이너 내부 bash 접근
|
||||
docker exec -it kis-trade-mcp /bin/bash
|
||||
|
||||
# Python 환경 확인
|
||||
docker exec kis-trade-mcp uv run python -c "import sys; print(sys.path)"
|
||||
|
||||
# 의존성 확인
|
||||
docker exec kis-trade-mcp uv pip list
|
||||
|
||||
# 네트워크 연결 확인
|
||||
docker exec kis-trade-mcp ping github.com
|
||||
```
|
||||
|
||||
## 🔒 보안 고려사항
|
||||
|
||||
- **컨테이너 격리**: 호스트 시스템과 완전히 분리된 환경에서 실행
|
||||
- **환경변수 보안**: 민감한 정보는 환경변수로 전달, 코드에 하드코딩 금지
|
||||
- **임시 파일 정리**: 각 API 호출 후 임시 파일 자동 삭제
|
||||
- **네트워크 격리**: 필요한 경우 Docker 네트워크를 통한 추가 격리
|
||||
|
||||
## ⚠️ 제한사항 및 성능
|
||||
|
||||
### API 호출 제한
|
||||
- 한국투자증권 API의 호출 제한을 준수해야 합니다
|
||||
- 분당 호출 횟수 제한이 있을 수 있습니다
|
||||
- 실전 환경에서는 더욱 신중한 사용이 필요합니다
|
||||
|
||||
### Docker 성능 고려사항
|
||||
- **컨테이너 오버헤드**: Docker 컨테이너 실행으로 인한 약간의 성능 오버헤드
|
||||
- **메모리 사용량**: SQLAlchemy와 pandas가 메모리를 많이 사용할 수 있음
|
||||
- **네트워크 지연**: GitHub 다운로드 시 네트워크 지연 발생
|
||||
|
||||
### 다단계 타임아웃 설정
|
||||
- 파일 다운로드: 30초 (GitHub 응답 대기)
|
||||
- 코드 실행: 15초 (API 호출 및 결과 처리)
|
||||
- 컨테이너 시작: 60초 (의존성 설치 및 초기화)
|
||||
|
||||
## 🔗 관련 링크
|
||||
|
||||
- [한국투자증권 개발자 센터](https://apiportal.koreainvestment.com/)
|
||||
- [한국투자증권 OPEN API GitHub](https://github.com/koreainvestment/open-trading-api)
|
||||
- [MCP (Model Context Protocol) 공식 문서](https://modelcontextprotocol.io/)
|
||||
- [Docker 공식 문서](https://docs.docker.com/)
|
||||
|
||||
---
|
||||
|
||||
**주의**: 이 프로젝트는 한국투자증권 OPEN API를 사용합니다. 사용 전 반드시 [한국투자증권 개발자 센터](https://apiportal.koreainvestment.com/)에서 API 이용약관을 확인하시기 바랍니다.
|
||||
|
||||
## ⚠️ 투자 책임 고지
|
||||
|
||||
**본 MCP 서버는 한국투자증권 OPEN API를 활용한 도구일 뿐이며, 투자 조언이나 권유를 제공하지 않습니다.**
|
||||
|
||||
- 📈 **투자 결정 책임**: 모든 투자 결정과 그에 따른 손익은 전적으로 투자자 본인의 책임입니다
|
||||
- 💰 **손실 위험**: 주식, 선물, 옵션 등 모든 금융상품 투자에는 원금 손실 위험이 있습니다
|
||||
- 🔍 **정보 검증**: API를 통해 제공되는 정보의 정확성은 한국투자증권에 의존하며, 투자 전 반드시 정보를 검증하시기 바랍니다
|
||||
- 🧠 **신중한 판단**: 충분한 조사와 신중한 판단 없이 투자하지 마시기 바랍니다
|
||||
- 🎯 **모의투자 권장**: 실전 투자 전 반드시 모의투자를 통해 충분히 연습하시기 바랍니다
|
||||
|
||||
**투자는 본인의 판단과 책임 하에 이루어져야 하며, 본 도구 사용으로 인한 어떠한 손실에 대해서도 개발자는 책임지지 않습니다.**
|
||||
105
한국투자증권(API)/MCP/Kis Trading MCP/configs/auth.json
Normal file
105
한국투자증권(API)/MCP/Kis Trading MCP/configs/auth.json
Normal file
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"tool_info": {
|
||||
"introduce": "한국투자증권의 auth OPEN API를 활용합니다.",
|
||||
"introduce_append": "",
|
||||
"examples": [
|
||||
{
|
||||
"api_type": "auth_token",
|
||||
"params": {
|
||||
"grant_type": "client_credentials",
|
||||
"env_dv": "real"
|
||||
}
|
||||
},
|
||||
{
|
||||
"api_type": "auth_ws_token",
|
||||
"params": {
|
||||
"grant_type": "client_credentials",
|
||||
"env_dv": "real"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"apis": {
|
||||
"auth_token": {
|
||||
"category": "OAuth인증",
|
||||
"name": "접근토큰발급(P)",
|
||||
"github_url": "https://github.com/koreainvestment/open-trading-api/tree/main/examples_llm/auth/auth_token",
|
||||
"method": "auth_token",
|
||||
"api_path": "/oauth2/tokenP",
|
||||
"params": {
|
||||
"grant_type": {
|
||||
"name": "grant_type",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "[필수] 권한부여 Type (client_credentials)"
|
||||
},
|
||||
"appkey": {
|
||||
"name": "appkey",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "[필수] 앱키 (한국투자증권 홈페이지에서 발급받은 appkey)"
|
||||
},
|
||||
"appsecret": {
|
||||
"name": "appsecret",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "[필수] 앱시크릿키 (한국투자증권 홈페이지에서 발급받은 appsecret)"
|
||||
},
|
||||
"env_dv": {
|
||||
"name": "env_dv",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "[필수] 환경구분 (real: 실전, demo: 모의)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"auth_ws_token": {
|
||||
"category": "OAuth인증",
|
||||
"name": "실시간 (웹소켓) 접속키 발급",
|
||||
"github_url": "https://github.com/koreainvestment/open-trading-api/tree/main/examples_llm/auth/auth_ws_token",
|
||||
"method": "auth_ws_token",
|
||||
"api_path": "/oauth2/Approval",
|
||||
"params": {
|
||||
"grant_type": {
|
||||
"name": "grant_type",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "[필수] 권한부여 Type (client_credentials)"
|
||||
},
|
||||
"appkey": {
|
||||
"name": "appkey",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "[필수] 고객 앱Key (한국투자증권 홈페이지에서 발급받은 appkey)"
|
||||
},
|
||||
"appsecret": {
|
||||
"name": "appsecret",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "[필수] 고객 앱Secret (한국투자증권 홈페이지에서 발급받은 appsecret)"
|
||||
},
|
||||
"env_dv": {
|
||||
"name": "env_dv",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "[필수] 환경구분 (real: 실전, demo: 모의)"
|
||||
},
|
||||
"token": {
|
||||
"name": "token",
|
||||
"type": "str",
|
||||
"required": false,
|
||||
"default_value": "",
|
||||
"description": "접근토큰 (OAuth 토큰이 필요한 API 경우 발급한 Access token)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1022
한국투자증권(API)/MCP/Kis Trading MCP/configs/domestic_bond.json
Normal file
1022
한국투자증권(API)/MCP/Kis Trading MCP/configs/domestic_bond.json
Normal file
File diff suppressed because it is too large
Load Diff
1311
한국투자증권(API)/MCP/Kis Trading MCP/configs/domestic_futureoption.json
Normal file
1311
한국투자증권(API)/MCP/Kis Trading MCP/configs/domestic_futureoption.json
Normal file
File diff suppressed because it is too large
Load Diff
4881
한국투자증권(API)/MCP/Kis Trading MCP/configs/domestic_stock.json
Normal file
4881
한국투자증권(API)/MCP/Kis Trading MCP/configs/domestic_stock.json
Normal file
File diff suppressed because it is too large
Load Diff
172
한국투자증권(API)/MCP/Kis Trading MCP/configs/elw.json
Normal file
172
한국투자증권(API)/MCP/Kis Trading MCP/configs/elw.json
Normal file
@@ -0,0 +1,172 @@
|
||||
{
|
||||
"tool_info": {
|
||||
"introduce": "한국투자증권의 ELW OPEN API를 활용합니다.",
|
||||
"introduce_append": "이 도구는 ELW 관련 시세 정보를 제공합니다.",
|
||||
"examples": [
|
||||
{
|
||||
"api_type": "volume_rank",
|
||||
"params": {
|
||||
"fid_cond_mrkt_div_code": "W",
|
||||
"fid_cond_scr_div_code": "20278",
|
||||
"fid_unas_input_iscd": "000000",
|
||||
"fid_input_iscd": "00000",
|
||||
"fid_input_rmnn_dynu_1": "",
|
||||
"fid_div_cls_code": "0",
|
||||
"fid_input_price_1": "1000",
|
||||
"fid_input_price_2": "5000",
|
||||
"fid_input_vol_1": "100",
|
||||
"fid_input_vol_2": "1000",
|
||||
"fid_input_date_1": "20230101",
|
||||
"fid_rank_sort_cls_code": "0",
|
||||
"fid_blng_cls_code": "0",
|
||||
"fid_input_iscd_2": "0000",
|
||||
"fid_input_date_2": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"apis": {
|
||||
"volume_rank": {
|
||||
"category": "[국내주식] ELW시세",
|
||||
"name": "ELW 거래량순위",
|
||||
"github_url": "https://github.com/koreainvestment/open-trading-api/tree/main/examples_llm/elw/volume_rank",
|
||||
"method": "volume_rank",
|
||||
"api_path": "/uapi/elw/v1/ranking/volume-rank",
|
||||
"params": {
|
||||
"fid_cond_mrkt_div_code": {
|
||||
"name": "fid_cond_mrkt_div_code",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "조건시장분류코드"
|
||||
},
|
||||
"fid_cond_scr_div_code": {
|
||||
"name": "fid_cond_scr_div_code",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "조건화면분류코드"
|
||||
},
|
||||
"fid_unas_input_iscd": {
|
||||
"name": "fid_unas_input_iscd",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "기초자산입력종목코드"
|
||||
},
|
||||
"fid_input_iscd": {
|
||||
"name": "fid_input_iscd",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "발행사"
|
||||
},
|
||||
"fid_input_rmnn_dynu_1": {
|
||||
"name": "fid_input_rmnn_dynu_1",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "입력잔존일수"
|
||||
},
|
||||
"fid_div_cls_code": {
|
||||
"name": "fid_div_cls_code",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "콜풋구분코드"
|
||||
},
|
||||
"fid_input_price_1": {
|
||||
"name": "fid_input_price_1",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "가격(이상)"
|
||||
},
|
||||
"fid_input_price_2": {
|
||||
"name": "fid_input_price_2",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "가격(이하)"
|
||||
},
|
||||
"fid_input_vol_1": {
|
||||
"name": "fid_input_vol_1",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "거래량(이상)"
|
||||
},
|
||||
"fid_input_vol_2": {
|
||||
"name": "fid_input_vol_2",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "거래량(이하)"
|
||||
},
|
||||
"fid_input_date_1": {
|
||||
"name": "fid_input_date_1",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "조회기준일"
|
||||
},
|
||||
"fid_rank_sort_cls_code": {
|
||||
"name": "fid_rank_sort_cls_code",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "순위정렬구분코드"
|
||||
},
|
||||
"fid_blng_cls_code": {
|
||||
"name": "fid_blng_cls_code",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "소속구분코드"
|
||||
},
|
||||
"fid_input_iscd_2": {
|
||||
"name": "fid_input_iscd_2",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "LP발행사"
|
||||
},
|
||||
"fid_input_date_2": {
|
||||
"name": "fid_input_date_2",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "만기일-최종거래일조회"
|
||||
},
|
||||
"tr_cont": {
|
||||
"name": "tr_cont",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": "",
|
||||
"description": "연속 거래 여부"
|
||||
},
|
||||
"dataframe": {
|
||||
"name": "dataframe",
|
||||
"type": "pd.DataFrame",
|
||||
"required": false,
|
||||
"default_value": null,
|
||||
"description": "누적 데이터프레임"
|
||||
},
|
||||
"depth": {
|
||||
"name": "depth",
|
||||
"type": "int",
|
||||
"required": true,
|
||||
"default_value": 0,
|
||||
"description": "현재 재귀 깊이"
|
||||
},
|
||||
"max_depth": {
|
||||
"name": "max_depth",
|
||||
"type": "int",
|
||||
"required": true,
|
||||
"default_value": 10,
|
||||
"description": "최대 재귀 깊이 (기본값: 10)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
한국투자증권(API)/MCP/Kis Trading MCP/configs/etfetn.json
Normal file
70
한국투자증권(API)/MCP/Kis Trading MCP/configs/etfetn.json
Normal file
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"tool_info": {
|
||||
"introduce": "한국투자증권의 ETF/ETN OPEN API를 활용합니다.",
|
||||
"introduce_append": "",
|
||||
"examples": [
|
||||
{
|
||||
"api_type": "inquire_price",
|
||||
"params": {
|
||||
"fid_cond_mrkt_div_code": "J",
|
||||
"fid_input_iscd": "123456"
|
||||
}
|
||||
},
|
||||
{
|
||||
"api_type": "nav_comparison_trend",
|
||||
"params": {
|
||||
"fid_cond_mrkt_div_code": "J",
|
||||
"fid_input_iscd": "069500"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"apis": {
|
||||
"inquire_price": {
|
||||
"category": "[국내주식] 기본시세",
|
||||
"name": "ETF/ETN 현재가",
|
||||
"github_url": "https://github.com/koreainvestment/open-trading-api/tree/main/examples_llm/etfetn/inquire_price",
|
||||
"method": "inquire_price",
|
||||
"api_path": "/uapi/etfetn/v1/quotations/inquire-price",
|
||||
"params": {
|
||||
"fid_cond_mrkt_div_code": {
|
||||
"name": "fid_cond_mrkt_div_code",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "[필수] 조건 시장 분류 코드 (ex. J:KRX, NX:NXT, UN:통합)"
|
||||
},
|
||||
"fid_input_iscd": {
|
||||
"name": "fid_input_iscd",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "[필수] 입력 종목코드 (ex. 123456)"
|
||||
}
|
||||
}
|
||||
},
|
||||
"nav_comparison_trend": {
|
||||
"category": "[국내주식] 기본시세",
|
||||
"name": "NAV 비교추이(종목)",
|
||||
"github_url": "https://github.com/koreainvestment/open-trading-api/tree/main/examples_llm/etfetn/nav_comparison_trend",
|
||||
"method": "nav_comparison_trend",
|
||||
"api_path": "/uapi/etfetn/v1/quotations/nav-comparison-trend",
|
||||
"params": {
|
||||
"fid_cond_mrkt_div_code": {
|
||||
"name": "fid_cond_mrkt_div_code",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "[필수] 조건 시장 분류 코드 (ex. J)"
|
||||
},
|
||||
"fid_input_iscd": {
|
||||
"name": "fid_input_iscd",
|
||||
"type": "str",
|
||||
"required": true,
|
||||
"default_value": null,
|
||||
"description": "[필수] 입력 종목코드"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1670
한국투자증권(API)/MCP/Kis Trading MCP/configs/overseas_futureoption.json
Normal file
1670
한국투자증권(API)/MCP/Kis Trading MCP/configs/overseas_futureoption.json
Normal file
File diff suppressed because it is too large
Load Diff
2914
한국투자증권(API)/MCP/Kis Trading MCP/configs/overseas_stock.json
Normal file
2914
한국투자증권(API)/MCP/Kis Trading MCP/configs/overseas_stock.json
Normal file
File diff suppressed because it is too large
Load Diff
29
한국투자증권(API)/MCP/Kis Trading MCP/model/__init__.py
Normal file
29
한국투자증권(API)/MCP/Kis Trading MCP/model/__init__.py
Normal file
@@ -0,0 +1,29 @@
|
||||
from .base import Base
|
||||
from .updated import Updated
|
||||
|
||||
# 툴별 마스터 모델들
|
||||
from .domestic_stock import DomesticStockMaster
|
||||
from .overseas_stock import OverseasStockMaster
|
||||
from .domestic_futureoption import DomesticFutureoptionMaster
|
||||
from .overseas_futureoption import OverseasFutureoptionMaster
|
||||
from .domestic_bond import DomesticBondMaster
|
||||
from .etfetn import EtfetnMaster
|
||||
from .elw import ElwMaster
|
||||
from .auth import AuthMaster
|
||||
|
||||
|
||||
# 모든 모델들을 리스트로 제공
|
||||
ALL_MODELS = [
|
||||
# 툴별 마스터 모델들
|
||||
DomesticStockMaster,
|
||||
OverseasStockMaster,
|
||||
DomesticFutureoptionMaster,
|
||||
OverseasFutureoptionMaster,
|
||||
DomesticBondMaster,
|
||||
EtfetnMaster,
|
||||
ElwMaster,
|
||||
AuthMaster,
|
||||
|
||||
# 업데이트 상태 추적
|
||||
Updated
|
||||
]
|
||||
11
한국투자증권(API)/MCP/Kis Trading MCP/model/auth.py
Normal file
11
한국투자증권(API)/MCP/Kis Trading MCP/model/auth.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from .base import Base
|
||||
|
||||
|
||||
class AuthMaster(Base):
|
||||
"""인증 마스터"""
|
||||
__tablename__ = 'auth_master'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50), index=True) # 종목명
|
||||
code = Column(String(50), index=True) # 종목코드
|
||||
5
한국투자증권(API)/MCP/Kis Trading MCP/model/base.py
Normal file
5
한국투자증권(API)/MCP/Kis Trading MCP/model/base.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from sqlalchemy.orm import declarative_base
|
||||
|
||||
# SQLAlchemy Base 클래스
|
||||
Base = declarative_base()
|
||||
|
||||
12
한국투자증권(API)/MCP/Kis Trading MCP/model/domestic_bond.py
Normal file
12
한국투자증권(API)/MCP/Kis Trading MCP/model/domestic_bond.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from .base import Base
|
||||
|
||||
|
||||
class DomesticBondMaster(Base):
|
||||
"""국내채권 마스터"""
|
||||
__tablename__ = 'domestic_bond_master'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50), index=True) # 종목명
|
||||
code = Column(String(50), index=True) # 종목코드
|
||||
ex = Column(String(30), index=True) # 거래소 코드
|
||||
@@ -0,0 +1,12 @@
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from .base import Base
|
||||
|
||||
|
||||
class DomesticFutureoptionMaster(Base):
|
||||
"""국내선물옵션 마스터"""
|
||||
__tablename__ = 'domestic_futureoption_master'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50), index=True) # 종목명
|
||||
code = Column(String(50), index=True) # 종목코드
|
||||
ex = Column(String(30), index=True) # 거래소 코드
|
||||
12
한국투자증권(API)/MCP/Kis Trading MCP/model/domestic_stock.py
Normal file
12
한국투자증권(API)/MCP/Kis Trading MCP/model/domestic_stock.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from .base import Base
|
||||
|
||||
|
||||
class DomesticStockMaster(Base):
|
||||
"""국내주식 마스터"""
|
||||
__tablename__ = 'domestic_stock_master'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50), index=True) # 종목명
|
||||
code = Column(String(50), index=True) # 종목코드
|
||||
ex = Column(String(30), index=True) # 거래소 코드
|
||||
12
한국투자증권(API)/MCP/Kis Trading MCP/model/elw.py
Normal file
12
한국투자증권(API)/MCP/Kis Trading MCP/model/elw.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from .base import Base
|
||||
|
||||
|
||||
class ElwMaster(Base):
|
||||
"""ELW 마스터"""
|
||||
__tablename__ = 'elw_master'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50), index=True) # 종목명
|
||||
code = Column(String(50), index=True) # 종목코드
|
||||
ex = Column(String(30), index=True) # 거래소 코드
|
||||
12
한국투자증권(API)/MCP/Kis Trading MCP/model/etfetn.py
Normal file
12
한국투자증권(API)/MCP/Kis Trading MCP/model/etfetn.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from .base import Base
|
||||
|
||||
|
||||
class EtfetnMaster(Base):
|
||||
"""ETF/ETN 마스터"""
|
||||
__tablename__ = 'etfetn_master'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50), index=True) # 종목명
|
||||
code = Column(String(50), index=True) # 종목코드
|
||||
ex = Column(String(30), index=True) # 거래소 코드
|
||||
@@ -0,0 +1,12 @@
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from .base import Base
|
||||
|
||||
|
||||
class OverseasFutureoptionMaster(Base):
|
||||
"""해외선물옵션 마스터"""
|
||||
__tablename__ = 'overseas_futureoption_master'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50), index=True) # 종목명
|
||||
code = Column(String(50), index=True) # 종목코드
|
||||
ex = Column(String(30), index=True) # 거래소 코드
|
||||
12
한국투자증권(API)/MCP/Kis Trading MCP/model/overseas_stock.py
Normal file
12
한국투자증권(API)/MCP/Kis Trading MCP/model/overseas_stock.py
Normal file
@@ -0,0 +1,12 @@
|
||||
from sqlalchemy import Column, Integer, String
|
||||
from .base import Base
|
||||
|
||||
|
||||
class OverseasStockMaster(Base):
|
||||
"""해외주식 마스터"""
|
||||
__tablename__ = 'overseas_stock_master'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String(50), index=True) # 종목명
|
||||
code = Column(String(50), index=True) # 종목코드
|
||||
ex = Column(String(30), index=True) # 거래소 코드
|
||||
15
한국투자증권(API)/MCP/Kis Trading MCP/model/updated.py
Normal file
15
한국투자증권(API)/MCP/Kis Trading MCP/model/updated.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from sqlalchemy import Column, Integer, String, DateTime
|
||||
from .base import Base
|
||||
|
||||
|
||||
class Updated(Base):
|
||||
"""마스터파일 업데이트 상태 추적 테이블"""
|
||||
__tablename__ = 'updated'
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
tool_name = Column(String(50), nullable=False, unique=True, index=True) # 툴명 (예: domestic_stock, overseas_stock)
|
||||
updated_at = Column(DateTime, nullable=False) # 마지막 업데이트 시간
|
||||
|
||||
def __repr__(self):
|
||||
return f"<Updated(tool_name='{self.tool_name}', updated_at='{self.updated_at}')>"
|
||||
|
||||
3
한국투자증권(API)/MCP/Kis Trading MCP/module/__init__.py
Normal file
3
한국투자증권(API)/MCP/Kis Trading MCP/module/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from .decorator import singleton
|
||||
from .plugin import setup_environment, EnvironmentConfig, setup_kis_config, MasterFileManager
|
||||
from .middleware import EnvironmentMiddleware
|
||||
32
한국투자증권(API)/MCP/Kis Trading MCP/module/decorator.py
Normal file
32
한국투자증권(API)/MCP/Kis Trading MCP/module/decorator.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import threading
|
||||
|
||||
|
||||
def singleton(cls):
|
||||
"""
|
||||
클래스 형태를 유지하는 싱글톤 데코레이터.
|
||||
- 여러 번 호출해도 동일 인스턴스 반환
|
||||
- __init__은 최초 1회만 실행
|
||||
- 스레드-세이프
|
||||
"""
|
||||
cls.__singleton_lock__ = getattr(cls, "__singleton_lock__", threading.Lock())
|
||||
cls.__singleton_instance__ = getattr(cls, "__singleton_instance__", None)
|
||||
|
||||
orig_init = cls.__init__
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
# 최초 1회만 실제 __init__ 수행
|
||||
if getattr(self, "__initialized__", False):
|
||||
return
|
||||
orig_init(self, *args, **kwargs)
|
||||
setattr(self, "__initialized__", True)
|
||||
|
||||
def __new__(inner_cls, *args, **kwargs):
|
||||
if inner_cls.__singleton_instance__ is None:
|
||||
with inner_cls.__singleton_lock__:
|
||||
if inner_cls.__singleton_instance__ is None:
|
||||
inner_cls.__singleton_instance__ = object.__new__(inner_cls)
|
||||
return inner_cls.__singleton_instance__
|
||||
|
||||
cls.__init__ = __init__
|
||||
cls.__new__ = staticmethod(__new__)
|
||||
return cls
|
||||
6
한국투자증권(API)/MCP/Kis Trading MCP/module/factory.py
Normal file
6
한국투자증권(API)/MCP/Kis Trading MCP/module/factory.py
Normal file
@@ -0,0 +1,6 @@
|
||||
# 기존 컨텍스트 상수들
|
||||
CONTEXT_REQUEST_ID = "context_request_id"
|
||||
CONTEXT_ENVIRONMENT = "context_environment"
|
||||
CONTEXT_STARTED_AT = "context_started_at"
|
||||
CONTEXT_ENDED_AT = "context_ended_at"
|
||||
CONTEXT_ELAPSED_SECONDS = "context_elapsed_seconds"
|
||||
46
한국투자증권(API)/MCP/Kis Trading MCP/module/middleware.py
Normal file
46
한국투자증권(API)/MCP/Kis Trading MCP/module/middleware.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
import time
|
||||
|
||||
from fastmcp.server.middleware import Middleware, MiddlewareContext
|
||||
|
||||
import module.factory as factory
|
||||
|
||||
# 기본 미들웨어
|
||||
class EnvironmentMiddleware(Middleware):
|
||||
def __init__(self, environment):
|
||||
self.environment = environment
|
||||
|
||||
async def on_call_tool(self, context: MiddlewareContext, call_next):
|
||||
ctx = context.fastmcp_context
|
||||
|
||||
# time counter start
|
||||
t0 = time.perf_counter()
|
||||
|
||||
# started_at
|
||||
started_dt = datetime.now()
|
||||
ctx.set_state(factory.CONTEXT_STARTED_AT, started_dt.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
|
||||
# request id
|
||||
request_id = uuid.uuid4().hex
|
||||
ctx.set_state(factory.CONTEXT_REQUEST_ID, request_id)
|
||||
|
||||
# context setup
|
||||
ctx.set_state(factory.CONTEXT_ENVIRONMENT, self.environment)
|
||||
|
||||
try:
|
||||
result = await call_next(context)
|
||||
return result
|
||||
except Exception as e:
|
||||
raise e
|
||||
finally:
|
||||
# ended at
|
||||
ended_at = datetime.now()
|
||||
ctx.set_state(factory.CONTEXT_ENDED_AT, ended_at.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
|
||||
# time counter end
|
||||
elapsed_sec = time.perf_counter() - t0
|
||||
ctx.set_state(factory.CONTEXT_ELAPSED_SECONDS, round(elapsed_sec, 2))
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
from .kis import setup_kis_config
|
||||
from .environment import setup_environment, EnvironmentConfig
|
||||
from .master_file import MasterFileManager
|
||||
from .database import DatabaseEngine, Database
|
||||
540
한국투자증권(API)/MCP/Kis Trading MCP/module/plugin/database.py
Normal file
540
한국투자증권(API)/MCP/Kis Trading MCP/module/plugin/database.py
Normal file
@@ -0,0 +1,540 @@
|
||||
from typing import Any, Dict, List, Optional, Type, Union
|
||||
from sqlalchemy import create_engine, Engine
|
||||
from sqlalchemy.orm import sessionmaker, Session
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
import logging
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DatabaseEngine:
|
||||
"""1 SQLite 파일 : 1 엔진을 관리하는 클래스"""
|
||||
|
||||
def __init__(self, db_path: str, models: List[Type]):
|
||||
"""
|
||||
Args:
|
||||
db_path: SQLite 파일 경로
|
||||
models: 해당 데이터베이스에 포함될 모델 클래스들의 리스트
|
||||
"""
|
||||
self.db_path = db_path
|
||||
self.models = models
|
||||
self.engine: Optional[Engine] = None
|
||||
self.SessionLocal: Optional[sessionmaker] = None
|
||||
self._initialize_engine()
|
||||
|
||||
def _initialize_engine(self):
|
||||
"""데이터베이스 엔진 초기화"""
|
||||
try:
|
||||
# SQLite 연결 문자열 생성
|
||||
db_url = f"sqlite:///{self.db_path}"
|
||||
|
||||
# 엔진 생성
|
||||
self.engine = create_engine(
|
||||
db_url,
|
||||
echo=False, # SQL 로그 출력 여부
|
||||
pool_pre_ping=True, # 연결 상태 확인
|
||||
connect_args={"check_same_thread": False} # SQLite 멀티스레드 지원
|
||||
)
|
||||
|
||||
# 세션 팩토리 생성
|
||||
self.SessionLocal = sessionmaker(
|
||||
autocommit=False,
|
||||
autoflush=False,
|
||||
bind=self.engine
|
||||
)
|
||||
|
||||
# 테이블 생성
|
||||
self._create_tables()
|
||||
|
||||
logger.info(f"Database engine initialized: {self.db_path}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize database engine {self.db_path}: {e}")
|
||||
raise
|
||||
|
||||
def _create_tables(self):
|
||||
"""모든 모델의 테이블 생성"""
|
||||
try:
|
||||
from model.base import Base
|
||||
Base.metadata.create_all(bind=self.engine)
|
||||
logger.info(f"Tables created for {self.db_path}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to create tables for {self.db_path}: {e}")
|
||||
raise
|
||||
|
||||
def get_session(self) -> Session:
|
||||
"""새로운 데이터베이스 세션 반환"""
|
||||
if not self.SessionLocal:
|
||||
raise RuntimeError("Database engine not initialized")
|
||||
return self.SessionLocal()
|
||||
|
||||
def insert(self, model_instance: Any) -> Any:
|
||||
"""
|
||||
모델 인스턴스를 데이터베이스에 삽입
|
||||
|
||||
Args:
|
||||
model_instance: 삽입할 모델 인스턴스
|
||||
|
||||
Returns:
|
||||
삽입된 모델 인스턴스 (ID 포함)
|
||||
"""
|
||||
session = self.get_session()
|
||||
try:
|
||||
session.add(model_instance)
|
||||
session.commit()
|
||||
session.refresh(model_instance)
|
||||
logger.info(f"Inserted record: {type(model_instance).__name__}")
|
||||
return model_instance
|
||||
except SQLAlchemyError as e:
|
||||
session.rollback()
|
||||
logger.error(f"Failed to insert record: {e}")
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def update(self, model_class: Type, record_id: int, update_data: Dict[str, Any]) -> Optional[Any]:
|
||||
"""
|
||||
ID로 레코드 업데이트
|
||||
|
||||
Args:
|
||||
model_class: 업데이트할 모델 클래스
|
||||
record_id: 업데이트할 레코드의 ID
|
||||
update_data: 업데이트할 필드와 값의 딕셔너리
|
||||
|
||||
Returns:
|
||||
업데이트된 모델 인스턴스 또는 None
|
||||
"""
|
||||
session = self.get_session()
|
||||
try:
|
||||
# 레코드 조회
|
||||
record = session.query(model_class).filter(model_class.id == record_id).first()
|
||||
if not record:
|
||||
logger.warning(f"Record not found: {model_class.__name__} ID {record_id}")
|
||||
return None
|
||||
|
||||
# 필드 업데이트
|
||||
for field, value in update_data.items():
|
||||
if hasattr(record, field):
|
||||
setattr(record, field, value)
|
||||
else:
|
||||
logger.warning(f"Field '{field}' not found in {model_class.__name__}")
|
||||
|
||||
session.commit()
|
||||
session.refresh(record)
|
||||
logger.info(f"Updated record: {model_class.__name__} ID {record_id}")
|
||||
return record
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
session.rollback()
|
||||
logger.error(f"Failed to update record: {e}")
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def delete(self, model_class: Type, record_id: int) -> bool:
|
||||
"""
|
||||
ID로 레코드 삭제
|
||||
|
||||
Args:
|
||||
model_class: 삭제할 모델 클래스
|
||||
record_id: 삭제할 레코드의 ID
|
||||
|
||||
Returns:
|
||||
삭제 성공 여부
|
||||
"""
|
||||
session = self.get_session()
|
||||
try:
|
||||
# 레코드 조회
|
||||
record = session.query(model_class).filter(model_class.id == record_id).first()
|
||||
if not record:
|
||||
logger.warning(f"Record not found: {model_class.__name__} ID {record_id}")
|
||||
return False
|
||||
|
||||
session.delete(record)
|
||||
session.commit()
|
||||
logger.info(f"Deleted record: {model_class.__name__} ID {record_id}")
|
||||
return True
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
session.rollback()
|
||||
logger.error(f"Failed to delete record: {e}")
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def list(self, model_class: Type, filters: Optional[Dict[str, Any]] = None,
|
||||
limit: Optional[int] = None, offset: Optional[int] = None) -> List[Any]:
|
||||
"""
|
||||
조건에 맞는 레코드 목록 조회
|
||||
|
||||
Args:
|
||||
model_class: 조회할 모델 클래스
|
||||
filters: 필터 조건 딕셔너리 {field: value}
|
||||
limit: 조회할 최대 개수
|
||||
offset: 건너뛸 개수
|
||||
|
||||
Returns:
|
||||
조회된 레코드 리스트
|
||||
"""
|
||||
session = self.get_session()
|
||||
try:
|
||||
query = session.query(model_class)
|
||||
|
||||
# 필터 적용
|
||||
if filters:
|
||||
for field, value in filters.items():
|
||||
if hasattr(model_class, field):
|
||||
query = query.filter(getattr(model_class, field) == value)
|
||||
else:
|
||||
logger.warning(f"Field '{field}' not found in {model_class.__name__}")
|
||||
|
||||
# 페이징 적용
|
||||
if offset:
|
||||
query = query.offset(offset)
|
||||
if limit:
|
||||
query = query.limit(limit)
|
||||
|
||||
results = query.all()
|
||||
logger.info(f"Listed {len(results)} records: {model_class.__name__}")
|
||||
return results
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
logger.error(f"Failed to list records: {e}")
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def get(self, model_class: Type, filters: Dict[str, Any]) -> Optional[Any]:
|
||||
"""
|
||||
조건에 맞는 첫 번째 레코드 조회
|
||||
|
||||
Args:
|
||||
model_class: 조회할 모델 클래스
|
||||
filters: 필터 조건 딕셔너리 {field: value}
|
||||
|
||||
Returns:
|
||||
조회된 레코드 또는 None
|
||||
"""
|
||||
session = self.get_session()
|
||||
try:
|
||||
query = session.query(model_class)
|
||||
|
||||
# 필터 적용
|
||||
for field, value in filters.items():
|
||||
if hasattr(model_class, field):
|
||||
query = query.filter(getattr(model_class, field) == value)
|
||||
else:
|
||||
logger.warning(f"Field '{field}' not found in {model_class.__name__}")
|
||||
|
||||
result = query.first()
|
||||
if result:
|
||||
logger.info(f"Found record: {model_class.__name__}")
|
||||
else:
|
||||
logger.info(f"No record found: {model_class.__name__}")
|
||||
return result
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
logger.error(f"Failed to get record: {e}")
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def count(self, model_class: Type, filters: Optional[Dict[str, Any]] = None) -> int:
|
||||
"""
|
||||
조건에 맞는 레코드 개수 조회
|
||||
|
||||
Args:
|
||||
model_class: 조회할 모델 클래스
|
||||
filters: 필터 조건 딕셔너리 {field: value}
|
||||
|
||||
Returns:
|
||||
레코드 개수
|
||||
"""
|
||||
session = self.get_session()
|
||||
try:
|
||||
query = session.query(model_class)
|
||||
|
||||
# 필터 적용
|
||||
if filters:
|
||||
for field, value in filters.items():
|
||||
if hasattr(model_class, field):
|
||||
query = query.filter(getattr(model_class, field) == value)
|
||||
else:
|
||||
logger.warning(f"Field '{field}' not found in {model_class.__name__}")
|
||||
|
||||
count = query.count()
|
||||
logger.info(f"Counted {count} records: {model_class.__name__}")
|
||||
return count
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
logger.error(f"Failed to count records: {e}")
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def bulk_replace_master_data(self, model_class: Type, data_list: List[Dict], master_name: str) -> int:
|
||||
"""
|
||||
마스터 데이터 추가 (INSERT만) - 카테고리 레벨에서 이미 삭제됨
|
||||
|
||||
Args:
|
||||
model_class: 마스터 데이터 모델 클래스
|
||||
data_list: 삽입할 데이터 리스트 (딕셔너리 리스트)
|
||||
master_name: 마스터파일명 (로깅용)
|
||||
|
||||
Returns:
|
||||
삽입된 레코드 수
|
||||
"""
|
||||
session = self.get_session()
|
||||
try:
|
||||
# 새 데이터 배치 삽입 (카테고리 레벨에서 이미 삭제되었으므로 INSERT만)
|
||||
if data_list:
|
||||
# 배치 크기 설정 (메모리 효율성을 위해 1000개씩)
|
||||
batch_size = 1000
|
||||
total_inserted = 0
|
||||
|
||||
for i in range(0, len(data_list), batch_size):
|
||||
batch = data_list[i:i + batch_size]
|
||||
batch_objects = []
|
||||
|
||||
for data in batch:
|
||||
# 모든 값을 문자열로 강제 변환 (SQLAlchemy 타입 추론 방지)
|
||||
str_data = {key: str(value) if value is not None else None for key, value in data.items()}
|
||||
|
||||
# 딕셔너리를 모델 인스턴스로 변환
|
||||
obj = model_class(**str_data)
|
||||
batch_objects.append(obj)
|
||||
|
||||
# 배치 삽입
|
||||
session.bulk_save_objects(batch_objects)
|
||||
total_inserted += len(batch_objects)
|
||||
|
||||
# 중간 커밋 (메모리 절약)
|
||||
if i + batch_size < len(data_list):
|
||||
session.commit()
|
||||
logger.info(f"Inserted batch {i//batch_size + 1}: {len(batch_objects)} records")
|
||||
|
||||
# 최종 커밋
|
||||
session.commit()
|
||||
logger.info(f"Bulk replace completed: {total_inserted} records inserted into {model_class.__name__}")
|
||||
return total_inserted
|
||||
else:
|
||||
logger.warning(f"No data to insert for {master_name}")
|
||||
return 0
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
session.rollback()
|
||||
logger.error(f"Failed to bulk replace master data for {master_name}: {e}")
|
||||
raise
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def update_master_timestamp(self, tool_name: str, record_count: int = None) -> bool:
|
||||
"""
|
||||
마스터파일 업데이트 시간 기록
|
||||
|
||||
Args:
|
||||
tool_name: 툴명 (예: domestic_stock, overseas_stock)
|
||||
record_count: 레코드 수 (선택사항)
|
||||
|
||||
Returns:
|
||||
업데이트 성공 여부
|
||||
"""
|
||||
from model.updated import Updated
|
||||
|
||||
session = self.get_session()
|
||||
try:
|
||||
# 기존 레코드 조회
|
||||
existing_record = session.query(Updated).filter(Updated.tool_name == tool_name).first()
|
||||
|
||||
if existing_record:
|
||||
# 기존 레코드 업데이트
|
||||
existing_record.updated_at = datetime.now()
|
||||
logger.info(f"Updated timestamp for {tool_name}")
|
||||
else:
|
||||
# 새 레코드 생성
|
||||
new_record = Updated(
|
||||
tool_name=tool_name,
|
||||
updated_at=datetime.now()
|
||||
)
|
||||
session.add(new_record)
|
||||
logger.info(f"Created new timestamp record for {tool_name}")
|
||||
|
||||
session.commit()
|
||||
return True
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
session.rollback()
|
||||
logger.error(f"Failed to update master timestamp for {tool_name}: {e}")
|
||||
return False
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def get_master_update_time(self, tool_name: str) -> Optional[datetime]:
|
||||
"""
|
||||
마스터파일 마지막 업데이트 시간 조회
|
||||
|
||||
Args:
|
||||
tool_name: 툴명 (예: domestic_stock, overseas_stock)
|
||||
|
||||
Returns:
|
||||
마지막 업데이트 시간 또는 None
|
||||
"""
|
||||
from model.updated import Updated
|
||||
|
||||
session = self.get_session()
|
||||
try:
|
||||
record = session.query(Updated).filter(Updated.tool_name == tool_name).first()
|
||||
if record:
|
||||
logger.info(f"Found update time for {tool_name}: {record.updated_at}")
|
||||
return record.updated_at
|
||||
else:
|
||||
logger.info(f"No update record found for {tool_name}")
|
||||
return None
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
logger.error(f"Failed to get master update time for {tool_name}: {e}")
|
||||
return None
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def is_master_data_available(self, model_class: Type) -> bool:
|
||||
"""
|
||||
마스터 데이터 존재 여부 확인
|
||||
|
||||
Args:
|
||||
model_class: 마스터 데이터 모델 클래스
|
||||
|
||||
Returns:
|
||||
데이터 존재 여부
|
||||
"""
|
||||
session = self.get_session()
|
||||
try:
|
||||
count = session.query(model_class).count()
|
||||
available = count > 0
|
||||
logger.info(f"Master data availability check for {model_class.__name__}: {available} ({count} records)")
|
||||
return available
|
||||
except SQLAlchemyError as e:
|
||||
logger.error(f"Failed to check master data availability for {model_class.__name__}: {e}")
|
||||
return False
|
||||
finally:
|
||||
session.close()
|
||||
|
||||
def close(self):
|
||||
"""데이터베이스 연결 종료"""
|
||||
if self.engine:
|
||||
self.engine.dispose()
|
||||
logger.info(f"Database engine closed: {self.db_path}")
|
||||
|
||||
def __repr__(self):
|
||||
return f"DatabaseEngine(db_path='{self.db_path}', models={len(self.models)})"
|
||||
|
||||
|
||||
class Database:
|
||||
"""데이터베이스 엔진들을 관리하는 Singleton 클래스"""
|
||||
|
||||
_instance: Optional['Database'] = None
|
||||
_initialized: bool = False
|
||||
|
||||
def __new__(cls) -> 'Database':
|
||||
"""Singleton 패턴 구현"""
|
||||
if cls._instance is None:
|
||||
cls._instance = super(Database, cls).__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
"""초기화 (한 번만 실행)"""
|
||||
if not self._initialized:
|
||||
self.dbs: Dict[str, DatabaseEngine] = {}
|
||||
self._initialized = True
|
||||
logger.info("Database singleton instance created")
|
||||
|
||||
def new(self, db_dir: str = "configs/master") -> None:
|
||||
"""
|
||||
마스터 데이터베이스 엔진 초기화
|
||||
|
||||
Args:
|
||||
db_dir: 데이터베이스 파일이 저장될 디렉토리
|
||||
"""
|
||||
try:
|
||||
# 데이터베이스 디렉토리 생성
|
||||
os.makedirs(db_dir, exist_ok=True)
|
||||
|
||||
# 하나의 통합 마스터 데이터베이스 엔진 생성
|
||||
self._create_master_engine(db_dir)
|
||||
|
||||
logger.info(f"Master database engine initialized: '{db_dir}/master.db'")
|
||||
logger.info(f"Available databases: {list(self.dbs.keys())}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize master database: {e}")
|
||||
raise
|
||||
|
||||
def _create_master_engine(self, db_dir: str):
|
||||
"""통합 마스터 데이터베이스 엔진 생성"""
|
||||
from model import ALL_MODELS
|
||||
|
||||
db_path = os.path.join(db_dir, "master.db")
|
||||
|
||||
self.dbs["master"] = DatabaseEngine(db_path, ALL_MODELS)
|
||||
logger.info("Created master database engine with all models")
|
||||
|
||||
def get_by_name(self, name: str) -> DatabaseEngine:
|
||||
"""
|
||||
이름으로 데이터베이스 엔진 조회
|
||||
|
||||
Args:
|
||||
name: 데이터베이스 이름
|
||||
|
||||
Returns:
|
||||
DatabaseEngine 인스턴스
|
||||
|
||||
Raises:
|
||||
KeyError: 해당 이름의 데이터베이스가 없는 경우
|
||||
"""
|
||||
if name not in self.dbs:
|
||||
available_dbs = list(self.dbs.keys())
|
||||
raise KeyError(f"Database '{name}' not found. Available databases: {available_dbs}")
|
||||
|
||||
return self.dbs[name]
|
||||
|
||||
def get_available_databases(self) -> List[str]:
|
||||
"""사용 가능한 데이터베이스 이름 목록 반환"""
|
||||
return list(self.dbs.keys())
|
||||
|
||||
def is_initialized(self) -> bool:
|
||||
"""데이터베이스가 초기화되었는지 확인"""
|
||||
return len(self.dbs) > 0
|
||||
|
||||
def ensure_initialized(self, db_dir: str = "configs/master") -> bool:
|
||||
"""데이터베이스가 초기화되지 않은 경우에만 초기화"""
|
||||
if not self.is_initialized():
|
||||
try:
|
||||
self.new(db_dir)
|
||||
logger.info("Database initialized on demand")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to initialize database on demand: {e}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def close_all(self):
|
||||
"""모든 데이터베이스 연결 종료"""
|
||||
for name, engine in self.dbs.items():
|
||||
try:
|
||||
engine.close()
|
||||
logger.info(f"Closed database: {name}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to close database {name}: {e}")
|
||||
|
||||
self.dbs.clear()
|
||||
logger.info("All database connections closed")
|
||||
|
||||
def __repr__(self):
|
||||
return f"Database(engines={len(self.dbs)}, names={list(self.dbs.keys())})"
|
||||
|
||||
def __del__(self):
|
||||
"""소멸자 - 모든 연결 정리"""
|
||||
if hasattr(self, 'dbs') and self.dbs:
|
||||
self.close_all()
|
||||
43
한국투자증권(API)/MCP/Kis Trading MCP/module/plugin/environment.py
Normal file
43
한국투자증권(API)/MCP/Kis Trading MCP/module/plugin/environment.py
Normal file
@@ -0,0 +1,43 @@
|
||||
import logging
|
||||
import os
|
||||
from collections import namedtuple
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
# Environment 설정을 위한 namedtuple 정의
|
||||
EnvironmentConfig = namedtuple('EnvironmentConfig', [
|
||||
'mcp_type', 'mcp_host', 'mcp_port', 'mcp_path'
|
||||
])
|
||||
|
||||
|
||||
def setup_environment(env: str) -> EnvironmentConfig:
|
||||
# get api env
|
||||
if not env:
|
||||
logging.error("Environment variable ENV not defined")
|
||||
exit(1)
|
||||
|
||||
# load .env
|
||||
dotenv_path = os.path.join(os.getcwd(), f".env.{env}")
|
||||
if not os.path.isfile(dotenv_path):
|
||||
logging.error(f"Environment variable file .env.{env} not found")
|
||||
exit(1)
|
||||
|
||||
load_dotenv(dotenv_path=dotenv_path)
|
||||
|
||||
# return environment
|
||||
# MCP_TYPE 검증 및 기본값 설정
|
||||
mcp_type = os.getenv("MCP_TYPE", "stdio")
|
||||
if mcp_type not in ['stdio', 'sse', 'streamable-http']:
|
||||
logging.warning(f"Invalid MCP_TYPE: {mcp_type}, using default: stdio")
|
||||
mcp_type = "stdio"
|
||||
|
||||
# MCP_PORT가 빈 문자열이면 기본값 사용
|
||||
mcp_port_str = os.getenv("MCP_PORT", "8000")
|
||||
mcp_port = int(mcp_port_str) if mcp_port_str.strip() else 8000
|
||||
|
||||
return EnvironmentConfig(
|
||||
mcp_type=mcp_type,
|
||||
mcp_host=os.getenv("MCP_HOST", "localhost"),
|
||||
mcp_port=mcp_port,
|
||||
mcp_path=os.getenv("MCP_PATH", "/mcp")
|
||||
)
|
||||
163
한국투자증권(API)/MCP/Kis Trading MCP/module/plugin/kis.py
Normal file
163
한국투자증권(API)/MCP/Kis Trading MCP/module/plugin/kis.py
Normal file
@@ -0,0 +1,163 @@
|
||||
import logging
|
||||
import os
|
||||
import requests
|
||||
import yaml
|
||||
|
||||
|
||||
def setup_kis_config(force_update=False):
|
||||
"""KIS 설정 파일 자동 생성 (템플릿 다운로드 + 환경변수로 값 덮어쓰기)
|
||||
|
||||
Args:
|
||||
force_update (bool): True면 기존 파일이 있어도 강제로 덮어쓰기
|
||||
"""
|
||||
|
||||
# kis_auth.py와 동일한 경로 생성 방식 사용
|
||||
kis_config_dir = os.path.join(os.path.expanduser("~"), "KIS", "config")
|
||||
|
||||
# KIS 설정 디렉토리 생성
|
||||
os.makedirs(kis_config_dir, exist_ok=True)
|
||||
|
||||
# 설정 파일 경로
|
||||
kis_config_path = os.path.join(kis_config_dir, "kis_devlp.yaml")
|
||||
|
||||
# 기존 파일 존재 확인
|
||||
if os.path.exists(kis_config_path) and not force_update:
|
||||
logging.info(f"✅ KIS 설정 파일이 이미 존재합니다: {kis_config_path}")
|
||||
logging.info("기존 파일을 사용합니다. 강제 업데이트가 필요한 경우 force_update=True 옵션을 사용하세요.")
|
||||
return True
|
||||
|
||||
# 1. kis_devlp.yaml 템플릿 다운로드
|
||||
template_url = "https://raw.githubusercontent.com/koreainvestment/open-trading-api/refs/heads/main/kis_devlp.yaml"
|
||||
|
||||
try:
|
||||
logging.info("KIS 설정 템플릿을 다운로드 중...")
|
||||
response = requests.get(template_url, timeout=30)
|
||||
response.raise_for_status()
|
||||
|
||||
# 원본 템플릿 텍스트 보존
|
||||
template_content = response.text
|
||||
logging.info("✅ KIS 설정 템플릿 다운로드 완료")
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"❌ KIS 설정 템플릿 다운로드 실패: {e}")
|
||||
return False
|
||||
|
||||
# 2. 환경변수로 민감한 정보 덮어쓰기
|
||||
# 필수값 (누락 시 경고)
|
||||
app_key = os.getenv("KIS_APP_KEY")
|
||||
app_secret = os.getenv("KIS_APP_SECRET")
|
||||
|
||||
if not app_key or not app_secret:
|
||||
logging.warning("⚠️ 필수 환경변수가 설정되지 않았습니다:")
|
||||
if not app_key:
|
||||
logging.warning(" - KIS_APP_KEY")
|
||||
if not app_secret:
|
||||
logging.warning(" - KIS_APP_SECRET")
|
||||
logging.warning("실제 거래 API 사용이 불가능할 수 있습니다.")
|
||||
|
||||
# 선택적 값들 (누락 시 빈값 또는 기본값)
|
||||
paper_app_key = os.getenv("KIS_PAPER_APP_KEY", "")
|
||||
paper_app_secret = os.getenv("KIS_PAPER_APP_SECRET", "")
|
||||
hts_id = os.getenv("KIS_HTS_ID", "")
|
||||
acct_stock = os.getenv("KIS_ACCT_STOCK", "")
|
||||
acct_future = os.getenv("KIS_ACCT_FUTURE", "")
|
||||
paper_stock = os.getenv("KIS_PAPER_STOCK", "")
|
||||
paper_future = os.getenv("KIS_PAPER_FUTURE", "")
|
||||
prod_type = os.getenv("KIS_PROD_TYPE", "01") # 기본값: 종합계좌
|
||||
url_rest = os.getenv("KIS_URL_REST", "")
|
||||
url_rest_paper = os.getenv("KIS_URL_REST_PAPER", "")
|
||||
url_ws = os.getenv("KIS_URL_WS", "")
|
||||
url_ws_paper = os.getenv("KIS_URL_WS_PAPER", "")
|
||||
|
||||
# 3. YAML 파싱하여 값 업데이트
|
||||
try:
|
||||
# YAML 파싱 (주석 보존을 위해 ruamel.yaml 사용하거나, 간단히 pyyaml 사용)
|
||||
config = yaml.safe_load(template_content)
|
||||
|
||||
# 환경변수 값이 있으면 해당 필드만 업데이트
|
||||
if app_key:
|
||||
config['my_app'] = app_key
|
||||
logging.info(f"✅ 실전 App Key 설정 완료")
|
||||
if app_secret:
|
||||
config['my_sec'] = app_secret
|
||||
logging.info(f"✅ 실전 App Secret 설정 완료")
|
||||
|
||||
if paper_app_key:
|
||||
config['paper_app'] = paper_app_key
|
||||
logging.info(f"✅ 모의 App Key 설정 완료")
|
||||
if paper_app_secret:
|
||||
config['paper_sec'] = paper_app_secret
|
||||
logging.info(f"✅ 모의 App Secret 설정 완료")
|
||||
|
||||
if hts_id:
|
||||
config['my_htsid'] = hts_id
|
||||
logging.info(f"✅ HTS ID 설정 완료: {hts_id}")
|
||||
else:
|
||||
logging.warning("⚠️ KIS_HTS_ID 환경변수가 설정되지 않았습니다.")
|
||||
|
||||
if acct_stock:
|
||||
config['my_acct_stock'] = acct_stock
|
||||
logging.info(f"✅ 증권계좌 설정 완료")
|
||||
if acct_future:
|
||||
config['my_acct_future'] = acct_future
|
||||
logging.info(f"✅ 선물옵션계좌 설정 완료")
|
||||
if paper_stock:
|
||||
config['my_paper_stock'] = paper_stock
|
||||
logging.info(f"✅ 모의 증권계좌 설정 완료")
|
||||
if paper_future:
|
||||
config['my_paper_future'] = paper_future
|
||||
logging.info(f"✅ 모의 선물옵션계좌 설정 완료")
|
||||
|
||||
if prod_type != "01": # 기본값이 아닌 경우만 업데이트
|
||||
config['my_prod'] = prod_type
|
||||
logging.info(f"✅ 계좌상품코드 설정 완료: {prod_type}")
|
||||
|
||||
# URL 설정 업데이트 (직접 필드)
|
||||
if url_rest:
|
||||
config['prod'] = url_rest
|
||||
logging.info(f"✅ 실전 REST URL 설정 완료")
|
||||
if url_rest_paper:
|
||||
config['vps'] = url_rest_paper
|
||||
logging.info(f"✅ 모의 REST URL 설정 완료")
|
||||
if url_ws:
|
||||
config['ops'] = url_ws
|
||||
logging.info(f"✅ 실전 WebSocket URL 설정 완료")
|
||||
if url_ws_paper:
|
||||
config['vops'] = url_ws_paper
|
||||
logging.info(f"✅ 모의 WebSocket URL 설정 완료")
|
||||
|
||||
# YAML로 다시 변환
|
||||
updated_content = yaml.dump(config, default_flow_style=False, allow_unicode=True, sort_keys=False)
|
||||
|
||||
except yaml.YAMLError as e:
|
||||
logging.error(f"❌ YAML 파싱 오류: {e}")
|
||||
logging.info("문자열 치환 방식으로 대체합니다...")
|
||||
# 실패 시 기존 문자열 치환 방식 사용
|
||||
updated_content = template_content
|
||||
if app_key:
|
||||
updated_content = updated_content.replace('my_app: "앱키"', f'my_app: "{app_key}"')
|
||||
if app_secret:
|
||||
updated_content = updated_content.replace('my_sec: "앱키 시크릿"', f'my_sec: "{app_secret}"')
|
||||
if hts_id:
|
||||
updated_content = updated_content.replace('my_htsid: "사용자 HTS ID"', f'my_htsid: "{hts_id}"')
|
||||
# ... 나머지 기존 로직
|
||||
|
||||
# 4. 수정된 설정을 파일로 저장 (원본 구조 보존)
|
||||
try:
|
||||
with open(kis_config_path, 'w', encoding='utf-8') as f:
|
||||
f.write(updated_content)
|
||||
|
||||
logging.info(f"✅ KIS 설정 파일이 생성되었습니다: {kis_config_path}")
|
||||
|
||||
# 설정 요약 출력
|
||||
logging.info("📋 KIS 설정 요약:")
|
||||
logging.info(f" - 실제 거래: {'✅' if app_key and app_secret else '❌'}")
|
||||
logging.info(f" - 모의 거래: {'✅' if paper_app_key and paper_app_secret else '❌'}")
|
||||
logging.info(f" - 계좌번호: {'✅' if any([acct_stock, acct_future, paper_stock, paper_future]) else '❌'}")
|
||||
logging.info(f" - URL 설정: {'✅' if any([url_rest, url_rest_paper, url_ws, url_ws_paper]) else '❌'}")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"❌ KIS 설정 파일 생성 실패: {e}")
|
||||
return False
|
||||
1593
한국투자증권(API)/MCP/Kis Trading MCP/module/plugin/master_file.py
Normal file
1593
한국투자증권(API)/MCP/Kis Trading MCP/module/plugin/master_file.py
Normal file
File diff suppressed because it is too large
Load Diff
17
한국투자증권(API)/MCP/Kis Trading MCP/pyproject.toml
Normal file
17
한국투자증권(API)/MCP/Kis Trading MCP/pyproject.toml
Normal file
@@ -0,0 +1,17 @@
|
||||
[project]
|
||||
name = "korea-investment-api-mcp"
|
||||
version = "0.1.0"
|
||||
description = "한국투자증권 OPEN API MCP 서버 - LLM이 쉽게 사용할 수 있는 금융 API 래퍼"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.13"
|
||||
dependencies = [
|
||||
"fastmcp>=2.11.2",
|
||||
"pandas>=2.3.1",
|
||||
"pycryptodome>=3.23.0",
|
||||
"pydantic>=2.11.7",
|
||||
"python-dotenv>=1.1.1",
|
||||
"requests>=2.32.4",
|
||||
"websockets>=15.0.1",
|
||||
"PyYAML>=6.0.1",
|
||||
"sqlalchemy>=2.0.43",
|
||||
]
|
||||
105
한국투자증권(API)/MCP/Kis Trading MCP/server.py
Normal file
105
한국투자증권(API)/MCP/Kis Trading MCP/server.py
Normal file
@@ -0,0 +1,105 @@
|
||||
import logging
|
||||
import os
|
||||
import platform
|
||||
import sys
|
||||
|
||||
from fastmcp import FastMCP
|
||||
|
||||
from module import setup_environment, EnvironmentMiddleware, EnvironmentConfig, setup_kis_config
|
||||
from module.plugin import Database
|
||||
from tools import *
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.DEBUG, # DEBUG 이상 (DEBUG, INFO, WARNING...) 모두 출력
|
||||
format='%(asctime)s [%(levelname)s] %(message)s',
|
||||
datefmt='%H:%M:%S'
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
env = os.getenv("ENV", None)
|
||||
|
||||
# 환경 설정
|
||||
logging.info("setup environment ...")
|
||||
env_config = setup_environment(env=env)
|
||||
|
||||
# KIS 설정 자동 생성 (템플릿 다운로드 + 값 덮어쓰기)
|
||||
logging.info("setup KIS configuration ...")
|
||||
if not setup_kis_config(force_update=env == "live"):
|
||||
logging.warning("KIS 설정 파일 생성에 실패했습니다. 수동으로 설정해주세요.")
|
||||
|
||||
# 데이터베이스 초기화
|
||||
logging.info("setup database ...")
|
||||
db = None
|
||||
db_exists = False
|
||||
|
||||
try:
|
||||
db = Database()
|
||||
db_exists = os.path.exists(os.path.join("configs/master", "master.db"))
|
||||
db.new(db_dir="configs/master")
|
||||
logging.info(f"📁 Available databases: {db.get_available_databases()}")
|
||||
except Exception as e:
|
||||
logging.error(f"❌ Database initialization failed: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# MCP 서버 설정
|
||||
mcp_server = FastMCP(
|
||||
name="My Awesome MCP Server",
|
||||
instructions="This is a server for a specific project.",
|
||||
version="1.0.0",
|
||||
stateless_http=False,
|
||||
)
|
||||
|
||||
# middleware
|
||||
mcp_server.add_middleware(EnvironmentMiddleware(environment=env_config))
|
||||
|
||||
# tools 등록
|
||||
DomesticStockTool().register(mcp_server=mcp_server)
|
||||
DomesticFutureOptionTool().register(mcp_server=mcp_server)
|
||||
DomesticBondTool().register(mcp_server=mcp_server)
|
||||
OverseasStockTool().register(mcp_server=mcp_server)
|
||||
OverseasFutureOptionTool().register(mcp_server=mcp_server)
|
||||
ElwTool().register(mcp_server=mcp_server)
|
||||
EtfEtnTool().register(mcp_server=mcp_server)
|
||||
AuthTool().register(mcp_server=mcp_server)
|
||||
|
||||
|
||||
# MCP 서버 실행 방식 결정
|
||||
logging.info(f"🚀 MCP 서버를 {env_config.mcp_type} 모드로 시작합니다...")
|
||||
|
||||
if env_config.mcp_type == "stdio":
|
||||
# stdio 모드 (기본값)
|
||||
logging.info("📝 stdio 모드로 MCP 서버를 시작합니다.")
|
||||
mcp_server.run(
|
||||
transport="stdio"
|
||||
)
|
||||
|
||||
elif env_config.mcp_type == "sse":
|
||||
# HTTP 모드로 실행
|
||||
logging.info(f"🌐 Server Sent Event 모드로 MCP 서버를 시작합니다: {env_config.mcp_host}:{env_config.mcp_port}")
|
||||
mcp_server.run(
|
||||
transport="sse",
|
||||
host=env_config.mcp_host,
|
||||
port=env_config.mcp_port,
|
||||
path=env_config.mcp_path,
|
||||
)
|
||||
|
||||
elif env_config.mcp_type == "streamable-http":
|
||||
# HTTP 모드로 실행
|
||||
logging.info(f"🌐 HTTP 모드로 MCP 서버를 시작합니다: {env_config.mcp_host}:{env_config.mcp_port}")
|
||||
mcp_server.run(
|
||||
transport="streamable-http",
|
||||
host=env_config.mcp_host,
|
||||
port=env_config.mcp_port,
|
||||
path=env_config.mcp_path,
|
||||
)
|
||||
else:
|
||||
logging.error(f"❌ 지원하지 않는 MCP_TYPE: {env_config.mcp_type}")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
logging.info("🛑 Application interrupted by user (Ctrl+C)")
|
||||
8
한국투자증권(API)/MCP/Kis Trading MCP/tools/__init__.py
Normal file
8
한국투자증권(API)/MCP/Kis Trading MCP/tools/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
from .domestic_bond import DomesticBondTool
|
||||
from .domestic_futureoption import DomesticFutureOptionTool
|
||||
from .domestic_stock import DomesticStockTool
|
||||
from .elw import ElwTool
|
||||
from .etfetn import EtfEtnTool
|
||||
from .overseas_futureoption import OverseasFutureOptionTool
|
||||
from .overseas_stock import OverseasStockTool
|
||||
from .auth import AuthTool
|
||||
9
한국투자증권(API)/MCP/Kis Trading MCP/tools/auth.py
Normal file
9
한국투자증권(API)/MCP/Kis Trading MCP/tools/auth.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from .base import BaseTool
|
||||
from module import singleton
|
||||
|
||||
|
||||
@singleton
|
||||
class AuthTool(BaseTool):
|
||||
@property
|
||||
def tool_name(self) -> str:
|
||||
return "auth"
|
||||
818
한국투자증권(API)/MCP/Kis Trading MCP/tools/base.py
Normal file
818
한국투자증권(API)/MCP/Kis Trading MCP/tools/base.py
Normal file
@@ -0,0 +1,818 @@
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Dict, Any, List
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
import shutil
|
||||
import subprocess
|
||||
import requests
|
||||
from fastmcp import FastMCP, Context
|
||||
|
||||
from module.plugin import MasterFileManager
|
||||
from module.plugin.database import Database
|
||||
import module.factory as factory
|
||||
|
||||
|
||||
class ApiExecutor:
|
||||
"""API 실행 클래스 - GitHub에서 코드를 다운로드하고 실행"""
|
||||
|
||||
def __init__(self, tool_name: str):
|
||||
"""초기화"""
|
||||
self.tool_name = tool_name
|
||||
self.temp_base_dir = "./tmp"
|
||||
# 절대 경로로 venv python 설정
|
||||
self.venv_python = os.path.join(os.getcwd(), ".venv", "bin", "python")
|
||||
|
||||
# temp 디렉토리 생성
|
||||
os.makedirs(self.temp_base_dir, exist_ok=True)
|
||||
|
||||
def _create_temp_directory(self, request_id: str) -> str:
|
||||
"""임시 디렉토리 생성"""
|
||||
timestamp = int(time.time() * 1_000_000) # 나노초 단위
|
||||
temp_dir = os.path.join(self.temp_base_dir, f"{timestamp}_{request_id}")
|
||||
os.makedirs(temp_dir, exist_ok=True)
|
||||
return temp_dir
|
||||
|
||||
@classmethod
|
||||
def _download_file(cls, url: str, file_path: str) -> bool:
|
||||
"""파일 다운로드"""
|
||||
try:
|
||||
response = requests.get(url, timeout=30)
|
||||
response.raise_for_status()
|
||||
|
||||
with open(file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(response.text)
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"파일 다운로드 실패: {url}, 오류: {str(e)}")
|
||||
return False
|
||||
|
||||
def _download_kis_auth(self, temp_dir: str) -> bool:
|
||||
"""kis_auth.py 다운로드"""
|
||||
kis_auth_url = "https://raw.githubusercontent.com/koreainvestment/open-trading-api/main/examples_llm/kis_auth.py"
|
||||
kis_auth_path = os.path.join(temp_dir, "kis_auth.py")
|
||||
return self._download_file(kis_auth_url, kis_auth_path)
|
||||
|
||||
def _download_api_code(self, github_url: str, temp_dir: str, api_type: str) -> str:
|
||||
"""API 코드 다운로드"""
|
||||
# GitHub URL을 raw URL로 변환하고 api_type/api_type.py를 붙여서 실제 파일 경로 생성
|
||||
raw_url = github_url.replace('/tree/', '/').replace('github.com', 'raw.githubusercontent.com')
|
||||
full_url = f"{raw_url}/{api_type}.py"
|
||||
api_code_path = os.path.join(temp_dir, "api_code.py")
|
||||
|
||||
if self._download_file(full_url, api_code_path):
|
||||
return api_code_path
|
||||
else:
|
||||
raise Exception(f"API 코드 다운로드 실패: {full_url}")
|
||||
|
||||
@classmethod
|
||||
def _extract_trenv_params_from_example(cls, api_code_content: str) -> Dict[str, str]:
|
||||
"""예제 파일에서 trenv 사용 패턴 완전 추출"""
|
||||
import re
|
||||
|
||||
# 🎯 완전 자동화: param_name=xxx.my_attr 패턴 찾기 (변수명 무관)
|
||||
trenv_mapping_pattern = r'(\w+)=\w*\.(my_\w+)'
|
||||
matches = re.findall(trenv_mapping_pattern, api_code_content)
|
||||
|
||||
dynamic_mappings = {}
|
||||
discovered_mappings = []
|
||||
|
||||
for param_name, trenv_attr in matches:
|
||||
# 발견된 매핑을 그대로 사용 (완전 자동화!)
|
||||
trenv_value = f'ka._TRENV.{trenv_attr}'
|
||||
|
||||
# 소문자 버전 (함수 파라미터)
|
||||
dynamic_mappings[param_name] = trenv_value
|
||||
# 대문자 버전 (API 파라미터)
|
||||
dynamic_mappings[param_name.upper()] = trenv_value
|
||||
|
||||
discovered_mappings.append(f"{param_name}=xxx.{trenv_attr}")
|
||||
|
||||
if discovered_mappings:
|
||||
print(f"[🎯자동발견] {len(discovered_mappings)}개 매핑: {', '.join(discovered_mappings)}")
|
||||
print(f"[🎯자동생성] {len(dynamic_mappings)}개 파라미터: {list(dynamic_mappings.keys())}")
|
||||
else:
|
||||
print("[🎯자동발견] .my_xxx 패턴 없음 - 조회성 API로 추정")
|
||||
|
||||
|
||||
return dynamic_mappings
|
||||
|
||||
@classmethod
|
||||
def _modify_api_code(cls, api_code_path: str, params: Dict[str, Any], api_type: str) -> str:
|
||||
"""API 코드 수정 (파라미터 적용)"""
|
||||
try:
|
||||
import re
|
||||
|
||||
with open(api_code_path, 'r', encoding='utf-8') as f:
|
||||
code = f.read()
|
||||
|
||||
# 1. sys.path.extend 관련 코드 제거
|
||||
code = re.sub(r"sys\.path\.extend\(\[.*?\]\)", "", code, flags=re.DOTALL)
|
||||
code = re.sub(r"import sys\n", "", code) # import sys도 제거
|
||||
|
||||
# 2. 코드에서 함수명과 시그니처 추출
|
||||
function_match = re.search(r'def\s+(\w+)\s*\((.*?)\):', code, re.DOTALL)
|
||||
if not function_match:
|
||||
raise Exception("코드에서 함수를 찾을 수 없습니다.")
|
||||
|
||||
function_name = function_match.group(1)
|
||||
function_params = function_match.group(2)
|
||||
|
||||
# 3. 함수가 max_depth 파라미터를 받는지 확인
|
||||
has_max_depth = 'max_depth' in function_params
|
||||
|
||||
# 4. 파라미터 조정
|
||||
adjusted_params = params.copy()
|
||||
|
||||
# max_depth 파라미터 처리
|
||||
if has_max_depth:
|
||||
# 함수가 max_depth를 받는 경우에만 처리
|
||||
if 'max_depth' not in adjusted_params:
|
||||
adjusted_params['max_depth'] = 1
|
||||
print(f"[기본값] {function_name} 함수에 max_depth=1 설정")
|
||||
else:
|
||||
print(f"[사용자 설정] {function_name} 함수에 max_depth={adjusted_params['max_depth']} 사용")
|
||||
else:
|
||||
# 함수가 max_depth를 받지 않는 경우 제거
|
||||
if 'max_depth' in adjusted_params:
|
||||
del adjusted_params['max_depth']
|
||||
print(f"[제거] {function_name} 함수는 max_depth 파라미터를 지원하지 않아 제거함")
|
||||
|
||||
# 🆕 동적으로 trenv 패턴 추출
|
||||
dynamic_mappings = cls._extract_trenv_params_from_example(code)
|
||||
|
||||
# 기본 매핑과 동적 매핑 결합
|
||||
account_mappings = {
|
||||
'cano': 'ka._TRENV.my_acct', # 종합계좌번호 (변수 접근)
|
||||
'acnt_prdt_cd': 'ka._TRENV.my_prod', # 계좌상품코드 (변수 접근)
|
||||
'my_htsid': 'ka._TRENV.my_htsid', # HTS ID (변수 접근)
|
||||
'user_id': 'ka._TRENV.my_htsid', # domestic_stock에서 발견된 변형
|
||||
**dynamic_mappings # 동적으로 발견된 매핑 추가
|
||||
}
|
||||
|
||||
for param_name, correct_value in account_mappings.items():
|
||||
if param_name in function_params:
|
||||
if param_name in adjusted_params:
|
||||
original_value = adjusted_params[param_name]
|
||||
adjusted_params[param_name] = correct_value
|
||||
print(f"[보안강제] {function_name} 함수의 {param_name}='{original_value}' → {correct_value} (LLM값 무시)")
|
||||
else:
|
||||
adjusted_params[param_name] = correct_value
|
||||
print(f"[자동설정] {function_name} 함수에 {param_name}={correct_value} 설정")
|
||||
|
||||
# 거래소ID구분코드 처리 (API 타입 기반 추론)
|
||||
if 'excg_id_dvsn_cd' in function_params and 'excg_id_dvsn_cd' not in adjusted_params:
|
||||
if api_type.startswith('domestic'):
|
||||
adjusted_params['excg_id_dvsn_cd'] = '"KRX"'
|
||||
print(f"[추론] 국내 API({api_type})로 판단하여 excg_id_dvsn_cd='KRX' 설정")
|
||||
else:
|
||||
print(f"[경고] {api_type} API에서 excg_id_dvsn_cd 파라미터가 필요합니다. (예: NASD, NYSE, KRX)")
|
||||
# overseas_stock 등은 사용자가 명시적으로 제공해야 함
|
||||
|
||||
# 5. 함수 호출 코드 생성 (ka.auth() - env_dv에 따라 분기)
|
||||
# env_dv 값에 따른 인증 방식 결정
|
||||
env_dv = params.get('env_dv', 'real')
|
||||
if env_dv == 'demo':
|
||||
auth_code = 'ka.auth("vps")'
|
||||
print(f"[모의투자] {function_name} 함수에 ka.auth(\"vps\") 적용")
|
||||
else:
|
||||
auth_code = 'ka.auth()'
|
||||
print(f"[실전투자] {function_name} 함수에 ka.auth() 적용")
|
||||
|
||||
call_code = f"""
|
||||
# API 함수 호출
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
# 인증 초기화 (env_dv={env_dv})
|
||||
{auth_code}
|
||||
|
||||
result = {function_name}({", ".join([f"{k}={v if isinstance(v, str) and v.startswith('ka._TRENV.') else repr(v)}" for k, v in adjusted_params.items()])})
|
||||
except TypeError as e:
|
||||
# 🚨 핵심 오류 메시지만 출력
|
||||
print(f"❌ TypeError: {{str(e)}}")
|
||||
print()
|
||||
|
||||
# 파라미터 오류 처리 - LLM 교육용 메시지
|
||||
if 'stock_name' in {repr(list(params.keys()))}:
|
||||
print("💡 해결방법: find_stock_code로 종목을 검색하세요.")
|
||||
else:
|
||||
print("💡 해결방법: find_api_detail로 API 상세 정보를 확인하세요")
|
||||
import sys
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
|
||||
# N개 튜플 반환 함수 처리 (예: inquire_balance는 (df1, df2) 반환)
|
||||
if isinstance(result, tuple):
|
||||
# 튜플인 경우 - N개의 DataFrame 처리
|
||||
output = {{}}
|
||||
for i, item in enumerate(result):
|
||||
if hasattr(item, 'to_dict'):
|
||||
# DataFrame인 경우
|
||||
output[f"output{{i+1}}"] = item.to_dict('records') if not item.empty else []
|
||||
else:
|
||||
# 일반 객체인 경우
|
||||
output[f"output{{i+1}}"] = str(item)
|
||||
|
||||
import json
|
||||
print(json.dumps(output, ensure_ascii=False, indent=2))
|
||||
elif hasattr(result, 'empty') and not result.empty:
|
||||
print(result.to_json(orient='records', force_ascii=False))
|
||||
elif isinstance(result, dict):
|
||||
import json
|
||||
print(json.dumps(result, ensure_ascii=False))
|
||||
elif isinstance(result, (list, tuple)):
|
||||
import json
|
||||
print(json.dumps(result, ensure_ascii=False))
|
||||
else:
|
||||
print(str(result))
|
||||
except Exception as e:
|
||||
print(f"오류 발생: {{str(e)}}")
|
||||
"""
|
||||
|
||||
# 6. 코드 끝에 함수 호출 추가
|
||||
modified_code = code + call_code
|
||||
|
||||
# 7. 수정된 코드 저장
|
||||
with open(api_code_path, 'w', encoding='utf-8') as f:
|
||||
f.write(modified_code)
|
||||
|
||||
return api_code_path
|
||||
except Exception as e:
|
||||
raise Exception(f"코드 수정 실패: {str(e)}")
|
||||
|
||||
def _execute_code(self, temp_dir: str, timeout: int = 15) -> Dict[str, Any]:
|
||||
"""코드 실행"""
|
||||
try:
|
||||
# 실행할 파일 경로 (상대 경로로 변경)
|
||||
api_code_path = "api_code.py"
|
||||
|
||||
# subprocess로 코드 실행
|
||||
result = subprocess.run(
|
||||
[self.venv_python, api_code_path],
|
||||
cwd=temp_dir,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
timeout=timeout
|
||||
)
|
||||
|
||||
if result.returncode == 0:
|
||||
# 성공 시 stdout을 결과로 반환
|
||||
return {
|
||||
"success": True,
|
||||
"output": result.stdout,
|
||||
"error": result.stderr
|
||||
}
|
||||
else:
|
||||
# 실패 시 stderr와 stdout 모두 확인
|
||||
error_message = result.stderr if result.stderr else result.stdout
|
||||
return {
|
||||
"success": False,
|
||||
"output": result.stdout,
|
||||
"error": error_message
|
||||
}
|
||||
|
||||
except subprocess.TimeoutExpired:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"실행 시간 초과 ({timeout}초)"
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
"success": False,
|
||||
"error": f"실행 중 오류: {str(e)}"
|
||||
}
|
||||
|
||||
def _cleanup_temp_directory(self, temp_dir: str):
|
||||
"""임시 디렉토리 정리"""
|
||||
try:
|
||||
if os.path.exists(temp_dir):
|
||||
shutil.rmtree(temp_dir, ignore_errors=True)
|
||||
except Exception as e:
|
||||
print(f"임시 디렉토리 정리 실패: {temp_dir}, 오류: {str(e)}")
|
||||
|
||||
async def execute_api(self, ctx: Context, api_type: str, params: Dict[str, Any], github_url: str) -> Dict[str, Any]:
|
||||
"""API 실행 메인 함수"""
|
||||
temp_dir = None
|
||||
start_time = time.time()
|
||||
|
||||
try:
|
||||
await ctx.info(f"API 실행 시작: {api_type}")
|
||||
|
||||
# 1. 임시 디렉토리 생성
|
||||
# FastMCP Context에서 request_id 안전하게 가져오기
|
||||
try:
|
||||
request_id = ctx.get_state(factory.CONTEXT_REQUEST_ID)
|
||||
except:
|
||||
request_id = "unknown"
|
||||
temp_dir = self._create_temp_directory(request_id)
|
||||
|
||||
# 2. kis_auth.py 다운로드
|
||||
if not self._download_kis_auth(temp_dir):
|
||||
raise Exception("kis_auth.py 다운로드 실패")
|
||||
|
||||
# 3. API 코드 다운로드
|
||||
api_code_path = self._download_api_code(github_url, temp_dir, api_type)
|
||||
|
||||
# 4. 코드 수정
|
||||
self._modify_api_code(api_code_path, params, api_type)
|
||||
|
||||
# 5. 코드 실행
|
||||
execution_result = self._execute_code(temp_dir)
|
||||
|
||||
# 6. 실행 시간 계산
|
||||
execution_time = time.time() - start_time
|
||||
|
||||
# 7. 결과 반환
|
||||
result = {
|
||||
"success": execution_result["success"],
|
||||
"api_type": api_type,
|
||||
"params": params,
|
||||
"message": f"{self.tool_name} API 호출 완료",
|
||||
"execution_time": f"{execution_time:.2f}s",
|
||||
"temp_dir": temp_dir,
|
||||
"venv_used": True,
|
||||
"cleanup_success": True
|
||||
}
|
||||
|
||||
if execution_result["success"]:
|
||||
result["data"] = execution_result["output"]
|
||||
else:
|
||||
result["error"] = execution_result["error"]
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
await ctx.error(f"API 실행 중 오류: {str(e)}")
|
||||
return {
|
||||
"success": False,
|
||||
"api_type": api_type,
|
||||
"params": params,
|
||||
"error": str(e),
|
||||
"execution_time": f"{time.time() - start_time:.2f}s",
|
||||
"temp_dir": temp_dir,
|
||||
"venv_used": True,
|
||||
"cleanup_success": False
|
||||
}
|
||||
finally:
|
||||
# 8. 임시 디렉토리 정리
|
||||
if temp_dir:
|
||||
self._cleanup_temp_directory(temp_dir)
|
||||
|
||||
|
||||
class BaseTool(ABC):
|
||||
"""MCP 도구 기본 클래스"""
|
||||
|
||||
def __init__(self):
|
||||
"""도구 초기화"""
|
||||
self._load_config()
|
||||
self.api_executor = ApiExecutor(self.tool_name)
|
||||
self.master_file_manager = MasterFileManager(self.tool_name)
|
||||
self.db = Database()
|
||||
|
||||
# ========== Abstract Properties ==========
|
||||
@property
|
||||
@abstractmethod
|
||||
def tool_name(self) -> str:
|
||||
"""도구 이름 (하위 클래스에서 구현 필수)"""
|
||||
pass
|
||||
|
||||
# ========== Public Properties ==========
|
||||
@property
|
||||
def description(self) -> str:
|
||||
"""도구 설명 (분류.json에서 동적 생성)"""
|
||||
return self._generate_description()
|
||||
|
||||
@property
|
||||
def config_file(self) -> str:
|
||||
"""JSON 설정 파일 경로 (tool_name 기반 자동 생성)"""
|
||||
return f"./configs/{self.tool_name}.json"
|
||||
|
||||
# ========== Public Methods ==========
|
||||
def register(self, mcp_server: FastMCP) -> None:
|
||||
"""MCP 서버에 도구 등록"""
|
||||
mcp_server.tool(
|
||||
self._run,
|
||||
name=self.tool_name,
|
||||
description=self.description,
|
||||
)
|
||||
|
||||
# ========== Protected Methods ==========
|
||||
def _load_config(self) -> None:
|
||||
"""JSON 설정 파일 로드"""
|
||||
try:
|
||||
with open(self.config_file, 'r', encoding='utf-8') as f:
|
||||
self.config = json.load(f)
|
||||
except FileNotFoundError:
|
||||
# 임시로 빈 설정으로 초기화
|
||||
self.config = {"apis": {}}
|
||||
|
||||
def _generate_description(self) -> str:
|
||||
"""분류.json에서 도구 설명 동적 생성"""
|
||||
try:
|
||||
config_json_path = f"./configs/{self.tool_name}.json"
|
||||
with open(config_json_path, 'r', encoding='utf-8') as f:
|
||||
config_data = json.load(f)
|
||||
|
||||
tool_info = config_data.get("tool_info")
|
||||
apis = config_data.get("apis", {})
|
||||
|
||||
if not tool_info:
|
||||
return f"{self.tool_name} 도구의 tool_info가 없습니다."
|
||||
|
||||
# description 문자열 구성
|
||||
lines = [tool_info.get("introduce", "")]
|
||||
|
||||
# introduce_append가 있으면 추가
|
||||
introduce_append = tool_info.get("introduce_append", "").strip()
|
||||
if introduce_append:
|
||||
lines.append(introduce_append)
|
||||
|
||||
lines.append("") # 빈 줄
|
||||
lines.append("[지원 기능]")
|
||||
|
||||
# API 목록 추가
|
||||
for api_type, api_info in apis.items():
|
||||
lines.append(f"- {api_info['name']} (api_type: \"{api_type}\")")
|
||||
|
||||
lines.append("") # 빈 줄
|
||||
|
||||
# 개선된 구조 적용
|
||||
lines.append("📋 사용 방법:")
|
||||
lines.append("1. find_api_detail로 API 상세 정보를 확인하세요")
|
||||
lines.append("2. api_type을 선택하고 params에 필요한 파라미터를 입력하세요")
|
||||
lines.append("3. 종목명으로 검색할 경우: stock_name='종목명' 파라미터를 사용하세요")
|
||||
lines.append("4. 모의투자 시에는 env_dv='demo'를 추가하세요")
|
||||
lines.append("")
|
||||
lines.append("🔧 특별한 api_type 및 예시:")
|
||||
lines.append(f"- find_stock_code (종목번호 검색) : {self.tool_name}({{ \"api_type\": \"find_stock_code\", \"params\": {{ \"stock_name\": \"삼성전자\" }} }})")
|
||||
lines.append(f"- find_api_detail (API 정보 조회) : {self.tool_name}({{ \"api_type\": \"find_api_detail\", \"params\": {{ \"api_type\": \"inquire_price\" }} }})")
|
||||
lines.append("")
|
||||
lines.append("🔍 종목명 사용: stock_name=\"삼성전자\" → 자동으로 종목번호 변환하여 실행")
|
||||
lines.append(f"{self.tool_name}({{ \"api_type\": \"inquire_price\", \"params\": {{ \"stock_name\": \"삼성전자\" }} }})")
|
||||
lines.append("")
|
||||
lines.append("💡 주요 파라미터:")
|
||||
if self.tool_name.startswith('domestic'):
|
||||
lines.append("- 시장코드(fid_cond_mrkt_div_code)='J'(KRX)/'NX'(넥스트레이드)/'UN'(통합)")
|
||||
lines.append("- 매매구분(ord_dv)='buy'(매수)/'sell'(매도)")
|
||||
lines.append("- 실전모의구분(env_dv)='real'(실전)/'demo'(모의)")
|
||||
lines.append("")
|
||||
lines.append("⚠️ 중요: API 호출 시 필수 주의사항")
|
||||
lines.append("**API 실행 전 반드시 API 상세 문서의 파라미터를 확인하세요. Request Query Params와 Request Body 입력 시 추측이나 과거 실행 값 사용 금지, 확인된 API 상세 문서의 값을 사용하세요.**")
|
||||
lines.append("**파라미터 description에 '공란'이 있는 경우 기본적으로 빈값으로 처리하되, 아닌 경우에는 값을 넣어도 됩니다.**")
|
||||
lines.append("**🎯 모의투자 관련: 사용자가 '모의', '모의투자', '데모', '테스트' 등의 용어를 언급하거나 모의투자 관련 요청을 할 경우, 반드시 env_dv 파라미터를 'demo'로 설정하여 API를 호출해야 합니다. env_dv 파라미터가 있는 모든 API에서 모의투자 시에는 env_dv='demo', 실전투자 시에는 env_dv='real'을 사용합니다. 기본값은 'real'이므로 모의투자 요청 시 반드시 env_dv='demo'를 명시적으로 설정해주세요.**")
|
||||
lines.append("")
|
||||
lines.append("🔒 자동 처리되는 파라미터 (제공하지 마세요):")
|
||||
lines.append("• cano (계좌번호), acnt_prdt_cd (계좌상품코드), my_htsid (HTS ID) - 시스템 자동 설정")
|
||||
if self.tool_name.startswith('domestic'):
|
||||
lines.append("• excg_id_dvsn_cd (거래소구분) - 국내 API는 자동으로 KRX 설정")
|
||||
lines.append("")
|
||||
|
||||
# 예시 호출 추가
|
||||
examples = tool_info.get("examples", [])
|
||||
if examples:
|
||||
lines.append("💻 예시 호출:")
|
||||
for example in examples:
|
||||
params_str = json.dumps(example.get('params', {}), ensure_ascii=False)
|
||||
lines.append(
|
||||
f"{self.tool_name}({{ \"api_type\": \"{example['api_type']}\",\"params\": {params_str} }})")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
except Exception as e:
|
||||
return f"{self.tool_name} 도구 설명 생성 중 오류: {str(e)}"
|
||||
|
||||
async def _run(self, ctx: Context, api_type: str, params: dict) -> Dict[str, Any]:
|
||||
"""공통 실행 로직"""
|
||||
try:
|
||||
await ctx.info(f"{self.tool_name} running with api_type: {api_type}")
|
||||
|
||||
# 1) 인자 구조 검증(가볍게)
|
||||
if not api_type or not isinstance(params, dict):
|
||||
return {
|
||||
"ok": False,
|
||||
"error": "MISSING_OR_INVALID_ARGS",
|
||||
"missing": [k for k in ("api_type", "params") if
|
||||
not (api_type if k == "api_type" else isinstance(params, dict))],
|
||||
"invalid": [] if isinstance(params, dict) else [{"field": "params", "expected": "object"}],
|
||||
}
|
||||
|
||||
# 2. 특별한 api_type 처리
|
||||
if api_type == "find_stock_code":
|
||||
return await self._handle_find_stock_code(ctx, params)
|
||||
elif api_type == "find_api_detail":
|
||||
return await self._handle_find_api_detail(ctx, params)
|
||||
|
||||
# 3. API 설정 조회
|
||||
if api_type not in self.config['apis']:
|
||||
return {"ok": False, "error": f"지원하지 않는 API 타입: {api_type}"}
|
||||
|
||||
|
||||
# 4. 종목명 자동 처리 (stock_name이 있으면 자동으로 pdno 변환)
|
||||
params = await self._process_stock_name(ctx, params)
|
||||
|
||||
# 5. 실제 실행 (래핑 함수 선택 → OPEN API 호출)
|
||||
data = await self._run_api(ctx, api_type, params)
|
||||
return {"ok": True, "data": data}
|
||||
|
||||
except Exception as e:
|
||||
await ctx.error(f"실행 중 오류: {str(e)}")
|
||||
return {"ok": False, "error": str(e)}
|
||||
|
||||
|
||||
async def _run_api(self, ctx: Context, api_type: str, params: Dict[str, Any]) -> Any:
|
||||
"""API 실행 - ApiExecutor 사용"""
|
||||
try:
|
||||
api_info = self.config['apis'][api_type]
|
||||
github_url = api_info.get('github_url')
|
||||
|
||||
if not github_url:
|
||||
return {"error": f"GitHub URL이 없습니다: {api_type}"}
|
||||
|
||||
# ApiExecutor를 사용하여 API 실행
|
||||
result = await self.api_executor.execute_api(
|
||||
ctx=ctx,
|
||||
api_type=api_type,
|
||||
params=params,
|
||||
github_url=github_url
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
return {"error": f"API 실행 중 오류: {str(e)}"}
|
||||
|
||||
async def _process_stock_name(self, ctx: Context, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""종목명/종목코드 자동 처리 (stock_name이 있으면 자동으로 pdno 변환)"""
|
||||
try:
|
||||
# 종목명으로 찾을 수 있는 파라미터들
|
||||
stock_name_params = ["stock_name", "stock_name_kr", "korean_name", "company_name"]
|
||||
|
||||
# 파라미터에서 종목명/종목코드 찾기
|
||||
search_value = None
|
||||
for param_name in stock_name_params:
|
||||
if param_name in params and params[param_name]:
|
||||
search_value = params[param_name]
|
||||
break
|
||||
|
||||
# 검색할 값이 없으면 그대로 반환
|
||||
if not search_value:
|
||||
return params
|
||||
|
||||
await ctx.info(f"검색값 발견: {search_value}, 자동 검색 시작")
|
||||
|
||||
# 종목명 또는 종목코드로 검색
|
||||
result = await self._find_stock_by_name_or_code(ctx, search_value)
|
||||
|
||||
if result["found"]:
|
||||
params["pdno"] = result["code"]
|
||||
await ctx.info(f"종목번호 자동 찾기 성공: {search_value} → {result['code']}")
|
||||
# 원본 검색값 보존
|
||||
params["_original_search_value"] = search_value
|
||||
params["_resolved_stock_code"] = result["code"]
|
||||
else:
|
||||
await ctx.warning(f"종목을 찾을 수 없음: {search_value}")
|
||||
# 종목을 찾지 못해도 원본 파라미터 유지
|
||||
|
||||
return params
|
||||
|
||||
except Exception as e:
|
||||
await ctx.error(f"종목명 자동 처리 실패: {str(e)}")
|
||||
return params
|
||||
|
||||
async def _find_stock_by_name_or_code(self, ctx: Context, search_value: str) -> Dict[str, Any]:
|
||||
"""종목명 또는 종목코드로 종목번호 찾기"""
|
||||
try:
|
||||
# 검색어에서 띄어쓰기 제거
|
||||
search_term = search_value.replace(" ", "")
|
||||
|
||||
# 데이터베이스 연결 확인
|
||||
if not self.db.ensure_initialized():
|
||||
return {"found": False, "message": "데이터베이스 초기화 실패"}
|
||||
|
||||
# 마스터 파일 업데이트 확인 (force_update=False로 필요시에만 업데이트)
|
||||
try:
|
||||
from module.plugin import MasterFileManager
|
||||
master_file_manager = MasterFileManager(self.tool_name)
|
||||
await master_file_manager.ensure_master_file_updated(ctx, force_update=False)
|
||||
except Exception as e:
|
||||
await ctx.warning(f"마스터 파일 업데이트 확인 중 오류: {str(e)}")
|
||||
|
||||
# DB 엔진
|
||||
db_engine = self.db.get_by_name("master")
|
||||
master_models = MasterFileManager.get_master_models_for_tool(self.tool_name)
|
||||
|
||||
if not master_models:
|
||||
return {"found": False, "message": f"지원하지 않는 툴: {self.tool_name}"}
|
||||
|
||||
# 각 모델에서 우선순위별 검색
|
||||
for model_class in master_models:
|
||||
try:
|
||||
# 1순위: 종목코드로 완전 매칭
|
||||
code_results = db_engine.list(
|
||||
model_class,
|
||||
filters={"code": search_term},
|
||||
limit=1
|
||||
)
|
||||
|
||||
if code_results:
|
||||
result = code_results[0]
|
||||
return {
|
||||
"found": True,
|
||||
"code": result.code,
|
||||
"name": result.name,
|
||||
"ex": result.ex if hasattr(result, 'ex') else None,
|
||||
"match_type": "code_exact"
|
||||
}
|
||||
|
||||
# 2순위: 종목명으로 완전 매칭
|
||||
name_results = db_engine.list(
|
||||
model_class,
|
||||
filters={"name": search_term},
|
||||
limit=1
|
||||
)
|
||||
|
||||
if name_results:
|
||||
result = name_results[0]
|
||||
return {
|
||||
"found": True,
|
||||
"code": result.code,
|
||||
"name": result.name,
|
||||
"ex": result.ex if hasattr(result, 'ex') else None,
|
||||
"match_type": "name_exact"
|
||||
}
|
||||
|
||||
# 3순위: 종목명으로 앞글자 매칭
|
||||
prefix_results = db_engine.list(
|
||||
model_class,
|
||||
filters={"name": f"{search_term}%"},
|
||||
limit=1
|
||||
)
|
||||
|
||||
if prefix_results:
|
||||
result = prefix_results[0]
|
||||
return {
|
||||
"found": True,
|
||||
"code": result.code,
|
||||
"name": result.name,
|
||||
"ex": result.ex if hasattr(result, 'ex') else None,
|
||||
"match_type": "name_prefix"
|
||||
}
|
||||
|
||||
# 4순위: 종목명으로 중간 매칭
|
||||
contains_results = db_engine.list(
|
||||
model_class,
|
||||
filters={"name": f"%{search_term}%"},
|
||||
limit=1
|
||||
)
|
||||
|
||||
if contains_results:
|
||||
result = contains_results[0]
|
||||
return {
|
||||
"found": True,
|
||||
"code": result.code,
|
||||
"name": result.name,
|
||||
"ex": result.ex if hasattr(result, 'ex') else None,
|
||||
"match_type": "name_contains"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
return {"found": False, "message": f"종목을 찾을 수 없음: {search_value}"}
|
||||
|
||||
except Exception as e:
|
||||
return {"found": False, "message": f"종목 검색 오류: {str(e)}"}
|
||||
|
||||
def get_api_info(self, api_type: str) -> Dict[str, Any]:
|
||||
"""API 정보 조회 (리소스 기능 통합)"""
|
||||
try:
|
||||
# API 설정 조회
|
||||
if api_type not in self.config['apis']:
|
||||
return {
|
||||
"error": f"지원하지 않는 API 타입: {api_type}",
|
||||
"available_apis": list(self.config['apis'].keys()),
|
||||
"api_type": api_type
|
||||
}
|
||||
|
||||
# API 정보 반환
|
||||
api_info = self.config['apis'][api_type]
|
||||
|
||||
# 파라미터 정보 정리
|
||||
params = api_info.get("params", {})
|
||||
param_details = {}
|
||||
|
||||
for param_name, param_info in params.items():
|
||||
param_details[param_name] = {
|
||||
"name": param_info.get("name", param_name),
|
||||
"type": param_info.get("type", "str"),
|
||||
"required": param_info.get("required", False),
|
||||
"default_value": param_info.get("default_value"),
|
||||
"description": param_info.get("description", "")
|
||||
}
|
||||
|
||||
result = {
|
||||
"tool_name": self.tool_name,
|
||||
"api_type": api_type,
|
||||
"name": api_info.get("name", ""),
|
||||
"category_detail": api_info.get("category", ""),
|
||||
"method": api_info.get("method", ""),
|
||||
"api_path": api_info.get("api_path", ""),
|
||||
"github_url": api_info.get("github_url", ""),
|
||||
"params": param_details
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
except Exception as e:
|
||||
return {
|
||||
"error": f"API 정보 조회 중 오류 발생: {str(e)}",
|
||||
"tool_name": self.tool_name,
|
||||
"api_type": api_type
|
||||
}
|
||||
|
||||
async def _handle_find_stock_code(self, ctx: Context, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""종목 검색 처리"""
|
||||
try:
|
||||
await ctx.info(f"종목 검색 요청: {self.tool_name}")
|
||||
|
||||
# stock_name 파라미터 확인
|
||||
search_value = params.get("stock_name")
|
||||
if not search_value:
|
||||
return {
|
||||
"ok": False,
|
||||
"error": "MISSING_OR_INVALID_ARGS",
|
||||
"missing": ["stock_name"],
|
||||
"message": "stock_name 파라미터가 필요합니다. (종목명 또는 종목코드 입력 가능)"
|
||||
}
|
||||
|
||||
# 종목 검색 실행 (종목명 또는 종목코드)
|
||||
result = await self._find_stock_by_name_or_code(ctx, search_value)
|
||||
|
||||
if result["found"]:
|
||||
return {
|
||||
"ok": True,
|
||||
"data": {
|
||||
"tool_name": self.tool_name,
|
||||
"search_value": search_value,
|
||||
"found": True,
|
||||
"stock_code": result["code"],
|
||||
"stock_name_found": result["name"],
|
||||
"ex": result.get("ex"),
|
||||
"match_type": result.get("match_type"),
|
||||
"message": f"'{search_value}' 종목을 찾았습니다. 종목번호: {result['code']}",
|
||||
"usage_guide": f"find_api_detail로 API상세정보를 확인하고 종목코드 '{result['code']}'를 해당 API의 종목코드 필드에 입력하여 실행하세요.",
|
||||
"next_step": f"{self.tool_name} 툴에서 find_api_detail로 확인한 종목코드 필드에 '{result['code']}'를 입력하세요."
|
||||
}
|
||||
}
|
||||
else:
|
||||
return {
|
||||
"ok": False,
|
||||
"error": "STOCK_NOT_FOUND",
|
||||
"message": f"'{search_value}' 종목을 찾을 수 없습니다.",
|
||||
"suggestions": [
|
||||
"종목명의 철자가 정확한지 확인",
|
||||
"종목코드가 정확한지 확인",
|
||||
"띄어쓰기나 특수문자가 있는지 확인",
|
||||
"다른 검색어로 시도 (예: '삼성전자' 대신 '삼성' 또는 '005930')"
|
||||
]
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
await ctx.error(f"종목 검색 처리 중 오류: {str(e)}")
|
||||
return {"ok": False, "error": str(e)}
|
||||
|
||||
async def _handle_find_api_detail(self, ctx: Context, params: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""API 상세 정보 조회 처리"""
|
||||
try:
|
||||
await ctx.info(f"API 상세 정보 조회 요청: {self.tool_name}")
|
||||
|
||||
# api_type 파라미터 확인
|
||||
target_api_type = params.get("api_type")
|
||||
if not target_api_type:
|
||||
return {
|
||||
"ok": False,
|
||||
"error": "MISSING_OR_INVALID_ARGS",
|
||||
"missing": ["api_type"],
|
||||
"message": "api_type 파라미터가 필요합니다.",
|
||||
"available_apis": list(self.config['apis'].keys())
|
||||
}
|
||||
|
||||
# API 정보 조회
|
||||
api_info = self.get_api_info(target_api_type)
|
||||
|
||||
if "error" in api_info:
|
||||
return {
|
||||
"ok": False,
|
||||
"error": api_info["error"],
|
||||
"available_apis": api_info.get("available_apis", [])
|
||||
}
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"data": api_info
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
await ctx.error(f"API 상세 정보 조회 처리 중 오류: {str(e)}")
|
||||
return {"ok": False, "error": str(e)}
|
||||
10
한국투자증권(API)/MCP/Kis Trading MCP/tools/domestic_bond.py
Normal file
10
한국투자증권(API)/MCP/Kis Trading MCP/tools/domestic_bond.py
Normal file
@@ -0,0 +1,10 @@
|
||||
from .base import BaseTool
|
||||
from module import singleton
|
||||
|
||||
|
||||
@singleton
|
||||
class DomesticBondTool(BaseTool):
|
||||
@property
|
||||
def tool_name(self) -> str:
|
||||
return "domestic_bond"
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
from .base import BaseTool
|
||||
from module import singleton
|
||||
import pandas as pd
|
||||
import urllib.request
|
||||
import ssl
|
||||
import zipfile
|
||||
import os
|
||||
|
||||
|
||||
@singleton
|
||||
class DomesticFutureOptionTool(BaseTool):
|
||||
@property
|
||||
def tool_name(self) -> str:
|
||||
return "domestic_futureoption"
|
||||
|
||||
9
한국투자증권(API)/MCP/Kis Trading MCP/tools/domestic_stock.py
Normal file
9
한국투자증권(API)/MCP/Kis Trading MCP/tools/domestic_stock.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from .base import BaseTool
|
||||
from module import singleton
|
||||
|
||||
|
||||
@singleton
|
||||
class DomesticStockTool(BaseTool):
|
||||
@property
|
||||
def tool_name(self) -> str:
|
||||
return "domestic_stock"
|
||||
9
한국투자증권(API)/MCP/Kis Trading MCP/tools/elw.py
Normal file
9
한국투자증권(API)/MCP/Kis Trading MCP/tools/elw.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from .base import BaseTool
|
||||
from module import singleton
|
||||
|
||||
|
||||
@singleton
|
||||
class ElwTool(BaseTool):
|
||||
@property
|
||||
def tool_name(self) -> str:
|
||||
return "elw"
|
||||
9
한국투자증권(API)/MCP/Kis Trading MCP/tools/etfetn.py
Normal file
9
한국투자증권(API)/MCP/Kis Trading MCP/tools/etfetn.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from .base import BaseTool
|
||||
from module import singleton
|
||||
|
||||
|
||||
@singleton
|
||||
class EtfEtnTool(BaseTool):
|
||||
@property
|
||||
def tool_name(self) -> str:
|
||||
return "etfetn"
|
||||
@@ -0,0 +1,9 @@
|
||||
from .base import BaseTool
|
||||
from module import singleton
|
||||
|
||||
|
||||
@singleton
|
||||
class OverseasFutureOptionTool(BaseTool):
|
||||
@property
|
||||
def tool_name(self) -> str:
|
||||
return "overseas_futureoption"
|
||||
9
한국투자증권(API)/MCP/Kis Trading MCP/tools/overseas_stock.py
Normal file
9
한국투자증권(API)/MCP/Kis Trading MCP/tools/overseas_stock.py
Normal file
@@ -0,0 +1,9 @@
|
||||
from .base import BaseTool
|
||||
from module import singleton
|
||||
|
||||
|
||||
@singleton
|
||||
class OverseasStockTool(BaseTool):
|
||||
@property
|
||||
def tool_name(self) -> str:
|
||||
return "overseas_stock"
|
||||
383
한국투자증권(API)/MCP/MCP AI 도구 연결 방법.md
Normal file
383
한국투자증권(API)/MCP/MCP AI 도구 연결 방법.md
Normal file
@@ -0,0 +1,383 @@
|
||||
# MCP를 AI 도구에 연결하는 방법
|
||||
|
||||
### 한국투자증권 Open API를 활용하는 KIS Trade MCP와 KIS Code Assistant MCP를 AI 도구(Claude Desktop | Cursor)에 연결하는 설정 방법을 단계별로 안내합니다.
|
||||
|
||||
---
|
||||
|
||||
# 공통사항
|
||||
|
||||
한국투자증권 계좌와 한국투자증권 OpenAPI 홈페이지에서 인증정보(App Key, App Secret)를 준비해 주세요.
|
||||
|
||||
개발 환경 : Python 3.13 이상 권장
|
||||
|
||||
Claude Desktop 또는 Cursor와 같은 한국투자증권 MCP를 연결할 AI 도구를 설치해 주세요.
|
||||
|
||||
## **KIS Open API 신청 및 설정**
|
||||
|
||||
1. 한국투자증권 **계좌 개설 및 ID 연결**
|
||||
2. 한국투자증권 홈페이지 or 앱에서 **Open API 서비스 신청**
|
||||
3. **앱키(App Key)**, **앱시크릿(App Secret)** 발급
|
||||
4. **모의투자** 및 **실전투자** 앱키 각각 준비
|
||||
|
||||
🍀 [서비스 신청 안내 바로가기](https://apiportal.koreainvestment.com/about-howto)
|
||||
|
||||
# 🔗 MCP(Model Context Protocol)란?
|
||||
|
||||
MCP는 Claude를 개발한 Anthropic에서 만든 프로토콜로, AI 모델이 외부 도구와 데이터에 안전하고 효율적으로 접근할 수 있게 해주는 표준화된 인터페이스입니다.
|
||||
이제 한국투자증권이 만든 2개의 MCP를 통해 한국투자증권 Open API를 자연어로 쉽게 활용할 수 있습니다.
|
||||
|
||||
# 한국투자증권 MCP 소개
|
||||
|
||||
## KIS Trade MCP
|
||||
|
||||
### **특징 및 용도**
|
||||
|
||||
국내/해외주식, 선물·옵션, 채권, ETF/ETN, 인증 등 한국투자증권의 다양한 Open API를 **MCP 서버의 "도구"**로 래핑하였습니다. LLM이 바로 사용할 수 있도록 *API 스키마·파라미터*를 리소스로 제공하고, *모의/실전 환경*을 구분하여 안전하게 실행합니다.
|
||||
|
||||
### 설정 방법
|
||||
|
||||
(9월 중 공개 예정)
|
||||
|
||||
## KIS Code Assistant MCP
|
||||
|
||||
### 특징 및 용도
|
||||
|
||||
한국투자증권의 많은 Open API 중에서 **자연어 검색으로 관련 API를 찾고**, **호출 예제(파라미터 포함)까지 자동 구성**해주는 MCP 서버입니다. "무엇을 하고 싶은지"만 말하면, 관련 API를 추천하고 예시 호출 코드를 만들어 드립니다.
|
||||
|
||||
### 설정 방법
|
||||
|
||||
1. Claude Desktop
|
||||
|
||||
Link : [https://smithery.ai/server/@KISOpenAPI/kis-code-assistant-mcp](https://smithery.ai/server/@KISOpenAPI/kis-code-assistant-mcp)
|
||||
|
||||
<img width="2048" height="958" alt="image" src="https://github.com/user-attachments/assets/82aa8bc4-b112-482c-8e8d-34c41fb0ed76" />
|
||||
|
||||
<img width="2048" height="816" alt="image 1" src="https://github.com/user-attachments/assets/3404acc4-058a-4b41-a4d4-0d5aa62ddd3b" />
|
||||
|
||||
**AUTO / Claude Desktop** 선택 → Terminal 명령어 Copy 클릭
|
||||
|
||||
<img width="2048" height="884" alt="image 2" src="https://github.com/user-attachments/assets/a5852435-baa9-4fe0-a5e6-41929552b900" />
|
||||
|
||||
터미널에 명령어 붙여넣기하고 엔터 → 설치 완료 메시지 후 Claude 재시작 질문에는 Y 입력 후 엔터를 누르면 Claude Desktop 재시작
|
||||
|
||||
<img width="2048" height="1000" alt="image 3" src="https://github.com/user-attachments/assets/911b7818-bedf-4d04-8721-09cc4cf5409d" />
|
||||
|
||||
|
||||
홈 화면 대화창 하단 **검색 및 도구** 버튼에서 설치 및 추가 확인 가능, `설정 → 개발자`에서도 확인 할 수 있습니다.
|
||||
|
||||
2. Cursor
|
||||
|
||||
Link : [https://smithery.ai/server/@KISOpenAPI/kis-code-assistant-mcp](https://smithery.ai/server/@KISOpenAPI/kis-code-assistant-mcp)
|
||||
|
||||
<img width="2048" height="988" alt="image 4" src="https://github.com/user-attachments/assets/5058bc1d-8046-47e4-9962-f7f1a5f3bcba" />
|
||||
|
||||
<img width="2048" height="988" alt="image 5" src="https://github.com/user-attachments/assets/6bb863b7-a8de-4435-8bdd-ef1deece02f0" />
|
||||
|
||||
|
||||
**AUTO / Cursor** 선택 → **One-Click Install** 클릭
|
||||
|
||||
<img width="2048" height="958" alt="image 6" src="https://github.com/user-attachments/assets/f3e2f17b-f1b6-4b8f-a388-2990ef6f2a0e" />
|
||||
|
||||
Cursor에서 **Install** 클릭하면 완료
|
||||
|
||||
<img width="2048" height="958" alt="image 7" src="https://github.com/user-attachments/assets/a4fcdcdc-d83b-4187-946d-28160d7f65bf" />
|
||||
|
||||
KIS Code Assistant MCP가 연결되었는지 확인 (경로 : `Settings` > `MCP Servers`)
|
||||
|
||||
|
||||
# 🚀 MCP기반 트레이딩 시스템 개발을 위한 환경 설정
|
||||
|
||||
트레이딩 시스템 개발을 시작하기 전에 필요한 Python 환경 구성부터 API 연결 테스트까지 개발 환경 설정 과정을 안내합니다.
|
||||
|
||||
### 1. 폴더 생성 및 파일 다운로드
|
||||
|
||||
트레이딩 시스템 개발을 위해 필요한 파일을 다운로드하고 폴더를 생성하고 경로를 지정하세요.
|
||||
|
||||
### **1-1. 보안 폴더 생성**
|
||||
|
||||
중요 정보를 저장하는 폴더와 실행 코드를 저장하는 폴더를 각각 생성합니다.
|
||||
|
||||
**맥/리눅스**:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/KIS/config
|
||||
cd ~/KIS/config
|
||||
```
|
||||
|
||||
**윈도우 PowerShell**:
|
||||
|
||||
```powershell
|
||||
mkdir "$HOME\KIS\config"
|
||||
cd "$HOME\KIS\config"
|
||||
```
|
||||
|
||||
### **1-2. 프로젝트 폴더 생성**
|
||||
|
||||
**맥/리눅스**:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/자동매매
|
||||
cd ~/자동매매
|
||||
```
|
||||
|
||||
**윈도우 PowerShell**:
|
||||
|
||||
```powershell
|
||||
mkdir "$HOME\자동매매"
|
||||
cd "$HOME\자동매매"
|
||||
```
|
||||
|
||||
### **1-3. GitHub에서 파일 다운로드**
|
||||
|
||||
한국투자증권 GitHub에서 세개 파일을 다운로드 받으세요.
|
||||
|
||||
**GitHub 링크**: https://github.com/koreainvestment/open-trading-api
|
||||
|
||||
1. **kis_devlp.yaml** → `~/kis/config` 폴더에 저장 **(보안 정보로 별도 관리)**
|
||||
|
||||
https://github.com/koreainvestment/open-trading-api/blob/main/kis_devlp.yaml
|
||||
|
||||
2. **kis_auth.py** → `~/자동매매/` 폴더에 저장
|
||||
|
||||
https://github.com/koreainvestment/open-trading-api/blob/main/examples_llm/kis_auth.py
|
||||
|
||||
3. **pyproject.toml** → `~/자동매매/` 폴더에 저장
|
||||
|
||||
https://github.com/koreainvestment/open-trading-api/blob/main/pyproject.toml
|
||||
|
||||
|
||||
> 경로 표기 안내
|
||||
문서에서 `~`는 **내 사용자 폴더(홈)**를 뜻합니다.
|
||||
`~/{폴더명}`은 그 안의 `{폴더명}` 폴더라는 의미이며, 실제 입력은 `~/kis/config`처럼 중괄호 없이 적습니다.
|
||||
(Windows PowerShell: `~` → `C:\Users\내이름`)
|
||||
>
|
||||
|
||||
### **1-4. `중요`kis_devlp.yaml 설정**
|
||||
|
||||
`~/KIS/kis_devlp.yaml` 파일에 발급받은 App key, App Secret, 계좌정보 (실전, 모의)를 입력하세요
|
||||
|
||||
```yaml
|
||||
#홈페이지에서 API서비스 신청시 발급 AppKey, AppSecret 값 설정
|
||||
#실전투자
|
||||
my_app: "발급받은_실제_APP_KEY" # 한국투자증권에서 발급받은 APP KEY 입력
|
||||
my_sec: "발급받은_실제_APP_SECRET" # 한국투자증권에서 발급받은 APP SECRET 입력
|
||||
|
||||
#모의투자
|
||||
paper_app: "발급받은_실제_APP_KEY" # 모의투자용 APP KEY (실전과 동일)
|
||||
paper_sec: "발급받은_실제_APP_SECRET" # 모의투자용 APP SECRET (실전과 동일)
|
||||
|
||||
# HTS ID
|
||||
my_htsid: "실제_HTS_ID" # 한국투자증권 HTS ID 입력
|
||||
|
||||
#계좌번호 및 8자리
|
||||
my_acct_stock: "실제_계좌번호" # 주식 계좌번호 (예: 50068418)
|
||||
my_acct_future: "실제_계좌번호" # 선물옵션 계좌번호 (주식과 동일 가능)
|
||||
my_paper_stock: "모의투자_계좌번호" # 모의투자 주식 계좌번호
|
||||
my_paper_future: "모의투자_계좌번호" # 모의투자 선물옵션 계좌번호 (주식과 동일 가능)
|
||||
|
||||
#계좌번호 뒤 2자리
|
||||
my_prod: "01" # 01(종합계좌), 03(국내선물옵션), 08(해외선물옵션), 22(개인연금), 29(퇴직연금)
|
||||
```
|
||||
|
||||
> **⚠️ 보안 주의사항:**
|
||||
>
|
||||
> - App Key/App Secret과 계좌번호는 절대 타인과 공유하지 마세요.
|
||||
> - GitHub 공개 저장소와 같이 외부에 공개된 저장소에는 절대 업로드하지 마세요.
|
||||
> - 트레이딩 시스템 폴더와 별도의 경로(~/KIS/config)에 보관하세요.
|
||||
> - 정기적으로 API Key를 재발급하여 보안을 강화하세요
|
||||
|
||||
## 2. uv 설치 및 가상환경 설정
|
||||
|
||||
### **2-1. uv 설치**
|
||||
|
||||
**맥/리눅스**:
|
||||
|
||||
```bash
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
```
|
||||
|
||||
**윈도우**:
|
||||
|
||||
```powershell
|
||||
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||
```
|
||||
|
||||
### **2-2. 가상환경 설정**
|
||||
|
||||
프로젝트 폴더로 이동 후 가상환경 생성:
|
||||
|
||||
```bash
|
||||
cd ~/자동매매
|
||||
uv sync
|
||||
```
|
||||
|
||||
### **2-3. 가상환경 활성화**
|
||||
|
||||
**맥/리눅스**:
|
||||
|
||||
```bash
|
||||
source .venv/bin/activate
|
||||
```
|
||||
|
||||
**윈도우**:
|
||||
|
||||
```bash
|
||||
.venv\Scripts\activate
|
||||
```
|
||||
|
||||
## 3. 연결 테스트 (필수 검증)
|
||||
|
||||
### 3-1. 기본 연결 테스트
|
||||
|
||||
`~/자동매매/test_connection.py` 파일을 생성하고 해당 코드를 복사/붙여넣기 합니다.
|
||||
|
||||
(모의투자로 세팅되어 있습니다.)
|
||||
|
||||
```python
|
||||
# test_connection.py
|
||||
# KIS Open API 연결 테스트 및 기본 정보 확인 스크립트
|
||||
import sys
|
||||
import os
|
||||
|
||||
try:
|
||||
from kis_auth import auth, getTREnv, getEnv, read_token
|
||||
import kis_auth
|
||||
|
||||
# 설정 파일 확인
|
||||
print("설정 파일 확인 중...")
|
||||
cfg = getEnv()
|
||||
print(f"앱키: {cfg.get('my_app', 'None')[:10]}...")
|
||||
print(f"서버 URL: {cfg.get('prod', 'None')}")
|
||||
|
||||
# 인증 토큰 발급 테스트
|
||||
print("토큰 발급 시도 중...")
|
||||
try:
|
||||
# 디버그 모드 활성화
|
||||
kis_auth._DEBUG = True
|
||||
|
||||
auth(svr="vps") # 모의투자 토큰 발급 및 저장
|
||||
print("토큰 발급 완료")
|
||||
|
||||
# 토큰이 제대로 설정되지 않은 경우 수동으로 설정
|
||||
env = getTREnv()
|
||||
if not env.my_token:
|
||||
print("토큰이 환경에 설정되지 않음. 저장된 토큰을 확인합니다...")
|
||||
saved_token = read_token()
|
||||
if saved_token:
|
||||
print("저장된 토큰을 찾았습니다. 환경에 설정합니다...")
|
||||
# 토큰을 직접 설정
|
||||
kis_auth._TRENV = kis_auth._TRENV._replace(my_token=saved_token)
|
||||
kis_auth._base_headers["authorization"] = f"Bearer {saved_token}"
|
||||
print("토큰 설정 완료")
|
||||
else:
|
||||
print("저장된 토큰도 없습니다.")
|
||||
|
||||
except Exception as auth_error:
|
||||
print(f"토큰 발급 중 오류: {auth_error}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# 환경 정보 확인
|
||||
env = getTREnv()
|
||||
|
||||
if hasattr(env, 'my_token') and env.my_token:
|
||||
print("✅ API 연결 성공!")
|
||||
print(f"토큰 앞 10자리: {env.my_token[:10]}...")
|
||||
print(f"계좌번호: {env.my_acct}")
|
||||
print(f"서버: {'모의투자' if env.my_url.find('vts') > 0 else '실전투자'}")
|
||||
else:
|
||||
print("❌ API 연결 실패 - 토큰이 없습니다")
|
||||
print(f"토큰 속성 존재: {hasattr(env, 'my_token')}")
|
||||
if hasattr(env, 'my_token'):
|
||||
print(f"토큰 값: {env.my_token}")
|
||||
print(f"토큰 길이: {len(env.my_token) if env.my_token else 0}")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 오류 발생: {e}")
|
||||
print("devlp.yaml 파일 경로와 설정을 확인해주세요")
|
||||
```
|
||||
|
||||
### 3-2. 테스트 실행
|
||||
|
||||
테스트를 실행하고 결과를 확인하세요.
|
||||
|
||||
```bash
|
||||
# 실행
|
||||
cd ~/자동매매
|
||||
python test_connection.py
|
||||
|
||||
# 결과
|
||||
✅ API 연결 성공!
|
||||
토큰 앞 10자리: asdfasdfas...
|
||||
계좌번호: 12345678
|
||||
서버: 모의투자
|
||||
```
|
||||
|
||||
### 🛠️ 자주 발생하는 문제와 해결방법
|
||||
|
||||
1. MCP 연결 실패 시
|
||||
- Claude Desktop/Cursor 재시작
|
||||
- MCP 서버 URL 확인 ([https://smithery.ai/server/@KISOpenAPI/kis-code-assistant-mcp](https://smithery.ai/server/@KISOpenAPI/kis-code-assistant-mcp))
|
||||
- 방화벽 설정 확인
|
||||
- 인터넷 연결 확인
|
||||
2. API 연결 오류 시
|
||||
- App Key와 Secret이 발급 받은 것과 동일한지 확인
|
||||
- kis_auth.py 의 내용이 다운로드 받은 파일과 동일한지 확인
|
||||
- kis_devlp.yaml 파일이 “~/KIS/config/” 혹은 “$HOME/KIS/config”에 있는지 확인
|
||||
- kis_devlp.yaml 파일에 작성한 개인정보가 정확한지 확인 (App Key/Secret, HTS ID, 계좌번호, 상품코드)
|
||||
- kis_devlp.yaml 파일의 문법이 올바른지 확인 (YAML 문법, 들여쓰기 주의)
|
||||
3. 가상환경 문제 시
|
||||
- uv 버전 확인: `uv --version`
|
||||
- pyproject.toml 의 내용이 다운로드 받은 파일과 동일한지 확인
|
||||
- 프로그램 실행에 필요한 전체 패키지 재설치: `uv sync`
|
||||
- 가상환경 재생성: `uv venv --force`
|
||||
4. Python 모듈 import 오류 시
|
||||
- 가상환경 활성화 확인
|
||||
- 필요 패키지 설치: `uv add {패키지명}`
|
||||
|
||||
---
|
||||
|
||||
## 4. 최종 폴더 구조 확인
|
||||
|
||||
설정이 성공적으로 완료되면 폴더 구조는 다음과 같습니다.
|
||||
|
||||
```
|
||||
~/KIS/
|
||||
└── config/
|
||||
└── devlp.yaml (보안 정보)
|
||||
|
||||
~/자동매매/
|
||||
├── kis_auth.py
|
||||
├── pyproject.toml
|
||||
├── test_connection.py
|
||||
├── .venv/ (uv sync 후 자동 생성)
|
||||
└── uv.lock (uv sync 후 자동 생성)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Step
|
||||
|
||||
설정이 완료되셨다면 이제 투자를 위한 전략를 구현하세요.
|
||||
|
||||
1. 🎯 MCP를 활용하여 개발 시작하기
|
||||
- Cursor에서 KIS Code Assistant MCP를 활용하여 자동매매 시스템 개발
|
||||
- 자연어로 '주식 현재가 조회 코드 보여줘' 같은 질문하기
|
||||
2. 📊 모의투자 환경에서 충분한 테스트 진행
|
||||
- 실제 거래 전 반드시 모의투자로 검증
|
||||
- 손절/익절 로직 구현 및 테스트
|
||||
3. 🔒 실전 투자 적용 시 보안과 리스크 관리 강화
|
||||
- 포트폴리오 분산 투자 권장
|
||||
- 정기적인 API 키 교체
|
||||
|
||||
🚀 고급 활용 팁
|
||||
|
||||
- 백테스팅을 통한 전략 검증
|
||||
- 실시간 알림 시스템 구축
|
||||
- 리스크 관리 자동화
|
||||
|
||||
---
|
||||
|
||||
한국투자증권은 기술을 통해 투자의 진입장벽을 낮추고, 투자자들이 더 나은 투자 경험을 할 수 있도록 MCP를 통해 복잡한 API 연동 등 개발환경을 개선하여 투자 전략 본질에 집중할 수 있도록 지원합니다.
|
||||
|
||||
AI와 함께하는 새로운 투자 시대, 여러분만의 성공 투자 스토리에 한국투자증권 MCP가 든든한 파트너가 되겠습니다.
|
||||
78
한국투자증권(API)/MCP/README.MD
Normal file
78
한국투자증권(API)/MCP/README.MD
Normal file
@@ -0,0 +1,78 @@
|
||||
# 한국투자증권 MCP: 투자 코딩이 쉬워지는 순간
|
||||
|
||||
## 🚀 소개
|
||||
|
||||
한국투자증권에서 개발자들을 위한 혁신적인 도구를 공개합니다. **MCP(Model Context Protocol)**를 활용해 AI 모델이 증권 데이터에 직접 접근할 수 있는 두 가지 도구를 제공합니다:
|
||||
|
||||
1. **KIS Code Assistant MCP**: 한국투자증권 OpenAPI 사용법과 샘플코드를 AI 도구가 검색/제공
|
||||
2. **KIS Trading MCP** : 한국투자증권 OpenAPI를 AI 도구에서 직접 호출
|
||||
|
||||
## 🔧 지원 기능
|
||||
|
||||
### KIS Code Assistant MCP
|
||||
https://smithery.ai/server/@KISOpenAPI/kis-code-assistant-mcp
|
||||
- **스마트 검색**: 자연어로 원하는 API 기능 검색
|
||||
- **샘플코드 제공**: GitHub에서 실제 구현 코드 자동 검색
|
||||
- **카테고리별 탐색**: 기능별 체계적인 API 분류
|
||||
|
||||
### KIS Trading MCP
|
||||
- **시세 조회**: 국내/해외 주식, 선물/옵션, 채권, ETF/ELW
|
||||
- **계좌 관리**: 잔고조회, 주문/체결내역, 손익현황
|
||||
- **매매 주문**: 현물/신용/선물옵션 주문 및 정정취소
|
||||
- **시장 분석**: 순위정보, 투자자별 매매동향, 프로그램매매
|
||||
|
||||
## 💡 활용 예시
|
||||
|
||||
### Claude와 함께 사용
|
||||
|
||||
```
|
||||
사용자: "삼성전자 현재가와 호가창 정보 알려줘"
|
||||
Claude: [KIS OpenAPI] → 실시간 데이터 제공
|
||||
|
||||
사용자: "내 계좌 잔고에서 수익률 높은 종목 5개 보여줘"
|
||||
Claude: [잔고 조회 + 데이터 분석] → 맞춤형 포트폴리오 분석
|
||||
```
|
||||
|
||||
### Cursor IDE에서 활용
|
||||
|
||||
```python
|
||||
# AI가 자동으로 API 호출 코드 생성 및 실행
|
||||
def get_stock_info(code):
|
||||
"""삼성전자 주가 정보를 가져오는 함수를 만들어줘"""
|
||||
# MCP를 통해 실제 API 호출 코드가 자동 생성됨
|
||||
```
|
||||
|
||||
## 🌟 특징
|
||||
|
||||
- **데이터**: 지연 없는 시세 정보
|
||||
- **완전한 API 커버리지**: 한투 OpenAPI의 모든 기능 지원
|
||||
- **자연어 인터페이스**: 복잡한 API 문서 없이 대화로 사용
|
||||
- **코드 자동 생성**: 샘플코드를 기반으로 한 맞춤형 구현
|
||||
- **보안**: OAuth 토큰 기반 안전한 인증
|
||||
|
||||
## 🎯 사용 시나리오
|
||||
|
||||
### 개인 투자자
|
||||
|
||||
- "오늘 상승률 상위 10개 종목의 PER, PBR 비교해줘"
|
||||
- "내 포트폴리오에서 손절매가 필요한 종목 찾아줘"
|
||||
|
||||
### 퀀트 개발자
|
||||
|
||||
- "볼린저밴드 돌파 전략으로 백테스팅 코드 만들어줘"
|
||||
- "RSI와 MACD 조합 신호로 매매 로직 구현해줘"
|
||||
|
||||
### 핀테크 개발사
|
||||
|
||||
- "고객별 맞춤 포트폴리오 추천 시스템 개발"
|
||||
- "실시간 리스크 모니터링 대시보드 구축"
|
||||
|
||||
## 📈 왜 MCP인가?
|
||||
|
||||
기존 REST API의 한계를 뛰어넘어, AI가 상황에 맞는 최적의 API를 선택하고 호출합니다. 개발자는 복잡한 API 문서를 읽을 필요 없이 자연어로 원하는 기능을 요청하면 됩니다.
|
||||
|
||||
---
|
||||
|
||||
*개발 지식이 있는 투자자라면 이제 아이디어만 있으면 됩니다. 복잡한 구현은 AI가, 실제 매매는 한투 API가 담당합니다. 더 스마트한 투자를 위한 새로운 도구를 경험해 보세요.*
|
||||
|
||||
---
|
||||
314
한국투자증권(API)/README.md
Normal file
314
한국투자증권(API)/README.md
Normal file
@@ -0,0 +1,314 @@
|
||||
**[당사에서 제공하는 샘플코드에 대한 유의사항]**
|
||||
|
||||
- 샘플 코드는 한국투자증권 Open API(KIS Developers)를 연동하는 예시입니다. 고객님의 개발 부담을 줄이고자 참고용으로 제공되고 있습니다.
|
||||
- 샘플 코드는 별도의 공지 없이 지속적으로 업데이트될 수 있습니다.
|
||||
- 샘플 코드를 활용하여 제작한 고객님의 프로그램으로 인한 손해에 대해서는 당사에서 책임지지 않습니다.
|
||||
|
||||
# KIS Open API 샘플 코드 저장소 (LLM 지원)
|
||||
|
||||
## 1. 제작 의도 및 대상
|
||||
|
||||
### 🎯 제작 의도
|
||||
|
||||
이 저장소는 **ChatGPT, Claude 등 LLM(Large Language Model)** 기반 자동화 환경과 Python 개발자 모두가
|
||||
**한국투자증권(Korea Investment & Securities) Open API를 쉽게 이해하고 활용**할 수 있도록 구성된 샘플 코드 모음입니다.
|
||||
|
||||
- `examples_llm/`: LLM이 단일 API 기능을 쉽게 탐색하고 호출할 수 있도록 구성된 기능 단위 샘플 코드
|
||||
- `examples_user/`: 사용자가 실제 투자 및 자동매매 구현에 활용할 수 있도록 상품별로 통합된 API 호출 예제 코드
|
||||
|
||||
> AI와 사람이 모두 활용하기 쉬운 구조를 지향합니다.
|
||||
|
||||
[한국투자증권 Open API 포털 바로가기](https://apiportal.koreainvestment.com/)
|
||||
|
||||
### 👤 대상 사용자
|
||||
|
||||
- 한국투자증권 Open API를 처음 사용하는 Python 개발자
|
||||
- 기존 Open API 사용자 중 코드 개선 및 구조 학습이 필요한 사용자
|
||||
- LLM 기반 코드 에이전트를 활용해 종목 검색, 시세 분석, 자동매매 등을 구현하고자 하는 사용자
|
||||
|
||||
## 2. 폴더 구조 및 주요 파일 설명
|
||||
|
||||
### 2.1. 폴더 구조
|
||||
|
||||
```
|
||||
# 프로젝트 구조
|
||||
.
|
||||
├── README.md # 프로젝트 설명서
|
||||
├── docs/
|
||||
│ └── convention.md # 코딩 컨벤션 가이드
|
||||
├── examples_llm/ # LLM용 샘플 코드
|
||||
│ ├── kis_auth.py # 인증 공통 함수
|
||||
│ ├── domestic_bond # 국내채권
|
||||
│ │ └── inquire_price # API 단일 기능별 폴더
|
||||
│ │ ├── inquire_price.py # 한줄 호출 파일 (예: 채권 가격 조회)
|
||||
│ │ └── chk_inquire_price.py # 테스트 파일 (예: 채권 가격 조회 결과 검증)
|
||||
│ ├── domestic_futureoption # 국내선물옵션
|
||||
│ ├── domestic_stock # 국내주식
|
||||
│ ├── elw # ELW
|
||||
│ ├── etfetn # ETF/ETN
|
||||
│ ├── overseas_futureoption # 해외선물옵션
|
||||
│ ├── overseas_price # 해외시세
|
||||
│ └── overseas_stock # 해외주식
|
||||
├── examples_user/ # user용 실제 사용 예제
|
||||
│ ├── kis_auth.py # 인증 공통 함수
|
||||
│ ├── domestic_bond # 국내채권
|
||||
│ │ ├── domestic_bond_functions.py # (REST) 통합 함수 파일 (모든 API 함수 모음)
|
||||
│ │ ├── domestic_bond_examples.py # (REST) 실행 예제 파일 (함수 사용법)
|
||||
│ │ ├── domestic_bond_functions_ws.py # (Websocket) 통합 함수 파일
|
||||
│ │ └── domestic_bond_examples_ws.py # (Websocket) 실행 예제 파일
|
||||
│ ├── domestic_futureoption # 국내선물옵션
|
||||
│ ├── domestic_stock # 국내주식
|
||||
│ ├── elw # ELW
|
||||
│ ├── etfetn # ETF/ETN
|
||||
│ ├── overseas_futureoption # 해외선물옵션
|
||||
│ ├── overseas_price # 해외시세
|
||||
│ └── overseas_stock # 해외주식
|
||||
├── legacy/ # 구 샘플코드 보관
|
||||
├── stock_info/ # 종목정보파일 참고 데이터
|
||||
├── kis_devlp.yaml # API 설정 파일 (개인정보 입력 필요)
|
||||
├── pyproject.toml # (uv)프로젝트 의존성 관리
|
||||
└── uv.lock # (uv)의존성 락 파일
|
||||
```
|
||||
|
||||
### 2.2. 지원되는 주요 API 카테고리
|
||||
|
||||
- 아래 카테고리 및 폴더 구조는 examples_llm/, examples_user/ 폴더 모두 동일하게 적용됩니다.
|
||||
|
||||
| 카테고리 | 설명 | 폴더명 |
|
||||
| --- | --- | --- |
|
||||
| 국내주식 | 국내 주식 시세, 주문, 잔고 등 | `domestic_stock` |
|
||||
| 국내채권 | 국내 채권 시세, 주문 등 | `domestic_bond` |
|
||||
| 국내선물옵션 | 국내 파생상품 관련 | `domestic_futureoption` |
|
||||
| 해외주식 | 해외 주식 시세, 주문 등 | `overseas_stock` |
|
||||
| 해외선물옵션 | 해외 파생상품 관련 | `overseas_futureoption` |
|
||||
| ELW | ELW 시세 API | `elw` |
|
||||
| ETF/ETN | ETF, ETN 시세 API | `etfetn` |
|
||||
|
||||
### 2.3. 주요 파일 설명
|
||||
|
||||
### `examples_llm/` - llm용 기능 단위 샘플 코드
|
||||
|
||||
**API별 개별 폴더 구조**: 단일 API 기능을 독립 폴더로 분리하여, LLM이 관련 코드를 쉽게 탐색할 수 있도록 구성
|
||||
- **한줄 호출 파일**: `[함수명].py` – 단일 기능을 호출하는 최소 단위 코드 (예: `inquire_price.py`)
|
||||
- **테스트 파일**: `chk_[함수명].py` – 호출 결과를 검증하는 테스트 실행 코드 (예: `chk_inquire_price.py`)
|
||||
|
||||
### `examples_user/` - 사용자용 통합 예제 코드
|
||||
|
||||
**카테고리별 개별 폴더 구조**: 카테고리(상품)별로 모든 기능을 통합하여, 사용자가 쉽게 샘플 코드를 탐색하고 실행할 수 있도록 구성
|
||||
- **통합 함수 파일**: `[카테고리]_functions.py` - 해당 카테고리의 모든 API 기능이 통합된 함수 모음
|
||||
- **실행 예제 파일**: `[카테고리]_examples.py` - 실제 사용 예제를 기반으로 한 실행 코드
|
||||
- **웹소켓 통합 함수 파일 및 실행 예제 파일**: `[카테고리]_functions_ws.py`, `[카테고리]_examples_ws.py`
|
||||
|
||||
### `kis_auth.py` - 인증 및 공통 기능
|
||||
|
||||
- 접근토큰 발급 및 관리
|
||||
- API 호출 공통 함수
|
||||
- 실전투자/모의투자 환경 전환 지원
|
||||
- 웹소켓 연결 설정 기능 제공
|
||||
|
||||
## 3. 사전 환경설정 안내
|
||||
|
||||
### 3.1. Python 환경 요구사항
|
||||
|
||||
- **Python 3.9 이상** 필요
|
||||
- **uv** **패키지 매니저 사용** 권장 (빠르고 간편한 의존성 관리)
|
||||
|
||||
### 3.2. uv 설치 방법
|
||||
|
||||
- 간편 설정을 위해 uv를 권장합니다
|
||||
|
||||
```bash
|
||||
# Windows (PowerShell)
|
||||
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
|
||||
|
||||
# macOS/Linux
|
||||
curl -LsSf https://astral.sh/uv/install.sh | sh
|
||||
|
||||
# 설치 확인
|
||||
uv --version
|
||||
# uv 0.x.x ... -> 설치 완료
|
||||
```
|
||||
|
||||
### 3.3. 프로젝트 클론 및 환경 설정
|
||||
|
||||
```bash
|
||||
# 저장소 클론
|
||||
git clone https://github.com/koreainvestment/open-trading-api
|
||||
cd open-trading-api/kis_github
|
||||
|
||||
# uv를 사용한 의존성 설치 - 한줄로 끝
|
||||
uv sync
|
||||
```
|
||||
|
||||
### 3.4. KIS Open API 신청 및 설정
|
||||
|
||||
🍀 [서비스 신청 안내 바로가기](https://apiportal.koreainvestment.com/about-howto)
|
||||
1. 한국투자증권 **계좌 개설 및 ID 연결**
|
||||
2. 한국투자증권 홈페이지 or 앱에서 **Open API 서비스 신청**
|
||||
3. **앱키(App Key)**, **앱시크릿(App Secret)** 발급
|
||||
4. **모의투자** 및 **실전투자** 앱키 각각 준비
|
||||
|
||||
### 3.5. kis_devlp.yaml 설정
|
||||
|
||||
- 본인의 계정 설정을 위해 `kis_devlp.yaml` 파일을 열어 다음과 같이 수정합니다.
|
||||
1. **프로젝트 루트에 위치한** `kis_devlp.yaml` 파일 열기
|
||||
2. **앱키와 앱시크릿** 정보 입력
|
||||
3. **HTS ID** 정보 입력
|
||||
4. **계좌번호** 정보 입력 (앞 8자리와 뒤 2자리 구분)
|
||||
5. **저장** 후 닫기
|
||||
|
||||
```yaml
|
||||
# 실전투자
|
||||
my_app: "여기에 실전투자 앱키 입력"
|
||||
my_sec: "여기에 실전투자 앱시크릿 입력"
|
||||
|
||||
# 모의투자
|
||||
paper_app: "여기에 모의투자 앱키 입력"
|
||||
paper_sec: "여기에 모의투자 앱시크릿 입력"
|
||||
|
||||
# HTS ID(KIS Developers 고객 ID) - 체결통보, 나의 조건 목록 확인 등에 사용됩니다.
|
||||
my_htsid: "사용자 HTS ID"
|
||||
|
||||
# 계좌번호 앞 8자리
|
||||
my_acct_stock: "증권계좌 8자리"
|
||||
my_acct_future: "선물옵션계좌 8자리"
|
||||
my_paper_stock: "모의투자 증권계좌 8자리"
|
||||
my_paper_future: "모의투자 선물옵션계좌 8자리"
|
||||
|
||||
# 계좌번호 뒤 2자리
|
||||
my_prod: "01" # 종합계좌
|
||||
# my_prod: "03" # 국내선물옵션 계좌
|
||||
# my_prod: "08" # 해외선물옵션 계좌
|
||||
# my_prod: "22" # 연금저축 계좌
|
||||
# my_prod: "29" # 퇴직연금 계좌
|
||||
|
||||
# User-Agent(기본값 사용 권장, 변경 불필요)
|
||||
my_agent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
|
||||
```
|
||||
|
||||
### 3.6. kis_auth.py 설정 경로 수정
|
||||
|
||||
- `kis_auth.py`의 config_root 경로를 본인 환경에 맞게 수정해줍니다. 발급된 토큰 파일이 저장될 경로로, 제3자가 찾기 어렵도록 설정하는것을 권장합니다.
|
||||
|
||||
```yaml
|
||||
# kis_auth.py 39번째 줄
|
||||
# windows - C:\Users\사용자이름\KIS\config
|
||||
# Linux/macOS - /home/사용자이름/KIS/config
|
||||
# config_root = os.path.join(os.path.expanduser("~"), "KIS", "config")
|
||||
config_root = os.path.join(os.path.expanduser("~"), "폴더 경로", "config")
|
||||
```
|
||||
### 3.7. 실행파일 내 인증 설정 검토
|
||||
|
||||
- 실행하려는 파일에서 인증 관련 설정을 검토 혹은 변경해줍니다. 국내주식 기능 전체를 이용하시려면, `domestic_stock/domestic_stock_examples.py` 파일을 확인해주세요.
|
||||
ka.auth() 함수의 svr, product 매개변수를 아래와 같이 수정하면 실전환경(prod)에서 위탁계좌(-01)로 매매 테스트가 가능합니다.
|
||||
|
||||
```python
|
||||
import kis_auth as ka
|
||||
|
||||
# 실전투자 인증
|
||||
ka.auth(svr="prod", product="01") # 모의투자: svr="vps"
|
||||
```
|
||||
|
||||
## 4. 샘플 코드 실행
|
||||
|
||||
### 4.1. 샘플 코드 실행
|
||||
|
||||
- **examples_user 기준**
|
||||
|
||||
```bash
|
||||
# 국내주식 샘플 코드 실행 (examples_user/domestic_stock/)
|
||||
python domestic_stock_examples.py # REST 방식
|
||||
python domestic_stock_examples_ws.py # Websocket 방식
|
||||
```
|
||||
|
||||
domestic_stock_examples.py에는 여러 함수가 포함되어 있으므로, 사용하려는 함수만 남기고 나머지는 주석 처리한 후, 입력값을 수정하여 호출해 주세요.
|
||||
|
||||
- **examples_llm 기준**
|
||||
|
||||
```bash
|
||||
# 국내주식 > 주식현재가 시세 샘플 코드 실행 (examples_llm/domestic_stock/inquire_price/)
|
||||
python chk_inquire_price.py
|
||||
```
|
||||
|
||||
examples_llm 은 각 기능별로 개별 실행 파일(chk_*.py)이 분리되어 있어, 특정 기능만 테스트하고자 할 때 유용합니다.
|
||||
|
||||
### 4.2. 예제 코드 샘플 (examples_user)
|
||||
|
||||
```python
|
||||
# REST API 호출 예제 - domestic_stock_examples.py
|
||||
import sys
|
||||
import logging
|
||||
import pandas as pd
|
||||
sys.path.extend(['..', '.'])
|
||||
|
||||
import kis_auth as ka
|
||||
from domestic_stock_functions import *
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 인증
|
||||
ka.auth()
|
||||
trenv = ka.getTREnv()
|
||||
|
||||
# 삼성전자 현재가 시세 조회
|
||||
result = inquire_price(env_dv="real", fid_cond_mrkt_div_code="J", fid_input_iscd="005930")
|
||||
print(result)
|
||||
```
|
||||
|
||||
```python
|
||||
# 웹소켓 호출 예제 - domestic_stock_examples_ws.py
|
||||
import sys
|
||||
import logging
|
||||
import pandas as pd
|
||||
sys.path.extend(['..', '.'])
|
||||
|
||||
import kis_auth as ka
|
||||
from domestic_stock_functions_ws import *
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 인증
|
||||
ka.auth()
|
||||
ka.auth_ws()
|
||||
trenv = ka.getTREnv()
|
||||
|
||||
# 웹소켓 선언
|
||||
kws = ka.KISWebSocket(api_url="/tryitout")
|
||||
|
||||
# 삼성전자, sk하이닉스 실시간 호가 구독
|
||||
kws.subscribe(request=asking_price_krx, data=["005930", "000660"])
|
||||
```
|
||||
|
||||
## 5. 문제 해결 가이드
|
||||
|
||||
### 토큰 오류 시
|
||||
|
||||
```python
|
||||
import kis_auth as ka
|
||||
|
||||
# 토큰 재발급 - 1분당 1회 발급됩니다.
|
||||
ka.auth(svr="prod") # 또는 "vps"
|
||||
```
|
||||
|
||||
### 설정 파일 오류 시
|
||||
|
||||
- `kis_devlp.yaml` 파일의 앱키, 앱시크릿이 올바른지 확인
|
||||
- 계좌번호 형식이 맞는지 확인 (앞 8자리 + 뒤 2자리)
|
||||
- 실시간 시세(WebSocket) 이용 중 ‘No close frame received’ 오류가 발생하는 경우, `kis_devlp.yaml`에 입력하신 HTS ID가 정확한지 확인
|
||||
|
||||
### 의존성 오류 시
|
||||
|
||||
```bash
|
||||
# 의존성 재설치
|
||||
uv sync --reinstall
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# 📧 문의사항
|
||||
|
||||
- [💬 한국투자증권 Open API 챗봇](https://chatgpt.com/g/g-68b920ee7afc8191858d3dc05d429571-hangugtujajeunggweon-open-api-seobiseu-gpts)에 언제든 궁금한 점을 물어보세요.
|
||||
112
한국투자증권(API)/docs/convention.md
Normal file
112
한국투자증권(API)/docs/convention.md
Normal file
@@ -0,0 +1,112 @@
|
||||
# 한국투자증권 OpenAPI 코드 컨벤션
|
||||
|
||||
|
||||
## 정의
|
||||
- 한줄호출함수: 사용자 코드에 import 해서 한줄로 API를 실행할 수 있도록 만들어 주는 함수들이 담긴 파일이다
|
||||
- 체크함수: 한줄호출함수 파일을 import하여, API를 실행한 후 결과 값을 출력하는 파일을 테스트 파일
|
||||
|
||||
|
||||
## 단어에 대한 정의
|
||||
- 1개의 용어에 대해서는 1개의 단어를 사용함으로써, 통일성을 유지하고, LLM이 혼란스럽지 않도록 사용한다.
|
||||
- 동음이의어, 이음동의어와 같은 문맥상 파악이 필요한 단어를 최대한 지양한다.
|
||||
|
||||
|
||||
## 네이밍 컨벤션
|
||||
- 네이밍 파이썬 기본 규칙에 벗어나지 않으며, 역할과 의미가 명확해야 한다.
|
||||
- 널리 알려진 축약어( URL,ID 등) 외에는 축약어와 모호한 이름은 사용하지 않는다.
|
||||
- 형식
|
||||
- 모듈 : snake_case
|
||||
- 변수: snake_case
|
||||
- 함수 : snake_case
|
||||
- 클래스 : PascalCase
|
||||
- 상수 : UPPER_SNAKE_CASE
|
||||
|
||||
|
||||
## 폴더 명명 규칙
|
||||
- API 주소에서 차용하여 생성
|
||||
```text
|
||||
url: /uapi/domestic-stock/v1/quotations/comp-program-trade-daily
|
||||
폴더이름: domestic_stock
|
||||
```
|
||||
- 웹소켓은 URL 이 없으므로, 홈페이의 분류명에 맞추어 적용
|
||||
|
||||
|
||||
## 파일 명명 규칙
|
||||
1. Rest API 한줄호출함수 파일 명명 규칙
|
||||
- 규칙 : API 주소에서 차용
|
||||
- 예시
|
||||
```text
|
||||
url: /uapi/domestic-stock/v1/quotations/comp-program-trade-daily
|
||||
폴더이름: comp_program_trade_daily
|
||||
파일이름: comp_program_trade_daily.py
|
||||
```
|
||||
|
||||
2. Websocket 한줄호출함수 파일 명명 규칙
|
||||
- 규칙 : 웹소켓 함수 직역과 함께, 유사한 이름의 RestAPI를 참고하여 생성
|
||||
|
||||
3. 테스트 파일 명명 규칙
|
||||
- 규칙 : "chk_한줄호출함수 파일이름.py"
|
||||
- 예시
|
||||
```text
|
||||
한줄호출함수 파일이름: comp_program_trade_daily.py
|
||||
|
||||
체크함수 : chk_comp_program_trade_daily.py
|
||||
```
|
||||
|
||||
|
||||
## Docstring 작성
|
||||
- 코드 블록의 목적, 인자, 반환 값, 예외등을 상세히 기술
|
||||
- Google, Sphinx, NumPy 등 널리 사용되는 Docstring 형식을 따라야 한다.
|
||||
- 예제 코드를 포함해야 한다.
|
||||
|
||||
|
||||
## 주석
|
||||
- 파일 상단에 모듈 전체의 목적과 주요 기능, 다른 모듈과의 관계, 주요 구성 요소와 그들간의 상호작용 방식을 설명하는 주석 필요 (인코딩, 작성 시각과 작성자)
|
||||
- 자연어를 기반으로 학습되므로, 가급적 완전한 문장 형태의 자연스러운 설명을 사용 할것 (축약되거나 암호를 사용하지 말것)
|
||||
- 코드 자체는 “무엇”을 하는지 자체로 명확해야하며, 주석은 “왜” 그렇게 작성했는지를 적어야함 (복잡한 로직, 비직관적인 해결 방법, 특정 디자인 결정의 배경 등)
|
||||
- 함수/클래스 : 해당 코드 블록이 무엇을 하는지 설명
|
||||
- 매개변수 : 이름,타입, 역할, 필수 여부, 기본 값 등을 상세 기술
|
||||
- 반환 값 : 무엇을 반환하는지, 타입
|
||||
- 예외처리 : 어떤 상황에서 어떤 예외가 발생할 수 있는지 명시
|
||||
- 사용예시 : 실제 코드를 사용하여, 어떻게 사용하는지를 보여주는 예시
|
||||
- 사전/사후 조건 : 함수 실행 전후에 보장되어야 하는 조건을 명시
|
||||
- Input, Ouput 파라미터에 대체 가능한 옵션들에 대한 설명을 추가
|
||||
- 파라미터는 Request Header/Query/Body, Response Header/Body 총 5개로 구성되어 있음
|
||||
- 각각을 Class화하고 타입을 명시적으로 선언, 필수값은 일반 타입이지만, 선택 값은 Optional을 사용
|
||||
- 코드가 변경되면 주석을 변경
|
||||
- TODO, FIXME 와 같은 태그를 활용하여 개선 혹은 수정할 내용을 명시
|
||||
|
||||
|
||||
## 코드의 모듈화 및 단일 책임 원칙
|
||||
- 함수와 클래스는 가능한 한 작고, 하나의 명확한 기능만을 갖도록 설계
|
||||
- 재사용성을 함께 제공하면서, LLM이 독립적으로 이해하는 데 도움이 된다.
|
||||
- 긴 함수/클래스는 지양한다.
|
||||
- 추상화 계층이나, 복잡한 디자인 패턴은 LLM이 이해하기 어렵게 만들 수 있으므로, 직관적인 코드를 넣을 것
|
||||
- 장황하더라도 명확한 코드가 좋음 (한줄 마법은 지양)
|
||||
|
||||
|
||||
## 설정 관리
|
||||
- API 키, 경로, 임계 값등 자주 변경되거나, 환경에 따라 달라지는 설정은 코드에서 분리
|
||||
- .env
|
||||
- config.py
|
||||
- etc..
|
||||
|
||||
|
||||
## import 정리
|
||||
- Wildcard는 지양하며, 필요한 것만 명시적으로 import 할 것
|
||||
- 라이브러리 from에 따라 그룹화하고, 알파벳순으로 정렬
|
||||
- 라이브러리 import 순서
|
||||
1) 표준 라이브러리
|
||||
2) 써드파티
|
||||
3) 로컬 어플리케이션/Lib
|
||||
|
||||
|
||||
## 변수 선언
|
||||
- 변수의 타입을 명시적으로 선언한다.
|
||||
- 함수의 파라미터, 리턴값, 변수의 타입을 명시적으로 선언해야 한다.
|
||||
- 복잡한 타입 (List, Dict, tuple, Optional, Union, Callable 등)을 활용하여 데이터 구조도를 명확하게 표현한다.
|
||||
|
||||
|
||||
## 에러 처리
|
||||
- try-except 블록을 사용하되, 구체적인 예외 타입을 명시할 것
|
||||
- 중요한 이벤트, 에러, 상태 변경 등을 “logging” 을 사용하여 기록 할 것
|
||||
120
한국투자증권(API)/examples_llm/auth/auth_token/auth_token.py
Normal file
120
한국투자증권(API)/examples_llm/auth/auth_token/auth_token.py
Normal file
@@ -0,0 +1,120 @@
|
||||
# [인증] OAuth 접근토큰 발급
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
import requests
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [인증] OAuth 접근토큰 발급
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/oauth2/tokenP"
|
||||
|
||||
def auth_token(
|
||||
grant_type: str,
|
||||
appkey: str,
|
||||
appsecret: str,
|
||||
env_dv: str
|
||||
) -> pd.DataFrame:
|
||||
"""
|
||||
OAuth 접근토큰 발급 API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
grant_type (str): [필수] 권한부여 Type (client_credentials)
|
||||
appkey (str): [필수] 앱키 (한국투자증권 홈페이지에서 발급받은 appkey)
|
||||
appsecret (str): [필수] 앱시크릿키 (한국투자증권 홈페이지에서 발급받은 appsecret)
|
||||
env_dv (str): [필수] 환경구분 (real: 실전, demo: 모의)
|
||||
|
||||
Returns:
|
||||
pd.DataFrame: OAuth 토큰 발급 결과
|
||||
|
||||
Example:
|
||||
>>> df = auth_token(
|
||||
... grant_type="client_credentials",
|
||||
... appkey=trenv.my_app,
|
||||
... appsecret=trenv.my_sec,
|
||||
... env_dv="real"
|
||||
... )
|
||||
>>> print(df)
|
||||
"""
|
||||
# 필수 파라미터 검증
|
||||
if not grant_type:
|
||||
logger.error("grant_type is required. (e.g. 'client_credentials')")
|
||||
raise ValueError("grant_type is required. (e.g. 'client_credentials')")
|
||||
|
||||
if not appkey:
|
||||
logger.error("appkey is required. (한국투자증권 홈페이지에서 발급받은 appkey)")
|
||||
raise ValueError("appkey is required. (한국투자증권 홈페이지에서 발급받은 appkey)")
|
||||
|
||||
if not appsecret:
|
||||
logger.error("appsecret is required. (한국투자증권 홈페이지에서 발급받은 appsecret)")
|
||||
raise ValueError("appsecret is required. (한국투자증권 홈페이지에서 발급받은 appsecret)")
|
||||
|
||||
if not env_dv:
|
||||
logger.error("env_dv is required. (real: 실전, demo: 모의)")
|
||||
raise ValueError("env_dv is required. (real: 실전, demo: 모의)")
|
||||
|
||||
# 환경 구분에 따른 서버 URL 설정
|
||||
config = ka.getEnv()
|
||||
if env_dv == "real":
|
||||
base_url = config.get("prod", "")
|
||||
elif env_dv == "demo":
|
||||
base_url = config.get("vps", "")
|
||||
else:
|
||||
logger.error("env_dv must be 'real' or 'demo'")
|
||||
raise ValueError("env_dv must be 'real' or 'demo'")
|
||||
|
||||
url = f"{base_url}{API_URL}"
|
||||
|
||||
# 헤더 설정
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "text/plain",
|
||||
"charset": "UTF-8"
|
||||
}
|
||||
|
||||
# 요청 데이터
|
||||
data = {
|
||||
"grant_type": grant_type,
|
||||
"appkey": appkey,
|
||||
"appsecret": appsecret,
|
||||
}
|
||||
|
||||
try:
|
||||
# POST 방식으로 직접 API 호출
|
||||
response = requests.post(url, data=json.dumps(data), headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
# 응답 데이터를 DataFrame으로 반환 (1개 row)
|
||||
response_data = response.json()
|
||||
current_data = pd.DataFrame([response_data])
|
||||
|
||||
logger.info("OAuth 토큰 발급 성공")
|
||||
return current_data
|
||||
else:
|
||||
logger.error("API call failed: %s - %s", response.status_code, response.text)
|
||||
return pd.DataFrame()
|
||||
|
||||
except requests.RequestException as e:
|
||||
logger.error("Request failed: %s", str(e))
|
||||
return pd.DataFrame()
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error("JSON decode failed: %s", str(e))
|
||||
return pd.DataFrame()
|
||||
104
한국투자증권(API)/examples_llm/auth/auth_token/chk_auth_token.py
Normal file
104
한국투자증권(API)/examples_llm/auth/auth_token/chk_auth_token.py
Normal file
@@ -0,0 +1,104 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
import kis_auth as ka
|
||||
from auth_token import auth_token
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [인증] OAuth 접근토큰 발급 테스트
|
||||
##############################################################################################
|
||||
|
||||
# 통합 컬럼 매핑
|
||||
COLUMN_MAPPING = {
|
||||
'access_token': '접근토큰',
|
||||
'token_type': '접근토큰유형',
|
||||
'expires_in': '접근토큰유효기간_초',
|
||||
'access_token_token_expired': '접근토큰유효기간_일시표시'
|
||||
}
|
||||
|
||||
def main():
|
||||
"""
|
||||
OAuth 접근토큰 발급 테스트 함수
|
||||
|
||||
Parameters:
|
||||
- grant_type (str): 권한부여 Type (client_credentials)
|
||||
- appkey (str): 앱키 (한국투자증권 홈페이지에서 발급받은 appkey)
|
||||
- appsecret (str): 앱시크릿키 (한국투자증권 홈페이지에서 발급받은 appsecret)
|
||||
- env_dv (str): 환경구분 (real: 실전, demo: 모의)
|
||||
|
||||
Returns:
|
||||
- pd.DataFrame: OAuth 토큰 발급 결과
|
||||
|
||||
Response Fields:
|
||||
- access_token: 접근토큰 (OAuth 토큰이 필요한 API 경우 발급한 Access token)
|
||||
- token_type: 접근토큰유형 ("Bearer")
|
||||
- expires_in: 접근토큰 유효기간(초)
|
||||
- access_token_token_expired: 접근토큰 유효기간(일시표시)
|
||||
|
||||
Example:
|
||||
>>> df = auth_token(grant_type="client_credentials", appkey=trenv.my_app, appsecret=trenv.my_sec, env_dv="real")
|
||||
"""
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 환경 설정에서 앱키와 앱시크릿 가져오기
|
||||
config = ka.getEnv()
|
||||
|
||||
# 실전투자용 앱키/앱시크릿 사용 (모의투자의 경우 paper_app, paper_sec 사용)
|
||||
appkey = config.get("my_app", "")
|
||||
appsecret = config.get("my_sec", "")
|
||||
|
||||
# 앱키와 앱시크릿이 설정되어 있는지 확인
|
||||
if not appkey or not appsecret:
|
||||
logger.error("앱키 또는 앱시크릿이 설정되지 않았습니다. kis_devlp.yaml 파일을 확인해주세요.")
|
||||
logger.info("필요한 설정: my_app (앱키), my_sec (앱시크릿)")
|
||||
return
|
||||
|
||||
# API 호출
|
||||
logger.info("OAuth 접근토큰 발급 API 호출 시작")
|
||||
result = auth_token(
|
||||
grant_type="client_credentials",
|
||||
appkey=appkey,
|
||||
appsecret=appsecret,
|
||||
env_dv="real" # 실전 환경으로 설정 (필요시 "demo"로 변경)
|
||||
)
|
||||
|
||||
# 결과 확인
|
||||
if result.empty:
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# 결과 처리
|
||||
logger.info("=== OAuth 접근토큰 발급 결과 ===")
|
||||
logger.info("사용 가능한 컬럼: %s", result.columns.tolist())
|
||||
|
||||
# 통합 컬럼명 한글 변환 (필요한 컬럼만 자동 매핑됨)
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
|
||||
logger.info("결과:")
|
||||
print(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
127
한국투자증권(API)/examples_llm/auth/auth_ws_token/auth_ws_token.py
Normal file
127
한국투자증권(API)/examples_llm/auth/auth_ws_token/auth_ws_token.py
Normal file
@@ -0,0 +1,127 @@
|
||||
# [인증] WebSocket 웹소켓 접속키 발급
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import sys
|
||||
from typing import Optional
|
||||
|
||||
import pandas as pd
|
||||
import requests
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [인증] WebSocket 웹소켓 접속키 발급
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/oauth2/Approval"
|
||||
|
||||
def auth_ws_token(
|
||||
grant_type: str,
|
||||
appkey: str,
|
||||
appsecret: str,
|
||||
env_dv: str,
|
||||
token: Optional[str] = ""
|
||||
) -> pd.DataFrame:
|
||||
"""
|
||||
WebSocket 웹소켓 접속키 발급 API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
grant_type (str): [필수] 권한부여 Type (client_credentials)
|
||||
appkey (str): [필수] 고객 앱Key (한국투자증권 홈페이지에서 발급받은 appkey)
|
||||
appsecret (str): [필수] 고객 앱Secret (한국투자증권 홈페이지에서 발급받은 appsecret)
|
||||
env_dv (str): [필수] 환경구분 (real: 실전, demo: 모의)
|
||||
token (Optional[str]): 접근토큰 (OAuth 토큰이 필요한 API 경우 발급한 Access token)
|
||||
|
||||
Returns:
|
||||
pd.DataFrame: WebSocket 접속키 발급 결과
|
||||
|
||||
Example:
|
||||
>>> df = auth_ws_token(
|
||||
... grant_type="client_credentials",
|
||||
... appkey=trenv.my_app,
|
||||
... appsecret=trenv.my_sec,
|
||||
... env_dv="real"
|
||||
... )
|
||||
>>> print(df)
|
||||
"""
|
||||
# 필수 파라미터 검증
|
||||
if not grant_type:
|
||||
logger.error("grant_type is required. (e.g. 'client_credentials')")
|
||||
raise ValueError("grant_type is required. (e.g. 'client_credentials')")
|
||||
|
||||
if not appkey:
|
||||
logger.error("appkey is required. (한국투자증권 홈페이지에서 발급받은 appkey)")
|
||||
raise ValueError("appkey is required. (한국투자증권 홈페이지에서 발급받은 appkey)")
|
||||
|
||||
if not appsecret:
|
||||
logger.error("appsecret is required. (한국투자증권 홈페이지에서 발급받은 appsecret)")
|
||||
raise ValueError("appsecret is required. (한국투자증권 홈페이지에서 발급받은 appsecret)")
|
||||
|
||||
if not env_dv:
|
||||
logger.error("env_dv is required. (real: 실전, demo: 모의)")
|
||||
raise ValueError("env_dv is required. (real: 실전, demo: 모의)")
|
||||
|
||||
# 환경 구분에 따른 서버 URL 설정
|
||||
config = ka.getEnv()
|
||||
if env_dv == "real":
|
||||
base_url = config.get("prod", "")
|
||||
elif env_dv == "demo":
|
||||
base_url = config.get("vps", "")
|
||||
else:
|
||||
logger.error("env_dv must be 'real' or 'demo'")
|
||||
raise ValueError("env_dv must be 'real' or 'demo'")
|
||||
|
||||
url = f"{base_url}{API_URL}"
|
||||
|
||||
# 헤더 설정
|
||||
headers = {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "text/plain",
|
||||
"charset": "UTF-8"
|
||||
}
|
||||
|
||||
# 요청 데이터
|
||||
data = {
|
||||
"grant_type": grant_type,
|
||||
"appkey": appkey,
|
||||
"secretkey": appsecret,
|
||||
}
|
||||
|
||||
# token이 있는 경우에만 data에 추가
|
||||
if token:
|
||||
data["token"] = token
|
||||
|
||||
try:
|
||||
# POST 방식으로 직접 API 호출
|
||||
response = requests.post(url, data=json.dumps(data), headers=headers)
|
||||
|
||||
if response.status_code == 200:
|
||||
# 응답 데이터를 DataFrame으로 반환 (1개 row)
|
||||
response_data = response.json()
|
||||
current_data = pd.DataFrame([response_data])
|
||||
|
||||
logger.info("WebSocket 접속키 발급 성공")
|
||||
return current_data
|
||||
else:
|
||||
logger.error("API call failed: %s - %s", response.status_code, response.text)
|
||||
return pd.DataFrame()
|
||||
|
||||
except requests.RequestException as e:
|
||||
logger.error("Request failed: %s", str(e))
|
||||
return pd.DataFrame()
|
||||
except json.JSONDecodeError as e:
|
||||
logger.error("JSON decode failed: %s", str(e))
|
||||
return pd.DataFrame()
|
||||
104
한국투자증권(API)/examples_llm/auth/auth_ws_token/chk_auth_ws_token.py
Normal file
104
한국투자증권(API)/examples_llm/auth/auth_ws_token/chk_auth_ws_token.py
Normal file
@@ -0,0 +1,104 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
import kis_auth as ka
|
||||
from auth_ws_token import auth_ws_token
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [인증] WebSocket 웹소켓 접속키 발급 테스트
|
||||
##############################################################################################
|
||||
|
||||
# 통합 컬럼 매핑
|
||||
COLUMN_MAPPING = {
|
||||
'code': '응답코드',
|
||||
'message': '응답메세지',
|
||||
'approval_key': '웹소켓접속키'
|
||||
}
|
||||
|
||||
def main():
|
||||
"""
|
||||
WebSocket 웹소켓 접속키 발급 테스트 함수
|
||||
|
||||
Parameters:
|
||||
- grant_type (str): 권한부여 Type (client_credentials)
|
||||
- appkey (str): 고객 앱Key (한국투자증권 홈페이지에서 발급받은 appkey)
|
||||
- appsecret (str): 고객 앱Secret (한국투자증권 홈페이지에서 발급받은 appsecret)
|
||||
- env_dv (str): 환경구분 (real: 실전, demo: 모의)
|
||||
- token (str): 접근토큰 (OAuth 토큰이 필요한 API 경우 발급한 Access token)
|
||||
|
||||
Returns:
|
||||
- pd.DataFrame: WebSocket 접속키 발급 결과
|
||||
|
||||
Response Fields:
|
||||
- code: 응답코드 (HTTP 응답코드)
|
||||
- message: 응답메세지
|
||||
|
||||
Example:
|
||||
>>> df = auth_ws_token(grant_type="client_credentials", appkey=trenv.my_app, appsecret=trenv.my_sec, env_dv="real")
|
||||
"""
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# OAuth 토큰 발급 (WebSocket 접속키 발급에 필요)
|
||||
logger.info("OAuth 토큰 발급 중...")
|
||||
ka.auth() # 토큰 발급
|
||||
logger.info("OAuth 토큰 발급 완료")
|
||||
|
||||
# 환경 설정에서 토큰, 앱키, 앱시크릿 가져오기
|
||||
trenv = ka.getTREnv()
|
||||
appkey = trenv.my_app
|
||||
appsecret = trenv.my_sec
|
||||
|
||||
# 토큰 및 앱키가 설정되어 있는지 확인
|
||||
if not appkey or not appsecret:
|
||||
logger.error("앱키 또는 앱시크릿이 설정되지 않았습니다.")
|
||||
return
|
||||
|
||||
# API 호출
|
||||
logger.info("WebSocket 웹소켓 접속키 발급 API 호출 시작")
|
||||
result = auth_ws_token(
|
||||
grant_type="client_credentials",
|
||||
appkey=appkey,
|
||||
appsecret=appsecret,
|
||||
env_dv="real", # 실전 환경으로 설정 (필요시 "demo"로 변경)
|
||||
)
|
||||
|
||||
# 결과 확인
|
||||
if result.empty:
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# 결과 처리
|
||||
logger.info("=== WebSocket 웹소켓 접속키 발급 결과 ===")
|
||||
logger.info("사용 가능한 컬럼: %s", result.columns.tolist())
|
||||
|
||||
# 통합 컬럼명 한글 변환 (필요한 컬럼만 자동 매핑됨)
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
|
||||
logger.info("결과:")
|
||||
print(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
171
한국투자증권(API)/examples_llm/domestic_bond/avg_unit/avg_unit.py
Normal file
171
한국투자증권(API)/examples_llm/domestic_bond/avg_unit/avg_unit.py
Normal file
@@ -0,0 +1,171 @@
|
||||
# [장내채권] 기본시세 - 장내채권 평균단가조회
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
import sys
|
||||
from typing import Optional, Tuple
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 기본시세 > 장내채권 평균단가조회 [국내채권-158]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-bond/v1/quotations/avg-unit"
|
||||
|
||||
def avg_unit(
|
||||
inqr_strt_dt: str, # 조회시작일자
|
||||
inqr_end_dt: str, # 조회종료일자
|
||||
pdno: str, # 상품번호
|
||||
prdt_type_cd: str, # 상품유형코드
|
||||
vrfc_kind_cd: str, # 검증종류코드
|
||||
NK30: str = "", # 연속조회키30
|
||||
FK100: str = "", # 연속조회검색조건100
|
||||
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output1)
|
||||
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output2)
|
||||
dataframe3: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output3)
|
||||
tr_cont: str = "",
|
||||
depth: int = 0,
|
||||
max_depth: int = 10
|
||||
) -> Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]:
|
||||
"""
|
||||
[장내채권] 기본시세
|
||||
장내채권 평균단가조회[국내주식-158]
|
||||
장내채권 평균단가조회 API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
inqr_strt_dt (str): 조회 시작 일자 (예: '20230101')
|
||||
inqr_end_dt (str): 조회 종료 일자 (예: '20230131')
|
||||
pdno (str): 상품번호, 공백: 전체, 특정종목 조회시 : 종목코드
|
||||
prdt_type_cd (str): 상품유형코드 (예: '302')
|
||||
vrfc_kind_cd (str): 검증종류코드 (예: '00')
|
||||
NK30 (str): 연속조회키30, 공백 허용
|
||||
FK100 (str): 연속조회검색조건100, 공백 허용
|
||||
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 (output1)
|
||||
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 (output2)
|
||||
dataframe3 (Optional[pd.DataFrame]): 누적 데이터프레임 (output3)
|
||||
tr_cont (str): 연속 거래 여부
|
||||
depth (int): 현재 재귀 깊이
|
||||
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
||||
|
||||
Returns:
|
||||
Tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame]: 장내채권 평균단가조회 데이터
|
||||
|
||||
Example:
|
||||
>>> df1, df2, df3 = avg_unit(
|
||||
... inqr_strt_dt='20230101',
|
||||
... inqr_end_dt='20230131',
|
||||
... pdno='KR2033022D33',
|
||||
... prdt_type_cd='302',
|
||||
... vrfc_kind_cd='00',
|
||||
... )
|
||||
>>> print(df1)
|
||||
>>> print(df2)
|
||||
>>> print(df3)
|
||||
"""
|
||||
# 필수 파라미터 검증
|
||||
if not inqr_strt_dt:
|
||||
logger.error("inqr_strt_dt is required. (e.g. '20230101')")
|
||||
raise ValueError("inqr_strt_dt is required. (e.g. '20230101')")
|
||||
|
||||
if not inqr_end_dt:
|
||||
logger.error("inqr_end_dt is required. (e.g. '20230131')")
|
||||
raise ValueError("inqr_end_dt is required. (e.g. '20230131')")
|
||||
|
||||
if not prdt_type_cd:
|
||||
logger.error("prdt_type_cd is required. (e.g. '302')")
|
||||
raise ValueError("prdt_type_cd is required. (e.g. '302')")
|
||||
|
||||
if not vrfc_kind_cd:
|
||||
logger.error("vrfc_kind_cd is required. (e.g. '00')")
|
||||
raise ValueError("vrfc_kind_cd is required. (e.g. '00')")
|
||||
|
||||
# 최대 재귀 깊이 체크
|
||||
if depth >= max_depth:
|
||||
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
||||
return (
|
||||
dataframe1 if dataframe1 is not None else pd.DataFrame(),
|
||||
dataframe2 if dataframe2 is not None else pd.DataFrame(),
|
||||
dataframe3 if dataframe3 is not None else pd.DataFrame()
|
||||
)
|
||||
|
||||
tr_id = "CTPF2005R"
|
||||
|
||||
params = {
|
||||
"INQR_STRT_DT": inqr_strt_dt,
|
||||
"INQR_END_DT": inqr_end_dt,
|
||||
"PDNO": pdno,
|
||||
"PRDT_TYPE_CD": prdt_type_cd,
|
||||
"VRFC_KIND_CD": vrfc_kind_cd,
|
||||
"CTX_AREA_NK30": NK30,
|
||||
"CTX_AREA_FK100": FK100,
|
||||
}
|
||||
|
||||
res = ka._url_fetch(API_URL, tr_id, tr_cont, params)
|
||||
|
||||
if res.isOK():
|
||||
# 연속조회 정보 업데이트
|
||||
tr_cont = res.getHeader().tr_cont
|
||||
NK30 = res.getBody().ctx_area_nk30
|
||||
FK100 = res.getBody().ctx_area_fk100
|
||||
|
||||
# output1 데이터 처리
|
||||
current_data1 = pd.DataFrame(res.getBody().output1)
|
||||
if dataframe1 is not None:
|
||||
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
||||
else:
|
||||
dataframe1 = current_data1
|
||||
|
||||
# output2 데이터 처리
|
||||
current_data2 = pd.DataFrame(res.getBody().output2)
|
||||
if dataframe2 is not None:
|
||||
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
||||
else:
|
||||
dataframe2 = current_data2
|
||||
|
||||
# output3 데이터 처리
|
||||
current_data3 = pd.DataFrame(res.getBody().output3)
|
||||
if dataframe3 is not None:
|
||||
dataframe3 = pd.concat([dataframe3, current_data3], ignore_index=True)
|
||||
else:
|
||||
dataframe3 = current_data3
|
||||
|
||||
if tr_cont in ["M", "F"]: # 다음 페이지 존재
|
||||
logger.info("Call Next page...")
|
||||
ka.smart_sleep() # 시스템 안정적 운영을 위한 지연
|
||||
return avg_unit(
|
||||
inqr_strt_dt,
|
||||
inqr_end_dt,
|
||||
pdno,
|
||||
prdt_type_cd,
|
||||
vrfc_kind_cd,
|
||||
NK30,
|
||||
FK100,
|
||||
dataframe1,
|
||||
dataframe2,
|
||||
dataframe3,
|
||||
"N",
|
||||
depth + 1,
|
||||
max_depth
|
||||
)
|
||||
else:
|
||||
logger.info("Data fetch complete.")
|
||||
return dataframe1, dataframe2, dataframe3
|
||||
else:
|
||||
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
||||
res.printError(API_URL)
|
||||
return pd.DataFrame(), pd.DataFrame(), pd.DataFrame()
|
||||
184
한국투자증권(API)/examples_llm/domestic_bond/avg_unit/chk_avg_unit.py
Normal file
184
한국투자증권(API)/examples_llm/domestic_bond/avg_unit/chk_avg_unit.py
Normal file
@@ -0,0 +1,184 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
import kis_auth as ka
|
||||
from avg_unit import avg_unit
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 기본시세 > 장내채권 평균단가조회 [국내채권-158]
|
||||
##############################################################################################
|
||||
|
||||
|
||||
# 통합 컬럼 매핑 (모든 output에서 공통 사용)
|
||||
COLUMN_MAPPING = {
|
||||
'evlu_dt': '평가일자',
|
||||
'pdno': '상품번호',
|
||||
'prdt_type_cd': '상품유형코드',
|
||||
'kis_unpr': '한국신용평가단가',
|
||||
'kbp_unpr': '한국채권평가단가',
|
||||
'nice_evlu_unpr': '한국신용정보평가단가',
|
||||
'fnp_unpr': '에프앤자산평가단가',
|
||||
'avg_evlu_unpr': '평균평가단가',
|
||||
'kis_crdt_grad_text': '한국신용평가신용등급내용',
|
||||
'kbp_crdt_grad_text': '한국채권평가신용등급내용',
|
||||
'nice_crdt_grad_text': '한국신용정보신용등급내용',
|
||||
'fnp_crdt_grad_text': '에프앤자산평가신용등급내용',
|
||||
'chng_yn': '변경여부',
|
||||
'kis_erng_rt': '한국신용평가수익율',
|
||||
'kbp_erng_rt': '한국채권평가수익율',
|
||||
'nice_evlu_erng_rt': '한국신용정보평가수익율',
|
||||
'fnp_erng_rt': '에프앤자산평가수익율',
|
||||
'avg_evlu_erng_rt': '평균평가수익율',
|
||||
'kis_rf_unpr': '한국신용평가RF단가',
|
||||
'kbp_rf_unpr': '한국채권평가RF단가',
|
||||
'nice_evlu_rf_unpr': '한국신용정보평가RF단가',
|
||||
'avg_evlu_rf_unpr': '평균평가RF단가',
|
||||
'evlu_dt': '평가일자',
|
||||
'pdno': '상품번호',
|
||||
'prdt_type_cd': '상품유형코드',
|
||||
'kis_evlu_amt': '한국신용평가평가금액',
|
||||
'kbp_evlu_amt': '한국채권평가평가금액',
|
||||
'nice_evlu_amt': '한국신용정보평가금액',
|
||||
'fnp_evlu_amt': '에프앤자산평가평가금액',
|
||||
'avg_evlu_amt': '평균평가금액',
|
||||
'chng_yn': '변경여부',
|
||||
'output3': '응답상세',
|
||||
'evlu_dt': '평가일자',
|
||||
'pdno': '상품번호',
|
||||
'prdt_type_cd': '상품유형코드',
|
||||
'kis_crcy_cd': '한국신용평가통화코드',
|
||||
'kis_evlu_unit_pric': '한국신용평가평가단위가격',
|
||||
'kis_evlu_pric': '한국신용평가평가가격',
|
||||
'kbp_crcy_cd': '한국채권평가통화코드',
|
||||
'kbp_evlu_unit_pric': '한국채권평가평가단위가격',
|
||||
'kbp_evlu_pric': '한국채권평가평가가격',
|
||||
'nice_crcy_cd': '한국신용정보통화코드',
|
||||
'nice_evlu_unit_pric': '한국신용정보평가단위가격',
|
||||
'nice_evlu_pric': '한국신용정보평가가격',
|
||||
'avg_evlu_unit_pric': '평균평가단위가격',
|
||||
'avg_evlu_pric': '평균평가가격',
|
||||
'chng_yn': '변경여부'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
[장내채권] 기본시세
|
||||
장내채권 평균단가조회[국내주식-158]
|
||||
|
||||
장내채권 평균단가조회 테스트 함수`
|
||||
|
||||
Parameters:
|
||||
- inqr_strt_dt (str): 조회시작일자 (일자 ~)
|
||||
- inqr_end_dt (str): 조회종료일자 (~ 일자)
|
||||
- pdno (str): 상품번호 (공백: 전체, 특정종목 조회시 : 종목코드)
|
||||
- prdt_type_cd (str): 상품유형코드 (Unique key(302))
|
||||
- vrfc_kind_cd (str): 검증종류코드 (Unique key(00))
|
||||
|
||||
Returns:
|
||||
- Tuple[DataFrame, ...]: 장내채권 평균단가조회 결과
|
||||
|
||||
Example:
|
||||
>>> df1, df2, df3 = avg_unit(inqr_strt_dt="20250101", inqr_end_dt="20250131", pdno="KR2033022D33", prdt_type_cd="302", vrfc_kind_cd="00")
|
||||
"""
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 토큰 발급
|
||||
logger.info("토큰 발급 중...")
|
||||
ka.auth()
|
||||
logger.info("토큰 발급 완료")
|
||||
|
||||
# API 호출
|
||||
logger.info("API 호출 시작: 장내채권 평균단가조회")
|
||||
result1, result2, result3 = avg_unit(
|
||||
inqr_strt_dt="20240101", # 조회시작일자
|
||||
inqr_end_dt="20250630", # 조회종료일자
|
||||
pdno="KR103502GA34", # 상품번호
|
||||
prdt_type_cd="302", # 상품유형코드
|
||||
vrfc_kind_cd="00", # 검증종류코드
|
||||
)
|
||||
|
||||
# 결과 확인
|
||||
results = [result1, result2, result3]
|
||||
if all(result is None or result.empty for result in results):
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# output1 결과 처리
|
||||
logger.info("=== output1 조회 ===")
|
||||
if not result1.empty:
|
||||
logger.info("사용 가능한 컬럼: %s", result1.columns.tolist())
|
||||
|
||||
# 통합 컬럼명 한글 변환 (필요한 컬럼만 자동 매핑됨)
|
||||
result1 = result1.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result1.columns:
|
||||
result1[col] = pd.to_numeric(result1[col], errors='coerce').round(2)
|
||||
|
||||
logger.info("output1 결과:")
|
||||
print(result1)
|
||||
else:
|
||||
logger.info("output1 데이터가 없습니다.")
|
||||
|
||||
# output2 결과 처리
|
||||
logger.info("=== output2 조회 ===")
|
||||
if not result2.empty:
|
||||
logger.info("사용 가능한 컬럼: %s", result2.columns.tolist())
|
||||
|
||||
# 통합 컬럼명 한글 변환 (필요한 컬럼만 자동 매핑됨)
|
||||
result2 = result2.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result2.columns:
|
||||
result2[col] = pd.to_numeric(result2[col], errors='coerce').round(2)
|
||||
|
||||
logger.info("output2 결과:")
|
||||
print(result2)
|
||||
else:
|
||||
logger.info("output2 데이터가 없습니다.")
|
||||
|
||||
# output3 결과 처리
|
||||
logger.info("=== output3 조회 ===")
|
||||
if not result3.empty:
|
||||
logger.info("사용 가능한 컬럼: %s", result3.columns.tolist())
|
||||
|
||||
# 통합 컬럼명 한글 변환 (필요한 컬럼만 자동 매핑됨)
|
||||
result3 = result3.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result3.columns:
|
||||
result3[col] = pd.to_numeric(result3[col], errors='coerce').round(2)
|
||||
|
||||
logger.info("output3 결과:")
|
||||
print(result3)
|
||||
else:
|
||||
logger.info("output3 데이터가 없습니다.")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,97 @@
|
||||
"""
|
||||
Created on 2025-07-09
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 실시간시세 > 일반채권 실시간호가 [실시간-053]
|
||||
##############################################################################################
|
||||
|
||||
def bond_asking_price(
|
||||
tr_type: str,
|
||||
tr_key: str,
|
||||
) -> tuple[dict, list[str]]:
|
||||
"""
|
||||
일반채권 실시간호가[H0BJASP0]
|
||||
일반채권 실시간호가 API를 통해 실시간 데이터를 구독합니다.
|
||||
|
||||
Args:
|
||||
tr_type (str): [필수] 구독 등록("1") 또는 해제("0") 여부를 나타냅니다.
|
||||
tr_key (str): [필수] 종목코드. 빈 문자열일 수 없습니다.
|
||||
|
||||
Returns:
|
||||
message (dict): 실시간 데이터 메시지.
|
||||
columns (list[str]): 응답 데이터의 컬럼 정보.
|
||||
|
||||
Raises:
|
||||
ValueError: tr_key가 빈 문자열인 경우 발생합니다.
|
||||
|
||||
Example:
|
||||
>>> msg, columns = bond_asking_price("1", "005930")
|
||||
>>> print(msg, columns)
|
||||
|
||||
[참고자료]
|
||||
채권 종목코드 마스터파일은 "KIS포털 - API문서 - 종목정보파일 - 장내채권 - 채권코드" 참고 부탁드립니다.
|
||||
"""
|
||||
|
||||
# 필수 파라미터 검증
|
||||
if not tr_key:
|
||||
raise ValueError("tr_key is required and cannot be an empty string")
|
||||
|
||||
tr_id = "H0BJASP0"
|
||||
|
||||
params = {
|
||||
"tr_key": tr_key,
|
||||
}
|
||||
|
||||
# 데이터 요청
|
||||
msg = ka.data_fetch(tr_id, tr_type, params)
|
||||
|
||||
# 응답 데이터 컬럼 정보
|
||||
columns = [
|
||||
"stnd_iscd",
|
||||
"stck_cntg_hour",
|
||||
"askp_ert1",
|
||||
"bidp_ert1",
|
||||
"askp1",
|
||||
"bidp1",
|
||||
"askp_rsqn1",
|
||||
"bidp_rsqn1",
|
||||
"askp_ert2",
|
||||
"bidp_ert2",
|
||||
"askp2",
|
||||
"bidp2",
|
||||
"askp_rsqn2",
|
||||
"bidp_rsqn2",
|
||||
"askp_ert3",
|
||||
"bidp_ert3",
|
||||
"askp3",
|
||||
"bidp3",
|
||||
"askp_rsqn3",
|
||||
"bidp_rsqn3",
|
||||
"askp_ert4",
|
||||
"bidp_ert4",
|
||||
"askp4",
|
||||
"bidp4",
|
||||
"askp_rsqn4",
|
||||
"bidp_rsqn4",
|
||||
"askp_ert5",
|
||||
"bidp_ert5",
|
||||
"askp5",
|
||||
"bidp5",
|
||||
"askp_rsqn52",
|
||||
"bidp_rsqn53",
|
||||
"total_askp_rsqn",
|
||||
"total_bidp_rsqn",
|
||||
]
|
||||
|
||||
return msg, columns
|
||||
@@ -0,0 +1,133 @@
|
||||
"""
|
||||
Created on 2025-07-09
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
from bond_asking_price import bond_asking_price
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 실시간시세 > 일반채권 실시간호가 [실시간-053]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
"stnd_iscd": "표준종목코드",
|
||||
"stck_cntg_hour": "주식체결시간",
|
||||
"askp_ert1": "매도호가수익률1",
|
||||
"bidp_ert1": "매수호가수익률1",
|
||||
"askp1": "매도호가1",
|
||||
"bidp1": "매수호가1",
|
||||
"askp_rsqn1": "매도호가잔량1",
|
||||
"bidp_rsqn1": "매수호가잔량1",
|
||||
"askp_ert2": "매도호가수익률2",
|
||||
"bidp_ert2": "매수호가수익률2",
|
||||
"askp2": "매도호가2",
|
||||
"bidp2": "매수호가2",
|
||||
"askp_rsqn2": "매도호가잔량2",
|
||||
"bidp_rsqn2": "매수호가잔량2",
|
||||
"askp_ert3": "매도호가수익률3",
|
||||
"bidp_ert3": "매수호가수익률3",
|
||||
"askp3": "매도호가3",
|
||||
"bidp3": "매수호가3",
|
||||
"askp_rsqn3": "매도호가잔량3",
|
||||
"bidp_rsqn3": "매수호가잔량3",
|
||||
"askp_ert4": "매도호가수익률4",
|
||||
"bidp_ert4": "매수호가수익률4",
|
||||
"askp4": "매도호가4",
|
||||
"bidp4": "매수호가4",
|
||||
"askp_rsqn4": "매도호가잔량4",
|
||||
"bidp_rsqn4": "매수호가잔량4",
|
||||
"askp_ert5": "매도호가수익률5",
|
||||
"bidp_ert5": "매수호가수익률5",
|
||||
"askp5": "매도호가5",
|
||||
"bidp5": "매수호가5",
|
||||
"askp_rsqn52": "매도호가잔량5",
|
||||
"bidp_rsqn53": "매수호가잔량5",
|
||||
"total_askp_rsqn": "총매도호가잔량",
|
||||
"total_bidp_rsqn": "총매수호가잔량"
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = [
|
||||
"매도호가수익률1", "매수호가수익률1", "매도호가1", "매수호가1", "매도호가잔량1", "매수호가잔량1",
|
||||
"매도호가수익률2", "매수호가수익률2", "매도호가2", "매수호가2", "매도호가잔량2", "매수호가잔량2",
|
||||
"매도호가수익률3", "매수호가수익률3", "매도호가3", "매수호가3", "매도호가잔량3", "매수호가잔량3",
|
||||
"매도호가수익률4", "매수호가수익률4", "매도호가4", "매수호가4", "매도호가잔량4", "매수호가잔량4",
|
||||
"매도호가수익률5", "매수호가수익률5", "매도호가5", "매수호가5", "매도호가잔량5", "매수호가잔량5",
|
||||
"총매도호가잔량", "총매수호가잔량"
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
일반채권 실시간호가
|
||||
|
||||
일반채권 실시간호가 API입니다.
|
||||
|
||||
[참고자료]
|
||||
채권 종목코드 마스터파일은 "KIS포털 - API문서 - 종목정보파일 - 장내채권 - 채권코드" 참고 부탁드립니다.
|
||||
|
||||
[호출 데이터]
|
||||
헤더와 바디 값을 합쳐 JSON 형태로 전송합니다.
|
||||
|
||||
[응답 데이터]
|
||||
1. 정상 등록 여부 (JSON)
|
||||
- JSON["body"]["msg1"] - 정상 응답 시, SUBSCRIBE SUCCESS
|
||||
- JSON["body"]["output"]["iv"] - 실시간 결과 복호화에 필요한 AES256 IV (Initialize Vector)
|
||||
- JSON["body"]["output"]["key"] - 실시간 결과 복호화에 필요한 AES256 Key
|
||||
|
||||
2. 실시간 결과 응답 ( | 로 구분되는 값)
|
||||
ex) 0|H0STCNT0|004|005930^123929^73100^5^...
|
||||
- 암호화 유무 : 0 암호화 되지 않은 데이터 / 1 암호화된 데이터
|
||||
- TR_ID : 등록한 tr_id (ex. H0STCNT0)
|
||||
- 데이터 건수 : (ex. 001 인 경우 데이터 건수 1건, 004인 경우 데이터 건수 4건)
|
||||
- 응답 데이터 : 아래 response 데이터 참조 ( ^로 구분됨)
|
||||
"""
|
||||
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 인증 토큰 발급
|
||||
ka.auth()
|
||||
ka.auth_ws()
|
||||
|
||||
# 인증(auth_ws()) 이후에 선언
|
||||
kws = ka.KISWebSocket(api_url="/tryitout")
|
||||
|
||||
# 조회
|
||||
kws.subscribe(request=bond_asking_price, data=["KR103502GA34", "KR6095572D81"])
|
||||
|
||||
# 결과 표시
|
||||
def on_result(ws, tr_id: str, result: pd.DataFrame, data_map: dict):
|
||||
try:
|
||||
# 컬럼 매핑
|
||||
|
||||
result.rename(columns=COLUMN_MAPPING, inplace=True)
|
||||
|
||||
# 숫자형 컬럼 변환
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce')
|
||||
|
||||
logging.info("결과:")
|
||||
print(result)
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"결과 처리 중 오류: {e}")
|
||||
logging.error(f"받은 데이터: {result}")
|
||||
|
||||
kws.start(on_result=on_result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,85 @@
|
||||
"""
|
||||
Created on 2025-07-09
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 실시간시세 > 일반채권 실시간체결가 [실시간-052]
|
||||
##############################################################################################
|
||||
|
||||
|
||||
def bond_ccnl(
|
||||
tr_type: str,
|
||||
tr_key: str,
|
||||
) -> tuple[dict, list[str]]:
|
||||
"""
|
||||
일반채권 실시간체결가[H0BJCNT0] 구독 함수
|
||||
한국투자증권 웹소켓 API를 통해 일반채권의 실시간 체결가 데이터를 구독합니다.
|
||||
|
||||
[참고자료]
|
||||
채권 종목코드 마스터파일은 "KIS포털 - API문서 - 종목정보파일 - 장내채권 - 채권코드" 참고 부탁드립니다.
|
||||
|
||||
Args:
|
||||
tr_type (str): [필수] 구독 등록("1") 또는 해제("0") 여부
|
||||
tr_key (str): [필수] 종목코드 (빈 문자열 불가)
|
||||
|
||||
Returns:
|
||||
message (dict): 실시간 데이터 구독 결과 메시지
|
||||
columns (list[str]): 응답 데이터의 컬럼 정보
|
||||
|
||||
Raises:
|
||||
ValueError: tr_key가 빈 문자열인 경우 발생
|
||||
|
||||
Example:
|
||||
>>> msg, columns = bond_ccnl("1", "005930")
|
||||
>>> print(msg, columns)
|
||||
|
||||
|
||||
"""
|
||||
|
||||
# 필수 파라미터 검증
|
||||
if not tr_key:
|
||||
raise ValueError("tr_key는 빈 문자열일 수 없습니다.")
|
||||
|
||||
tr_id = "H0BJCNT0"
|
||||
|
||||
params = {
|
||||
"tr_key": tr_key,
|
||||
}
|
||||
|
||||
# 데이터 구독 요청
|
||||
msg = ka.data_fetch(tr_id, tr_type, params)
|
||||
|
||||
# 응답 데이터 컬럼 정보
|
||||
columns = [
|
||||
"stnd_iscd", # 표준종목코드
|
||||
"bond_isnm", # 채권종목명
|
||||
"stck_cntg_hour", # 주식체결시간
|
||||
"prdy_vrss_sign", # 전일대비부호
|
||||
"prdy_vrss", # 전일대비
|
||||
"prdy_ctrt", # 전일대비율
|
||||
"stck_prpr", # 현재가
|
||||
"cntg_vol", # 체결거래량
|
||||
"stck_oprc", # 시가
|
||||
"stck_hgpr", # 고가
|
||||
"stck_lwpr", # 저가
|
||||
"stck_prdy_clpr", # 전일종가
|
||||
"bond_cntg_ert", # 현재수익률
|
||||
"oprc_ert", # 시가수익률
|
||||
"hgpr_ert", # 고가수익률
|
||||
"lwpr_ert", # 저가수익률
|
||||
"acml_vol", # 누적거래량
|
||||
"prdy_vol", # 전일거래량
|
||||
"cntg_type_cls_code", # 체결유형코드
|
||||
]
|
||||
|
||||
return msg, columns
|
||||
@@ -0,0 +1,112 @@
|
||||
"""
|
||||
Created on 2025-07-09
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
from bond_ccnl import bond_ccnl
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 실시간시세 > 일반채권 실시간체결가 [실시간-052]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
"stnd_iscd": "표준종목코드",
|
||||
"bond_isnm": "채권종목명",
|
||||
"stck_cntg_hour": "주식체결시간",
|
||||
"prdy_vrss_sign": "전일대비부호",
|
||||
"prdy_vrss": "전일대비",
|
||||
"prdy_ctrt": "전일대비율",
|
||||
"stck_prpr": "현재가",
|
||||
"cntg_vol": "체결거래량",
|
||||
"stck_oprc": "시가",
|
||||
"stck_hgpr": "고가",
|
||||
"stck_lwpr": "저가",
|
||||
"stck_prdy_clpr": "전일종가",
|
||||
"bond_cntg_ert": "현재수익률",
|
||||
"oprc_ert": "시가수익률",
|
||||
"hgpr_ert": "고가수익률",
|
||||
"lwpr_ert": "저가수익률",
|
||||
"acml_vol": "누적거래량",
|
||||
"prdy_vol": "전일거래량",
|
||||
"cntg_type_cls_code": "체결유형코드"
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = [
|
||||
"전일대비", "전일대비율", "현재가", "체결거래량", "시가", "고가", "저가", "전일종가",
|
||||
"현재수익률", "시가수익률", "고가수익률", "저가수익률", "누적거래량", "전일거래량"
|
||||
]
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
일반채권 실시간체결가
|
||||
|
||||
일반채권 실시간체결가 API입니다.
|
||||
|
||||
[호출 데이터]
|
||||
헤더와 바디 값을 합쳐 JSON 형태로 전송합니다.
|
||||
|
||||
[응답 데이터]
|
||||
1. 정상 등록 여부 (JSON)
|
||||
- JSON["body"]["msg1"] - 정상 응답 시, SUBSCRIBE SUCCESS
|
||||
- JSON["body"]["output"]["iv"] - 실시간 결과 복호화에 필요한 AES256 IV (Initialize Vector)
|
||||
- JSON["body"]["output"]["key"] - 실시간 결과 복호화에 필요한 AES256 Key
|
||||
|
||||
2. 실시간 결과 응답 ( | 로 구분되는 값)
|
||||
ex) 0|H0STCNT0|004|005930^123929^73100^5^...
|
||||
- 암호화 유무 : 0 암호화 되지 않은 데이터 / 1 암호화된 데이터
|
||||
- TR_ID : 등록한 tr_id (ex. H0STCNT0)
|
||||
- 데이터 건수 : (ex. 001 인 경우 데이터 건수 1건, 004인 경우 데이터 건수 4건)
|
||||
- 응답 데이터 : 아래 response 데이터 참조 ( ^로 구분됨)
|
||||
|
||||
[참고자료]
|
||||
채권 종목코드 마스터파일은 "KIS포털 - API문서 - 종목정보파일 - 장내채권 - 채권코드" 참고 부탁드립니다.
|
||||
"""
|
||||
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 인증 토큰 발급
|
||||
ka.auth()
|
||||
ka.auth_ws()
|
||||
|
||||
# 인증(auth_ws()) 이후에 선언
|
||||
kws = ka.KISWebSocket(api_url="/tryitout")
|
||||
|
||||
# 조회
|
||||
kws.subscribe(request=bond_ccnl, data=["KR103502GA34", "KR6095572D81"])
|
||||
|
||||
# 결과 표시
|
||||
def on_result(ws, tr_id: str, result: pd.DataFrame, data_map: dict):
|
||||
try:
|
||||
# 컬럼 매핑
|
||||
result.rename(columns=COLUMN_MAPPING, inplace=True)
|
||||
|
||||
# 숫자형 컬럼 변환
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce')
|
||||
|
||||
logging.info("결과:")
|
||||
print(result)
|
||||
except Exception as e:
|
||||
logging.error(f"결과 처리 중 오류: {e}")
|
||||
logging.error(f"받은 데이터: {result}")
|
||||
|
||||
kws.start(on_result=on_result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,84 @@
|
||||
"""
|
||||
Created on 2025-07-09
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 실시간시세 > 채권지수 실시간체결가 [실시간-060]
|
||||
##############################################################################################
|
||||
|
||||
|
||||
def bond_index_ccnl(
|
||||
tr_type: str,
|
||||
tr_key: str,
|
||||
) -> tuple[dict, list[str]]:
|
||||
"""
|
||||
채권지수 실시간체결가[H0BICNT0]
|
||||
채권지수 실시간체결가 API를 통해 실시간 데이터를 구독합니다.
|
||||
|
||||
Args:
|
||||
tr_type (str): [필수] 구독 등록("1") 또는 해제("0") 여부를 나타냅니다.
|
||||
tr_key (str): [필수] 구독할 종목코드. 빈 문자열이 아니어야 합니다.
|
||||
|
||||
Returns:
|
||||
message (dict): 구독 요청에 대한 응답 메시지.
|
||||
columns (list[str]): 실시간 데이터의 컬럼 정보.
|
||||
|
||||
Raises:
|
||||
ValueError: tr_key가 빈 문자열인 경우 발생합니다.
|
||||
|
||||
Example:
|
||||
>>> msg, columns = bond_index_ccnl("1", "005930")
|
||||
>>> print(msg, columns)
|
||||
|
||||
[참고자료]
|
||||
채권 종목코드 마스터파일은 "KIS포털 - API문서 - 종목정보파일 - 장내채권 - 채권코드" 참고 부탁드립니다.
|
||||
"""
|
||||
|
||||
# 필수 파라미터 검증
|
||||
if not tr_key:
|
||||
raise ValueError("tr_key is required and cannot be an empty string")
|
||||
|
||||
tr_id = "H0BICNT0"
|
||||
|
||||
params = {
|
||||
"tr_key": tr_key,
|
||||
}
|
||||
|
||||
# 데이터 구독 요청
|
||||
msg = ka.data_fetch(tr_id, tr_type, params)
|
||||
|
||||
# 응답 데이터 컬럼 정보
|
||||
columns = [
|
||||
"nmix_id", # 지수ID
|
||||
"stnd_date1", # 기준일자1
|
||||
"trnm_hour", # 전송시간
|
||||
"totl_ernn_nmix_oprc", # 총수익지수시가지수
|
||||
"totl_ernn_nmix_hgpr", # 총수익지수최고가
|
||||
"totl_ernn_nmix_lwpr", # 총수익지수최저가
|
||||
"totl_ernn_nmix", # 총수익지수
|
||||
"prdy_totl_ernn_nmix", # 전일총수익지수
|
||||
"totl_ernn_nmix_prdy_vrss", # 총수익지수전일대비
|
||||
"totl_ernn_nmix_prdy_vrss_sign", # 총수익지수전일대비부호
|
||||
"totl_ernn_nmix_prdy_ctrt", # 총수익지수전일대비율
|
||||
"clen_prc_nmix", # 순가격지수
|
||||
"mrkt_prc_nmix", # 시장가격지수
|
||||
"bond_call_rnvs_nmix", # Call재투자지수
|
||||
"bond_zero_rnvs_nmix", # Zero재투자지수
|
||||
"bond_futs_thpr", # 선물이론가격
|
||||
"bond_avrg_drtn_val", # 평균듀레이션
|
||||
"bond_avrg_cnvx_val", # 평균컨벡서티
|
||||
"bond_avrg_ytm_val", # 평균YTM
|
||||
"bond_avrg_frdl_ytm_val", # 평균선도YTM
|
||||
]
|
||||
|
||||
return msg, columns
|
||||
@@ -0,0 +1,124 @@
|
||||
"""
|
||||
Created on 2025-07-09
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
from bond_index_ccnl import bond_index_ccnl
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 실시간시세 > 채권지수 실시간체결가 [실시간-060]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
"nmix_id": "지수ID",
|
||||
"stnd_date1": "기준일자1",
|
||||
"trnm_hour": "전송시간",
|
||||
"totl_ernn_nmix_oprc": "총수익지수시가지수",
|
||||
"totl_ernn_nmix_hgpr": "총수익지수최고가",
|
||||
"totl_ernn_nmix_lwpr": "총수익지수최저가",
|
||||
"totl_ernn_nmix": "총수익지수",
|
||||
"prdy_totl_ernn_nmix": "전일총수익지수",
|
||||
"totl_ernn_nmix_prdy_vrss": "총수익지수전일대비",
|
||||
"totl_ernn_nmix_prdy_vrss_sign": "총수익지수전일대비부호",
|
||||
"totl_ernn_nmix_prdy_ctrt": "총수익지수전일대비율",
|
||||
"clen_prc_nmix": "순가격지수",
|
||||
"mrkt_prc_nmix": "시장가격지수",
|
||||
"bond_call_rnvs_nmix": "Call재투자지수",
|
||||
"bond_zero_rnvs_nmix": "Zero재투자지수",
|
||||
"bond_futs_thpr": "선물이론가격",
|
||||
"bond_avrg_drtn_val": "평균듀레이션",
|
||||
"bond_avrg_cnvx_val": "평균컨벡서티",
|
||||
"bond_avrg_ytm_val": "평균YTM",
|
||||
"bond_avrg_frdl_ytm_val": "평균선도YTM"
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = [
|
||||
"총수익지수시가지수", "총수익지수최고가", "총수익지수최저가", "총수익지수",
|
||||
"전일총수익지수", "총수익지수전일대비", "총수익지수전일대비율", "순가격지수",
|
||||
"시장가격지수", "Call재투자지수", "Zero재투자지수", "선물이론가격"]
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
채권지수 실시간체결가
|
||||
|
||||
채권지수 실시간체결가 API입니다.
|
||||
|
||||
[참고자료]
|
||||
채권 종목코드 마스터파일은 "KIS포털 - API문서 - 종목정보파일 - 장내채권 - 채권코드" 참고 부탁드립니다.
|
||||
|
||||
[호출 데이터]
|
||||
헤더와 바디 값을 합쳐 JSON 형태로 전송합니다.
|
||||
|
||||
[응답 데이터]
|
||||
1. 정상 등록 여부 (JSON)
|
||||
- JSON["body"]["msg1"] - 정상 응답 시, SUBSCRIBE SUCCESS
|
||||
- JSON["body"]["output"]["iv"] - 실시간 결과 복호화에 필요한 AES256 IV (Initialize Vector)
|
||||
- JSON["body"]["output"]["key"] - 실시간 결과 복호화에 필요한 AES256 Key
|
||||
|
||||
2. 실시간 결과 응답 ( | 로 구분되는 값)
|
||||
ex) 0|H0STCNT0|004|005930^123929^73100^5^...
|
||||
- 암호화 유무 : 0 암호화 되지 않은 데이터 / 1 암호화된 데이터
|
||||
- TR_ID : 등록한 tr_id (ex. H0STCNT0)
|
||||
- 데이터 건수 : (ex. 001 인 경우 데이터 건수 1건, 004인 경우 데이터 건수 4건)
|
||||
- 응답 데이터 : 아래 response 데이터 참조 ( ^로 구분됨)
|
||||
"""
|
||||
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 인증 토큰 발급
|
||||
ka.auth()
|
||||
ka.auth_ws()
|
||||
|
||||
# 인증(auth_ws()) 이후에 선언
|
||||
kws = ka.KISWebSocket(api_url="/tryitout")
|
||||
|
||||
# 조회
|
||||
# 한경채권지수: KBPR01, KBPR02, KBPR03, KBPR04
|
||||
# KIS채권지수: KISR01, MSBI07, KTBL10, MSBI09, MSBI10, CDIX01
|
||||
# 매경채권지수: MKFR01, MSBI01, MSBI03, MSBI10, CORP01
|
||||
kws.subscribe(request=bond_index_ccnl, data=[
|
||||
# 한경채권지수
|
||||
"KBPR01", "KBPR02", "KBPR03", "KBPR04",
|
||||
# KIS채권지수
|
||||
"KISR01", "MSBI07", "KTBL10", "MSBI09", "MSBI10", "CDIX01",
|
||||
# 매경채권지수
|
||||
"MKFR01", "MSBI01", "MSBI03", "MSBI10", "CORP01"
|
||||
])
|
||||
|
||||
# 결과 표시
|
||||
def on_result(ws, tr_id: str, result: pd.DataFrame, data_map: dict):
|
||||
try:
|
||||
# 컬럼 매핑
|
||||
|
||||
result.rename(columns=COLUMN_MAPPING, inplace=True)
|
||||
|
||||
# 숫자형 컬럼 변환
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce')
|
||||
|
||||
logging.info("결과:")
|
||||
print(result)
|
||||
except Exception as e:
|
||||
logging.error(f"결과 처리 중 오류: {e}")
|
||||
logging.error(f"받은 데이터: {result}")
|
||||
|
||||
kws.start(on_result=on_result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
112
한국투자증권(API)/examples_llm/domestic_bond/buy/buy.py
Normal file
112
한국투자증권(API)/examples_llm/domestic_bond/buy/buy.py
Normal file
@@ -0,0 +1,112 @@
|
||||
# [장내채권] 주문/계좌 - 장내채권 매수주문
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-20
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional, Tuple
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 주문/계좌 > 장내채권 매수주문 [국내주식-124]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-bond/v1/trading/buy"
|
||||
|
||||
def buy(
|
||||
cano: str,
|
||||
acnt_prdt_cd: str,
|
||||
pdno: str,
|
||||
ord_qty2: str,
|
||||
bond_ord_unpr: str,
|
||||
samt_mket_ptci_yn: str,
|
||||
bond_rtl_mket_yn: str,
|
||||
idcr_stfno: str = "",
|
||||
mgco_aptm_odno: str = "",
|
||||
ord_svr_dvsn_cd: str = "",
|
||||
ctac_tlno: str = ""
|
||||
) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
[장내채권] 주문/계좌
|
||||
장내채권 매수주문[국내주식-124]
|
||||
장내채권 매수주문 API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
cano (str): 종합계좌번호 (8자리)
|
||||
acnt_prdt_cd (str): 계좌상품코드 (2자리)
|
||||
pdno (str): 상품번호 (12자리)
|
||||
ord_qty2 (str): 주문수량2 (19자리)
|
||||
bond_ord_unpr (str): 채권주문단가 (182자리)
|
||||
samt_mket_ptci_yn (str): 소액시장참여여부 ('Y' or 'N')
|
||||
bond_rtl_mket_yn (str): 채권소매시장여부 ('Y' or 'N')
|
||||
idcr_stfno (str, optional): 유치자직원번호 (6자리). Defaults to "".
|
||||
mgco_aptm_odno (str, optional): 운용사지정주문번호 (12자리). Defaults to "".
|
||||
ord_svr_dvsn_cd (str, optional): 주문서버구분코드. Defaults to "".
|
||||
ctac_tlno (str, optional): 연락전화번호. Defaults to "".
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: 장내채권 매수주문 데이터
|
||||
|
||||
Example:
|
||||
>>> df = buy(
|
||||
... cano=trenv.my_acct,
|
||||
... acnt_prdt_cd=trenv.my_prod,
|
||||
... pdno="KR1234567890",
|
||||
... ord_qty2="10",
|
||||
... bond_ord_unpr="10000",
|
||||
... samt_mket_ptci_yn="N",
|
||||
... bond_rtl_mket_yn="Y"
|
||||
... )
|
||||
>>> print(df)
|
||||
"""
|
||||
tr_id = "TTTC0952U"
|
||||
|
||||
params = {
|
||||
"CANO": cano,
|
||||
"ACNT_PRDT_CD": acnt_prdt_cd,
|
||||
"PDNO": pdno,
|
||||
"ORD_QTY2": ord_qty2,
|
||||
"BOND_ORD_UNPR": bond_ord_unpr,
|
||||
"SAMT_MKET_PTCI_YN": samt_mket_ptci_yn,
|
||||
"BOND_RTL_MKET_YN": bond_rtl_mket_yn,
|
||||
"IDCR_STFNO": idcr_stfno,
|
||||
"MGCO_APTM_ODNO": mgco_aptm_odno,
|
||||
"ORD_SVR_DVSN_CD": ord_svr_dvsn_cd,
|
||||
"CTAC_TLNO": ctac_tlno,
|
||||
}
|
||||
|
||||
res = ka._url_fetch(api_url=API_URL,
|
||||
ptr_id=tr_id,
|
||||
tr_cont="",
|
||||
params=params,
|
||||
postFlag=True
|
||||
)
|
||||
|
||||
if res.isOK():
|
||||
if hasattr(res.getBody(), 'output'):
|
||||
output_data = res.getBody().output
|
||||
if not isinstance(output_data, list):
|
||||
output_data = [output_data]
|
||||
dataframe = pd.DataFrame(output_data)
|
||||
else:
|
||||
dataframe = pd.DataFrame()
|
||||
|
||||
logger.info("Data fetch complete.")
|
||||
return dataframe
|
||||
else:
|
||||
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
||||
res.printError(API_URL)
|
||||
return pd.DataFrame()
|
||||
114
한국투자증권(API)/examples_llm/domestic_bond/buy/chk_buy.py
Normal file
114
한국투자증권(API)/examples_llm/domestic_bond/buy/chk_buy.py
Normal file
@@ -0,0 +1,114 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-20
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
import kis_auth as ka
|
||||
from buy import buy
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 주문/계좌 > 장내채권 매수주문 [국내주식-124]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
'KRX_FWDG_ORD_ORGNO': '한국거래소전송주문조직번호',
|
||||
'ODNO': '주문번호',
|
||||
'ORD_TMD': '주문시각'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
def main():
|
||||
"""
|
||||
[장내채권] 주문/계좌`
|
||||
장내채권 매수주문[국내주식-124]
|
||||
|
||||
장내채권 매수주문 테스트 함수
|
||||
|
||||
Parameters:
|
||||
cano (str): 종합계좌번호 (8자리)
|
||||
acnt_prdt_cd (str): 계좌상품코드 (2자리)
|
||||
pdno (str): 상품번호 (12자리)
|
||||
ord_qty2 (str): 주문수량2 (19자리)
|
||||
bond_ord_unpr (str): 채권주문단가 (182자리)
|
||||
samt_mket_ptci_yn (str): 소액시장참여여부 ('Y' or 'N')
|
||||
bond_rtl_mket_yn (str): 채권소매시장여부 ('Y' or 'N')
|
||||
idcr_stfno (str, optional): 유치자직원번호 (6자리). Defaults to "".
|
||||
mgco_aptm_odno (str, optional): 운용사지정주문번호 (12자리). Defaults to "".
|
||||
|
||||
Returns:
|
||||
- DataFrame: 장내채권 매수주문 결과
|
||||
|
||||
Example:
|
||||
>>> df = main()
|
||||
>>> print(df)
|
||||
"""
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 토큰 발급
|
||||
logger.info("토큰 발급 중...")
|
||||
ka.auth()
|
||||
logger.info("토큰 발급 완료")
|
||||
|
||||
# kis_auth 모듈에서 계좌 정보 가져오기
|
||||
trenv = ka.getTREnv()
|
||||
|
||||
# API 호출
|
||||
logger.info("API 호출 시작: 장내채권 매수주문")
|
||||
result = buy(
|
||||
cano=trenv.my_acct, # 종합계좌번호
|
||||
acnt_prdt_cd=trenv.my_prod, # 계좌상품코드
|
||||
pdno="KR6095572D81", # 상품번호
|
||||
ord_qty2="10", # 주문수량
|
||||
bond_ord_unpr="9900", # 채권주문단가
|
||||
samt_mket_ptci_yn="N", # 소액시장참여여부
|
||||
bond_rtl_mket_yn="N", # 채권소매시장여부
|
||||
idcr_stfno="", # 유치자직원번호
|
||||
mgco_aptm_odno="", # 운용사지정주문번호
|
||||
ord_svr_dvsn_cd="0", # 주문서버구분코드
|
||||
ctac_tlno="", # 연락전화번호
|
||||
)
|
||||
|
||||
if result is None or result.empty:
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# 컬럼명 출력
|
||||
logger.info("사용 가능한 컬럼 목록:")
|
||||
logger.info(result.columns.tolist())
|
||||
|
||||
# 한글 컬럼명으로 변환
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 변환
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce')
|
||||
|
||||
# 결과 출력
|
||||
logger.info("=== 장내채권 매수주문 결과 ===")
|
||||
logger.info("조회된 데이터 건수: %d", len(result))
|
||||
print(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,123 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
import kis_auth as ka
|
||||
from inquire_asking_price import inquire_asking_price
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 기본시세 > 장내채권현재가(호가) [국내주식-132]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
'aspr_acpt_hour': '호가 접수 시간',
|
||||
'bond_askp1': '채권 매도호가1',
|
||||
'bond_askp2': '채권 매도호가2',
|
||||
'bond_askp3': '채권 매도호가3',
|
||||
'bond_askp4': '채권 매도호가4',
|
||||
'bond_askp5': '채권 매도호가5',
|
||||
'bond_bidp1': '채권 매수호가1',
|
||||
'bond_bidp2': '채권 매수호가2',
|
||||
'bond_bidp3': '채권 매수호가3',
|
||||
'bond_bidp4': '채권 매수호가4',
|
||||
'bond_bidp5': '채권 매수호가5',
|
||||
'askp_rsqn1': '매도호가 잔량1',
|
||||
'askp_rsqn2': '매도호가 잔량2',
|
||||
'askp_rsqn3': '매도호가 잔량3',
|
||||
'askp_rsqn4': '매도호가 잔량4',
|
||||
'askp_rsqn5': '매도호가 잔량5',
|
||||
'bidp_rsqn1': '매수호가 잔량1',
|
||||
'bidp_rsqn2': '매수호가 잔량2',
|
||||
'bidp_rsqn3': '매수호가 잔량3',
|
||||
'bidp_rsqn4': '매수호가 잔량4',
|
||||
'bidp_rsqn5': '매수호가 잔량5',
|
||||
'total_askp_rsqn': '총 매도호가 잔량',
|
||||
'total_bidp_rsqn': '총 매수호가 잔량',
|
||||
'ntby_aspr_rsqn': '순매수 호가 잔량',
|
||||
'seln_ernn_rate1': '매도 수익 비율1',
|
||||
'seln_ernn_rate2': '매도 수익 비율2',
|
||||
'seln_ernn_rate3': '매도 수익 비율3',
|
||||
'seln_ernn_rate4': '매도 수익 비율4',
|
||||
'seln_ernn_rate5': '매도 수익 비율5',
|
||||
'shnu_ernn_rate1': '매수2 수익 비율1',
|
||||
'shnu_ernn_rate2': '매수2 수익 비율2',
|
||||
'shnu_ernn_rate3': '매수2 수익 비율3',
|
||||
'shnu_ernn_rate4': '매수2 수익 비율4',
|
||||
'shnu_ernn_rate5': '매수2 수익 비율5'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
def main():
|
||||
"""
|
||||
[장내채권] 기본시세
|
||||
장내채권현재가(호가)[국내주식-132]
|
||||
|
||||
장내채권현재가(호가) 테스트 함수
|
||||
|
||||
Parameters:
|
||||
- fid_cond_mrkt_div_code (str): 시장 분류 코드 (B 입력)
|
||||
- fid_input_iscd (str): 채권종목코드
|
||||
Returns:
|
||||
- DataFrame: 장내채권현재가(호가) 결과
|
||||
|
||||
Example:
|
||||
>>> df = inquire_asking_price(fid_cond_mrkt_div_code="B", fid_input_iscd="KR2033022D33")
|
||||
"""
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 토큰 발급
|
||||
logger.info("토큰 발급 중...")
|
||||
ka.auth()
|
||||
logger.info("토큰 발급 완료")
|
||||
|
||||
# API 호출
|
||||
logger.info("API 호출 시작: 장내채권현재가(호가)")
|
||||
result = inquire_asking_price(
|
||||
fid_cond_mrkt_div_code="B", # 시장 분류 코드
|
||||
fid_input_iscd="KR2033022D33", # 채권종목코드
|
||||
)
|
||||
|
||||
if result is None or result.empty:
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# 컬럼명 출력
|
||||
logger.info("사용 가능한 컬럼 목록:")
|
||||
logger.info(result.columns.tolist())
|
||||
|
||||
# 한글 컬럼명으로 변환
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 변환
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce')
|
||||
|
||||
# 결과 출력
|
||||
logger.info("=== 장내채권현재가(호가) 결과 ===")
|
||||
logger.info("조회된 데이터 건수: %d", len(result))
|
||||
print(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,116 @@
|
||||
# [장내채권] 기본시세 - 장내채권현재가(호가)
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional, Tuple
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 기본시세 > 장내채권현재가(호가) [국내주식-132]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-bond/v1/quotations/inquire-asking-price"
|
||||
|
||||
def inquire_asking_price(
|
||||
fid_cond_mrkt_div_code: str, # 시장 분류 코드
|
||||
fid_input_iscd: str, # 채권종목코드
|
||||
tr_cont: str = "", # 연속 거래 여부
|
||||
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
||||
depth: int = 0, # 현재 재귀 깊이
|
||||
max_depth: int = 10 # 최대 재귀 깊이
|
||||
) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
[장내채권] 기본시세
|
||||
장내채권현재가(호가)[국내주식-132]
|
||||
장내채권현재가(호가) API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
fid_cond_mrkt_div_code (str): 시장 분류 코드 (B 입력)
|
||||
fid_input_iscd (str): 채권종목코드 (ex KR2033022D33)
|
||||
tr_cont (str): 연속 거래 여부 (기본값: "")
|
||||
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임 (기본값: None)
|
||||
depth (int): 현재 재귀 깊이 (기본값: 0)
|
||||
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: 장내채권현재가(호가) 데이터
|
||||
|
||||
Example:
|
||||
>>> df = inquire_asking_price(fid_cond_mrkt_div_code="B", fid_input_iscd="KR2033022D33")
|
||||
>>> print(df)
|
||||
"""
|
||||
# 필수 파라미터 검증
|
||||
if not fid_cond_mrkt_div_code:
|
||||
logger.error("fid_cond_mrkt_div_code is required. (e.g. 'B')")
|
||||
raise ValueError("fid_cond_mrkt_div_code is required. (e.g. 'B')")
|
||||
|
||||
if not fid_input_iscd:
|
||||
logger.error("fid_input_iscd is required. (e.g. 'KR2033022D33')")
|
||||
raise ValueError("fid_input_iscd is required. (e.g. 'KR2033022D33')")
|
||||
|
||||
# 최대 재귀 깊이 체크
|
||||
if depth >= max_depth:
|
||||
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
||||
return dataframe if dataframe is not None else pd.DataFrame()
|
||||
|
||||
tr_id = "FHKBJ773401C0"
|
||||
|
||||
params = {
|
||||
"FID_COND_MRKT_DIV_CODE": fid_cond_mrkt_div_code,
|
||||
"FID_INPUT_ISCD": fid_input_iscd,
|
||||
}
|
||||
|
||||
# API 호출
|
||||
res = ka._url_fetch(API_URL, tr_id, tr_cont, params)
|
||||
|
||||
if res.isOK():
|
||||
# 응답 데이터 처리
|
||||
if hasattr(res.getBody(), 'output'):
|
||||
output_data = res.getBody().output
|
||||
if not isinstance(output_data, list):
|
||||
output_data = [output_data]
|
||||
current_data = pd.DataFrame(output_data)
|
||||
else:
|
||||
current_data = pd.DataFrame()
|
||||
|
||||
# 데이터프레임 병합
|
||||
if dataframe is not None:
|
||||
dataframe = pd.concat([dataframe, current_data], ignore_index=True)
|
||||
else:
|
||||
dataframe = current_data
|
||||
|
||||
# 연속 거래 여부 확인
|
||||
tr_cont = res.getHeader().tr_cont
|
||||
|
||||
if tr_cont == "M":
|
||||
logger.info("Calling next page...")
|
||||
ka.smart_sleep()
|
||||
return inquire_asking_price(
|
||||
fid_cond_mrkt_div_code,
|
||||
fid_input_iscd,
|
||||
"N", dataframe, depth + 1, max_depth
|
||||
)
|
||||
else:
|
||||
logger.info("Data fetch complete.")
|
||||
return dataframe
|
||||
else:
|
||||
# API 에러 처리
|
||||
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
||||
res.printError(API_URL)
|
||||
return pd.DataFrame()
|
||||
@@ -0,0 +1,111 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-20
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
|
||||
import kis_auth as ka
|
||||
from inquire_balance import inquire_balance
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 주문/계좌 > 장내채권 잔고조회 [국내주식-198]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
'pdno': '상품번호',
|
||||
'buy_dt': '매수일자',
|
||||
'buy_sqno': '매수일련번호',
|
||||
'cblc_qty': '잔고수량',
|
||||
'agrx_qty': '종합과세수량',
|
||||
'sprx_qty': '분리과세수량',
|
||||
'exdt': '만기일',
|
||||
'buy_erng_rt': '매수수익율',
|
||||
'buy_unpr': '매수단가',
|
||||
'buy_amt': '매수금액',
|
||||
'ord_psbl_qty': '주문가능수량'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
def main():
|
||||
"""
|
||||
[장내채권] 주문/계좌
|
||||
장내채권 잔고조회[국내주식-198]
|
||||
|
||||
장내채권 잔고조회 테스트 함수
|
||||
|
||||
Parameters:
|
||||
- cano (str): 종합계좌번호 ()
|
||||
- acnt_prdt_cd (str): 계좌상품코드 ()
|
||||
- inqr_cndt (str): 조회조건 (00: 전체, 01: 상품번호단위)
|
||||
- pdno (str): 상품번호 (공백)
|
||||
- buy_dt (str): 매수일자 (공백)
|
||||
Returns:
|
||||
- DataFrame: 장내채권 잔고조회 결과
|
||||
|
||||
Example:
|
||||
>>> df = inquire_balance(cano=trenv.my_acct, acnt_prdt_cd=trenv.my_prod, inqr_cndt="00", pdno="", buy_dt="")
|
||||
"""
|
||||
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 토큰 발급
|
||||
logger.info("토큰 발급 중...")
|
||||
ka.auth()
|
||||
logger.info("토큰 발급 완료")
|
||||
|
||||
# kis_auth 모듈에서 계좌 정보 가져오기
|
||||
trenv = ka.getTREnv()
|
||||
|
||||
# API 호출
|
||||
logger.info("API 호출 시작: 장내채권 잔고조회")
|
||||
result = inquire_balance(
|
||||
cano=trenv.my_acct, # 종합계좌번호
|
||||
acnt_prdt_cd=trenv.my_prod, # 계좌상품코드
|
||||
inqr_cndt="00", # 조회조건
|
||||
pdno="", # 상품번호
|
||||
buy_dt="", # 매수일자
|
||||
)
|
||||
|
||||
if result is None or result.empty:
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# 컬럼명 출력
|
||||
logger.info("사용 가능한 컬럼 목록:")
|
||||
logger.info(result.columns.tolist())
|
||||
|
||||
# 한글 컬럼명으로 변환
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 변환
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce')
|
||||
|
||||
# 결과 출력
|
||||
logger.info("=== 장내채권 잔고조회 결과 ===")
|
||||
logger.info("조회된 데이터 건수: %d", len(result))
|
||||
print(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,147 @@
|
||||
# [장내채권] 주문/계좌 - 장내채권 잔고조회
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-20
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 주문/계좌 > 장내채권 잔고조회 [국내주식-198]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-bond/v1/trading/inquire-balance"
|
||||
|
||||
def inquire_balance(
|
||||
cano: str, # 종합계좌번호
|
||||
acnt_prdt_cd: str, # 계좌상품코드
|
||||
inqr_cndt: str, # 조회조건
|
||||
pdno: str, # 상품번호
|
||||
buy_dt: str, # 매수일자
|
||||
FK200: str = "", # 연속조회검색조건200
|
||||
NK200: str = "", # 연속조회키200
|
||||
tr_cont: str = "", # 연속 거래 여부
|
||||
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
||||
depth: int = 0, # 현재 재귀 깊이
|
||||
max_depth: int = 10 # 최대 재귀 깊이
|
||||
) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
[장내채권] 주문/계좌
|
||||
장내채권 잔고조회[국내주식-198]
|
||||
장내채권 잔고조회 API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
cano (str): 종합계좌번호
|
||||
acnt_prdt_cd (str): 계좌상품코드
|
||||
inqr_cndt (str): 조회조건 (00: 전체, 01: 상품번호단위)
|
||||
pdno (str): 상품번호 (공백 허용)
|
||||
buy_dt (str): 매수일자 (공백 허용)
|
||||
FK200 (str): 연속조회검색조건200
|
||||
NK200 (str): 연속조회키200
|
||||
tr_cont (str): 연속 거래 여부 (기본값: "")
|
||||
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
||||
depth (int): 현재 재귀 깊이
|
||||
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: 장내채권 잔고조회 데이터
|
||||
|
||||
Example:
|
||||
>>> df = inquire_balance(
|
||||
... cano=trenv.my_acct,
|
||||
... acnt_prdt_cd=trenv.my_prod,
|
||||
... inqr_cndt='00',
|
||||
... pdno='',
|
||||
... buy_dt='',
|
||||
... )
|
||||
>>> print(df)
|
||||
"""
|
||||
# 로깅 설정
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 필수 파라미터 검증
|
||||
if not cano:
|
||||
logger.error("cano is required. (e.g. '12345678')")
|
||||
raise ValueError("cano is required. (e.g. '12345678')")
|
||||
|
||||
if not acnt_prdt_cd:
|
||||
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
||||
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
||||
|
||||
if not inqr_cndt:
|
||||
logger.error("inqr_cndt is required. (e.g. '00')")
|
||||
raise ValueError("inqr_cndt is required. (e.g. '00')")
|
||||
|
||||
# 최대 재귀 깊이 체크
|
||||
if depth >= max_depth:
|
||||
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
||||
return dataframe if dataframe is not None else pd.DataFrame()
|
||||
|
||||
tr_id = "CTSC8407R"
|
||||
|
||||
params = {
|
||||
"CANO": cano,
|
||||
"ACNT_PRDT_CD": acnt_prdt_cd,
|
||||
"INQR_CNDT": inqr_cndt,
|
||||
"PDNO": pdno,
|
||||
"BUY_DT": buy_dt,
|
||||
"CTX_AREA_FK200": FK200,
|
||||
"CTX_AREA_NK200": NK200,
|
||||
}
|
||||
|
||||
# API 호출
|
||||
res = ka._url_fetch(API_URL, tr_id, tr_cont, params)
|
||||
|
||||
if res.isOK():
|
||||
if hasattr(res.getBody(), 'output'):
|
||||
output_data = res.getBody().output
|
||||
if not isinstance(output_data, list):
|
||||
output_data = [output_data]
|
||||
current_data = pd.DataFrame(output_data)
|
||||
else:
|
||||
current_data = pd.DataFrame()
|
||||
|
||||
if dataframe is not None:
|
||||
dataframe = pd.concat([dataframe, current_data], ignore_index=True)
|
||||
else:
|
||||
dataframe = current_data
|
||||
|
||||
tr_cont = res.getHeader().tr_cont
|
||||
NK200 = res.getBody().ctx_area_nk200
|
||||
FK200 = res.getBody().ctx_area_fk200
|
||||
|
||||
if tr_cont == "M":
|
||||
logger.info("Calling next page...")
|
||||
ka.smart_sleep()
|
||||
return inquire_balance(
|
||||
cano,
|
||||
acnt_prdt_cd,
|
||||
inqr_cndt,
|
||||
pdno,
|
||||
buy_dt,
|
||||
FK200,
|
||||
NK200,
|
||||
"N", dataframe, depth + 1, max_depth
|
||||
)
|
||||
else:
|
||||
logger.info("Data fetch complete.")
|
||||
return dataframe
|
||||
else:
|
||||
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
||||
res.printError(API_URL)
|
||||
return pd.DataFrame()
|
||||
@@ -0,0 +1,99 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
|
||||
import kis_auth as ka
|
||||
from inquire_ccnl import inquire_ccnl
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 기본시세 > 장내채권현재가(체결) [국내주식-201]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
'output1': '응답상세',
|
||||
'stck_cntg_hour': '주식 체결 시간',
|
||||
'bond_prpr': '채권 현재가',
|
||||
'bond_prdy_vrss': '채권 전일 대비',
|
||||
'prdy_vrss_sign': '전일 대비 부호',
|
||||
'prdy_ctrt': '전일 대비율',
|
||||
'cntg_vol': '체결 거래량',
|
||||
'acml_vol': '누적 거래량'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
[장내채권] 기본시세
|
||||
장내채권현재가(체결)[국내주식-201]
|
||||
|
||||
장내채권현재가(체결) 테스트 함수
|
||||
|
||||
Parameters:
|
||||
- fid_cond_mrkt_div_code (str): 조건시장분류코드 (B (업종코드))
|
||||
- fid_input_iscd (str): 입력종목코드 (채권종목코드(ex KR2033022D33))
|
||||
Returns:
|
||||
- DataFrame: 장내채권현재가(체결) 결과
|
||||
|
||||
Example:
|
||||
>>> df = inquire_ccnl(fid_cond_mrkt_div_code="B", fid_input_iscd="KR2033022D33")
|
||||
"""
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 토큰 발급
|
||||
logger.info("토큰 발급 중...")
|
||||
ka.auth()
|
||||
logger.info("토큰 발급 완료")
|
||||
|
||||
# API 호출
|
||||
logger.info("API 호출 시작: 장내채권현재가(체결)")
|
||||
result = inquire_ccnl(
|
||||
fid_cond_mrkt_div_code="B", # 조건시장분류코드
|
||||
fid_input_iscd="KR103502GA34", # 입력종목코드
|
||||
)
|
||||
|
||||
if result is None or result.empty:
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# 컬럼명 출력
|
||||
logger.info("사용 가능한 컬럼 목록:")
|
||||
logger.info(result.columns.tolist())
|
||||
|
||||
# 한글 컬럼명으로 변환
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 변환s
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce')
|
||||
|
||||
# 결과 출력
|
||||
logger.info("=== 장내채권현재가(체결) 결과 ===")
|
||||
logger.info("조회된 데이터 건수: %d", len(result))
|
||||
print(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,117 @@
|
||||
# [장내채권] 기본시세 - 장내채권현재가(체결)
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 기본시세 > 장내채권현재가(체결) [국내주식-201]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-bond/v1/quotations/inquire-ccnl"
|
||||
|
||||
def inquire_ccnl(
|
||||
fid_cond_mrkt_div_code: str, # 조건시장분류코드
|
||||
fid_input_iscd: str, # 입력종목코드
|
||||
tr_cont: str = "", # 연속 거래 여부
|
||||
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
||||
depth: int = 0, # 현재 재귀 깊이
|
||||
max_depth: int = 10 # 최대 재귀 깊이
|
||||
) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
[장내채권] 기본시세
|
||||
장내채권현재가(체결)[국내주식-201]
|
||||
장내채권현재가(체결) API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
fid_cond_mrkt_div_code (str): 조건시장분류코드 (예: 'B')
|
||||
fid_input_iscd (str): 입력종목코드 (예: 'KR2033022D33')
|
||||
tr_cont (str): 연속 거래 여부 (기본값: "")
|
||||
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임 (기본값: None)
|
||||
depth (int): 현재 재귀 깊이 (기본값: 0)
|
||||
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: 장내채권현재가(체결) 데이터
|
||||
|
||||
Example:
|
||||
>>> df = inquire_ccnl('B', 'KR2033022D33')
|
||||
>>> print(df)
|
||||
"""
|
||||
# 필수 파라미터 검증
|
||||
if not fid_cond_mrkt_div_code:
|
||||
logger.error("fid_cond_mrkt_div_code is required. (e.g. 'B')")
|
||||
raise ValueError("fid_cond_mrkt_div_code is required. (e.g. 'B')")
|
||||
|
||||
if not fid_input_iscd:
|
||||
logger.error("fid_input_iscd is required. (e.g. 'KR2033022D33')")
|
||||
raise ValueError("fid_input_iscd is required. (e.g. 'KR2033022D33')")
|
||||
|
||||
# 최대 재귀 깊이 체크
|
||||
if depth >= max_depth:
|
||||
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
||||
return dataframe if dataframe is not None else pd.DataFrame()
|
||||
|
||||
tr_id = "FHKBJ773403C0"
|
||||
|
||||
# API 요청 파라미터 설정
|
||||
params = {
|
||||
"FID_COND_MRKT_DIV_CODE": fid_cond_mrkt_div_code,
|
||||
"FID_INPUT_ISCD": fid_input_iscd,
|
||||
}
|
||||
|
||||
# API 호출
|
||||
res = ka._url_fetch(API_URL, tr_id, tr_cont, params)
|
||||
|
||||
# API 응답 처리
|
||||
if res.isOK():
|
||||
if hasattr(res.getBody(), 'output'):
|
||||
output_data = res.getBody().output
|
||||
if not isinstance(output_data, list):
|
||||
output_data = [output_data]
|
||||
current_data = pd.DataFrame(output_data)
|
||||
else:
|
||||
current_data = pd.DataFrame()
|
||||
|
||||
# 데이터프레임 병합
|
||||
if dataframe is not None:
|
||||
dataframe = pd.concat([dataframe, current_data], ignore_index=True)
|
||||
else:
|
||||
dataframe = current_data
|
||||
|
||||
# 연속 거래 여부 확인
|
||||
tr_cont = res.getHeader().tr_cont
|
||||
|
||||
if tr_cont == "M":
|
||||
logger.info("Calling next page...")
|
||||
ka.smart_sleep()
|
||||
return inquire_ccnl(
|
||||
fid_cond_mrkt_div_code,
|
||||
fid_input_iscd,
|
||||
"N", dataframe, depth + 1, max_depth
|
||||
)
|
||||
else:
|
||||
logger.info("Data fetch complete.")
|
||||
return dataframe
|
||||
else:
|
||||
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
||||
res.printError(API_URL)
|
||||
return pd.DataFrame()
|
||||
@@ -0,0 +1,156 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-20
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
import kis_auth as ka
|
||||
from inquire_daily_ccld import inquire_daily_ccld
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 주문/계좌 > 장내채권 일별체결조회 [국내주식-127]
|
||||
##############################################################################################
|
||||
|
||||
# 통합 컬럼 매핑 (모든 output에서 공통 사용)
|
||||
COLUMN_MAPPING = {
|
||||
'tot_ord_qty': '총주문수량',
|
||||
'tot_ccld_qty_smtl': '총체결수량합계',
|
||||
'tot_bond_ccld_avg_unpr': '총채권체결평균단가',
|
||||
'tot_ccld_amt_smtl': '총체결금액합계',
|
||||
'ord_dt': '주문일자',
|
||||
'odno': '주문번호',
|
||||
'orgn_odno': '원주문번호',
|
||||
'ord_dvsn_name': '주문구분명',
|
||||
'sll_buy_dvsn_cd_name': '매도매수구분코드명',
|
||||
'shtn_pdno': '단축상품번호',
|
||||
'prdt_abrv_name': '상품약어명',
|
||||
'ord_qty': '주문수량',
|
||||
'bond_ord_unpr': '채권주문단가',
|
||||
'ord_tmd': '주문시각',
|
||||
'tot_ccld_qty': '총체결수량',
|
||||
'bond_avg_unpr': '채권평균단가',
|
||||
'tot_ccld_amt': '총체결금액',
|
||||
'loan_dt': '대출일자',
|
||||
'buy_dt': '매수일자',
|
||||
'samt_mket_ptci_yn_name': '소액시장참여여부명',
|
||||
'sprx_psbl_yn_ifom': '분리과세가능여부알림',
|
||||
'ord_mdia_dvsn_name': '주문매체구분묭',
|
||||
'sll_buy_dvsn_cd': '매도매수구분코드',
|
||||
'nccs_qty': '미체결수량',
|
||||
'ord_gno_brno': '주문채번지점번호'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
def main():
|
||||
"""
|
||||
[장내채권] 주문/계좌
|
||||
장내채권 주문체결내역[국내주식-127]
|
||||
|
||||
장내채권 주문체결내역 테스트 함수
|
||||
|
||||
Parameters:
|
||||
- cano (str): 종합계좌번호 (종합계좌번호)
|
||||
- acnt_prdt_cd (str): 계좌상품코드 (계좌상품코드)
|
||||
- inqr_strt_dt (str): 조회시작일자 (일자 ~ (1주일 이내))
|
||||
- inqr_end_dt (str): 조회종료일자 (~ 일자 (조회 당일))
|
||||
- sll_buy_dvsn_cd (str): 매도매수구분코드 (%(전체), 01(매도), 02(매수))
|
||||
- sort_sqn_dvsn (str): 정렬순서구분 (01(주문순서), 02(주문역순))
|
||||
- pdno (str): 상품번호 ()
|
||||
- nccs_yn (str): 미체결여부 (N(전체), C(체결), Y(미체결))
|
||||
|
||||
Returns:
|
||||
- Tuple[DataFrame, ...]: 장내채권 주문체결내역 결과
|
||||
|
||||
Example:
|
||||
>>> df1, df2 = inquire_daily_ccld(cano=trenv.my_acct, acnt_prdt_cd=trenv.my_prod, inqr_strt_dt="20250601", inqr_end_dt="20250630", sll_buy_dvsn_cd="%", sort_sqn_dvsn="01", pdno="", nccs_yn="N", ctx_area_nk200="", ctx_area_fk200="")
|
||||
"""
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 토큰 발급
|
||||
logger.info("토큰 발급 중...")
|
||||
ka.auth()
|
||||
logger.info("토큰 발급 완료")
|
||||
|
||||
# kis_auth 모듈에서 계좌 정보 가져오기
|
||||
trenv = ka.getTREnv()
|
||||
|
||||
# API 호출
|
||||
logger.info("API 호출 시작: 장내채권 주문체결내역")
|
||||
result1, result2 = inquire_daily_ccld(
|
||||
cano=trenv.my_acct, # 종합계좌번호
|
||||
acnt_prdt_cd=trenv.my_prod, # 계좌상품코드
|
||||
inqr_strt_dt="20250601", # 조회시작일자
|
||||
inqr_end_dt="20250630", # 조회종료일자
|
||||
sll_buy_dvsn_cd="%", # 매도매수구분코드
|
||||
sort_sqn_dvsn="01", # 정렬순서구분
|
||||
pdno="", # 상품번호
|
||||
nccs_yn="N", # 미체결여부
|
||||
ctx_area_nk200="", # 연속조회키200
|
||||
ctx_area_fk200="", # 연속조회검색조건200
|
||||
)
|
||||
|
||||
# 결과 확인
|
||||
results = [result1, result2]
|
||||
if all(result is None or result.empty for result in results):
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# output1 결과 처리
|
||||
logger.info("=== output1 조회 ===")
|
||||
if not result1.empty:
|
||||
logger.info("사용 가능한 컬럼: %s", result1.columns.tolist())
|
||||
|
||||
# 통합 컬럼명 한글 변환 (필요한 컬럼만 자동 매핑됨)
|
||||
result1 = result1.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 변환s
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result1.columns:
|
||||
result1[col] = pd.to_numeric(result1[col], errors='coerce')
|
||||
|
||||
logger.info("output1 결과:")
|
||||
print(result1)
|
||||
else:
|
||||
logger.info("output1 데이터가 없습니다.")
|
||||
|
||||
# output2 결과 처리
|
||||
logger.info("=== output2 조회 ===")
|
||||
if not result2.empty:
|
||||
logger.info("사용 가능한 컬럼: %s", result2.columns.tolist())
|
||||
|
||||
# 통합 컬럼명 한글 변환 (필요한 컬럼만 자동 매핑됨)
|
||||
result2 = result2.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 변환s
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result2.columns:
|
||||
result2[col] = pd.to_numeric(result2[col], errors='coerce')
|
||||
|
||||
logger.info("output2 결과:")
|
||||
print(result2)
|
||||
else:
|
||||
logger.info("output2 데이터가 없습니다.")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,202 @@
|
||||
# [장내채권] 주문/계좌 - 장내채권 주문체결내역
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-20
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional, Tuple
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 주문/계좌 > 장내채권 일별체결조회 [국내주식-127]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-bond/v1/trading/inquire-daily-ccld"
|
||||
|
||||
def inquire_daily_ccld(
|
||||
cano: str, # 종합계좌번호
|
||||
acnt_prdt_cd: str, # 계좌상품코드
|
||||
inqr_strt_dt: str, # 조회시작일자
|
||||
inqr_end_dt: str, # 조회종료일자
|
||||
sll_buy_dvsn_cd: str, # 매도매수구분코드
|
||||
sort_sqn_dvsn: str, # 정렬순서구분
|
||||
pdno: str, # 상품번호
|
||||
nccs_yn: str, # 미체결여부
|
||||
ctx_area_nk200: str, # 연속조회키200
|
||||
ctx_area_fk200: str, # 연속조회검색조건200
|
||||
dataframe1: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output1)
|
||||
dataframe2: Optional[pd.DataFrame] = None, # 누적 데이터프레임 (output2)
|
||||
tr_cont: str = "",
|
||||
depth: int = 0,
|
||||
max_depth: int = 10
|
||||
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
||||
"""
|
||||
[장내채권] 주문/계좌
|
||||
장내채권 주문체결내역[국내주식-127]
|
||||
장내채권 주문체결내역 API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
cano (str): 종합계좌번호
|
||||
acnt_prdt_cd (str): 계좌상품코드
|
||||
inqr_strt_dt (str): 조회시작일자 (1주일 이내)
|
||||
inqr_end_dt (str): 조회종료일자 (조회 당일)
|
||||
sll_buy_dvsn_cd (str): 매도매수구분코드 (%(전체), 01(매도), 02(매수))
|
||||
sort_sqn_dvsn (str): 정렬순서구분 (01(주문순서), 02(주문역순))
|
||||
pdno (str): 상품번호
|
||||
nccs_yn (str): 미체결여부 (N(전체), C(체결), Y(미체결))
|
||||
ctx_area_nk200 (str): 연속조회키200
|
||||
ctx_area_fk200 (str): 연속조회검색조건200
|
||||
dataframe1 (Optional[pd.DataFrame]): 누적 데이터프레임 (output1)
|
||||
dataframe2 (Optional[pd.DataFrame]): 누적 데이터프레임 (output2)
|
||||
tr_cont (str): 연속 거래 여부
|
||||
depth (int): 현재 재귀 깊이
|
||||
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
||||
|
||||
Returns:
|
||||
Tuple[pd.DataFrame, pd.DataFrame]: 장내채권 주문체결내역 데이터
|
||||
|
||||
Example:
|
||||
>>> df1, df2 = inquire_daily_ccld(
|
||||
... cano=trenv.my_acct,
|
||||
... acnt_prdt_cd=trenv.my_prod,
|
||||
... inqr_strt_dt='20230101',
|
||||
... inqr_end_dt='20230107',
|
||||
... sll_buy_dvsn_cd='01',
|
||||
... sort_sqn_dvsn='01',
|
||||
... pdno='000000000001',
|
||||
... nccs_yn='N',
|
||||
... ctx_area_nk200='',
|
||||
... ctx_area_fk200=''
|
||||
... )
|
||||
>>> print(df1)
|
||||
>>> print(df2)
|
||||
"""
|
||||
# 필수 파라미터 검증
|
||||
if not cano:
|
||||
logger.error("cano is required. (e.g. '12345678')")
|
||||
raise ValueError("cano is required. (e.g. '12345678')")
|
||||
if not acnt_prdt_cd:
|
||||
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
||||
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
||||
if not inqr_strt_dt:
|
||||
logger.error("inqr_strt_dt is required. (e.g. '20230101')")
|
||||
raise ValueError("inqr_strt_dt is required. (e.g. '20230101')")
|
||||
if not inqr_end_dt:
|
||||
logger.error("inqr_end_dt is required. (e.g. '20230107')")
|
||||
raise ValueError("inqr_end_dt is required. (e.g. '20230107')")
|
||||
if not sll_buy_dvsn_cd in ["%", "01", "02"]:
|
||||
logger.error("sll_buy_dvsn_cd is required. (e.g. '01')")
|
||||
raise ValueError("sll_buy_dvsn_cd is required. (e.g. '01')")
|
||||
if not sort_sqn_dvsn in ["01", "02"]:
|
||||
logger.error("sort_sqn_dvsn is required. (e.g. '01')")
|
||||
raise ValueError("sort_sqn_dvsn is required. (e.g. '01')")
|
||||
if not nccs_yn in ["N", "C", "Y"]:
|
||||
logger.error("nccs_yn is required. (e.g. 'N')")
|
||||
raise ValueError("nccs_yn is required. (e.g. 'N')")
|
||||
|
||||
# 최대 재귀 깊이 체크
|
||||
if depth >= max_depth:
|
||||
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
||||
return dataframe1 if dataframe1 is not None else pd.DataFrame(), dataframe2 if dataframe2 is not None else pd.DataFrame()
|
||||
|
||||
tr_id = "CTSC8013R"
|
||||
|
||||
params = {
|
||||
"CANO": cano,
|
||||
"ACNT_PRDT_CD": acnt_prdt_cd,
|
||||
"INQR_STRT_DT": inqr_strt_dt,
|
||||
"INQR_END_DT": inqr_end_dt,
|
||||
"SLL_BUY_DVSN_CD": sll_buy_dvsn_cd,
|
||||
"SORT_SQN_DVSN": sort_sqn_dvsn,
|
||||
"PDNO": pdno,
|
||||
"NCCS_YN": nccs_yn,
|
||||
"CTX_AREA_NK200": ctx_area_nk200,
|
||||
"CTX_AREA_FK200": ctx_area_fk200,
|
||||
}
|
||||
|
||||
res = ka._url_fetch(API_URL, tr_id, tr_cont, params)
|
||||
|
||||
if res.isOK():
|
||||
# output1 처리
|
||||
if hasattr(res.getBody(), 'output1'):
|
||||
output_data = res.getBody().output1
|
||||
if output_data:
|
||||
# output1은 단일 객체, output2는 배열일 수 있음
|
||||
if isinstance(output_data, list):
|
||||
current_data1 = pd.DataFrame(output_data)
|
||||
else:
|
||||
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
||||
current_data1 = pd.DataFrame([output_data])
|
||||
|
||||
if dataframe1 is not None:
|
||||
dataframe1 = pd.concat([dataframe1, current_data1], ignore_index=True)
|
||||
else:
|
||||
dataframe1 = current_data1
|
||||
else:
|
||||
if dataframe1 is None:
|
||||
dataframe1 = pd.DataFrame()
|
||||
else:
|
||||
if dataframe1 is None:
|
||||
dataframe1 = pd.DataFrame()
|
||||
# output2 처리
|
||||
if hasattr(res.getBody(), 'output2'):
|
||||
output_data = res.getBody().output2
|
||||
if output_data:
|
||||
# output1은 단일 객체, output2는 배열일 수 있음
|
||||
if isinstance(output_data, list):
|
||||
current_data2 = pd.DataFrame(output_data)
|
||||
else:
|
||||
# 단일 객체인 경우 리스트로 감싸서 DataFrame 생성
|
||||
current_data2 = pd.DataFrame([output_data])
|
||||
|
||||
if dataframe2 is not None:
|
||||
dataframe2 = pd.concat([dataframe2, current_data2], ignore_index=True)
|
||||
else:
|
||||
dataframe2 = current_data2
|
||||
else:
|
||||
if dataframe2 is None:
|
||||
dataframe2 = pd.DataFrame()
|
||||
else:
|
||||
if dataframe2 is None:
|
||||
dataframe2 = pd.DataFrame()
|
||||
tr_cont = res.getHeader().tr_cont
|
||||
ctx_area_nk200 = res.getBody().ctx_area_nk200
|
||||
ctx_area_fk200 = res.getBody().ctx_area_fk200
|
||||
|
||||
if tr_cont in ["M", "F"]:
|
||||
logger.info("Calling next page...")
|
||||
ka.smart_sleep()
|
||||
return inquire_daily_ccld(
|
||||
cano,
|
||||
acnt_prdt_cd,
|
||||
inqr_strt_dt,
|
||||
inqr_end_dt,
|
||||
sll_buy_dvsn_cd,
|
||||
sort_sqn_dvsn,
|
||||
pdno,
|
||||
nccs_yn,
|
||||
ctx_area_nk200,
|
||||
ctx_area_fk200,
|
||||
"N", dataframe1, dataframe2, depth + 1, max_depth
|
||||
)
|
||||
else:
|
||||
logger.info("Data fetch complete.")
|
||||
return dataframe1, dataframe2
|
||||
else:
|
||||
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
||||
res.printError(API_URL)
|
||||
return pd.DataFrame(), pd.DataFrame()
|
||||
@@ -0,0 +1,95 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
import kis_auth as ka
|
||||
from inquire_daily_itemchartprice import inquire_daily_itemchartprice
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 기본시세 > 장내채권 기간별시세(일) [국내주식-159]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
'stck_bsop_date': '주식영업일자',
|
||||
'bond_oprc': '채권시가2',
|
||||
'bond_hgpr': '채권고가',
|
||||
'bond_lwpr': '채권저가',
|
||||
'bond_prpr': '채권현재가',
|
||||
'acml_vol': '누적거래량'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
def main():
|
||||
"""
|
||||
[장내채권] 기본시세
|
||||
장내채권 기간별시세(일)[국내주식-159]
|
||||
|
||||
장내채권 기간별시세(일) 테스트 함수
|
||||
|
||||
Parameters:
|
||||
- fid_cond_mrkt_div_code (str): 조건 시장 구분 코드 (Unique key(B))
|
||||
- fid_input_iscd (str): 입력 종목코드 (채권종목코드)
|
||||
Returns:
|
||||
- DataFrame: 장내채권 기간별시세(일) 결과
|
||||
|
||||
Example:
|
||||
>>> df = inquire_daily_itemchartprice(fid_cond_mrkt_div_code="B", fid_input_iscd="KR2033022D33")
|
||||
"""
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 토큰 발급
|
||||
logger.info("토큰 발급 중...")
|
||||
ka.auth()
|
||||
logger.info("토큰 발급 완료")
|
||||
|
||||
# API 호출
|
||||
logger.info("API 호출 시작: 장내채권 기간별시세(일)")
|
||||
result = inquire_daily_itemchartprice(
|
||||
fid_cond_mrkt_div_code="B", # 조건 시장 구분 코드
|
||||
fid_input_iscd="KR103502GA34", # 입력 종목코드
|
||||
)
|
||||
|
||||
if result is None or result.empty:
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# 컬럼명 출력
|
||||
logger.info("사용 가능한 컬럼 목록:")
|
||||
logger.info(result.columns.tolist())
|
||||
|
||||
# 한글 컬럼명으로 변환
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 변환
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce')
|
||||
|
||||
# 결과 출력
|
||||
logger.info("=== 장내채권 기간별시세(일) 결과 ===")
|
||||
logger.info("조회된 데이터 건수: %d", len(result))
|
||||
print(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,114 @@
|
||||
# [장내채권] 기본시세 - 장내채권 기간별시세(일)
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional, Tuple
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 기본시세 > 장내채권 기간별시세(일) [국내주식-159]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-bond/v1/quotations/inquire-daily-itemchartprice"
|
||||
|
||||
def inquire_daily_itemchartprice(
|
||||
fid_cond_mrkt_div_code: str, # 조건 시장 구분 코드
|
||||
fid_input_iscd: str, # 입력 종목코드
|
||||
tr_cont: str = "", # 연속 거래 여부
|
||||
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
||||
depth: int = 0, # 현재 재귀 깊이
|
||||
max_depth: int = 10 # 최대 재귀 깊이
|
||||
) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
[장내채권] 기본시세
|
||||
장내채권 기간별시세(일)[국내주식-159]
|
||||
장내채권 기간별시세(일) API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
fid_cond_mrkt_div_code (str): 조건 시장 구분 코드 (필수)
|
||||
fid_input_iscd (str): 입력 종목코드 (필수)
|
||||
tr_cont (str): 연속 거래 여부 (기본값: "")
|
||||
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임 (기본값: None)
|
||||
depth (int): 현재 재귀 깊이 (기본값: 0)
|
||||
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: 장내채권 기간별시세(일) 데이터
|
||||
|
||||
Example:
|
||||
>>> df = inquire_daily_itemchartprice("B", "KR2033022D33")
|
||||
>>> print(df)
|
||||
"""
|
||||
# 필수 파라미터 검증
|
||||
if not fid_cond_mrkt_div_code:
|
||||
logger.error("fid_cond_mrkt_div_code is required. (e.g. 'B')")
|
||||
raise ValueError("fid_cond_mrkt_div_code is required. (e.g. 'B')")
|
||||
|
||||
if not fid_input_iscd:
|
||||
logger.error("fid_input_iscd is required. (e.g. 'KR2033022D33')")
|
||||
raise ValueError("fid_input_iscd is required. (e.g. 'KR2033022D33')")
|
||||
|
||||
# 최대 재귀 깊이 체크
|
||||
if depth >= max_depth:
|
||||
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
||||
return dataframe if dataframe is not None else pd.DataFrame()
|
||||
|
||||
tr_id = "FHKBJ773701C0"
|
||||
|
||||
params = {
|
||||
"FID_COND_MRKT_DIV_CODE": fid_cond_mrkt_div_code,
|
||||
"FID_INPUT_ISCD": fid_input_iscd,
|
||||
}
|
||||
|
||||
# API 호출
|
||||
res = ka._url_fetch(API_URL, tr_id, tr_cont, params)
|
||||
|
||||
if res.isOK():
|
||||
if hasattr(res.getBody(), 'output'):
|
||||
output_data = res.getBody().output
|
||||
if not isinstance(output_data, list):
|
||||
output_data = [output_data]
|
||||
current_data = pd.DataFrame(output_data)
|
||||
else:
|
||||
current_data = pd.DataFrame()
|
||||
|
||||
# 데이터프레임 병합
|
||||
if dataframe is not None:
|
||||
dataframe = pd.concat([dataframe, current_data], ignore_index=True)
|
||||
else:
|
||||
dataframe = current_data
|
||||
|
||||
# 연속 거래 여부 확인
|
||||
tr_cont = res.getHeader().tr_cont
|
||||
|
||||
if tr_cont == "M":
|
||||
logger.info("Calling next page...")
|
||||
ka.smart_sleep()
|
||||
return inquire_daily_itemchartprice(
|
||||
fid_cond_mrkt_div_code,
|
||||
fid_input_iscd,
|
||||
"N", dataframe, depth + 1, max_depth
|
||||
)
|
||||
else:
|
||||
logger.info("Data fetch complete.")
|
||||
return dataframe
|
||||
else:
|
||||
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
||||
res.printError(API_URL)
|
||||
return pd.DataFrame()
|
||||
@@ -0,0 +1,99 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
import kis_auth as ka
|
||||
|
||||
from inquire_daily_price import inquire_daily_price
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 기본시세 > 장내채권현재가(일별) [국내주식-202]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
'output1': '응답상세',
|
||||
'stck_bsop_date': '주식영업일자',
|
||||
'bond_prpr': '채권현재가',
|
||||
'bond_prdy_vrss': '채권전일대비',
|
||||
'prdy_vrss_sign': '전일대비부호',
|
||||
'prdy_ctrt': '전일대비율',
|
||||
'acml_vol': '누적거래량',
|
||||
'bond_oprc': '채권시가2',
|
||||
'bond_hgpr': '채권고가',
|
||||
'bond_lwpr': '채권저가'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
def main():
|
||||
"""
|
||||
[장내채권] 기본시세
|
||||
장내채권현재가(일별)[국내주식-202]
|
||||
|
||||
장내채권현재가(일별) 테스트 함수
|
||||
|
||||
Parameters:
|
||||
- fid_cond_mrkt_div_code (str): 조건시장분류코드 (B (업종코드))
|
||||
- fid_input_iscd (str): 입력종목코드 (채권종목코드(ex KR2033022D33))
|
||||
Returns:
|
||||
- DataFrame: 장내채권현재가(일별) 결과
|
||||
|
||||
Example:
|
||||
>>> df = inquire_daily_price(fid_cond_mrkt_div_code="B", fid_input_iscd="KR2033022D33")
|
||||
"""
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 토큰 발급
|
||||
logger.info("토큰 발급 중...")
|
||||
ka.auth()
|
||||
logger.info("토큰 발급 완료")
|
||||
|
||||
# API 호출
|
||||
logger.info("API 호출 시작: 장내채권현재가(일별)")
|
||||
result = inquire_daily_price(
|
||||
fid_cond_mrkt_div_code="B", # 조건시장분류코드
|
||||
fid_input_iscd="KR6095572D81", # 입력종목코드
|
||||
)
|
||||
|
||||
if result is None or result.empty:
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# 컬럼명 출력
|
||||
logger.info("사용 가능한 컬럼 목록:")
|
||||
logger.info(result.columns.tolist())
|
||||
|
||||
# 한글 컬럼명으로 변환
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 변환
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce')
|
||||
|
||||
# 결과 출력
|
||||
logger.info("=== 장내채권현재가(일별) 결과 ===")
|
||||
logger.info("조회된 데이터 건수: %d", len(result))
|
||||
print(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,119 @@
|
||||
# [장내채권] 기본시세 - 장내채권현재가(일별)
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 기본시세 > 장내채권현재가(일별) [국내주식-202]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-bond/v1/quotations/inquire-daily-price"
|
||||
|
||||
def inquire_daily_price(
|
||||
fid_cond_mrkt_div_code: str, # 조건시장분류코드
|
||||
fid_input_iscd: str, # 입력종목코드
|
||||
tr_cont: str = "", # 연속 거래 여부
|
||||
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
||||
depth: int = 0, # 현재 재귀 깊이
|
||||
max_depth: int = 10 # 최대 재귀 깊이
|
||||
) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
[장내채권] 기본시세
|
||||
장내채권현재가(일별)[국내주식-202]
|
||||
장내채권현재가(일별) API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
fid_cond_mrkt_div_code (str): 조건시장분류코드 (예: 'B')
|
||||
fid_input_iscd (str): 입력종목코드 (예: 'KR2033022D33')
|
||||
tr_cont (str): 연속 거래 여부 (기본값: "")
|
||||
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임 (기본값: None)
|
||||
depth (int): 현재 재귀 깊이 (기본값: 0)
|
||||
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: 장내채권현재가(일별) 데이터
|
||||
|
||||
Example:
|
||||
>>> df = inquire_daily_price('B', 'KR2033022D33')
|
||||
>>> print(df)
|
||||
"""
|
||||
# 로깅 설정
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 필수 파라미터 검증
|
||||
if not fid_cond_mrkt_div_code:
|
||||
logger.error("fid_cond_mrkt_div_code is required. (e.g. 'B')")
|
||||
raise ValueError("fid_cond_mrkt_div_code is required. (e.g. 'B')")
|
||||
|
||||
if not fid_input_iscd:
|
||||
logger.error("fid_input_iscd is required. (e.g. 'KR2033022D33')")
|
||||
raise ValueError("fid_input_iscd is required. (e.g. 'KR2033022D33')")
|
||||
|
||||
# 최대 재귀 깊이 체크
|
||||
if depth >= max_depth:
|
||||
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
||||
return dataframe if dataframe is not None else pd.DataFrame()
|
||||
|
||||
tr_id = "FHKBJ773404C0"
|
||||
|
||||
params = {
|
||||
"FID_COND_MRKT_DIV_CODE": fid_cond_mrkt_div_code,
|
||||
"FID_INPUT_ISCD": fid_input_iscd,
|
||||
}
|
||||
|
||||
# API 호출
|
||||
res = ka._url_fetch(API_URL, tr_id, tr_cont, params)
|
||||
|
||||
if res.isOK():
|
||||
# 응답 데이터 처리
|
||||
if hasattr(res.getBody(), 'output'):
|
||||
output_data = res.getBody().output
|
||||
if not isinstance(output_data, list):
|
||||
output_data = [output_data]
|
||||
current_data = pd.DataFrame(output_data)
|
||||
else:
|
||||
current_data = pd.DataFrame()
|
||||
|
||||
# 데이터프레임 병합
|
||||
if dataframe is not None:
|
||||
dataframe = pd.concat([dataframe, current_data], ignore_index=True)
|
||||
else:
|
||||
dataframe = current_data
|
||||
|
||||
# 연속 거래 여부 확인
|
||||
tr_cont = res.getHeader().tr_cont
|
||||
|
||||
if tr_cont == "M":
|
||||
logger.info("Calling next page...")
|
||||
ka.smart_sleep()
|
||||
return inquire_daily_price(
|
||||
fid_cond_mrkt_div_code,
|
||||
fid_input_iscd,
|
||||
"N", dataframe, depth + 1, max_depth
|
||||
)
|
||||
else:
|
||||
logger.info("Data fetch complete.")
|
||||
return dataframe
|
||||
else:
|
||||
# API 에러 처리
|
||||
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
||||
res.printError(API_URL)
|
||||
return pd.DataFrame()
|
||||
@@ -0,0 +1,107 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
import kis_auth as ka
|
||||
from inquire_price import inquire_price
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 기본시세 > 장내채권현재가(시세) [국내주식-200]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
'stnd_iscd': '표준종목코드',
|
||||
'hts_kor_isnm': 'HTS한글종목명',
|
||||
'bond_prpr': '채권현재가',
|
||||
'prdy_vrss_sign': '전일대비부호',
|
||||
'bond_prdy_vrss': '채권전일대비',
|
||||
'prdy_ctrt': '전일대비율',
|
||||
'acml_vol': '누적거래량',
|
||||
'bond_prdy_clpr': '채권전일종가',
|
||||
'bond_oprc': '채권시가2',
|
||||
'bond_hgpr': '채권고가',
|
||||
'bond_lwpr': '채권저가',
|
||||
'ernn_rate': '수익비율',
|
||||
'oprc_ert': '시가2수익률',
|
||||
'hgpr_ert': '최고가수익률',
|
||||
'lwpr_ert': '최저가수익률',
|
||||
'bond_mxpr': '채권상한가',
|
||||
'bond_llam': '채권하한가'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
def main():
|
||||
"""
|
||||
[장내채권] 기본시세
|
||||
장내채권현재가(시세)[국내주식-200]
|
||||
|
||||
장내채권현재가(시세) 테스트 함수
|
||||
|
||||
Parameters:
|
||||
- fid_cond_mrkt_div_code (str): 조건시장분류코드 (B (업종코드))
|
||||
- fid_input_iscd (str): 입력종목코드 (채권종목코드(ex KR2033022D33))
|
||||
Returns:
|
||||
- DataFrame: 장내채권현재가(시세) 결과
|
||||
|
||||
Example:
|
||||
>>> df = inquire_price(fid_cond_mrkt_div_code="B", fid_input_iscd="KR2033022D33")
|
||||
"""
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 토큰 발급
|
||||
logger.info("토큰 발급 중...")
|
||||
ka.auth()
|
||||
logger.info("토큰 발급 완료")
|
||||
|
||||
# API 호출
|
||||
logger.info("API 호출 시작: 장내채권현재가(시세)")
|
||||
result = inquire_price(
|
||||
fid_cond_mrkt_div_code="B", # 조건시장분류코드
|
||||
fid_input_iscd="KR2033022D33", # 입력종목코드
|
||||
)
|
||||
|
||||
if result is None or result.empty:
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# 컬럼명 출력
|
||||
logger.info("사용 가능한 컬럼 목록:")
|
||||
logger.info(result.columns.tolist())
|
||||
|
||||
# 한글 컬럼명으로 변환
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 변환
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce')
|
||||
|
||||
# 결과 출력
|
||||
logger.info("=== 장내채권현재가(시세) 결과 ===")
|
||||
logger.info("조회된 데이터 건수: %d", len(result))
|
||||
print(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,114 @@
|
||||
# [장내채권] 기본시세 - 장내채권현재가(시세)
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 기본시세 > 장내채권현재가(시세) [국내주식-200]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-bond/v1/quotations/inquire-price"
|
||||
|
||||
def inquire_price(
|
||||
fid_cond_mrkt_div_code: str, # 조건시장분류코드
|
||||
fid_input_iscd: str, # 입력종목코드
|
||||
tr_cont: str = "", # 연속 거래 여부
|
||||
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
||||
depth: int = 0, # 현재 재귀 깊이
|
||||
max_depth: int = 10 # 최대 재귀 깊이
|
||||
) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
[장내채권] 기본시세
|
||||
장내채권현재가(시세)[국내주식-200]
|
||||
장내채권현재가(시세) API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
fid_cond_mrkt_div_code (str): 조건시장분류코드 (예: 'B')
|
||||
fid_input_iscd (str): 입력종목코드 (예: 'KR2033022D33')
|
||||
tr_cont (str): 연속 거래 여부 (기본값: "")
|
||||
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임 (기본값: None)
|
||||
depth (int): 현재 재귀 깊이 (기본값: 0)
|
||||
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: 장내채권현재가(시세) 데이터
|
||||
|
||||
Example:
|
||||
>>> df = inquire_price('B', 'KR2033022D33')
|
||||
>>> print(df)
|
||||
"""
|
||||
# 필수 파라미터 검증
|
||||
if not fid_cond_mrkt_div_code:
|
||||
logger.error("fid_cond_mrkt_div_code is required. (e.g. 'B')")
|
||||
raise ValueError("fid_cond_mrkt_div_code is required. (e.g. 'B')")
|
||||
|
||||
if not fid_input_iscd:
|
||||
logger.error("fid_input_iscd is required. (e.g. 'KR2033022D33')")
|
||||
raise ValueError("fid_input_iscd is required. (e.g. 'KR2033022D33')")
|
||||
|
||||
# 최대 재귀 깊이 체크
|
||||
if depth >= max_depth:
|
||||
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
||||
return dataframe if dataframe is not None else pd.DataFrame()
|
||||
|
||||
tr_id = "FHKBJ773400C0"
|
||||
|
||||
params = {
|
||||
"FID_COND_MRKT_DIV_CODE": fid_cond_mrkt_div_code,
|
||||
"FID_INPUT_ISCD": fid_input_iscd,
|
||||
}
|
||||
|
||||
# API 호출
|
||||
res = ka._url_fetch(API_URL, tr_id, tr_cont, params)
|
||||
|
||||
if res.isOK():
|
||||
if hasattr(res.getBody(), 'output'):
|
||||
output_data = res.getBody().output
|
||||
if not isinstance(output_data, list):
|
||||
output_data = [output_data]
|
||||
current_data = pd.DataFrame(output_data)
|
||||
else:
|
||||
current_data = pd.DataFrame()
|
||||
|
||||
# 데이터프레임 병합
|
||||
if dataframe is not None:
|
||||
dataframe = pd.concat([dataframe, current_data], ignore_index=True)
|
||||
else:
|
||||
dataframe = current_data
|
||||
|
||||
# 연속 거래 여부 확인
|
||||
tr_cont = res.getHeader().tr_cont
|
||||
|
||||
if tr_cont == "M":
|
||||
logger.info("Calling next page...")
|
||||
ka.smart_sleep()
|
||||
return inquire_price(
|
||||
fid_cond_mrkt_div_code,
|
||||
fid_input_iscd,
|
||||
"N", dataframe, depth + 1, max_depth
|
||||
)
|
||||
else:
|
||||
logger.info("Data fetch complete.")
|
||||
return dataframe
|
||||
else:
|
||||
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
||||
res.printError(API_URL)
|
||||
return pd.DataFrame()
|
||||
@@ -0,0 +1,104 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-20
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
import kis_auth as ka
|
||||
from inquire_psbl_order import inquire_psbl_order
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 주문/계좌 > 장내채권 매수가능조회 [국내주식-199]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
'ord_psbl_cash': '주문가능현금',
|
||||
'ord_psbl_sbst': '주문가능대용',
|
||||
'ruse_psbl_amt': '재사용가능금액',
|
||||
'bond_ord_unpr2': '채권주문단가2',
|
||||
'buy_psbl_amt': '매수가능금액',
|
||||
'buy_psbl_qty': '매수가능수량',
|
||||
'cma_evlu_amt': 'CMA평가금액'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
def main():
|
||||
"""
|
||||
[장내채권] 주문/계좌
|
||||
장내채권 매수가능조회[국내주식-199]
|
||||
|
||||
장내채권 매수가능조회 테스트 함수
|
||||
|
||||
Parameters:
|
||||
- cano (str): 종합계좌번호
|
||||
- acnt_prdt_cd (str): 계좌상품코드
|
||||
- pdno (str): 채권종목코드(ex KR2033022D33)
|
||||
- bond_ord_unpr (str): 채권주문단가
|
||||
Returns:
|
||||
- DataFrame: 장내채권 매수가능조회 결과
|
||||
|
||||
Example:
|
||||
>>> df = inquire_psbl_order(cano=trenv.my_acct, acnt_prdt_cd=trenv.my_prod, pdno="KR2033022D33", bond_ord_unpr="1000")
|
||||
"""
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 토큰 발급
|
||||
logger.info("토큰 발급 중...")
|
||||
ka.auth()
|
||||
logger.info("토큰 발급 완료")
|
||||
|
||||
# kis_auth 모듈에서 계좌 정보 가져오기
|
||||
trenv = ka.getTREnv()
|
||||
|
||||
# API 호출
|
||||
logger.info("API 호출 시작: 장내채권 매수가능조회")
|
||||
result = inquire_psbl_order(
|
||||
cano=trenv.my_acct, # 종합계좌번호
|
||||
acnt_prdt_cd=trenv.my_prod, # 계좌상품코드
|
||||
pdno="KR2033022D33", # 채권종목코드(ex KR2033022D33)
|
||||
bond_ord_unpr="1000", # 채권주문단가
|
||||
)
|
||||
|
||||
if result is None or result.empty:
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# 컬럼명 출력
|
||||
logger.info("사용 가능한 컬럼 목록:")
|
||||
logger.info(result.columns.tolist())
|
||||
|
||||
# 한글 컬럼명으로 변환
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 변환
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce')
|
||||
|
||||
# 결과 출력
|
||||
logger.info("=== 장내채권 매수가능조회 결과 ===")
|
||||
logger.info("조회된 데이터 건수: %d", len(result))
|
||||
print(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,132 @@
|
||||
# [장내채권] 주문/계좌 - 장내채권 매수가능조회
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-20
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 주문/계좌 > 장내채권 매수가능조회 [국내주식-199]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-bond/v1/trading/inquire-psbl-order"
|
||||
|
||||
def inquire_psbl_order(
|
||||
cano: str, # 종합계좌번호
|
||||
acnt_prdt_cd: str, # 계좌상품코드
|
||||
pdno: str, # 상품번호
|
||||
bond_ord_unpr: str, # 채권주문단가
|
||||
tr_cont: str = "", # 연속 거래 여부
|
||||
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
||||
depth: int = 0, # 현재 재귀 깊이
|
||||
max_depth: int = 10 # 최대 재귀 깊이
|
||||
) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
[장내채권] 주문/계좌
|
||||
장내채권 매수가능조회[국내주식-199]
|
||||
장내채권 매수가능조회 API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
cano (str): 종합계좌번호 (필수)
|
||||
acnt_prdt_cd (str): 계좌상품코드 (필수)
|
||||
pdno (str): 채권종목코드(ex KR2033022D33)
|
||||
bond_ord_unpr (str): 채권주문단가 (필수)
|
||||
tr_cont (str): 연속 거래 여부 (기본값: "")
|
||||
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
||||
depth (int): 현재 재귀 깊이
|
||||
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: 장내채권 매수가능조회 데이터
|
||||
|
||||
Example:
|
||||
>>> df = inquire_psbl_order("12345678", "01", "KR2033022D33", "1000")
|
||||
>>> print(df)
|
||||
"""
|
||||
# 필수 파라미터 검증
|
||||
if not cano:
|
||||
logger.error("cano is required. (e.g. '1234567890')")
|
||||
raise ValueError("cano is required. (e.g. '1234567890')")
|
||||
|
||||
if not acnt_prdt_cd:
|
||||
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
||||
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
||||
|
||||
if not pdno:
|
||||
logger.error("pdno is required. (e.g. 'KR2033022D33')")
|
||||
raise ValueError("pdno is required. (e.g. 'KR2033022D33')")
|
||||
|
||||
if not bond_ord_unpr:
|
||||
logger.error("bond_ord_unpr is required. (e.g. '1000')")
|
||||
raise ValueError("bond_ord_unpr is required. (e.g. '1000')")
|
||||
|
||||
# 최대 재귀 깊이 체크
|
||||
if depth >= max_depth:
|
||||
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
||||
return dataframe if dataframe is not None else pd.DataFrame()
|
||||
|
||||
tr_id = "TTTC8910R"
|
||||
|
||||
params = {
|
||||
"CANO": cano,
|
||||
"ACNT_PRDT_CD": acnt_prdt_cd,
|
||||
"PDNO": pdno,
|
||||
"BOND_ORD_UNPR": bond_ord_unpr,
|
||||
}
|
||||
|
||||
# API 호출
|
||||
res = ka._url_fetch(API_URL, tr_id, tr_cont, params)
|
||||
|
||||
if res.isOK():
|
||||
# 응답 데이터 처리
|
||||
if hasattr(res.getBody(), 'output'):
|
||||
output_data = res.getBody().output
|
||||
if not isinstance(output_data, list):
|
||||
output_data = [output_data]
|
||||
current_data = pd.DataFrame(output_data)
|
||||
else:
|
||||
current_data = pd.DataFrame()
|
||||
|
||||
# 데이터프레임 병합
|
||||
if dataframe is not None:
|
||||
dataframe = pd.concat([dataframe, current_data], ignore_index=True)
|
||||
else:
|
||||
dataframe = current_data
|
||||
|
||||
# 연속 거래 여부 확인
|
||||
tr_cont = res.getHeader().tr_cont
|
||||
|
||||
if tr_cont == "M":
|
||||
logger.info("Calling next page...")
|
||||
ka.smart_sleep()
|
||||
return inquire_psbl_order(
|
||||
cano,
|
||||
acnt_prdt_cd,
|
||||
pdno,
|
||||
bond_ord_unpr,
|
||||
"N", dataframe, depth + 1, max_depth
|
||||
)
|
||||
else:
|
||||
logger.info("Data fetch complete.")
|
||||
return dataframe
|
||||
else:
|
||||
# API 에러 처리
|
||||
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
||||
res.printError(API_URL)
|
||||
return pd.DataFrame()
|
||||
@@ -0,0 +1,114 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-20
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
import kis_auth as ka
|
||||
from inquire_psbl_rvsecncl import inquire_psbl_rvsecncl
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 주문/계좌 > 채권정정취소가능주문조회 [국내주식-126]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
'odno': '주문번호',
|
||||
'pdno': '상품번호',
|
||||
'rvse_cncl_dvsn_name': '정정취소구분명',
|
||||
'ord_qty': '주문수량',
|
||||
'bond_ord_unpr': '채권주문단가',
|
||||
'ord_tmd': '주문시각',
|
||||
'tot_ccld_qty': '총체결수량',
|
||||
'tot_ccld_amt': '총체결금액',
|
||||
'ord_psbl_qty': '주문가능수량',
|
||||
'orgn_odno': '원주문번호',
|
||||
'sll_buy_dvsn_cd': '매도매수구분코드',
|
||||
'ord_dvsn_cd': '주문구분코드',
|
||||
'mgco_aptm_odno': '운용사지정주문번호',
|
||||
'samt_mket_ptci_yn': '소액시장참여여부'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
def main():
|
||||
"""
|
||||
[장내채권] 주문/계좌
|
||||
채권정정취소가능주문조회[국내주식-126]
|
||||
|
||||
채권정정취소가능주문조회 테스트 함수
|
||||
|
||||
Parameters:
|
||||
- cano (str): 종합계좌번호 ()
|
||||
- acnt_prdt_cd (str): 계좌상품코드 ()
|
||||
- ord_dt (str): 주문일자 ()
|
||||
- odno (str): 주문번호 ()
|
||||
- ctx_area_fk200 (str): 연속조회검색조건200 ()
|
||||
- ctx_area_nk200 (str): 연속조회키200 ()
|
||||
Returns:
|
||||
- DataFrame: 채권정정취소가능주문조회 결과
|
||||
|
||||
Example:
|
||||
>>> df = inquire_psbl_rvsecncl(cano=trenv.my_acct, acnt_prdt_cd=trenv.my_prod, ord_dt="20250601", odno="", ctx_area_fk200="", ctx_area_nk200="")
|
||||
"""
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 토큰 발급
|
||||
logger.info("토큰 발급 중...")
|
||||
ka.auth()
|
||||
logger.info("토큰 발급 완료")
|
||||
|
||||
# kis_auth 모듈에서 계좌 정보 가져오기
|
||||
trenv = ka.getTREnv()
|
||||
|
||||
# API 호출
|
||||
logger.info("API 호출 시작: 채권정정취소가능주문조회")
|
||||
result = inquire_psbl_rvsecncl(
|
||||
cano=trenv.my_acct, # 종합계좌번호
|
||||
acnt_prdt_cd=trenv.my_prod, # 계좌상품코드
|
||||
ord_dt="", # 주문일자
|
||||
odno="", # 주문번호
|
||||
ctx_area_fk200="", # 연속조회검색조건200
|
||||
ctx_area_nk200="", # 연속조회키200
|
||||
)
|
||||
|
||||
if result is None or result.empty:
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# 컬럼명 출력
|
||||
logger.info("사용 가능한 컬럼 목록:")
|
||||
logger.info(result.columns.tolist())
|
||||
|
||||
# 한글 컬럼명으로 변환
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 변환
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce')
|
||||
|
||||
# 결과 출력
|
||||
logger.info("=== 채권정정취소가능주문조회 결과 ===")
|
||||
logger.info("조회된 데이터 건수: %d", len(result))
|
||||
print(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,136 @@
|
||||
# [장내채권] 주문/계좌 - 채권정정취소가능주문조회
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-20
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 주문/계좌 > 채권정정취소가능주문조회 [국내주식-126]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-bond/v1/trading/inquire-psbl-rvsecncl"
|
||||
|
||||
def inquire_psbl_rvsecncl(
|
||||
cano: str, # 종합계좌번호
|
||||
acnt_prdt_cd: str, # 계좌상품코드
|
||||
ord_dt: str, # 주문일자
|
||||
odno: str, # 주문번호
|
||||
ctx_area_fk200: str, # 연속조회검색조건200
|
||||
ctx_area_nk200: str, # 연속조회키200
|
||||
tr_cont: str = "", # 연속 거래 여부
|
||||
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
||||
depth: int = 0, # 현재 재귀 깊이
|
||||
max_depth: int = 10 # 최대 재귀 깊이
|
||||
) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
[장내채권] 주문/계좌
|
||||
채권정정취소가능주문조회[국내주식-126]
|
||||
채권정정취소가능주문조회 API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
cano (str): 종합계좌번호 (예: '12345678')
|
||||
acnt_prdt_cd (str): 계좌상품코드 (예: '01')
|
||||
ord_dt (str): 주문일자 (예: '20230101')
|
||||
odno (str): 주문번호 (예: '0000000001')
|
||||
ctx_area_fk200 (str): 연속조회검색조건200 (예: '조건값')
|
||||
ctx_area_nk200 (str): 연속조회키200 (예: '키값')
|
||||
tr_cont (str): 연속 거래 여부 (기본값: "")
|
||||
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
||||
depth (int): 현재 재귀 깊이
|
||||
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: 채권정정취소가능주문조회 데이터
|
||||
|
||||
Example:
|
||||
>>> df = inquire_psbl_rvsecncl(
|
||||
... cano=trenv.my_acct,
|
||||
... acnt_prdt_cd=trenv.my_prod,
|
||||
... ord_dt='20230101',
|
||||
... odno='0000000001',
|
||||
... ctx_area_fk200='조건값',
|
||||
... ctx_area_nk200='키값'
|
||||
... )
|
||||
>>> print(df)
|
||||
"""
|
||||
# 필수 파라미터 검증
|
||||
if not cano:
|
||||
logger.error("cano is required. (e.g. '12345678')")
|
||||
raise ValueError("cano is required. (e.g. '12345678')")
|
||||
if not acnt_prdt_cd:
|
||||
logger.error("acnt_prdt_cd is required. (e.g. '01')")
|
||||
raise ValueError("acnt_prdt_cd is required. (e.g. '01')")
|
||||
|
||||
|
||||
# 최대 재귀 깊이 체크
|
||||
if depth >= max_depth:
|
||||
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
||||
return dataframe if dataframe is not None else pd.DataFrame()
|
||||
|
||||
tr_id = "CTSC8035R"
|
||||
|
||||
params = {
|
||||
"CANO": cano,
|
||||
"ACNT_PRDT_CD": acnt_prdt_cd,
|
||||
"ORD_DT": ord_dt,
|
||||
"ODNO": odno,
|
||||
"CTX_AREA_FK200": ctx_area_fk200,
|
||||
"CTX_AREA_NK200": ctx_area_nk200,
|
||||
}
|
||||
|
||||
res = ka._url_fetch(API_URL, tr_id, tr_cont, params)
|
||||
|
||||
if res.isOK():
|
||||
if hasattr(res.getBody(), 'output'):
|
||||
output_data = res.getBody().output
|
||||
if not isinstance(output_data, list):
|
||||
output_data = [output_data]
|
||||
current_data = pd.DataFrame(output_data)
|
||||
else:
|
||||
current_data = pd.DataFrame()
|
||||
|
||||
if dataframe is not None:
|
||||
dataframe = pd.concat([dataframe, current_data], ignore_index=True)
|
||||
else:
|
||||
dataframe = current_data
|
||||
|
||||
tr_cont = res.getHeader().tr_cont
|
||||
ctx_area_nk200 = res.getBody().ctx_area_nk200
|
||||
ctx_area_fk200 = res.getBody().ctx_area_fk200
|
||||
|
||||
if tr_cont == "M":
|
||||
logger.info("Calling next page...")
|
||||
ka.smart_sleep()
|
||||
return inquire_psbl_rvsecncl(
|
||||
cano,
|
||||
acnt_prdt_cd,
|
||||
ord_dt,
|
||||
odno,
|
||||
ctx_area_fk200,
|
||||
ctx_area_nk200,
|
||||
"N", dataframe, depth + 1, max_depth
|
||||
)
|
||||
else:
|
||||
logger.info("Data fetch complete.")
|
||||
return dataframe
|
||||
else:
|
||||
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
||||
res.printError(API_URL)
|
||||
return pd.DataFrame()
|
||||
@@ -0,0 +1,176 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
import kis_auth as ka
|
||||
from issue_info import issue_info
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 기본시세 > 장내채권 발행정보 [국내주식-156]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
'pdno': '상품번호',
|
||||
'prdt_type_cd': '상품유형코드',
|
||||
'prdt_name': '상품명',
|
||||
'prdt_eng_name': '상품영문명',
|
||||
'ivst_heed_prdt_yn': '투자유의상품여부',
|
||||
'exts_yn': '연장여부',
|
||||
'bond_clsf_cd': '채권분류코드',
|
||||
'bond_clsf_kor_name': '채권분류한글명',
|
||||
'papr': '액면가',
|
||||
'int_mned_dvsn_cd': '이자월말구분코드',
|
||||
'rvnu_shap_cd': '매출형태코드',
|
||||
'issu_amt': '발행금액',
|
||||
'lstg_rmnd': '상장잔액',
|
||||
'int_dfrm_mcnt': '이자지급개월수',
|
||||
'bond_int_dfrm_mthd_cd': '채권이자지급방법코드',
|
||||
'splt_rdpt_rcnt': '분할상환횟수',
|
||||
'prca_dfmt_term_mcnt': '원금거치기간개월수',
|
||||
'int_anap_dvsn_cd': '이자선후급구분코드',
|
||||
'bond_rght_dvsn_cd': '채권권리구분코드',
|
||||
'prdt_pclc_text': '상품특성내용',
|
||||
'prdt_abrv_name': '상품약어명',
|
||||
'prdt_eng_abrv_name': '상품영문약어명',
|
||||
'sprx_psbl_yn': '분리과세가능여부',
|
||||
'pbff_pplc_ofrg_mthd_cd': '공모사모모집방법코드',
|
||||
'cmco_cd': '주간사코드',
|
||||
'issu_istt_cd': '발행기관코드',
|
||||
'issu_istt_name': '발행기관명',
|
||||
'pnia_dfrm_agcy_istt_cd': '원리금지급대행기관코드',
|
||||
'dsct_ec_rt': '할인할증율',
|
||||
'srfc_inrt': '표면이율',
|
||||
'expd_rdpt_rt': '만기상환율',
|
||||
'expd_asrc_erng_rt': '만기보장수익율',
|
||||
'bond_grte_istt_name': '채권보증기관명',
|
||||
'int_dfrm_day_type_cd': '이자지급일유형코드',
|
||||
'ksd_int_calc_unit_cd': '증권예탁결제원이자계산단위코드',
|
||||
'int_wunt_uder_prcs_dvsn_cd': '이자원화단위미만처리구분코드',
|
||||
'rvnu_dt': '매출일자',
|
||||
'issu_dt': '발행일자',
|
||||
'lstg_dt': '상장일자',
|
||||
'expd_dt': '만기일자',
|
||||
'rdpt_dt': '상환일자',
|
||||
'sbst_pric': '대용가격',
|
||||
'rgbf_int_dfrm_dt': '직전이자지급일자',
|
||||
'nxtm_int_dfrm_dt': '차기이자지급일자',
|
||||
'frst_int_dfrm_dt': '최초이자지급일자',
|
||||
'ecis_pric': '행사가격',
|
||||
'rght_stck_std_pdno': '권리주식표준상품번호',
|
||||
'ecis_opng_dt': '행사개시일자',
|
||||
'ecis_end_dt': '행사종료일자',
|
||||
'bond_rvnu_mthd_cd': '채권매출방법코드',
|
||||
'oprt_stfno': '조작직원번호',
|
||||
'oprt_stff_name': '조작직원명',
|
||||
'rgbf_int_dfrm_wday': '직전이자지급요일',
|
||||
'nxtm_int_dfrm_wday': '차기이자지급요일',
|
||||
'kis_crdt_grad_text': '한국신용평가신용등급내용',
|
||||
'kbp_crdt_grad_text': '한국채권평가신용등급내용',
|
||||
'nice_crdt_grad_text': '한국신용정보신용등급내용',
|
||||
'fnp_crdt_grad_text': '에프앤자산평가신용등급내용',
|
||||
'dpsi_psbl_yn': '예탁가능여부',
|
||||
'pnia_int_calc_unpr': '원리금이자계산단가',
|
||||
'prcm_idx_bond_yn': '물가지수채권여부',
|
||||
'expd_exts_srdp_rcnt': '만기연장분할상환횟수',
|
||||
'expd_exts_srdp_rt': '만기연장분할상환율',
|
||||
'loan_psbl_yn': '대출가능여부',
|
||||
'grte_dvsn_cd': '보증구분코드',
|
||||
'fnrr_rank_dvsn_cd': '선후순위구분코드',
|
||||
'krx_lstg_abol_dvsn_cd': '한국거래소상장폐지구분코드',
|
||||
'asst_rqdi_dvsn_cd': '자산유동화구분코드',
|
||||
'opcb_dvsn_cd': '옵션부사채구분코드',
|
||||
'crfd_item_yn': '크라우드펀딩종목여부',
|
||||
'crfd_item_rstc_cclc_dt': '크라우드펀딩종목제한해지일자',
|
||||
'bond_nmpr_unit_pric': '채권호가단위가격',
|
||||
'ivst_heed_bond_dvsn_name': '투자유의채권구분명',
|
||||
'add_erng_rt': '추가수익율',
|
||||
'add_erng_rt_aply_dt': '추가수익율적용일자',
|
||||
'bond_tr_stop_dvsn_cd': '채권거래정지구분코드',
|
||||
'ivst_heed_bond_dvsn_cd': '투자유의채권구분코드',
|
||||
'pclr_cndt_text': '특이조건내용',
|
||||
'hbbd_yn': '하이브리드채권여부',
|
||||
'cdtl_cptl_scty_type_cd': '조건부자본증권유형코드',
|
||||
'elec_scty_yn': '전자증권여부',
|
||||
'sq1_clop_ecis_opng_dt': '1차콜옵션행사개시일자',
|
||||
'frst_erlm_stfno': '최초등록직원번호',
|
||||
'frst_erlm_dt': '최초등록일자',
|
||||
'frst_erlm_tmd': '최초등록시각',
|
||||
'tlg_rcvg_dtl_dtime': '전문수신상세일시'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
def main():
|
||||
"""
|
||||
[장내채권] 기본시세
|
||||
장내채권 발행정보[국내주식-156]
|
||||
|
||||
장내채권 발행정보 테스트 함수
|
||||
|
||||
Parameters:
|
||||
- pdno (str): 사용자권한정보 (채권 종목번호(ex. KR6449111CB8))
|
||||
- prdt_type_cd (str): 거래소코드 (Unique key(302))
|
||||
Returns:
|
||||
- DataFrame: 장내채권 발행정보 결과
|
||||
|
||||
Example:
|
||||
>>> df = issue_info(pdno="KR6449111CB8", prdt_type_cd="302")
|
||||
"""
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 토큰 발급
|
||||
logger.info("토큰 발급 중...")
|
||||
ka.auth()
|
||||
logger.info("토큰 발급 완료")
|
||||
|
||||
# API 호출
|
||||
logger.info("API 호출 시작: 장내채권 발행정보")
|
||||
result = issue_info(
|
||||
pdno="KR6449111CB8", # 사용자권한정보
|
||||
prdt_type_cd="302", # 거래소코드
|
||||
)
|
||||
|
||||
if result is None or result.empty:
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# 컬럼명 출력
|
||||
logger.info("사용 가능한 컬럼 목록:")
|
||||
logger.info(result.columns.tolist())
|
||||
|
||||
# 한글 컬럼명으로 변환
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 변환
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce')
|
||||
|
||||
# 결과 출력
|
||||
logger.info("=== 장내채권 발행정보 결과 ===")
|
||||
logger.info("조회된 데이터 건수: %d", len(result))
|
||||
print(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
121
한국투자증권(API)/examples_llm/domestic_bond/issue_info/issue_info.py
Normal file
121
한국투자증권(API)/examples_llm/domestic_bond/issue_info/issue_info.py
Normal file
@@ -0,0 +1,121 @@
|
||||
# [장내채권] 기본시세 - 장내채권 발행정보
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 기본시세 > 장내채권 발행정보 [국내주식-156]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-bond/v1/quotations/issue-info"
|
||||
|
||||
def issue_info(
|
||||
pdno: str, # 사용자권한정보
|
||||
prdt_type_cd: str, # 거래소코드
|
||||
tr_cont: str = "", # 연속 거래 여부
|
||||
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
||||
depth: int = 0, # 현재 재귀 깊이
|
||||
max_depth: int = 10 # 최대 재귀 깊이
|
||||
) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
[장내채권] 기본시세
|
||||
장내채권 발행정보[국내주식-156]
|
||||
장내채권 발행정보 API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
pdno (str): 채권 종목번호(ex. KR6449111CB8)
|
||||
prdt_type_cd (str): Unique key(302)
|
||||
tr_cont (str): 연속 거래 여부
|
||||
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
||||
depth (int): 현재 재귀 깊이
|
||||
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: 장내채권 발행정보 데이터
|
||||
|
||||
Example:
|
||||
>>> df = issue_info("KR6449111CB8", "302")
|
||||
>>> print(df)
|
||||
"""
|
||||
# 로깅 설정
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 필수 파라미터 검증
|
||||
if not pdno:
|
||||
logger.error("pdno is required. (e.g. 'KR6449111CB8')")
|
||||
raise ValueError("pdno is required. (e.g. 'KR6449111CB8')")
|
||||
|
||||
if not prdt_type_cd:
|
||||
logger.error("prdt_type_cd is required. (e.g. '302')")
|
||||
raise ValueError("prdt_type_cd is required. (e.g. '302')")
|
||||
|
||||
# 최대 재귀 깊이 체크
|
||||
if depth >= max_depth:
|
||||
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
||||
return dataframe if dataframe is not None else pd.DataFrame()
|
||||
|
||||
# API 호출 URL 및 거래 ID 설정
|
||||
tr_id = "CTPF1101R"
|
||||
|
||||
# 요청 파라미터 설정
|
||||
params = {
|
||||
"PDNO": pdno,
|
||||
"PRDT_TYPE_CD": prdt_type_cd,
|
||||
}
|
||||
|
||||
# API 호출
|
||||
res = ka._url_fetch(API_URL, tr_id, tr_cont, params)
|
||||
|
||||
# API 호출 성공 여부 확인
|
||||
if res.isOK():
|
||||
# 응답 데이터 처리
|
||||
if hasattr(res.getBody(), 'output'):
|
||||
output_data = res.getBody().output
|
||||
if not isinstance(output_data, list):
|
||||
output_data = [output_data]
|
||||
current_data = pd.DataFrame(output_data)
|
||||
else:
|
||||
current_data = pd.DataFrame()
|
||||
|
||||
# 데이터프레임 병합
|
||||
if dataframe is not None:
|
||||
dataframe = pd.concat([dataframe, current_data], ignore_index=True)
|
||||
else:
|
||||
dataframe = current_data
|
||||
|
||||
# 연속 거래 여부 확인
|
||||
tr_cont = res.getHeader().tr_cont
|
||||
if tr_cont == "M":
|
||||
logger.info("Calling next page...")
|
||||
ka.smart_sleep()
|
||||
return issue_info(
|
||||
pdno,
|
||||
prdt_type_cd,
|
||||
"N", dataframe, depth + 1, max_depth
|
||||
)
|
||||
else:
|
||||
logger.info("Data fetch complete.")
|
||||
return dataframe
|
||||
else:
|
||||
# API 호출 실패 시 에러 로그 출력
|
||||
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
||||
res.printError(API_URL)
|
||||
return pd.DataFrame()
|
||||
@@ -0,0 +1,115 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-20
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
import kis_auth as ka
|
||||
from order_rvsecncl import order_rvsecncl
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 주문/계좌 > 장내채권 정정취소주문 [국내주식-125]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
'KRX_FWDG_ORD_ORGNO': '한국거래소전송주문조직번호',
|
||||
'ODNO': '주문번호',
|
||||
'ORD_TMD': '주문시각'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
def main():
|
||||
"""
|
||||
[장내채권] 주문/계좌
|
||||
장내채권 정정취소주문[국내주식-125]
|
||||
|
||||
장내채권 정정취소주문 테스트 함수
|
||||
|
||||
Parameters:
|
||||
cano (str): 종합계좌번호
|
||||
acnt_prdt_cd (str): 계좌상품코드
|
||||
pdno (str): 상품번호
|
||||
orgn_odno (str): 원주문번호
|
||||
ord_qty2 (str): 주문수량2
|
||||
bond_ord_unpr (str): 채권주문단가
|
||||
qty_all_ord_yn (str): 잔량전부주문여부
|
||||
rvse_cncl_dvsn_cd (str): 정정취소구분코드
|
||||
mgco_aptm_odno (str): 운용사지정주문번호
|
||||
ord_svr_dvsn_cd (str): 주문서버구분코드
|
||||
ctac_tlno (str): 연락전화번호
|
||||
|
||||
Returns:
|
||||
- DataFrame: 장내채권 정정취소주문 결과
|
||||
|
||||
Example:
|
||||
>>> df = main()
|
||||
>>> print(df)
|
||||
"""
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 토큰 발급
|
||||
logger.info("토큰 발급 중...")
|
||||
ka.auth()
|
||||
logger.info("토큰 발급 완료")
|
||||
|
||||
# kis_auth 모듈에서 계좌 정보 가져오기
|
||||
trenv = ka.getTREnv()
|
||||
|
||||
# API 호출
|
||||
logger.info("API 호출 시작: 장내채권 정정취소주문")
|
||||
result = order_rvsecncl(
|
||||
cano=trenv.my_acct,
|
||||
acnt_prdt_cd=trenv.my_prod,
|
||||
pdno="KR6095572D81",
|
||||
orgn_odno="0004357900", # 실제 테스트 시 유효한 원주문번호로 변경해야 합니다.
|
||||
ord_qty2="1", # 정정/취소 수량
|
||||
bond_ord_unpr="10470", # 정정 단가
|
||||
qty_all_ord_yn="Y", # 잔량 전부 주문 여부
|
||||
rvse_cncl_dvsn_cd="01", # 01: 정정, 02: 취소
|
||||
mgco_aptm_odno="",
|
||||
ord_svr_dvsn_cd="0",
|
||||
ctac_tlno="",
|
||||
)
|
||||
|
||||
if result is None or result.empty:
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# 컬럼명 출력
|
||||
logger.info("사용 가능한 컬럼 목록:")
|
||||
logger.info(result.columns.tolist())
|
||||
|
||||
# 한글 컬럼명으로 변환
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 변환
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce')
|
||||
|
||||
# 결과 출력
|
||||
logger.info("=== 장내채권 정정취소주문 결과 ===")
|
||||
logger.info("조회된 데이터 건수: %d", len(result))
|
||||
print(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,113 @@
|
||||
# [장내채권] 주문/계좌 - 장내채권 정정취소주문
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-20
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 주문/계좌 > 장내채권 정정취소주문 [국내주식-125]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-bond/v1/trading/order-rvsecncl"
|
||||
|
||||
def order_rvsecncl(
|
||||
cano: str,
|
||||
acnt_prdt_cd: str,
|
||||
pdno: str,
|
||||
orgn_odno: str,
|
||||
ord_qty2: str,
|
||||
bond_ord_unpr: str,
|
||||
qty_all_ord_yn: str,
|
||||
rvse_cncl_dvsn_cd: str,
|
||||
mgco_aptm_odno: str = "",
|
||||
ord_svr_dvsn_cd: str = "0",
|
||||
ctac_tlno: str = ""
|
||||
) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
[장내채권] 주문/계좌
|
||||
장내채권 정정취소주문[국내주식-125]
|
||||
장내채권 정정취소주문 API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
cano (str): 종합계좌번호
|
||||
acnt_prdt_cd (str): 계좌상품코드
|
||||
pdno (str): 상품번호
|
||||
orgn_odno (str): 원주문번호
|
||||
ord_qty2 (str): 주문수량2
|
||||
bond_ord_unpr (str): 채권주문단가
|
||||
qty_all_ord_yn (str): 잔량전부주문여부
|
||||
rvse_cncl_dvsn_cd (str): 정정취소구분코드
|
||||
mgco_aptm_odno (str, optional): 운용사지정주문번호. Defaults to "".
|
||||
ord_svr_dvsn_cd (str, optional): 주문서버구분코드. Defaults to "0".
|
||||
ctac_tlno (str, optional): 연락전화번호. Defaults to "".
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: 장내채권 정정취소주문 데이터
|
||||
|
||||
Example:
|
||||
>>> df = order_rvsecncl(
|
||||
... cano=trenv.my_acct,
|
||||
... acnt_prdt_cd=trenv.my_prod,
|
||||
... pdno="KR6095572D81",
|
||||
... orgn_odno="0000015402",
|
||||
... ord_qty2="2",
|
||||
... bond_ord_unpr="10460",
|
||||
... qty_all_ord_yn="Y",
|
||||
... rvse_cncl_dvsn_cd="01"
|
||||
... )
|
||||
>>> print(df)
|
||||
"""
|
||||
tr_id = "TTTC0953U"
|
||||
|
||||
params = {
|
||||
"CANO": cano,
|
||||
"ACNT_PRDT_CD": acnt_prdt_cd,
|
||||
"PDNO": pdno,
|
||||
"ORGN_ODNO": orgn_odno,
|
||||
"ORD_QTY2": ord_qty2,
|
||||
"BOND_ORD_UNPR": bond_ord_unpr,
|
||||
"QTY_ALL_ORD_YN": qty_all_ord_yn,
|
||||
"RVSE_CNCL_DVSN_CD": rvse_cncl_dvsn_cd,
|
||||
"MGCO_APTM_ODNO": mgco_aptm_odno,
|
||||
"ORD_SVR_DVSN_CD": ord_svr_dvsn_cd,
|
||||
"CTAC_TLNO": ctac_tlno
|
||||
}
|
||||
|
||||
res = ka._url_fetch(api_url=API_URL,
|
||||
ptr_id=tr_id,
|
||||
tr_cont="",
|
||||
params=params,
|
||||
postFlag=True
|
||||
)
|
||||
|
||||
if res.isOK():
|
||||
if hasattr(res.getBody(), 'output'):
|
||||
output_data = res.getBody().output
|
||||
if not isinstance(output_data, list):
|
||||
output_data = [output_data]
|
||||
dataframe = pd.DataFrame(output_data)
|
||||
else:
|
||||
dataframe = pd.DataFrame()
|
||||
|
||||
logger.info("Data fetch complete.")
|
||||
return dataframe
|
||||
else:
|
||||
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
||||
res.printError(API_URL)
|
||||
return pd.DataFrame()
|
||||
@@ -0,0 +1,171 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
import kis_auth as ka
|
||||
from search_bond_info import search_bond_info
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 기본시세 > 장내채권 기본조회 [국내주식-129]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
'pdno': '상품번호',
|
||||
'prdt_type_cd': '상품유형코드',
|
||||
'ksd_bond_item_name': '증권예탁결제원채권종목명',
|
||||
'ksd_bond_item_eng_name': '증권예탁결제원채권종목영문명',
|
||||
'ksd_bond_lstg_type_cd': '증권예탁결제원채권상장유형코드',
|
||||
'ksd_ofrg_dvsn_cd': '증권예탁결제원모집구분코드',
|
||||
'ksd_bond_int_dfrm_dvsn_cd': '증권예탁결제원채권이자지급구분',
|
||||
'issu_dt': '발행일자',
|
||||
'rdpt_dt': '상환일자',
|
||||
'rvnu_dt': '매출일자',
|
||||
'iso_crcy_cd': '통화코드',
|
||||
'mdwy_rdpt_dt': '중도상환일자',
|
||||
'ksd_rcvg_bond_dsct_rt': '증권예탁결제원수신채권할인율',
|
||||
'ksd_rcvg_bond_srfc_inrt': '증권예탁결제원수신채권표면이율',
|
||||
'bond_expd_rdpt_rt': '채권만기상환율',
|
||||
'ksd_prca_rdpt_mthd_cd': '증권예탁결제원원금상환방법코드',
|
||||
'int_caltm_mcnt': '이자계산기간개월수',
|
||||
'ksd_int_calc_unit_cd': '증권예탁결제원이자계산단위코드',
|
||||
'uval_cut_dvsn_cd': '절상절사구분코드',
|
||||
'uval_cut_dcpt_dgit': '절상절사소수점자릿수',
|
||||
'ksd_dydv_caltm_aply_dvsn_cd': '증권예탁결제원일할계산기간적용',
|
||||
'dydv_calc_dcnt': '일할계산일수',
|
||||
'bond_expd_asrc_erng_rt': '채권만기보장수익율',
|
||||
'padf_plac_hdof_name': '원리금지급장소본점명',
|
||||
'lstg_dt': '상장일자',
|
||||
'lstg_abol_dt': '상장폐지일자',
|
||||
'ksd_bond_issu_mthd_cd': '증권예탁결제원채권발행방법코드',
|
||||
'laps_indf_yn': '경과이자지급여부',
|
||||
'ksd_lhdy_pnia_dfrm_mthd_cd': '증권예탁결제원공휴일원리금지급',
|
||||
'frst_int_dfrm_dt': '최초이자지급일자',
|
||||
'ksd_prcm_lnkg_gvbd_yn': '증권예탁결제원물가연동국고채여',
|
||||
'dpsi_end_dt': '예탁종료일자',
|
||||
'dpsi_strt_dt': '예탁시작일자',
|
||||
'dpsi_psbl_yn': '예탁가능여부',
|
||||
'atyp_rdpt_bond_erlm_yn': '비정형상환채권등록여부',
|
||||
'dshn_occr_yn': '부도발생여부',
|
||||
'expd_exts_yn': '만기연장여부',
|
||||
'pclr_ptcr_text': '특이사항내용',
|
||||
'dpsi_psbl_excp_stat_cd': '예탁가능예외상태코드',
|
||||
'expd_exts_srdp_rcnt': '만기연장분할상환횟수',
|
||||
'expd_exts_srdp_rt': '만기연장분할상환율',
|
||||
'expd_rdpt_rt': '만기상환율',
|
||||
'expd_asrc_erng_rt': '만기보장수익율',
|
||||
'bond_int_dfrm_mthd_cd': '채권이자지급방법코드',
|
||||
'int_dfrm_day_type_cd': '이자지급일유형코드',
|
||||
'prca_dfmt_term_mcnt': '원금거치기간개월수',
|
||||
'splt_rdpt_rcnt': '분할상환횟수',
|
||||
'rgbf_int_dfrm_dt': '직전이자지급일자',
|
||||
'nxtm_int_dfrm_dt': '차기이자지급일자',
|
||||
'sprx_psbl_yn': '분리과세가능여부',
|
||||
'ictx_rt_dvsn_cd': '소득세율구분코드',
|
||||
'bond_clsf_cd': '채권분류코드',
|
||||
'bond_clsf_kor_name': '채권분류한글명',
|
||||
'int_mned_dvsn_cd': '이자월말구분코드',
|
||||
'pnia_int_calc_unpr': '원리금이자계산단가',
|
||||
'frn_intr': 'FRN금리',
|
||||
'aply_day_prcm_idx_lnkg_cefc': '적용일물가지수연동계수',
|
||||
'ksd_expd_dydv_calc_bass_cd': '증권예탁결제원만기일할계산기준',
|
||||
'expd_dydv_calc_dcnt': '만기일할계산일수',
|
||||
'ksd_cbbw_dvsn_cd': '증권예탁결제원신종사채구분코드',
|
||||
'crfd_item_yn': '크라우드펀딩종목여부',
|
||||
'pnia_bank_ofdy_dfrm_mthd_cd': '원리금은행휴무일지급방법코드',
|
||||
'qib_yn': 'QIB여부',
|
||||
'qib_cclc_dt': 'QIB해지일자',
|
||||
'csbd_yn': '영구채여부',
|
||||
'csbd_cclc_dt': '영구채해지일자',
|
||||
'ksd_opcb_yn': '증권예탁결제원옵션부사채여부',
|
||||
'ksd_sodn_yn': '증권예탁결제원후순위채권여부',
|
||||
'ksd_rqdi_scty_yn': '증권예탁결제원유동화증권여부',
|
||||
'elec_scty_yn': '전자증권여부',
|
||||
'rght_ecis_mbdy_dvsn_cd': '권리행사주체구분코드',
|
||||
'int_rkng_mthd_dvsn_cd': '이자산정방법구분코드',
|
||||
'ofrg_dvsn_cd': '모집구분코드',
|
||||
'ksd_tot_issu_amt': '증권예탁결제원총발행금액',
|
||||
'next_indf_chk_ecls_yn': '다음이자지급체크제외여부',
|
||||
'ksd_bond_intr_dvsn_cd': '증권예탁결제원채권금리구분코드',
|
||||
'ksd_inrt_aply_dvsn_cd': '증권예탁결제원이율적용구분코드',
|
||||
'krx_issu_istt_cd': 'KRX발행기관코드',
|
||||
'ksd_indf_frqc_uder_calc_cd': '증권예탁결제원이자지급주기미만',
|
||||
'ksd_indf_frqc_uder_calc_dcnt': '증권예탁결제원이자지급주기미만',
|
||||
'tlg_rcvg_dtl_dtime': '전문수신상세일시'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
def main():
|
||||
"""
|
||||
[장내채권] 기본시세
|
||||
장내채권 기본조회[국내주식-129]
|
||||
|
||||
장내채권 기본조회 테스트 함수
|
||||
|
||||
Parameters:
|
||||
- pdno (str): 상품번호 (상품번호)
|
||||
- prdt_type_cd (str): 상품유형코드 (Unique key(302))
|
||||
Returns:
|
||||
- DataFrame: 장내채권 기본조회 결과
|
||||
|
||||
Example:
|
||||
>>> df = search_bond_info(pdno="", prdt_type_cd="302")
|
||||
"""
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 토큰 발급
|
||||
logger.info("토큰 발급 중...")
|
||||
ka.auth()
|
||||
logger.info("토큰 발급 완료")
|
||||
|
||||
# API 호출
|
||||
logger.info("API 호출 시작: 장내채권 기본조회")
|
||||
result = search_bond_info(
|
||||
pdno="KR103502GA34", # 상품번호
|
||||
prdt_type_cd="302", # 상품유형코드
|
||||
)
|
||||
|
||||
if result is None or result.empty:
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# 컬럼명 출력
|
||||
logger.info("사용 가능한 컬럼 목록:")
|
||||
logger.info(result.columns.tolist())
|
||||
|
||||
# 한글 컬럼명으로 변환
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 변환
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce')
|
||||
|
||||
# 결과 출력
|
||||
logger.info("=== 장내채권 기본조회 결과 ===")
|
||||
logger.info("조회된 데이터 건수: %d", len(result))
|
||||
print(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,115 @@
|
||||
# [장내채권] 기본시세 - 장내채권 기본조회
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-19
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from typing import Optional
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 기본시세 > 장내채권 기본조회 [국내주식-129]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-bond/v1/quotations/search-bond-info"
|
||||
|
||||
def search_bond_info(
|
||||
pdno: str, # 상품번호
|
||||
prdt_type_cd: str, # 상품유형코드
|
||||
tr_cont: str = "", # 연속 거래 여부
|
||||
dataframe: Optional[pd.DataFrame] = None, # 누적 데이터프레임
|
||||
depth: int = 0, # 현재 재귀 깊이
|
||||
max_depth: int = 10 # 최대 재귀 깊이
|
||||
) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
[장내채권] 기본시세
|
||||
장내채권 기본조회[국내주식-129]
|
||||
장내채권 기본조회 API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
pdno (str): 상품번호 (필수)
|
||||
prdt_type_cd (str): 상품유형코드 (필수)
|
||||
tr_cont (str): 연속 거래 여부 (기본값: "")
|
||||
dataframe (Optional[pd.DataFrame]): 누적 데이터프레임
|
||||
depth (int): 현재 재귀 깊이
|
||||
max_depth (int): 최대 재귀 깊이 (기본값: 10)
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: 장내채권 기본조회 데이터
|
||||
|
||||
Example:
|
||||
>>> df = search_bond_info("KR2033022D33", "302")
|
||||
>>> print(df)
|
||||
"""
|
||||
# 로깅 설정
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# 필수 파라미터 검증
|
||||
if not pdno:
|
||||
logger.error("pdno is required. (e.g. 'KR2033022D33')")
|
||||
raise ValueError("pdno is required. (e.g. 'KR2033022D33')")
|
||||
|
||||
if not prdt_type_cd:
|
||||
logger.error("prdt_type_cd is required. (e.g. '302')")
|
||||
raise ValueError("prdt_type_cd is required. (e.g. '302')")
|
||||
|
||||
# 최대 재귀 깊이 체크
|
||||
if depth >= max_depth:
|
||||
logger.warning("Maximum recursion depth (%d) reached. Stopping further requests.", max_depth)
|
||||
return dataframe if dataframe is not None else pd.DataFrame()
|
||||
|
||||
tr_id = "CTPF1114R"
|
||||
|
||||
params = {
|
||||
"PDNO": pdno,
|
||||
"PRDT_TYPE_CD": prdt_type_cd,
|
||||
}
|
||||
|
||||
# API 호출
|
||||
res = ka._url_fetch(API_URL, tr_id, tr_cont, params)
|
||||
|
||||
if res.isOK():
|
||||
if hasattr(res.getBody(), 'output'):
|
||||
output_data = res.getBody().output
|
||||
if not isinstance(output_data, list):
|
||||
output_data = [output_data]
|
||||
current_data = pd.DataFrame(output_data)
|
||||
else:
|
||||
current_data = pd.DataFrame()
|
||||
|
||||
if dataframe is not None:
|
||||
dataframe = pd.concat([dataframe, current_data], ignore_index=True)
|
||||
else:
|
||||
dataframe = current_data
|
||||
|
||||
tr_cont = res.getHeader().tr_cont
|
||||
|
||||
if tr_cont == "M":
|
||||
logger.info("Calling next page...")
|
||||
ka.smart_sleep()
|
||||
return search_bond_info(
|
||||
pdno,
|
||||
prdt_type_cd,
|
||||
"N", dataframe, depth + 1, max_depth
|
||||
)
|
||||
else:
|
||||
logger.info("Data fetch complete.")
|
||||
return dataframe
|
||||
else:
|
||||
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
||||
res.printError(API_URL)
|
||||
return pd.DataFrame()
|
||||
124
한국투자증권(API)/examples_llm/domestic_bond/sell/chk_sell.py
Normal file
124
한국투자증권(API)/examples_llm/domestic_bond/sell/chk_sell.py
Normal file
@@ -0,0 +1,124 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-20
|
||||
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.']) # kis_auth 파일 경로 추가
|
||||
import kis_auth as ka
|
||||
from sell import sell
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 주문/계좌 > 장내채권 매도주문 [국내주식-123]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
'KRX_FWDG_ORD_ORGNO': '한국거래소전송주문조직번호',
|
||||
'ODNO': '주문번호',
|
||||
'ORD_TMD': '주문시각'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
def main():
|
||||
"""
|
||||
[장내채권] 주문/계좌
|
||||
장내채권 매도주문[국내주식-123]
|
||||
|
||||
장내채권 매도주문 테스트 함수
|
||||
|
||||
Parameters:
|
||||
cano (str): 종합계좌번호
|
||||
acnt_prdt_cd (str): 계좌상품코드
|
||||
ord_dvsn (str): 주문구분
|
||||
pdno (str): 상품번호
|
||||
ord_qty2 (str): 주문수량2
|
||||
bond_ord_unpr (str): 채권주문단가
|
||||
sprx_yn (str): 분리과세여부
|
||||
buy_dt (str): 매수일자
|
||||
buy_seq (str): 매수순번
|
||||
samt_mket_ptci_yn (str): 소액시장참여여부
|
||||
sll_agco_opps_sll_yn (str): 매도대행사반대매도여부
|
||||
bond_rtl_mket_yn (str): 채권소매시장여부
|
||||
mgco_aptm_odno (str): 운용사지정주문번호
|
||||
ord_svr_dvsn_cd (str): 주문서버구분코드
|
||||
ctac_tlno (str): 연락전화번호
|
||||
|
||||
Returns:
|
||||
- DataFrame: 장내채권 매도주문 결과
|
||||
|
||||
Example:
|
||||
>>> df = main()
|
||||
>>> print(df)
|
||||
"""
|
||||
try:
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 토큰 발급
|
||||
logger.info("토큰 발급 중...")
|
||||
ka.auth()
|
||||
logger.info("토큰 발급 완료")
|
||||
|
||||
# kis_auth 모듈에서 계좌 정보 가져오기
|
||||
trenv = ka.getTREnv()
|
||||
|
||||
# API 호출
|
||||
logger.info("API 호출 시작: 장내채권 매도주문")
|
||||
result = sell(
|
||||
cano=trenv.my_acct, # 종합계좌번호
|
||||
acnt_prdt_cd=trenv.my_prod, # 계좌상품코드
|
||||
ord_dvsn="01", # 주문구분
|
||||
pdno="KR6095572D81", # 상품번호
|
||||
ord_qty2="1", # 주문수량
|
||||
bond_ord_unpr="10000.0", # 채권주문단가
|
||||
sprx_yn="N", # 분리과세여부
|
||||
buy_dt="", # 매수일자
|
||||
buy_seq="", # 매수순번
|
||||
samt_mket_ptci_yn="N", # 소액시장참여여부
|
||||
sll_agco_opps_sll_yn="N", # 매도대행사반대매도여부
|
||||
bond_rtl_mket_yn="N", # 채권소매시장여부
|
||||
mgco_aptm_odno="", # 운용사지정주문번호
|
||||
ord_svr_dvsn_cd="0", # 주문서버구분코드
|
||||
ctac_tlno="", # 연락전화번호
|
||||
)
|
||||
|
||||
if result is None or result.empty:
|
||||
logger.warning("조회된 데이터가 없습니다.")
|
||||
return
|
||||
|
||||
# 컬럼명 출력
|
||||
logger.info("사용 가능한 컬럼 목록:")
|
||||
logger.info(result.columns.tolist())
|
||||
|
||||
# 한글 컬럼명으로 변환
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 변환
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce')
|
||||
|
||||
# 결과 출력
|
||||
logger.info("=== 장내채권 매도주문 결과 ===")
|
||||
logger.info("조회된 데이터 건수: %d", len(result))
|
||||
print(result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error("에러 발생: %s", str(e))
|
||||
raise
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
127
한국투자증권(API)/examples_llm/domestic_bond/sell/sell.py
Normal file
127
한국투자증권(API)/examples_llm/domestic_bond/sell/sell.py
Normal file
@@ -0,0 +1,127 @@
|
||||
# [장내채권] 주문/계좌 - 장내채권 매도주문
|
||||
# Generated by KIS API Generator (Single API Mode)
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Created on 2025-06-20
|
||||
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import Optional
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO, format='%(levelname)s - %(message)s')
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
##############################################################################################
|
||||
# [장내채권] 주문/계좌 > 장내채권 매도주문 [국내주식-123]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-bond/v1/trading/sell"
|
||||
|
||||
def sell(
|
||||
cano: str,
|
||||
acnt_prdt_cd: str,
|
||||
ord_dvsn: str,
|
||||
pdno: str,
|
||||
ord_qty2: str,
|
||||
bond_ord_unpr: str,
|
||||
sprx_yn: str,
|
||||
samt_mket_ptci_yn: str,
|
||||
sll_agco_opps_sll_yn: str,
|
||||
bond_rtl_mket_yn: str,
|
||||
buy_dt: str = "",
|
||||
buy_seq: str = "",
|
||||
mgco_aptm_odno: str = "",
|
||||
ord_svr_dvsn_cd: str = "0",
|
||||
ctac_tlno: str = ""
|
||||
) -> Optional[pd.DataFrame]:
|
||||
"""
|
||||
[장내채권] 주문/계좌
|
||||
장내채권 매도주문[국내주식-123]
|
||||
장내채권 매도주문 API를 호출하여 DataFrame으로 반환합니다.
|
||||
|
||||
Args:
|
||||
cano (str): 종합계좌번호
|
||||
acnt_prdt_cd (str): 계좌상품코드
|
||||
ord_dvsn (str): 주문구분
|
||||
pdno (str): 상품번호
|
||||
ord_qty2 (str): 주문수량2
|
||||
bond_ord_unpr (str): 채권주문단가
|
||||
sprx_yn (str): 분리과세여부
|
||||
samt_mket_ptci_yn (str): 소액시장참여여부
|
||||
sll_agco_opps_sll_yn (str): 매도대행사반대매도여부
|
||||
bond_rtl_mket_yn (str): 채권소매시장여부
|
||||
buy_dt (str, optional): 매수일자. Defaults to "".
|
||||
buy_seq (str, optional): 매수순번. Defaults to "".
|
||||
mgco_aptm_odno (str, optional): 운용사지정주문번호. Defaults to "".
|
||||
ord_svr_dvsn_cd (str, optional): 주문서버구분코드. Defaults to "0".
|
||||
ctac_tlno (str, optional): 연락전화번호. Defaults to "".
|
||||
|
||||
Returns:
|
||||
Optional[pd.DataFrame]: 장내채권 매도주문 데이터
|
||||
|
||||
Example:
|
||||
>>> df = sell(
|
||||
... cano=trenv.my_acct,
|
||||
... acnt_prdt_cd=trenv.my_prod,
|
||||
... ord_dvsn="01",
|
||||
... pdno="KR6095572D81",
|
||||
... ord_qty2="1",
|
||||
... bond_ord_unpr="10000.0",
|
||||
... sprx_yn="N",
|
||||
... samt_mket_ptci_yn="N",
|
||||
... sll_agco_opps_sll_yn="N",
|
||||
... bond_rtl_mket_yn="N"
|
||||
... )
|
||||
>>> print(df)
|
||||
"""
|
||||
tr_id = "TTTC0958U"
|
||||
|
||||
params = {
|
||||
"CANO": cano,
|
||||
"ACNT_PRDT_CD": acnt_prdt_cd,
|
||||
"ORD_DVSN": ord_dvsn,
|
||||
"PDNO": pdno,
|
||||
"ORD_QTY2": ord_qty2,
|
||||
"BOND_ORD_UNPR": bond_ord_unpr,
|
||||
"SPRX_YN": sprx_yn,
|
||||
"BUY_DT": buy_dt,
|
||||
"BUY_SEQ": buy_seq,
|
||||
"SAMT_MKET_PTCI_YN": samt_mket_ptci_yn,
|
||||
"SLL_AGCO_OPPS_SLL_YN": sll_agco_opps_sll_yn,
|
||||
"BOND_RTL_MKET_YN": bond_rtl_mket_yn,
|
||||
"MGCO_APTM_ODNO": mgco_aptm_odno,
|
||||
"ORD_SVR_DVSN_CD": ord_svr_dvsn_cd,
|
||||
"CTAC_TLNO": ctac_tlno
|
||||
}
|
||||
|
||||
res = ka._url_fetch(api_url=API_URL,
|
||||
ptr_id=tr_id,
|
||||
tr_cont="",
|
||||
params=params,
|
||||
postFlag=True
|
||||
)
|
||||
|
||||
if res.isOK():
|
||||
if hasattr(res.getBody(), 'output'):
|
||||
output_data = res.getBody().output
|
||||
if not isinstance(output_data, list):
|
||||
output_data = [output_data]
|
||||
dataframe = pd.DataFrame(output_data)
|
||||
else:
|
||||
dataframe = pd.DataFrame()
|
||||
|
||||
logger.info("Data fetch complete.")
|
||||
return dataframe
|
||||
else:
|
||||
logger.error("API call failed: %s - %s", res.getErrorCode(), res.getErrorMessage())
|
||||
res.printError(API_URL)
|
||||
return pd.DataFrame()
|
||||
@@ -0,0 +1,116 @@
|
||||
"""
|
||||
Created on 20250601
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
from commodity_futures_realtime_conclusion import commodity_futures_realtime_conclusion
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
##############################################################################################
|
||||
# [국내선물옵션] 실시간시세 > 상품선물 실시간체결가[실시간-022]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
"futs_shrn_iscd": "선물 단축 종목코드",
|
||||
"bsop_hour": "영업 시간",
|
||||
"futs_prdy_vrss": "선물 전일 대비",
|
||||
"prdy_vrss_sign": "전일 대비 부호",
|
||||
"futs_prdy_ctrt": "선물 전일 대비율",
|
||||
"futs_prpr": "선물 현재가",
|
||||
"futs_oprc": "선물 시가2",
|
||||
"futs_hgpr": "선물 최고가",
|
||||
"futs_lwpr": "선물 최저가",
|
||||
"last_cnqn": "최종 거래량",
|
||||
"acml_vol": "누적 거래량",
|
||||
"acml_tr_pbmn": "누적 거래 대금",
|
||||
"hts_thpr": "HTS 이론가",
|
||||
"mrkt_basis": "시장 베이시스",
|
||||
"dprt": "괴리율",
|
||||
"nmsc_fctn_stpl_prc": "근월물 약정가",
|
||||
"fmsc_fctn_stpl_prc": "원월물 약정가",
|
||||
"spead_prc": "스프레드1",
|
||||
"hts_otst_stpl_qty": "HTS 미결제 약정 수량",
|
||||
"otst_stpl_qty_icdc": "미결제 약정 수량 증감",
|
||||
"oprc_hour": "시가 시간",
|
||||
"oprc_vrss_prpr_sign": "시가2 대비 현재가 부호",
|
||||
"oprc_vrss_nmix_prpr": "시가 대비 지수 현재가",
|
||||
"hgpr_hour": "최고가 시간",
|
||||
"hgpr_vrss_prpr_sign": "최고가 대비 현재가 부호",
|
||||
"hgpr_vrss_nmix_prpr": "최고가 대비 지수 현재가",
|
||||
"lwpr_hour": "최저가 시간",
|
||||
"lwpr_vrss_prpr_sign": "최저가 대비 현재가 부호",
|
||||
"lwpr_vrss_nmix_prpr": "최저가 대비 지수 현재가",
|
||||
"shnu_rate": "매수2 비율",
|
||||
"cttr": "체결강도",
|
||||
"esdg": "괴리도",
|
||||
"otst_stpl_rgbf_qty_icdc": "미결제 약정 직전 수량 증감",
|
||||
"thpr_basis": "이론 베이시스",
|
||||
"futs_askp1": "선물 매도호가1",
|
||||
"futs_bidp1": "선물 매수호가1",
|
||||
"askp_rsqn1": "매도호가 잔량1",
|
||||
"bidp_rsqn1": "매수호가 잔량1",
|
||||
"seln_cntg_csnu": "매도 체결 건수",
|
||||
"shnu_cntg_csnu": "매수 체결 건수",
|
||||
"ntby_cntg_csnu": "순매수 체결 건수",
|
||||
"seln_cntg_smtn": "총 매도 수량",
|
||||
"shnu_cntg_smtn": "총 매수 수량",
|
||||
"total_askp_rsqn": "총 매도호가 잔량",
|
||||
"total_bidp_rsqn": "총 매수호가 잔량",
|
||||
"prdy_vol_vrss_acml_vol_rate": "전일 거래량 대비 등락율",
|
||||
"dscs_bltr_acml_qty": "협의 대량 거래량",
|
||||
"dynm_mxpr": "실시간상한가",
|
||||
"dynm_llam": "실시간하한가",
|
||||
"dynm_prc_limt_yn": "실시간가격제한구분"
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
상품선물 실시간체결가
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 인증 토큰 발급
|
||||
ka.auth()
|
||||
ka.auth_ws()
|
||||
|
||||
# 인증(auth_ws()) 이후에 선언
|
||||
kws = ka.KISWebSocket(api_url="/tryitout")
|
||||
|
||||
# 조회
|
||||
kws.subscribe(request=commodity_futures_realtime_conclusion, data=["165W09"])
|
||||
|
||||
# 결과 표시
|
||||
def on_result(ws, tr_id: str, result: pd.DataFrame, data_map: dict):
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce').round(2)
|
||||
|
||||
logging.info("결과:")
|
||||
print(result)
|
||||
|
||||
kws.start(on_result=on_result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,108 @@
|
||||
"""
|
||||
Created on 20250601
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
##############################################################################################
|
||||
# [국내선물옵션] 실시간시세 > 상품선물 실시간체결가[실시간-022]
|
||||
##############################################################################################
|
||||
|
||||
def commodity_futures_realtime_conclusion(
|
||||
tr_type: str,
|
||||
tr_key: str,
|
||||
) -> (dict, list[str]):
|
||||
"""
|
||||
상품선물 실시간체결가 API입니다.
|
||||
실시간 웹소켓 연결을 통해 상품선물의 실시간 체결가 정보를 수신할 수 있습니다.
|
||||
현재가, 시고저가, 체결량, 누적거래량, 이론가, 베이시스, 괴리율 등의 상세 정보를 제공합니다.
|
||||
매도/매수 호가, 체결 건수, 미결제 약정 수량 등의 선물거래 필수 정보를 포함합니다.
|
||||
|
||||
Args:
|
||||
tr_type (str): [필수] 구독 등록/해제 여부 (ex. "1": 구독, "2": 해제)
|
||||
tr_key (str): [필수] 종목코드 (ex. 101S12)
|
||||
|
||||
Returns:
|
||||
message (str): 메시지 데이터
|
||||
|
||||
Example:
|
||||
>>> msg, columns = commodity_futures_realtime_conclusion("1", "101S12")
|
||||
>>> print(msg, columns)
|
||||
"""
|
||||
|
||||
# 필수 파라미터 검증
|
||||
if tr_type == "":
|
||||
raise ValueError("tr_type is empty")
|
||||
|
||||
if tr_key == "":
|
||||
raise ValueError("tr_key is required")
|
||||
|
||||
tr_id = "H0CFCNT0"
|
||||
|
||||
params = {
|
||||
"tr_key": tr_key,
|
||||
}
|
||||
|
||||
msg = ka.data_fetch(tr_id, tr_type, params)
|
||||
|
||||
columns = [
|
||||
"futs_shrn_iscd",
|
||||
"bsop_hour",
|
||||
"futs_prdy_vrss",
|
||||
"prdy_vrss_sign",
|
||||
"futs_prdy_ctrt",
|
||||
"futs_prpr",
|
||||
"futs_oprc",
|
||||
"futs_hgpr",
|
||||
"futs_lwpr",
|
||||
"last_cnqn",
|
||||
"acml_vol",
|
||||
"acml_tr_pbmn",
|
||||
"hts_thpr",
|
||||
"mrkt_basis",
|
||||
"dprt",
|
||||
"nmsc_fctn_stpl_prc",
|
||||
"fmsc_fctn_stpl_prc",
|
||||
"spead_prc",
|
||||
"hts_otst_stpl_qty",
|
||||
"otst_stpl_qty_icdc",
|
||||
"oprc_hour",
|
||||
"oprc_vrss_prpr_sign",
|
||||
"oprc_vrss_nmix_prpr",
|
||||
"hgpr_hour",
|
||||
"hgpr_vrss_prpr_sign",
|
||||
"hgpr_vrss_nmix_prpr",
|
||||
"lwpr_hour",
|
||||
"lwpr_vrss_prpr_sign",
|
||||
"lwpr_vrss_nmix_prpr",
|
||||
"shnu_rate",
|
||||
"cttr",
|
||||
"esdg",
|
||||
"otst_stpl_rgbf_qty_icdc",
|
||||
"thpr_basis",
|
||||
"futs_askp1",
|
||||
"futs_bidp1",
|
||||
"askp_rsqn1",
|
||||
"bidp_rsqn1",
|
||||
"seln_cntg_csnu",
|
||||
"shnu_cntg_csnu",
|
||||
"ntby_cntg_csnu",
|
||||
"seln_cntg_smtn",
|
||||
"shnu_cntg_smtn",
|
||||
"total_askp_rsqn",
|
||||
"total_bidp_rsqn",
|
||||
"prdy_vol_vrss_acml_vol_rate",
|
||||
"dscs_bltr_acml_qty",
|
||||
"dynm_mxpr",
|
||||
"dynm_llam",
|
||||
"dynm_prc_limt_yn"
|
||||
]
|
||||
|
||||
return msg, columns
|
||||
@@ -0,0 +1,104 @@
|
||||
"""
|
||||
Created on 20250601
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
from commodity_futures_realtime_quote import commodity_futures_realtime_quote
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
##############################################################################################
|
||||
# [국내선물옵션] 실시간시세 > 상품선물 실시간호가[실시간-023]
|
||||
##############################################################################################
|
||||
COLUMN_MAPPING = {
|
||||
"futs_shrn_iscd": "선물 단축 종목코드",
|
||||
"bsop_hour": "영업 시간",
|
||||
"futs_askp1": "선물 매도호가1",
|
||||
"futs_askp2": "선물 매도호가2",
|
||||
"futs_askp3": "선물 매도호가3",
|
||||
"futs_askp4": "선물 매도호가4",
|
||||
"futs_askp5": "선물 매도호가5",
|
||||
"futs_bidp1": "선물 매수호가1",
|
||||
"futs_bidp2": "선물 매수호가2",
|
||||
"futs_bidp3": "선물 매수호가3",
|
||||
"futs_bidp4": "선물 매수호가4",
|
||||
"futs_bidp5": "선물 매수호가5",
|
||||
"askp_csnu1": "매도호가 건수1",
|
||||
"askp_csnu2": "매도호가 건수2",
|
||||
"askp_csnu3": "매도호가 건수3",
|
||||
"askp_csnu4": "매도호가 건수4",
|
||||
"askp_csnu5": "매도호가 건수5",
|
||||
"bidp_csnu1": "매수호가 건수1",
|
||||
"bidp_csnu2": "매수호가 건수2",
|
||||
"bidp_csnu3": "매수호가 건수3",
|
||||
"bidp_csnu4": "매수호가 건수4",
|
||||
"bidp_csnu5": "매수호가 건수5",
|
||||
"askp_rsqn1": "매도호가 잔량1",
|
||||
"askp_rsqn2": "매도호가 잔량2",
|
||||
"askp_rsqn3": "매도호가 잔량3",
|
||||
"askp_rsqn4": "매도호가 잔량4",
|
||||
"askp_rsqn5": "매도호가 잔량5",
|
||||
"bidp_rsqn1": "매수호가 잔량1",
|
||||
"bidp_rsqn2": "매수호가 잔량2",
|
||||
"bidp_rsqn3": "매수호가 잔량3",
|
||||
"bidp_rsqn4": "매수호가 잔량4",
|
||||
"bidp_rsqn5": "매수호가 잔량5",
|
||||
"total_askp_csnu": "총 매도호가 건수",
|
||||
"total_bidp_csnu": "총 매수호가 건수",
|
||||
"total_askp_rsqn": "총 매도호가 잔량",
|
||||
"total_bidp_rsqn": "총 매수호가 잔량",
|
||||
"total_askp_rsqn_icdc": "총 매도호가 잔량 증감",
|
||||
"total_bidp_rsqn_icdc": "총 매수호가 잔량 증감"
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
상품선물 실시간호가
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 인증 토큰 발급
|
||||
ka.auth()
|
||||
ka.auth_ws()
|
||||
|
||||
# 인증(auth_ws()) 이후에 선언
|
||||
kws = ka.KISWebSocket(api_url="/tryitout")
|
||||
|
||||
# 조회
|
||||
kws.subscribe(request=commodity_futures_realtime_quote, data=["165W09"])
|
||||
|
||||
# 결과 표시
|
||||
def on_result(ws, tr_id: str, result: pd.DataFrame, data_map: dict):
|
||||
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce').round(2)
|
||||
|
||||
logging.info("결과:")
|
||||
print(result)
|
||||
|
||||
kws.start(on_result=on_result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
Created on 20250601
|
||||
"""
|
||||
|
||||
import logging
|
||||
import sys
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
##############################################################################################
|
||||
# [국내선물옵션] 실시간시세 > 상품선물 실시간호가[실시간-023]
|
||||
##############################################################################################
|
||||
|
||||
def commodity_futures_realtime_quote(
|
||||
tr_type: str,
|
||||
tr_key: str,
|
||||
) -> (dict, list[str]):
|
||||
"""
|
||||
상품선물 실시간호가 API입니다.
|
||||
실시간 웹소켓 연결을 통해 상품선물 매도/매수 호가 정보를 실시간으로 수신할 수 있습니다.
|
||||
실전계좌만 지원되며, 모의투자는 지원하지 않습니다.
|
||||
선물옵션 호가 데이터는 0.2초 필터링 옵션이 적용됩니다.
|
||||
|
||||
Args:
|
||||
tr_type (str): [필수] 구독 등록/해제 여부 (ex. "1": 구독, "2": 해제)
|
||||
tr_key (str): [필수] 종목코드 (ex. 101S12)
|
||||
|
||||
Returns:
|
||||
message (str): 메시지 데이터
|
||||
|
||||
Example:
|
||||
>>> msg, columns = commodity_futures_realtime_quote("1", "101S12")
|
||||
>>> print(msg, columns)
|
||||
"""
|
||||
|
||||
# 필수 파라미터 검증
|
||||
if tr_type == "":
|
||||
raise ValueError("tr_type is required")
|
||||
|
||||
if tr_key == "":
|
||||
raise ValueError("tr_key is required")
|
||||
|
||||
tr_id = "H0CFASP0"
|
||||
|
||||
params = {
|
||||
"tr_key": tr_key,
|
||||
}
|
||||
|
||||
msg = ka.data_fetch(tr_id, tr_type, params)
|
||||
|
||||
columns = [
|
||||
"futs_shrn_iscd",
|
||||
"bsop_hour",
|
||||
"futs_askp1",
|
||||
"futs_askp2",
|
||||
"futs_askp3",
|
||||
"futs_askp4",
|
||||
"futs_askp5",
|
||||
"futs_bidp1",
|
||||
"futs_bidp2",
|
||||
"futs_bidp3",
|
||||
"futs_bidp4",
|
||||
"futs_bidp5",
|
||||
"askp_csnu1",
|
||||
"askp_csnu2",
|
||||
"askp_csnu3",
|
||||
"askp_csnu4",
|
||||
"askp_csnu5",
|
||||
"bidp_csnu1",
|
||||
"bidp_csnu2",
|
||||
"bidp_csnu3",
|
||||
"bidp_csnu4",
|
||||
"bidp_csnu5",
|
||||
"askp_rsqn1",
|
||||
"askp_rsqn2",
|
||||
"askp_rsqn3",
|
||||
"askp_rsqn4",
|
||||
"askp_rsqn5",
|
||||
"bidp_rsqn1",
|
||||
"bidp_rsqn2",
|
||||
"bidp_rsqn3",
|
||||
"bidp_rsqn4",
|
||||
"bidp_rsqn5",
|
||||
"total_askp_csnu",
|
||||
"total_bidp_csnu",
|
||||
"total_askp_rsqn",
|
||||
"total_bidp_rsqn",
|
||||
"total_askp_rsqn_icdc",
|
||||
"total_bidp_rsqn_icdc"
|
||||
]
|
||||
|
||||
return msg, columns
|
||||
@@ -0,0 +1,130 @@
|
||||
"""
|
||||
Created on 20250601
|
||||
"""
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
from display_board_callput import display_board_callput
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
##############################################################################################
|
||||
# [국내선물옵션] 기본시세 > 국내옵션전광판_콜풋[국내선물-022]
|
||||
##############################################################################################
|
||||
|
||||
# 컬럼명 한글 변환 및 데이터 출력
|
||||
COLUMN_MAPPING = {
|
||||
'acpr': '행사가',
|
||||
'unch_prpr': '환산 현재가',
|
||||
'optn_shrn_iscd': '옵션 단축 종목코드',
|
||||
'optn_prpr': '옵션 현재가',
|
||||
'optn_prdy_vrss': '옵션 전일 대비',
|
||||
'prdy_vrss_sign': '전일 대비 부호',
|
||||
'optn_prdy_ctrt': '옵션 전일 대비율',
|
||||
'optn_bidp': '옵션 매수호가',
|
||||
'optn_askp': '옵션 매도호가',
|
||||
'tmvl_val': '시간가치 값',
|
||||
'nmix_sdpr': '지수 기준가',
|
||||
'acml_vol': '누적 거래량',
|
||||
'seln_rsqn': '매도 잔량',
|
||||
'shnu_rsqn': '매수2 잔량',
|
||||
'acml_tr_pbmn': '누적 거래 대금',
|
||||
'hts_otst_stpl_qty': 'HTS 미결제 약정 수량',
|
||||
'otst_stpl_qty_icdc': '미결제 약정 수량 증감',
|
||||
'delta_val': '델타 값',
|
||||
'gama': '감마',
|
||||
'vega': '베가',
|
||||
'theta': '세타',
|
||||
'rho': '로우',
|
||||
'hts_ints_vltl': 'HTS 내재 변동성',
|
||||
'invl_val': '내재가치 값',
|
||||
'esdg': '괴리도',
|
||||
'dprt': '괴리율',
|
||||
'hist_vltl': '역사적 변동성',
|
||||
'hts_thpr': 'HTS 이론가',
|
||||
'optn_oprc': '옵션 시가2',
|
||||
'optn_hgpr': '옵션 최고가',
|
||||
'optn_lwpr': '옵션 최저가',
|
||||
'optn_mxpr': '옵션 상한가',
|
||||
'optn_llam': '옵션 하한가',
|
||||
'atm_cls_name': 'ATM 구분 명',
|
||||
'rgbf_vrss_icdc': '직전 대비 증감',
|
||||
'total_askp_rsqn': '총 매도호가 잔량',
|
||||
'total_bidp_rsqn': '총 매수호가 잔량',
|
||||
'futs_antc_cnpr': '선물예상체결가',
|
||||
'futs_antc_cntg_vrss': '선물예상체결대비',
|
||||
'antc_cntg_vrss_sign': '예상 체결 대비 부호',
|
||||
'antc_cntg_prdy_ctrt': '예상 체결 전일 대비율',
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
국내옵션전광판_콜풋 조회 테스트 함수
|
||||
|
||||
이 함수는 국내옵션전광판_콜풋 API를 호출하여 결과를 출력합니다.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 인증 토큰 발급
|
||||
ka.auth()
|
||||
|
||||
# Case1 테스트
|
||||
logging.info("=== Case1 테스트 ===")
|
||||
try:
|
||||
result1, result2 = display_board_callput(
|
||||
fid_cond_mrkt_div_code="O",
|
||||
fid_cond_scr_div_code="20503",
|
||||
fid_mrkt_cls_code="CO",
|
||||
fid_mtrt_cnt="202508",
|
||||
fid_mrkt_cls_code1="PO"
|
||||
)
|
||||
except ValueError as e:
|
||||
logging.error("에러 발생: %s" % str(e))
|
||||
return
|
||||
|
||||
# Output1 처리
|
||||
logging.info("=== Output1 결과 ===")
|
||||
logging.info("사용 가능한 컬럼: %s", result1.columns.tolist())
|
||||
|
||||
result1 = result1.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result1.columns:
|
||||
result1[col] = pd.to_numeric(result1[col], errors='coerce').round(2)
|
||||
|
||||
logging.info("결과:")
|
||||
print(result1)
|
||||
|
||||
# Output2 처리
|
||||
logging.info("=== Output2 결과 ===")
|
||||
logging.info("사용 가능한 컬럼: %s", result2.columns.tolist())
|
||||
|
||||
result2 = result2.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 소수점 둘째자리까지 표시 (메타데이터에 number 자료형 없음)
|
||||
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result2.columns:
|
||||
result2[col] = pd.to_numeric(result2[col], errors='coerce').round(2)
|
||||
|
||||
logging.info("결과:")
|
||||
print(result2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
Created on 20250601
|
||||
"""
|
||||
|
||||
import sys
|
||||
from typing import Tuple
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
##############################################################################################
|
||||
# [국내선물옵션] 기본시세 > 국내옵션전광판_콜풋[국내선물-022]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-futureoption/v1/quotations/display-board-callput"
|
||||
|
||||
def display_board_callput(
|
||||
fid_cond_mrkt_div_code: str, # [필수] 조건 시장 분류 코드 (ex. O: 옵션)
|
||||
fid_cond_scr_div_code: str, # [필수] 조건 화면 분류 코드 (ex. 20503)
|
||||
fid_mrkt_cls_code: str, # [필수] 시장 구분 코드 (ex. CO: 콜옵션)
|
||||
fid_mtrt_cnt: str, # [필수] 만기 수 (ex. 202508)
|
||||
fid_mrkt_cls_code1: str, # [필수] 시장 구분 코드 (ex. PO: 풋옵션)
|
||||
fid_cond_mrkt_cls_code: str = "" # 조건 시장 구분 코드
|
||||
) -> Tuple[pd.DataFrame, pd.DataFrame]:
|
||||
"""
|
||||
국내옵션전광판_콜풋 API입니다.
|
||||
한국투자 HTS(eFriend Plus) > [0503] 선물옵션 종합시세(Ⅰ) 화면의 "중앙" 기능을 API로 개발한 사항으로, 해당 화면을 참고하시면 기능을 이해하기 쉽습니다.
|
||||
|
||||
※ output1, output2 각각 100건까지만 확인이 가능합니다. (FY25년도 서비스 개선 예정)
|
||||
※ 조회시간이 긴 API인 점 참고 부탁드리며, 잦은 호출을 삼가해주시기 바랍니다. (1초당 최대 1건 권장)
|
||||
|
||||
Args:
|
||||
fid_cond_mrkt_div_code (str): [필수] 조건 시장 분류 코드 (ex. O: 옵션)
|
||||
fid_cond_scr_div_code (str): [필수] 조건 화면 분류 코드 (ex. 20503)
|
||||
fid_mrkt_cls_code (str): [필수] 시장 구분 코드 (ex. CO: 콜옵션)
|
||||
fid_mtrt_cnt (str): [필수] 만기 수 (ex. 202508)
|
||||
fid_mrkt_cls_code1 (str): [필수] 시장 구분 코드 (ex. PO: 풋옵션)
|
||||
fid_cond_mrkt_cls_code (str): 조건 시장 구분 코드
|
||||
|
||||
Returns:
|
||||
Tuple[pd.DataFrame, pd.DataFrame]: (output1 DataFrame, output2 DataFrame)
|
||||
|
||||
Example:
|
||||
>>> df1, df2 = display_board_callput("O", "20503", "CO", "202508", "PO")
|
||||
>>> print(df1)
|
||||
>>> print(df2)
|
||||
"""
|
||||
|
||||
# 필수 파라미터 검증
|
||||
if fid_cond_mrkt_div_code == "":
|
||||
raise ValueError("fid_cond_mrkt_div_code is required (e.g. 'O')")
|
||||
|
||||
if fid_cond_scr_div_code == "":
|
||||
raise ValueError("fid_cond_scr_div_code is required (e.g. '20503')")
|
||||
|
||||
if fid_mrkt_cls_code == "":
|
||||
raise ValueError("fid_mrkt_cls_code is required (e.g. 'CO')")
|
||||
|
||||
if fid_mtrt_cnt == "":
|
||||
raise ValueError("fid_mtrt_cnt is required (e.g. '202508')")
|
||||
|
||||
if fid_mrkt_cls_code1 == "":
|
||||
raise ValueError("fid_mrkt_cls_code1 is required (e.g. 'PO')")
|
||||
|
||||
tr_id = "FHPIF05030100"
|
||||
|
||||
params = {
|
||||
"FID_COND_MRKT_DIV_CODE": fid_cond_mrkt_div_code,
|
||||
"FID_COND_SCR_DIV_CODE": fid_cond_scr_div_code,
|
||||
"FID_MRKT_CLS_CODE": fid_mrkt_cls_code,
|
||||
"FID_MTRT_CNT": fid_mtrt_cnt,
|
||||
"FID_MRKT_CLS_CODE1": fid_mrkt_cls_code1,
|
||||
"FID_COND_MRKT_CLS_CODE": fid_cond_mrkt_cls_code
|
||||
}
|
||||
|
||||
res = ka._url_fetch(API_URL, tr_id, "", params)
|
||||
|
||||
if res.isOK():
|
||||
output1_df = pd.DataFrame(res.getBody().output1)
|
||||
output2_df = pd.DataFrame(res.getBody().output2)
|
||||
return output1_df, output2_df
|
||||
else:
|
||||
res.printError(url=API_URL)
|
||||
return pd.DataFrame(), pd.DataFrame()
|
||||
@@ -0,0 +1,91 @@
|
||||
"""
|
||||
Created on 20250112
|
||||
"""
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
from display_board_futures import display_board_futures
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
##############################################################################################
|
||||
# [국내선물옵션] 기본시세 > 국내옵션전광판_선물[국내선물-023]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
'futs_shrn_iscd': '선물 단축 종목코드',
|
||||
'hts_kor_isnm': 'HTS 한글 종목명',
|
||||
'futs_prpr': '선물 현재가',
|
||||
'futs_prdy_vrss': '선물 전일 대비',
|
||||
'prdy_vrss_sign': '전일 대비 부호',
|
||||
'futs_prdy_ctrt': '선물 전일 대비율',
|
||||
'hts_thpr': 'HTS 이론가',
|
||||
'acml_vol': '누적 거래량',
|
||||
'futs_askp': '선물 매도호가',
|
||||
'futs_bidp': '선물 매수호가',
|
||||
'hts_otst_stpl_qty': 'HTS 미결제 약정 수량',
|
||||
'futs_hgpr': '선물 최고가',
|
||||
'futs_lwpr': '선물 최저가',
|
||||
'hts_rmnn_dynu': 'HTS 잔존 일수',
|
||||
'total_askp_rsqn': '총 매도호가 잔량',
|
||||
'total_bidp_rsqn': '총 매수호가 잔량',
|
||||
'futs_antc_cnpr': '선물예상체결가',
|
||||
'futs_antc_cntg_vrss': '선물예상체결대비',
|
||||
'antc_cntg_vrss_sign': '예상 체결 대비 부호',
|
||||
'antc_cntg_prdy_ctrt': '예상 체결 전일 대비율'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
국내선물옵션 선물전광판 조회 테스트 함수
|
||||
|
||||
이 함수는 국내선물옵션 선물전광판 API를 호출하여 결과를 출력합니다.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 인증 토큰 발급
|
||||
ka.auth()
|
||||
|
||||
# case1 테스트
|
||||
logging.info("=== case1 테스트 ===")
|
||||
try:
|
||||
result = display_board_futures(
|
||||
fid_cond_mrkt_div_code="F",
|
||||
fid_cond_scr_div_code="20503",
|
||||
fid_cond_mrkt_cls_code="MKI"
|
||||
)
|
||||
except ValueError as e:
|
||||
logging.error("에러 발생: %s", str(e))
|
||||
return
|
||||
|
||||
logging.info("사용 가능한 컬럼: %s", result.columns.tolist())
|
||||
|
||||
# 컬럼명 한글 변환
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 소수점 표시 (메타데이터에 number 자료형이 명시된 컬럼 없음)
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce').round(2)
|
||||
|
||||
logging.info("결과:")
|
||||
print(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,69 @@
|
||||
"""
|
||||
Created on 20250112
|
||||
"""
|
||||
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
##############################################################################################
|
||||
# [국내선물옵션] 기본시세 > 국내옵션전광판_선물[국내선물-023]
|
||||
##############################################################################################
|
||||
|
||||
# 상수 정의
|
||||
API_URL = "/uapi/domestic-futureoption/v1/quotations/display-board-futures"
|
||||
|
||||
def display_board_futures(
|
||||
fid_cond_mrkt_div_code: str, # 조건 시장 분류 코드
|
||||
fid_cond_scr_div_code: str, # 조건 화면 분류 코드
|
||||
fid_cond_mrkt_cls_code: str # 조건 시장 구분 코드
|
||||
) -> pd.DataFrame:
|
||||
"""
|
||||
국내옵션전광판_선물 API입니다.
|
||||
한국투자 HTS(eFriend Plus) > [0503] 선물옵션 종합시세(Ⅰ) 화면의 "하단" 기능을 API로 개발한 사항입니다.
|
||||
|
||||
Args:
|
||||
fid_cond_mrkt_div_code (str): [필수] 조건 시장 분류 코드 (ex. F)
|
||||
fid_cond_scr_div_code (str): [필수] 조건 화면 분류 코드 (ex. 20503)
|
||||
fid_cond_mrkt_cls_code (str): [필수] 조건 시장 구분 코드 (ex. MKI)
|
||||
|
||||
Returns:
|
||||
pd.DataFrame: 국내선물옵션 선물전광판 데이터
|
||||
|
||||
Example:
|
||||
>>> df = display_board_futures("F", "20503", "MKI")
|
||||
>>> print(df)
|
||||
"""
|
||||
|
||||
# 필수 파라미터 검증
|
||||
if fid_cond_mrkt_div_code == "":
|
||||
raise ValueError("fid_cond_mrkt_div_code is required (e.g. 'F')")
|
||||
|
||||
if fid_cond_scr_div_code == "":
|
||||
raise ValueError("fid_cond_scr_div_code is required (e.g. '20503')")
|
||||
|
||||
if fid_cond_mrkt_cls_code == "":
|
||||
raise ValueError("fid_cond_mrkt_cls_code is required (e.g. 'MKI')")
|
||||
|
||||
tr_id = "FHPIF05030200"
|
||||
|
||||
params = {
|
||||
"FID_COND_MRKT_DIV_CODE": fid_cond_mrkt_div_code,
|
||||
"FID_COND_SCR_DIV_CODE": fid_cond_scr_div_code,
|
||||
"FID_COND_MRKT_CLS_CODE": fid_cond_mrkt_cls_code
|
||||
}
|
||||
|
||||
res = ka._url_fetch(API_URL, tr_id, "", params)
|
||||
|
||||
if res.isOK():
|
||||
return pd.DataFrame(res.getBody().output)
|
||||
else:
|
||||
res.printError(url=API_URL)
|
||||
return pd.DataFrame()
|
||||
@@ -0,0 +1,71 @@
|
||||
"""
|
||||
Created on 20250601
|
||||
"""
|
||||
import sys
|
||||
import logging
|
||||
|
||||
import pandas as pd
|
||||
|
||||
sys.path.extend(['../..', '.'])
|
||||
import kis_auth as ka
|
||||
from display_board_option_list import display_board_option_list
|
||||
|
||||
# 로깅 설정
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
##############################################################################################
|
||||
# [국내선물옵션] 기본시세 > 국내옵션전광판_옵션월물리스트[국내선물-020]
|
||||
##############################################################################################
|
||||
|
||||
COLUMN_MAPPING = {
|
||||
'mtrt_yymm_code': '만기 년월 코드',
|
||||
'mtrt_yymm': '만기 년월'
|
||||
}
|
||||
|
||||
NUMERIC_COLUMNS = []
|
||||
|
||||
|
||||
def main():
|
||||
"""
|
||||
국내옵션전광판_옵션월물리스트 조회 테스트 함수
|
||||
|
||||
이 함수는 국내옵션전광판_옵션월물리스트 API를 호출하여 결과를 출력합니다.
|
||||
|
||||
Returns:
|
||||
None
|
||||
"""
|
||||
|
||||
# pandas 출력 옵션 설정
|
||||
pd.set_option('display.max_columns', None) # 모든 컬럼 표시
|
||||
pd.set_option('display.width', None) # 출력 너비 제한 해제
|
||||
pd.set_option('display.max_rows', None) # 모든 행 표시
|
||||
|
||||
# 인증 토큰 발급
|
||||
ka.auth()
|
||||
|
||||
# case1 조회
|
||||
logging.info("=== case1 조회 ===")
|
||||
try:
|
||||
result = display_board_option_list(fid_cond_scr_div_code="509")
|
||||
except ValueError as e:
|
||||
logging.error("에러 발생: %s" % str(e))
|
||||
return
|
||||
|
||||
logging.info("사용 가능한 컬럼: %s", result.columns.tolist())
|
||||
|
||||
# 컬럼명 한글 변환 및 데이터 출력
|
||||
|
||||
result = result.rename(columns=COLUMN_MAPPING)
|
||||
|
||||
# 숫자형 컬럼 소수점 둘째자리까지 표시 (메타데이터에 number 자료형이 명시된 필드가 없음)
|
||||
|
||||
for col in NUMERIC_COLUMNS:
|
||||
if col in result.columns:
|
||||
result[col] = pd.to_numeric(result[col], errors='coerce').round(2)
|
||||
|
||||
logging.info("결과:")
|
||||
print(result)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user