文档用途:面向外包实施团队的完整交易策略规格说明。本文档完整描述策略的设计思想、系统架构、算法逻辑和参数体系,实施方应依据本文档从零开始设计实现,无需参考原有代码。
目标标的:ETH/USDT 永续合约(兼容 Binance / Gate.io / OKX) 策略类型:多段价格区间 · 做多网格 · 实时动态委托
- 策略核心思想
- 整体架构
- 价格区间箱体的内部结构
- 区间管理器:激活与切换逻辑
- 追踪建仓模式(机器人激活前) ← 决定"什么时候开始交易"
- 主网格运行逻辑
- 追逐下单:实时定价与订单追踪 ← 决定"以什么价格和方式下单"
- 止损缓冲区与算法单哨兵
- 止损与反弹恢复状态机
- 配置参数完整说明
- 参数选择方法论 ← 为什么这样选参数
- 关键约束与边界条件
- 优化空间分析
- AI Agent 增强方向
本策略在以下几个核心设计思想上有别于交易所原生网格机器人:
传统网格一次性将所有买卖单挂入交易所,订单挂出后不再移动,等市场自己来"撞单"。本策略的核心出发点截然不同:像一个有经验的人类交易员盯盘一样,先观察当前行情,再动态决定是否挂单、挂什么价格、还是撤掉旧单。
具体表现为:
- 系统每次收到行情更新时,先判断当前价格与网格目标价的关系
- 如果条件不利(价格高于目标买价,进入"熔断区")→ 拒绝下单,或撤销现有挂单,等待观望
- 如果条件有利(价格低于目标买价,进入"POC 区")→ 在当前最优买价挂一个 Post-Only 单
- 如果价格大幅偏离(进入"GTC 区")→ 改用市价立即成交,锁定超额利润
系统在任意时刻不一定有网格单挂着——这与传统网格始终保持满屏挂单的方式截然不同。本质上,这是在基本网格策略上增加了一层自动化盯盘决策:用程序代替人工,持续观察并实时调整是否下单、如何下单。
这样做的收益:
- 以当前市场最优价卡位,而非等市场来撞预设的静态订单
- 价格条件不利时主动停手,避免在错误价位成交
- 价格大幅跳跃时可捕获超出网格步长的额外价差利润
每次挂单时,系统会根据当前市场价与网格目标价的关系,动态选择挂单方式:
- Maker 挂单(默认):价格在网格底价附近,挂 Post-Only 限价单,等待被动成交,以更低的 Maker 手续费率(0.02%)成交,比主动吃单节省 0.03%
- Taker 吃单(特许):价格已大幅低于网格底价,额外利润超过 Taker 手续费时,主动吃单以锁定更大价差
- 熔断不动:当前价格比网格底价更贵时,拒绝下单,避免高买低卖
当价格从上方下落进入网格区间时,系统不立即开仓,而是追踪价格低点,等到价格从低点反弹确定回调幅度后才建仓。这避免了"一建仓就站在高位被套"的情形。
在主网格下方设置一段"止损缓冲区":目标持仓随价格下跌逐格递减,以动态限价单分层减仓,而非在单一价位一次性全平;仅在清算线挂一张"全平"算法条件单作为最后防线(详见第八章统一止损机制)。若价格短暂跌破后反弹,残余仓位可直接受益,省去重新建仓的手续费。
针对 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 空间单实现)统一计算,前后端共同消费。
箱体的几何锚定在止盈端:用户输入 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 空间单实现,做空自动镜像做多。
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)直接相邻:
- 价格稍微越过满仓线进入止损区(向亏损方向)→ 止损减仓触发,减一份
- 价格稍微反弹回主网格 → 目标仓位算法触发,加一份
- 如此来回,每次都要付出买卖两次手续费,而实际价格并未真正单边移动
隔离带的宽度(建议与主网格步长相同)提供了一个"死区",使得短暂的价格噪声无法同时触发两套逻辑,大幅减少无效交易。
区间管理器在以下条件全部满足时,激活对应区间的网格机器人:
- 当前无活跃机器人(同一时刻只允许一个机器人运行)
- 价格在激活窗口内(d 空间方向无关):
activationDepth < d(当前价格) < boxDepth- 即价格在
activationPrice与清算线liquidationPrice之间(朝亏损方向) - 做多等价于
liquidationPrice < 当前价格 < activationPrice;做空镜像为activationPrice < 当前价格 < liquidationPrice activationPrice默认取主网格中点(d = mainGridDepth / 2,做多即(takeProfitPrice + fullPositionPrice) / 2),也可手动配置;校验要求其落在主网格内0 < d(activationPrice) < mainGridDepth
- 即价格在
激活窗口限制在靠亏损端启动,理由:若价格离止盈端太近,止盈方向利润空间小而亏损方向风险大,期望值为负;靠主网格中下部启动时,两侧空间更均衡。
激活时,判断价格是从上方"落入"还是从下方"涨入"激活窗口:
激活时,按 d 空间判断价格是从止盈端方向"进入"还是从亏损端方向"涨/跌入"激活窗口(做多/做空镜像统一为:价格是否仍在向亏损方向移动 d(当前价格) > d(上次记录价格)):
| 价格进入方向 | 判断条件(d 空间) | 建仓模式 |
|---|---|---|
| 朝亏损方向进入 | d(当前价格) > d(上次记录价格)(或无历史记录) |
追踪建仓模式 |
| 朝止盈方向进入 | d(当前价格) ≤ d(上次记录价格) |
直接运行模式:不需要追踪建仓,直接进入 RUNNING 状态 |
朝亏损方向进入意味着价格仍在向不利方向移动,贸然建仓可能建在途中;追踪建仓会等待反弹确认再开仓。朝止盈方向进入时亏损端已在身后,直接进入 RUNNING 状态(实现见 robot/decide-box-activation.ts,几何判定下沉 packages/shared-types/src/box-geometry.ts)。
若机器人因异常(程序崩溃、网络断开)退出但仍有未平仓位,重启后管理器会识别该状态,跳过追踪建仓阶段,直接以 RUNNING 状态接管现有持仓。
机器人每次启动时,必须先清理上一轮会话遗留的算法单,再挂入当前会话的新算法单。
原因:止损缓冲区的算法单由上一轮机器人实例在运行中挂出,若程序崩溃或异常退出,这些算法单仍留在交易所账户中。新一轮机器人启动后会重新计算并挂新算法单;若不先清理旧单,账户中会同时存在新旧两套算法单,触发价格重叠,导致重复止损或方向混乱。
清理范围:仅清理属于本策略管理的止损缓冲区算法单,不影响用户手动挂的其他条件单。
机器人退出原因及后续行为:
| 退出原因 | 处理方式 |
|---|---|
| 止损退出(StopLoss) | 清理实例,回到监听状态,等待下一次激活条件 |
| 止盈退出(TakeProfit) | 清理实例,回到监听状态 |
| 手动停止 | 清理实例 |
| 余额不足 | 暂停评估,等待余额补充 |
与"追逐下单"的区别:
- 追踪建仓(本章)是 BotManager 层的激活决策,发生在机器人尚未开始交易之前,解决的是"什么时候开始交易"的问题。
- 追逐下单(第七章)是已激活的机器人在每次执行买卖时的下单策略,解决的是"以什么价格和方式挂单"的问题。
两者完全独立,互不干扰。
追踪建仓(Trailing Entry)是机器人在正式开始网格交易前的等待确认阶段。
下面以做多为直觉表述,做空为镜像(极值取高点、反弹为向下回调、越界为涨破清算线);几何判定均在 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%。
追踪建仓的本质是"不预测最低点,只确认已经离开了最低点"。无论价格跌到哪里,只要开始反弹并超过设定幅度,就认为下跌阶段结束,可以安全建仓。
当价格朝止盈方向进入激活窗口(做多:价格此前在更低处、现在上涨回来;做空镜像),系统会跳过追踪等待,直接进入 RUNNING 状态。
原因:用户预设的箱体就是用户认可的运营区间。当价格从亏损端更深处反向进入这个区间,说明入场成本比满仓线更靠近清算端——在这个位置建仓,朝止盈方向移动时每一格的平仓利润都比从区间内部建仓要高。追踪等待反而会错过这段额外收益。
对比:
- 朝亏损方向进入 → 不知道极值在哪,需要等确认,否则可能一路建在不利途中
- 朝止盈方向进入 → 亏损端已经在身后,当前价格是经过市场确认的相对有利位,直接入场期望值更优
主网格区被均分为 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
根据当前持仓 CurrentPosition 与目标值的差异,决定操作:
| 条件 | 动作 | 说明 |
|---|---|---|
CurrentPosition ≥ TargetHeldSize + ε |
卖出,数量 = 差值 | 持仓多于应持,立即卖 |
CurrentPosition ≤ TargetBoughtSize - ε |
买入,数量 = 差值 | 低于应买量,触发买入 |
| 其他(两者之间) | 不动作 | 缓冲区内,等待价格移动 |
其中 ε 为最小下单量(交易所精度约束)。
非对称设计的意义:
- 卖出使用"应持仓位"作为基准(更宽松,更容易触发卖出)
- 买入使用"应买入仓位"作为基准(更严格,必须偏离超过一格才买)
- 这让策略在价格上涨时更快卖出锁定利润,在价格下跌时更保守地买入,积累正向偏度
目标仓位算法的"状态无关"特性(自愈能力):
目标仓位完全由当前价格决定,不依赖任何历史状态。这不是设计副产品,而是针对分布式系统特性的主动设计:
本地程序与交易所是两个独立的网络节点,以下情况都会导致本地记录的仓位状态与交易所实际持仓不一致:
- 网络抖动 / 超时:下单请求发出后不知道是否成交
- 订单部分成交:委托 0.02 ETH,实际只成交 0.01 ETH,本地记录可能未及时同步剩余量
- 高并发竞态:行情推送与订单回报同时到达,本地状态机未来得及处理其中一个
- 程序崩溃重启:重启后本地状态清零,但交易所仍有残留持仓或挂单
状态无关算法完全绕过了这个问题:每次价格更新时,系统只问"当前价格下应该持有多少仓位",再与交易所实时查询到的实际持仓对比,直接纠偏——不需要追溯"之前发生了什么",也不需要维护本地订单历史。
这意味着:无论程序崩溃多久、网络断线几小时、甚至用户手动在交易所操作了仓位——只要程序重启并拿到当前价格,系统就能立刻算出应有仓位并开始纠偏,没有"状态脏了要手工清理"的运维负担。
配套约束——必须使用最新持仓数据下单:
上述自愈特性能成立,有一个不可缺少的前提:每次计算实际下单量时,必须使用从交易所收到的最新持仓信息,而非本地缓存的旧快照。
实际操作量 = 目标仓位(由当前价格推导)- 当前实际持仓(来自交易所最新推送)
持仓数据的更新策略采用主动 + 被动双保险:
- 被动接收:每次收到交易所的持仓推送事件时,立即更新本地持仓记录
- 主动拉取:每次网格订单成交(收到成交回报)时,主动向交易所 API 查询一次最新持仓,不依赖推送是否及时到达
主动拉取是必要的:成交回报与持仓推送是两条独立的 WebSocket 通道,持仓推送可能因网络抖动或交易所内部处理延迟而滞后于成交事件。如果仅依赖被动推送,系统在处理下一笔订单时可能读到成交前的旧持仓数据。
如果以旧的持仓数据计算下单量,可能出现:
- 某笔买入刚刚成交,持仓已增加,但本地缓存未更新 → 再次买入造成超买
- 某笔止损卖出刚刚触发,持仓已减少,但本地未感知 → 继续挂卖单造成超卖
简言之:目标仓位由价格决定(无状态推导),实际操作量由最新持仓决定(实时取用)。两者缺一不可,才能构成真正的自愈闭环。
如果当前价格已朝亏损方向越过满仓线 fullPositionPrice 进入隔离带/止损区(d > mainGridDepth),统一仓位计算函数 computeTargetPosition 自动按隔离带(满仓无操作)/止损区(递减减仓)规则给出目标仓位——不再需要主网格与止损区分别处理(见第八章统一止损机制)。
"追逐下单"是本策略中每次执行网格买卖时使用的下单机制,目标是:在满足网格基本利润的前提下,尽量以 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 身份成交或在大幅偏离时锁定超额利润。
关键细节: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 上。
参数定义:
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%,合算 |
Post-Only 单挂出后,系统在每次收到价格更新时都会检查是否需要改单:
触发条件 A:订单类型升级(POC → GTC)
- 当前挂的是 POC 单,但最新价格计算显示应进入 GTC 区
- 立即撤单,重新以 GTC 方式吃单(GTC 机会稍纵即逝,优先执行)
触发条件 B:价格变化超过改单阈值
- 新的 bestBid/bestAsk 与当前挂单价格的差距 >
当前价格 × 0.02% - 市场已移动,继续挂原价成交概率降低,撤单重挂在新价位以维持 Maker 竞争力
- 方向不区分:价格向有利方向(继续下跌,买更便宜)或不利方向(回升,原位置竞争力下降)变化超过阈值,均触发改单。理由:无论哪个方向,原挂单价格已不再是当前最优 Maker 竞价点,改单的目的是保持最优买盘(卖盘)位置。(见 13.5 节:对两个方向设置不同阈值是一个潜在优化方向。)
触发条件 C(隐含):进入熔断区
- 重新计算后发现价格不利(买入时价格涨过了 GridPrice)
- 撤单,等待下次价格回落后重新判断
1. 发送撤单请求(带原订单 ID)
2. 确认撤单后的实际剩余量:
- 已完全成交(剩余量为零):无需重挂,结束
- 剩余量低于交易所最小下单要求:放弃剩余量,结束
- 其余情况:继续下一步
3. 以剩余量重新下单,下单时重新抓取当前最优市场价(确保使用最新行情,而非触发改单决策时的价格)
4. 如果当前行情已进入熔断区:放弃重挂,等待下次价格更新触发
5. 以剩余量 + 新最优价格,提交新订单,继续追踪
任意时刻,系统最多只有一个活跃的网格订单处于 Pending 状态:
| 状态 | 触发时机 | 系统行为 |
|---|---|---|
| 有活跃订单(系统正在等待某笔网格订单成交) | 每次收到价格更新 | 只执行追踪改单逻辑(7.4 节),不重新计算目标仓位 |
| 无活跃订单 | 每次收到价格更新,或订单成交/撤销后立即触发 | ① 主动拉取交易所最新持仓;② 重新计算目标仓位;③ 若持仓与目标有偏差则发起新的追逐下单 |
"无活跃订单"触发时机的说明:不能只依赖价格更新来驱动下一笔订单。订单刚成交时,持仓已变化,如果等到下一次价格 tick 才重新判断,可能因行情已移动而错过最优下单时机。因此订单成交/撤销事件本身也应直接触发一次"无活跃订单"的完整流程。
串行化确保了不会因为仓位计算时序问题产生重复下单或仓位冲突。
止损优先保障:所有订单提交(包括初次下单和改单重挂)均有止损优先机制:一旦系统进入止损流程(如价格越过清算线 liquidationPrice 触发清算,见 §9.2),立即放弃所有进行中的网格下单操作,确保止损触发后不会继续开新仓位。
疑问: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 负责大偏差时的超额利润,两者互补,覆盖全部有利场景。
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 采用统一的仓位计算函数来决定所有买卖操作。止损区不再需要特殊的条件单维护逻辑,而是使用与主网格区相同的方式动态下单。
系统的核心函数 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 退役)。
在所有区域(包括止损区),下单决策使用完全相同的逻辑:
// StrategyEngine.computeDesiredOrders()
if (actualHeld >= targetHoldSize + ε) {
→ SELL(减仓)
} else if (actualHeld <= targetBoughtSize - ε) {
→ BUY(补仓)
} else {
→ HOLD
}止损区的行为:
- 当价格在止损区内下跌时,
targetHoldSize递减 - 若
actualHeld >= targetHoldSize + ε,触发 SELL 订单 - 价格每下跌一个止损格子,触发一次减仓
- 无需特殊的 ping-pong 翻转机制
SHORT 方向对称:做空时止盈线在低价端、止损区在高价端(清算线为最高价),价格上升(朝亏损方向)进入止损区时触发 BUY 减仓。由于全部逻辑在 d 空间单实现,做空数值自动镜像做多,无需单独分支。
为了防止极端情况下动态下单失败(网络中断、交易所故障等),在清算线 liquidationPrice 处设置一个条件单作为最后防线。
| 属性 | LONG 方向 | SHORT 方向 |
|---|---|---|
| 位置 | liquidationPrice(清算线,箱体最低价) | liquidationPrice(清算线,箱体最高价) |
| 触发条件 | price_below(价格跌破清算线) | price_above(价格涨破清算线) |
| 行为 | closePosition = true | closePosition = true |
| 数量 | 完全平仓 | 完全平仓 |
目的:作为安全网,确保即使动态下单系统失效,也能在价格越过清算线时执行平仓。
注意:兜底止损单只在清算线挂一个,不需要在止损区内挂多个条件单。止损区内的减仓主要通过动态下单完成。
当 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 末)。
| 状态名称 | 含义 |
|---|---|
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 |
启动时检测到继承持仓,待激活 |
转换条件均按 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 同步修正。
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 将重新创建新的机器人实例开始下一轮交易 - 每次止损后的重新入场均经历完整的激活→追踪建仓流程,不自动续仓
止盈的工程实现:止盈条件为"价格到达止盈端(d ≤ 0,越过 takeProfitPrice)且持仓已为零"。这不是一个硬止盈(强制平仓),而是让网格自然做完——随着价格朝止盈方向移动,网格持续触发平仓,仓位逐步清空;当最后一格平掉后价格已越过止盈线,此时 BotManager 识别到机器人任务自然完成,触发退出。
含义:策略会把朝止盈方向移动过程中每一格的利润都赚到,不提前放弃任何已在区间内的利润空间。
主动止盈策略:如果用户预判价格即将反转,可以通过调整 takeProfitPrice 使止盈线更靠近当前价来主动提前止盈(做多调低止盈线、做空调高止盈线):
做多原始配置:takeProfitPrice = 3400
用户预判 3000 附近可能出现高位风险
主动操作:重新配置 takeProfitPrice = 3000
→ 网格在 3000 附近平掉最后一份仓位后,系统自然止盈退出
→ 规避了 3000 以上价格可能的暴跌风险
→ 相当于把止盈线作为"动态止盈线"使用
这一用法的本质是以箱体配置代替传统止盈单,既不需要额外的止盈逻辑,又能精确控制最终退出价格。
进入 LIQUIDATING 后的操作顺序:
- 取消所有未成交的网格挂单(包括 POC / GTC 挂单)
- 取消所有止损缓冲区的算法单(条件单)
- 市价全平持仓(ClosePosition 市价单)
- 确认交易所回报持仓已归零
- 进入 LIQUIDATED 状态
字段名以当前实现为准(活跃箱体增改入口为
/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回退,几何向后兼容。
[
{
"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(价格朝亏损方向越过此值、且在清算线之上才激活)
本章说明各核心参数的选择逻辑与权衡依据,是参数表的"理由层"。实施方应理解这些依据,而不是机械套用示例数值。
本策略的收益来源依赖三件事:价格在区间内来回震荡、每次震荡的波动足够覆盖手续费和步长、订单簿的价格跳跃为追逐下单提供超额利润。ETH 在这三点上的综合表现优于其他主流合约品种:
| 考量维度 | ETH 的特点 | 对策略的影响 |
|---|---|---|
| 波动率 | 相同时间窗口内波动幅度高于 BTC | 网格来回触发次数更多,日利润更高 |
| 区间可预测性 | 历史上多次在较明确的技术区间内长期震荡 | 更容易识别有效箱体边界,策略存活周期更长 |
| Gate.io 价格行为 | 价格跳跃频率高于 Binance | 追逐下单机制中 GTC 区超额利润捕获机会更多 |
| 合约精度 | 合约乘数和最小下单量使得小资金也能精细划格 | 可以以较少资金实现密集网格划分 |
BTC 不选的主要原因:波动率相对较低,单位时间内网格触发次数少,同等资金的日收益率低于 ETH;同时 BTC 在 Gate.io 的价格跳跃特性相对不如 ETH 显著。
结论先行: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 手续费率较低的交易所,而非简单追求流动性最好的交易所。
核心原则:箱体上下边界应能容纳接下来 2-3 个月内 ETH 大概率的价格波动范围,使得在这段时间内,价格刺破边界的概率较小。
技术分析维度(综合判断,无绝对标准):
- 历史支撑/阻力位:多次测试但未被有效突破的价格区域(被市场"记住"的关键位)
- 成交量密集区(VPVR):历史成交量高度集中的价格带,往往构成较强支撑
- 整数关口 + 宏观周期高低点:2000 / 2500 / 3000 等整数位,以及上一轮牛熊周期的关键价格
- 动态均线支撑:MA/EMA 等中长期均线形成的动态支撑区
范围大小的权衡:
| 箱体范围 | 优势 | 劣势 |
|---|---|---|
| 过大(如跨越 2000 USDT) | 刺破概率低,策略存活时间长 | 满仓保证金 = mainGridPortionSize × mainGridCount × 价格,资金需求大 |
| 过小(如仅 200 USDT) | 所需资金少 | 价格在边界附近震荡时,频繁触发止损-建仓循环,手续费损耗高 |
| 推荐(约 500-800 USDT) | 覆盖 ETH 约 2-3 个月的典型波动幅度 | 需要定期(每 1-2 个月)根据市况重新评估是否调整 |
调整时机:当宏观趋势发生明显变化(如进入单边牛/熊市),或已确认某个边界被有效突破,应重新划定箱体而不是延伸旧箱体。
来源:通过反复 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(止损区与隔离带是在主网格之外、沿亏损方向额外叠加的深度,不占用主网格格数)。
本策略通常配置多个不重叠的价格区间,共用同一个交易账户。资金分配规则:
每个区间分配资金 = 账户总余额 / 区间配置数量
各区间的 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 倍。杠杆的作用是放大资金使用效率,但不改变止损逻辑。止损缓冲区深度应覆盖最坏情况下的价格波动,与杠杆倍数无直接耦合——杠杆越高,等量资金能撑的名义持仓越大,但爆仓风险也相应提高,需要保持足够的保证金余量。
止损区(stopLossGridCount × stopLossGridStep)的深度代表"允许价格临时朝亏损方向越出主网格多远而不触发完全平仓"。深度越大,保留仓位反弹的能力越强,但触发完全止损前可能积累更大的浮亏。
实用参考:
- 止损缓冲区总深度建议约为箱体总范围的 3-5%,且不超过 1/5
- 每个子格步长建议与主网格步长相同(保持数学体系一致),子格数量 4-6 个
- 在 ETH 箱体为 600 USDT 的场景下:10 USDT 深度(4 格 × 2.5 USDT)≈ 1.7%,属于保守配置
多个配置的 boxLowPrice ~ boxHighPrice(由各自 takeProfitPrice + 深度推导的绝对边界)区间必须完全不重叠。若有重叠,系统应在启动时报错并拒绝运行(重叠校验见 robot/validate-box-addition.ts,按 boxHighPrice/boxLowPrice 判断)。
BotManager 必须保证串行激活,当前机器人未完全退出前,不得启动下一个机器人。
mainGridPortionSize必须符合交易所最小下单量MinQtymainGridPortionSize必须是交易所步长StepSize的整数倍- 合约交易所(如 Gate.io)以"合约数"而非"币数"为单位,需要转换:
合约数 = mainGridPortionSize(币数) / 合约乘数(QuantoMultiplier)
GTC 吃单决策的隐含逻辑:只有当价格偏离超过 GtcThreshold 时,额外价差利润才大于改用 Taker 而多付的手续费。
最小合算阈值 = takerFeeRate - makerFeeRate
Gate.io 示例:0.05% - 0.02% = 0.03%
GtcThreshold 应显著高于最小阈值以留有安全余量,建议范围 0.05% ~ 0.3%,当前默认值 0.1% ≈ 最小阈值的 3.3 倍,属于保守合理配置。具体值应根据所用交易所的手续费率调整。
触发止损后,Bot 退出(LIQUIDATED → 止损退出),系统不会自动续仓,而是由 BotManager 按正常激活流程重新评估:
- 止损退出后系统回到"未激活"状态,停止一切交易
- 只有当价格重新进入激活窗口(
activationDepth < d(价格) < boxDepth,做多即liquidationPrice < 价格 < activationPrice)时,BotManager 才会重新激活 - 重新激活后若价格从上方进入,走追踪建仓流程,确认反弹后才正式建仓
- 这一机制天然形成"冷静期":价格需先回到激活区间,再经历一次反弹确认,才能重新入场,避免在仍处于下跌通道时立即重新建仓
实现含义:重新激活的等待时间完全取决于市场价格何时重新满足激活条件,没有固定的等待时长——如果市场持续下跌,系统就持续等待;如果价格迅速反弹,系统就迅速重新评估激活。
管理器应定期(建议每 10 分钟)查询账户 USDT 余额:
- 若余额低于最低安全线(如 10 USDT),立即停止当前机器人并暂停新机器人激活
- 目的:防止保证金耗尽后仍持续开仓导致强制平仓(爆仓)
- 持仓模式必须设置为单向持仓(One-Way),不支持双向持仓模式
- 保证金模式必须设置为全仓模式(Cross Margin),不支持逐仓
以下是在深入理解当前策略实现后,发现的几处有优化价值的方向。各项互相独立,可按优先级逐步实施。
现状: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 保留为策略参数。
现状:reorderThreshold 已为 Box 配置字段(默认 0.0002 = 0.02%)并接入决策链(buildBotConfig → StrategyEngine / runner)。
残留:箱体增改 API(AddBoxDto / EditBoxDto)暂未暴露该字段,经 API 创建的箱体恒取 schema 默认值。
后续优化方向:Gate.io 价格跳跃频繁(每次 0.1-0.5 USDT),0.02% 在高波动时段会触发非常频繁的撤单重挂。可考虑按交易所/品种调优默认值,或按当前 ATR 自适应。
现状:止盈触发条件为"价格到达止盈端(d ≤ 0,越过 takeProfitPrice)且 当前持仓为零"。
问题:正常网格运行时,价格朝止盈方向移动到 takeProfitPrice 附近,网格会持续平仓,仓位减少到零后价格已越过止盈线。但如果价格快速跳过止盈线(跳空),可能存在还有仓位但价格已越过止盈端的情况——此时系统不会触发止盈退出,仓位将悬空。
建议:增加超时机制,当价格持续越过 takeProfitPrice 超过 N 分钟时,即使仓位不为零也触发止盈流程(先平仓再退出)。
现状:清算线兜底条件单由 runner 在每个 tick(默认轮询 10 秒)且有持仓时检查并补齐(ensureEmergencyStopLoss),启动主网格时也会检查一次——实际频率高于此前建议的 1-2 分钟间隔。
勘正(2026-08):本条此前引用的"8.5 节"并不存在(第八章仅 §8.1–§8.4),相关引用已从 §9.3 移除;§8.3"仅清算线一个兜底条件单"即当前设计。
背景说明:兜底单的主要维护依赖 WebSocket 实时事件,每 tick 哨兵为兜底机制。在止损缓冲区较浅的配置下,高频检查可避免价格在哨兵发现遗漏前已穿越多个止损格。
现状:改单阈值只看价格变化的绝对幅度,不区分价格是往有利方向还是不利方向移动。
潜在优化:对于买单,如果价格变化方向是向上(远离 GridPrice,进入熔断风险),应该降低改单阈值(更快响应),甚至直接取消;如果是向下(向 GTC 区靠近),可以适当宽松阈值(不需要急着追)。
这样可以减少"价格短暂上涨又回落"时不必要的改单次数。
现状:追踪改单逻辑不区分当前活跃订单是 Maker 挂单(POC)还是 Taker 吃单(GTC)。
问题:若某笔 GTC 吃单因深度或时延原因未在提交后立即成交,下次价格更新时系统可能仍检测到当前行情处于 GTC 区,从而触发"撤单重挂 GTC"——这是一次无效操作。
建议:系统应在追踪活跃订单时记录其下单类型(Maker 挂单或 Taker 吃单)。当活跃订单已是 GTC 且当前行情仍处于 GTC 区时,跳过改单,等待成交或直至行情脱离 GTC 区。
现状:GTC 吃单时的实际下单价格在 GtcPrice 附近,并设有一个固定比例的价格保护边界(当前约 2%)。
问题:固定保护边界在极端行情(如闪崩后价格已跌去 5%)下可能仍会以较差价位成交。
建议:GTC 吃单的保护上限应不超过 GridPrice——无论如何,买入价格不应高于网格底价。可将 GridPrice 作为硬性上界,叠加比例保护边界,取其中更低者作为实际下单价。
2026-08 核查:
GtcPrice = GridPrice × (1 ∓ GtcThreshold)恒小于(买入)/ 大于(卖出)GridPrice,且实际下单价 =min(GtcPrice, 当前价 × 1.02)(买入时),因此「买入不超过 GridPrice」的硬性上界已隐含满足;本条仅余固定 2% 比例边界的调优空间。
本章说明 AI Agent 在本策略中确定性有价值的介入场景,以及明确不适合介入的场景。判断标准:只有当 AI 介入的错误成本可控(非实时干预交易),且 AI 完成质量显著优于纯人工时,才纳入规划。
问题根源:第 11.2 节的箱体边界选择目前依赖人工读图判断,每 1-2 个月需要重做一次。人工分析容易受近期价格走势影响(锚定效应),且难以系统覆盖所有时间维度的数据。
AI 的角色:基于历史 K 线和成交量数据,自动计算并推荐下一轮箱体配置,供人工审核确认后再生效。
AI 的分析内容:
- VPVR(成交量分布):统计各价格区间的历史成交量密度,识别成交量集中的支撑/阻力带
- 历史触及次数:统计各价格位在过去 N 个月内被触碰但未有效突破的次数
- 网格触发回测:在候选箱体配置下模拟历史行情,输出预估网格触发频率和收益分布
- 当前配置健康度:判断现有箱体是否接近失效(价格频繁触及边界、止损触发率偏高)
输出形式:推荐的箱体候选方案(2-3 个,含 takeProfitPrice 锚点 + 格数步长,可附推导出的 boxHighPrice/boxLowPrice 便于人工核对),附每个方案的数据依据。人工选择后手动更新配置,不自动写入。
为什么这里有确定性价值:
- 任务是结构化数据分析,不涉及市场预测,AI 完成质量稳定
- 错误成本完全可控——AI 输出的是建议,人确认后才生效
- 覆盖人工容易忽略的长周期数据(6 个月前的支撑位权重与最近数据一致)
触发时机:
- 定期(每月一次)自动运行
- 当价格连续 N 天在箱体边界 5% 范围内震荡时,自动触发重新评估
以下场景在讨论中曾被考虑,但因错误成本或可靠性问题被排除,记录在此以备后续决策参考:
| 场景 | 排除原因 |
|---|---|
| 实时优化网格步长或格子数 | 步长来自大量 A/B 实测,AI 无同等数据支撑,任何改动均为猜测 |
| 预测激活/停止时机 | 激活逻辑已是规则系统;AI 在价格预测上无可靠优势,且错误直接影响仓位 |
| 自动调整 takeProfitPrice 主动止盈 | 需判断"价格是否将暴跌",属于市场预测问题,可靠性无保障 |
| 止损后的重新入场时机判断 | 第 12.5 节的冷静期机制已经过实测验证;AI 替代该判断的错误成本是直接亏损 |
| 实时干预追逐下单定价 | 三区间定价机制已有清晰的经济模型支撑,叠加 AI 更可能引入噪音 |
原则:核心交易执行逻辑(追踪建仓、追逐下单、止损缓冲)不引入 AI 决策层。AI 只参与离线分析与配置建议,不参与实时交易决策。