From f2ce17ba48c7ef3ba0913b5774aec0650af7cfba Mon Sep 17 00:00:00 2001 From: jongjae Date: Mon, 18 May 2026 13:05:17 +0900 Subject: [PATCH] =?UTF-8?q?[2026-05-18]=20KIS=20API=20rate=20limit=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=E2=80=94=20=EB=AA=A8=EC=9D=98=ED=88=AC?= =?UTF-8?q?=EC=9E=90=201=EA=B1=B4/=EC=B4=88,=20=EC=A7=84=EC=9E=85=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=20=EB=B6=88=ED=95=84=EC=9A=94=20=ED=98=B8=EC=B6=9C=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/execution/kis_client.py | 10 +++++----- app/main.py | 21 ++++++++------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/app/execution/kis_client.py b/app/execution/kis_client.py index 64b5a58..08722c5 100644 --- a/app/execution/kis_client.py +++ b/app/execution/kis_client.py @@ -49,9 +49,10 @@ class KISClient: self._access_token : Optional[str] = None self._token_expires_at: Optional[datetime] = None - # rate limit: 초당 20건 - self._semaphore = asyncio.Semaphore(20) - self._req_times : list = [] + # rate limit: 모의투자 1건/초, 실거래 5건/초 + self._rate_limit = 1 if self.is_mock else 5 + self._semaphore = asyncio.Semaphore(1) + self._req_times : list = [] mode = "모의투자" if self.is_mock else "실거래" logger.info(f"KISClient 초기화 완료 [{mode}] 계좌: {self.account_no}") @@ -127,10 +128,9 @@ class KISClient: } async with self._semaphore: - # 초당 20건 rate limit now = time.monotonic() self._req_times = [t for t in self._req_times if now - t < 1.0] - if len(self._req_times) >= 20: + if len(self._req_times) >= self._rate_limit: wait = 1.0 - (now - self._req_times[0]) if wait > 0: await asyncio.sleep(wait) diff --git a/app/main.py b/app/main.py index e560060..54263eb 100644 --- a/app/main.py +++ b/app/main.py @@ -239,13 +239,15 @@ class StockBot: continue if len(self.positions) >= MAX_POSITIONS: break + # 목표가 미계산 종목 스킵 (불필요한 API 호출 방지) + if self.strategy.get_target(ticker) <= 0: + continue try: - price_info = await self.kis.get_price(ticker) + price_info = await self.kis.get_price(ticker) # rate limiter가 자동 throttle current = price_info["current"] name = price_info.get("name", ticker) - # 전략 신호 체크 signal = self.strategy.check_entry( ticker=ticker, name=name, @@ -255,15 +257,11 @@ class StockBot: if not signal["signal"]: continue - # 포지션 사이즈 계산 - balance = await self.kis.get_balance() - cash = balance["cash"] - invest = self.risk.get_pos_size( - cash, signal.get("multiplier", 1.0) - ) - qty = max(1, int(invest // current)) + balance = await self.kis.get_balance() + cash = balance["cash"] + invest = self.risk.get_pos_size(cash, signal.get("multiplier", 1.0)) + qty = max(1, int(invest // current)) - # 매수 실행 result = await self.executor.buy( ticker=ticker, name=name, qty=qty, reason=signal["reason"], @@ -273,7 +271,6 @@ class StockBot: if result["success"]: entry_price = result["price"] or current sl_price = entry_price * (1 - self.risk.get_sl_pct()) - tp1_price = entry_price * (1 + 0.02) self.positions[ticker] = { "name" : name, @@ -293,8 +290,6 @@ class StockBot: boosted=signal.get("boosted", False), ) - await asyncio.sleep(1.1) - except Exception as e: logger.error(f"진입 체크 오류 {ticker}: {e}")