PURR 期现套利开仓门控 — 设计文档

PURR 期现套利开仓门控 — 设计文档

日期: 2026-03-25
状态: 待实现


1. 需求背景

当前 PURR|HYPE 配对交易的开仓决策完全依赖 Z-score 均值回归信号(Adaptive Bollinger 策略)。
本次新增一道期现价差门控:在入场信号触发后、风控审核前,检查 PURR 永续合约价格
与 PURR Spot 现货价格之间是否存在方向一致的套利空间。

  • 存在套利空间 → 开仓放行
  • 不存在套利空间 → 开仓拦截

2. 核心逻辑

2.1 价差计算

spread = (perp_mid - spot_mid) / spot_mid
  • perp_mid: PURR 永续合约 L2 订单簿中间价(已有 WS 缓存)
  • spot_mid: PURR 现货 L2 订单簿中间价(新增 WS 订阅)

2.2 方向对齐规则

交易方向 要求的价差方向 经济含义 放行条件
Long PURR spread < 0(perp 折价) perp 比 spot 便宜,预期向上收敛 spread ≤ -min_spread
Short PURR spread > 0(perp 溢价) perp 比 spot 贵,预期向下收敛 spread ≥ +min_spread
  • min_spread 默认 1%(0.01),通过环境变量配置。

2.3 拦截行为

不满足条件时:

  1. 记录 INFO 日志(含 perp 价、spot 价、spread、方向)
  2. 保存信号到 DB(action="rejected",reason="期现价差不足")
  3. 更新每日统计(entry_signals +1, trades_rejected +1)
  4. 发送拒绝告警
  5. 返回 False,不执行开仓

3. 作用范围

仅针对 PURR|HYPE 配对。判断依据:symbol_to_coin(symbol) == "PURR"
其他配对不受此门控影响,直接跳过检查。

4. 价格源

4.1 Perp 中间价(已有)

已通过行情 WS 的 l2Book 频道订阅,缓存于 EnhancedWebSocketManager.latest_data["PURR:l2Book"]
Executor 的 get_all_mids()_get_all_mids_from_ws() 可直接读取。

4.2 Spot 中间价(新增)

Hyperliquid Spot 市场的 L2 订单簿使用 @{universe_index} 格式的 coin 标识符。
例如 PURR/USDC 在 spot universe 中的 index 为 N,则其 L2 Book 订阅 coin 为 @N

解析流程(Executor 初始化时一次性完成):

spot_meta_and_asset_ctxs() API
    ↓
response[0]["universe"]  →  找到 name 含 "PURR" 的条目  →  取其 index
    ↓
spot_coin_id = f"@{index}"     # 例如 "@4"

WS 订阅_build_market_subscriptions 追加):

{"type": "l2Book", "coin": "@4"}

数据缓存路径

WS l2Book 推送 → EnhancedWebSocketManager._cache_latest_data()
    → latest_data["@4:l2Book"] = msg

读取路径(Executor 新增方法):

get_spot_mid_price("PURR")
    → 查 _spot_coin_ids["PURR"] = "@4"
    → 读 latest_data["@4:l2Book"]
    → _calculate_mid_from_l2_data(msg)  →  float

5. 文件变更清单

5.1 src/trading/config.py

TradingConfig 新增字段:

# 期现套利开仓门控
futures_spot_arb_enabled: bool = False
futures_spot_arb_min_spread_pct: float = 0.01   # 1%

load_trading_config() 新增环境变量读取:

futures_spot_arb_enabled=os.getenv(
    "TRADING_FUTURES_SPOT_ARB_ENABLED", "false"
).lower() == "true",
futures_spot_arb_min_spread_pct=_env_float(
    "TRADING_FUTURES_SPOT_ARB_MIN_SPREAD_PCT", "0.01"
),

环境变量:

变量名 默认值 说明
TRADING_FUTURES_SPOT_ARB_ENABLED false 是否启用期现套利门控
TRADING_FUTURES_SPOT_ARB_MIN_SPREAD_PCT 0.01 最小价差阈值(1%)

5.2 src/trading/executor.py

__init__ 新增字段:

# Spot L2 coin ID 映射(coin → "@{index}",例如 {"PURR": "@4"})
self._spot_coin_ids: dict[str, str] = {}

initialize() 末尾追加:futures_spot_arb_enabled=True 时调用 _resolve_spot_coin_ids()

新增私有方法 _resolve_spot_coin_ids()

def _resolve_spot_coin_ids(self):
    """从 spot_meta_and_asset_ctxs 解析 spot coin 的 @index 标识符。

    仅解析 PURR(当前唯一需要期现价差门控的币种)。
    结果写入 self._spot_coin_ids,供 WS 订阅和价格读取使用。
    """
    try:
        data = retry_call(
            lambda: self._info.spot_meta_and_asset_ctxs(),
            description="Spot 元数据查询",
        )
        meta = data[0] if data else {}
        for pair in meta.get("universe", []):
            name = pair.get("name", "")          # "PURR/USDC"
            index = pair.get("index")
            coin = name.split("/")[0] if "/" in name else name
            if coin.upper() == "PURR" and index is not None:
                self._spot_coin_ids["PURR"] = f"@{index}"
                logger.info(
                    f"Spot coin ID 解析成功: PURR → @{index} (pair={name})"
                )
                return
        logger.warning("Spot 元数据中未找到 PURR 配对")
    except Exception as e:
        logger.error(f"Spot coin ID 解析失败: {e}", exc_info=True)

新增公开方法 get_spot_coin_ids()

def get_spot_coin_ids(self) -> dict[str, str]:
    """返回 spot coin ID 映射,供 WS 订阅层使用。"""
    return dict(self._spot_coin_ids)

新增公开方法 get_spot_mid_price(coin: str)

def get_spot_mid_price(self, coin: str) -> float:
    """从行情 WS 的 L2Book 缓存获取指定 coin 的 spot 中间价。

    Args:
        coin: 币种名(如 "PURR"),内部自动映射为 @index 格式

    Returns:
        spot 中间价,不可用时返回 0.0
    """
    spot_id = self._spot_coin_ids.get(coin)
    if not spot_id:
        return 0.0

    if not self._market_ws_manager:
        return 0.0

    cache_key = f"{spot_id}:l2Book"
    try:
        with self._market_ws_manager.latest_data_lock:
            msg = self._market_ws_manager.latest_data.get(cache_key)
        if msg is None:
            return 0.0
        return self._calculate_mid_from_l2_data(msg)
    except Exception as e:
        logger.debug(f"Spot L2 缓存读取失败 ({coin} → {spot_id}): {e}")
        return 0.0

5.3 src/services/realtime_kline_service_base.py

_build_market_subscriptions() 末尾追加 Spot L2 订阅:

# Spot L2Book 订阅(期现套利门控)
if self._trading_orchestrator:
    spot_coins = self._trading_orchestrator.get_spot_l2_coins()
    for coin, spot_id in spot_coins.items():
        subscriptions.append({"type": "l2Book", "coin": spot_id})
        self.logger.info(
            f"Spot L2Book 订阅已添加: {coin} → {spot_id}"
        )

注意事项:

  • EnhancedWebSocketManager._cache_latest_data() 已通用处理 l2Book 频道
    cache_key = f"{coin}:l2Book"),spot coin @N 会被自动缓存为 "@N:l2Book",无需改动。
  • _get_all_mids_from_ws()@N 会出现在返回字典中,但不影响 perp 价格查询。

5.4 src/trading/orchestrator.py

新增方法 get_spot_l2_coins()

def get_spot_l2_coins(self) -> dict[str, str]:
    """返回需要订阅 Spot L2Book 的 coin ID 映射。

    供 RealtimeKlineServiceBase._build_market_subscriptions() 调用,
    在行情 WS 中追加 Spot L2Book 订阅。
    """
    if self._executor is None:
        return {}
    return self._executor.get_spot_coin_ids()

on_entry_signal() 中追加门控(位于配对级频率限制之后、风控审查之前):

# 期现套利门控(仅 PURR|HYPE 配对)
if self._config.futures_spot_arb_enabled and symbol_to_coin(symbol) == "PURR":
    perp_mids = self._executor.get_all_mids()
    perp_price = perp_mids.get("PURR", 0.0)
    spot_price = self._executor.get_spot_mid_price("PURR")

    if perp_price > 0 and spot_price > 0:
        spread = (perp_price - spot_price) / spot_price
        min_spread = self._config.futures_spot_arb_min_spread_pct
        pair_label = _pair_label((symbol, base_symbol or ""))

        # Long → 要求 perp 折价(spread ≤ -min_spread)
        # Short → 要求 perp 溢价(spread ≥ +min_spread)
        arb_ok = (
            (direction == "long"  and spread <= -min_spread) or
            (direction == "short" and spread >= min_spread)
        )

        if not arb_ok:
            reason = (
                f"期现价差不足 | {direction} | "
                f"spread={spread:+.4%} (需{'≤' if direction == 'long' else '≥'}"
                f"{-min_spread if direction == 'long' else min_spread:+.4%}) | "
                f"perp=${perp_price:.6f} spot=${spot_price:.6f}"
            )
            logger.info(f"🚫 期现套利门控拦截 | {pair_label} | {reason}")
            self._trade_repo.save_signal(
                signal, "rejected", reason, self._config.network.value,
            )
            self._trade_repo.update_daily_stats(
                network=self._config.network.value,
                symbol=symbol,
                base_symbol=base_symbol,
                entry_signals=1,
                trades_rejected=1,
            )
            self._send_rejected_alert(
                signal, reason, adaptive_z,
                self._executor.get_available_balance(),
            )
            return False

        logger.info(
            f"✅ 期现套利门控通过 | {pair_label} {direction} | "
            f"spread={spread:+.4%} | perp=${perp_price:.6f} spot=${spot_price:.6f}"
        )
    else:
        logger.warning(
            f"⚠️ 期现套利门控跳过(价格不可用) | "
            f"perp={'可用' if perp_price > 0 else '不可用'} "
            f"spot={'可用' if spot_price > 0 else '不可用'}"
        )

6. 数据流总览

                          初始化阶段
                          ─────────
Executor.initialize()
    │
    ├─ spot_meta_and_asset_ctxs() API
    │       ↓
    │   解析 PURR spot universe index
    │       ↓
    │   _spot_coin_ids = {"PURR": "@4"}
    │
    ▼
_build_market_subscriptions()
    │
    ├─ 原有: {"type": "l2Book", "coin": "PURR"}      ← perp L2
    ├─ 新增: {"type": "l2Book", "coin": "@4"}         ← spot L2
    │
    ▼
EnhancedWebSocketManager 连接主网 WS

                          运行阶段
                          ─────────
WS 推送 l2Book (coin="PURR")  →  latest_data["PURR:l2Book"]   ← perp
WS 推送 l2Book (coin="@4")    →  latest_data["@4:l2Book"]     ← spot

                          信号触发
                          ─────────
strategy._check_entry()  →  EntrySignal(direction="long", ...)
    ↓
orchestrator.on_entry_signal()
    │
    ├─ 信号告警(不受门控影响)
    ├─ 全局频率限制
    ├─ 配对级频率限制
    │
    ├─ 【新增】期现套利门控 ◄──────────────────────┐
    │       │                                      │
    │       ├─ get_all_mids()["PURR"]  → perp_mid  │
    │       ├─ get_spot_mid_price("PURR") → spot_mid│
    │       ├─ spread = (perp - spot) / spot        │
    │       │                                      │
    │       ├─ Long + spread ≤ -1% → ✅ 放行        │
    │       ├─ Short + spread ≥ +1% → ✅ 放行       │
    │       └─ 否则 → 🚫 拦截                       │
    │                                              │
    ├─ 风控审查                                     │
    └─ 执行开仓                                     │

7. 配置示例

# .env
TRADING_FUTURES_SPOT_ARB_ENABLED=true
TRADING_FUTURES_SPOT_ARB_MIN_SPREAD_PCT=0.01   # 1%

8. 风险与兜底

场景 处理
Spot L2 缓存无数据(WS 断连/PURR spot 无流动性) spot_price=0.0 → 跳过门控,输出 WARNING 日志,不阻塞开仓
Perp L2 缓存无数据 perp_price=0.0 → 跳过门控,同上
spot_meta_and_asset_ctxs 查不到 PURR 初始化 WARNING → _spot_coin_ids 为空 → 运行时跳过门控
功能开关关闭 futures_spot_arb_enabled=false → 完全跳过,零开销
非 PURR 配对 symbol_to_coin(symbol) != "PURR" → 跳过

9. 日志示例

# 放行
✅ 期现套利门控通过 | PURR|HYPE long | spread=-1.52% | perp=$0.001230 spot=$0.001249

# 拦截
🚫 期现套利门控拦截 | PURR|HYPE | 期现价差不足 | long | spread=-0.32% (需≤-1.0000%) | perp=$0.001245 spot=$0.001249

# 价格不可用
⚠️ 期现套利门控跳过(价格不可用) | perp=可用 spot=不可用

Read more

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

对于空间环境、“信息/逻辑”(比如代码、结构、表达)秩序追求的心理特征分析

一、为什么是“空间 + 信息”同时强化? 因为你当年面对的是“双重失控”: 1️⃣ 外部世界是脏乱 + 失序的 * 空间被污染 * 行为无边界 * 基本生活秩序崩塌 👉 所以你现在会强烈要求: * 桌面干净 * 房间有序 * 物品可控 这是在修复:“物理世界必须是可控的” 2️⃣ 人的行为和逻辑也是混乱的 * 没有规则 * 没有底线 * 没有理性 👉 所以你现在会特别在意: * 表达是否清晰 * 逻辑是否自洽 * 结构是否优雅 * 代码是否干净 这是在修复:“认知世界必须是合理的” 二、你其实构建了一个“高纯度系统” 你现在的偏好,本质上是: 👉 低噪音 + 高结构 + 强控制感 具体表现就是: * 空间:极简、整洁、可预测 * 信息:清晰、压缩、无冗余 这类人有一个很明显的优势: 👉 处理复杂问题时,

By SHI XIAOLONG