--- name: parse-signal-trader-and-price-pitfall description: "trader 解析 fallback + 价格格式化 (避免 18 位小数和"币种"误标)" version: 1.0.0 type: reference --- # ⚠️ process_signal.py: trader 解析 + 价格格式化 实战教训 ## Bug 1: `trader` 字段错误 = "币种" **症状**: 推送里显示 `信号源: 币种 ? SKHY (价值$?)` — trader 字段名是 "币种",不是真 trader。 **根因**: `parse_signal` L41 用 `re.search(r'【([^】]+)】', text)` 抓**第一个**方括号 — 第一个就是 `【币种】...`,导致 trader = "币种"。 **真实 trader** 通常不是 `【X】` 格式,而是 **`👉 跟单就选 X聚合社区`**(在信号末尾)。 ### 修复方案 (3 层 fallback) ```python FIELD_NAMES = {'币种', '方向', '杠杆', '仓位大小', '仓位价值', '开仓价', '当前价', '未实现盈亏', '收益额', '持仓量', '强平价', '数量'} # 1. 优先: 【交易员】标签 m_trader = re.search(r'【交易员】\s*[::]?\s*([^【\n]{1,20})', text) if m_trader: fields['trader'] = m_trader.group(1).strip() else: # 2. Fallback: 👉 跟单就选 X (真实 trader 来源) m_follow = re.search(r'👉\s*跟单就选\s*(\S+)', text) if m_follow: fields['trader'] = m_follow.group(1).strip() else: # 3. 最终 fallback: 第一个【xx】但跳过字段名 m_first = re.search(r'【([^】]{1,20})】', text) if m_first and m_first.group(1) not in FIELD_NAMES: fields['trader'] = m_first.group(1).strip() else: fields['trader'] = 'unknown' ``` ### 测试用例 ```python signal = """📈 注意 【币种】: SKHYUSDT|永续|5x 【方向】: 做空 🟥 【仓位】: 528.16 SKHY 【开仓价】: 161.90000 👉 跟单就选 X聚合社区""" parse_signal(signal)['trader'] # ✅ "X聚合社区" (不再是 "币种") ``` ## Bug 2: 价格小数位溢出 (18 位) **症状**: `入场: $161.01076124567473` — OKX 返回的浮点 × 比率算出 18 位小数。 **根因**: f-string 直接嵌入 float,没有 round。 ### 修复: 通用 _fmt() helper ```python def _fmt(x, n=4): """格式化数字: 字符串保留原样, 数字 round 到 n 位.""" try: return f"{float(x):.{n}f}" except (ValueError, TypeError): return str(x) # 模板里全部用 _fmt() msg = f"""⚡ 跟单建议 ... 入场: ${_fmt(entry_price)} | 当前: ${_fmt(current)} • SL: ${_fmt(rec['sl_price'])} → 预亏 -{_fmt(rec.get('sl_pnl', 0))} USDT """ ``` ⚠️ **`value` 缺失**: OKX 信号有时价值字段空 (e.g. `价值 $?`),`_fmt` 不能 float('?') 抛异常,**保留 `?`** — 让推送显示 "?" 但不报错。 ## 相关 - `process_signal.py` L36-67 (parse_signal) - `process_signal.py` L217-302 (format_message) - 测试: `python3 -c "import process_signal; process_signal.parse_signal(s)"`