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 PositionSnapshot, Contract, DailyBar from app.engine.game_theory import net_position, net_pnl, format_pnl router = APIRouter(prefix="/analysis", tags=["analysis"]) INSTITUTIONS = ["中信期货", "高盛期货", "国泰君安期货", "华泰期货", "东证期货", "银河期货"] @router.get("/", response_class=HTMLResponse) def analysis_page(request: Request, db: Session = Depends(get_db)): # Get active contracts for selector active_contracts = ( db.query(Contract.code) .filter(Contract.is_active == True) .order_by(Contract.code) .all() ) contract_list = [c[0] for c in active_contracts] # Default to first contract, or use query param selected = request.query_params.get("contract", contract_list[0] if contract_list else None) if not selected: template = request.app.state.templates.get_template("analysis.html") return HTMLResponse( template.render(request=request, active_nav="analysis", rows=[], latest_date=None) ) # Get latest date for selected contract latest_snap = ( db.query(PositionSnapshot.date) .filter(PositionSnapshot.contract_code == selected) .order_by(PositionSnapshot.date.desc()) .first() ) if not latest_snap: template = request.app.state.templates.get_template("analysis.html") return HTMLResponse( template.render( request=request, active_nav="analysis", rows=[], latest_date=None, contracts=contract_list, selected=selected, ) ) latest_date = latest_snap[0] # Get latest close for current_price reference latest_bar = ( db.query(DailyBar) .filter(DailyBar.contract == selected) .order_by(DailyBar.date.desc()) .first() ) current_price = int(latest_bar.close) if latest_bar else 0 rows = [] total_net_short = 0 pnl_sum = 0.0 for inst in INSTITUTIONS: long = ( db.query(PositionSnapshot) .filter( PositionSnapshot.contract_code == selected, PositionSnapshot.institution == inst, PositionSnapshot.direction == "long", PositionSnapshot.date == latest_date, ) .first() ) short = ( db.query(PositionSnapshot) .filter( PositionSnapshot.contract_code == selected, PositionSnapshot.institution == inst, PositionSnapshot.direction == "short", PositionSnapshot.date == latest_date, ) .first() ) long_pos = long.position if long else 0 short_pos = short.position if short else 0 long_cost = long.avg_cost if long else 0 short_cost = short.avg_cost if short else 0 np = net_position(long_pos, short_pos) pnl = 0.0 if np < 0: pnl = net_pnl(np, short_cost, current_price) elif np > 0: pnl = net_pnl(np, long_cost, current_price) if np < 0: total_net_short += abs(np) pnl_sum += pnl rows.append({ "institution": inst, "long_pos": f"{long_pos / 10000:.1f}万" if long_pos else "—", "short_pos": f"{short_pos / 10000:.1f}万" if short_pos else "—", "net_pos": f"净{'多' if np > 0 else '空'} {abs(np) / 10000:.1f}万", "pnl": format_pnl(pnl), "pnl_raw": pnl, }) template = request.app.state.templates.get_template("analysis.html") return HTMLResponse( template.render( request=request, active_nav="analysis", rows=rows, latest_date=latest_date.strftime("%Y-%m-%d"), current_price=current_price, total_net_short=f"{total_net_short / 10000:.1f}万", total_pnl=format_pnl(pnl_sum), contracts=contract_list, selected=selected, ) )