1
This commit is contained in:
parent
17a5828266
commit
829905842a
@ -68,7 +68,7 @@ X-Webhook-Token: your-shared-secret
|
||||
|
||||
标题和正文模板支持 `{{field}}` 占位符,字段来自 TradingView alert JSON。嵌套字段可以写成 `{{order.id}}`。
|
||||
|
||||
每条路由规则通过「发送到」下拉框选择一个飞书 Webhook。`timeframe`、`symbol`、`strategy` 至少填写一个,空字段表示不限。例如只填 `symbol=BTCUSDT` 会匹配所有 BTCUSDT 信号。需要同一个信号发到多个群时,可以建多条匹配条件相同、目标不同的规则,并用优先级控制命中顺序;当前默认路由逻辑只发送最高优先级命中的规则。
|
||||
每条路由规则通过「发送到」多选框选择一个或多个飞书 Webhook。`timeframe`、`symbol`、`strategy` 至少填写一个,空字段表示不限。例如只填 `symbol=BTCUSDT` 会匹配所有 BTCUSDT 信号。当前默认路由逻辑只匹配最高优先级的规则,但这条规则可以同时分发到多个飞书目标。
|
||||
|
||||
示例正文模板:
|
||||
|
||||
|
||||
@ -400,7 +400,9 @@ class Handler(BaseHTTPRequestHandler):
|
||||
f"品种={html.escape(rule['symbol'])}" if rule["symbol"] else "",
|
||||
f"策略={html.escape(rule['strategy'])}" if rule["strategy"] else "",
|
||||
]
|
||||
target_name = target_names.get(rule["target_ids"][0], "-") if rule["target_ids"] else "-"
|
||||
target_name = "<br>".join(
|
||||
html.escape(target_names.get(target_id, f"#{target_id}")) for target_id in rule["target_ids"]
|
||||
) or "-"
|
||||
rows += f"""<tr>
|
||||
<td>{rule['id']}</td>
|
||||
<td>{html.escape(rule['name'])}</td>
|
||||
@ -434,7 +436,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
"target_ids": [],
|
||||
"enabled": 1,
|
||||
}
|
||||
selected_targets = target_select_options(targets, rule.get("target_ids", []), placeholder=True)
|
||||
selected_targets = target_select_options(targets, rule.get("target_ids", []))
|
||||
hidden_id = f'<input type="hidden" name="id" value="{rule["id"]}">' if rule.get("id") else ""
|
||||
button_text = "保存修改" if rule.get("id") else "创建规则"
|
||||
sample_payload = sample_payload or json.dumps(
|
||||
@ -454,7 +456,7 @@ class Handler(BaseHTTPRequestHandler):
|
||||
</div>
|
||||
<label>卡片标题模板<input name="card_title_template" value="{html.escape(str(rule['card_title_template']))}" required></label>
|
||||
<label>卡片正文模板<textarea name="card_body_template" rows="6">{html.escape(str(rule['card_body_template']))}</textarea></label>
|
||||
<label class="field-target">发送到<select class="select-target" name="target_ids" required>{selected_targets}</select></label>
|
||||
<label class="field-target">发送到<select class="select-target multi" name="target_ids" multiple required size="5">{selected_targets}</select></label>
|
||||
<label class="check"><input name="enabled" type="checkbox" {'checked' if rule.get('enabled') else ''}> 启用</label>
|
||||
<h2>规则命中与卡片预览</h2>
|
||||
<input type="hidden" name="source_action" value="{html.escape(action)}">
|
||||
|
||||
@ -197,6 +197,10 @@ td textarea {
|
||||
width: clamp(220px, 34vw, 360px);
|
||||
}
|
||||
|
||||
.select-target.multi {
|
||||
min-height: 138px;
|
||||
}
|
||||
|
||||
.check {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
@ -42,6 +42,17 @@ class DispatcherTest(unittest.TestCase):
|
||||
timeframe: str = "5m",
|
||||
symbol: str = "BTCUSDT",
|
||||
strategy: str = "breakout",
|
||||
) -> int:
|
||||
return self.add_rule_with_targets([target_id], priority, name, timeframe, symbol, strategy)
|
||||
|
||||
def add_rule_with_targets(
|
||||
self,
|
||||
target_ids: list[int],
|
||||
priority: int = 100,
|
||||
name: str = "rule",
|
||||
timeframe: str = "5m",
|
||||
symbol: str = "BTCUSDT",
|
||||
strategy: str = "breakout",
|
||||
) -> int:
|
||||
now = now_iso()
|
||||
with self.db.connect() as conn:
|
||||
@ -55,7 +66,7 @@ class DispatcherTest(unittest.TestCase):
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, 'card', 'Signal {{symbol}}', 'Price {{price}}', 1, ?, ?, ?)
|
||||
""",
|
||||
(name, timeframe, symbol, strategy, priority, to_json([target_id]), now, now),
|
||||
(name, timeframe, symbol, strategy, priority, to_json(target_ids), now, now),
|
||||
)
|
||||
return int(cur.lastrowid)
|
||||
|
||||
@ -125,6 +136,20 @@ class DispatcherTest(unittest.TestCase):
|
||||
self.assertEqual(delivery["attempts"], 1)
|
||||
self.assertIsNotNone(delivery["error"])
|
||||
|
||||
def test_rule_can_dispatch_to_multiple_targets(self) -> None:
|
||||
target_a = self.add_target("ops-a")
|
||||
target_b = self.add_target("ops-b")
|
||||
self.add_rule_with_targets([target_a, target_b])
|
||||
|
||||
result = self.dispatcher.receive_alert(
|
||||
{"timeframe": "5m", "symbol": "BTCUSDT", "strategy": "breakout", "action": "buy"}
|
||||
)
|
||||
|
||||
self.assertEqual(len(result["delivery_ids"]), 2)
|
||||
with self.db.connect() as conn:
|
||||
count = conn.execute("SELECT COUNT(*) AS c FROM deliveries WHERE alert_id = ?", (result["alert_id"],)).fetchone()["c"]
|
||||
self.assertEqual(count, 2)
|
||||
|
||||
def test_card_template_uses_alert_fields(self) -> None:
|
||||
message = build_feishu_message(
|
||||
{"timeframe": "5m", "symbol": "BTCUSDT", "strategy": "breakout", "action": "buy", "price": 68000},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user