Coverage for brokers / korea_investment / korea_invest_trading_api.py: 94%
58 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-04 15:08 +0000
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-04 15:08 +0000
1# brokers/korea_investment/korea_invest_trading_api.py
2import json
3import os
4import certifi
5import asyncio # 비동기 처리를 위해 추가
6import httpx
8from brokers.korea_investment.korea_invest_api_base import KoreaInvestApiBase
9from brokers.korea_investment.korea_invest_env import KoreaInvestApiEnv
10from brokers.korea_investment.korea_invest_params_provider import Params
11from brokers.korea_investment.korea_invest_header_provider import KoreaInvestHeaderProvider
12from brokers.korea_investment.korea_invest_url_provider import KoreaInvestUrlProvider
13from brokers.korea_investment.korea_invest_url_keys import EndpointKey
14from brokers.korea_investment.korea_invest_trid_provider import KoreaInvestTrIdProvider
15from typing import Optional
16from common.types import ResCommonResponse, ErrorCode, Exchange
19class KoreaInvestApiTrading(KoreaInvestApiBase):
20 def __init__(self,
21 env: KoreaInvestApiEnv,
22 logger,
23 market_clock,
24 async_client: Optional[httpx.AsyncClient] = None,
25 header_provider: Optional[KoreaInvestHeaderProvider] = None,
26 url_provider: Optional[KoreaInvestUrlProvider] = None,
27 trid_provider: Optional[KoreaInvestTrIdProvider] = None):
28 super().__init__(env,
29 logger,
30 market_clock,
31 async_client=async_client,
32 header_provider=header_provider,
33 url_provider=url_provider,
34 trid_provider=trid_provider)
36 async def _get_hashkey(self, data): # async def로 변경됨
37 """
38 주문 요청 Body를 기반으로 Hashkey를 생성하여 반환합니다.
39 이는 별도의 API 호출을 통해 이루어집니다.
40 """
41 response = None
43 try:
44 response: ResCommonResponse = await self.call_api('POST', EndpointKey.HASHKEY,
45 data=data, expect_standard_schema=False, retry_count=3)
47 if response.rt_cd != ErrorCode.SUCCESS.value:
48 return response
50 # response.data.raise_for_status()
51 hash_data = response.data
52 calculated_hashkey = hash_data.get('HASH')
54 if not calculated_hashkey:
55 self._logger.error(f"Hashkey API 응답에 HASH 값이 없습니다: {hash_data}")
56 return None
58 self._logger.info(f"Hashkey 계산 성공: {calculated_hashkey}")
59 return calculated_hashkey
61 except httpx.TimeoutException as e:
62 self._logger.exception(f"Hashkey API 타임아웃: {e}")
63 return None
64 except httpx.HTTPStatusError as e:
65 status = e.response.status_code if e.response is not None else "unknown"
66 body = e.response.text if e.response is not None else ""
67 self._logger.exception(f"Hashkey API HTTP 오류: {status}, 응답: {body!r}")
68 return None
69 except json.JSONDecodeError:
70 self._logger.exception(f"Hashkey API 응답 JSON 디코딩 실패: {response.data.text!r}")
71 return None
72 except Exception as e:
73 self._logger.exception(f"Hashkey API 호출 중 알 수 없는 오류: {e}")
74 return None
76 async def place_stock_order(self, stock_code, order_price, order_qty,
77 is_buy: bool, exchange: Exchange = Exchange.KRX) -> ResCommonResponse: # async def로 변경됨
78 full_config = self._env.active_config
80 tr_id = self._trid_provider.trading_order_cash(is_buy) # 모드에 따라 자동
82 order_dvsn = '00' if int(order_price) > 0 else '01' # 00: 지정가, 01: 시장가
84 # NXT 거래소에서 시장가 주문은 지원하지 않음
85 if exchange == Exchange.NXT and order_dvsn == '01': 85 ↛ 86line 85 didn't jump to line 86 because the condition on line 85 was never true
86 return ResCommonResponse(
87 rt_cd=ErrorCode.INVALID_INPUT.value,
88 msg1="NXT 거래소에서는 시장가 주문을 지원하지 않습니다. 지정가 주문을 사용하세요.",
89 data=None
90 )
92 data = Params.order_cash_body(
93 cano=full_config['stock_account_number'],
94 acnt_prdt_cd="01",
95 pdno=stock_code,
96 ord_dvsn=order_dvsn,
97 ord_qty=order_qty,
98 ord_unpr=order_price,
99 excg_id_dvsn_cd=exchange.value if exchange != Exchange.KRX else "",
100 )
102 calculated_hashkey = await self._get_hashkey(data)
103 if not calculated_hashkey:
104 return ResCommonResponse(
105 rt_cd=ErrorCode.MISSING_KEY.value,
106 msg1=f"hashkey 계산 실패 - {calculated_hashkey}",
107 data=None
108 )
110 with self._headers.temp(tr_id=tr_id, custtype=full_config['custtype'], hashkey=calculated_hashkey):
111 # gt_uid는 temp에서 자동 생성(값 미지정 시)
112 self._headers.set_gt_uid()
113 self._logger.info(
114 f"주식 {'매수' if is_buy else '매도'} 주문 시도 - 종목:{stock_code}, 수량:{order_qty}, 가격:{order_price}")
115 return await self.call_api('POST', EndpointKey.ORDER_CASH, data=data, retry_count=10)