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

1# brokers/korea_investment/korea_invest_trading_api.py 

2import json 

3import os 

4import certifi 

5import asyncio # 비동기 처리를 위해 추가 

6import httpx 

7 

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 

17 

18 

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) 

35 

36 async def _get_hashkey(self, data): # async def로 변경됨 

37 """ 

38 주문 요청 Body를 기반으로 Hashkey를 생성하여 반환합니다. 

39 이는 별도의 API 호출을 통해 이루어집니다. 

40 """ 

41 response = None 

42 

43 try: 

44 response: ResCommonResponse = await self.call_api('POST', EndpointKey.HASHKEY, 

45 data=data, expect_standard_schema=False, retry_count=3) 

46 

47 if response.rt_cd != ErrorCode.SUCCESS.value: 

48 return response 

49 

50 # response.data.raise_for_status() 

51 hash_data = response.data 

52 calculated_hashkey = hash_data.get('HASH') 

53 

54 if not calculated_hashkey: 

55 self._logger.error(f"Hashkey API 응답에 HASH 값이 없습니다: {hash_data}") 

56 return None 

57 

58 self._logger.info(f"Hashkey 계산 성공: {calculated_hashkey}") 

59 return calculated_hashkey 

60 

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 

75 

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 

79 

80 tr_id = self._trid_provider.trading_order_cash(is_buy) # 모드에 따라 자동 

81 

82 order_dvsn = '00' if int(order_price) > 0 else '01' # 00: 지정가, 01: 시장가 

83 

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 ) 

91 

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 ) 

101 

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 ) 

109 

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)