启动速查¶
按使用场景分类的命令速查表。每条命令都可以直接 copy-paste 用——把
X / Y 换成你自己的账号密码即可。
约定
X= 登录账号(手机号 / 牛牛号 / moomoo ID / 邮箱)Y= 密码明文Z= 短信验证码(首次登录时服务端发到手机)
1. 账号平台速选¶
富途有两个独立账号体系:牛牛(auth.futunn.com)和 moomoo
(auth.moomoo.com)。两边都支持数字 ID / 手机号 / 邮箱三种格式
作为 --login-account。同一手机号 / 邮箱可以分别在两边注册独立账号
(不同密码)——用 --platform 显式选平台避免登到错账号。
# 牛牛账号(默认,CN / HK 归属)—— 数字 ID / 手机号 / 邮箱 任选一种
./futu-opend --login-account 12345678 --login-pwd Y
./futu-opend --login-account '+86-13900000000' --login-pwd Y
./futu-opend --login-account 'user@example.com' --login-pwd Y
# moomoo 账号(US / SG / AU / JP / CA 归属)—— 同样三种格式,加 --platform moomoo
./futu-opend --login-account 87654321 --login-pwd Y --platform moomoo
./futu-opend --login-account '+1-4155551234' --login-pwd Y --platform moomoo
./futu-opend --login-account 'user@example.com' --login-pwd Y --platform moomoo
手机号格式必须带区号 + 破折号
手机号请严格使用 +<区号>-<号码> 格式,如 +86-13900000000 /
+1-4155551234,否则将导致识别失败。
2. 首次部署(推荐流程)¶
强烈推荐分两步:先前台 setup 一次完成 SMS,再启动生产实例。这样 systemd / Docker 场景不会在启动时卡 SMS。
# 第一步:前台跑一次 setup(daemon 启动后会立即发 SMS 到手机,然后交互等验证码)
./futu-opend --setup-only \
--login-account X --login-pwd Y --platform moomoo
# 第二步:正常启动(自动用缓存凭据跳过 SMS)
./futu-opend --login-account X --login-pwd Y --platform moomoo \
--rest-port 22222 --grpc-port 33333
--setup-only 完成认证后立即退出,不启动任何 server。凭据会写到
~/.futu-opend-rs/credentials-<hash>.json,30 天内不再要 SMS。
⚠️ SMS 时序与中断恢复¶
首次建立 challenge 时,daemon 会先向 backend 请求并触发 SMS:
- daemon 连 backend → POST
/authority/→ backend 返code=20+device_verify_sig - daemon 调
req_device_code触发 SMS 发到手机 ← 此时 SMS 已到 - daemon 等验证码 — tty 模式下 stdin prompt;非 tty 直接 abort
一旦同一 HOME 中已有仍有效、尚未完成的 challenge,后续启动采用恢复流程:
- 前台 TTY:在发出任何可能替换 challenge 的认证请求前,直接提示输入当前验证码
- systemd / Docker / CI 等非 TTY:未提供
--verify-code时,在发请求前失败, 保留原 challenge,并提示使用同一 HOME 加--verify-code <CODE>重启 - 显式
--verify-code <CODE>:优先复用缓存 challenge 完成验证,不请求新 SMS
注意:
- 如果 daemon 在 systemd / Docker / CI 等非 tty 环境跑,daemon 会 abort 但
SMS 已发到手机。用户手机收到 SMS 后可用
--verify-code <CODE>重启 - 不要盲目重启、清缓存或
--reset-device:这些操作可能丢弃或替换当前 challenge。前台 TTY 直接按提示输入;非 TTY 使用同一 HOME 加--verify-code <CODE> - 如果 challenge 已超时,再按新一轮提示操作,并以手机上最新收到的验证码为准
- 验证码 60 秒有效,用 Telegram / IM 转发时建议带
--verify-code跳过 stdin 延迟
--verify-code 非 tty 场景推荐流程¶
# 1. 首次不带 --verify-code 启动 → daemon 发 SMS 后 abort(SMS 已到手机)
./futu-opend --setup-only --login-account X --login-pwd Y --platform moomoo
# daemon log 含 "📱 v1.4.84 A1: SMS sent to phone; awaiting verification code via stdin"
# 然后因非 tty abort
# 2. 手机收 SMS 后,使用同一 HOME,立即带验证码重新启动
./futu-opend --setup-only --login-account X --login-pwd Y --platform moomoo \
--verify-code 123456
# 期望:复用缓存的验证码上下文,不触发新 SMS,直接完成验证
3. 生产环境(无人值守)¶
systemd¶
/etc/systemd/system/futu-opend.service:
[Unit]
Description=FutuOpenD Rust Gateway
After=network-online.target
[Service]
Type=simple
User=futu
Environment=FUTU_ACCOUNT=...
Environment=FUTU_PWD=...
ExecStart=/usr/local/bin/futu-opend \
--login-account ${FUTU_ACCOUNT} --login-pwd ${FUTU_PWD} \
--platform moomoo \
--rest-port 22222 --grpc-port 33333 \
--rest-keys-file /etc/futu-opend/keys.json \
--rest-tls-cert /etc/futu-opend/rest-cert.pem \
--rest-tls-key /etc/futu-opend/rest-key.pem \
--grpc-keys-file /etc/futu-opend/keys.json \
--audit-log /var/log/futu-opend/audit.jsonl
Restart=on-failure
RestartSec=10
[Install]
WantedBy=multi-user.target
daemon 用户的 cert 和 key 必须可读;健康检查或反向代理用户必须能读取
/etc/futu-opend/rest-ca.pem。
部署步骤:
# 1. 前台跑一次 setup 写 credentials(用 futu 用户身份)
sudo -u futu /usr/local/bin/futu-opend --setup-only \
--login-account X --login-pwd Y --platform moomoo
# 2. 启动 systemd 服务
sudo systemctl enable --now futu-opend
Docker¶
docker build -t futu-opend-rs:1.7.4 .
docker run -d --name futu-opend --restart unless-stopped \
-p 11111:11111 -p 22222:22222 -p 33333:33333 \
-v ~/.futu-opend-rs:/root/.futu-opend-rs \
-v /etc/futu-opend/keys.json:/etc/futu-opend/keys.json:ro \
-v /etc/futu-opend/rest-cert.pem:/etc/futu-opend/rest-cert.pem:ro \
-v /etc/futu-opend/rest-key.pem:/etc/futu-opend/rest-key.pem:ro \
-v /etc/futu-opend/rest-ca.pem:/etc/futu-opend/rest-ca.pem:ro \
futu-opend-rs:1.7.4 \
--login-account X --login-pwd Y --platform moomoo \
--rest-port 22222 --grpc-port 33333 \
--rest-keys-file /etc/futu-opend/keys.json \
--rest-tls-cert /etc/futu-opend/rest-cert.pem \
--rest-tls-key /etc/futu-opend/rest-key.pem \
--grpc-keys-file /etc/futu-opend/keys.json
凭据目录挂载
-v ~/.futu-opend-rs:/root/.futu-opend-rs 至关重要 —— 容器重建后
device_id 和 credentials 需要持久化,否则每次重启都要重新 SMS 验证。
4. 多实例并行(牛牛 + moomoo 同时跑)¶
常见场景:一个账号在牛牛、一个在 moomoo,想同时连两个后端。端口必须错开,
否则 futucli 默认连 1.7.4.0.1:11111 会指向错的实例。
# 实例 A:牛牛账号(默认端口)
./futu-opend --login-account 12345678 --login-pwd Y1 \
--rest-port 22222 --grpc-port 33333 &
# 实例 B:moomoo 账号(端口 +1)
./futu-opend --login-account '+86-13900000000' --login-pwd Y2 --platform moomoo \
--port 11112 --rest-port 22223 --grpc-port 33334 &
v1.4.16 起启动时会检测端口占用,冲突会打 WARN 日志提醒你。
客户端访问:
5. 调试与开发¶
# 详细日志(看协议帧 + HTTP 请求响应)
./futu-opend --login-account X --login-pwd Y --log-level trace
# 只看鉴权流程(POST raw response / salt / tgtgt 关键行)
./futu-opend --login-account X --login-pwd Y --log-level debug 2>&1 \
| grep -E "POST.*raw|salt|tgtgt|error_code"
# JSON 结构化日志(给 logging pipeline / Loki / ELK)
./futu-opend --login-account X --login-pwd Y --json-log
# 单独写审计日志(只记 auth / 交易事件,常规日志不变)
./futu-opend --login-account X --login-pwd Y \
--audit-log /var/log/futu-opend/audit.jsonl
6. 故障恢复¶
device_id 被锁(ret_type=15 或 ret_type=21)¶
# 方法 A:清空文件 + 重新 setup(推荐,最彻底)
./futu-opend --reset-device --setup-only \
--login-account X --login-pwd Y --platform moomoo
# 方法 B:换一个特定 device_id(不触发新 SMS 的情况,比如迁移机器时)
./futu-opend --device-id abcdef0123456789 \
--login-account X --login-pwd Y
# 方法 C:手动清所有状态(怀疑所有缓存都坏了)
rm -rf ~/.futu-opend-rs/
v1.4.17 起,SMS 验证码输错(error_code=21)自动轮换 device_id 重试 2 次,
一般不用手动处理。
密码对了但 error_code=2 账号密码不匹配¶
几乎一定是平台选错了(moomoo 账号发到 futunn 了,或反之):
连接卡住(Connection timed out)¶
v1.4.10 加了 10s 超时,v1.4.11 按地区选 IP 池,v1.4.12 并发 3 IP 竞速。 新版一般不会卡。如仍挂:
# 看具体卡在哪
./futu-opend --login-account X --login-pwd Y --log-level debug
# 如果是海外账号但所在区域 IP 全不通(ISP 屏蔽),只能走 VPN
客户端侧常见症状¶
| 症状 | 原因 | 处理 |
|---|---|---|
connect refused :11111 |
网关根本没起来 / 被别的进程占端口 | 看 futu-opend 控制台日志;v1.4.16+ 启动时会 WARN 端口占用 |
account/password mismatch |
登录凭证错 | 改 --login-account / --login-pwd,或加 --platform moomoo 选对平台 |
device not authorized |
新设备需短信验证 | 前台跑一次 --setup-only 输入验证码 |
no subscription |
先 subscribe 再 quote | 先 POST /api/subscribe 再 POST /api/quote |
gRPC UNAUTHENTICATED |
没带 Bearer Token 或 key 错 | 看下一节配 --grpc-keys-file |
7. 登录密码安全存储(v1.4.18+)¶
避免明文密码出现在 ps aux / ~/.bash_history / 配置文件 backup 里。
opend 按 7 层优先级查密码,发布包推荐 mode-0600 文件:
install -m 600 /dev/null "$HOME/.futu-opend-rs/login-pwd"
# 用安全的本地编辑器写入一行密码
./futu-opend --login-account 12345678 \
--login-pwd-file "$HOME/.futu-opend-rs/login-pwd" \
--platform moomoo --rest-port 22222
macOS 发布包不支持三个不同 ad-hoc signing identity 之间的无人值守 Keychain
共享;set-login-pwd / set-trade-pwd 会在读取秘密前拒绝。
7 层优先级(高到低)¶
| 优先级 | 方式 | 适用场景 | 安全 |
|---|---|---|---|
| 1 | --login-pwd-file <path> |
Docker secrets / systemd LoadCredential | ⭐⭐⭐⭐⭐ |
| 2 | --login-pwd <plain> |
老脚本兼容(会打 WARN) | ⭐ 暴露在 argv |
| 3 | --login-pwd-md5 <hex> |
同上,md5 等同明文 | ⭐ 暴露在 argv |
| 4 | FUTU_PWD env var |
CI / cron / bash export | ⭐⭐⭐ 无 argv 泄露 |
| 5 | OS credential store | Linux / Windows 兼容;macOS 发布包仅有界读取旧条目 | ⭐⭐⭐⭐ |
| 6 | 交互式 prompt(stdin 是 tty) | 本地开发随手跑 | ⭐⭐⭐⭐ 不回显不进 history |
| 7 | 以上都没有 | 报错退出 | — |
部署场景选择指南¶
# A. 本地长期使用(最推荐)
./futu-opend --login-account 12345678 \
--login-pwd-file "$HOME/.futu-opend-rs/login-pwd" ...
# B. Docker(用 secret 挂载到文件)
docker run -v pwd-file:/run/secrets/futu-pwd:ro \
ghcr.io/futuleaf/futu-opend-rs \
--login-account X --login-pwd-file /run/secrets/futu-pwd ...
# C. systemd(LoadCredential 从 EncryptedDir / TPM 读)
# /etc/systemd/system/futu-opend.service
[Service]
LoadCredential=login-pwd:/etc/futu/pwd
ExecStart=/usr/local/bin/futu-opend \
--login-account X \
--login-pwd-file ${CREDENTIALS_DIRECTORY}/login-pwd ...
# D. CI / cron(FUTU_PWD env)
FUTU_PWD='...' ./futu-opend --login-account X ...
# E. 本地随手跑(啥都不配,会弹 prompt)
./futu-opend --login-account 12345678 ...
# → "Login password for account 12345678: "
清除密码¶
8. REST / gRPC / 核心 WebSocket 鉴权¶
生产环境强烈建议加 Bearer Token 鉴权,否则 /api/* 端口对所有客户端
开放。
# 1. 生成 API key(限定 scope + 过期时间)
futucli gen-key \
--keys-file ~/.futu-opend-rs/keys.json \
--scopes quote,trade:read \
--expires 30d
# 2. 启动 opend 带 keys-file
./futu-opend --login-account X --login-pwd Y \
--rest-keys-file ~/.futu-opend-rs/keys.json \
--rest-tls-cert /etc/futu-opend/rest-cert.pem \
--rest-tls-key /etc/futu-opend/rest-key.pem \
--grpc-keys-file ~/.futu-opend-rs/keys.json \
--ws-keys-file ~/.futu-opend-rs/keys.json \
--rest-port 22222 --grpc-port 33333 --websocket-port 44444
# 3. 客户端带 Bearer token 访问
curl --cacert /etc/futu-opend/rest-ca.pem \
-H "Authorization: Bearer <key_plaintext>" \
https://localhost:22222/api/qot/get_global_state
详细的 scope 表和 key 管理见 API Key 授权配置。
9. 网络受限场景(进阶)¶
走 HTTPS 代理(调试 / 抓包)¶
# --auth-server 显式指定时,不会被 attribution 自动切换覆盖(v1.4.15+)
./futu-opend --login-account X --login-pwd Y \
--auth-server http://1.7.4.0.1:19998
用自写的 HTTP 代理(比如 mitmproxy)可以抓到完整的 /authority/salt、
POST /authority/ 请求体,用于调试账号识别问题。
海外连接域名不通¶
富途的海外连接域名偶尔被 ISP 屏蔽。现象:所有 HK/US 地区连接全 connect timeout(10s 后失败),gateway 进 offline mode。
- 全 FAIL,CN 连接能通 → ISP 屏蔽了海外连接 → 走 VPN
- 全 FAIL,443 能通但 9595 不通 → 防火墙挡 9595 → IT 开端口
- 部分通 → 并发竞速会自动选择可用连接
10. 凭据文件位置¶
v1.4.17 起所有持久化文件统一在 ~/.futu-opend-rs/:
~/.futu-opend-rs/
├── device-<hash>.dat # 16-hex device_id(每 account 一份)
├── credentials-<hash>.json # device_sig + tgtgt + rand_key + uid
└── keys.json # (可选)API key 文件
<hash> = md5(normalized_account)[..16],所以 +86-13900000000 和
13900000000 共享同一份文件(归一化会把区号拆出去)。
什么时候删这些文件:
| 情况 | 删哪个 |
|---|---|
| device_id 被服务端锁定 | device-<hash>.dat + credentials-<hash>.json(或用 --reset-device) |
| 换了密码 | credentials-<hash>.json 保留,让重新 SMS 验证 |
| 切换平台(futunn ↔ moomoo) | 无需删,两个平台有独立缓存 |
| 彻底清理 | rm -rf ~/.futu-opend-rs/ |
11. 按券商解锁交易(v1.4.47+)¶
unlock-trade 默认会并发解锁所有 broker(v1.4.31 起的行为)。如果某个
"看不见的子账户"卡住了整个 unlock,可以加 --security-firm 只解一个券商,
精准绕开问题账户:
futucli unlock-trade --security-firm hk # 只解港股(FutuHK / 1)
futucli unlock-trade --security-firm us # 只解美股 / moomoo(FutuUS / 2)
futucli unlock-trade # 默认:所有 broker
--security-firm 的取值可以是 1-7 数字、官方名(FutuHK / FutuUS /
FutuSG / FutuAU / FutuCA / FutuMY / FutuJP)、或短别名(hk /
us / sg / au / ca / my / jp)。传无效值会返回清晰错误并列出
你账户实际可用的 security_firm。
12. FAQ:为什么非交易时段下不了单?(v1.4.47+)¶
现象:周末 / 节假日 / 深夜调 /api/order 或 futu_place_order,服务端返
"错误,请稍候再试 / 请求超时" 这类通用错,怎么改 order_type 都不管用。
原因:OpenD(Rust 版和 C++ 版都一样)是协议透传层——它只能把你的 订单当下发给后端。富途牛牛 / moomoo APP 有一个 app 服务端侧的 预提交队列(APP 服务器缓存你的订单,到开市时间自动触发标准的 PlaceOrder), 但这个队列不和 OpenD 共享。
不要尝试的事:
- HK order_type=AUCTION 只对当天开市前(09:00-09:30)竞价有效,不跨天
- US fill_outside_rth=true 只是盘中允许盘前盘后成交,不是"周末也能挂"
- 这两个 flag 在休市时响应一字不差,不要乱试
正确做法: 1. 等开市后通过 OpenD API 重新下单 2. 用 APP 挂单(APP 独立队列会排到开市自动触发)
已支持的 STOP / MIT / TRAILING_STOP 等条件单仍是 OpenD 当下提交给后端的订单类型, 不是休市预提交队列;休市期间不要把条件单当作 APP 挂单替代方案。
各市场开市时间:
| 市场 | 时段 |
|---|---|
| HK | 周一至五 09:30-16:00 HKT(开盘前 09:00-09:30 可 AUCTION) |
| US | 周一至五 09:30-16:00 ET(HKT 冬令 22:30-05:00,夏令 21:30-04:00) |
| CN A 股 | 周一至五 09:30-11:30 + 13:00-15:00 CST |
| SG | 周一至五 09:00-17:00 SGT |
| JP | 周一至五 09:00-11:30 + 12:30-15:00 JST |
| AU | 周一至五 10:00-16:00 AEST/AEDT |
| CA | 周一至五 09:30-16:00 EST/EDT |
| MY | 周一至五 09:00-12:30 + 14:30-17:00 MYT |
v1.4.47 起 daemon 检测到"通用服务端错 + 对应市场 closed"会自动在错误消息后
追加 【hint】 段说明这层关系,避免你浪费时间试错。
索引¶
- 第一次跑起来:快速开始
- 深入主题:API Key 授权配置 / MCP 接入 LLM / 部署到生产
- 完整 CLI 参数表:CLI 参数
- 遇到新问题:Contact 反馈