
在阅读和使用本策略前,请务必注意以下三点:
1. 策略需要耐心等待开仓机会
币安下架合约属于低频事件,并非每天都会发生。策略启动后可能需要等待数天甚至更长时间才会触发首次开仓,运行期间程序大部分时间处于”待机监控”状态,请做好长时间等待的心理准备,不要因为短期内没有交易而误以为策略失效。
2. 开仓检测时机仍有优化空间
本文采用的是每 15 秒轮询一次 fapi/v1/exchangeInfo 接口、通过 deliveryDate 字段变化来识别下架信号的方案。该方法实盘测试存在一定延迟,并非最快路径。读者可以根据自身需求进一步优化检测方式,例如:缩短轮询间隔、并行监控币安公告 API、订阅 WebSocket 推送、结合多源信号交叉验证等,以争取更早的入场时机。越早入场,越能吃到第一波急跌的最大跌幅。
3. 务必及时关停策略,防止利润回撤
实盘中观察到,并非所有下架币种都会一路阴跌到下架时刻。部分品种在公告后急跌一波,随后会在数小时到一天内逐步反弹,价格甚至可能恢复至公告前的水平。如果不及时关停,前期累积的浮盈会在反弹中被大幅吞噬,甚至出现由盈转亏的情况。
建议设置以下任一条件作为主动退出信号: – 达到预设的盈利目标后立即清仓退出; – 价格反弹超过公告后最低点的某一比例(如 20%~30%)时强制平仓; – 持仓回撤达到峰值利润的一定比例(如 30%~50%)时触发止盈保护。
切勿被动等到下架前 60 分钟才平仓——那是兜底机制,不是最优退出时机。
在币安合约市场中,有一类特殊的交易机会往往被大多数人忽视——合约下架事件。
每隔一段时间,币安会发布公告,宣布将某些流动性较差或交易量萎缩的永续合约下架。公告发出的瞬间,市场会迅速反应:持有多头的交易者被迫平仓离场,恐慌性抛售接踵而至,币价往往在公告后的几分钟内出现剧烈下跌,随后进入一段漫长的震荡下行走势,直到最终下架。
以本次 MLNUSDT 为例:

半个小时内跌去近三分之一,而整个下架周期通常持续数天,期间价格会持续在低位震荡。这样的行情,对于空头策略而言是天然的温床。
然而,手动盯盘操作这类机会有两个难点:
第一,信息时效性极强。 公告发出后的前5分钟是跌幅最大的窗口,如果错过了第一时间入场,后续追空的风险会大幅上升。人工监控难以做到7×24小时实时响应。
第二,行情并非单边瀑布。 价格下跌过程中会不断出现反弹,纯粹持有空单虽然能赚到趋势收益,但会错过反弹过程中大量的高频差价机会。
正是为了解决这两个问题,本文介绍一套完整的自动化策略:通过程序实时监控币安下架信号,在公告发出的第一时间自动开空底仓,同时启动动态空头网格,在整体下跌趋势中持续捕捉震荡差价,最终在下架前自动平仓退出,全程无需人工干预。
在深入讲解策略之前,我们需要先理解下架币种的价格走势特征,这是整个策略设计的基础。
当币安宣布下架某个永续合约时,市场的第一反应是恐慌。持有多头仓位的交易者知道合约即将消失,必须在下架前平仓,否则将被强制结算。这种集中抛售形成了短期内强烈的卖压,导致价格迅速下行。
与此同时,做市商会迅速收窄报价或撤出流动性,进一步加剧了价格的波动。这就是为什么下架公告后的前几分钟,往往是整个下架周期内跌幅最大的时段。
第一波急跌之后,价格不会直线跌到底部,而是呈现出一种典型的震荡下行走势:

这种走势的形成有其内在逻辑:每一次反弹都是短线交易者认为跌多了进场抄底,但由于基本面没有改变(合约即将消失),抄底盘很快被套牢,价格重新下行。反弹的高度越来越低,直到下架前流动性彻底枯竭。
这种有规律的震荡正是网格策略最适合的行情。
基于以上分析,我们可以设计出两条独立的盈利路径:
| 收益来源 | 对应工具 | 盈利条件 |
|---|---|---|
| 趋势下跌 | 底仓空单 | 价格整体向下 |
| 震荡差价 | 空头网格 | 价格在区间内反复振荡 |
两者叠加,使策略在下架行情中具备较强的盈利能力。即使价格出现较大幅度的反弹,网格部分仍然可以持续收割差价;而只要整体趋势向下,底仓就持续盈利。
对于监控合约信息,本策略采用了一种更直接的方法:直接监控币安合约接口的数据变化。
币安的 fapi/v1/exchangeInfo 接口返回所有合约的详细信息,其中有一个字段叫做 deliveryDate,表示合约的交割时间。
对于永续合约,这个字段通常被设置为一个遥远的未来时间戳:
4133404800000 → 对应 2100年12月31日
这相当于”永不到期”的占位符。
关键在于:当币安决定下架某个永续合约时,会在发布公告的同时,将该合约的 deliveryDate 修改为真实的下架时间戳。
正常永续合约: deliveryDate = 4133404800000(永不到期)
即将下架合约: deliveryDate = 1744106400000(2026-04-08 17:00:00)
这个变化会立刻反映在接口数据中,比公告页面的渲染更快,也更结构化,不需要解析任何 HTML。
每15秒调用一次接口,过滤出 deliveryDate 已经变为真实时间戳的 USDT 永续合约:
def fetch_delist_symbols():
body = HttpQuery("https://fapi.binance.com/fapi/v1/exchangeInfo")
data = json.loads(body)
now_ms = get_now_ms()
result = {}
for s in data.get("symbols", []):
if not s["symbol"].endswith("USDT"):
continue
if s.get("contractType") != "PERPETUAL":
continue
dd = s.get("deliveryDate", PERPETUAL_END)
if dd < PERPETUAL_END and dd > now_ms:
result[s["symbol"]] = dd
return result
返回结果示例:
{
"HIPPOUSDT": 1744106400000, # 2026-04-08 17:00:00
"OLUSDT": 1744106400000,
"RLSUSDT": 1744106400000,
"PUFFERUSDT":1744106400000,
}
注意: 实盘测试后,稍有延迟,可选择更及时的验证方法。
整套策略分为两个并行运行的模块:

发现新合约后,会对每个合约独立创建一个 task 对象,包含该合约的所有状态:
task = {
"symbol": "HIPPO_USDT",
"delist_time_ms": 1744106400000,
"fund_per_task": 250.0, # 分配资金
"base_short_qty": 1500000, # 底仓张数
"range_high": 0.0005287, # 网格上沿
"range_low": 0.0004758, # 网格下沿
"grid_width": 0.0000529, # 区间宽度(固定)
"grids": [...], # 10个格子的状态
"shift_count": 0, # 已移动次数
...
}
多个合约的 task 相互独立,互不干扰,并行运行。
发现N个下架合约后,将账户可用余额动态平分:
每个合约分配资金 = 可用余额 × 80% / N
留20%作为保证金缓冲,防止价格短期反弹导致爆仓。
关键细节:多个合约依次初始化时,每初始化一个合约,账户可用余额就会减少(底仓占用了保证金)。因此不能在循环开始前一次性计算所有合约的分配资金,而是每次初始化前重新查询:
for idx, (binance_sym, delist_ms) in enumerate(delist_map.items()):
update_global_account()
remaining_count = total_new - idx
available_now = total_balance - margin_used
fund_per_task = available_now * 0.8 / remaining_count
task = init_task(binance_sym, delist_ms, fund_per_task)
这样可以确保每个合约都能获得合理的资金分配,不会因为前面的合约占用了太多保证金导致后面的合约资金不足。
公告发出、初始化时立刻以市价开空,不等网格:
底仓名义仓位 = 分配资金 × 50% × 杠杆倍数
示例:分配资金250U,杠杆10倍
底仓名义仓位 = 250 × 50% × 10 = 1250U
底仓全程持有,不参与网格的反复开平,只在下架前强制平仓时一并平掉。
底仓是整个策略中收益最大的部分——如果下架币种从公告到下架跌了50%,底仓就贡献了这50%的空头收益(乘以杠杆)。
以当前价为上沿,向下10%为下沿,均匀分10格:
示例(当前价 0.0005287,区间宽度10%):
上沿 = 0.0005287
下沿 = 0.0005287 × (1 - 10%) = 0.0004758
格距 = (0.0005287 - 0.0004758) / 10 = 0.0000053
格9: 开空@0.0005287 → 平空@0.0005234
格8: 开空@0.0005234 → 平空@0.0005181
格7: 开空@0.0005181 → 平空@0.0005128
...
格0: 开空@0.0004811 → 平空@0.0004758
每格资金均分,逻辑简洁:价格反弹到开空价,挂单成交开空;价格下跌到平空价,挂单成交平空;完成一个来回,重新挂开空单,等待下一次反弹。
启动时,所有开空价 >= 当前价的格子全部挂单:
当前价 0.0005287
格9 开空价 = 0.0005287 ≥ 0.0005287 → 挂单 ✅
格8 开空价 = 0.0005234 < 0.0005287 → skip_below(价格已跌过)
格7 以下全部 skip_below
之所以把所有高于当前价的格子都挂上,是因为下跌行情中价格可能随时出现超预期的反弹,提前挂好所有格子可以确保不错过任何反弹带来的开空机会。
这是整个策略中最核心的机制。价格不会永远停留在初始区间内,网格必须跟随价格移动才能持续捕捉差价。
当价格跌破网格下沿时,说明下跌幅度超出了当前区间的覆盖范围,需要将区间整体下移:
旧区间: 0.0004758 ~ 0.0005287
当前价: 0.0004500(跌破下沿 0.0004758)
计算新区间(移动步长5%):
shift_step = 0.0004500 × 5% = 0.0000225
新上沿 = 0.0005287 - 0.0000225 = 0.0005062
新下沿 = 0.0005062 - 0.0000529 = 0.0004533
新区间: 0.0004533 ~ 0.0005062
当价格反弹超过网格上沿时,区间跟随上移:
旧区间: 0.0004494 ~ 0.0005023
当前价: 0.0005100(超过上沿 0.0005023)
新区间上移,确保当前价在新区间内
新区间: 0.0004758 ~ 0.0005287
这一机制保证了无论价格如何波动,网格始终跟随价格运行,不会出现价格脱离区间导致所有格子都空置的情况。
每次移动只改变区间的位置,不改变区间的宽度:
grid_width = round(range_high - range_low, 8) # 初始化时固定
# 下移时
range_high = fp(task, range_high - shift_step)
range_low = fp(task, range_high - grid_width) # 用固定宽度计算
这样避免了浮点数累积误差导致区间越来越窄或越来越宽的问题。
区间移动时,所有挂单都会被撤销,网格重建。但已经开空且等待平仓的格子该怎么处理?
如果直接重建,这些格子的持仓就”失联”了——有空头仓位但没有对应的平仓单,变成裸持仓,完全暴露在反弹风险中。
解决方案是:移动前记录所有持仓中的格子合约数,移动后在新区间最低格重新挂平空单:
# 移动前汇总持仓
holding_contracts = sum(
g.get("sell_contracts", 0)
for g in task["grids"]
if g["status"] in ("pending_cover", "holding_no_cover")
)
cancel_all_orders(task)
activate_grids(task, new_high, new_low)
# 移动后在最低格挂平空保护
if holding_contracts > 0:
_place_grid_cover(task, 0, holding_contracts)
这样无论区间如何移动,已开空的持仓都不会丢失追踪。

旧合约平仓释放的资金会重新参与分配,确保新合约得到充足的资金支持。
每个合约的生命周期:

像 HIPPOUSDT 这种价格在 0.0003 量级的币种,计算区间移动步长时会遇到精度问题:
shift_step = 0.0003 × 5% = 0.000015
若 PricePrecision = 4(保留4位小数)
round(0.000015, 4) = 0.0 ← 步长变成0!
→ while 循环永远结束不了,死循环
解决方案是不对步长做精度截断,并设置最小步长兜底:
shift_step = price * SHIFT_STEP_PCT
min_step = 10 ** (-PricePrecision)
shift_step = max(shift_step, min_step) # 至少等于最小精度单位
市价开空可能因为资金不足或网络问题失败,这时不能继续建网格,否则会出现无底仓的裸网格:
def open_base_short(task):
oid = place_market_short(task, usdt_amount)
Sleep(1000)
amt, _, _ = get_short_position(task)
task["base_short_qty"] = amt
if amt <= 0:
Log(f"底仓开空失败,跳过该合约")
return False
return True
# init_task 里
if not open_base_short(task):
return None # 初始化失败,不加入 tasks
选择下架前60分钟而不是30分钟(币安禁止开新仓的时间节点),是为了给平仓留出足够的时间窗口。越接近下架,流动性越差,平仓难度越大。
平仓时用略高于市价的限价单,而不是真正的市价单,避免在流动性极差时被恶意撮合:
buy_p = fp(task, price * 1.005) # 高于市价 0.5%
exchange.CreateOrder(swapcode, "closesell", buy_p, fc)
如果一次平不完,重试最多10次,每次重新获取最新价格。
在下跌行情中,资金费率通常对空头有利(空头收钱)。这是持有底仓的额外收益,不体现在网格的差价统计中,但会反映在账户权益的增长上。
LEVERAGE = 10 # 杠杆倍数,建议5~10,不宜过高
GRID_WIDTH_PCT = 0.10 # 区间宽度10%,覆盖正常震荡幅度
SHIFT_STEP_PCT = 0.05 # 移动步长5%,约为区间宽度的一半
GRID_COUNT = 10 # 格数,格数越多每格资金越少
BASE_SHORT_RATIO = 0.5 # 底仓占50%,网格占40%,留10%缓冲
FORCE_CLOSE_MINS = 60 # 提前60分钟平仓
MONITOR_INTERVAL = 15000 # 15秒监控一次,兼顾时效和频率限制
区间宽度的选择:宽度越大,覆盖的震荡范围越大,但每格格距也越大,收割频率降低。建议根据该币种历史波动率调整,通常10%是一个合理的起点。
格数的选择:格数越多,格距越小,收割频率越高,但每格资金越少,单次盈利也越小。格数过多还会导致每格资金不足最小下单量的问题。10格是一个较为平衡的选择。
在使用本策略前,需要充分了解以下风险:
反弹风险:如果消息已经提前被市场price in,公告发出后可能出现”利空出尽”的反弹,底仓会短暂亏损。网格部分在反弹时仍能收割差价,但整体可能出现短暂回撤。
流动性风险:越接近下架时间,合约的流动性越差,买卖价差扩大,平仓时滑点增加。策略中设置了0.5%的溢价和10次重试来应对,但极端情况下仍可能无法全部平仓。
假信号风险:极少数情况下币安会撤销或延迟下架计划,此时策略会继续持有空头仓位,直到手动干预或下次监控更新。
高杠杆风险:下架币种的波动极大,10倍杠杆下即使10%的反弹也会造成接近全仓的亏损。建议根据自身风险承受能力调整杠杆,并控制整体仓位。
策略及时启停:经实盘发现,个别品种在震荡下跌一天时间,价格会逐渐恢复至公告前状态,需要及时关闭策略。
运行时长:策略需要等待较长时间,才能发现机会,需要耐心等待。
本策略的核心价值在于将信息优势(第一时间发现下架信号)转化为交易优势(自动化执行),同时通过底仓+网格的双重结构,在单边下跌行情中同时捕捉趋势收益和震荡差价。
整套系统的关键设计要点:
| 模块 | 核心设计 | 解决的问题 |
|---|---|---|
| deliveryDate 监控 | 直接读接口字段变化 | 秒级发现下架信号 |
| 动态资金分配 | 每次初始化前重查余额 | 多合约资金分配均衡 |
| 底仓市价开空 | 公告发出立刻执行 | 不错过第一波急跌 |
| 全格挂单 | 所有高于当前价的格子全挂 | 不错过超预期反弹 |
| 区间宽度固定 | 移动时保持宽度不变 | 防止浮点漂移 |
| 持仓保护 | 移动后重挂平空单 | 防止裸持仓 |
| 提前60分钟平仓 | 留足平仓时间窗口 | 应对低流动性 |
下架行情不是每天都有,但每次出现都是相对确定性较高的交易机会。通过程序化自动监控和执行,可以在不需要盯盘的情况下,稳定地参与这类机会。
策略源码: 下架合约网格策略