Hide SIM label in WeCom; count completed rounds from closed groups.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -55,11 +55,11 @@ def wecom_webhook_url() -> str:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def venue_label() -> str:
|
def venue_label() -> str | None:
|
||||||
"""模拟盘 / 实盘·OKX / 实盘·BINANCE。"""
|
"""实盘时返回「实盘·交易所」;SIM 不展示盘口标签。"""
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
if s.is_sim:
|
if s.is_sim:
|
||||||
return "模拟盘"
|
return None
|
||||||
try:
|
try:
|
||||||
ex = load_runtime_settings().exchange
|
ex = load_runtime_settings().exchange
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -67,13 +67,17 @@ def venue_label() -> str:
|
|||||||
ex_u = str(ex).strip().lower()
|
ex_u = str(ex).strip().lower()
|
||||||
if ex_u in ("binance", "bn"):
|
if ex_u in ("binance", "bn"):
|
||||||
return "实盘·币安"
|
return "实盘·币安"
|
||||||
|
if ex_u in ("gate", "gateio"):
|
||||||
|
return "实盘·Gate"
|
||||||
return "实盘·OKX"
|
return "实盘·OKX"
|
||||||
|
|
||||||
|
|
||||||
def build_markdown(*, tag: str, title: str, lines: list[str] | None = None) -> str:
|
def build_markdown(*, tag: str, title: str, lines: list[str] | None = None) -> str:
|
||||||
body = "\n".join(f"> {ln}" if not ln.startswith(">") else ln for ln in (lines or []))
|
body = "\n".join(f"> {ln}" if not ln.startswith(">") else ln for ln in (lines or []))
|
||||||
|
venue = venue_label()
|
||||||
|
head = f"## 【{venue}】{title}" if venue else f"## {title}"
|
||||||
parts = [
|
parts = [
|
||||||
f"## 【{venue_label()}】{title}",
|
head,
|
||||||
f"> **标识**: `{tag}`",
|
f"> **标识**: `{tag}`",
|
||||||
f"> **时间**: {time.strftime('%Y-%m-%d %H:%M:%S')}",
|
f"> **时间**: {time.strftime('%Y-%m-%d %H:%M:%S')}",
|
||||||
]
|
]
|
||||||
@@ -211,9 +215,13 @@ def notify_fault(*, title: str, detail: str, dedupe_key: str | None = None) -> N
|
|||||||
|
|
||||||
|
|
||||||
def notify_test() -> tuple[bool, str]:
|
def notify_test() -> tuple[bool, str]:
|
||||||
|
venue = venue_label()
|
||||||
|
lines = ["企业微信通知已连通。"]
|
||||||
|
if venue:
|
||||||
|
lines.append(f"当前盘口标签: **{venue}**")
|
||||||
content = build_markdown(
|
content = build_markdown(
|
||||||
tag=TAG_TEST,
|
tag=TAG_TEST,
|
||||||
title="测试推送",
|
title="测试推送",
|
||||||
lines=["企业微信通知已连通。", f"当前盘口标签: **{venue_label()}**"],
|
lines=lines,
|
||||||
)
|
)
|
||||||
return _post_markdown_sync(content)
|
return _post_markdown_sync(content)
|
||||||
|
|||||||
@@ -119,7 +119,7 @@ class StrategyEngine:
|
|||||||
return {
|
return {
|
||||||
"running": bool(row["running"]),
|
"running": bool(row["running"]),
|
||||||
"phase": row["phase"],
|
"phase": row["phase"],
|
||||||
"rounds_done": int(row["rounds_done"] or 0),
|
"rounds_done": self._closed_rounds(),
|
||||||
"window_key": row["window_key"],
|
"window_key": row["window_key"],
|
||||||
"rest_until_ms": rest_until,
|
"rest_until_ms": rest_until,
|
||||||
"rest_left_sec": rest_left,
|
"rest_left_sec": rest_left,
|
||||||
@@ -266,11 +266,16 @@ class StrategyEngine:
|
|||||||
"""全平成功后进入组间休息(自动 / 手动 / 紧急共用)。"""
|
"""全平成功后进入组间休息(自动 / 手动 / 紧急共用)。"""
|
||||||
self._after_close()
|
self._after_close()
|
||||||
|
|
||||||
|
def _closed_rounds(self) -> int:
|
||||||
|
"""已完成组数 = 已平仓组数量(与顶栏总交易口径一致,避免计数漂移)。"""
|
||||||
|
row = self.db.fetchone(
|
||||||
|
"SELECT COUNT(*) AS n FROM groups WHERE status='closed'"
|
||||||
|
)
|
||||||
|
return int(row["n"] or 0) if row else 0
|
||||||
|
|
||||||
def _after_close(self) -> None:
|
def _after_close(self) -> None:
|
||||||
s = get_settings()
|
s = get_settings()
|
||||||
row = self.db.fetchone("SELECT * FROM strategy_state WHERE id=1")
|
rounds = self._closed_rounds()
|
||||||
assert row is not None
|
|
||||||
rounds = int(row["rounds_done"] or 0) + 1
|
|
||||||
rest_sec = self.ledger.get_setting_int("rest_seconds", s.rest_seconds)
|
rest_sec = self.ledger.get_setting_int("rest_seconds", s.rest_seconds)
|
||||||
rest_until = int(time.time() * 1000) + rest_sec * 1000
|
rest_until = int(time.time() * 1000) + rest_sec * 1000
|
||||||
self._set_state(
|
self._set_state(
|
||||||
|
|||||||
@@ -10,13 +10,20 @@ def test_enter_rest_after_close_sets_resting(tmp_path) -> None:
|
|||||||
db = Database(tmp_path / "rest.db")
|
db = Database(tmp_path / "rest.db")
|
||||||
set_db(db)
|
set_db(db)
|
||||||
db.set_setting("rest_seconds", "120")
|
db.set_setting("rest_seconds", "120")
|
||||||
|
now = int(time.time() * 1000)
|
||||||
|
db.execute(
|
||||||
|
"""INSERT INTO groups(
|
||||||
|
group_id, status, open_at_ms, close_at_ms, realized_pnl
|
||||||
|
) VALUES (?,?,?,?,?)""",
|
||||||
|
("G-20260729-01", "closed", now - 1000, now, 1.0),
|
||||||
|
)
|
||||||
eng = StrategyEngine()
|
eng = StrategyEngine()
|
||||||
before = int(time.time() * 1000)
|
before = int(time.time() * 1000)
|
||||||
eng.enter_rest_after_close()
|
eng.enter_rest_after_close()
|
||||||
row = db.fetchone("SELECT * FROM strategy_state WHERE id=1")
|
row = db.fetchone("SELECT * FROM strategy_state WHERE id=1")
|
||||||
assert row is not None
|
assert row is not None
|
||||||
assert row["phase"] == "resting"
|
assert row["phase"] == "resting"
|
||||||
assert int(row["rounds_done"] or 0) >= 1
|
assert int(row["rounds_done"] or 0) == 1
|
||||||
until = int(row["rest_until_ms"] or 0)
|
until = int(row["rest_until_ms"] or 0)
|
||||||
assert until >= before + 100_000
|
assert until >= before + 100_000
|
||||||
db.close()
|
db.close()
|
||||||
|
|||||||
@@ -5,6 +5,15 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 2026-07-29 — 企微不标模拟盘;已完成组数按已平仓统计
|
||||||
|
|
||||||
|
### 变更
|
||||||
|
|
||||||
|
1. 企业微信:SIM 不再显示「模拟盘」;LIVE 显示「实盘·OKX/币安」。
|
||||||
|
2. 「已完成 N 组」改为按 `groups.status=closed` 计数,与实际平仓组一致(修正漂移显示 6 实为 5)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 2026-07-29 — 规则说明展示策略数值;导航加大;不可开才通知
|
## 2026-07-29 — 规则说明展示策略数值;导航加大;不可开才通知
|
||||||
|
|
||||||
### 变更
|
### 变更
|
||||||
|
|||||||
Reference in New Issue
Block a user