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:
@@ -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,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user