Skip to content

Latest commit

 

History

History
1127 lines (778 loc) · 70.4 KB

File metadata and controls

1127 lines (778 loc) · 70.4 KB

ETH 永续合约动态网格交易策略规格说明书

文档用途:面向外包实施团队的完整交易策略规格说明。本文档完整描述策略的设计思想、系统架构、算法逻辑和参数体系,实施方应依据本文档从零开始设计实现,无需参考原有代码。

目标标的:ETH/USDT 永续合约(兼容 Binance / Gate.io / OKX) 策略类型:多段价格区间 · 做多网格 · 实时动态委托


目录

  1. 策略核心思想
  2. 整体架构
  3. 价格区间箱体的内部结构
  4. 区间管理器:激活与切换逻辑
  5. 追踪建仓模式(机器人激活前) ← 决定"什么时候开始交易"
  6. 主网格运行逻辑
  7. 追逐下单:实时定价与订单追踪 ← 决定"以什么价格和方式下单"
  8. 止损缓冲区与算法单哨兵
  9. 止损与反弹恢复状态机
  10. 配置参数完整说明
  11. 参数选择方法论 ← 为什么这样选参数
  12. 关键约束与边界条件
  13. 优化空间分析
  14. AI Agent 增强方向

一、策略核心思想

本策略在以下几个核心设计思想上有别于交易所原生网格机器人:

1.1 先观察后决策的"盯盘"思维,而非批量静态挂单

传统网格一次性将所有买卖单挂入交易所,订单挂出后不再移动,等市场自己来"撞单"。本策略的核心出发点截然不同:像一个有经验的人类交易员盯盘一样,先观察当前行情,再动态决定是否挂单、挂什么价格、还是撤掉旧单。

具体表现为:

  • 系统每次收到行情更新时,先判断当前价格与网格目标价的关系
  • 如果条件不利(价格高于目标买价,进入"熔断区")→ 拒绝下单,或撤销现有挂单,等待观望
  • 如果条件有利(价格低于目标买价,进入"POC 区")→ 在当前最优买价挂一个 Post-Only 单
  • 如果价格大幅偏离(进入"GTC 区")→ 改用市价立即成交,锁定超额利润

系统在任意时刻不一定有网格单挂着——这与传统网格始终保持满屏挂单的方式截然不同。本质上,这是在基本网格策略上增加了一层自动化盯盘决策:用程序代替人工,持续观察并实时调整是否下单、如何下单。

这样做的收益:

  • 以当前市场最优价卡位,而非等市场来撞预设的静态订单
  • 价格条件不利时主动停手,避免在错误价位成交
  • 价格大幅跳跃时可捕获超出网格步长的额外价差利润

1.2 Maker 优先、Taker 特许、不利熔断的三区间定价

每次挂单时,系统会根据当前市场价与网格目标价的关系,动态选择挂单方式:

  • Maker 挂单(默认):价格在网格底价附近,挂 Post-Only 限价单,等待被动成交,以更低的 Maker 手续费率(0.02%)成交,比主动吃单节省 0.03%
  • Taker 吃单(特许):价格已大幅低于网格底价,额外利润超过 Taker 手续费时,主动吃单以锁定更大价差
  • 熔断不动:当前价格比网格底价更贵时,拒绝下单,避免高买低卖

1.3 追踪建仓,避免顶部开仓被套

当价格从上方下落进入网格区间时,系统不立即开仓,而是追踪价格低点,等到价格从低点反弹确定回调幅度后才建仓。这避免了"一建仓就站在高位被套"的情形。

1.4 分层止损缓冲,保留反弹恢复能力

在主网格下方设置一段"止损缓冲区":目标持仓随价格下跌逐格递减,以动态限价单分层减仓,而非在单一价位一次性全平;仅在清算线挂一张"全平"算法条件单作为最后防线(详见第八章统一止损机制)。若价格短暂跌破后反弹,残余仓位可直接受益,省去重新建仓的手续费。

1.5 多段价格区间,价格去哪里策略就跟到哪里

针对 ETH 的宽幅波动,预先配置多个不重叠的价格区间(如 2000-2600、2600-3200 等)。系统实时监控价格,价格进入哪个区间就激活该区间对应的网格机器人,其余区间保持休眠。


二、整体架构

┌─────────────────────────────────────────────────────────────────┐
│                      区间管理器 BotManager                        │
│                                                                   │
│  配置 A [2000-2600]     配置 B [2600-3200]     配置 C [3200-4000] │
│  状态: 休眠             状态: 运行中            状态: 休眠          │
│                              │                                    │
│                     激活 ▼ / 休眠 ▲                              │
│                              │                                    │
│                    ┌─────────▼──────────┐                        │
│                    │   网格机器人 GridBot  │                        │
│                    │                     │                        │
│                    │  FSM 状态机          │                        │
│                    │  ┌──────────────┐  │                        │
│                    │  │TRAILING_ENTRY│  │                        │
│                    │  │   RUNNING    │  │                        │
│                    │  │ LIQUIDATING  │  │                        │
│                    │  │ LIQUIDATED   │  │                        │
│                    │  │ TAKE_PROFIT  │  │                        │
│                    │  └──────────────┘  │                        │
│                    └─────────────────────┘                        │
│                                                                   │
│  WebSocket 数据流:行情 Ticker / 订单成交 / 算法单触发 / 持仓变化   │
└─────────────────────────────────────────────────────────────────┘

核心原则:

  • 任何时刻最多只有一个区间的网格机器人处于运行状态
  • 区间管理器持续监听行情,负责决定何时激活、何时停止机器人
  • 机器人退出后,管理器回到监听状态,等待下一个区间满足条件

三、价格区间箱体的内部结构

几何核心唯一实现:本节描述的所有结构线、区域与目标仓位推导,均由 packages/shared-types/src/box-geometry.ts(d 空间单实现)统一计算,前后端共同消费。

3.1 锚点与 d 空间坐标

箱体的几何锚定在止盈端:用户输入 takeProfitPrice(止盈线,做多为高价端、做空为低价端),箱体一律从止盈线向亏损方向展开。其余配置项为格数与步长(mainGridCount / mainGridStep / stopLossGridCount / stopLossGridStep / isolationStep)。boxHighPrice / boxLowPrice 是从锚点 + 深度推导出来的展示/校验用绝对边界,不再是用户输入项。

为消除做多/做空两套镜像逻辑,几何计算在归一化的 d 空间进行:

d = 价格离止盈端的有向距离,亏损方向为正

LONG:  d(price) = takeProfitPrice − price     price(d) = takeProfitPrice − d
SHORT: d(price) = price − takeProfitPrice     price(d) = takeProfitPrice + d

direction 分支只出现在这两个映射函数里;区域判定、目标仓位、激活窗口、追踪建仓、FSM 触发全部在 d 空间单实现,做空自动镜像做多。

3.2 四条结构线(沿亏损方向依次)

d = 0 ───────────────── takeProfitPrice    止盈线(用户输入锚点)
  │   主网格区            mainGridDepth = mainGridCount × mainGridStep
d = mainGridDepth ────── fullPositionPrice  满仓线
  │   隔离带(无操作区)   + isolationStep
d = isolationEndDepth ── stopLossStartPrice 止损区起点
  │   止损区             + stopLossGridCount × stopLossGridStep
d = boxDepth ─────────── liquidationPrice   清算线(推导,兜底条件单挂这里)

LONG 方向(止盈线在上,价格向下为亏损):

takeProfitPrice   ─────────────────── 止盈线(最高价)   d = 0
                   │   主网格区        │  ← 正常网格交易
fullPositionPrice ─────────────────── 满仓线            d = mainGridDepth
                   │   隔离带          │  ← 无操作区
stopLossStartPrice ────────────────── 止损区起点        d = isolationEndDepth
                   │   止损区          │  ← 动态减仓区
liquidationPrice  ─────────────────── 清算线(最低价)   d = boxDepth

SHORT 方向(止盈线在下,价格向上为亏损 —— 上图整体翻转):

liquidationPrice  ─────────────────── 清算线(最高价)   d = boxDepth
                   │   止损区          │  ← 动态减仓区
stopLossStartPrice ────────────────── 止损区起点        d = isolationEndDepth
                   │   隔离带          │  ← 无操作区
fullPositionPrice ─────────────────── 满仓线            d = mainGridDepth
                   │   主网格区        │  ← 正常网格交易
takeProfitPrice   ─────────────────── 止盈线(最低价)   d = 0

各结构线的 d 空间推导:

结构线 d 值 绝对价格
takeProfitPrice(止盈线) 0 用户输入锚点
fullPositionPrice(满仓线) mainGridDepth = mainGridCount × mainGridStep toPrice(mainGridDepth)
stopLossStartPrice(止损区起点) isolationEndDepth = mainGridDepth + isolationStep toPrice(isolationEndDepth)
liquidationPrice(清算线) boxDepth = isolationEndDepth + stopLossGridCount × stopLossGridStep toPrice(boxDepth)
boxHighPrice / boxLowPrice — max / min(takeProfitPrice, liquidationPrice)(展示/重叠校验用)

约束:止损区总深度不得超过箱体总深度的 1/5,即:

stopLossGridCount × stopLossGridStep ≤ boxDepth / 5

隔离带的真实用途:隔离带不只是一个"无操作缓冲区",它有明确的经济动机——防止价格在止损区与主网格边界(满仓线 fullPositionPrice)附近震荡时,主网格加仓和止损减仓交替触发,造成频繁的手续费损耗。

如果没有隔离带,满仓线(fullPositionPrice)与止损区起点(stopLossStartPrice)直接相邻:

  • 价格稍微越过满仓线进入止损区(向亏损方向)→ 止损减仓触发,减一份
  • 价格稍微反弹回主网格 → 目标仓位算法触发,加一份
  • 如此来回,每次都要付出买卖两次手续费,而实际价格并未真正单边移动

隔离带的宽度(建议与主网格步长相同)提供了一个"死区",使得短暂的价格噪声无法同时触发两套逻辑,大幅减少无效交易。


四、区间管理器:激活与切换逻辑

4.1 激活条件

区间管理器在以下条件全部满足时,激活对应区间的网格机器人:

  1. 当前无活跃机器人(同一时刻只允许一个机器人运行)
  2. 价格在激活窗口内(d 空间方向无关):activationDepth < d(当前价格) < boxDepth
    • 即价格在 activationPrice 与清算线 liquidationPrice 之间(朝亏损方向)
    • 做多等价于 liquidationPrice < 当前价格 < activationPrice;做空镜像为 activationPrice < 当前价格 < liquidationPrice
    • activationPrice 默认取主网格中点(d = mainGridDepth / 2,做多即 (takeProfitPrice + fullPositionPrice) / 2),也可手动配置;校验要求其落在主网格内 0 < d(activationPrice) < mainGridDepth

激活窗口限制在靠亏损端启动,理由:若价格离止盈端太近,止盈方向利润空间小而亏损方向风险大,期望值为负;靠主网格中下部启动时,两侧空间更均衡。

4.2 激活方向判断与建仓模式选择

激活时,判断价格是从上方"落入"还是从下方"涨入"激活窗口:

激活时,按 d 空间判断价格是从止盈端方向"进入"还是从亏损端方向"涨/跌入"激活窗口(做多/做空镜像统一为:价格是否仍在向亏损方向移动 d(当前价格) > d(上次记录价格)):

价格进入方向 判断条件(d 空间) 建仓模式
朝亏损方向进入 d(当前价格) > d(上次记录价格)(或无历史记录) 追踪建仓模式
朝止盈方向进入 d(当前价格) ≤ d(上次记录价格) 直接运行模式:不需要追踪建仓,直接进入 RUNNING 状态

朝亏损方向进入意味着价格仍在向不利方向移动,贸然建仓可能建在途中;追踪建仓会等待反弹确认再开仓。朝止盈方向进入时亏损端已在身后,直接进入 RUNNING 状态(实现见 robot/decide-box-activation.ts,几何判定下沉 packages/shared-types/src/box-geometry.ts)。

4.3 异常退出恢复

若机器人因异常(程序崩溃、网络断开)退出但仍有未平仓位,重启后管理器会识别该状态,跳过追踪建仓阶段,直接以 RUNNING 状态接管现有持仓。

4.4 启动时的旧算法单清理

机器人每次启动时,必须先清理上一轮会话遗留的算法单,再挂入当前会话的新算法单。

原因:止损缓冲区的算法单由上一轮机器人实例在运行中挂出,若程序崩溃或异常退出,这些算法单仍留在交易所账户中。新一轮机器人启动后会重新计算并挂新算法单;若不先清理旧单,账户中会同时存在新旧两套算法单,触发价格重叠,导致重复止损或方向混乱。

清理范围:仅清理属于本策略管理的止损缓冲区算法单,不影响用户手动挂的其他条件单。

4.5 机器人退出后的处理

机器人退出原因及后续行为:

退出原因 处理方式
止损退出(StopLoss) 清理实例,回到监听状态,等待下一次激活条件
止盈退出(TakeProfit) 清理实例,回到监听状态
手动停止 清理实例
余额不足 暂停评估,等待余额补充

五、追踪建仓模式(机器人激活前)

与"追逐下单"的区别:

  • 追踪建仓(本章)是 BotManager 层的激活决策,发生在机器人尚未开始交易之前,解决的是"什么时候开始交易"的问题。
  • 追逐下单(第七章)是已激活的机器人在每次执行买卖时的下单策略,解决的是"以什么价格和方式挂单"的问题。

两者完全独立,互不干扰。

追踪建仓(Trailing Entry)是机器人在正式开始网格交易前的等待确认阶段。

5.1 算法逻辑

下面以做多为直觉表述,做空为镜像(极值取高点、反弹为向下回调、越界为涨破清算线);几何判定均在 d 空间统一实现。

初始化:
  极值价格 ExtremePrice = 进入时的当前价格(做多取低点 / 做空取高点)

每次收到价格更新:
  如果 d(当前价格) < activationDepth(价格已越过激活价、离止盈端更近):
    → 追踪窗口已关闭,退出 TRAILING_ENTRY,回到 BotManager 监听状态
       (当前位置期望值不再有利,等待下次重新满足激活条件)

  如果 当前价格朝亏损方向创新极值(做多更低 / 做空更高):
    → 更新 ExtremePrice = 当前价格(追踪亏损端极值)

  计算触发价 TriggerPrice = ExtremePrice × (1 ± TrailingCallbackRate)(做多 +,做空 −)

  如果 当前价格已朝止盈方向反弹 ≥ TriggerPrice:
    → 确认反弹!进入 RUNNING 状态,开始正式网格交易

  如果 d(当前价格) > boxDepth 且 StopLossGridCount > 0(越过清算线 liquidationPrice):
    → 直接进入 LIQUIDATING 状态(即使仓位为零,也走止损流程)

TrailingCallbackRate:反弹确认比例,例如 0.002 代表从极值低点反弹 0.2% 即确认。若配置为 0,默认取 0.2%。

5.2 直觉解释

追踪建仓的本质是"不预测最低点,只确认已经离开了最低点"。无论价格跌到哪里,只要开始反弹并超过设定幅度,就认为下跌阶段结束,可以安全建仓。

5.3 为什么朝止盈方向进入时直接启动、不追踪

当价格朝止盈方向进入激活窗口(做多:价格此前在更低处、现在上涨回来;做空镜像),系统会跳过追踪等待,直接进入 RUNNING 状态。

原因:用户预设的箱体就是用户认可的运营区间。当价格从亏损端更深处反向进入这个区间,说明入场成本比满仓线更靠近清算端——在这个位置建仓,朝止盈方向移动时每一格的平仓利润都比从区间内部建仓要高。追踪等待反而会错过这段额外收益。

对比:

  • 朝亏损方向进入 → 不知道极值在哪,需要等确认,否则可能一路建在不利途中
  • 朝止盈方向进入 → 亏损端已经在身后,当前价格是经过市场确认的相对有利位,直接入场期望值更优

六、主网格运行逻辑

6.1 目标仓位算法(d 空间索引)

主网格区被均分为 mainGridCount 个格子。索引在 d 空间从止盈端向亏损方向编号,Index 0 紧贴止盈线(gridIndex = ⌊d / mainGridStep⌋):

(沿亏损方向 d 递增;reduce 边朝止盈端、add 边朝亏损端)
Index 0: reduce 边 d = 0×mainGridStep(= takeProfitPrice),  add 边 d = 1×mainGridStep
Index 1: reduce 边 d = 1×mainGridStep,                        add 边 d = 2×mainGridStep
...
Index i: reduce 边 d = i×mainGridStep,                        add 边 d = (i+1)×mainGridStep

价格换算:price = toPrice(d)。做多 reduce=SELL/add=BUY,做空 reduce=BUY/add=SELL。
(索引↔价格映射见 packages/shared-types/src/box-geometry.ts 的 gridIndexToSellPrice/gridIndexToBuyPrice)

目标仓位计算规则(方向无关,以"离止盈端的距离"判断):

  • TargetHeldGrids:统计所有"reduce 边比当前价格更靠止盈端"的格子数量(即当前价格已朝亏损方向越过该格 reduce 边,应当持仓)
  • TargetBoughtGrids:统计所有"add 边比当前价格更靠止盈端"的格子数量(即当前价格已朝亏损方向越过该格 add 边,理论上应已建仓)
TargetHeldSize  = TargetHeldGrids  × mainGridPortionSize
TargetBoughtSize = TargetBoughtGrids × mainGridPortionSize

6.2 非对称缓冲决策

根据当前持仓 CurrentPosition 与目标值的差异,决定操作:

条件 动作 说明
CurrentPosition ≥ TargetHeldSize + ε 卖出,数量 = 差值 持仓多于应持,立即卖
CurrentPosition ≤ TargetBoughtSize - ε 买入,数量 = 差值 低于应买量,触发买入
其他(两者之间) 不动作 缓冲区内,等待价格移动

其中 ε 为最小下单量(交易所精度约束)。

非对称设计的意义:

  • 卖出使用"应持仓位"作为基准(更宽松,更容易触发卖出)
  • 买入使用"应买入仓位"作为基准(更严格,必须偏离超过一格才买)
  • 这让策略在价格上涨时更快卖出锁定利润,在价格下跌时更保守地买入,积累正向偏度

目标仓位算法的"状态无关"特性(自愈能力):

目标仓位完全由当前价格决定,不依赖任何历史状态。这不是设计副产品,而是针对分布式系统特性的主动设计:

本地程序与交易所是两个独立的网络节点,以下情况都会导致本地记录的仓位状态与交易所实际持仓不一致:

  • 网络抖动 / 超时:下单请求发出后不知道是否成交
  • 订单部分成交:委托 0.02 ETH,实际只成交 0.01 ETH,本地记录可能未及时同步剩余量
  • 高并发竞态:行情推送与订单回报同时到达,本地状态机未来得及处理其中一个
  • 程序崩溃重启:重启后本地状态清零,但交易所仍有残留持仓或挂单

状态无关算法完全绕过了这个问题:每次价格更新时,系统只问"当前价格下应该持有多少仓位",再与交易所实时查询到的实际持仓对比,直接纠偏——不需要追溯"之前发生了什么",也不需要维护本地订单历史。

这意味着:无论程序崩溃多久、网络断线几小时、甚至用户手动在交易所操作了仓位——只要程序重启并拿到当前价格,系统就能立刻算出应有仓位并开始纠偏,没有"状态脏了要手工清理"的运维负担。

配套约束——必须使用最新持仓数据下单:

上述自愈特性能成立,有一个不可缺少的前提:每次计算实际下单量时,必须使用从交易所收到的最新持仓信息,而非本地缓存的旧快照。

实际操作量 = 目标仓位(由当前价格推导)- 当前实际持仓(来自交易所最新推送)

持仓数据的更新策略采用主动 + 被动双保险:

  • 被动接收:每次收到交易所的持仓推送事件时,立即更新本地持仓记录
  • 主动拉取:每次网格订单成交(收到成交回报)时,主动向交易所 API 查询一次最新持仓,不依赖推送是否及时到达

主动拉取是必要的:成交回报与持仓推送是两条独立的 WebSocket 通道,持仓推送可能因网络抖动或交易所内部处理延迟而滞后于成交事件。如果仅依赖被动推送,系统在处理下一笔订单时可能读到成交前的旧持仓数据。

如果以旧的持仓数据计算下单量,可能出现:

  • 某笔买入刚刚成交,持仓已增加,但本地缓存未更新 → 再次买入造成超买
  • 某笔止损卖出刚刚触发,持仓已减少,但本地未感知 → 继续挂卖单造成超卖

简言之:目标仓位由价格决定(无状态推导),实际操作量由最新持仓决定(实时取用)。两者缺一不可,才能构成真正的自愈闭环。

6.3 止损区价格判断

如果当前价格已朝亏损方向越过满仓线 fullPositionPrice 进入隔离带/止损区(d > mainGridDepth),统一仓位计算函数 computeTargetPosition 自动按隔离带(满仓无操作)/止损区(递减减仓)规则给出目标仓位——不再需要主网格与止损区分别处理(见第八章统一止损机制)。


七、追逐下单:实时定价与订单追踪

7.1 概念说明

"追逐下单"是本策略中每次执行网格买卖时使用的下单机制,目标是:在满足网格基本利润的前提下,尽量以 Maker 身份成交(手续费率 0.02%,低于 Taker 的 0.05%);当价格偏离足够大时,主动以 Taker 方式锁定超额利润。

以做多买入为例,完整流程:

策略计算出需要在网格索引 X 处买入,理论网格底价 GridPrice = 2200

此时市场买一价(bestBid)= 2198(还没到 2200)

→ 立即在 2198 挂一个 Post-Only 买单(成为当前最优买盘,Maker 身份)
→ 若下一笔市价卖单进来,我们以 2198 成交,仅付 Maker 手续费(0.02%),且比理论价 2200 更优

市场继续下跌到 2197(变化了 0.5 USDT,超过 2197 × 0.02% ≈ 0.44 USDT 改单阈值):
→ 撤掉 2198 的单,在 2197 重新挂 Post-Only

市场大幅下跌到 2195.8(偏离 GridPrice 的 0.19% > GtcThreshold 0.1%):
→ 撤掉 2197 的单,改用 GTC 直接吃单(立即成交)
→ 成交价 ≈ 2197.8(GtcPrice),比 GridPrice 低了 2.2 USDT = 额外利润

这就是"追逐"——系统不等价格跌到 2200 才下单,而是在价格还在 2200 以下时就主动卡位,追着买一价反复预挂,力争以 Maker 身份成交或在大幅偏离时锁定超额利润。

7.2 下单价格的来源

关键细节:POC 单的下单价格 ≠ 网格理论价,而是锚定对手价向内一档(实现见 pricing.ts / strategy-engine.ts):

  • 买入时:min(GridPrice, bestAsk − tick) —— 以卖一价向内一档挂 Post-Only 单
  • 卖出时:max(GridPrice, bestBid + tick) —— 以买一价向外一档挂 Post-Only 单

这样设计有两个用意:其一,直接挂在 bestBid/bestAsk 上,价格微动就可能导致 Post-Only 越价被拒单,锚定对手价向内缩一档可避免;其二,当盘口价差(spread)大于一个 tick 时,该单位于盘口内部、成为全场最优盘口,下一笔市价单进来即以 Maker 身份成交,手续费率仅 0.02%(Taker 为 0.05%),每笔节省 0.03%。成交价不劣于、通常优于网格理论价(买入更低 / 卖出更高),构成额外收益。

注:§7.1 的示例为简化起见按零价差(bestAsk = bestBid + 1 tick)描述,此时锚定对手价向内一档即退化为挂在 bestBid 上。

7.3 三区间状态机(定价决策)

参数定义:

  • GridPrice:当前网格的理论买/卖价(由目标仓位算法确定)
  • GtcThreshold:触发 Taker 吃单的偏离比例(默认 0.1%)
  • GtcPrice(买入时)= GridPrice × (1 - GtcThreshold)
  • GtcPrice(卖出时)= GridPrice × (1 + GtcThreshold)

买入场景(以 GridPrice=2200, GtcThreshold=0.1% 为例,GtcPrice=2197.8):

当前价格 > 2200 (GridPrice):
  → 【熔断区】价格比目标买价贵,挂单无利润保障,拒绝下单
     等待价格自然回落后重新判断

2197.8 (GtcPrice) < 当前价格 ≤ 2200 (GridPrice):
  → 【POC 区】以当前买一价(bestBid)挂 Post-Only 单
     等待被动成交,手续费率仅 0.02%(Maker)

当前价格 ≤ 2197.8 (GtcPrice):
  → 【GTC 区】以市价附近主动吃单,立即成交
     实际下单价 = min(GtcPrice, 当前价格 × 1.02)
     额外利润 > Taker 手续费,合算

卖出场景(对称):

当前价格 < GridPrice:
  → 【熔断区】拒绝卖出

GridPrice ≤ 当前价格 < GtcPrice:
  → 【POC 区】以当前卖一价(bestAsk)挂 Post-Only 单

当前价格 ≥ GtcPrice:
  → 【GTC 区】主动吃单
     实际下单价 = max(GtcPrice, 当前价格 × 0.98)
区间 触发条件 下单方式 手续费影响 经济合理性
熔断区 价格方向不利 不下单 零 没有基本利润保障,不交易
POC 区 偏离 < GtcThreshold Post-Only,下在买一/卖一价 付 0.02%(低于 Taker 0.05%) 默认首选,以 Maker 身份卡位
GTC 区 偏离 ≥ GtcThreshold 市价吃单 Taker 付 0.05% 额外价差利润 > 比 Maker 多付的 0.03%,合算

7.4 追踪改单:何时撤单重挂

Post-Only 单挂出后,系统在每次收到价格更新时都会检查是否需要改单:

触发条件 A:订单类型升级(POC → GTC)

  • 当前挂的是 POC 单,但最新价格计算显示应进入 GTC 区
  • 立即撤单,重新以 GTC 方式吃单(GTC 机会稍纵即逝,优先执行)

触发条件 B:价格变化超过改单阈值

  • 新的 bestBid/bestAsk 与当前挂单价格的差距 > 当前价格 × 0.02%
  • 市场已移动,继续挂原价成交概率降低,撤单重挂在新价位以维持 Maker 竞争力
  • 方向不区分:价格向有利方向(继续下跌,买更便宜)或不利方向(回升,原位置竞争力下降)变化超过阈值,均触发改单。理由:无论哪个方向,原挂单价格已不再是当前最优 Maker 竞价点,改单的目的是保持最优买盘(卖盘)位置。(见 13.5 节:对两个方向设置不同阈值是一个潜在优化方向。)

触发条件 C(隐含):进入熔断区

  • 重新计算后发现价格不利(买入时价格涨过了 GridPrice)
  • 撤单,等待下次价格回落后重新判断

7.5 改单执行流程

1. 发送撤单请求(带原订单 ID)
2. 确认撤单后的实际剩余量:
   - 已完全成交(剩余量为零):无需重挂,结束
   - 剩余量低于交易所最小下单要求:放弃剩余量,结束
   - 其余情况:继续下一步
3. 以剩余量重新下单,下单时重新抓取当前最优市场价(确保使用最新行情,而非触发改单决策时的价格)
4. 如果当前行情已进入熔断区:放弃重挂,等待下次价格更新触发
5. 以剩余量 + 新最优价格,提交新订单,继续追踪

7.6 执行串行化

任意时刻,系统最多只有一个活跃的网格订单处于 Pending 状态:

状态 触发时机 系统行为
有活跃订单(系统正在等待某笔网格订单成交) 每次收到价格更新 只执行追踪改单逻辑(7.4 节),不重新计算目标仓位
无活跃订单 每次收到价格更新,或订单成交/撤销后立即触发 ① 主动拉取交易所最新持仓;② 重新计算目标仓位;③ 若持仓与目标有偏差则发起新的追逐下单

"无活跃订单"触发时机的说明:不能只依赖价格更新来驱动下一笔订单。订单刚成交时,持仓已变化,如果等到下一次价格 tick 才重新判断,可能因行情已移动而错过最优下单时机。因此订单成交/撤销事件本身也应直接触发一次"无活跃订单"的完整流程。

串行化确保了不会因为仓位计算时序问题产生重复下单或仓位冲突。

止损优先保障:所有订单提交(包括初次下单和改单重挂)均有止损优先机制:一旦系统进入止损流程(如价格越过清算线 liquidationPrice 触发清算,见 §9.2),立即放弃所有进行中的网格下单操作,确保止损触发后不会继续开新仓位。

7.7 为什么用等待挂单而非直接市价成交

疑问:Post-Only 单挂出后,如果价格反转上涨,岂不是错过了这次买入机会?

概率层面

价格已经到达这个位置,说明市场卖方已把价格推到了这里。在有足够流动性的合约市场里,买一价处的 Post-Only 单等同于当前最优买盘——下一笔市价卖单进来就会成交,1-2 秒内成交是极为普遍的情形。"挂上去没人来"的概率,远低于直觉估计。

此外,改单阈值(0.02%)保证了价格若继续向下(往有利方向),系统会持续追价保持 Maker 竞争力;只有价格反转超过熔断区,系统才会主动撤单——这是"有利坚持、不利止步"的主动机制,而非被动等待。

期望值层面

关键不在于具体填单率是多少,而在于 Taker 策略的基准净利润:

以 Gate.io 为例(makerFee=0.02%,takerFee=0.05%,网格步长≈0.1%):
注:以上均为向交易所缴纳的手续费,Maker 费率更低,但仍是支出而非收入。

Taker 双侧成交(100% 填单率):
  净利润 = 0.1%(步长)- 0.05% × 2(买卖手续费)= 0%
  手续费恰好吃尽步长利润,收支持平

Maker 双侧成交(设整个循环以 50% 概率完成):
  每次循环净利润 = 0.1%(步长)- 0.02% × 2(买卖手续费)= 0.06%
  期望利润 = 50% × 0.06% = 0.03% > Taker 的 0%

推广结论:只要循环完成概率 > 0%,Maker 策略的期望利润就高于 Taker——因为 Taker 每轮净利润本已为零,任何 Maker 填单都是正向收益。

附:推荐人返佣机制(额外优化):如果注册交易所账号时填写了推荐人邀请码,推荐人可代为向交易所申请将已缴手续费的约 20% 以返还形式退回给用户,进一步降低实际费率(Maker 有效费率 ≈ 0.016%,Taker ≈ 0.04%),属于独立于策略逻辑的成本优化,条件允许时应当充分利用。

与 GTC 区的互补:当价格跌离 GridPrice 超过 GtcThreshold(0.1%),额外价差利润已超过改用 Taker 的额外成本,系统会主动切换为 GTC 立即成交——"利润足够大时直接市价"是内建逻辑,而非需要外部判断的特例。Maker 负责低成本常规成交,GTC 负责大偏差时的超额利润,两者互补,覆盖全部有利场景。

7.8 GTC 触发阈值的经济模型

GTC 阈值默认 0.1%,其背后经济含义:

使用 GTC 合算的最低条件:
  额外价差利润 > 改用 Taker 而多付的手续费

  额外利润(买入时)= (GridPrice - 当前价格) / 当前价格
  改用 Taker 多付的手续费 = takerFeeRate - makerFeeRate(仅本笔订单的费率差)

  合算条件:(GridPrice - 当前价格) / 当前价格 > takerFeeRate - makerFeeRate

以 Gate.io 为例(takerFee=0.05%,makerFee=0.02%):
  最小合算阈值 = 0.05% - 0.02% = 0.03%
  当前配置 0.1% ≈ 最小阈值的 3.3 倍(留有充足余量,保守合理)

配置参数 ExcessProfitMultiplier(默认 2.0)的含义:
  GtcThreshold = ExcessProfitMultiplier × takerFeeRate
  当 takerFeeRate=0.05%, Multiplier=2.0 → GtcThreshold = 0.1%(与当前硬编码一致)
  注:此公式以 takerFee 为基准乘以倍数,结果高于最小阈值(0.03%),是有意保留余量的保守设定

实现现状(2026-08 勘正):阈值函数 effectiveGtcThreshold(pricing.ts)已实现——设计上的生效阈值 = max(配置阈值, ExcessProfitMultiplier × takerFeeRate)。但配置链路尚未接通:Box 表无 gtcThreshold 列(恒取默认 0.001),runner 注入策略配置时也未传入 ExcessProfitMultiplier / takerFeeRate——当前运行时生效阈值恒为默认 0.1%。参数联动接线见 §13.1。


八、统一止损机制

设计原则(2026-06-02):GridPilot 采用统一的仓位计算函数来决定所有买卖操作。止损区不再需要特殊的条件单维护逻辑,而是使用与主网格区相同的方式动态下单。

8.1 统一仓位计算原则

系统的核心函数 computeTargetPosition(price, config)(实现于 packages/shared-types/src/box-geometry.ts,d 空间单实现)适用于箱体内所有区域:主网格区、隔离区、止损区。

// 输入(几何配置 + 每格下单量)
interface BoxTargetConfig {
  takeProfitPrice: number;        // 止盈端锚点
  direction: 'LONG' | 'SHORT';
  mainGridCount: number;
  mainGridStep: number;
  mainGridPortionSize: number;    // 每格下单量
  stopLossGridCount: number;
  stopLossGridStep: number;
  isolationStep: number;          // 运行时约定 = stopLossGridStep
}

// 输出
interface BoxTargetPosition {
  targetBoughtSize: number;  // 目标买入仓位(下界)
  targetHoldSize: number;    // 目标持有仓位(上界)
  gridIndex: number;         // 当前所在格子索引
  zone: BoxZone;             // 当前所在区域
  lines: BoxLines;           // 四条结构线 + boxHighPrice/boxLowPrice(供显示使用)
}

type BoxZone = 'MAIN' | 'ISOLATION' | 'STOP_LOSS' | 'PROFIT_EXIT' | 'LOSS_EXIT';

各区域行为(按 d 空间表述,方向无关):

区域(zone) d 范围 targetHoldSize 说明
PROFIT_EXIT d ≤ 0(越过止盈线) 0 已越过止盈端,清仓退出
MAIN 0 < d < mainGridDepth(止盈线~满仓线) 根据格子数计算 正常网格交易
ISOLATION mainGridDepth ≤ d ≤ isolationEndDepth(满仓线~止损区起点) baseSize(满仓) 防止边界频繁交易
STOP_LOSS isolationEndDepth < d < boxDepth(止损区起点~清算线) 递减计算 越靠清算端目标仓位越小
LOSS_EXIT d ≥ boxDepth(越过清算线) 0 完全清仓(stopLossGridCount = 0 时见 §8.4)

关键特性:所有区域的计算逻辑都由 computeTargetPosition 封装,无需在止损区使用特殊处理。PROFIT_EXIT/LOSS_EXIT 按"越过止盈线还是清算线"归类,方向无关(旧 ABOVE_BOX/BELOW_BOX 退役)。

8.2 下单决策逻辑

在所有区域(包括止损区),下单决策使用完全相同的逻辑:

// StrategyEngine.computeDesiredOrders()
if (actualHeld >= targetHoldSize + ε) {
  → SELL(减仓)
} else if (actualHeld <= targetBoughtSize - ε) {
  → BUY(补仓)
} else {
  → HOLD
}

止损区的行为:

  • 当价格在止损区内下跌时,targetHoldSize 递减
  • 若 actualHeld >= targetHoldSize + ε,触发 SELL 订单
  • 价格每下跌一个止损格子,触发一次减仓
  • 无需特殊的 ping-pong 翻转机制

SHORT 方向对称:做空时止盈线在低价端、止损区在高价端(清算线为最高价),价格上升(朝亏损方向)进入止损区时触发 BUY 减仓。由于全部逻辑在 d 空间单实现,做空数值自动镜像做多,无需单独分支。

8.3 兜底止损单

为了防止极端情况下动态下单失败(网络中断、交易所故障等),在清算线 liquidationPrice 处设置一个条件单作为最后防线。

属性 LONG 方向 SHORT 方向
位置 liquidationPrice(清算线,箱体最低价) liquidationPrice(清算线,箱体最高价)
触发条件 price_below(价格跌破清算线) price_above(价格涨破清算线)
行为 closePosition = true closePosition = true
数量 完全平仓 完全平仓

目的:作为安全网,确保即使动态下单系统失效,也能在价格越过清算线时执行平仓。

注意:兜底止损单只在清算线挂一个,不需要在止损区内挂多个条件单。止损区内的减仓主要通过动态下单完成。

8.4 stopLossGridCount = 0 时的无止损模式

当 stopLossGridCount 配置为 0 时:

  • 系统不设置止损区(不存在止损区起点 ~ 清算线区间)
  • 价格朝亏损方向越过箱体(d ≥ boxDepth,LOSS_EXIT 区)时,保持满仓硬扛——不动态清仓、不触发清算(targetBoughtSize = targetHoldSize = baseSize,见 computeTargetPosition 无止损分支)
  • 主网格的价格熔断逻辑仍然有效

行为说明:旧文档曾描述此模式下"越界 targetHoldSize = 0 动态清仓",与现行代码不符。现行实现按"无止损 = 硬扛满仓"语义为准(设计文档 2026-06-10-short-box-geometry-design.md §3 有意保留的现行为)。

适用场景:用户对当前价格区间有较强信心,愿意承担价格短暂越出箱体并继续持有满仓的风险。


九、止损与反弹恢复状态机

网格机器人的核心交易状态用有限状态机(FSM)管理。与 futures-grid-go 对齐的核心交易态有五个(见 9.1);NestJS 运行时实现 BotFsmState 在此之上另有三个运维 / 边界态 PAUSED / CANCELLED / HOLD(见 9.1 末)。

9.1 状态定义(核心交易态权威:futures-grid-go/internal/core/fsm_types.go,五态)

状态名称 含义
TRAILING_ENTRY 追踪建仓等待阶段(价格从上方进入区间时)
RUNNING 正常网格交易运行阶段
LIQUIDATING 止损触发,正在紧急平仓
LIQUIDATED 平仓完成,立即触发 Bot 退出,由 BotManager 负责后续重新激活
TAKE_PROFIT 价格到达止盈端(d ≤ 0,越过 takeProfitPrice)且持仓为零,止盈退出(进入即退出,与 LIQUIDATED 同为终态,不再响应后续 tick)

运行时另有三个运维 / 边界态(BotFsmState,超出 Go 核心五态):

状态名称 含义
PAUSED 用户暂停(USER_PAUSE)或永久错误暂停(PERMANENT_ERROR);可经 USER_RESUME 复位回 RUNNING(暂停后再启动走 startBot 复位 Run.state,而非新建 run)
CANCELLED 追踪窗口关闭(TRAILING_ENTRY 下 d < activationDepth 越过激活价)时未建仓即退出
HOLD 启动时检测到继承持仓,待激活

9.2 状态转换规则

转换条件均按 d 空间表述(方向无关)。d(price) = 价格离止盈端的有向距离,亏损方向为正;mainGridDepth = 满仓线深度,boxDepth = 清算线深度。

TRAILING_ENTRY ──[价格朝止盈方向反弹 ≥ TrailingCallbackRate 且 d(price) ≥ activationDepth]──► RUNNING
TRAILING_ENTRY ──[d(price) < activationDepth(越过激活价、更靠止盈端)]──────────────────────► CANCELLED(退出,回到 BotManager 监听)
TRAILING_ENTRY ──[d(price) > boxDepth(越过清算线)且 stopLossGridCount > 0]────────────────► LIQUIDATING

RUNNING ──[d(price) > boxDepth(越过清算线 liquidationPrice)且 stopLossGridCount > 0]───────► LIQUIDATING
RUNNING ──[d(price) ≤ 0(到达止盈线 takeProfitPrice)且持仓为零]──────────────────────────────► TAKE_PROFIT

LIQUIDATING ──[持仓 = 0]────────────────────────────────────────────────► LIQUIDATED

LIQUIDATED ──[立即]─────────────────────────────────────────────────────► Bot 退出
TAKE_PROFIT ──[立即]────────────────────────────────────────────────────► Bot 退出(终态,不再响应后续 tick;与 LIQUIDATED 同为退出终态)

RUNNING / TRAILING_ENTRY ──[USER_PAUSE 或 PERMANENT_ERROR]──► PAUSED
PAUSED ──[USER_RESUME]──► RUNNING(runner 保活,仅复位 Run.state;按持仓恢复到对应交易态)

LIQUIDATING 触发线说明:清算(整体平仓)在价格越过清算线 liquidationPrice(d > boxDepth,且配置了止损区)时才触发,与 §8 "统一止损机制"一致——止损区内(fullPositionPrice ~ liquidationPrice)由 §8.2 的统一仓位计算逐格生成 SELL 订单减仓,只有在动态减仓仍未能清空、价格已经越过清算线时,才由 FSM 强制整体平仓(兜底条件单,§8.3,同样挂在清算线)。

勘误(2026-07-26):2026-05-31 一次"字段改名"重构(e200380,把 boxLow/boxHigh 改名为 mainGridTop/mainGridBottom)误把触发阈值从 boxDepth(清算线)替换成了 mainGridDepth(满仓线,止损区的起点而非终点),导致价格一进入隔离区/止损区就立即整体清仓,止损区的逐格减仓逻辑在运行时从未被真正执行到。2026-06-10 的 d 空间重构发现了这个"文档与代码不符",但错误地把文档改成了迁就代码(即本节之前的版本),而非查明代码本身是回归。现已定位并修复代码(bot-fsm.ts / bot-state-reconstructor.ts),本节描述的是修复后、也是最初设计的行为。2026-06-10-short-box-geometry-design.md §4.2 同步修正。

9.3 各状态的行为

TRAILING_ENTRY 状态:

  • 每次价格更新时追踪极值低点
  • 计算是否已触发反弹确认条件
  • 不下任何订单,纯观察等待

RUNNING 状态:

  • 每次价格更新:调用目标仓位算法,生成网格买卖指令
  • 每个 tick 检查并补齐清算线兜底条件单(ensureEmergencyStopLoss;实际频率高于此前建议的 1-2 分钟间隔,见 §13.4)
  • 监测清算触发条件(价格越过清算线 liquidationPrice,d > boxDepth 且 stopLossGridCount > 0)

LIQUIDATING 状态:

  • 每次价格更新:若持仓 > 0,立即以市价全平(CancelAll + MarketClose)
  • 强制中断所有正在进行的网格委托操作
  • 持续检测持仓是否已归零

LIQUIDATED 状态:

  • 持仓已归零,立即触发 Bot 退出,不进入任何等待或观察流程
  • BotManager 接收到退出通知后清理当前机器人实例,回到空闲监听状态
  • 若价格回到激活窗口(activationDepth < d(价格) < boxDepth,做多即 liquidationPrice < 价格 < activationPrice),BotManager 将重新创建新的机器人实例开始下一轮交易
  • 每次止损后的重新入场均经历完整的激活→追踪建仓流程,不自动续仓

9.4 止盈的设计哲学与主动用法

止盈的工程实现:止盈条件为"价格到达止盈端(d ≤ 0,越过 takeProfitPrice)且持仓已为零"。这不是一个硬止盈(强制平仓),而是让网格自然做完——随着价格朝止盈方向移动,网格持续触发平仓,仓位逐步清空;当最后一格平掉后价格已越过止盈线,此时 BotManager 识别到机器人任务自然完成,触发退出。

含义:策略会把朝止盈方向移动过程中每一格的利润都赚到,不提前放弃任何已在区间内的利润空间。

主动止盈策略:如果用户预判价格即将反转,可以通过调整 takeProfitPrice 使止盈线更靠近当前价来主动提前止盈(做多调低止盈线、做空调高止盈线):

做多原始配置:takeProfitPrice = 3400
用户预判 3000 附近可能出现高位风险

主动操作:重新配置 takeProfitPrice = 3000
→ 网格在 3000 附近平掉最后一份仓位后,系统自然止盈退出
→ 规避了 3000 以上价格可能的暴跌风险
→ 相当于把止盈线作为"动态止盈线"使用

这一用法的本质是以箱体配置代替传统止盈单,既不需要额外的止盈逻辑,又能精确控制最终退出价格。

9.5 止损后的仓位清理顺序

进入 LIQUIDATING 后的操作顺序:

  1. 取消所有未成交的网格挂单(包括 POC / GTC 挂单)
  2. 取消所有止损缓冲区的算法单(条件单)
  3. 市价全平持仓(ClosePosition 市价单)
  4. 确认交易所回报持仓已归零
  5. 进入 LIQUIDATED 状态

十、配置参数完整说明

10.1 每个区间的配置项

字段名以当前实现为准(活跃箱体增改入口为 /trading-engine/robots/{id}/boxes,交叉核对 bot-manager.service.ts 的 AddBoxInput/EditBoxInput)。箱体不再输入上下边界,而是输入止盈端锚点 + 格数步长,boxHighPrice/boxLowPrice 由几何核心推导(见 §三)。

参数名 类型 说明
credentialId string 交易所凭据 ID
symbol string 交易对(如 ETH_USDT)
direction enum 方向:LONG(做多)或 SHORT(做空)
takeProfitPrice float 止盈端锚点价(做多为高价端,做空为低价端);箱体由此向亏损方向展开
mainGridCount int 主网格区的格子总数
mainGridStep float 主网格每格步长(USDT)
mainGridPortionSize float 每个格子的下单量(以币为单位,如 0.02 ETH)
leverage int 杠杆倍数(如 20)
stopLossGridCount int 止损区格子数(0 = 无止损区,见 §8.4 硬扛模式)
stopLossGridStep float 止损区每格步长(USDT)
isolationStep float 隔离带宽度(满仓线 → 止损区起点的缓冲距离,USDT);0 = 无隔离带。可独立设置;缺省/历史箱体回退 = stopLossGridStep。启用止损(stopLossGridCount > 0)时须 > 0
activationPrice float 区间激活价格(0/省略 = 使用主网格中点 d = mainGridDepth / 2);校验要求落在主网格内 0 < d(activationPrice) < mainGridDepth
trailingCallbackRate float 追踪建仓回调比例(如 0.002 = 0.2%;0/未设置 = 默认 0.2%;是否启用追踪由 trailingEntry 字段控制)
excessProfitMultiplier float 超额利润倍数(默认 2.0);设计上与 takerFeeRate 共同决定 GtcThreshold:GtcThreshold = excessProfitMultiplier × takerFeeRate。当前未接入决策链(配置链路未注入,见 §13.1),且箱体增改 API 暂未暴露该字段,实际恒取 DB 默认值 2.0。
reorderThreshold float 改单(撤单重挂)阈值(默认 0.0002 = 0.02%);盘口价变动超过 当前价格 × reorderThreshold 时触发改单。已为 Box 配置字段并接入决策链;箱体增改 API 暂未暴露该字段,经 API 创建的箱体恒取默认值(见 §13.2)。

派生量(非输入项):fullPositionPrice / stopLossStartPrice / liquidationPrice / boxHighPrice / boxLowPrice 均由 takeProfitPrice + 格数步长经 deriveBoxLines 推导(见 §三)。isolationStep 自 2026-06-16 起为独立可设字段(此前恒等于 stopLossGridStep);缺省与历史箱体(值为 null)按 isolationStep ?? stopLossGridStep 回退,几何向后兼容。

10.2 典型配置示例

[
  {
    "symbol": "ETH_USDT",
    "direction": "LONG",
    "takeProfitPrice": 3400,
    "mainGridCount": 235,
    "mainGridStep": 2.5,
    "mainGridPortionSize": 0.02,
    "leverage": 20,
    "stopLossGridCount": 4,
    "stopLossGridStep": 2.5,
    "activationPrice": 0,
    "trailingCallbackRate": 0.002
  },
  {
    "symbol": "ETH_USDT",
    "direction": "LONG",
    "takeProfitPrice": 2800,
    "mainGridCount": 235,
    "mainGridStep": 2.5,
    "mainGridPortionSize": 0.05,
    "leverage": 20,
    "stopLossGridCount": 4,
    "stopLossGridStep": 2.5,
    "activationPrice": 0,
    "trailingCallbackRate": 0.002
  }
]

上例解读(以第二条为例,做多,止盈线在高价端):

  • 止盈线 takeProfitPrice = 2800(d = 0)
  • 主网格深度 mainGridDepth = 235 × 2.5 = 587.5 → 满仓线 fullPositionPrice = 2800 − 587.5 = 2212.5
  • 隔离带宽度 isolationStep(示例未单独设置,缺省回退 stopLossGridStep = 2.5)→ 止损区起点 stopLossStartPrice = 2212.5 − 2.5 = 2210
  • 止损区深度 = 4 × 2.5 = 10 → 清算线 liquidationPrice = 2210 − 10 = 2200
  • 派生绝对边界:boxHighPrice = 2800,boxLowPrice = 2200
  • 每格下单:0.05 ETH;满仓持仓:235 × 0.05 = 11.75 ETH
  • 激活价(默认主网格中点 d = mainGridDepth/2):2800 − 587.5/2 ≈ 2506 USDT(价格朝亏损方向越过此值、且在清算线之上才激活)

十一、参数选择方法论

本章说明各核心参数的选择逻辑与权衡依据,是参数表的"理由层"。实施方应理解这些依据,而不是机械套用示例数值。

11.1 交易标的选择:为什么是 ETH

本策略的收益来源依赖三件事:价格在区间内来回震荡、每次震荡的波动足够覆盖手续费和步长、订单簿的价格跳跃为追逐下单提供超额利润。ETH 在这三点上的综合表现优于其他主流合约品种:

考量维度 ETH 的特点 对策略的影响
波动率 相同时间窗口内波动幅度高于 BTC 网格来回触发次数更多,日利润更高
区间可预测性 历史上多次在较明确的技术区间内长期震荡 更容易识别有效箱体边界,策略存活周期更长
Gate.io 价格行为 价格跳跃频率高于 Binance 追逐下单机制中 GTC 区超额利润捕获机会更多
合约精度 合约乘数和最小下单量使得小资金也能精细划格 可以以较少资金实现密集网格划分

BTC 不选的主要原因:波动率相对较低,单位时间内网格触发次数少,同等资金的日收益率低于 ETH;同时 BTC 在 Gate.io 的价格跳跃特性相对不如 ETH 显著。

11.1.1 交易所选择:为什么是 Gate.io

结论先行:Gate.io 看似流动性更差的交易所,在本策略下的实测收益比 Binance 高出约 60%。这是通过 A/B 测试不同交易所效果数据时的意外发现,但背后有清晰的机制解释。

两个交易所的对比特征:

Binance Gate.io
买卖价差 极小(0.01-0.05 USDT) 较大(0.1-0.5 USDT)
价格行为 平滑,很少大幅跳跃 频繁跳跃,瞬间跳 5-10 USDT
订单簿深度 极好,大单冲击小 较薄,大单容易造成价格偏移

为什么"更差"反而"更赚":

本策略的追逐下单机制专门为捕捉"价格偏离网格底价"的机会而设计:

  • 偏离幅度 > GtcThreshold → 触发 GTC 吃单,捕获超额利润
  • Binance 价格平滑 → GTC 区很少被触发,大部分时间在 POC 区以 Maker 低费率成交(稳定但有限)
  • Gate.io 价格频繁跳跃 → GTC 区频繁触发,每次跳跃都是一次额外利润机会

关键洞察:Gate.io 的价格"混乱"不是风险,而是本策略的燃料——订单簿越混乱,价格跳跃越大,捕获的超额利润就越多。这是一个与传统策略完全相反的逻辑(传统策略希望流动性好、滑点小)。

交易所选择原则:优先选择在目标品种上价格跳跃明显、Maker 手续费率较低的交易所,而非简单追求流动性最好的交易所。

11.2 箱体范围选择:2-3 个月视野

核心原则:箱体上下边界应能容纳接下来 2-3 个月内 ETH 大概率的价格波动范围,使得在这段时间内,价格刺破边界的概率较小。

技术分析维度(综合判断,无绝对标准):

  1. 历史支撑/阻力位:多次测试但未被有效突破的价格区域(被市场"记住"的关键位)
  2. 成交量密集区(VPVR):历史成交量高度集中的价格带,往往构成较强支撑
  3. 整数关口 + 宏观周期高低点:2000 / 2500 / 3000 等整数位,以及上一轮牛熊周期的关键价格
  4. 动态均线支撑:MA/EMA 等中长期均线形成的动态支撑区

范围大小的权衡:

箱体范围 优势 劣势
过大(如跨越 2000 USDT) 刺破概率低,策略存活时间长 满仓保证金 = mainGridPortionSize × mainGridCount × 价格,资金需求大
过小(如仅 200 USDT) 所需资金少 价格在边界附近震荡时,频繁触发止损-建仓循环,手续费损耗高
推荐(约 500-800 USDT) 覆盖 ETH 约 2-3 个月的典型波动幅度 需要定期(每 1-2 个月)根据市况重新评估是否调整

调整时机:当宏观趋势发生明显变化(如进入单边牛/熊市),或已确认某个边界被有效突破,应重新划定箱体而不是延伸旧箱体。

11.3 网格步长选择:价格的千分之一

来源:通过反复 A/B 测试和调参对比得出——步长约等于当前价格的 1/1000 时,日收益率最优。

参考公式:mainGridStep ≈ 当前价格 × 0.001

示例:ETH 价格在 2400 附近 → 推荐步长约 2.4 ≈ 2.5 USDT(精确到 TickSize)
       ETH 价格在 3000 附近 → 推荐步长约 3.0 USDT

背后的权衡逻辑:

步长过小(< 0.05%) 步长过大(> 0.3%)
每笔利润微薄,即使以 Maker 低费率成交,手续费也占据利润的大部分,净收益极低 单日触发的网格次数少,资金利用率低
增加改单频率,API 调用成本上升 等待成交时间长,资金大量闲置
极细步长在波动率低时根本无法成交 一次大幅价格变动可能跨越多格,但每格利润也相应增大

计算 mainGridCount:箱体几何由止盈端锚点向亏损方向展开,主网格深度 = mainGridCount × mainGridStep。先按 1/1000 选定 mainGridStep,再按期望的主网格价格跨度反推格数:mainGridCount = 主网格价格跨度 / mainGridStep(止损区与隔离带是在主网格之外、沿亏损方向额外叠加的深度,不占用主网格格数)。

11.4 资金分配框架:多区间共享账户

本策略通常配置多个不重叠的价格区间,共用同一个交易账户。资金分配规则:

每个区间分配资金 = 账户总余额 / 区间配置数量

各区间的 mainGridPortionSize 根据分配资金反推:
  满仓保证金 = mainGridCount × mainGridPortionSize × 当前价格 / leverage
  满仓保证金 ≤ 该区间分配资金 × (1 - 保证金安全余量)

  建议保证金安全余量 ≥ 20%(防止因浮亏导致保证金率不足)

示例:账户余额 10000 USDT,配置 2 个区间,每区间分配 5000 USDT:

区间(箱体 2200-2800,mainGridCount=235,leverage=20):
  满仓保证金 = 235 × mainGridPortionSize × 2500 / 20 = 29375 × mainGridPortionSize
  要求: 29375 × mainGridPortionSize ≤ 5000 × 80% = 4000 USDT
  → mainGridPortionSize ≤ 0.136 ETH/格
  → 取整到最小 StepSize 后选 0.05 或 0.1 ETH,留有足够余量

杠杆倍数:通常选 20 倍。杠杆的作用是放大资金使用效率,但不改变止损逻辑。止损缓冲区深度应覆盖最坏情况下的价格波动,与杠杆倍数无直接耦合——杠杆越高,等量资金能撑的名义持仓越大,但爆仓风险也相应提高,需要保持足够的保证金余量。

11.5 止损缓冲区深度的选择

止损区(stopLossGridCount × stopLossGridStep)的深度代表"允许价格临时朝亏损方向越出主网格多远而不触发完全平仓"。深度越大,保留仓位反弹的能力越强,但触发完全止损前可能积累更大的浮亏。

实用参考:

  • 止损缓冲区总深度建议约为箱体总范围的 3-5%,且不超过 1/5
  • 每个子格步长建议与主网格步长相同(保持数学体系一致),子格数量 4-6 个
  • 在 ETH 箱体为 600 USDT 的场景下:10 USDT 深度(4 格 × 2.5 USDT)≈ 1.7%,属于保守配置

十二、关键约束与边界条件

12.1 区间不可重叠

多个配置的 boxLowPrice ~ boxHighPrice(由各自 takeProfitPrice + 深度推导的绝对边界)区间必须完全不重叠。若有重叠,系统应在启动时报错并拒绝运行(重叠校验见 robot/validate-box-addition.ts,按 boxHighPrice/boxLowPrice 判断)。

12.2 同一时刻只有一个活跃机器人

BotManager 必须保证串行激活,当前机器人未完全退出前,不得启动下一个机器人。

12.3 订单大小精度约束

  • mainGridPortionSize 必须符合交易所最小下单量 MinQty
  • mainGridPortionSize 必须是交易所步长 StepSize 的整数倍
  • 合约交易所(如 Gate.io)以"合约数"而非"币数"为单位,需要转换: 合约数 = mainGridPortionSize(币数) / 合约乘数(QuantoMultiplier)

12.4 GTC 触发的经济合理性

GTC 吃单决策的隐含逻辑:只有当价格偏离超过 GtcThreshold 时,额外价差利润才大于改用 Taker 而多付的手续费。

最小合算阈值 = takerFeeRate - makerFeeRate
Gate.io 示例:0.05% - 0.02% = 0.03%

GtcThreshold 应显著高于最小阈值以留有安全余量,建议范围 0.05% ~ 0.3%,当前默认值 0.1% ≈ 最小阈值的 3.3 倍,属于保守合理配置。具体值应根据所用交易所的手续费率调整。

12.5 止损后的重新激活机制

触发止损后,Bot 退出(LIQUIDATED → 止损退出),系统不会自动续仓,而是由 BotManager 按正常激活流程重新评估:

  • 止损退出后系统回到"未激活"状态,停止一切交易
  • 只有当价格重新进入激活窗口(activationDepth < d(价格) < boxDepth,做多即 liquidationPrice < 价格 < activationPrice)时,BotManager 才会重新激活
  • 重新激活后若价格从上方进入,走追踪建仓流程,确认反弹后才正式建仓
  • 这一机制天然形成"冷静期":价格需先回到激活区间,再经历一次反弹确认,才能重新入场,避免在仍处于下跌通道时立即重新建仓

实现含义:重新激活的等待时间完全取决于市场价格何时重新满足激活条件,没有固定的等待时长——如果市场持续下跌,系统就持续等待;如果价格迅速反弹,系统就迅速重新评估激活。

12.6 余额保护

管理器应定期(建议每 10 分钟)查询账户 USDT 余额:

  • 若余额低于最低安全线(如 10 USDT),立即停止当前机器人并暂停新机器人激活
  • 目的:防止保证金耗尽后仍持续开仓导致强制平仓(爆仓)

12.7 持仓模式要求

  • 持仓模式必须设置为单向持仓(One-Way),不支持双向持仓模式
  • 保证金模式必须设置为全仓模式(Cross Margin),不支持逐仓

十三、优化空间分析

以下是在深入理解当前策略实现后,发现的几处有优化价值的方向。各项互相独立,可按优先级逐步实施。

13.1 【优先级:高】GtcThreshold 应由参数驱动,而非硬编码

现状:GTC 触发阈值运行时恒为默认 0.001(0.1%)。阈值函数 effectiveGtcThreshold(pricing.ts)已实现(生效阈值 = max(配置阈值, ExcessProfitMultiplier × takerFeeRate)),但配置链路未接通:Box 表无 gtcThreshold 列,runner 注入策略配置时未传入 ExcessProfitMultiplier / takerFeeRate,配置字段 ExcessProfitMultiplier 实际为死配置。

问题:切换交易所或升级 VIP 费率档位时,需要修改代码而非配置文件。

建议实现:

GtcThreshold = ExcessProfitMultiplier × takerFeeRate

其中 takerFeeRate 按交易所和账户等级配置:
  Gate.io 普通用户: 0.05%
  Gate.io VIP1:     0.04%
  Binance 普通用户: 0.05%
  ...

将 takerFeeRate 作为交易所适配层的参数,由适配层注入;ExcessProfitMultiplier 保留为策略参数。

13.2 【已完成:参数化】改单阈值可配置

现状:reorderThreshold 已为 Box 配置字段(默认 0.0002 = 0.02%)并接入决策链(buildBotConfig → StrategyEngine / runner)。

残留:箱体增改 API(AddBoxDto / EditBoxDto)暂未暴露该字段,经 API 创建的箱体恒取 schema 默认值。

后续优化方向:Gate.io 价格跳跃频繁(每次 0.1-0.5 USDT),0.02% 在高波动时段会触发非常频繁的撤单重挂。可考虑按交易所/品种调优默认值,或按当前 ATR 自适应。

13.3 【优先级:中】止盈区间触发逻辑缺失

现状:止盈触发条件为"价格到达止盈端(d ≤ 0,越过 takeProfitPrice)且 当前持仓为零"。

问题:正常网格运行时,价格朝止盈方向移动到 takeProfitPrice 附近,网格会持续平仓,仓位减少到零后价格已越过止盈线。但如果价格快速跳过止盈线(跳空),可能存在还有仓位但价格已越过止盈端的情况——此时系统不会触发止盈退出,仓位将悬空。

建议:增加超时机制,当价格持续越过 takeProfitPrice 超过 N 分钟时,即使仓位不为零也触发止盈流程(先平仓再退出)。

13.4 【已实现】算法单哨兵的刷新频率

现状:清算线兜底条件单由 runner 在每个 tick(默认轮询 10 秒)且有持仓时检查并补齐(ensureEmergencyStopLoss),启动主网格时也会检查一次——实际频率高于此前建议的 1-2 分钟间隔。

勘正(2026-08):本条此前引用的"8.5 节"并不存在(第八章仅 §8.1–§8.4),相关引用已从 §9.3 移除;§8.3"仅清算线一个兜底条件单"即当前设计。

背景说明:兜底单的主要维护依赖 WebSocket 实时事件,每 tick 哨兵为兜底机制。在止损缓冲区较浅的配置下,高频检查可避免价格在哨兵发现遗漏前已穿越多个止损格。

13.5 【优先级:低】改单时的价格方向感知

现状:改单阈值只看价格变化的绝对幅度,不区分价格是往有利方向还是不利方向移动。

潜在优化:对于买单,如果价格变化方向是向上(远离 GridPrice,进入熔断风险),应该降低改单阈值(更快响应),甚至直接取消;如果是向下(向 GTC 区靠近),可以适当宽松阈值(不需要急着追)。

这样可以减少"价格短暂上涨又回落"时不必要的改单次数。

13.6 【优先级:低】已是 GTC 吃单时应跳过改单逻辑

现状:追踪改单逻辑不区分当前活跃订单是 Maker 挂单(POC)还是 Taker 吃单(GTC)。

问题:若某笔 GTC 吃单因深度或时延原因未在提交后立即成交,下次价格更新时系统可能仍检测到当前行情处于 GTC 区,从而触发"撤单重挂 GTC"——这是一次无效操作。

建议:系统应在追踪活跃订单时记录其下单类型(Maker 挂单或 Taker 吃单)。当活跃订单已是 GTC 且当前行情仍处于 GTC 区时,跳过改单,等待成交或直至行情脱离 GTC 区。

13.7 【优先级:低】GTC 下单价格保护边界

现状:GTC 吃单时的实际下单价格在 GtcPrice 附近,并设有一个固定比例的价格保护边界(当前约 2%)。

问题:固定保护边界在极端行情(如闪崩后价格已跌去 5%)下可能仍会以较差价位成交。

建议:GTC 吃单的保护上限应不超过 GridPrice——无论如何,买入价格不应高于网格底价。可将 GridPrice 作为硬性上界,叠加比例保护边界,取其中更低者作为实际下单价。

2026-08 核查:GtcPrice = GridPrice × (1 ∓ GtcThreshold) 恒小于(买入)/ 大于(卖出)GridPrice,且实际下单价 = min(GtcPrice, 当前价 × 1.02)(买入时),因此「买入不超过 GridPrice」的硬性上界已隐含满足;本条仅余固定 2% 比例边界的调优空间。


十四、AI Agent 增强方向

本章说明 AI Agent 在本策略中确定性有价值的介入场景,以及明确不适合介入的场景。判断标准:只有当 AI 介入的错误成本可控(非实时干预交易),且 AI 完成质量显著优于纯人工时,才纳入规划。

14.1 箱体配置辅助(计划实现)

问题根源:第 11.2 节的箱体边界选择目前依赖人工读图判断,每 1-2 个月需要重做一次。人工分析容易受近期价格走势影响(锚定效应),且难以系统覆盖所有时间维度的数据。

AI 的角色:基于历史 K 线和成交量数据,自动计算并推荐下一轮箱体配置,供人工审核确认后再生效。

AI 的分析内容:

  • VPVR(成交量分布):统计各价格区间的历史成交量密度,识别成交量集中的支撑/阻力带
  • 历史触及次数:统计各价格位在过去 N 个月内被触碰但未有效突破的次数
  • 网格触发回测:在候选箱体配置下模拟历史行情,输出预估网格触发频率和收益分布
  • 当前配置健康度:判断现有箱体是否接近失效(价格频繁触及边界、止损触发率偏高)

输出形式:推荐的箱体候选方案(2-3 个,含 takeProfitPrice 锚点 + 格数步长,可附推导出的 boxHighPrice/boxLowPrice 便于人工核对),附每个方案的数据依据。人工选择后手动更新配置,不自动写入。

为什么这里有确定性价值:

  1. 任务是结构化数据分析,不涉及市场预测,AI 完成质量稳定
  2. 错误成本完全可控——AI 输出的是建议,人确认后才生效
  3. 覆盖人工容易忽略的长周期数据(6 个月前的支撑位权重与最近数据一致)

触发时机:

  • 定期(每月一次)自动运行
  • 当价格连续 N 天在箱体边界 5% 范围内震荡时,自动触发重新评估

14.2 明确不引入 AI 的场景

以下场景在讨论中曾被考虑,但因错误成本或可靠性问题被排除,记录在此以备后续决策参考:

场景 排除原因
实时优化网格步长或格子数 步长来自大量 A/B 实测,AI 无同等数据支撑,任何改动均为猜测
预测激活/停止时机 激活逻辑已是规则系统;AI 在价格预测上无可靠优势,且错误直接影响仓位
自动调整 takeProfitPrice 主动止盈 需判断"价格是否将暴跌",属于市场预测问题,可靠性无保障
止损后的重新入场时机判断 第 12.5 节的冷静期机制已经过实测验证;AI 替代该判断的错误成本是直接亏损
实时干预追逐下单定价 三区间定价机制已有清晰的经济模型支撑,叠加 AI 更可能引入噪音

原则:核心交易执行逻辑(追踪建仓、追逐下单、止损缓冲)不引入 AI 决策层。AI 只参与离线分析与配置建议,不参与实时交易决策。