Metadata-Version: 2.4
Name: finestock
Version: 2.1.0.0
Summary: Korean Stock OpenAPI Package(EBest, KIS, LS) creation written by alshin
Author-email: "A.Lok, Shin" <shinalok357@gmail.com>
Project-URL: Homepage, https://github.com/shinalok/finestock
Keywords: ebest,kis,ls,kiwoom,nh,db,openapi,stock,kr
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Requires-Python: >=3.7
Description-Content-Type: text/markdown
Requires-Dist: websockets
Requires-Dist: requests
Requires-Dist: loguru
Provides-Extra: examples
Requires-Dist: python-dotenv; extra == "examples"

# finestock
Korean Stock OpenAPI Package (EBest, LS, KIS, Kiwoom, NH, DB증권)
Created by alshin

---

## Table of Contents
1. [설치](#설치)
2. [지원 브로커](#지원-브로커)
   - [증권사별 지원 기능](#증권사별-지원-기능)
   - [증권사별 미지원 기능](#증권사별-미지원-기능)
3. [환경변수 설정 (.env)](#환경변수-설정-env)
4. [아키텍처](#아키텍처)
5. [사용법](#사용법)
6. [테스트](#테스트)
7. [Release Notes](#release-notes)
8. [License](#license)

---

## 설치

PyPI에 배포된 버전을 쓰려면:

```bash
pip install finestock
```

저장소를 직접 클론해서 최신 소스로 개발/테스트하려면(editable install):

```bash
git clone https://github.com/shinalok/finestock.git
cd finestock
pip install -e .
```

런타임 의존성은 `websockets`, `requests`, `loguru` 세 개뿐입니다(`requirements.txt` 참고).

---

## 지원 브로커

`APIProvider` enum(`finestock/api_factory.py`)으로 브로커를 선택하고, `mode`로 실전(`"live"`, 기본값) 또는 모의투자(`"virtual"`)를 지정합니다.

| Provider | 브로커 | 지원 환경 |
| --- | --- | --- |
| `APIProvider.EBEST` | 이베스트투자증권 | 실전 |
| `APIProvider.LS` | LS증권 | 실전·모의 |
| `APIProvider.KIS` | 한국투자증권 | 실전·모의 |
| `APIProvider.KIWOOM` | 키움증권 | 실전·모의 |
| `APIProvider.NH` | NH투자증권(나무) | 실전·모의 |
| `APIProvider.DB` | DB증권(DB금융투자) | 실전·모의 |

```python
import finestock
from finestock import APIProvider

api = finestock.create_api(APIProvider.LS)
virtual_api = finestock.create_api(APIProvider.LS, mode="virtual")
```

기존 `APIProvider.LSV`, `KISV`, `KIWOOMV`, `NHV`, `DBV`도 호환을 위해 모의투자로 동작합니다. 새 코드에서는 증권사 Provider와 `mode`를 사용하세요. `EBEST`는 모의투자 환경을 지원하지 않습니다.

브로커마다 구현된 메서드 범위가 다릅니다. 아래 지원·미지원 기능 표와 각 클래스의 docstring(`finestock/{ebest,ls,kis,kiwoom,nh,db}/*.py`)을 참고하세요.

### 증권사별 지원 기능

현재 저장소 코드 기준(2026-09-28)으로 **실제 요청·응답 처리 또는 실시간 구독·수신 코드가 구현된 기능**을 정리했습니다. 실전·모의 서버 검증 완료를 의미하지 않으며, 같은 행의 모의투자 Provider도 서버 제공 범위에 따라 사용이 제한될 수 있습니다.

모든 증권사 계열에 인증(`oauth`, `set_oauth_info`, `set_access_token`), 주식 가격·일봉·분봉·호가 조회(`get_price`, `get_ohlcv`, `get_ohlcv_min`, `get_orderbook`), 잔고·보유종목 조회(`get_balance`, `get_holds`), 주문·취소(`do_order`, `do_order_cancel`)가 구현되어 있습니다. 추가 기능은 다음과 같습니다.

| 증권사 / Provider | 지수 조회 | 주문 정정 | 종목·지수 목록 | 실시간 수신 |
| --- | --- | --- | --- | --- |
| LS·이베스트 (`LS`, `LSV`, `EBEST`) | 일봉·분봉 | `do_order_modify` | `get_stock_list`, `get_index_list` | 가격·호가·지수, 계좌 주문·체결 이벤트(`recv_order_status`) |
| 한국투자 (`KIS`, `KISV`) | 일봉·분봉 | `do_order_modify` | — | 가격·지수·호가·계좌 통보 구독 (수신 범위는 아래 참고) |
| 키움 (`KIWOOM`, `KIWOOMV`) | 일봉·분봉 | — | `get_stock_list`, `get_index_list` | 가격·호가·지수, 주문·체결 이벤트(`recv_trade` / `recv_order_status`) |
| NH (`NH`, `NHV`) | — | — | — | 가격·호가 |
| DB (`DB`, `DBV`) | 일봉·분봉 | `do_order_modify` | — | 가격·호가, 계좌 체결 이벤트(`recv_trade`) |

지수 일봉·분봉은 각각 `get_index`·`get_index_min`, 실시간 가격·호가·지수는 각각 `recv_price`·`recv_orderbook`·`recv_index`에 대응합니다. 표의 `—`는 미구현이며, 자세한 호출 결과는 아래 미지원 기능 표를 참고하세요.

주식 분봉은 모든 계열에서 별도 시각 입력 없이 호출할 수 있지만 반환 범위는 다릅니다. KIS는 지정 날짜를 자동 연속조회하여 중복 제거·시간순 정렬하고, LS는 서버의 연속조회 정보를 따라 추가 조회합니다. NH·키움·DB는 현재 한 요청의 결과를 반환하므로 하루치 전체 수집이나 해당 날짜만의 반환을 공통으로 보장하지 않습니다.


### 증권사별 미지원 기능

현재 저장소 코드 기준(2026-09-28)으로, **미지원은 finestock에서 미구현·미연동된 기능**을 뜻합니다. 증권사 API 자체의 제공 여부를 뜻하지 않으며, 표에 없는 기능도 실전·모의 서버 검증이 완료되었다는 의미는 아닙니다. 같은 행의 Provider는 아래 미지원 메서드를 공유합니다.

| 증권사 / Provider | 미지원 기능 및 메서드 | 현재 호출 결과 |
| --- | --- | --- |
| LS·이베스트 (`LS`, `LSV`, `EBEST`) | 공통 체결 구독 `recv_trade` | 경고만 출력하며 구독하지 않음 |
| 한국투자 (`KIS`, `KISV`) | 종목·지수 목록 `get_stock_list`, `get_index_list` | `[]` 반환 |
| 키움 (`KIWOOM`, `KIWOOMV`) | 주문 정정 `do_order_modify` | `None` 반환 |
| NH (`NH`, `NHV`) | 지수 일봉·분봉 `get_index`, `get_index_min`<br>종목·지수 목록 `get_stock_list`, `get_index_list`<br>주문 정정 `do_order_modify`<br>실시간 지수·체결 구독 `recv_index`, `recv_trade` | 조회는 `[]`, 정정은 `None` 반환. 실시간 구독은 안내만 출력하며 구독하지 않음 |
| DB (`DB`, `DBV`) | 종목·지수 목록 `get_stock_list`, `get_index_list`<br>실시간 지수 구독 `recv_index` | 목록은 `[]` 반환. 지수 구독은 경고만 출력하며 구독하지 않음 |

관련 대체 경로와 구현 제한:

- **LS·이베스트:** 계좌 주문·체결 이벤트에는 `await api.recv_order_status()`를 사용합니다. 공통 `recv_trade()`와는 별도 메서드입니다.
- **한국투자:** `await connect()` 후 네 `recv_*` 메서드로 구독하며 `status=False`로 해제합니다. `recv_trade(code)`에는 HTS ID를 전달합니다. 실서버에서 네 구독·해제와 가격·호가 수신을 확인했습니다. 지수·계좌 통보 데이터 수신, AES 복호화 및 모의 서버는 미검증입니다. 아래 실시간 실행 예제와 검증 범위를 참고하세요.
- **한국투자 분봉:** `KIS.get_ohlcv_min()`은 주식일별분봉조회로 구현했습니다. `KISV`도 같은 구현을 상속하지만 모의 서버의 해당 API 지원 여부는 검증되지 않았습니다. 아래 사용 예제는 `KIS` 기준입니다.
- **NH:** 지수 조회는 현재 빈 목록을 반환하며, 라이브러리에 대체 조회 경로가 구현되어 있지 않습니다.
- **DB:** 지수 일봉·분봉은 차트 조회의 시장 구분값 `U`를 사용하는 구현이 있습니다. `recv_trade(code, status=True)`는 `code`를 사용하지 않고 계좌 전체를 구독하며, `status=False`로 구독을 해제하는 동작은 검증되지 않았습니다.

메서드가 존재하거나 `isinstance(api, RealtimeProvider)` 등이 참이어도 개별 기능의 구현을 보장하지 않습니다. 현재 `[]`와 `None`만으로는 미지원·빈 결과·업무 실패를 구분할 수 없으므로 호출 전 위 표를 확인하세요.

---

### 한국투자 지수 분봉 조회

인증을 설정한 KIS 인스턴스에서 다음과 같이 호출합니다.

```python
prices = api.get_index_min("0001")  # 코스피 1분봉, Price 목록
prices = api.get_index_min("0001", interval=600, include_past=False)  # 당일 10분봉
prices = api.get_index_min("0001", todate="20260928", exclude_after_hours=True)
```

`interval`은 초 단위이며 기본값은 60입니다. `include_past=True`는 과거 데이터를
포함하고, `exclude_after_hours=True`는 장마감·시간외를 제외합니다.
`todate`는 한 번의 API 응답에서 해당 날짜만 선택합니다. 임의 과거 날짜 지정 조회와
커서 연속조회는 지원하지 않으므로 응답 범위 밖의 날짜는 빈 목록을 반환합니다.
`Price.time`은 체결 시간, `volume`은 체결 거래량, `volume_amt`는 응답의 누적 거래대금입니다.
실전·모의 서버 호출은 검증하지 않았습니다.

요청·응답 필드는 [한국투자증권 공식 샘플](https://github.com/koreainvestment/open-trading-api/tree/main/examples_llm/domestic_stock/inquire_time_indexchartprice)을 참고했습니다.

## 환경변수 설정 (.env)

브로커 앱키/시크릿/계좌번호/액세스 토큰은 코드에 직접 하드코딩하지 말고 환경변수로 주입합니다. 저장소 루트의 `.env.example`을 복사해 `.env`로 만들고 실제 값을 채워 넣으세요.

```bash
cp .env.example .env
```

`.env` 파일 내용:

```
APP_KEY=YOUR_APP_KEY
APP_SECRET=YOUR_APP_SECRET
ACCOUNT_NUM=YOUR_ACCOUNT_NUM
ACCOUNT_NUM_SUB=01
ACCESS_TOKEN=
```

`.env`는 `.gitignore`에 의해 커밋되지 않습니다(`.env.example`만 커밋 대상).

> ⚠️ 실제 앱키/시크릿을 스크립트에 직접 문자열로 적어두고 커밋하지 마세요. 한 번이라도 원격 저장소(특히 public)에 올라간 값은 즉시 노출된 것으로 간주하고 발급처(브로커 개발자센터)에서 폐기 후 재발급받아야 합니다.

### 값 로드 방법

**1) python-dotenv로 자동 로드 (권장)**

```bash
pip install python-dotenv
```

스크립트 최상단에서 아래처럼 호출하면 `.env` 값이 `os.environ`에 채워집니다(설치돼 있지 않으면 `ImportError`를 잡아 조용히 건너뛰도록 작성하는 게 일반적입니다).

```python
try:
    from dotenv import load_dotenv
    load_dotenv()
except ImportError:
    pass

import os
app_key = os.environ.get("APP_KEY")
app_secret = os.environ.get("APP_SECRET")
```

**2) 셸에서 직접 환경변수 설정**

```powershell
# PowerShell
$env:APP_KEY = "YOUR_APP_KEY"
$env:APP_SECRET = "YOUR_APP_SECRET"
```

```bash
# bash
export APP_KEY="YOUR_APP_KEY"
export APP_SECRET="YOUR_APP_SECRET"
```

---

## 아키텍처

`finestock`은 **파사드 패턴(Facade Pattern)**과 **인터페이스 분리 원칙(ISP)**을 결합하여 설계되었습니다.

### 1. 통합된 상태 관리 (Facade)
`create_api`로 생성되는 객체는 인증, 시세, 주문, 잔고, 실시간 등 모든 기능을 통합하여 관리합니다. 이를 통해 로그인 세션이나 소켓 연결 상태를 여러 모듈이 공유할 수 있어 사용이 편리합니다.

### 2. 인터페이스를 통한 명확한 사용 (ISP)
하나의 거대한 객체이지만, **타입 힌팅(Type Hinting)**을 통해 필요한 기능만 노출하여 안전하게 사용할 수 있습니다. `finestock/comm/api_interface.py`에 정의된 세그먼트:

- `AuthenticationProvider`: 로그인 및 토큰 관리(`oauth`, `set_oauth_info`, `set_access_token`)
- `MarketDataProvider`: 시세/OHLCV/지수/호가 조회(`get_price`, `get_ohlcv`, `get_ohlcv_min`, `get_index`, `get_orderbook`, ...)
- `TradingProvider`: 주문 실행/취소(`do_order`, `do_order_cancel`)
- `RealtimeProvider`: WebSocket 실시간 구독(`set_data_queue`, `recv_price`, `recv_orderbook`, ...)
- `AccountProvider`: 계좌 잔고/보유종목 조회(`get_balance`, `get_holds`)
- `InfoProvider`: 종목/지수 마스터 목록(`get_stock_list`, `get_index_list`)

이 인터페이스들의 합집합(`BaseProvider`)을 모든 브로커 클래스가 구현합니다. 타입 힌팅은 IDE 자동완성을 좁히기 위한 것일 뿐, 런타임 제약은 없습니다.

### 3. 데이터 모델 (`finestock/model/`)
브로커가 반환하는 원시 JSON/TR 응답은 파싱되어 공통 `@dataclass(frozen=True)` 타입(`Price`, `OrderBook`, `Hold`, `Account`, `Order`, `Trade`, `Stock`, `Index`)으로 통일됩니다.

---

## 사용법

### 1. 기본 사용 (Factory & Interface)

```python
import finestock
from finestock import APIProvider
from finestock.comm.api_interface import AuthenticationProvider, MarketDataProvider

# 1. API 객체 생성 (통합 객체)
api = finestock.create_api(APIProvider.LS)

# 2. 인증 (AuthenticationProvider 인터페이스 활용)
if isinstance(api, AuthenticationProvider):
    api.set_oauth_info("YOUR_APP_KEY", "YOUR_APP_SECRET")
    # api.oauth() # 로그인 필요 시 호출

# 3. 데이터 조회 (MarketDataProvider 인터페이스 활용)
if isinstance(api, MarketDataProvider):
    # IDE에서 get_ohlcv 등 시세 관련 메서드만 자동완성됨
    ohlcvs = api.get_ohlcv("005930", frdate="20260101", todate="20260110")
    print(ohlcvs)
```

### 2. 계좌 조회 (AccountProvider)

```python
from finestock.comm.api_interface import AccountProvider

if isinstance(api, AccountProvider):
    account = api.get_balance()
    if account:
        print(account.deposit, len(account.hold))
```

### 3. 실시간 데이터 (Queue Injection)

```python
import queue
from finestock.comm.api_interface import RealtimeProvider

if isinstance(api, RealtimeProvider):
    # 1. 데이터를 받을 큐 생성
    q = queue.Queue()

    # 2. 큐 주입
    api.set_data_queue(q)

    # 3. 데이터 수신 (asyncio 이벤트 루프에서 api.connect()/api.run() 실행 필요)
    #    브로커별 recv_price / recv_orderbook / recv_trade 등을 구독한 뒤
    #    q.get()으로 파싱된 finestock.Price / OrderBook / Trade 객체를 받는다.
```

### 4. 타입 힌팅 활용 (Type Hinting)

IDE에서 각 인터페이스별 메서드만 자동완성되도록 하려면 변수에 타입을 명시할 수 있습니다.

```python
from finestock.comm.api_interface import MarketDataProvider

# api 변수는 모든 기능을 가지고 있지만...
full_api = finestock.create_api(APIProvider.LS)

# MarketDataProvider로 타입을 명시하면 시세 관련 메서드만 자동완성에 노출됩니다.
market_api: MarketDataProvider = full_api

# market_api. (여기서 get_ohlcv 등만 보임)
ohlcvs = market_api.get_ohlcv("005930", frdate="20260101", todate="20260110")
```

---

### 5. 한국투자증권 주식 분봉 조회

인증을 마친 `APIProvider.KIS` 객체에서 날짜만 지정하면 내부에서 연속조회하여 하루치 분봉을 반환합니다.

```python
# api = finestock.create_api(APIProvider.KIS)
# set_oauth_info / oauth 또는 set_access_token으로 실전 인증을 완료한 뒤 호출
prices = api.get_ohlcv_min("005930", todate="20260925")
for price in prices:
    print(price.workday, price.time, price.open, price.high, price.low,
          price.close, price.volume, price.volume_amt)
```

[주식일별분봉조회 공식 문서](https://apiportal.koreainvestment.com/apiservice-apiservice?/uapi/domestic-stock/v1/quotations/inquire-time-dailychartprice)와 [한국투자증권 공식 예제](https://github.com/koreainvestment/open-trading-api/blob/main/examples_llm/domestic_stock/inquire_time_dailychartprice/inquire_time_dailychartprice.py)를 기준으로 `FHKST03010230`을 사용합니다. 서버의 요청당 최대 120건 제한에 맞춰 내부에서 조회 시각을 이동하며 해당 날짜의 분봉을 모읍니다. 오늘은 현재까지 서버에서 제공하는 분봉을 반환합니다. 과거 데이터는 서버 보관 범위(공식 예제 기준 최대 1년)에 한정됩니다.

| 인자 | 한국투자 분봉에서의 의미 |
| --- | --- |
| `todate` | 조회 날짜 `YYYYMMDD`. 생략하면 호출 시점의 한국 날짜 |
| `cts_time` | 생략하면 하루 전체 조회. 지정하면 해당 시각(`HHMMSS`)까지 조회 |
| `cts_date` | 지정하면 `todate` 대신 사용할 조회 날짜 |
| `exchgubun` | `K` 또는 `J`: KRX(기본), `NX`: NXT, `UN`: 통합 |
| `pw_data_incu_yn` | 과거 데이터 포함 여부. `N`(기본) 또는 `Y` |
| `fake_tick_incu_yn` | 허봉 포함 여부. 기본 빈 문자열, `Y` 또는 `N` 지정 가능 |
| `tr_cont_key` | 이 API에서는 사용하지 않음. 값을 전달하면 `ValueError` |

결과는 해당 날짜만 남기고 날짜·시각 중복을 제거한 시간 오름차순 `Price` 목록입니다. `workday`·`time`은 각 행의 영업일·체결 시각, `price`·`close`는 현재가, `volume`은 체결 거래량입니다. **`volume_amt`는 API의 누적 거래 대금(`acml_tr_pbmn`)이며 해당 1분만의 거래 대금이 아닙니다.**

빈 응답이나 이전 날짜에 도달하면 연속조회를 종료합니다. 첫 요청의 업무 거절이나 빈 결과는 `[]`, 잘못된 인자는 `ValueError`로 처리합니다. 중간 요청의 업무 거절·반복 응답·잘못된 성공 응답은 `FinestockAPIError`를 발생시켜 불완전한 하루치를 정상 결과로 반환하지 않습니다. 통신 오류는 공통 예외를 그대로 전달합니다. 테스트는 합성 응답 기반이며 실제 서버 호출은 검증하지 않았습니다.

### LS 주문 정정

인증된 LS 객체에서 기존 미체결 주문의 번호와 새 가격·수량을 전달합니다.

```python
# 실제 정정 주문을 전송합니다. order_num은 기존 주문의 주문번호입니다.
modified = api.do_order_modify(order_num, "005930", price=70000, qty=2)
if modified is not None:
    print(modified.order_num, modified.price, modified.qty)
```

- `CSPAT00701`을 사용하며 `price=0`은 시장가, 그 외는 지정가입니다. 주문조건은 없음(`0`)으로 전송합니다.
- `LSV`는 6자리 숫자 주식코드에 `A`를 붙입니다. 이미 지정한 `A`·`J`·`Q` 접두사는 유지합니다. `EBEST`도 구현을 상속합니다.
- 반환되는 `order_num`은 원주문번호가 아닌 응답의 정정 주문번호(`OrdNo`)입니다. 매수·매도 구분은 응답의 `BnsTpCode`를 사용합니다.
- 제공된 `doc/ls-openapi-tr-guides.json`에는 성공 코드 목록이 없어 HTTP 200의 양수 `OrdNo`를 접수 근거로 사용합니다. HTTP 거절 또는 주문번호가 없는 거절 응답은 `None`, 잘못된 접수 응답은 `FinestockAPIError`입니다. `Order` 반환은 체결 완료를 의미하지 않습니다.
- 통신 오류와 비정상 응답은 자동 재전송하지 않습니다. 명세 기반 합성 응답으로 테스트했으며 실제 정정 주문은 전송하지 않았습니다.

## 호출 계약 변경 (개발 버전)

- 키움/키움V 주문·취소는 성공 시 `Order`, HTTP/업무 거절 시 `None`을 반환합니다. 기존 `result['ord_no']`는 `result.order_num`으로 변경하고 `None`을 먼저 확인하세요. 성공 판정은 `return_code`를 사용합니다.
- 비-JSON 응답이나 성공 응답의 주문번호 누락은 `FinestockAPIError`입니다. 접수 여부가 불명확한 오류를 자동 재주문으로 처리하지 마세요.
- `Order`는 접수 결과이며 체결 완료를 뜻하지 않습니다. 키움 취소 결과의 `qty`는 요청 수량입니다(`0`은 잔량 전부 요청). 실제 취소·체결 수량을 확인한 값은 아닙니다. 제공되지 않는 이름·시간은 빈 문자열·`None`입니다.
- LS 계열 `recv_trade(code, status=True)`는 이제 `await`가 필요합니다. 여전히 미구현이며 경고만 출력합니다. 계좌 이벤트에는 기존 `recv_order_status()`를 사용하세요.
- LS `get_stock_list(mrkt_tp='0')`는 시장 인자를 받습니다(LS 기준 0 전체, 1 코스피, 2 코스닥). 시장 코드 의미는 증권사마다 다릅니다. LS 종목·지수 목록은 원본 dict 목록을 유지합니다.
- LS 목록형 조회의 암묵적 `None`과 KIS 미지원 지수 목록 반환은 `[]`로 정리했습니다. 빈 결과·업무 실패·미지원 구분과 목록 원소 모델 통일은 후속 작업입니다.
- LS·KIS의 수정된 차트 경로는 `Price.from_values()`로 숫자를 변환합니다. 잘못된 숫자는 `ValueError` 등으로 전달되며 전체 파서 오류의 공통 예외화는 후속 작업입니다.

키움 응답 필드 근거: [키움 공식 REST API 주문 문서](https://github.com/Kiwoom-Securities/Kiwoom-REST-API/blob/main/kiwoom_docs/%EC%A3%BC%EB%AC%B8.md). 이번 응답 테스트는 명세와 기존 코드에 맞춘 합성 데이터이며 실제 계좌 응답을 수집한 것이 아닙니다.

### 한국투자증권 실시간 실행 테스트

[example_async.py](./example_async.py)에 KIS 구독·해제 및 응답 확인 기능을 추가했습니다. 환경변수 또는 `.env`에 `APP_KEY`, `APP_SECRET`, `HTS_ID`를 설정합니다. 소문자 `htsid`도 지원하며 HTS ID가 없으면 계좌 통보 구독은 건너뜁니다.

```powershell
.\venv\Scripts\python.exe -m pip install python-dotenv
.\venv\Scripts\python.exe -B example_async.py --provider KIS --seconds 30
```

`--code`로 종목(기본 `005930`), `--index`로 지수(기본 `0001`)를 지정합니다. `--provider` 생략 시 기본값은 `LSV`입니다. KIS에서는 지정 시간 동안 수신 후 구독을 해제하고 최대 5초 동안 해제 응답을 확인한 다음 연결을 닫습니다. 거절 또는 응답 누락은 오류로 처리합니다. 주문은 전송하지 않으며 인증정보·계좌 통보 원문 대신 응답 코드와 데이터 건수를 출력합니다.

| 메서드 | TR ID | code 인자 |
| --- | --- | --- |
| `recv_price` | `H0STCNT0` | 종목코드 |
| `recv_index` | `H0UPCNT0` | 업종코드 |
| `recv_orderbook` | `H0STASP0` | 종목코드 |
| `recv_trade` | 실전 `H0STCNI0`, 모의 `H0STCNI9` | HTS ID |

```python
await api.connect()
await api.recv_price("005930")
await api.recv_index("0001")
await api.recv_orderbook("005930")
await api.recv_trade("YOUR_HTS_ID")
await api.recv_price("005930", status=False)  # 해제
```

**실서버 테스트 결과(2026-09-28, KIS, 30초):**

| 메서드 | 구독 / 해제 | 수신 건수 |
| --- | --- | --- |
| `recv_price` | 성공 / 성공 | 25 |
| `recv_index` | 성공 / 성공 | 0 |
| `recv_orderbook` | 성공 / 성공 | 148 |
| `recv_trade` | 성공 / 성공 | 0 |

네 TR 모두 구독 응답은 `rt_cd=0, msg_cd=OPSP0000`, 해제 응답은 `rt_cd=0, msg_cd=OPSP0001`이었습니다. 위 건수는 30초 수신 구간 기준이며 해제 응답 대기 중 호가 2건이 추가 수신되었습니다. 건수는 실행 시점에 따라 달라집니다.

지수·계좌 통보는 해당 구간에 데이터가 없어 실제 데이터 수신을 검증하지 못했습니다. 계좌 통보 AES 복호화와 모의 서버도 미검증입니다. 구독 메서드는 요청을 전송하고 `None`을 반환합니다. KIS 라이브러리에는 아직 `run()`, 모델 변환, 자동 큐 전달이 없으며 예제 수신 루프는 구독 응답·PINGPONG·데이터 건수만 처리합니다. 계좌 통보는 구독 응답의 key/IV를 이용해 별도로 AES 복호화해야 합니다.

프로토콜 참고: [공식 구독 예제](https://github.com/koreainvestment/open-trading-api/blob/main/examples_user/domestic_stock/domestic_stock_examples_ws.py), [요청 형식 및 해제 구현](https://github.com/koreainvestment/open-trading-api/blob/main/examples_user/kis_auth.py).

## 테스트

```bash
python -m unittest discover tests          # 전체 테스트
python -m unittest tests.test_model -v      # 특정 모듈만
```

`pytest` 설정은 없으며 표준 라이브러리 `unittest`를 사용합니다.

---

## Release Notes

전체 변경 이력은 [CHANGELOG.md](./CHANGELOG.md)를 참고하세요. 최근 주요 변경:

- **1.0.1.0**: `MarketDataProvider`에 `get_price(code)` 추가(당일 OHLCV를 단일 `Price`로 반환)
- **2.0.0.0**: Kiwoom 주문·취소 반환값을 `Order`로 통일, KIS 분봉·실시간 구독과 LS 주문 정정 추가, DB 브로커 구현 및 배포 도구 도입. 호환성 변경과 미검증 범위는 [CHANGELOG.md](./CHANGELOG.md)를 확인하세요.
- **1.0.0.1**: `APIFactory` lazy loading 도입, 인터페이스 분리(ISP) 리팩터링, 메서드 네이밍 PEP 8 표준화
- (개발 중) NH투자증권(나무) 브로커 지원 추가 — `APIProvider.NH` / `APIProvider.NHV`
- (개발 중) DB증권(DB금융투자) 브로커 지원 추가 — `APIProvider.DB` / `APIProvider.DBV`

---

## License

별도의 LICENSE 파일은 아직 저장소에 포함되어 있지 않습니다. 라이선스 조건이 필요하다면 저장소 관리자(`shinalok`)에게 문의해 주세요.
