中控条件单页:下发/取消/改出场,有仓客户端不可选。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-03 02:13:17 +08:00
parent fdb71d6896
commit 1546bc49f0
6 changed files with 616 additions and 37 deletions
+151 -1
View File
@@ -25,7 +25,14 @@ if str(ROOT) not in sys.path:
ENV_PATH = ROOT / ".env"
load_dotenv(ENV_PATH)
from lib.bridge_client_lib import post_close_full, pull_all_enabled
from lib.bridge_client_lib import (
dispatch_cond_order_to_ids,
fetch_cond_orders,
post_close_full,
post_cond_cancel,
post_cond_exit,
pull_all_enabled,
)
from lib.db_lib import init_db
from lib.env_lib import apply_env_updates
from lib.instances_lib import (
@@ -264,5 +271,148 @@ def api_instance_options_close(iid: int):
return jsonify(result), status
@app.route("/cond_orders")
@login_required
def cond_orders_page():
return render_template("cond_orders.html")
@app.route("/api/cond_orders/candidates")
@login_required
def api_cond_order_candidates():
"""可选客户端:在线且无持仓、未被条件单锁定."""
rows = pull_all_enabled()
out = []
for row in rows:
snap = row.get("snapshot") if isinstance(row.get("snapshot"), dict) else {}
brief = snap.get("positions_brief") or snap.get("positions") or []
has_pos = isinstance(brief, list) and len(brief) > 0
locked = bool(snap.get("cond_order_locked"))
selectable = bool(row.get("online")) and not has_pos and not locked
out.append(
{
"id": row.get("id"),
"name": row.get("name"),
"online": bool(row.get("online")),
"has_position": has_pos,
"cond_order_locked": locked,
"selectable": selectable,
"error": row.get("error") or "",
}
)
return jsonify({"ok": True, "instances": out})
@app.route("/api/cond_orders", methods=["GET"])
@login_required
def api_cond_orders_list():
instances = list_instances(enabled_only=True)
all_orders = []
for inst in instances:
data = fetch_cond_orders(inst)
for o in data.get("orders") or []:
if not isinstance(o, dict):
continue
item = dict(o)
item["instance_id"] = inst.get("id")
item["instance_name"] = inst.get("name")
all_orders.append(item)
return jsonify({"ok": True, "orders": all_orders})
@app.route("/api/cond_orders", methods=["POST"])
@login_required
def api_cond_orders_create():
import uuid
data = request.get_json(silent=True) or {}
ids_raw = data.get("instance_ids") or []
try:
instance_ids = [int(x) for x in ids_raw]
except (TypeError, ValueError):
return jsonify({"ok": False, "msg": "instance_ids 无效"})
if not instance_ids:
return jsonify({"ok": False, "msg": "请选择客户端"})
# 再次按快照过滤有仓/锁定
rows = {r.get("id"): r for r in pull_all_enabled()}
allowed = []
rejected = []
for iid in instance_ids:
row = rows.get(iid)
if not row or not row.get("online"):
rejected.append({"instance_id": iid, "msg": "离线或不可用"})
continue
snap = row.get("snapshot") if isinstance(row.get("snapshot"), dict) else {}
brief = snap.get("positions_brief") or []
if brief:
rejected.append({"instance_id": iid, "msg": "已有持仓不可选"})
continue
if snap.get("cond_order_locked"):
rejected.append({"instance_id": iid, "msg": "已有条件单占用"})
continue
allowed.append(iid)
if not allowed:
return jsonify({"ok": False, "msg": "没有可下发的客户端", "rejected": rejected})
hub_order_id = (data.get("hub_order_id") or "").strip() or str(uuid.uuid4())
payload = {
"hub_order_id": hub_order_id,
"underlying": (data.get("underlying") or "ETH").strip().upper(),
"opt_type": (data.get("opt_type") or "P").strip().upper(),
"trigger_side": (data.get("trigger_side") or "gte").strip().lower(),
"trigger_px": data.get("trigger_px"),
"deviation_pct": data.get("deviation_pct"),
"min_expiry_hours": data.get("min_expiry_hours"),
"exit": {
"target_index": data.get("target_index"),
"profit_exit_enabled": bool(data.get("profit_exit_enabled")),
"profit_exit_mult": data.get("profit_exit_mult"),
},
}
results = dispatch_cond_order_to_ids(allowed, payload)
ok_n = sum(1 for r in results if r.get("ok"))
return jsonify(
{
"ok": ok_n > 0,
"msg": f"成功 {ok_n}/{len(results)}",
"hub_order_id": hub_order_id,
"results": results,
"rejected": rejected,
}
)
@app.route("/api/cond_orders/<hub_order_id>/cancel", methods=["POST"])
@login_required
def api_cond_orders_cancel(hub_order_id: str):
data = request.get_json(silent=True) or {}
iid = data.get("instance_id")
if iid is None:
return jsonify({"ok": False, "msg": "缺少 instance_id"})
inst = get_instance(int(iid))
if not inst:
return jsonify({"ok": False, "msg": "实例不存在"}), 404
return jsonify(post_cond_cancel(inst, hub_order_id))
@app.route("/api/cond_orders/<hub_order_id>/exit", methods=["POST"])
@login_required
def api_cond_orders_exit(hub_order_id: str):
data = request.get_json(silent=True) or {}
iid = data.get("instance_id")
if iid is None:
return jsonify({"ok": False, "msg": "缺少 instance_id"})
inst = get_instance(int(iid))
if not inst:
return jsonify({"ok": False, "msg": "实例不存在"}), 404
payload = {
"target_index": data.get("target_index"),
"profit_exit_enabled": data.get("profit_exit_enabled"),
"profit_exit_mult": data.get("profit_exit_mult"),
}
return jsonify(post_cond_exit(inst, hub_order_id, payload))
if __name__ == "__main__":
app.run(host=APP_HOST, port=APP_PORT, debug=True, threaded=True)