from datetime import date from fastapi import APIRouter, Depends, Form, Request from fastapi.responses import HTMLResponse, RedirectResponse from sqlalchemy.orm import Session from app.database import get_db from app.models import Round, Position, Contract, Product router = APIRouter(prefix="/positions", tags=["positions"]) 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] def get_product_contracts(db: Session) -> dict: result: dict[str, list[str]] = {} products = db.query(Product).order_by(Product.code).all() for p in products: codes = [c.code for c in p.contracts if c.is_active] if codes: result[p.code] = codes return result @router.get("/", response_class=HTMLResponse) def positions_page(request: Request, db: Session = Depends(get_db)): active_rounds = ( db.query(Round) .filter(Round.status == "active") .order_by(Round.started_at.desc()) .all() ) template = request.app.state.templates.get_template("positions.html") return HTMLResponse( template.render( request=request, active_nav="positions", contracts=get_active_contracts(db), product_contracts=get_product_contracts(db), active_rounds=active_rounds, ) ) @router.post("/round") def create_round( request: Request, contract_code: str = Form(...), daily_hands: int = Form(1), db: Session = Depends(get_db), ): code = contract_code.upper() existing = db.query(Round).filter( Round.status == "active", Round.contract_code == code ).first() if existing: return RedirectResponse(f"/positions/?error={code} 已有活跃轮次", status_code=303) r = Round( contract_code=code, daily_hands=daily_hands, status="active", started_at=date.today(), ) db.add(r) db.commit() return RedirectResponse("/positions/", status_code=303) @router.post("/{round_id}/add") def add_position( request: Request, round_id: int, open_price: int = Form(...), amp_threshold: int = Form(...), lock_price: str = Form(""), hands: int = Form(1), db: Session = Depends(get_db), ): r = db.query(Round).filter(Round.id == round_id).first() if not r or r.status != "active": return RedirectResponse("/positions/?error=轮次不存在或已结束", status_code=303) final_lock = int(lock_price) if lock_price else open_price + amp_threshold p = Position( round_id=r.id, open_price=open_price, amp_threshold=amp_threshold, lock_price=final_lock, hands=hands, opened_at=date.today(), ) db.add(p) db.commit() return RedirectResponse("/positions/", status_code=303) @router.post("/{position_id}/lock") def lock_position( position_id: int, lock_price: str = Form(""), db: Session = Depends(get_db), ): p = db.query(Position).filter(Position.id == position_id).first() if p and p.locked_count < p.hands: if lock_price: p.lock_price = int(lock_price) p.locked_count += 1 db.commit() return RedirectResponse("/positions/", status_code=303) @router.post("/{position_id}/unlock") def unlock_position(position_id: int, db: Session = Depends(get_db)): p = db.query(Position).filter(Position.id == position_id).first() if p and p.locked_count > 0: p.locked_count -= 1 db.commit() return RedirectResponse("/positions/", status_code=303) @router.post("/{position_id}/update-lock-price") def update_lock_price( position_id: int, lock_price: int = Form(...), db: Session = Depends(get_db), ): p = db.query(Position).filter(Position.id == position_id).first() if p and p.locked_count == 0: p.lock_price = lock_price db.commit() return RedirectResponse("/positions/", status_code=303) @router.post("/{round_id}/max-locks") def update_max_locks( round_id: int, max_locks: int = Form(...), db: Session = Depends(get_db), ): r = db.query(Round).filter(Round.id == round_id).first() if r and r.status == "active" and 1 <= max_locks <= 10: r.max_locks = max_locks db.commit() return RedirectResponse("/positions/", status_code=303) @router.post("/{round_id}/close") def close_round( request: Request, round_id: int, result: str = Form(...), db: Session = Depends(get_db), ): r = db.query(Round).filter(Round.id == round_id).first() if r and r.status == "active": r.status = result db.commit() return RedirectResponse("/positions/", status_code=303) @router.post("/{round_id}/delete") def delete_round(round_id: int, db: Session = Depends(get_db)): r = db.query(Round).filter(Round.id == round_id).first() if r: db.delete(r) db.commit() return RedirectResponse("/positions/", status_code=303)