e19bb452f9
Co-authored-by: Cursor <cursoragent@cursor.com>
115 lines
3.6 KiB
Python
115 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Annotated
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel, Field
|
|
|
|
from ..market import get_gateway
|
|
from ..models.db import get_db
|
|
from ..sim.ledger import Ledger
|
|
from ..sim.matcher import Matcher
|
|
from ..strategy.clock import window_key
|
|
from ..strategy.group import next_group_id
|
|
from .auth import require_user
|
|
|
|
router = APIRouter(prefix="/api/sim", tags=["sim"])
|
|
|
|
|
|
class ManualOpenBody(BaseModel):
|
|
"""可选强制方向;默认按卖一比价自动选。"""
|
|
force_option_side: str | None = Field(default=None, description="call|put")
|
|
|
|
|
|
@router.get("/ledger")
|
|
async def sim_ledger(_user: Annotated[str, Depends(require_user)]) -> dict:
|
|
return Ledger().snapshot()
|
|
|
|
|
|
@router.get("/position")
|
|
async def sim_position(_user: Annotated[str, Depends(require_user)]) -> dict:
|
|
m = Matcher()
|
|
return {"position": m.current_position(), "unrealized": m.unrealized()}
|
|
|
|
|
|
@router.post("/open-group")
|
|
async def sim_open_group(
|
|
_user: Annotated[str, Depends(require_user)],
|
|
body: ManualOpenBody | None = None,
|
|
) -> dict:
|
|
gw = get_gateway()
|
|
pick = await gw.pick_for_open_async()
|
|
if pick is None:
|
|
raise HTTPException(
|
|
status_code=409,
|
|
detail="无合格期权:请检查剩余时长(≥设置小时)与杠杆(现价/卖一)",
|
|
)
|
|
|
|
force = (body.force_option_side if body else None) or None
|
|
if force in ("call", "put"):
|
|
option_side = force
|
|
perp_side = "short" if force == "call" else "long"
|
|
bias = "manual_" + force
|
|
option_ask = pick.call_ask if force == "call" else pick.put_ask
|
|
from ..config import get_settings
|
|
from ..strategy.selection import option_leverage
|
|
from ..sim.ledger import Ledger as Led
|
|
|
|
s = get_settings()
|
|
min_lev = Led().get_setting_float("min_option_leverage", s.min_option_leverage)
|
|
lev = option_leverage(pick.underlying_px, option_ask)
|
|
if lev is None or lev < min_lev:
|
|
raise HTTPException(
|
|
status_code=409,
|
|
detail=f"强制方向杠杆不足: {lev or 0:.1f} < {min_lev:.0f}",
|
|
)
|
|
else:
|
|
option_side = pick.option_side
|
|
perp_side = pick.perp_side
|
|
bias = pick.bias
|
|
|
|
option_inst = (
|
|
pick.pair.call_inst_id if option_side == "call" else pick.pair.put_inst_id
|
|
)
|
|
|
|
wkey = window_key()
|
|
db = get_db()
|
|
count = len(
|
|
db.fetchall("SELECT group_id FROM groups WHERE group_id LIKE ?", (f"G-{wkey}-%",))
|
|
)
|
|
gid = next_group_id(count)
|
|
r = Matcher().open_group(
|
|
group_id=gid,
|
|
bias=bias,
|
|
option_side=option_side,
|
|
perp_side=perp_side,
|
|
option_inst_id=option_inst,
|
|
entry_index_px=float(pick.underlying_px),
|
|
strike=pick.pair.strike,
|
|
expiry_ymd=pick.pair.expiry_ymd,
|
|
)
|
|
if not r.ok:
|
|
raise HTTPException(status_code=400, detail=r.detail)
|
|
return {
|
|
"ok": True,
|
|
**(r.data or {}),
|
|
"detail": r.detail,
|
|
"option_leverage": pick.option_leverage,
|
|
"hours_left": pick.hours_left,
|
|
"expiry_ymd": pick.pair.expiry_ymd,
|
|
"strike": pick.pair.strike,
|
|
}
|
|
|
|
|
|
@router.post("/close-group")
|
|
async def sim_close_group(_user: Annotated[str, Depends(require_user)]) -> dict:
|
|
r = Matcher().close_group(reason="manual")
|
|
if not r.ok and not r.liquidity_wait:
|
|
raise HTTPException(status_code=400, detail=r.detail)
|
|
return {
|
|
"ok": r.ok,
|
|
"liquidity_wait": r.liquidity_wait,
|
|
"detail": r.detail,
|
|
"data": r.data,
|
|
}
|