ef9238041b
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
193 lines
5.7 KiB
Python
193 lines
5.7 KiB
Python
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()
|
||
)
|
||
|
||
# Compute stop-profit threshold: latest position A × point_value per round
|
||
round_thresholds: dict[int, dict] = {}
|
||
for r in active_rounds:
|
||
if r.positions:
|
||
latest_a = r.positions[-1].amp_threshold
|
||
else:
|
||
latest_a = 0
|
||
contract = db.query(Contract).filter(Contract.code == r.contract_code).first()
|
||
pv = contract.product.point_value if contract and contract.product else 20
|
||
round_thresholds[r.id] = {
|
||
"amp": latest_a,
|
||
"point_value": pv,
|
||
"daily_hands": r.daily_hands,
|
||
"threshold": latest_a * pv * r.daily_hands,
|
||
}
|
||
|
||
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,
|
||
round_thresholds=round_thresholds,
|
||
)
|
||
)
|
||
|
||
|
||
@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)
|