kiwoom-rest-trade

πŸ—οΈ kiwoom-rest-trade μ•„ν‚€ν…μ²˜ μ„€κ³„μ„œ (ARCHITECTURE.md)

λ³Έ λ¬Έμ„œλŠ” ν‚€μ›€μ¦κΆŒ REST APIλ₯Ό λž˜ν•‘ν•˜λŠ” ν˜„λŒ€μ μΈ 비동기 파이썬 라이브러리인 kiwoom-rest-trade νŒ¨ν‚€μ§€μ˜ μ‹œμŠ€ν…œ 섀계와 핡심 섀계 결정을 λ‹€λ£Ήλ‹ˆλ‹€.


1. μ•„ν‚€ν…μ²˜ 섀계 원칙

  1. Async-First (비동기 μš°μ„ ): μ‹€μ „ μžλ™λ§€λ§€ ν™˜κ²½μ—μ„œλŠ” 속도와 효율적인 I/O μ²˜λ¦¬κ°€ 생λͺ…μž…λ‹ˆλ‹€. λͺ¨λ“  λ„€νŠΈμ›Œν¬ 톡신(HTTP, WebSocket)은 asyncioλ₯Ό 기반으둜 μ„€κ³„ν•©λ‹ˆλ‹€. 단, 초보 개발자λ₯Ό μœ„ν•΄ λ‚΄λΆ€ 비동기 루프λ₯Ό κ°μ‹ΈλŠ” 동기(Sync) μΈν„°νŽ˜μ΄μŠ€λ„ ν•¨κ»˜ μ œκ³΅ν•©λ‹ˆλ‹€.
  2. Type Safety (νƒ€μž… μ•ˆμ „μ„±): 킀움 REST API의 μˆ˜λ§Žμ€ μš”μ²­/응닡 ν•„λ“œμ— μ™„λ²½ν•œ νƒ€μž… νžŒνŠΈμ™€ 검증을 μ œκ³΅ν•˜κΈ° μœ„ν•΄ Pydantic v2λ₯Ό 적극적으둜 λ„μž…ν•©λ‹ˆλ‹€.
  3. Smart Rate Limiting (슀마트 μœ λŸ‰ μ œμ–΄): ν‚€μ›€μ¦κΆŒ μ„œλ²„μ—μ„œ μ •μ˜ν•œ κ°€ν˜Ήν•œ 호좜 횟수 μ œν•œ(1μ΄ˆλ‹Ή 5회~10회 λ“±)에 κ±Έλ € 계정 μ •μ§€λ‚˜ μ—λŸ¬κ°€ λ‚˜μ§€ μ•Šλ„λ‘ 라이브러리 λ ˆλ²¨μ—μ„œ API μš”μ²­ μ£ΌκΈ°λ₯Ό μžλ™μœΌλ‘œ λ―Έμ„Έ μ‘°μ •ν•©λ‹ˆλ‹€.
  4. Codegen-Friendly (μžλ™ν™” μ½”λ“œ 생성): 337κ°œμ— λ‹¬ν•˜λŠ” λ°©λŒ€ν•œ TR λͺ©λ‘μ„ 직접 μ†μœΌλ‘œ μ½”λ”©ν•˜λŠ” λΉ„νš¨μœ¨κ³Ό μ‹€μˆ˜ κ°€λŠ₯성을 μ œκ±°ν•˜κΈ° μœ„ν•΄, μˆ˜μ§‘λœ API μŠ€νŽ™ 데이터(kiwoom_api_spec.json) 기반으둜 λͺ¨λΈ 및 API λ©”μ„œλ“œ μ½”λ“œλ₯Ό μžλ™ 생성(Codegen)ν•˜λŠ” ꡬ쑰λ₯Ό μ·¨ν•©λ‹ˆλ‹€.

2. 디렉토리 ꡬ쑰 (Package Layout)

λΌμ΄λΈŒλŸ¬λ¦¬λŠ” λ‹€μŒκ³Ό 같이 관심사λ₯Ό λͺ…ν™•ν•˜κ²Œ λΆ„λ¦¬ν•˜μ—¬ κ΅¬μ„±λ©λ‹ˆλ‹€.

kiwoom/
β”œβ”€β”€ __init__.py          # νŒ¨ν‚€μ§€ μ§„μž…μ , 톡합 ν΄λΌμ΄μ–ΈνŠΈ λ…ΈμΆœ
β”œβ”€β”€ client.py            # AsyncKiwoomClient 및 KiwoomClient(동기) μ •μ˜
β”œβ”€β”€ auth.py              # 토큰 λ°œκΈ‰/κ°±μ‹  및 μ„œλͺ… 인증 λͺ¨λ“ˆ
β”œβ”€β”€ exceptions.py        # 킀움 μ—λŸ¬ μ½”λ“œμ— 맞좘 μ˜ˆμ™Έ 계측 μ •μ˜
β”œβ”€β”€ limiter.py           # μœ λŸ‰ μ œμ–΄(Rate Limit) λ°μ½”λ ˆμ΄ν„° 및 토큰 버킷
β”œβ”€β”€ websocket.py         # μ›Ήμ†ŒμΌ“(WebSocket) μ‹€μ‹œκ°„ μ‹œμ„Έ 및 체결 슀트리밍
β”œβ”€β”€ models/              # Pydantic 데이터 λͺ¨λΈ (Codegen λŒ€μƒ)
β”‚   β”œβ”€β”€ __init__.py
β”‚   β”œβ”€β”€ base.py          # 곡톡 Pydantic 베이슀 클래슀 μ •μ˜
β”‚   β”œβ”€β”€ auth.py          # OAuth κ΄€λ ¨ μŠ€ν‚€λ§ˆ
β”‚   β”œβ”€β”€ domestic.py      # ꡭ내주식 κ΄€λ ¨ μŠ€ν‚€λ§ˆ (μžλ™μƒμ„±)
β”‚   └── overseas.py      # 해외주식 κ΄€λ ¨ μŠ€ν‚€λ§ˆ (μžλ™μƒμ„±)
└── api/                 # REST API μ—”λ“œν¬μΈνŠΈ λ©”μ„œλ“œ λͺ¨λ“ˆ (Codegen λŒ€μƒ)
    β”œβ”€β”€ __init__.py
    β”œβ”€β”€ base.py          # API μš”μ²­ 전솑 곡톡 둜직
    β”œβ”€β”€ auth.py          # 인증 API ν˜ΈμΆœλΆ€
    β”œβ”€β”€ domestic.py      # ꡭ내주식 API ν˜ΈμΆœλΆ€ (μžλ™μƒμ„±)
    └── overseas.py      # 해외주식 API ν˜ΈμΆœλΆ€ (μžλ™μƒμ„±)

3. 핡심 λͺ¨λ“ˆ κ°„ 관계 및 흐름 (Mermaid Diagram)

μ‚¬μš©μž(Trader)κ°€ AsyncKiwoomClientλ₯Ό 톡해 κ΅­λ‚΄ 주식 ν˜„μž¬κ°€λ₯Ό μ‘°νšŒν•˜κ³  체결 톡보λ₯Ό λ°›λŠ” 전체적인 νλ¦„μž…λ‹ˆλ‹€.

graph TD
    User([μ‚¬μš©μž λ§€λ§€ μ „λž΅]) -->|1. λ©”μ„œλ“œ 호좜| Client[AsyncKiwoomClient]
    Client -->|2. μœ λŸ‰ ν•œλ„ 확인| Limiter[RateLimiter]
    Client -->|3. μžλ™ 토큰 검증 & 첨뢀| Auth[TokenManager]
    Client -->|4. HTTP μš”μ²­| HTTPX[httpx.AsyncClient]
    HTTPX -->|5. JSON 응닡| Parser[Pydantic Parser]
    Parser -->|6. μ—λŸ¬ μ½”λ“œ 발견 μ‹œ| Exc[ExceptionMapper]
    Parser -->|7. 성곡 μ‹œ νƒ€μž… 검증| Model[Pydantic Models]
    
    WS([μ‹€μ‹œκ°„ μ›Ήμ†ŒμΌ“ 이벀트]) -->|8. 데이터 μˆ˜μ‹ | WebSocketClient[KiwoomWebSocket]
    WebSocketClient -->|9. μ‚¬μš©μž 콜백 ν•¨μˆ˜ 트리거| User
    
    Exc -.->|μ˜ˆμ™Έ λ°œμƒ| User
    Model -.->|κ²€μ¦λœ 데이터 λ°˜ν™˜| User

4. μ„ΈλΆ€ 섀계 κ²°μ • (Design Decisions)

4.1. 비동기/동기 ν•˜μ΄λΈŒλ¦¬λ“œ μΈν„°νŽ˜μ΄μŠ€

라이브러리의 핡심은 비동기 ν΄λΌμ΄μ–ΈνŠΈ(AsyncKiwoomClient)μž…λ‹ˆλ‹€. λ‚΄λΆ€ ν†΅μ‹ μ—λŠ” httpx.AsyncClient 컀λ„₯μ…˜ 풀을 ν™œμš©ν•΄ νš¨μœ¨μ„ κ·ΉλŒ€ν™”ν•©λ‹ˆλ‹€. 비동기가 μ–΄μƒ‰ν•œ μ‚¬μš©μžλ₯Ό μœ„ν•΄ λ‹€μŒκ³Ό 같은 동기식 래퍼(KiwoomClient)도 ν•¨κ»˜ μ§€μ›ν•©λ‹ˆλ‹€.

# 1. 비동기식 μ‚¬μš© (ꢌμž₯)
import asyncio
from kiwoom import AsyncKiwoomClient

async def main():
    client = AsyncKiwoomClient(appkey="...", secretkey="...")
    balance = await client.domestic.get_balance(acnt_no="...")
    print(balance)

asyncio.run(main())

# 2. 동기식 μ‚¬μš© (동기 래퍼)
from kiwoom import KiwoomClient

client = KiwoomClient(appkey="...", secretkey="...")
balance = client.domestic.get_balance(acnt_no="...")  # λ‚΄λΆ€μ μœΌλ‘œ asyncio.run λ˜λŠ” 루프λ₯Ό 돌렀 처리
print(balance)

4.2. Pydantic 기반 데이터 λͺ¨λΈλ§ & μ½”λ“œμ   μ „λž΅

ν‚€μ›€μ˜ kiwoom_api_spec.json νŒŒμΌμ—λŠ” 각 TR(예: ka10001 - μ£Όμ‹κΈ°λ³Έμ •λ³΄μš”μ²­)의 request body ν•„λ“œλͺ…, νƒ€μž…, ν•„μˆ˜ μ—¬λΆ€ 등이 κ³ μŠ€λž€νžˆ 담겨 μžˆμŠ΅λ‹ˆλ‹€. 이λ₯Ό νŒŒμ΄μ‹±ν•˜μ—¬ 파이썬 μ½”λ“œλ₯Ό μžλ™ μƒμ„±ν•©λ‹ˆλ‹€.

# [μ˜ˆμ‹œ] μžλ™ 생성될 models/domestic.py 의 일뢀
from pydantic import BaseModel, Field
from typing import Optional

class StockBasicInfoRequest(BaseModel):
    fid_cond_mrkt_div_code: str = Field(..., description="μ‹œμž₯κ΅¬λΆ„μ½”λ“œ (J: 주식, W: ELW λ“±)")
    fid_input_iscd: str = Field(..., description="μ’…λͺ©μ½”λ“œ")

class StockBasicInfoResponse(BaseModel):
    prdy_vrss: int = Field(..., description="μ „μΌλŒ€λΉ„")
    stck_prpr: int = Field(..., description="μ£Όμ‹ν˜„μž¬κ°€")
    # ... 45개 ν•„λ“œ λ§€ν•‘

4.3. λ˜‘λ˜‘ν•œ 비동기 μœ λŸ‰ μ œμ–΄ (Rate Limiting)

킀움 REST APIλŠ” κ΅­λ‚΄ 주식 1μ΄ˆλ‹Ή 5회, λ―Έκ΅­ 주식 1μ΄ˆλ‹Ή 10회(ν”Όν¬νƒ€μž„ 3회)의 μ—„κ²©ν•œ μœ λŸ‰ ν•œλ„λ₯Ό κ°–μŠ΅λ‹ˆλ‹€. limiter.pyμ—μ„œλŠ” 각 μΉ΄ν…Œκ³ λ¦¬λ³„λ‘œ Semaphore λ˜λŠ” 토큰 버킷(Token Bucket) ꡬ쑰의 비동기 속도 μ œν•œκΈ°λ₯Ό λ‘‘λ‹ˆλ‹€.

class RateLimiter:
    def __init__(self, requests_per_second: float):
        self.delay = 1.0 / requests_per_second
        self.lock = asyncio.Lock()
        self.last_called = 0.0

    async def wait(self):
        async with self.lock:
            now = asyncio.get_event_loop().time()
            elapsed = now - self.last_called
            sleep_time = self.delay - elapsed
            if sleep_time > 0:
                await asyncio.sleep(sleep_time)
            self.last_called = asyncio.get_event_loop().time()

APIλ₯Ό ν˜ΈμΆœν•˜λŠ” λͺ¨λ“  μ—”λ“œν¬μΈνŠΈ λ©”μ„œλ“œμ— 이 λ°μ½”λ ˆμ΄ν„°λ‚˜ Limiter의 wait()λ₯Ό μ μš©ν•˜μ—¬ 호좜 μ£ΌκΈ°κ°€ μ •λ°€ν•˜κ²Œ κ°•μ œλ˜λ„λ‘ μ²˜λ¦¬ν•©λ‹ˆλ‹€.

4.4. μ—λŸ¬ μ½”λ“œ 핸듀링 계측

kiwoom_error_codes.json에 κΈ°λ°˜ν•˜μ—¬ μ—λŸ¬ μ½”λ“œλ₯Ό μ μ ˆν•œ 파이썬 μ΅μ…‰μ…˜μœΌλ‘œ λ²ˆμ—­ν•©λ‹ˆλ‹€.

API 응닡을 λ°›μœΌλ©΄ 응닡 ν—€λ”λ‚˜ λ°”λ”” λ‚΄μ˜ μ—λŸ¬ 정보λ₯Ό κ²€μ‚¬ν•˜μ—¬ μžλ™μœΌλ‘œ ν•΄λ‹Ή μ˜ˆμ™Έ μΈμŠ€ν„΄μŠ€λ₯Ό λ°œμƒ(raise)μ‹œν‚΅λ‹ˆλ‹€.

4.5. WebSocket μ‹€μ‹œκ°„ 슀트리밍 관리

μ‹€μ‹œκ°„ 데이터λ₯Ό μ†‘μˆ˜μ‹ ν•˜κΈ° μœ„ν•΄ μ›Ήμ†ŒμΌ“ μ—°κ²°(kiwoom/websocket.py)을 λž©ν•‘ν•©λ‹ˆλ‹€.