ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

AutoHedge:加密货币现货自动对冲与调仓系统

AutoHedge:加密货币现货自动对冲与调仓系统 我在2022年第一次认真做对冲时才意识到手动盯盘是件反人性的事。现货仓位一多行情稍微抖一下就得掏出计算器算该补多少空单等算完、打开交易界面、下完单行情已经走了好几步。那段时间我几乎每天处于一种“追着价格跑”的状态直到我把这套逻辑全部写成代码才有了AutoHedge这个项目。AutoHedge说白了就是一个帮我自动完成“持仓保护”的交易机器人它会根据现货仓位自动计算需要对冲的空单数量在合约市场下卖单行情变化超出设定区间时自动调仓资金费率合适的时候保持对冲仓位吃费率出现异常时自己停手并报警。这篇文章我会把整套系统的设计思路、仓位计算逻辑、回测中容易被美化的环节、实盘里绕不开的细节以及风控熔断机制一五一十讲清楚。适合手里有现货但想用合约做保护的个人交易者也想把对冲逻辑工程化、减少手动操作的量化方向开发者参考。1. 为什么要自己写一套自动对冲工具1.1 手动对冲的三个致命问题我最早做对冲的方式很简单在现货平台买入一定数量的资产然后在合约平台开等值的空单。听着容易真正操作起来全是问题。第一个问题是算不准。现货价格在变合约价格也在变两个市场之间的价差随时在拉大或者收敛。上午开好的空单数量到下午可能就对不上了。比如你持有2个BTC开空2个BTC的永续合约价格下跌时现货亏钱、空单赚钱这个逻辑没错但BTC价格在波动过程中你开空的数量是按“当时价格”折算的如果之后币价涨了你的空单对应的美元价值跟现货就不匹配了敞口又暴露出来了。第二个问题是下不快。行情剧烈波动的时候从战术决策到鼠标落下去那几秒钟可能就是几百美元的滑点。尤其是大饼这种波动率极高的品种手动挂限价单经常被瞬间穿越改挂市价单又容易被扫一波。第三个问题最坑——情绪。浮亏的时候不敢加仓生怕“对冲变成加杠杆”盈利的时候又想赶紧平掉空单结果平早了后面行情回来还是裸奔。手动操作最大的问题不是手速是纪律。而纪律这件事人永远不如代码可靠。1.2 现成工具为什么不顶用可能有人会说现在主流的交易平台不都带“套保”功能吗我排查过一圈多数平台的套保功能只是针对合约账户内部的仓位跟你钱包里的现货是脱节的。它的本质是“一键市价平仓”而不是“持续盯住现货敞口自动调整空单”。市面上确实也有不少量化交易框架比如很流行的开源交易机器人项目功能确实强能对接几十个交易所能写各种策略。但问题是它太“大”了为了通用性牺牲了专注度。我想做的是把“现货市值—合约空单市值”的偏差精确控制在目标范围内并且把资金费率、调仓阈值、风控熔断这些参数全部做成可配置。通用框架里这些逻辑分散在各个模块改起来非常费劲不如自己写一套轻量的。还有一个很实际的原因现成工具大多是“策略代码”不是“风控系统”。它们会把精力放在如何开仓平仓上但很少关心“如果只成交了一条腿怎么办”“如果API连续超时怎么办”这类极端情况。而对冲交易真正吃人的地方恰恰是这些极端情况。1.3 AutoHedge的设计目标我立项的时候给AutoHedge定了四条硬性要求仓位自动计算每隔几秒采样现货持仓和合约持仓算出目标空单数量与当前空单数量的偏差。自动下单调仓偏差超过阈值时自动挂限价单超时未成交自动撤单重发。资金费率感知费率窗口前暂停调仓费率合适时保持持仓不合适时主动对冲锁定。风控熔断API连续失败、持仓偏差过大、亏损超限、下单超时都会触发对应等级的熔断。第一版我用一个周末写出来了就是一个轮询脚本加几个下单函数。后面陆陆续续加了状态持久化、WebSocket行情、监控告警整整打磨了三个多月才敢让它挂真实的小仓位跑。下面我把核心逻辑一条条拆开讲。2. 对冲仓位是怎么算出来的2.1 核心公式目标对冲数量AutoHedge最核心的函数就一个计算目标空单数量。公式写出来其实非常简单N_target V_spot / (P_contract * M) * HV_spot当前现货市值单位是计价币比如USDTP_contract合约当前价格M每张合约对应的标的数量合约面值H对冲比例系数默认1.0拿BTC永续合约举例假设合约面值是0.001 BTC/张你持有2个BTC现货当前BTC价格为60000 USDT那么目标空单数量就是V_spot 2 * 60000 120000 USDT N_target 120000 / (60000 * 0.001) 120000 / 60 2000 张也就是说你需要开2000张空单才能完全对冲2个BTC现货的风险敞口。H系数默认是1.0但如果你觉得市场波动率很高想稍微激进一步可以设成0.8只对冲80%的敞口反过来如果特别怕回撤可以设成1.2超量对冲一部分。2.2 用指数价格而不是最新价这个地方我踩过坑。第一版我直接用合约最新价来计算目标仓位结果遇到一次市场插针最新价瞬间偏离真实价值好几个百分点AutoHedge立刻算出“仓位偏差巨大”疯狂下了一堆空单。等价格回归正常仓位已经变成超量对冲现货涨的时候空单在亏钱。后来我把计算基准换成了指数价格。指数价格一般是多家主流现货交易所价格的加权平均不容易被单一交易所的异常成交影响。具体做法是先通过WebSocket订阅交易所提供的指数价格流如果拿不到指数价就用多个现货交易所的价格自己算加权平均。下单价格仍然使用合约盘口的合理价格但计算目标数量时一定用指数价格。这样即使合约价格被插针目标仓位也不会乱跳。2.3 调仓阈值别让系统变成高频交易器如果每次价格变化都调仓AutoHedge会变成一台高频交易机器手续费和滑点能把利润吃穿。比如价格涨0.1%现货市值变了0.1%对应空单数量也要变0.1%。看起来不多但如果一天触发几百次调仓每笔都吃手续费回测时利润就全没了。我的解决方案是引入“死区Band”机制。只有当目标空单数量与实际空单数量之间的差额超过阈值时才执行调仓。阈值我用的相对值按当前持仓市值的百分比计算。实测下来0.2%到0.5%是比较合理的区间太小了频繁调仓太大了敞口暴露过多。我默认用的是0.3%。对应的伪代码逻辑如下def check_rebalance(): target_qty calc_target_qty(spot_value, index_price, contract_multiplier) current_qty get_current_short_qty() diff target_qty - current_qty band spot_value * 0.003 / (index_price * contract_multiplier) if diff band: place_order(sell, diff) # 空单不足补空单 elif diff -band: place_order(buy, -diff) # 空单过多买入平仓 else: keep_holding() # 偏差在死区内不动作死区的引入还有一个好处它天然起到平滑作用。价格在小范围波动时系统完全不动一旦趋势出来偏差累积到阈值之后才出手相当于滤掉了市场噪声。2.4 资金费率怎么进策略永续合约的资金费率是AutoHedge另一个收入来源。资金费率每8小时结算一次如果市场多头情绪浓资金费率为正多头向空头支付资金费。也就是说你持有现货空单的组合在正费率的行情里现货不受影响空单还能持续收到资金费这是典型的 delta 中性吃费率策略。但资金费率不是恒定不变的。它由基础利率和溢价指数两部分组成市场情绪越极端费率越高。AutoHedge会在每个结算窗口之前获取下一次预估费率判断是否值得保持当前对冲仓位预估年化收益高于阈值保持空单头寸继续吃费率。预估年化收益低于阈值但为正保持对冲但不再加仓。预估费率为负这时持有空单不仅要付资金费还要承受费率损失系统会提示是否关闭对冲或者直接按照配置自动平掉空单。一个简单年化收益估算年化费率收益 当前资金费率 * 3每天3次 * 365如果单次费率为0.01%简单年化就是10.95%扣除手续费和滑点之后依然有正收益。资金费率策略的精髓是不要在结算前最后一刻追费率因为那时候费率经常已经被套利者抢跑吃掉了你冲进去接的是高价筹码。3. 系统架构与模块分工3.1 整体流程AutoHedge不是一个大脚本而是拆成了几个各司其职的模块。整体数据流是这样的行情采集模块通过WebSocket订阅指数价格、合约最新价和盘口数据推送给策略引擎策略引擎维护一个状态机每三秒运行一次计算目标仓位、判断调仓信号一旦触发调仓信号进入风控模块做前置检查通过后交给订单执行模块执行模块负责下撤单、跟踪成交、处理超时所有状态变化和成交记录写进数据库监控面板实时展示当前持仓偏差、盈亏、API延迟等指标。模块之间通过消息队列解耦我用的Redis Stream就算订单执行模块临时卡住行情采集也不会停策略引擎还能继续计算风险敞口。3.2 行情源与订单执行选型行情采集我优先走WebSocket因为REST轮询的延迟太高而且容易触发交易所的限频。最合适的方式是WebSocket订阅实时K线和盘口增量本地维护一个order book下单时从本地拿盘口数据计算最优限价。订单执行不走WebSocket原因很简单WebSocket是推送式的没有可靠的请求-响应确认机制下单这种敏感操作需要明确的“已收到”“已成交”回执。所以下单、撤单、查单全部走REST API带时间戳签名确保请求语义明确。接口库我用了CCXT因为它统一了主流交易所的API格式切换交易所不用改写整个系统。但也有一个问题CCXT对某些冷门接口支持不完全而且每次封装都要多一跳。后来我在核心的下单路径上直接用原生HTTP请求只在行情和余额查询上保留CCXT速度和可控性都提升了。如果你打算长期维护建议核心路径自己写辅助功能用库。3.3 状态存储与监控每一轮策略计算结果我都会存一条快照包括时间戳、现货市值、指数价格、当前空单数量、目标空单数量、偏差比例、资金费率预估、API延迟等。有了这些历史数据出问题的时候可以精确回放某个时间点系统为什么下了那笔单是因为价格跳了还是因为仓位计算有bug没有快照排查这类问题基本靠猜。实时状态用Redis存字段很精简比如现货市值、持仓数量、风控状态、最近一次调仓时间。监控面板用的是Grafana连接时序数据库展示偏差变化曲线和资金费率走势。告警通过Telegram机器人推送API连续失败、持仓偏差超出风控阈值、系统心跳中断都会触发不同级别的告警。3.4 部署位置对冲交易对延迟极其敏感。不是说你手动操作时的那几秒感觉不出来而是系统自动调仓时如果服务器离交易所API机房太远RTT可能高达两三百毫秒。平时的限价单没太大影响但遇到行情突变、需要立刻撤单改单的场合这些延迟就是真金白银。我强烈建议把AutoHedge部署在主流云厂商的海外机房尽量靠近你所使用交易所的API服务器位置。部署前先用命令行工具测一下到交易所API域名的延迟选延迟最低的节点实测能相差一个数量级。另外服务器时间必须同步NTP签名请求时间偏差太大会直接鉴权失败这个坑很多第一次做的人都会踩。4. 回测里最容易被“美化”的环节4.1 手续费和资金费率建模很多人在回测里只设一个固定手续费率比如0.05%但我实测下来maker和taker的区别极大。挂限价单被动成交一般是0.02%到0.04%吃单主动成交可能要0.05%到0.07%遇到深度不好的币种滑点更离谱。回测里把手续费统一按一个值算等于给策略贴金。资金费率方面我重新拉取了历史资金费率数据在回测里模拟每个结算时间点的资金收取。这个必须按真实的时间戳做不能简单按日折算因为费率结算存在“谁在结算前持有谁收钱”的规则持仓时间不同收益完全不同。4.2 滑点模型固定滑点模型是最天真的假设。真实滑点取决于你下单的量和当前盘口深度。一个单子如果只有几个BTC直接在盘口第一档成交滑点几乎为零但如果要下几百张合约可能要把盘口吃穿好几层实际成交均价远差于盘口价。我的做法是回测时分两段模拟限价单部分假设按盘口价成交市价单部分按盘口深度分档计算冲击成本。所以回测引擎里我保留了一个简化的ob模型逐档消耗盘口挂单。这样回测结果能更接近真实。4.3 常见回测陷阱回测中最容易犯的错误是前视偏差。比如你用当根K线收盘之后才有的数据去生成交易信号然后假设自己在K线开始的时候就成交了——这种偏差在趋势策略里可能不明显但在对冲策略里会被无限放大因为对冲策略赚的就是价差价差本身很小任何微小的假设错误都会吃掉大部分利润。另一个问题是成交时间。信号产生到订单到达交易所之间有时间延迟回测里不能假设信号一产生就立即成交。我第一版回测引擎就犯了这个问题后来强制加入了一个延迟模块最小延迟50ms、最大延迟500ms按随机分布注入回测结果立刻“难看”了很多但也真实了很多。4.4 回测到实盘的落差案例我做过一次BTC永续资金费率套利的回测结果非常漂亮年化27%最大回撤不到3%。当时我心里已经盘算着上更大仓位了但理智告诉我先跑实盘小仓位验证。实盘第一周的账面收益只有年化4.1%差距大得吓人。复盘后找到了几个原因第一回测里假设每次都能抢在费率结算前15分钟开好仓但实盘里结算前半小时价差就已经被人抢跑开仓点位变差第二回测里的滑点模型低估了市价单冲击成本第三实盘中的部分成交导致仓位偏移频繁触发调仓手续费被反复收割。那次之后我给回测模块加了两个“保守系数”所有收益乘以0.6所有成本乘以1.5。虽然数字不再吸引人但后续实盘表现跟回测的匹配度高了很多。回测的意义不是给你一个“可以赚多少”的预期而是告诉你“亏损最坏能坏到什么程度”这个认知比收益率重要十倍。5. 实盘中最需要处理的细节5.1 部分成交与超时重试限价单最大的坑是部分成交。你以为下了一笔2000张的空单结果只成交了800张剩下1200张挂在订单簿上。如果AutoHedge不识别这个状态直接按“已下2000张”记录仓位那实际对冲仓位就是不足的敞口在悄悄暴露。我的订单执行逻辑是这样设计的下单后立即进入“等待成交”状态。每隔2秒查一次订单状态判断累计成交数量。如果订单在指定时间内默认20秒没有完全成交撤掉剩余部分。根据撤单后剩余待成交数量重新生成补单指令。连续补单超过3次仍未成交转用市价单完成剩余数量并记录额外滑点成本。这样设计的好处是大部分时间挂限价单省手续费遇到行情突变时又有市价单兜底不会让仓位偏差拖太久。5.2 行情断连与API限流WebSocket连接在公网上被断开是常态不是意外。我让行情模块维持一个心跳计数器每5秒检查一次最近收到的消息时间超过15秒没有任何推送就主动断开重连。重连之后的第一件事不是继续跑策略而是重新同步一次订单簿快照因为断连期间盘口已经变了本地旧数据不能再用。REST API限流的问题也很现实。交易所对下单接口的限频通常是每分钟几十次到几百次不等。我的处理方案是所有REST请求带指数退避重试第一次失败等1秒第二次等2秒第三次等4秒最多重试5次。如果连续失败超过阈值直接触发风控熔断而不是无限重试加重限频惩罚。5.3 资金费率结算瞬间的波动资金费率结算前15到30分钟是套利资金最活跃的时期价差可能被压缩到几乎没有利润。这时候强行调仓不仅吃不到费率还会付出不必要的滑点和手续费。我把系统逻辑设计成结算前30分钟冻结调仓信号只监控不动手结算完成之后15分钟再恢复正常调仓。冻结期间如果发生极端行情仓位偏差异常大怎么办我加了一个例外规则如果偏差超过正常阈值的三倍允许跳过冻结窗口强制执行风控减仓。这种“一般情况让位于成本控制极端情况让位于风险控制”的分级处理兼顾了收益和安全性。5.4 多币种与多账户的统一缠错AutoHedge可以同时监控多个币种的对冲状态。每个币种有独立的目标仓位计算、独立的调仓阈值但全局风控需要统一汇总。比如你有BTC和ETH的现货对应开了两种永续空单单个币种的偏差都在范围内但如果BTC偏差为正、ETH偏差为负组合起来可能整体敞口已经偏了。我在状态面板里加了一张汇总表币种现货市值当前空单市值目标空单市值偏差比例风控状态BTC120000118500120000-1.25%正常ETH500004940050000-1.20%正常SOL200001500020000-25.0%告警偏差比例超过5%自动标黄超过10%自动触发该币种熔断全局所有币种总偏差超过5%触发全系统告警。这种多层级风控在单一币种上感觉不出重要性多币种一起跑的时候就是救命稻草。6. 风控与熔断这套系统真正的护城河6.1 三层熔断机制很多人觉得AutoHedge的核心价值是“自动调仓省心”但我的真实体感是这套系统最值钱的部分是风控熔断。执行层面我做了三层熔断第一层单笔订单熔断。一笔订单连续重试5次失败或者从下单到撤销重发超过3轮就暂停这个币种的交易逻辑只保留监控同时推送告警。第二层单币种风控熔断。某个币种的未实现盈亏超过设定的最大亏损额度比如总资金的2%强制平掉该币种所有对冲仓位并把状态置为“手动恢复”。第三层全局熔断。所有币种的总持仓偏差、总未实现盈亏、API连续错误次数同时满足多重条件时全部平仓并永久停用自动化需要人工确认才能重新启用。设置三层而不是一层是因为单点故障不可避免。某一笔订单卡住了不代表系统坏了某一次API抖动也不值得全平。分级处理可以避免“小事大惊小怪、大事来不及反应”。6.2 状态持久化与手动接管AutoHedge每次下单前后的状态变化都会写入数据库。如果程序崩溃重启它会先从数据库恢复“当前持仓快照”自动比对交易所账本数据修正差异后再继续运行。这样能避免重复下单或者漏掉撤单。手动接管是最后一道屏障。系统面板上有一个“锁定”按钮按下去之后所有自动调仓逻辑停止只保留撤销未完成订单和监控功能。还有一个“紧急全平”按钮按下后按优先级顺序把所有对冲仓位全部市价平掉。这两个按钮我没有做成二次确认因为极端情况下可能没时间弹窗确认。6.3 真实踩坑当风控逻辑本身有bug有一个教训我必须详细说。某次升级代码我把“未实现亏损超过限额”的风控条件写成了“未实现盈利超过限额”。结果行情下跌时系统不仅没有触发熔断反而因为仓位偏差扩大而在持续补仓越跌越补直到触发全局熔断才停下。那次事故吃掉了我之前将近一个月的利润而且完全是我自己的代码问题。复盘之后我做了两件事。第一在回测引擎里加入故障注入测试模拟行情暴跌、API超时、部分成交等各种异常场景验证风控能否按预期触发。第二上线前强制在模拟盘跑至少24小时期间人为制造行情波动和接口故障确认所有熔断路径都有效。这个习惯我一直保留到现在每次改风控逻辑必走这套流程。7. 这套系统还能扩展成什么7.1 跨市场基差套利AutoHedge目前的逻辑是“现货永续空单”的对冲但同样的框架可以很自然地扩展到跨市场基差套利。现货与永续合约之间经常存在价差当合约价格明显高于现货时做多现货、做空合约等基差收敛后平仓赚的就是这个价差。AutoHedge的仓位计算模块、订单执行模块和风控模块基本不用改只需要替换策略引擎里的信号逻辑。7.2 期权波动率对冲如果是做期权卖方delta对冲是一个至少每天都要处理的日常工作。期权持仓的delta会随标的价格、波动率、时间变化而不断漂移手动做delta对冲的人都知道这有多累。AutoHedge的仓位计算模块扩展一下接入期权的Greeks计算就能自动按照当前持仓delta对冲到中性甚至还可以根据gamma的大小决定调仓频率——gamma大的时候更需要勤快调仓gamma小的时候死区可以放宽。7.3 面向多用户的服务化改造做到后面我开始考虑把AutoHedge的核心能力封装成服务。每个用户可以配置自己的交易所API key、对冲比例、调仓阈值、风控参数系统按用户维度隔离状态。关键的安全约束是API key绝不能上传到中心化服务器只能留在用户本地的客户端进程里。服务端只负责策略模板下发和状态展示签名和下单都在用户侧完成。这个架构会复杂很多但安全性是自动化交易工具不可妥协的底线。AutoHedge从最初的一个周末原型到现在稳定运行了将近一年我最深的体会是它不是一个“买入然后躺赚”的印钞机而是一个把交易纪律固化下来的执行工具。它让我在行情剧烈波动时不再需要盯盘也让我避免了无数次情绪化操作。每次解决一个实盘暴露出来的问题我都觉得这个系统比前一天更可靠了一点。如果你也想做类似的对冲系统我的建议很简单先用模拟盘跑满一个月把各种异常场景都验证一遍再上最小仓位之后再慢慢放大。代码可以帮你执行纪律但制定纪律这件事永远只能靠你自己。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进