zephyrdark 3d5e695559
All checks were successful
Deploy to Production / deploy (push) Successful in 2m7s
fix: resolve multiple frontend/backend bugs and add missing functionality
Backend:
- Fix Decimal serialization in data_explorer.py (Decimal → FloatDecimal)
- Fix Optional type hints for query parameters in admin.py
- Fix authentication bypass in market.py search_stocks endpoint

Frontend:
- Fix 404 page: link to "/" instead of "/dashboard", proper back button
- Rewrite dashboard with real API data instead of hardcoded samples
- Implement actual equity curve and drawdown charts in backtest detail
- Remove mock data from backtest list, load real results from API
- Fix null dividend_yield display in quality strategy page
- Add skeleton loading states to 7 pages that returned null during load

Infrastructure:
- Fix PostgreSQL 18 volume mount compatibility in docker-compose.yml

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-13 22:49:17 +09:00

113 lines
3.1 KiB
Python

"""
Market data API endpoints.
"""
from typing import List, Optional
from datetime import date, timedelta
from fastapi import APIRouter, Depends, HTTPException, Query
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.api.deps import CurrentUser
from app.models.stock import Stock, Valuation, Price, Sector
from app.schemas.strategy import StockInfo, StockSearchResult, PriceData
router = APIRouter(prefix="/api/market", tags=["market"])
@router.get("/stocks/{ticker}", response_model=StockInfo)
async def get_stock(
ticker: str,
current_user: CurrentUser,
db: Session = Depends(get_db),
):
"""Get stock information."""
stock = db.query(Stock).filter(Stock.ticker == ticker).first()
if not stock:
raise HTTPException(status_code=404, detail="Stock not found")
valuation = (
db.query(Valuation)
.filter(Valuation.ticker == ticker)
.order_by(Valuation.base_date.desc())
.first()
)
sector = db.query(Sector).filter(Sector.ticker == ticker).first()
return StockInfo(
ticker=stock.ticker,
name=stock.name,
market=stock.market,
sector_name=sector.sector_name if sector else None,
stock_type=stock.stock_type.value if stock.stock_type else "common",
close_price=stock.close_price,
market_cap=int(stock.market_cap / 100_000_000) if stock.market_cap else None,
per=valuation.per if valuation else None,
pbr=valuation.pbr if valuation else None,
psr=valuation.psr if valuation else None,
pcr=valuation.pcr if valuation else None,
dividend_yield=valuation.dividend_yield if valuation else None,
eps=stock.eps,
bps=stock.bps,
base_date=stock.base_date,
)
@router.get("/stocks/{ticker}/prices", response_model=List[PriceData])
async def get_stock_prices(
ticker: str,
current_user: CurrentUser,
db: Session = Depends(get_db),
days: int = Query(default=365, ge=1, le=3650),
):
"""Get historical prices for a stock."""
start_date = date.today() - timedelta(days=days)
prices = (
db.query(Price)
.filter(Price.ticker == ticker)
.filter(Price.date >= start_date)
.order_by(Price.date.desc())
.all()
)
return [
PriceData(
date=p.date,
open=p.open,
high=p.high,
low=p.low,
close=p.close,
volume=p.volume,
)
for p in prices
]
@router.get("/search", response_model=List[StockSearchResult])
async def search_stocks(
current_user: CurrentUser,
q: str = Query(..., min_length=1),
db: Session = Depends(get_db),
limit: int = Query(default=20, ge=1, le=100),
):
"""Search stocks by ticker or name."""
stocks = (
db.query(Stock)
.filter(
(Stock.ticker.ilike(f"%{q}%")) | (Stock.name.ilike(f"%{q}%"))
)
.limit(limit)
.all()
)
return [
StockSearchResult(
ticker=s.ticker,
name=s.name,
market=s.market,
)
for s in stocks
]