Skip to content

Repository files navigation

币安带单监控 + HTX 自动跟单

监控币安「跟单交易」带单员的实时操作,推送到飞书/钉钉,并可选地把他的开平仓动作复制到自己的 HTX USDT 本位永续账户。

币安带单页接口  ──轮询──▶  去重  ──▶  飞书 / 钉钉通知
                                └──▶  HTX 下单(可选,默认关闭)

⚠️ 先读这一段

这个程序会动真钱。开启跟单前请理解以下事实,它们不是代码能解决的:

  • 开仓无条件跟随。 带单员判断失误会被原样复制到你的账户,程序不做任何行情判断。
  • 平仓依赖进程存活。 进程挂掉或币安接口断供期间发生的平仓信号,重启后会因超过信号有效期被跳过,账户里会留下无人看管的裸仓
  • 平仓是全平,会平掉你手动开的仓。 程序区分不了哪部分持仓是它开的,带单员平仓时会把该方向持仓全部平掉。
  • 数据源是币安站内未公开接口,没有 SLA,路径和字段可能随时变动。
  • 轮询有延迟。 默认 60 秒一轮,你的成交价必然滞后于带单员,滑点无法避免。
  • 多笔叠加会放大敞口。 单笔名义敞口默认等于账户全部权益,开满 3 笔就是 3 倍权益敞口。

强烈建议:真跑实盘前,在 HTX 侧对每个仓位另挂一道独立止损单兜底,别把风控全押在这个进程上。


环境要求

项目 要求
JDK 8 以上(源码按 Java 8 编译,实测 21 可运行)
Maven 3.6+
网络 国内需 HTTP 代理,见下节
HTX 账户 仅跟单功能需要;需开通 USDⓈ 本位合约

网络前提(重要)

  • www.binance.com 在国内 DNS 被污染,会解析到无关 IP,必须走代理。
  • api.hbdm.com(HTX)实测部分网络直连超时,也建议走代理。
  • 飞书 open.larksuite.com、钉钉 oapi.dingtalk.com 一般可直连。

自测代理是否可用:

curl -x http://127.0.0.1:7890 -s -o /dev/null -w '%{http_code}\n' \
  https://www.binance.com/bapi/composite/v1/public/promo/campaign/enable
curl -x http://127.0.0.1:7890 -s -o /dev/null -w '%{http_code}\n' \
  'https://api.hbdm.com/linear-swap-api/v1/swap_contract_info?contract_code=BTC-USDT'

两条都返回 200 才继续。Clash 默认端口 7890,Surge 一般 6152,按自己的工具改。


快速开始

1. 构建

mvn clean package -DskipTests
# 产物:target/test-1.0-SNAPSHOT.jar

2. 配置

cp config/application.yml.example config/application.yml
chmod 600 config/application.yml     # 里面有 API 密钥
vim config/application.yml

config/application.yml 会被 Spring Boot 自动加载,不需要任何启动参数。该文件已在 .gitignore 中。

最小可用配置(只监控 + 通知,不跟单):

bn:
  portfolio-ids:
    - "5075281354358777856" # 带单员 ID,见下文
  proxy-host: "127.0.0.1"
  proxy-port: 7890
  notify:
    lark:
      url: "https://open.larksuite.com/open-apis/bot/v2/hook/xxxx"

带单员 ID 从带单页 URL 末尾取https://www.binance.com/zh-CN/copy-trading/lead-details/5075281354358777856 → ID 是 5075281354358777856。名字程序会自动取昵称,不用填。

3. 运行

java -jar target/test-1.0-SNAPSHOT.jar

首次运行会把每个带单员近 24 小时的历史记录静默存为基线,不推送通知。从第二轮起才推送新增操作。


配置项说明

监控(bn.*

配置项 默认 说明
portfolio-ids 带单员 ID 列表,必填。也可用 portfolio-id 填单个,两者取并集
portfolio-labels {} 名字覆盖,如 "5075...": 老熬。留空则自动取币安昵称
interval-ms 60000 轮询间隔。低于 60000 会告警,容易触发币安 WAF
lookback-hours 24 每轮回溯多少小时
page-size 50 每轮拉取的记录条数上限
proxy-host / proxy-port 空 / 7890 代理,国内必填
state-file ./binance-lead-monitor-state.json 去重状态。删掉即重建基线
stats-file ./binance-lead-monitor-stats.json 拉取成功率快照,每轮覆盖写
stats-interval-ms 3600000 统计汇总日志间隔
stats-alert-rate 0.5 窗口内失败率超此值推群告警
notify-on-startup true 启动时在日志里输出最新一条操作做自检,只打日志不推群
cookie / csrf-token 实测公开带单数据无需登录态,留空即可
dump-raw false 打印接口原始响应,排查字段变动时用

通知渠道(bn.notify.*

填了 url 即启用,可多渠道并存;全部留空则只打日志。

bn:
  notify:
    via-proxy: false # 通知一般直连,本地 OneBot 必须直连
    lark:
      url: "https://open.larksuite.com/open-apis/bot/v2/hook/xxxx"
    dingtalk:
      url: "https://oapi.dingtalk.com/robot/send?access_token=xxxx"
      secret: "" # 「加签」模式填 SEC 开头的密钥;「关键词」模式留空
    qq: # 需自行部署 OneBot 11 实现(NapCat 等)
      url: "http://127.0.0.1:3000/send_group_msg"
      group-id: "123456789"
      token: ""
    raw: # 通用 {"text":"..."},自建接收端用
      url: ""

钉钉若用「自定义关键词」安全模式,关键词请设为 带单监控 —— 所有消息都以 【带单监控】 开头。

跟单(htx.*

两道开关都放开才会下真单

htx:
  enabled: false # 总开关
  dry-run: true # 演练:只推送「将要下的单」,不提交

建议放开顺序:先 enabled: true 保持 dry-run: true 跑几天,核对推送里的换算数字,确认无误再关 dry-run

配置项 默认 说明
access-key / secret-key HTX API Key。只勾「合约交易」,不要勾提币,建议绑 IP 白名单
via-proxy false 是否复用 bn.proxy-* 访问 HTX
margin-mode cross cross 全仓 / isolated 逐仓
lever-rate 5 期望杠杆。v5 下单参数里没有杠杆,它是合约维度的独立设置;程序只读交易所实际值用于换算并在不一致时告警,不会自动修改(改杠杆会影响已有持仓强平价)
order-price-type market v5 的 type,只支持 market / limit / post_only
price-match v5 的 price_matchopponent / optimal_5/10/20,与限价互斥
sizing-mode equity_percent 见下节
equity-percent 20 单笔保证金占账户权益的百分比
max-notional-usdt 0 单笔名义金额硬顶,0 表示不限
volume-rounding up 张数取整:up 向上(保证金不够自动下压)/ nearest / down
margin-buffer-rate 0.02 下压张数时留的余量,给手续费与滑点
close-mode full full 市价全平 / sized 按量平
follow-portfolio-ids 只跟这些带单员,留空表示全跟
symbol-whitelist / symbol-blacklist 币安标的过滤,黑名单优先
symbol-mapping {} 币种映射覆盖,如 1000PEPEUSDT: PEPE-USDT
max-signal-age-ms 600000 信号有效期,超时只通知不跟单
dry-run-equity-usdt 1000 演练且未配 Key 时的假定权益,实盘不参与计算

仓位是怎么算出来的

sizing-mode 有三种,默认第一种:

模式 含义 相关配置
equity_percent 按账户权益百分比投保证金,完全忽略带单员仓位 equity-percent
ratio 按带单员成交量的固定比例跟 ratio
fixed_notional 每单固定名义金额 fixed-notional-usdt

默认的 equity_percent 只借用带单员的方向和时机,仓位大小由你自己的账户决定:

目标名义金额 = 账户权益 × equity-percent% × 实际杠杆
张数        = 目标名义 ÷ (contract_size × 单币价格),按 volume-rounding 取整

举例(权益 173 USDT,equity-percent: 20,杠杆 5x):

目标名义 = 173 × 20% × 5 = 173 USDT

BTC-USDT   1 张 = 64.16 USDT  → 3 张 = 192.48  达成 111%   保证金 38.50
SNDK-USDT  1 张 =  1.68 USDT  → 104 张 = 174.72 达成 101%   保证金 34.94

三件容易踩的事

  1. 20% × 5x 意味着单笔名义敞口 = 权益的 100%,即 1 倍账户杠杆。想让敞口只占权益 20%,equity-percent 要填 4。
  2. HTX 下单以「张」为单位,不是 USDT,也不是币。 v5 接口没有按金额下单的参数,所以取整误差不可避免。每张价值越接近目标名义,误差越大 —— BTC 一张 64 USDT,向上取整可能超出 11%;某些低杠杆合约甚至能超 46%。用 max-notional-usdt 设硬顶可以兜住。
  3. 杠杆按合约分别设置。 同一账户里 BTC 可能是 5x 而 SOL 是 3x,导致不同标的的实际仓位大小不一致。程序按交易所实际值算并告警,要统一得去 HTX 逐个合约调。

平仓语义

  • close-mode: full(默认):调 HTX 市价全平接口,不传张数,由交易所按实际持仓全平。好处是没有「查到的张数在下单前已过期」的竞态,也保证带单员离场后本地不留残仓。代价是对方分批止盈时你一次性走完,并且会平掉你手动开的同方向仓位
  • close-mode: sized:按开仓那套换算算出张数再平,带 reduce_only=1,与实际可平量取小。对方分批止盈时更贴近,但对方一次性清仓时你会留下残仓,而且后面不会再有平仓信号了

运行中怎么看状态

随时查:统计快照

cat binance-lead-monitor-stats.json
watch -n5 cat binance-lead-monitor-stats.json
{
  "uptime": "22 分",
  "traders": {
    "5075281354358777856": {
      "name": "熬鹰资本",
      "attempts": 23,
      "failures": 0,
      "failureRate": "0.0%",
      "consecutiveFailures": 0,
      "maxConsecutiveFailures": 0,
      "errorCounts": {}
    }
  }
}

errorCounts 按币安业务码归类,能直接看出是不是同一个毛病在反复出现。

看趋势:汇总日志

stats-interval-ms 打一条,同时给累计和窗口两个口径:

[STAT] 拉取统计(已运行 3 小时 12 分,本窗口 1 小时)
  熬鹰资本:累计 190/192 成功(失败率 1.0%),本窗口 60/60(失败率 0.0%)
    错误分布 code=11012005 ×2

不用盯:自动告警

窗口内失败率超 stats-alert-rate 会推群(样本不足 3 次不判定)。

已知的币安错误码

错误码 含义 处理
11012005 当前系统正忙 偶发抖动,占比高于 10% 时考虑调大 interval-ms
11012030 当前项目已关闭 该带单员已停止带单,不是网络问题

常驻运行(macOS launchd)

~/Library/LaunchAgents/com.you.bn-lead-monitor.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
  "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>com.you.bn-lead-monitor</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/bin/java</string>
    <string>-jar</string>
    <string>/绝对路径/target/test-1.0-SNAPSHOT.jar</string>
  </array>
  <!-- 必须设为项目根目录,否则读不到 config/application.yml -->
  <key>WorkingDirectory</key><string>/绝对路径</string>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>StandardOutPath</key><string>/绝对路径/logs/monitor.log</string>
  <key>StandardErrorPath</key><string>/绝对路径/logs/monitor.err</string>
</dict>
</plist>
mkdir -p logs
launchctl load ~/Library/LaunchAgents/com.you.bn-lead-monitor.plist
launchctl list | grep bn-lead        # 确认已托管
launchctl unload ~/Library/LaunchAgents/com.you.bn-lead-monitor.plist

WorkingDirectory 必须是项目根目录 —— 配置文件和状态文件都是相对路径。


安全须知

  • 绝对不要把密钥写进 src/main/resources/application.yml。那份文件会被打进 jar、也会进 git,它只放 ${ENV_VAR:} 占位。真实凭证一律放 config/application.yml(600 权限 + 已 gitignore)。
  • HTX API Key 只勾「合约交易」,不要勾提币,并绑定 IP 白名单。
  • 币安 Cookie 是账号级会话凭证,泄露等同账号被接管。本程序默认不需要它(公开带单数据免登录),别随手往配置里贴。
  • 提交前自查:
git diff --cached | grep -iE 'secret|access-key|access_token|cookie|hook/'
unzip -p target/test-1.0-SNAPSHOT.jar BOOT-INF/classes/application.yml | grep -E 'key|token|url'

排错

现象 原因与处理
无法连接 www.binance.com / No route to host DNS 污染,必须配 bn.proxy-host
bn.proxy-host 的值填成了配置项名 填成了 bn.proxy-host 字面量,应填 127.0.0.1
启动即报 至少要配一个带单员 portfolio-idportfolio-ids 都是空的
htx.order-price-type 不是 v5 接受的委托类型 填了 v1 的 opponent/optimal_5,v5 只认 market/limit/post_only
HTX 返回 err_code 6023 调到了 v1 老接口。统一账户下账户/持仓/下单必须用 /v5/*
HTX 返回 code 1067 v5 查询/撤单类接口缺 margin_mode
跟单日志全是「可用保证金不足」 已有持仓占满保证金,属正常保护;程序不会盲目丢单
「换算后不足 1 张」 目标名义小于一张的价值,调大 equity-percentlever-rate
「HTX 没有 XXX-USDT 这个合约」 HTX 未上该标的,或需在 symbol-mapping 里手动映射
通知没收到但日志有 看日志里的 [WARN] [lark] / [dingtalk] 行;钉钉常见原因是关键词不匹配
新增带单员后刷了一屏历史 不应发生。基线按人记录,新人首轮静默入库
想重置去重 删掉 state-file,下次启动重新建基线

临时验证配置而不影响线上

# 绕开 config/application.yml,只用 jar 内默认值
java -jar target/test-1.0-SNAPSHOT.jar \
  --spring.config.location=classpath:/application.yml \
  --bn.portfolio-ids=5075281354358777856 \
  --bn.proxy-host=127.0.0.1 --bn.proxy-port=7890 \
  --bn.state-file=./tmp-state.json \
  --bn.notify.lark.url= --bn.notify.dingtalk.url= \
  --htx.enabled=true --htx.dry-run=true

命令行参数优先级最高。用独立的 state-file 和清空的 webhook,就不会污染线上去重、也不会误发到群里。


代码结构

src/main/java/org/example/
├── binance/
│   ├── BinanceMonitorApplication  入口,@EnableScheduling
│   ├── LeadOrderMonitorTask       @Scheduled 轮询、去重、编排
│   ├── LeadOrderClient            币安接口客户端(订单历史、带单员昵称)
│   ├── LeadOrder                  一条带单记录,含指纹去重键
│   ├── SeenStore                  去重状态持久化,按带单员记录基线
│   ├── PollStats                  成功率统计与快照
│   ├── Notifier                   多渠道通知
│   ├── MonitorProperties          bn.* 配置
│   └── NotifyProperties           bn.notify.* 配置
└── htx/
    ├── CopyTradeService           信号 → 委托的翻译与风控
    ├── HtxClient                  HTX v5 客户端
    ├── HtxSigner                  HMAC-SHA256 验签
    ├── CopyTradeResult            跟单结果
    └── HtxProperties              htx.* 配置

几个实现要点

  • 去重键portfolioId|fp:时间|币种|方向|持仓方向|价格|数量。币安该接口不返回订单 ID,只能用指纹。带单员前缀是必须的,否则两人同毫秒的相同操作会互相吞掉。昵称刻意不参与去重键 —— 对方改名不该让历史记录重推一遍。
  • 落盘在下单之前。进程若在下单过程中被杀,重启后不会把同一批信号再执行一遍。
  • 按人隔离拉取。单个带单员失败不影响其他人,只有全部失败才走整体告警。
  • 币安倍数标的1000PEPEUSDT 表示一手 1000 个 PEPE)会自动识别前缀并还原为单币口径,否则数量会差三个数量级。
  • HTX v5 的 1000/10000 前缀识别用的正则分支按长到短排列 —— 若 1000 在前,10000SATS 会被错拆成 1000 + 0SATS

免责声明

本项目仅供技术学习。加密货币合约交易风险极高,可能导致本金全部损失。使用者需自行承担全部后果,作者不对任何资金损失负责。开启自动跟单前请务必先用演练模式验证。

About

监控带单员最新操作并下单

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages