Add auto break-even toggle and WeCom hub notifications.

Desktop can auto-pull stop to open+/-1 after a 3$ favorable move; hub pushes open/close events and a 23:00 BJT daily summary via webhook.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-10-27 11:25:44 +08:00
parent 828038a5ae
commit 75b9bffe96
13 changed files with 582 additions and 5 deletions
+103 -1
View File
@@ -157,7 +157,52 @@ def register_routes(app: Flask) -> None:
@app.get("/settings")
@login_required
def settings_page():
return render_template("settings.html")
from app import wecom as wecom_mod
wecom_cfg = {"enabled": False, "webhook": "", "daily_hour": Config.WECOM_DAILY_HOUR}
if session.get("role") == "admin":
with database.get_db() as conn:
wecom_cfg = {
"enabled": wecom_mod.is_enabled(conn),
"webhook": wecom_mod.webhook_url(conn),
"daily_hour": Config.WECOM_DAILY_HOUR,
"daily_minute": Config.WECOM_DAILY_MINUTE,
"daily_last": database.get_setting(conn, wecom_mod.SETTING_DAILY_LAST, ""),
}
return render_template("settings.html", wecom=wecom_cfg)
@app.post("/settings/wecom")
@admin_required
def settings_wecom():
from app import wecom as wecom_mod
enabled = "1" if request.form.get("enabled") == "1" else "0"
webhook = (request.form.get("webhook") or "").strip()
with database.get_db() as conn:
database.set_setting(conn, wecom_mod.SETTING_ENABLED, enabled)
database.set_setting(conn, wecom_mod.SETTING_WEBHOOK, webhook)
flash("企业微信推送设置已保存", "ok")
return redirect(url_for("settings_page"))
@app.post("/settings/wecom/test")
@admin_required
def settings_wecom_test():
from app import wecom as wecom_mod
ok, detail = wecom_mod.send_text(
"【测试】Gold Monitor 企业微信推送正常。\n开仓/平仓即时通知 · 每日 23:00 日报"
)
flash("测试发送成功" if ok else f"测试失败:{detail}", "ok" if ok else "error")
return redirect(url_for("settings_page"))
@app.post("/settings/wecom/daily")
@admin_required
def settings_wecom_daily():
from app import wecom as wecom_mod
ok, detail = wecom_mod.send_daily_reports()
flash("日报已推送" if ok else f"日报推送失败:{detail}", "ok" if ok else "error")
return redirect(url_for("settings_page"))
@app.post("/settings/profile")
@login_required
@@ -654,6 +699,7 @@ def register_routes(app: Flask) -> None:
def api_trades_upsert():
token = request.headers.get("X-Device-Token") or ""
data = request.get_json(silent=True) or {}
wecom_job = None # ("open"|"close", kwargs) 提交后再推,避免嵌套事务
with database.get_db() as conn:
act = database.auth_token(conn, token)
if not act:
@@ -712,11 +758,24 @@ def register_routes(app: Flask) -> None:
(act["account_id"], ticket),
).fetchone()
was_open = bool(
existing and existing["close_time"] in (None, "")
)
is_new = existing is None
event_name = str((snapshot or {}).get("event") or "").strip().lower()
now_closed = close_time not in (None, "")
notify_close_price = close_price
acc_row = conn.execute(
"SELECT name FROM accounts WHERE id=?", (act["account_id"],)
).fetchone()
account_name = (acc_row["name"] if acc_row else "") or f"账户#{act['account_id']}"
if existing:
# 浮盈更新不覆盖已有平仓;空备注不覆盖已标注
keep_close = existing["close_time"] not in (None, "") and close_time in (None, "")
new_close_time = existing["close_time"] if keep_close else close_time
new_close_price = existing["close_price"] if keep_close else close_price
notify_close_price = new_close_price
new_entry = entry_style or (existing["entry_style"] or "")
new_note = review_note or (existing["review_note"] or "")
new_open_time = open_time if open_time is not None else existing["open_time"]
@@ -809,6 +868,49 @@ def register_routes(app: Flask) -> None:
""",
(dup_snap, act["account_id"], ticket, tid),
)
# 企业微信:仅真实开仓/平仓推送(浮盈、改 SL/TP、对账假平仓不发)
if event_name == "open" and is_new:
wecom_job = (
"open",
{
"account_name": account_name,
"ticket": ticket,
"symbol": symbol,
"side": side,
"volume": volume,
"open_price": open_price,
"sl": sl,
"tp": tp,
},
)
elif event_name in ("close", "history_close") and now_closed and (
is_new or was_open
):
wecom_job = (
"close",
{
"account_name": account_name,
"ticket": ticket,
"symbol": symbol,
"side": side,
"volume": volume,
"open_price": open_price,
"close_price": notify_close_price,
"profit": profit,
},
)
if wecom_job:
try:
from app import wecom as wecom_mod
kind, kwargs = wecom_job
if kind == "open":
wecom_mod.notify_trade_open(**kwargs)
else:
wecom_mod.notify_trade_close(**kwargs)
except Exception:
pass
return jsonify({"ok": True, "id": tid})
@app.post("/api/v1/trades/reconcile")