Files
finestock/CLAUDE.md
T
shinalokandClaude Sonnet 5 eaf18362f4 Add NH(나무) broker integration and fix balance/holds parsing
- finestock/nh/: Nh/NhV 브로커 클래스 추가 (시세/주문/잔고/실시간 WS)
- api_factory.py, path.py: APIProvider.NH/NHV 등록, 도메인/엔드포인트 매핑
- kis.py: get_holds/get_ohlcv_min/get_index_min/get_stock_list 스텁 추가,
  oauth() Content-Type 헤더 수정
- get_balance()의 실전 디버깅으로 드러난 버그 수정:
  - Hold.total(매입금액)이 존재하지 않는 byn_amt 필드를 참조해 항상 0이던 것을
    eal_amt - eal_pls_amt로 계산하도록 수정
  - rsp_cd를 "00000" 단일 값으로만 성공 판정해 정상 응답('00218' 연속조회 중,
    '00166' 마지막 페이지 등)을 실패로 오판하던 것을 Output_0 존재 여부로 판정
  - 응답 헤더의 cts/cts_flag로 연속조회를 재귀 처리해 10건 넘는 보유종목도
    전부 합쳐서 반환하도록 구현
- doc/, tests/, example_*.py, setup.py, requirements.txt, CLAUDE.md 등 추가
- README.md에 .env 환경변수 설정 가이드 추가

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01225Lu4Fc2UpMz6QixEcNT8
2026-08-31 14:35:30 +09:00

6.0 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project overview

finestock is a Python package unifying Korean stock brokerage OpenAPIs (EBest, LS, KIS, Kiwoom, NH — live and mock/simulated variants) behind one facade object. It wraps each broker's REST/WebSocket API and normalizes results into shared dataclasses (Price, OrderBook, Account, Order, Trade, ...).

Commands

pip install -e .              # install package in editable mode (requirements: websockets, requests, loguru)
python -m unittest discover tests   # run tests (unittest, not pytest — no pytest config in repo)
python -m unittest tests.test_model -v   # run a single test module

There is no lint/format tooling configured in the repo.

Architecture

Facade + Factory + ISP

finestock.create_api(APIProvider.X) (thin wrapper around APIFactory.create_api, in finestock/api_factory.py) lazy-imports and instantiates one broker class. Every broker class multiply-inherits BaseProvider (finestock/comm/api_interface.py), a union of segregated ABC interfaces:

  • AuthenticationProvider — oauth/token handling
  • MarketDataProvider — price/OHLCV/index/orderbook queries
  • TradingProvider — order placement/cancellation
  • RealtimeProvider — WebSocket subscribe/unsubscribe (recv_price, recv_orderbook, etc.)
  • AccountProvider — balance/holdings
  • InfoProvider — stock/index master lists

Callers narrow the fat facade object to one interface via type hints (market_api: MarketDataProvider = full_api) purely for IDE autocomplete — there is no runtime restriction. When adding a method, add it to the relevant interface in api_interface.py first, then implement it in each concrete broker.

Class hierarchy per broker

Each broker directory (finestock/{ebest,ls,kis,kiwoom,nh}/) has a real/live class and, in most cases, a mock-trading (*V) subclass that only overrides DOMAIN/DOMAIN_WS (via finestock/path.py) or a handful of TR IDs:

  • finestock.comm.api.API — shared base (finestock/comm/api.py): holds app_key/app_secret/access_token, generic headers dict, generic oauth() (client_credentials POST), and set_data_queue/add_data for realtime fan-out.
  • LS(API) — the fullest, canonical implementation (finestock/ls/ls.py, ~860 lines): TR-code-based REST calls (t8410, t8407, t8452, ...) plus a WebSocket connect/run/recv_* loop keyed by tr_cd.
    • EBest(LS) — EBest's API is wire-compatible with LS, so it inherits everything and only swaps DOMAIN/DOMAIN_WS via path.py.
    • LSV(LS) — LS mock trading; same overrides pattern.
  • Kis(API) (finestock/kis/kis.py) — KIS uses tr_id header-based REST TRs.
    • KisV(Kis) — mock trading; overrides get_balance/do_order with VTS-prefixed tr_ids (VTTC... vs TTTC...).
  • Kiwoom(API) (finestock/kiwoom/kiwoom.py, ~850 lines) — Kiwoom's REST+WS shape differs most from the others (api-id header instead of tr_cd/tr_id, trnm/REG WS subscription protocol).
    • KiwoomV(Kiwoom) — mock trading, DOMAIN/DOMAIN_WS swapped to mockapi.kiwoom.com.
  • Nh(API) (finestock/nh/nh.py) — NH투자증권(나무/Namuh) uses a uniform POST {"Input_0": {...}}{rsp_cd, Output_0[, Output_1, Output_2], message} envelope for every REST TR, auth via Authorization/x-client-id/x-client-secret headers (set_oauth_info overridden to set the latter two). No dedicated 호가/지수 TRs — get_orderbook reuses the currentPrice TR's askp1..10/bidp1..10 fields, and get_index/get_index_list/get_stock_list are unimplemented stubs (지수 TR 없음; 종목은 REST가 아니라 .mst 마스터 파일로만 제공). oauth() always targets the fixed live domain (OAUTH_DOMAIN in path.py) even for NhV, since 접근토큰발급 is live-only regardless of which domain trades run against.
    • NhV(Nh) — mock trading (moapi.nhplug.com); no method overrides needed, only DOMAIN/DOMAIN_WS swapped via path.py.

finestock/path.py centralizes all per-broker base URLs and endpoint path fragments in dict constants (_LS_, _KIS_, _KIWOOM_, ...) merged into _API_PATH_, keyed by class name; API._init_path() reads _API_PATH_[self.api_type] and sets each entry as an instance attribute (so self.DOMAIN, self.CHART, self.ORDER, etc. exist post-__init__). When adding a broker or endpoint, edit path.py, not the broker class, unless the URL needs runtime logic.

Data models (finestock/model/)

All public dataclasses are frozen (@dataclass(frozen=True)) and re-exported through finestock/model/__init__.py and top-level finestock/__init__.py. Broker methods parse raw JSON/TR responses and construct these dataclasses directly (e.g. finestock.Price(...), finestock.Order(...)) rather than returning raw dicts — new broker code should follow the same convention. Price.from_values / Price.from_series are the two supported construction paths for Price beyond the raw constructor.

Realtime data flow

Realtime WebSocket data does not use callbacks; a caller injects a queue.Queue-like object via set_data_queue() (in comm/api.py), and broker WS loops (LS.run, Kiwoom.run) push parsed messages onto it via add_data/add_price/add_trade/add_orderbook. Broker-specific TR/type codes in the incoming WS frame determine how a message is parsed and dispatched.

Conventions worth knowing

  • Continuation/pagination in REST calls follows a broker-specific cts_date/cts_time/tr_cont_key (LS, Kiwoom) or CTX_AREA_FK100/NK100 (KIS) pattern — recursive calls with time.sleep() between pages are the existing pattern for LS's get_ohlcv.
  • Method names are standardized across brokers per CHANGELOG.md (PEP 8, single-underscore private prefix, do_order_cancel not do_order_cancle) — but KisV.do_order_cancle (kis_v.py) still uses the old misspelled name and is a stub (pass); don't assume it matches the interface's do_order_cancel without checking.
  • debug.log at the repo root is loguru output, not source.