中控增加下单,关键位,系统设置
This commit is contained in:
+240
@@ -0,0 +1,240 @@
|
||||
"""
|
||||
各 crypto_monitor_* 注册 /api/hub/* JSON 接口,供 manual_trading_hub 调用。
|
||||
实例末尾:app.config["HUB_CTX"] = {...}; register_hub_routes(app)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
from functools import wraps
|
||||
|
||||
from flask import current_app, get_flashed_messages, jsonify, request, session
|
||||
|
||||
from hub_auth import request_allowed
|
||||
|
||||
|
||||
def _hub_auth_required(f):
|
||||
@wraps(f)
|
||||
def wrapped(*args, **kwargs):
|
||||
from flask import current_app as cap
|
||||
|
||||
auth_disabled = bool(cap.config.get("HUB_AUTH_DISABLED"))
|
||||
if not request_allowed(bool(session.get("logged_in")), auth_disabled):
|
||||
return jsonify({"ok": False, "msg": "未授权(登录或 HUB_BRIDGE_TOKEN)"}), 401
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
|
||||
def _ctx():
|
||||
return current_app.config.get("HUB_CTX") or {}
|
||||
|
||||
|
||||
def _row_to_dict(row):
|
||||
fn = _ctx().get("row_to_dict")
|
||||
if fn and row is not None:
|
||||
return fn(row)
|
||||
return dict(row) if row is not None else {}
|
||||
|
||||
|
||||
def _invoke_view(view_name: str, path: str, form=None) -> dict:
|
||||
views = _ctx().get("views") or {}
|
||||
view = views.get(view_name)
|
||||
if not view:
|
||||
return {"ok": False, "messages": [f"未配置视图 {view_name}"]}
|
||||
data = form if form is not None else request.form
|
||||
with current_app.test_request_context(path, method="POST", data=data):
|
||||
session["logged_in"] = True
|
||||
try:
|
||||
view()
|
||||
except Exception as e:
|
||||
return {"ok": False, "messages": [str(e)]}
|
||||
msgs = [str(x) for x in get_flashed_messages()]
|
||||
ok = True
|
||||
for m in msgs:
|
||||
if any(k in m for k in ("失败", "错误", "拒绝", "无效", "缺少", "无法", "过期")):
|
||||
ok = False
|
||||
break
|
||||
return {"ok": ok, "messages": msgs}
|
||||
|
||||
|
||||
def install_on_app(
|
||||
app,
|
||||
*,
|
||||
exchange: str,
|
||||
capabilities: list,
|
||||
has_trend: bool,
|
||||
get_db,
|
||||
row_to_dict,
|
||||
meta_fn,
|
||||
views: dict,
|
||||
):
|
||||
app.config["HUB_CTX"] = {
|
||||
"exchange": exchange,
|
||||
"capabilities": list(capabilities),
|
||||
"has_trend": bool(has_trend),
|
||||
"get_db": get_db,
|
||||
"row_to_dict": row_to_dict,
|
||||
"meta_fn": meta_fn,
|
||||
"views": views,
|
||||
}
|
||||
register_hub_routes(app)
|
||||
|
||||
|
||||
def register_hub_routes(app):
|
||||
auth_disabled = False
|
||||
try:
|
||||
import os
|
||||
|
||||
auth_disabled = os.getenv("APP_AUTH_DISABLED", "false").lower() in (
|
||||
"1",
|
||||
"true",
|
||||
"yes",
|
||||
"on",
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
app.config.setdefault("HUB_AUTH_DISABLED", auth_disabled)
|
||||
|
||||
@app.route("/api/hub/ping")
|
||||
@_hub_auth_required
|
||||
def api_hub_ping():
|
||||
c = _ctx()
|
||||
return jsonify(
|
||||
{
|
||||
"ok": True,
|
||||
"exchange": c.get("exchange"),
|
||||
"capabilities": c.get("capabilities") or [],
|
||||
}
|
||||
)
|
||||
|
||||
@app.route("/api/hub/meta")
|
||||
@_hub_auth_required
|
||||
def api_hub_meta():
|
||||
c = _ctx()
|
||||
meta_fn = c.get("meta_fn")
|
||||
meta = meta_fn() if callable(meta_fn) else {}
|
||||
return jsonify({"ok": True, "meta": meta})
|
||||
|
||||
@app.route("/api/hub/monitor")
|
||||
@_hub_auth_required
|
||||
def api_hub_monitor():
|
||||
c = _ctx()
|
||||
get_db = c.get("get_db")
|
||||
if not get_db:
|
||||
return jsonify({"ok": False, "msg": "HUB_CTX 缺少 get_db"}), 500
|
||||
conn = get_db()
|
||||
keys = []
|
||||
for row in conn.execute("SELECT * FROM key_monitors ORDER BY id DESC").fetchall():
|
||||
keys.append(_row_to_dict(row))
|
||||
orders = []
|
||||
for row in conn.execute(
|
||||
"SELECT * FROM order_monitors WHERE status='active' ORDER BY id DESC"
|
||||
).fetchall():
|
||||
orders.append(_row_to_dict(row))
|
||||
trends = []
|
||||
if c.get("has_trend"):
|
||||
for row in conn.execute(
|
||||
"SELECT * FROM trend_pullback_plans WHERE status='active' ORDER BY id DESC"
|
||||
).fetchall():
|
||||
trends.append(_row_to_dict(row))
|
||||
conn.close()
|
||||
enrich = c.get("enrich_monitor")
|
||||
if callable(enrich):
|
||||
try:
|
||||
payload = enrich(keys=keys, orders=orders, trends=trends)
|
||||
if isinstance(payload, dict):
|
||||
return jsonify({"ok": True, **payload})
|
||||
except Exception as e:
|
||||
return jsonify({"ok": False, "msg": str(e)}), 500
|
||||
return jsonify(
|
||||
{"ok": True, "keys": keys, "orders": orders, "trends": trends, "key_prices": []}
|
||||
)
|
||||
|
||||
@app.route("/api/hub/add_order", methods=["POST"])
|
||||
@_hub_auth_required
|
||||
def api_hub_add_order():
|
||||
return jsonify(_invoke_view("add_order", "/trade"))
|
||||
|
||||
@app.route("/api/hub/add_key", methods=["POST"])
|
||||
@_hub_auth_required
|
||||
def api_hub_add_key():
|
||||
return jsonify(_invoke_view("add_key", "/key_monitor"))
|
||||
|
||||
@app.route("/api/hub/trend/preview", methods=["POST"])
|
||||
@_hub_auth_required
|
||||
def api_hub_trend_preview():
|
||||
if not _ctx().get("has_trend"):
|
||||
return jsonify({"ok": False, "msg": "该实例无趋势回调"}), 400
|
||||
data = _invoke_view("preview_trend_pullback", "/trade")
|
||||
pid = _latest_preview_id()
|
||||
preview = _fetch_preview(pid) if pid else None
|
||||
return jsonify(
|
||||
{
|
||||
"ok": bool(data.get("ok")),
|
||||
"messages": data.get("messages") or [],
|
||||
"preview_id": pid,
|
||||
"preview": preview,
|
||||
}
|
||||
)
|
||||
|
||||
@app.route("/api/hub/trend/execute", methods=["POST"])
|
||||
@_hub_auth_required
|
||||
def api_hub_trend_execute():
|
||||
if not _ctx().get("has_trend"):
|
||||
return jsonify({"ok": False, "msg": "该实例无趋势回调"}), 400
|
||||
pid = (request.form.get("preview_id") or "").strip()
|
||||
if not pid:
|
||||
body = request.get_json(silent=True) or {}
|
||||
pid = str(body.get("preview_id") or "").strip()
|
||||
form = {"preview_id": pid} if pid else {}
|
||||
return jsonify(_invoke_view("execute_trend_pullback", "/trade", form=form))
|
||||
|
||||
@app.route("/api/hub/trend/preview/<pid>")
|
||||
@_hub_auth_required
|
||||
def api_hub_trend_preview_get(pid):
|
||||
if not _ctx().get("has_trend"):
|
||||
return jsonify({"ok": False, "msg": "该实例无趋势回调"}), 400
|
||||
preview = _fetch_preview(pid)
|
||||
if not preview:
|
||||
return jsonify({"ok": False, "msg": "预览不存在或已过期"}), 404
|
||||
return jsonify({"ok": True, "preview": preview})
|
||||
|
||||
|
||||
def _latest_preview_id():
|
||||
get_db = _ctx().get("get_db")
|
||||
if not get_db:
|
||||
return None
|
||||
conn = get_db()
|
||||
row = conn.execute(
|
||||
"SELECT id FROM trend_pullback_previews ORDER BY created_at DESC LIMIT 1"
|
||||
).fetchone()
|
||||
conn.close()
|
||||
return row["id"] if row else None
|
||||
|
||||
|
||||
def _fetch_preview(pid):
|
||||
get_db = _ctx().get("get_db")
|
||||
if not get_db or not pid:
|
||||
return None
|
||||
conn = get_db()
|
||||
row = conn.execute(
|
||||
"SELECT * FROM trend_pullback_previews WHERE id=?", (pid,)
|
||||
).fetchone()
|
||||
conn.close()
|
||||
if not row:
|
||||
return None
|
||||
d = _row_to_dict(row)
|
||||
now_ms = int(time.time() * 1000)
|
||||
d["expires_in_sec"] = max(0, int((int(d.get("expires_at_ms") or 0) - now_ms) / 1000))
|
||||
try:
|
||||
grid = json.loads(d.get("grid_prices_json") or "[]")
|
||||
legs = json.loads(d.get("leg_amounts_json") or "[]")
|
||||
d["grid_levels"] = [
|
||||
{"i": i + 1, "price": grid[i], "contracts": legs[i] if i < len(legs) else None}
|
||||
for i in range(len(grid))
|
||||
]
|
||||
except Exception:
|
||||
d["grid_levels"] = []
|
||||
return d
|
||||
Reference in New Issue
Block a user