from datetime import date from fastapi import APIRouter, Depends, Request from fastapi.responses import HTMLResponse from sqlalchemy.orm import Session from app.database import get_db from app.models import DailyBar, Contract router = APIRouter(prefix="/contracts", tags=["contracts"]) WEEKDAY_ZH = {0: "周一", 1: "周二", 2: "周三", 3: "周四", 4: "周五", 5: "周六", 6: "周日"} def get_active_contracts(db: Session) -> list[str]: contracts = ( db.query(Contract.code) .filter(Contract.is_active == True) .order_by(Contract.code) .all() ) return [c[0] for c in contracts] @router.get("/", response_class=HTMLResponse) def contract_index(request: Request, db: Session = Depends(get_db)): active_contracts = get_active_contracts(db) latest = ( db.query(DailyBar) .filter(DailyBar.contract.in_(active_contracts)) .order_by(DailyBar.date.desc()) .all() ) contract_bars = {} for bar in latest: if bar.contract not in contract_bars: contract_bars[bar.contract] = bar total_bars = db.query(DailyBar).count() latest_bar = ( db.query(DailyBar).order_by(DailyBar.date.desc()).first() ) template = request.app.state.templates.get_template("index.html") return HTMLResponse( template.render( request=request, active_nav="contracts", contracts=active_contracts, contract_bars=contract_bars, total_bars=total_bars, latest_date=latest_bar.date.strftime("%Y-%m-%d") if latest_bar else "—", ) ) @router.get("/{contract}", response_class=HTMLResponse) def contract_detail(request: Request, contract: str, db: Session = Depends(get_db)): active_contracts = get_active_contracts(db) bars = ( db.query(DailyBar) .filter(DailyBar.contract == contract.upper()) .order_by(DailyBar.date.desc()) .all() ) rows = [] for bar in bars: rows.append({ "date": bar.date.strftime("%Y/%-m/%-d"), "weekday": WEEKDAY_ZH.get(bar.date.weekday(), ""), "open": int(bar.open) if bar.open else "-", "close": int(bar.close) if bar.close else "-", "high": int(bar.high) if bar.high else "-", "low": int(bar.low) if bar.low else "-", "diff": int(bar.diff) if bar.diff else 0, "amp_5d": int(bar.amp_5d) if bar.amp_5d is not None else None, "has_amp": bar.amp_5d is not None, }) latest = bars[0] if bars else None # Predict next trading day amplitude: mean of latest 5 diffs next_amp = None if len(bars) >= 5: next_amp = round(sum(b.diff for b in bars[:5]) / 5) template = request.app.state.templates.get_template("contract.html") return HTMLResponse( template.render( request=request, active_nav="contracts", contract=contract.upper(), contracts=active_contracts, rows=rows, latest=latest, next_amp=next_amp, row_count=len(rows), ) )