Files
finance-talk/ft-app/app/routers/trades.py
T
2026-07-26 14:09:20 +08:00

151 lines
4.6 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 Trade, Contract, Product
router = APIRouter(prefix="/trades", tags=["trades"])
DIRECTIONS = [("short", "空"), ("long", "多")]
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:
"""Return {product_code: [contract_codes]} for active contracts."""
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
def today_str() -> str:
return date.today().isoformat()
@router.get("/", response_class=HTMLResponse)
def trades_page(request: Request, db: Session = Depends(get_db)):
open_trades = (
db.query(Trade).filter(Trade.status == "open")
.order_by(Trade.open_date.desc()).all()
)
closed_trades = (
db.query(Trade).filter(Trade.status == "closed")
.order_by(Trade.close_date.desc()).limit(50).all()
)
template = request.app.state.templates.get_template("trades.html")
return HTMLResponse(
template.render(
request=request,
active_nav="trades",
contracts=get_active_contracts(db),
product_contracts=get_product_contracts(db),
directions=DIRECTIONS,
open_trades=open_trades,
closed_trades=closed_trades,
today=today_str(),
weekdays=["周一","周二","周三","周四","周五","周六","周日"],
)
)
@router.post("/open")
def open_trade(
request: Request,
contract_code: str = Form(...),
direction: str = Form(...),
open_date: str = Form(...),
open_price: float = Form(...),
open_fee: float = Form(0.0),
db: Session = Depends(get_db),
):
code = contract_code.upper()
contract = db.query(Contract).filter(Contract.code == code).first()
product_code = contract.product.code if contract else code[:2]
t = Trade(
product_code=product_code,
contract_code=code,
direction=direction,
open_date=date.fromisoformat(open_date),
open_price=open_price,
open_fee=open_fee,
status="open",
)
db.add(t)
db.commit()
return RedirectResponse("/trades/", status_code=303)
@router.post("/{trade_id}/close")
def close_trade(
request: Request,
trade_id: int,
close_date: str = Form(...),
close_price: float = Form(...),
close_fee: float = Form(0.0),
db: Session = Depends(get_db),
):
t = db.query(Trade).filter(Trade.id == trade_id).first()
if t and t.status == "open":
t.close_date = date.fromisoformat(close_date)
t.close_price = close_price
t.close_fee = close_fee
t.status = "closed"
db.commit()
return RedirectResponse("/trades/", status_code=303)
@router.post("/{trade_id}/edit")
def edit_trade(
request: Request,
trade_id: int,
contract_code: str = Form(...),
direction: str = Form(...),
open_date: str = Form(...),
open_price: float = Form(...),
open_fee: float = Form(0.0),
close_date: str = Form(""),
close_price: float = Form(None),
close_fee: float = Form(None),
db: Session = Depends(get_db),
):
t = db.query(Trade).filter(Trade.id == trade_id).first()
if t:
code = contract_code.upper()
contract = db.query(Contract).filter(Contract.code == code).first()
t.product_code = contract.product.code if contract else code[:2]
t.contract_code = code
t.direction = direction
t.open_date = date.fromisoformat(open_date)
t.open_price = open_price
t.open_fee = open_fee
if close_date and close_price is not None:
t.close_date = date.fromisoformat(close_date)
t.close_price = close_price
t.close_fee = close_fee or 0
db.commit()
return RedirectResponse("/trades/", status_code=303)
@router.post("/{trade_id}/delete")
def delete_trade(trade_id: int, db: Session = Depends(get_db)):
t = db.query(Trade).filter(Trade.id == trade_id).first()
if t:
db.delete(t)
db.commit()
return RedirectResponse("/trades/", status_code=303)