main分支开仓告警BUG 2

开仓信号详细告警模块设计分析报告

结论:未发现严重 BUG

整体设计具备多层降级与异常隔离,递归有界,关键依赖有兜底,不存在会导致崩溃、死循环或错误告警内容的严重缺陷。以下为完整因果链与审查要点。


完整因果链:输入 → 状态变化 → 调用路径 → 出错点 → 根因

输入 (Input)

来源 内容
策略/服务层 on_entry_signal(symbol, multi_period_result, latest_alt_price, avg_zscore_4h, direction, signal_strength, adaptive_z, l2_snapshot)
开仓成功后 signal(本函数内构造)、multi_period_result(原样透传)、position, order_result(来自 open_position 返回值)、adaptive_z
配置/运行时 ENABLE_SIGNAL_DETAIL_ALERTleveragestrategy_adaptive_thresholdstrategy_reversion_factornetwork_labelget_available_balance() 的实时返回值

上述输入在进入告警路径前已被使用:multi_period_result.get("zscore_list") 等会先执行,若 multi_period_result 为 None 会在此处抛错,不会进入 _send_entry_alert


状态变化 (State changes)

阶段 位置 变化
开仓成功 src/trading/orchestrator.py save_signal(signal, "opened")update_daily_stats(..., trades_opened=1)circuit_breaker.record_success()
发告警前 _send_entry_alert available_balance = self._executor.get_available_balance()(只读,无持久状态)
格式化 format_signal_alert / _format_full 无持久状态,纯函数计算 (title, content)
发送成功 src/utils/monitoring/alert_sender.py _record(pair_name, content_hash)_history[pair_name].append(now)_recent_hashes[content_hash] = now(仅当未限流、未去重且即将调用 sender_colourful 时;若之后抛错则未执行 _record)

限流/去重时 send() 直接返回 THROTTLED,不调用 sender_colourful,也不执行 _record,故不会改变 AlertSender 内部状态。


调用路径 (Call path)

策略/服务层
  → Orchestrator.on_entry_signal(symbol, multi_period_result, ...)
      → multi_period_result.get("zscore_list") / .get("base_symbol") / .get("cointegration_count")   [若 None 则 AttributeError,在此结束]
      → 构造 PairTradeSignal(signal)
      → 频率/风控检查 → position_manager.open_position(signal, adaptive_z)
      → 若 open_result 为真:(position, order_result) = open_result
      → save_signal / update_daily_stats / circuit_breaker.record_success()
      → _send_entry_alert(signal, multi_period_result, position, order_result, adaptive_z)

_send_entry_alert
  → available_balance = self._executor.get_available_balance()
  → if not ENABLE_SIGNAL_DETAIL_ALERT: 直接跳到 _send_simple_entry_notification
  → try:
        title, content = format_signal_alert(signal, multi_period_result, position, order_result, ...)
        status = self._alert_sender.send(title, content, pair_name=signal.symbol, priority=...)
        if status == AlertStatus.SUCCESS: return
        else: 记录 warning,继续往下
     except Exception: 记录 error,继续往下
  → _send_simple_entry_notification(signal, position, order_result, adaptive_z, available_balance)

format_signal_alert(level=FULL)
  → _format_full(...)
      → risk = evaluate_risk(multi_period_result)   [内部 try/except,异常时返回默认 RiskAssessment]
      → for (name, builder, args) in builders: try builder(*args) → sections.append(...); except → sections.append("**{name}**: ⚠️ 数据缺失")
      → content = join(sections);若超长则截断
      → return (title, content)
  若上述任一步抛异常:
      → level==FULL  → format_signal_alert(..., level=SIMPLIFIED)
      → level==SIMPLIFIED → format_signal_alert(..., level=BASIC)
      → level==BASIC  → 兜底 return ("⚠️ 开仓成功(告警格式化异常) - {coin}", "**方向**: ...")

AlertSender.send
  → if priority != "high" and not _check_rate_limit(pair_name): return THROTTLED
  → if _is_duplicate(content_hash): return THROTTLED
  → sender_colourful(content=content, title=title)   [可能抛 RuntimeError 等]
  → _record(pair_name, content_hash)
  → return SUCCESS

_send_simple_entry_notification
  → 拼装 title/content(使用 position.*, order_result.*, signal.*, available_balance)
  → _send_trading_notification(title, content)
      → try: sender_colourful(content=..., title=...)
         except (OSError, ValueError): logger.error(...)

出错点 (Error points)

# 位置 现象 是否被捕获/降级
E1 on_entry_signalmulti_period_result.get(...) multi_period_result 为 None → AttributeError 否,抛给调用方,不会进入告警路径
E2 _format_fullevaluate_risk(multi_period_result) 异常数据结构导致内部异常 是,evaluate_risk 内部 try/except 返回默认 RiskAssessment
E3 _format_full 内某 builder(*args) 某区块依赖字段缺失/类型错误 是,该区块 try/except → append "数据缺失"
E4 format_signal_alert 兜底分支 _symbol_to_coin(signal.symbol)signal.direction / signal.zscore_4h 若为 None 或非预期类型 仅在 level==BASIC 且再次抛异常时进入兜底;兜底内若 signal.symbol 为 None 会 AttributeError,无再一层捕获
E5 AlertSender.send 限流 → return THROTTLED;去重 → return THROTTLED 不抛异常,orchestrator 收到 THROTTLED 后走 _send_simple_entry_notification
E6 AlertSender.sendsender_colourful(...) 队列满等 → RuntimeError 未在 send 内捕获,抛给 orchestrator;orchestrator 的 try 捕获后走 _send_simple_entry_notification
E7 _send_simple_entry_notification 内拼装 content position.alt_size / position.position_id 等为 None 未在此处 try,异常会抛到 _send_entry_alert 的调用方
E8 _send_trading_notificationsender_colourful(...) OSError / ValueError 已捕获,只打日志
E9 同上 RuntimeError(如队列满) 未捕获,向 _send_entry_alert 的调用方传播

根因 (Root cause)

出错点 根因归纳
E1 输入约束未在边界保证:调用方传入 multi_period_result=None 或未做前置校验,导致在 on_entry_signal 内首次 .get 即崩溃。
E2 数据形态多样:multi_period_result 来自上游分析/DB,可能缺键或类型异常;evaluate_risk 通过内部 try/except 兜底,避免影响告警。
E3 区块依赖的键或类型不一致:如 details 中 period key 为 tuple 与 string 混用、health_monitor 缺失等;单区块失败被隔离为「数据缺失」。
E4 兜底分支仍依赖 signal 必选字段:若 signal.symbol 为 None(理论上不应出现,因由 on_entry_signal 构造),兜底会再抛错;根因是 signal 构造或传递未保证非空。
E5 限流/去重策略:同一 pair 在窗口内超过 N 条或内容哈希重复,主动返回 THROTTLED,属业务策略而非缺陷。
E6 异步发送队列满或 lark_bot 内部异常:sender_colourful 或下层实现抛错,AlertSender 未吞掉异常,由 orchestrator 统一降级为简短通知。
E7 position/order_result 形态依赖 open_position 契约:若 open_position 返回的 position 某字段为 None(违反契约),会在此暴露;根因在仓位/订单结果构造或序列化。
E8/E9 发送层异常类型与捕获范围不一致:_send_trading_notification 只捕获 OSError/ValueError,RuntimeError 等未捕获会导致「开仓成功但通知失败且异常上抛」;根因是异常分类未覆盖发送端可能抛出的全部类型。

流程与数据流

sequenceDiagram
    participant O as Orchestrator
    participant F as format_signal_alert
    participant A as AlertSender
    participant S as sender_colourful

    O->>O: get_available_balance()
    alt ENABLE_SIGNAL_DETAIL_ALERT
        O->>F: format_signal_alert (level=FULL)
        alt FULL 成功
            F-->>O: title, content
            O->>A: send(title, content, ...)
            alt SUCCESS
                A->>S: sender_colourful
                A-->>O: SUCCESS
                O-->>O: return
            else THROTTLED
                A-->>O: THROTTLED
                O->>O: _send_simple_entry_notification
                O->>S: sender_colourful (直接调用)
            end
        else FULL/SIMPLIFIED 异常
            F-->>F: 降级 SIMPLIFIED/BASIC 或兜底
            F-->>O: title, content
            O->>A: send(...)
        end
    end
    O->>O: _send_simple_entry_notification
    O->>S: sender_colourful (直接)
  • 详细告警路径:仅通过 src/utils/monitoring/alert_sender.pyAlertSender.send(限流+去重)。
  • 降级路径:走 src/trading/orchestrator.py_send_trading_notification直接调用 sender_colourful,不经过 AlertSender。

已确认的安全设计

说明
递归有界 format_signal_alert 仅在 except 中按 FULL → SIMPLIFIED → BASIC 降级并递归,最多 3 层;BASIC 的兜底不再递归。
evaluate_risk 兜底 evaluate_risk 内部 try/except,异常时返回默认 RiskAssessment,不会把 None/异常结构抛给告警格式化。
区块级隔离 _format_full 中 10 个区块各自 try/except,单块失败只写「数据缺失」,不拖垮整条告警。
超长截断 content 超过 _MAX_CONTENT_LENGTH 时按核心区块截断,且仅在 len(sections) >= 10 时用固定下标,与 10 个 builder 一致,无越界。
入参来源 multi_period_result 来自 on_entry_signal 参数,在到达 _send_entry_alert 前已用过 .get("zscore_list") 等,若为 None 会提前抛错,不会在告警内出现 None。

设计点与可改进边界

3.1 降级路径不经过 AlertSender(设计取舍)

  • 现象:详细告警被限流/去重时,会走 _send_simple_entry_notification_send_trading_notification直接 sender_colourful,不经过 AlertSender。
  • 影响:同一币对在限流窗口内多次开仓时,可能收到「多次简短通知」(每次开仓一条),而详细告警仍被限流。
  • 判断:属于设计取舍——保证「至少有一条开仓通知」;若希望简短通知也受同一限流/去重约束,需要让简短通知也走 AlertSender 或统一入口。

3.2 详细告警发送抛错时的重复风险(边缘)

  • 现象:若 AlertSender.send() 在调用 sender_colourful 之后、_record() 之前抛错(如队列满),orchestrator 会 catch 后再发简短通知。
  • 影响:理论上存在「详细消息已入队 + 再发简短」导致两条通知的极小概率。
  • 建议:若 lark_bot 在「队列满」时是先抛错、未入队,则当前逻辑无重复;否则可在文档或注释中说明该边缘情况,或考虑在 send 内更细的异常处理与记录。

3.3 _send_trading_notification 的异常范围

  • 现象_send_trading_notification 仅捕获 OSError, ValueError;若 sender_colourful 抛出 RuntimeError(如队列满),会向调用方继续传播。
  • 影响:降级后的简短通知发送若因此抛错,会一路抛到 _send_entry_alert 的调用方;开仓本身已成功,仅通知失败。
  • 建议:若希望「通知失败不干扰主流程」,可在此处增加对 RuntimeError 的捕获并打日志,或统一为「所有发送异常只记日志不抛出」。

3.4 ENABLE_SIGNAL_DETAIL_ALERT 类型

  • 现象:配置通过 os.getenv(..., 'true').lower() in ('true', '1', 'yes') 得到 bool,行为明确,无类型问题。

小结

  • 是否存在严重 BUG。未发现崩溃、无限递归、错误告警内容或数据越界等问题;降级链与风险评估均有兜底。
  • 建议:若希望简短通知与详细告警共享限流/去重,可将简短通知也经 AlertSender 发送;并可在 _send_trading_notification 中扩展异常捕获(如 RuntimeError)或文档化边缘行为,以便后续维护。

Read more

跑步的技巧(滚动落地)

“滚动落地(rolling contact / rolling foot strike)”不是一种教条式的“脚法”,而是一种 让冲击沿着整只脚、整条后链逐级传递的落地机制。 它的核心不是“你先用哪儿着地”,而是: 你的脚落地之后,冲击是不是像轮子一样滚过去,而不是像锤子一样砸下去。 这就是滚动落地的本质。 一、什么叫“滚动落地”? 你可以把它理解成两种完全不同的落地方式: 1. 砸地(撞击式) 脚像锤子一样拍到地上: * 要么后跟先砸 * 要么前掌先戳 * 冲击集中在一个点 * 一个结构瞬间吃掉大部分载荷 结果就是: * 后跟砸 → 膝盖难受 * 前掌戳 → 前脚掌磨烂 * 都不是长跑友好模式 这叫 撞击式着地(impact strike)。 2. 滚地(滚动式) 脚像轮胎一样“滚”过地面: * 不是某一点硬砸 * 而是外侧中足先轻触 * 再向前滚到前掌 * 最后从大脚趾蹬离

By SHI XIAOLONG

AMI的优越性

世界模型(World Models)的具体例子 如下,我按类型分类,便于理解。每类都附带实际实现、演示效果和应用场景。 1. Yann LeCun / Meta 的 JEPA 系列(最直接对应“世界模型”概念) 这些是 LeCun 主张的非生成式抽象预测世界模型代表。 * I-JEPA(Image JEPA,2023) 输入一张图像,模型把不同区域(context 和 target)编码成抽象表示,然后预测 target 的表示(不在像素级别重建)。 例子:给定一张遮挡了部分物体的图片,模型能预测“被遮挡物体的大致位置和属性”,构建对物体持久性和空间关系的理解。 这是一个“原始世界模型”,能学习物理常识(如物体不会凭空消失)。 * V-JEPA / V-JEPA 2(Video JEPA,

By SHI XIAOLONG

什么是:“世界模型(World Models)”

世界模型(World Models) 是人工智能领域的一个核心概念,尤其在 Yann LeCun 等研究者推动的下一代 AI 架构中占据中心位置。它指的是 AI 系统在内部构建的对现实世界的抽象模拟或内部表示,让机器能够像人类或动物一样“理解”物理世界、预测未来、规划行动。 简单比喻 想象你闭上眼睛也能“看到”房间里的物体会如何移动、碰撞或掉落——这就是你大脑里的世界模型。AI 的世界模型就是类似的“数字孪生”(digital twin)或“内部模拟器”:它不是简单记住数据,而是学习世界的动态、因果关系和物理直觉(如重力、物体持久性、遮挡、因果等)。 为什么需要世界模型? 当前主流的大型语言模型(LLM) 擅长处理文本(统计模式预测),但存在根本局限: * 缺乏对物理世界的真正理解 → 容易“幻觉”、无法可靠规划。 * 样本效率低 → 人类/

By SHI XIAOLONG

K线周期可配置化设计方案

K线周期可配置化设计方案 1. 背景与目标 当前 Beta 套利策略的 K 线周期硬编码为 "1h",分散在多个文件中。需要: 1. 将 K 线周期从 1h 改为 2h 2. 提取为环境变量 BETA_ARB_KLINE_INTERVAL,使其可在 .env 中配置 2. 影响范围分析 2.1 需要修改的文件(共 6 个) 文件 硬编码位置 修改内容 src/trading/config.py BetaArbConfig dataclass 新增 kline_interval 字段,

By SHI XIAOLONG