账户余额查询与稳定币兑换机制

账户余额查询与稳定币兑换机制

1. 概述

本系统运行在 Hyperliquid 交易所上,支持两种账户模式:统一账户(Unified Account)普通账户(Standard Account)。两种模式下余额查询的 API 和计算逻辑完全不同。

核心文件:

  • src/trading/executor.py — 余额查询、兑换执行
  • src/trading/config.py — 兑换配置
  • src/trading/risk_manager.py — 风控中的余额校验
  • src/trading/orchestrator.py — 编排层调用入口

2. 账户模式检测

2.1 检测逻辑(_detect_unified_account

通过 Spot API 推断账户类型:

spot_user_state(address)
├─ USDC total > 0 且 hold > 0  → 统一账户(hold 被 perp 占用)
├─ USDC total > 0 且 perp accountValue == 0  → 统一账户(资金全在 spot 层)
└─ 其他  → 普通账户

2.2 统一账户 vs 普通账户资金结构

维度 统一账户 普通账户
资金位置 全部在 Spot 层 Perp 层独立
Perp 保证金 Spot USDC 中 hold 部分 Perp accountValue
可用余额 Spot 所有代币 sum(total - hold) Perp withdrawable
Perp withdrawable 恒为 0(不代表可用) 真实可提取金额

3. Hyperliquid API 调用

3.1 调用的 API 列表

API 方法 封装方法 用途 返回关键字段
info.spot_user_state(addr) _spot_user_state_with_retry Spot 层余额 balances[].{coin, total, hold}
info.user_state(addr) _user_state_with_retry Perp 账户状态 marginSummary.accountValue, withdrawable
info.user_state(addr, dex=) HIP-3 DEX 账户状态 marginSummary.accountValue

3.2 spot_user_state 返回结构

{
  "balances": [
    {"coin": "USDC", "token": 0,   "total": "85.68", "hold": "82.16"},
    {"coin": "USDH", "token": 360, "total": "10.97", "hold": "0.0"},
    {"coin": "USDE", "token": 235, "total": "10.24", "hold": "0.0"}
  ]
}
  • total:该代币的总余额
  • hold:被占用部分(统一账户下 = perp 保证金占用)
  • available = total - hold

3.3 user_state 返回结构(Perp)

{
  "marginSummary": {
    "accountValue": "82.16",
    "totalMarginUsed": "82.16",
    "totalNtlPos": "243.61",
    "totalRawUsd": "-161.45"
  },
  "withdrawable": "0.0",
  "crossMarginSummary": { ... }
}
  • 统一账户下 accountValue = spot USDC hold(perp 占用的那部分)
  • 统一账户下 withdrawable 恒为 0

3.4 重试策略

场景 最大重试次数 最大退避时间
初始化阶段(is_critical=True 5 次 20 秒
运行时(is_critical=False 3 次 10 秒

4. 余额计算逻辑

4.1 账户总价值(get_account_value

统一账户:
  Spot total_value(所有代币 total 之和)
  + HIP-3 DEX accountValue(按 DEX 分别查询)
  = 总价值

普通账户:
  Perp marginSummary.accountValue(WS 缓存优先)
  + HIP-3 DEX accountValue
  = 总价值

4.2 可用余额(get_available_balance

统一账户:
  → _get_spot_available_value()
  → 遍历 spot balances,累加 max(total - hold, 0)
  → 所有代币可用余额之和(USDC + USDH + USDE + ...)

普通账户:
  → WS 缓存优先(BalanceChangedEvent 驱动更新,TTL 5秒)
  → 缓存未命中 → HTTP 降级:user_state().withdrawable

4.3 缓存机制

普通账户下,余额通过 WebSocket 事件驱动缓存:

Trading WS → BalanceChangedEvent → _on_balance_changed()
  → _cached_available_balance = event.available_balance
  → _balance_cache_ts = now

Trading WS → PositionUpdatedEvent → _on_position_updated()
  → _cached_account_value = event.account_value
  → _position_cache_ts = now
  • 缓存 TTL:5 秒
  • WS 断连时跳过缓存,直接 HTTP 查询
  • WS 重连时清空所有缓存

统一账户不使用 WS 缓存,每次都通过 HTTP 查询 spot_user_state


5. 稳定币兑换机制

5.1 配置项

配置 环境变量 默认值 说明
quote_swap_enabled TRADING_QUOTE_SWAP_ENABLED false 总开关
quote_swap_buffer_pct TRADING_QUOTE_SWAP_BUFFER_PCT 0.05 兑换额外缓冲 5%
quote_swap_slippage TRADING_QUOTE_SWAP_SLIPPAGE 0.005 Spot 兑换滑点 0.5%
quote_swap_min_amount TRADING_QUOTE_SWAP_MIN_AMOUNT 10.0 最低兑换金额(HL 要求 ≥10 USD)

5.2 HIP-3 报价货币识别

HIP-3 DEX 可能使用非 USDC 的稳定币作为保证金(如 USDH)。识别流程:

info.meta(dex=dex_name)
  → universe[].collateralToken(整数索引)
  → info.spot_meta().tokens[index].name
  → 报价货币名称

示例:
  collateralToken=0   → token_index_map[0]   → "USDC"
  collateralToken=360 → token_index_map[360] → "USDH"

结果存储在 _hip3_dex_quote_map: dict[str, str]{dex_name → quote_currency})。

5.3 兑换入口(_ensure_quote_currency

开仓前由 open_position 调用,判断是否需要兑换:

_ensure_quote_currency(coin, required_usd)
│
├─ quote_swap_enabled == False → 直接放行
│
├─ _get_required_quote(coin)
│   ├─ 标准 perp(coin 不含 ':')→ None
│   ├─ HIP-3 USDC 计价 DEX     → None
│   └─ HIP-3 非 USDC 计价 DEX  → "USDH" 等
│
├─ quote is None(标准 perp / USDC 计价):
│   ├─ _hip3_dex_quote_map 为空 → 放行
│   ├─ _get_spot_balance("USDC") → usdc_available
│   ├─ usdc_available >= required → 放行
│   └─ 不足 → _sell_stables_for_usdc(deficit × 1.05)
│
└─ quote is not None(HIP-3 非 USDC 计价):
    ├─ _get_spot_balance(quote) → available
    ├─ available >= required → 放行
    ├─ 不足 → 检查 USDC 余额
    │   ├─ USDC 够 → _execute_quote_swap(quote, amount)
    │   └─ USDC 不够 → _sell_stables_for_usdc() 先补 USDC
    └─ 再执行 _execute_quote_swap(quote, amount)

5.4 正向兑换:USDC → 目标稳定币(_execute_quote_swap

场景:HIP-3 DEX 需要 USDH 保证金,用 USDC 买入 USDH。

# Spot 市价买入
exchange.market_open(
    f"{target_token}/USDC",   # 如 "USDH/USDC"
    True,                      # is_buy = True
    amount,                    # 兑换数量
    slippage=quote_swap_slippage,
)

金额计算:

deficit = required - available
swap_amount = deficit × (1 + buffer_pct)
swap_amount = max(swap_amount, min_amount)           # 强制最低值
swap_amount = math.floor(swap_amount × 100) / 100    # 按 szDecimals=2 截断

5.5 反向兑换:其他稳定币 → USDC(_sell_stables_for_usdc

场景:标准 perp 开仓时 Spot USDC 可用余额不足,卖出闲置的 USDH/USDE 等换取 USDC。

# 从 _hip3_dex_quote_map 收集所有非 USDC 稳定币
candidates = {token for token in _hip3_dex_quote_map.values()
              if token != "USDC" and token != exclude_token}

# 逐个尝试卖出
for token in candidates:
    available = _get_spot_balance(token)  # (total - hold)
    sell_amount = min(available, max(required_usdc, min_amount))
    sell_amount = math.floor(sell_amount × 100) / 100

    if sell_amount < min_amount:
        continue  # 余额不够最低兑换金额,跳过

    exchange.market_open(f"{token}/USDC", False, sell_amount, slippage=...)

5.6 兑换失败处理

  • 每次兑换失败都通过 _alert_quote_swap_failure 发送飞书告警
  • 错误信息返回给调用方,由 open_position 记录到 OrderResult.error_message
  • 信号状态记录为 rejected,写入 DB

6. 风控中的余额校验

6.1 开仓前风控(RiskManager.pre_trade_check

编排层在执行开仓前获取余额并传入风控:

account_value = executor.get_account_value()
available_balance = executor.get_available_balance()

passed, reason = risk_manager.pre_trade_check(
    signal, open_positions, account_value, available_balance
)

6.2 可用余额校验公式

position_usd = base_position_usd × strength_scale
legs = 2 (pair 模式) 或 1 (single 模式)
min_required = position_usd × legs / leverage × min_balance_factor

if available_balance < min_required:
    → 拒绝开仓

6.3 开仓后余额复查

Leg A 限价单成交后、Leg B 下单前,强制 HTTP 刷新余额:

recheck_balance = executor.get_available_balance(force_refresh=True)

7. 数据流全景图

┌─────────────────────────────────────────────────────────────────┐
│                        开仓信号触发                               │
└──────────────────────────┬──────────────────────────────────────┘
                           ▼
┌─────────────────────────────────────────────────────────────────┐
│ orchestrator._handle_entry_signal()                             │
│   ├─ get_account_value()  ─┐                                    │
│   ├─ get_available_balance() ── → risk_manager.pre_trade_check()│
│   └─ 风控通过 → open_position()                                 │
└──────────────────────────┬──────────────────────────────────────┘
                           ▼
┌─────────────────────────────────────────────────────────────────┐
│ executor.open_position()                                        │
│   ├─ margin_needed = size × price / leverage                    │
│   ├─ _ensure_quote_currency(coin, margin_needed)                │
│   │   ├─ 标准 perp: 检查 Spot USDC,不足则卖其他稳定币           │
│   │   └─ HIP-3: 检查对应 quote token,不足则 USDC 兑换           │
│   ├─ _place_limit_order(coin, ..., l2_snapshot)  ← Leg A        │
│   ├─ _track_limit_order(...)  ← 追踪成交                        │
│   ├─ get_available_balance(force_refresh=True)  ← 复查余额       │
│   └─ _place_market_order(base_coin, ...)  ← Leg B               │
└─────────────────────────────────────────────────────────────────┘
                           ▼
┌─────────────────────────────────────────────────────────────────┐
│                     Hyperliquid API                              │
│   ├─ spot_user_state(addr) → Spot 余额(统一账户的资金来源)       │
│   ├─ user_state(addr) → Perp 状态(普通账户的资金来源)           │
│   ├─ user_state(addr, dex=) → HIP-3 DEX 状态                   │
│   ├─ exchange.market_open() → Spot 兑换(稳定币互换)             │
│   ├─ exchange.order() → Perp 限价单                              │
│   └─ exchange.market_open() → Perp 市价单                       │
└─────────────────────────────────────────────────────────────────┘

8. 已知问题与注意事项

  1. 统一账户下 Perp withdrawable 恒为 0:不能用 user_state().withdrawable 判断统一账户的可用余额,必须用 spot_user_state() 计算。

  2. Available to Trade 只看 USDC:Hyperliquid 标准 perp 的 "Available to Trade" 仅等于 Spot USDC available(total - hold),不会自动使用 USDH/USDE 等其他稳定币。需要通过 Spot 市价单手动兑换。

  3. quote_swap_min_amount 需对齐交易所要求:Hyperliquid Spot 交易最低名义价值为 10 USD,该配置不应高于此值,否则会导致可卖稳定币被错误跳过。

  4. 统一账户不使用 WS 余额缓存:每次查询都走 HTTP spot_user_state,在高频查询场景下可能产生 API 限速风险。

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