中控条件单页:下发/取消/改出场,有仓客户端不可选。
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user