Add WeCom markdown alerts and keep target exits while strategy is paused.

Notify open/close/start/pause/fault with SIM vs LIVE venue labels; configure webhook in Settings.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-26 23:19:39 +08:00
parent 4d331da962
commit 56a4007130
12 changed files with 602 additions and 16 deletions
+51
View File
@@ -329,3 +329,54 @@ async def put_runtime_settings(
pass
return _runtime_payload()
class NotifySettingsBody(BaseModel):
enabled: bool | None = None
webhook_url: str | None = None
def _notify_payload() -> dict:
from ..notify import wecom
from ..env_store import mask_secret
s = get_settings()
url = wecom.wecom_webhook_url()
return {
"enabled": wecom.wecom_enabled(),
"webhook_configured": bool(url),
"webhook_url_masked": mask_secret(url) if url else None,
"venue_label": wecom.venue_label(),
}
@router.get("/notify")
async def get_notify_settings(_user: Annotated[str, Depends(require_user)]) -> dict:
return _notify_payload()
@router.put("/notify")
async def put_notify_settings(
body: NotifySettingsBody,
_user: Annotated[str, Depends(require_user)],
) -> dict:
updates: dict[str, str] = {}
if body.enabled is not None:
updates["WECOM_ENABLED"] = "1" if body.enabled else "0"
get_db().set_setting("wecom_enabled", "1" if body.enabled else "0")
if body.webhook_url is not None and body.webhook_url.strip():
updates["WECOM_WEBHOOK_URL"] = body.webhook_url.strip()
get_db().set_setting("wecom_webhook_url", body.webhook_url.strip())
if updates:
upsert_env_keys(updates)
return _notify_payload()
@router.post("/notify/test")
async def test_notify(_user: Annotated[str, Depends(require_user)]) -> dict:
from ..notify import wecom
ok, msg = wecom.notify_test()
if not ok:
raise HTTPException(status_code=400, detail=f"推送失败: {msg}")
return {"ok": True, "detail": "测试消息已发送", **_notify_payload()}
+26
View File
@@ -115,6 +115,22 @@ async def sim_open_group(
)
if not r.ok:
raise HTTPException(status_code=400, detail=r.detail)
try:
from ..notify import wecom
wecom.notify_open(
group_id=gid,
detail=r.detail,
extra={
"bias": bias,
"option_side": option_side,
"option_inst_id": option_inst,
"strike": pick.pair.strike,
"expiry_ymd": pick.pair.expiry_ymd,
},
)
except Exception:
pass
return {
"ok": True,
**(r.data or {}),
@@ -141,6 +157,16 @@ async def sim_close_group(_user: Annotated[str, Depends(require_user)]) -> dict:
if r.ok:
# 与自动/紧急全平一致:成功全平后进入组间休息
get_engine().enter_rest_after_close()
try:
from ..notify import wecom
wecom.notify_close(
reason="manual",
detail=r.detail,
data=r.data or {},
)
except Exception:
pass
return {
"ok": r.ok,
"liquidity_wait": r.liquidity_wait,