Files
finestock/example_v1.py
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

192 lines
8.1 KiB
Python

import sys
from loguru import logger
import finestock
from finestock import APIProvider
from finestock.comm.api_interface import AuthenticationProvider, MarketDataProvider, TradingProvider, AccountProvider
# Configure logging
logger.remove()
logger.add(sys.stdout, level="INFO")
def main():
finestock.print_version_info()
# 1. Create API Instance
# The 'api' object implements multiple interfaces (Authentication, MarketData, Realtime, etc.)
api = finestock.create_api(APIProvider.KIWOOMV)
# 2. Setup Authentication (Sync)
# Type Hinting: View 'api' as AuthenticationProvider to see only auth-related methods
auth_api: AuthenticationProvider = api
if isinstance(auth_api, AuthenticationProvider):
#APP_KEY = "YOUR_APP_KEY"
#APP_SECRET = "YOUR_APP_SECRET"
#APP_KEY = "PSgjLpp90XLhWexJYGL0P36oyzM3ne5kjhDW" #LSV
#APP_SECRET = "htA3h9kULFPD75vtHAt0JaXyYAjsbf0b" #LSV
#APP_KEY = "RSvh1vtzHj8Z5F15Ee7_rJ5znHuAKOF6A8v90TEkyBk"
#APP_SECRET = "_7wuGnkD-Crf1TJKErJPYkQp7AVuh_gVOPgQ2Uyyh7k"
APP_KEY = "5-7V0r97uwlzmPNFa_FnwFEZtnyhFAZ1qlnDXQH2rX4"
APP_SECRET = "YOMvV7X1CwwST-_iFmRAPKXO7Wwr4zGGyTPX5gFcAqk"
auth_api.set_oauth_info(APP_KEY, APP_SECRET)
token = auth_api.oauth()
print(token)
# auth_api.oauth() # Perform actual login
# or auth_api.set_access_token("TOKEN")
# 3. Market Data (Sync)
logger.info("Fetching Market Data (Sync)...")
# Type Hinting: View 'api' as MarketDataProvider
market_api: MarketDataProvider = api
if isinstance(market_api, MarketDataProvider):
try:
# ohlcvs = market_api.get_ohlcv("005930", "20240101", "20240105")
# print(f"Received {len(ohlcvs)} records")
ohlcv = market_api.get_price("005930")
print(f"Received {ohlcv}")
exit()
# Example: Parsing Price objects
print(f"{'Date':<10} | {'Close':<10} | {'Volume':<10}")
print("-" * 36)
price: finestock.Price = ohlcv
# Accessing fields of the Price dataclass
print(f"{price.workday:<10} | {price.close:<10} | {price.volume:<10}")
except Exception as e:
logger.error(f"Market Data Error: {e}")
exit()
try:
# ohlcvs = market_api.get_ohlcv("005930", "20240101", "20240105")
# print(f"Received {len(ohlcvs)} records")
ohlcvs = market_api.get_ohlcv("005930", "20240101", "20240105")
print(f"Received {len(ohlcvs)} records")
# Example: Parsing Price objects
print(f"{'Date':<10} | {'Close':<10} | {'Volume':<10}")
print("-" * 36)
for price in ohlcvs:
price: finestock.Price = price
# Accessing fields of the Price dataclass
print(f"{price.workday:<10} | {price.close:<10} | {price.volume:<10}")
except Exception as e:
logger.error(f"Market Data Error: {e}")
try:
logger.info("Fetching Stock List (KOSPI)...")
# info_api = api # InfoProvider
if hasattr(market_api, 'get_stock_list'):
stocks = market_api.get_stock_list("0") # 0: KOSPI
print(f"Received {len(stocks)} stocks for KOSPI")
print(f"First 5 stocks:")
for s in stocks[:5]:
print(s)
except Exception as e:
logger.error(f"Stock List Error: {e}")
try:
logger.info("Fetching KOSDAQ Index List...")
# market_api is typed as MarketDataProvider if using interface, but get_index_list is specific to Kiwoom or extended interface
# Since market_api is actually the API instance, we can call it if available
if hasattr(market_api, 'get_index_list'):
indices = market_api.get_index_list("0") # 0: KOSPI
print(f"Received {len(indices)} indices for KOSPI")
print(f"First 5 indices:")
for idx in indices[:5]:
print(f"Code: {idx.code}, Name: {idx.name}, Market: {idx.market}, Group: {idx.group}")
except Exception as e:
logger.error(f"Index List Error: {e}")
try:
logger.info("Fetching KOSDAQ 150 Index Data...")
# KOSDAQ 150 Code: 150
ohlcvs = market_api.get_index("150", "20260201", "20260213")
print(f"Received {len(ohlcvs)} records for KOSDAQ 150")
print(f"{'Date':<10} | {'Close':<10} | {'Volume':<10}")
print("-" * 36)
for price in ohlcvs:
price: finestock.Price = price
print(f"{price.workday:<10} | {price.close:<10} | {price.volume:<10}")
except Exception as e:
logger.error(f"KOSDAQ 150 Error: {e}")
try:
logger.info("Fetching Orderbook for Samsung Electronics (005930)...")
orderbook = market_api.get_orderbook("005930")
if orderbook:
print(f"Orderbook for {orderbook.code}")
print(f"Total Buy: {orderbook.total_buy}, Total Sell: {orderbook.total_sell}")
print("Sell Side (Top 5):")
for h in orderbook.sell[:5]:
print(f" Price: {h.price}, Qty: {h.qty}")
print("Buy Side (Top 5):")
for h in orderbook.buy[:5]:
print(f" Price: {h.price}, Qty: {h.qty}")
else:
print("Orderbook empty or failed.")
except Exception as e:
logger.error(f"Orderbook Error: {e}")
# 4. Trading (Sync)
logger.info("Trading Example...")
trading_api: TradingProvider = api
if isinstance(trading_api, TradingProvider):
try:
# Example: Buy Samsung Electronics (005930) 1 share at Market Price
# trde_tp 3: Market Price, 0: Limit Price
# In do_order, price=0 means Market Price (trde_tp="3")
# Uncomment to execute
# logger.info("Placing Buy Order for 005930 (1 qty, Market Price)...")
#result = trading_api.do_order("005930", finestock.ORDER_FLAG.BUY, 0, 1)
#result = trading_api.do_order("005930", finestock.ORDER_FLAG.BUY, 173000, 1)
#result = trading_api.do_order("005930", finestock.ORDER_FLAG.BUY, 176000, 1)
result = trading_api.do_order_cancel("0157069", "005930", 1)
#result = trading_api.do_order("005930", finestock.ORDER_FLAG.SELL, 177200, 1)
print(f"Order Result: {result}")
#print("To place an order, uncomment the lines in the script.")
# Example: Cancel Order
# logger.info("Cancelling Order 0000140 for 005930 (1 qty)...")
# result = trading_api.do_order_cancel("0000140", "005930", 1)
# print(f"Cancel Result: {result}")
except Exception as e:
logger.error(f"Trading Error: {e}")
# 5. Account (Sync)
logger.info("Account Example...")
account_api: AccountProvider = api
if isinstance(account_api, AccountProvider):
try:
logger.info("Fetching Account Balance...")
balance = account_api.get_balance()
if balance:
print(f"Account: {balance.account_num}")
print(f"Deposit: {balance.deposit}")
print(f"Next Deposit: {balance.next_deposit}")
print(f"Pay Deposit: {balance.pay_deposit}")
else:
print("Failed to get balance.")
logger.info("Fetching Account Holdings...")
holds = account_api.get_holds()
print(f"Received {len(holds)} holdings")
if holds:
print(f"{'Code':<10} | {'Name':<20} | {'Qty':<10} | {'Eval':<15}")
print("-" * 60)
for h in holds:
print(f"{h.code:<10} | {h.name:<20} | {h.qty:<10} | {h.eval:<15}")
else:
print("No holdings or failed to fetch.")
except Exception as e:
logger.error(f"Account Error: {e}")
if __name__ == "__main__":
main()