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
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
import asyncio
|
||||
import queue
|
||||
import sys
|
||||
from loguru import logger
|
||||
|
||||
import finestock
|
||||
from finestock import APIProvider
|
||||
from finestock.comm.api_interface import AuthenticationProvider, RealtimeProvider
|
||||
|
||||
# Configure logging
|
||||
logger.remove()
|
||||
logger.add(sys.stdout, level="DEBUG")
|
||||
|
||||
async def consume_data(data_queue: queue.Queue):
|
||||
"""
|
||||
Background task to consume data from the queue asynchronously.
|
||||
"""
|
||||
logger.info("Data consumer started...")
|
||||
while True:
|
||||
try:
|
||||
if not data_queue.empty():
|
||||
data = data_queue.get_nowait()
|
||||
print(f"Received Realtime Data: {data}")
|
||||
else:
|
||||
await asyncio.sleep(0.1) # Yield control
|
||||
except Exception as e:
|
||||
logger.error(f"Consumer Error: {e}")
|
||||
await asyncio.sleep(1)
|
||||
|
||||
async def main():
|
||||
finestock.print_version_info()
|
||||
|
||||
# 1. Create API Instance
|
||||
api = finestock.create_api(APIProvider.KIWOOM)
|
||||
|
||||
# 2. Setup Authentication
|
||||
# Kiwoom requires app key/secret?
|
||||
# Based on kiwoom.py:
|
||||
# self.app_key = None
|
||||
# self.app_secret = None
|
||||
# OAuth uses them.
|
||||
# I should use the keys if I have them or reuse from current context if needed.
|
||||
# example_v1.py used:
|
||||
# api.set_oauth_info("RSvh1vtzHj8Z5F15Ee7_rJ5znHuAKOF6A8v90TEkyBk", "_7wuGnkD-Crf1TJKErJPYkQp7AVuh_gVOPgQ2Uyyh7k")
|
||||
|
||||
auth_api: AuthenticationProvider = api
|
||||
if isinstance(auth_api, AuthenticationProvider):
|
||||
APP_KEY = "RSvh1vtzHj8Z5F15Ee7_rJ5znHuAKOF6A8v90TEkyBk"
|
||||
APP_SECRET = "_7wuGnkD-Crf1TJKErJPYkQp7AVuh_gVOPgQ2Uyyh7k"
|
||||
auth_api.set_oauth_info(APP_KEY, APP_SECRET)
|
||||
token = auth_api.oauth()
|
||||
logger.info(f"Logged in.")
|
||||
|
||||
# 3. Setup Realtime Data (Async)
|
||||
realtime_api: RealtimeProvider = api
|
||||
if isinstance(realtime_api, RealtimeProvider):
|
||||
# Create Queue for data
|
||||
data_queue = queue.Queue()
|
||||
realtime_api.set_data_queue(data_queue)
|
||||
|
||||
# Connect to WebSocket
|
||||
logger.info("Connecting to WebSocket...")
|
||||
await realtime_api.connect()
|
||||
|
||||
# Login is handled in connect
|
||||
|
||||
|
||||
# Subscribe to Realtime Prices
|
||||
# Ex: Samsung Electronics (005930)
|
||||
logger.info("Subscribing to 005930...")
|
||||
await realtime_api.recv_price("005930")
|
||||
|
||||
# Subscribe to Realtime Index
|
||||
# Ex: KOSPI (001)
|
||||
logger.info("Subscribing to KOSPI (001)...")
|
||||
await realtime_api.recv_index("001")
|
||||
|
||||
# Start the data consumer task
|
||||
consumer_task = asyncio.create_task(consume_data(data_queue))
|
||||
|
||||
# Start the API run loop
|
||||
logger.info("Starting API Event Loop...")
|
||||
|
||||
# Run for 10 seconds then exit for verification
|
||||
try:
|
||||
await asyncio.wait_for(realtime_api.run(), timeout=30)
|
||||
except asyncio.TimeoutError:
|
||||
logger.info("Test finished (timeout)")
|
||||
|
||||
# Cleanup
|
||||
consumer_task.cancel()
|
||||
await realtime_api.disconnect()
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
asyncio.run(main())
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Terminated by user")
|
||||
Reference in New Issue
Block a user