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

@@ -5,22 +5,59 @@ from sqlalchemy.orm import Session
from app.core.auth import token_manager
from app.core.database import get_db
from app.models.stock import Holding, PriceHistory, Strategy, Trade, Stock
from app.models.stock import Holding, Strategy, Trade, Stock
from app.engine.scheduler import scheduler
from app.services.account import account_service
from app.services.realtime import realtime_service
router = APIRouter(prefix="/api/dashboard", tags=["dashboard"])
@router.get("/")
def dashboard(db: Session = Depends(get_db)) -> dict:
holdings = db.query(Holding).all()
async def dashboard(db: Session = Depends(get_db)) -> dict:
recent_trades = db.query(Trade).order_by(Trade.created_at.desc()).limit(20).all()
active_strategies = db.query(Strategy).filter(Strategy.is_active == True).all()
active_stocks = db.query(Stock).filter(Stock.is_active == True).all()
total_profit = sum(h.profit for h in holdings)
total_invested = sum(h.avg_price * h.qty for h in holdings if h.avg_price > 0)
total_evaluated = sum(h.current_price * h.qty for h in holdings if h.current_price > 0)
holdings = []
total_profit = 0.0
total_invested = 0.0
total_evaluated = 0.0
if token_manager.is_authenticated:
try:
balance = await account_service.get_balance()
for s in balance.get("stocks", []):
holdings.append({
"stock_code": s["stock_code"],
"stock_name": s["stock_name"],
"qty": s["qty"],
"avg_price": s["avg_price"],
"current_price": s["current_price"],
"profit": s["profit"],
"profit_rate": s["profit_rate"],
})
total_invested += s.get("buy_amount", s["avg_price"] * s["qty"])
total_evaluated += s.get("eval_amount", s["current_price"] * s["qty"])
total_profit += s["profit"]
except Exception:
pass
if not holdings:
local = db.query(Holding).all()
for h in local:
holdings.append({
"stock_code": h.stock_code,
"stock_name": h.stock_name,
"qty": h.qty,
"avg_price": h.avg_price,
"current_price": h.current_price,
"profit": h.profit,
"profit_rate": h.profit_rate,
})
total_invested += h.avg_price * h.qty
total_evaluated += h.current_price * h.qty
total_profit += h.profit
jobs = []
if scheduler.running:
@@ -42,18 +79,7 @@ def dashboard(db: Session = Depends(get_db)) -> dict:
"active_strategies": len(active_strategies),
"active_stocks": len(active_stocks),
},
"holdings": [
{
"stock_code": h.stock_code,
"stock_name": h.stock_name,
"qty": h.qty,
"avg_price": h.avg_price,
"current_price": h.current_price,
"profit": h.profit,
"profit_rate": h.profit_rate,
}
for h in holdings
],
"holdings": holdings,
"recent_trades": [
{
"id": t.id,
@@ -68,3 +94,11 @@ def dashboard(db: Session = Depends(get_db)) -> dict:
],
"scheduler_jobs": jobs,
}
@router.get("/status")
def get_status() -> dict:
return {
"authenticated": token_manager.is_authenticated,
"realtime_connected": realtime_service.is_connected,
}