From 50b026edd7f38796d024f5861676280009f4df75 Mon Sep 17 00:00:00 2001 From: jongjae Date: Tue, 19 May 2026 08:08:00 +0900 Subject: [PATCH] =?UTF-8?q?[2026-05-19]=20KIS=20=ED=86=A0=ED=81=B0=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=BA=90=EC=8B=9C=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=E2=80=94=20=EB=B4=87=20=EC=9E=AC=EC=8B=9C=EC=9E=91=20=EC=8B=9C?= =?UTF-8?q?=20API=20=EC=86=8D=EB=8F=84=20=EC=A0=9C=ED=95=9C=20=ED=9A=8C?= =?UTF-8?q?=ED=94=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 재시작 간격이 짧을 때 발생하는 EGW00133(1분당 1회 제한) 오류를 방지하기 위해 토큰을 data/kis_token_{mode}.json에 저장하고 재시작 시 유효 토큰을 재사용한다. Co-Authored-By: Claude Sonnet 4.6 --- app/execution/kis_client.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/app/execution/kis_client.py b/app/execution/kis_client.py index 99727ce..d4d52c4 100644 --- a/app/execution/kis_client.py +++ b/app/execution/kis_client.py @@ -48,6 +48,12 @@ class KISClient: # 토큰 관련 self._access_token : Optional[str] = None self._token_expires_at: Optional[datetime] = None + # 토큰 파일 캐시 경로 (재시작 시 재사용) + mode_tag = "mock" if self.is_mock else "real" + self._token_cache_file = os.path.join( + os.path.dirname(__file__), "..", "..", "data", f"kis_token_{mode_tag}.json" + ) + self._load_token_from_file() # rate limit: 모의투자 1건/초, 실거래 5건/초 self._rate_limit = 1 if self.is_mock else 5 @@ -71,6 +77,32 @@ class KISClient: # 토큰 관리 # ───────────────────────────────────────── + def _load_token_from_file(self): + """재시작 시 파일 캐시에서 토큰 복원""" + try: + if os.path.exists(self._token_cache_file): + with open(self._token_cache_file, encoding="utf-8") as f: + cached = json.load(f) + expires_at = datetime.fromisoformat(cached["expires_at"]) + if datetime.now() < expires_at - timedelta(minutes=30): + self._access_token = cached["access_token"] + self._token_expires_at = expires_at + logger.info("KIS 토큰 파일 캐시 복원 완료") + except Exception: + pass + + def _save_token_to_file(self): + """토큰을 파일에 저장 (재시작 시 재사용)""" + try: + os.makedirs(os.path.dirname(self._token_cache_file), exist_ok=True) + with open(self._token_cache_file, "w", encoding="utf-8") as f: + json.dump({ + "access_token": self._access_token, + "expires_at": self._token_expires_at.isoformat(), + }, f) + except Exception: + pass + async def get_access_token(self) -> str: """액세스 토큰 발급/갱신 (만료 30분 전 자동 갱신)""" now = datetime.now() @@ -96,6 +128,7 @@ class KISClient: self._access_token = data["access_token"] # 유효기간 24시간 self._token_expires_at = now + timedelta(hours=24) + self._save_token_to_file() logger.info("KIS 액세스 토큰 발급/갱신 완료") return self._access_token