中控增加条件单委托
This commit is contained in:
@@ -35,6 +35,8 @@ from exchange_orders import (
|
||||
cancel_order as hub_cancel_order,
|
||||
cancel_orders_for_symbol,
|
||||
list_open_orders,
|
||||
replace_position_tpsl,
|
||||
symbols_match,
|
||||
)
|
||||
|
||||
HOST = os.getenv("HOST", "127.0.0.1")
|
||||
@@ -299,6 +301,14 @@ class CancelSymbolOrdersBody(BaseModel):
|
||||
scope: str = "all" # all | conditional | limit
|
||||
|
||||
|
||||
class PlaceTpslBody(BaseModel):
|
||||
symbol: str
|
||||
side: str # long | short
|
||||
stop_loss: float
|
||||
take_profit: float
|
||||
contracts: float | None = None
|
||||
|
||||
|
||||
def _close_position_market(
|
||||
ex: Any, sym: str, side: str, contracts: float
|
||||
) -> tuple[dict[str, Any] | None, str | None]:
|
||||
@@ -629,6 +639,58 @@ def cancel_symbol_orders(
|
||||
)
|
||||
|
||||
|
||||
@app.post("/orders/place-tpsl")
|
||||
def place_tpsl_orders(
|
||||
body: PlaceTpslBody,
|
||||
x_control_token: str | None = Header(default=None, alias="X-Control-Token"),
|
||||
):
|
||||
"""先撤该合约全部条件单,再挂止盈+止损(与四实例策略逻辑一致)。"""
|
||||
_check_token(x_control_token)
|
||||
sym = (body.symbol or "").strip()
|
||||
side = (body.side or "").strip().lower()
|
||||
if not sym or side not in ("long", "short"):
|
||||
raise HTTPException(status_code=400, detail="symbol 与 side(long/short) 必填")
|
||||
try:
|
||||
sl = float(body.stop_loss)
|
||||
tp = float(body.take_profit)
|
||||
except (TypeError, ValueError) as e:
|
||||
raise HTTPException(status_code=400, detail="stop_loss / take_profit 须为数字") from e
|
||||
try:
|
||||
ex = get_exchange()
|
||||
_ensure_markets()
|
||||
amt = body.contracts
|
||||
if amt is None or float(amt) <= 0:
|
||||
raw = ex.fetch_positions() or []
|
||||
found = None
|
||||
for p in raw:
|
||||
psym = p.get("symbol") or ""
|
||||
if not symbols_match(sym, psym):
|
||||
continue
|
||||
c = abs(float(p.get("contracts") or 0))
|
||||
if c <= 0:
|
||||
continue
|
||||
ps = (p.get("side") or "").lower()
|
||||
if ps and ps != side:
|
||||
continue
|
||||
found = c
|
||||
break
|
||||
if found is None:
|
||||
return JSONResponse(
|
||||
{"ok": False, "error": f"未找到持仓 {sym} {side}", "exchange": EXCHANGE_KIND},
|
||||
status_code=200,
|
||||
)
|
||||
amt = found
|
||||
info = replace_position_tpsl(ex, EXCHANGE_KIND, sym, side, float(amt), sl, tp)
|
||||
return {"ok": True, "exchange": EXCHANGE_KIND, "placed": info}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
return JSONResponse(
|
||||
{"ok": False, "error": str(e), "exchange": EXCHANGE_KIND},
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
|
||||
@app.post("/emergency/close-all")
|
||||
def emergency_close_all(x_control_token: str | None = Header(default=None, alias="X-Control-Token")):
|
||||
_check_token(x_control_token)
|
||||
|
||||
Reference in New Issue
Block a user