Files
stockautomtion/app/routers/stocks.py
gerd 182c2b38e6 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메뉴: 실거래 / 과거기록 / 종목조회
2026-07-17 15:25:34 +09:00

98 lines
3.1 KiB
Python

from __future__ import annotations
import httpx
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.models.stock import Stock
from app.services.market_data import market_data_service
router = APIRouter(prefix="/api/stocks", tags=["stocks"])
class StockCreate(BaseModel):
code: str
name: str
market: str = "KRX"
class StockUpdate(BaseModel):
name: str | None = None
is_active: bool | None = None
@router.get("/")
def list_stocks(db: Session = Depends(get_db)) -> list[dict]:
stocks = db.query(Stock).all()
return [
{
"code": s.code,
"name": s.name,
"market": s.market,
"is_active": s.is_active,
"created_at": s.created_at.isoformat() if s.created_at else None,
}
for s in stocks
]
@router.post("/")
def add_stock(data: StockCreate, db: Session = Depends(get_db)) -> dict:
existing = db.query(Stock).filter(Stock.code == data.code).first()
if existing:
raise HTTPException(status_code=409, detail="이미 존재하는 종목입니다")
stock = Stock(code=data.code, name=data.name, market=data.market)
db.add(stock)
db.commit()
db.refresh(stock)
return {"code": stock.code, "name": stock.name, "market": stock.market, "is_active": stock.is_active}
@router.delete("/{stock_code}")
def remove_stock(stock_code: str, db: Session = Depends(get_db)) -> dict:
stock = db.query(Stock).filter(Stock.code == stock_code).first()
if not stock:
raise HTTPException(status_code=404, detail="종목을 찾을 수 없습니다")
stock.is_active = False
db.commit()
return {"message": "종목이 비활성화되었습니다", "code": stock_code}
@router.get("/{stock_code}/price")
async def get_price(stock_code: str) -> dict:
try:
price = await market_data_service.get_current_price(stock_code)
except (httpx.TimeoutException, httpx.ConnectError):
raise HTTPException(status_code=504, detail="KIS 서버 응답 시간 초과")
if not price:
raise HTTPException(status_code=404, detail="시세 조회 실패")
return price
@router.get("/{stock_code}/chart")
async def get_chart(stock_code: str, days: int = 30) -> list[dict]:
from datetime import datetime, timedelta
end = datetime.now().strftime("%Y%m%d")
start = (datetime.now() - timedelta(days=days)).strftime("%Y%m%d")
try:
chart = await market_data_service.get_daily_chart(stock_code, start, end, days)
except (httpx.TimeoutException, httpx.ConnectError):
raise HTTPException(status_code=504, detail="KIS 서버 응답 시간 초과")
return chart
@router.get("/{stock_code}/orderbook")
async def get_orderbook(stock_code: str) -> dict:
try:
orderbook = await market_data_service.get_orderbook(stock_code)
except (httpx.TimeoutException, httpx.ConnectError):
raise HTTPException(status_code=504, detail="KIS 서버 응답 시간 초과")
if not orderbook:
raise HTTPException(status_code=404, detail="호가 조회 실패")
return orderbook