Files
finestock/CLAUDE.md
T
shinalokandClaude Sonnet 5 361b560624 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

64 lines
6.0 KiB
Markdown

# 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
```bash
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_id`s (`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.