feat: config.yaml 기반 설정 마이그레이션 및 대시보드/종목조회 기능 추가

- config.yaml에서 직접 KIS 설정 관리 (BaseSettings 제거)
- 대시보드 KIS 계좌 잔고 실시간 연동, 로컬 DB 폴백
- 종목 조회 페이지: 시세/호가/일봉차트 (canvas) 구현
- 호가 REST API 엔드포인트 (/api/stocks/{code}/orderbook) 추가
- 토큰 캐시(.auth_cache.json) 저장/복원, 1분 재시도 제한
- KIS WebSocket 자동 연결 + 재연결 로직
- httpx 타임아웃 10초→30초, 라우터 타임아웃 핸들링 (504)
- rate limit: requests_per_second 2.0 (모의투자 초당 2건)
- 사이드바 3메뉴: 실거래 / 과거기록 / 종목조회
This commit is contained in:
2026-07-17 15:25:34 +09:00
parent d3f3931c38
commit 182c2b38e6
10 changed files with 419 additions and 85 deletions

View File

@@ -19,7 +19,7 @@ _FID_COND_MRKT_DIV_CODE = "FID_COND_MRKT_DIV_CODE"
class MarketDataService:
def __init__(self) -> None:
self._client = httpx.AsyncClient(timeout=10.0)
self._client = httpx.AsyncClient(timeout=30.0)
self._rate_limiter = RateLimiter(settings.rate_limit.requests_per_second)
def _check_auth(self) -> bool:

View File

@@ -30,6 +30,10 @@ class RealtimeService:
self._heartbeat_task: asyncio.Task | None = None
self._receive_task: asyncio.Task | None = None
@property
def is_connected(self) -> bool:
return self._ws is not None and self._running
def on(self, event: str, callback: Callable) -> None:
self._callbacks.setdefault(event, []).append(callback)
@@ -40,7 +44,7 @@ class RealtimeService:
try:
self._ws = await websockets.connect(
url,
extra_headers={"approval_key": approval_key, "type": "Y"},
additional_headers={"approval_key": approval_key, "type": "Y"},
)
self._running = True
self._receive_task = asyncio.create_task(self._receive_loop())
@@ -106,11 +110,14 @@ class RealtimeService:
await self._handle_message(raw_msg)
except websockets.ConnectionClosed:
logger.warning("WebSocket 연결 끊김. 5초 후 재연결...")
if self._running:
await asyncio.sleep(5)
await self.connect()
except Exception as e:
logger.error("WebSocket 수신 오류: %s", e)
if self._running:
await asyncio.sleep(5)
try:
await self.connect()
except Exception:
logger.error("WebSocket 재연결 실패")
async def _handle_message(self, raw_msg: str) -> None:
try: