修复前端
This commit is contained in:
@@ -6343,6 +6343,8 @@ def api_key_kline():
|
||||
@app.route("/add_key", methods=["POST"])
|
||||
@login_required
|
||||
def add_key():
|
||||
conn = None
|
||||
try:
|
||||
d = request.form
|
||||
symbol = normalize_symbol_input(d.get("symbol"))
|
||||
if not symbol:
|
||||
@@ -6366,13 +6368,16 @@ def add_key():
|
||||
flash("日成交量排名读取失败,请稍后重试")
|
||||
return redirect("/key_monitor")
|
||||
if rank > KEY_DAILY_VOLUME_RANK_MAX:
|
||||
flash(f"{symbol} 当前日成交量排名为 {rank}/{total},不在前{KEY_DAILY_VOLUME_RANK_MAX},已拒绝添加关键位")
|
||||
flash(
|
||||
f"{symbol} 当前日成交量排名为 {rank}/{total},不在前{KEY_DAILY_VOLUME_RANK_MAX},已拒绝添加关键位"
|
||||
)
|
||||
return redirect("/key_monitor")
|
||||
conn = get_db()
|
||||
if mt in KEY_MONITOR_AUTO_TYPES:
|
||||
occupied = get_active_position_count(conn)
|
||||
if occupied >= MAX_ACTIVE_POSITIONS:
|
||||
conn.close()
|
||||
conn = None
|
||||
flash(
|
||||
f"当前持仓已达上限({occupied}/{MAX_ACTIVE_POSITIONS}):无法添加「箱体突破 / 收敛突破」。"
|
||||
"请平仓后再试,或使用「关键阻力位/关键支撑位」(仅单次提醒)。"
|
||||
@@ -6383,8 +6388,16 @@ def add_key():
|
||||
ensure_markets_loaded()
|
||||
except Exception:
|
||||
pass
|
||||
upper_px = round_price_to_exchange(ex_sym_key, float(d["upper"]))
|
||||
lower_px = round_price_to_exchange(ex_sym_key, float(d["lower"]))
|
||||
try:
|
||||
upper_raw = float(d.get("upper") or 0)
|
||||
lower_raw = float(d.get("lower") or 0)
|
||||
except (TypeError, ValueError):
|
||||
conn.close()
|
||||
conn = None
|
||||
flash("上下沿须为有效数字")
|
||||
return redirect("/key_monitor")
|
||||
upper_px = round_price_to_exchange(ex_sym_key, upper_raw)
|
||||
lower_px = round_price_to_exchange(ex_sym_key, lower_raw)
|
||||
be_flag = parse_breakeven_enabled_form(d.get("breakeven_enabled"))
|
||||
if is_fib_key_monitor_type(mt):
|
||||
ok_fib, err_fib = _add_fib_key_monitor(
|
||||
@@ -6392,6 +6405,7 @@ def add_key():
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
conn = None
|
||||
if not ok_fib:
|
||||
flash(err_fib or "斐波监控添加失败")
|
||||
return redirect("/key_monitor")
|
||||
@@ -6411,14 +6425,17 @@ def add_key():
|
||||
manual_tp = 0
|
||||
if manual_tp <= 0:
|
||||
conn.close()
|
||||
conn = None
|
||||
flash("趋势单方案须填写有效止盈价")
|
||||
return redirect("/key_monitor")
|
||||
if direction_sel == "long" and manual_tp <= upper_px:
|
||||
conn.close()
|
||||
conn = None
|
||||
flash("做多趋势单:止盈价应高于上沿(阻力)")
|
||||
return redirect("/key_monitor")
|
||||
if direction_sel == "short" and manual_tp >= lower_px:
|
||||
conn.close()
|
||||
conn = None
|
||||
flash("做空趋势单:止盈价应低于下沿(支撑)")
|
||||
return redirect("/key_monitor")
|
||||
mtpx = round_price_to_exchange(ex_sym_key, manual_tp)
|
||||
@@ -6432,6 +6449,7 @@ def add_key():
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
conn = None
|
||||
ctr = False
|
||||
try:
|
||||
coin4h_status, _, _ = _status_by_ema55(symbol, "4h")
|
||||
@@ -6449,6 +6467,14 @@ def add_key():
|
||||
"⚠️ 4h EMA55 提示:当前与所选方向逆势;「箱体突破/收敛突破」在条件满足时仍会按计划自动市价开仓,请注意仓位。"
|
||||
)
|
||||
return redirect("/key_monitor")
|
||||
except Exception as e:
|
||||
if conn is not None:
|
||||
try:
|
||||
conn.close()
|
||||
except Exception:
|
||||
pass
|
||||
flash(f"添加关键位失败:{e}")
|
||||
return redirect("/key_monitor")
|
||||
|
||||
@app.route("/add_order", methods=["POST"])
|
||||
@login_required
|
||||
|
||||
+31
-3
@@ -38,27 +38,55 @@ def _row_to_dict(row):
|
||||
return dict(row) if row is not None else {}
|
||||
|
||||
|
||||
_FAIL_HINTS = (
|
||||
"失败",
|
||||
"错误",
|
||||
"拒绝",
|
||||
"无效",
|
||||
"缺少",
|
||||
"无法",
|
||||
"过期",
|
||||
"未达",
|
||||
"不能为空",
|
||||
"已有",
|
||||
"不允许",
|
||||
"异常",
|
||||
)
|
||||
|
||||
|
||||
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
|
||||
if hasattr(data, "items") and not isinstance(data, dict):
|
||||
data = {k: v for k, v in data.items()}
|
||||
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)]}
|
||||
try:
|
||||
msgs = [str(x) for x in get_flashed_messages()]
|
||||
except Exception as e:
|
||||
return {"ok": False, "messages": [f"读取提示信息失败: {e}"]}
|
||||
ok = True
|
||||
for m in msgs:
|
||||
if any(k in m for k in ("失败", "错误", "拒绝", "无效", "缺少", "无法", "过期")):
|
||||
if any(k in m for k in _FAIL_HINTS):
|
||||
ok = False
|
||||
break
|
||||
return {"ok": ok, "messages": msgs}
|
||||
|
||||
|
||||
def _hub_json(view_name: str, path: str, form=None):
|
||||
try:
|
||||
return jsonify(_invoke_view(view_name, path, form=form))
|
||||
except Exception as e:
|
||||
return jsonify({"ok": False, "messages": [str(e)]})
|
||||
|
||||
|
||||
def install_on_app(
|
||||
app,
|
||||
*,
|
||||
@@ -155,12 +183,12 @@ def register_hub_routes(app):
|
||||
@app.route("/api/hub/add_order", methods=["POST"])
|
||||
@_hub_auth_required
|
||||
def api_hub_add_order():
|
||||
return jsonify(_invoke_view("add_order", "/trade"))
|
||||
return _hub_json("add_order", "/add_order")
|
||||
|
||||
@app.route("/api/hub/add_key", methods=["POST"])
|
||||
@_hub_auth_required
|
||||
def api_hub_add_key():
|
||||
return jsonify(_invoke_view("add_key", "/key_monitor"))
|
||||
return _hub_json("add_key", "/add_key")
|
||||
|
||||
@app.route("/api/hub/trend/preview", methods=["POST"])
|
||||
@_hub_auth_required
|
||||
|
||||
@@ -182,6 +182,46 @@ async def _fetch_agent_status(client: httpx.AsyncClient, ex: dict) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def _exchange_brief(ex: dict | None) -> dict | None:
|
||||
if not ex:
|
||||
return None
|
||||
return {
|
||||
"id": ex.get("id"),
|
||||
"key": ex.get("key"),
|
||||
"name": ex.get("name"),
|
||||
}
|
||||
|
||||
|
||||
def _form_plain_dict(form) -> dict[str, str]:
|
||||
out: dict[str, str] = {}
|
||||
for k, v in form.multi_items() if hasattr(form, "multi_items") else form.items():
|
||||
if hasattr(v, "read"):
|
||||
continue
|
||||
out[str(k)] = "" if v is None else str(v)
|
||||
return out
|
||||
|
||||
|
||||
def _parse_http_json_body(r: httpx.Response) -> dict:
|
||||
text = (r.text or "").strip()
|
||||
if not text:
|
||||
return {"ok": False, "status": r.status_code, "text": "(empty body)"}
|
||||
try:
|
||||
data = r.json()
|
||||
if isinstance(data, dict):
|
||||
return data
|
||||
return {"ok": False, "status": r.status_code, "text": text[:500]}
|
||||
except Exception:
|
||||
snippet = text[:500]
|
||||
if snippet.lstrip().lower().startswith("<!") or "internal server error" in snippet.lower():
|
||||
return {
|
||||
"ok": False,
|
||||
"status": r.status_code,
|
||||
"messages": [f"实例返回 HTML 错误(HTTP {r.status_code}),请查看该 Flask 日志"],
|
||||
"text": snippet,
|
||||
}
|
||||
return {"ok": False, "status": r.status_code, "messages": [snippet], "text": snippet}
|
||||
|
||||
|
||||
async def _fetch_flask_json(
|
||||
client: httpx.AsyncClient, ex: dict, path: str, method: str = "GET", data=None
|
||||
) -> dict | None:
|
||||
@@ -194,8 +234,11 @@ async def _fetch_flask_json(
|
||||
else:
|
||||
r = await client.post(f"{base}{path}", headers=_hub_headers(), data=data, timeout=120.0)
|
||||
if r.status_code >= 400:
|
||||
return {"ok": False, "status": r.status_code, "text": (r.text or "")[:500]}
|
||||
return r.json()
|
||||
parsed = _parse_http_json_body(r)
|
||||
parsed.setdefault("ok", False)
|
||||
parsed.setdefault("status", r.status_code)
|
||||
return parsed
|
||||
return _parse_http_json_body(r)
|
||||
except Exception as e:
|
||||
return {"ok": False, "error": str(e)}
|
||||
|
||||
@@ -308,10 +351,18 @@ async def api_trade_order(exchange_id: str, request: Request):
|
||||
ex = _find_exchange(exchange_id)
|
||||
if not ex or not ex.get("enabled"):
|
||||
raise HTTPException(status_code=404, detail="账户未启用")
|
||||
form = await request.form()
|
||||
try:
|
||||
form = _form_plain_dict(await request.form())
|
||||
async with httpx.AsyncClient() as client:
|
||||
result = await _fetch_flask_json(client, ex, "/api/hub/add_order", "POST", dict(form))
|
||||
return {"exchange": ex, "result": result}
|
||||
result = await _fetch_flask_json(client, ex, "/api/hub/add_order", "POST", form)
|
||||
return {"exchange": _exchange_brief(ex), "result": result}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
return JSONResponse(
|
||||
{"exchange": _exchange_brief(ex), "result": {"ok": False, "messages": [str(e)]}},
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
|
||||
@app.post("/api/trade/key/{exchange_id}")
|
||||
@@ -321,10 +372,18 @@ async def api_trade_key(exchange_id: str, request: Request):
|
||||
raise HTTPException(status_code=404, detail="账户未启用")
|
||||
if "key" not in (ex.get("capabilities") or []):
|
||||
raise HTTPException(status_code=400, detail="该账户不支持关键位")
|
||||
form = await request.form()
|
||||
try:
|
||||
form = _form_plain_dict(await request.form())
|
||||
async with httpx.AsyncClient() as client:
|
||||
result = await _fetch_flask_json(client, ex, "/api/hub/add_key", "POST", dict(form))
|
||||
return {"exchange": ex, "result": result}
|
||||
result = await _fetch_flask_json(client, ex, "/api/hub/add_key", "POST", form)
|
||||
return {"exchange": _exchange_brief(ex), "result": result}
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
return JSONResponse(
|
||||
{"exchange": _exchange_brief(ex), "result": {"ok": False, "messages": [str(e)]}},
|
||||
status_code=200,
|
||||
)
|
||||
|
||||
|
||||
@app.post("/api/trade/trend/preview/{exchange_id}")
|
||||
|
||||
@@ -294,15 +294,34 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function parseJsonResponse(r) {
|
||||
const text = await r.text();
|
||||
if (!text) return {};
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (e) {
|
||||
const snippet = text.slice(0, 200);
|
||||
throw new Error(
|
||||
`HTTP ${r.status} 响应不是 JSON:${snippet}${text.length > 200 ? "…" : ""}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async function submitForm(path, formEl) {
|
||||
const id = document.getElementById("trade-account").value;
|
||||
const fd = new FormData(formEl);
|
||||
try {
|
||||
const r = await fetch(path + encodeURIComponent(id), { method: "POST", body: fd });
|
||||
const j = await r.json();
|
||||
const j = await parseJsonResponse(r);
|
||||
const res = j.result || {};
|
||||
const msgs = (res.messages || []).join("\n") || JSON.stringify(res, null, 2);
|
||||
showToast(msgs, !res.ok);
|
||||
const msgs =
|
||||
(res.messages || []).join("\n") ||
|
||||
res.error ||
|
||||
res.msg ||
|
||||
res.text ||
|
||||
JSON.stringify(res, null, 2);
|
||||
const failed = res.ok === false || r.status >= 400 || !!res.error || !!res.text;
|
||||
showToast(msgs || (failed ? "操作失败" : "已提交"), failed);
|
||||
if (res.ok && res.preview) {
|
||||
showTrendPreview(res);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user