Python SDK 接入 futu-opend-rs 注意事项¶
如果你在 futu-opend-rs daemon 上跑 Futu Python SDK (futu-api PyPI 包) 写 agent / one-shot 脚本, 大概率会撞脚本退出悬挂坑. 这是 SDK 自身的设计选择, 不是 daemon bug.
本页给出最小修复 + 推荐姿势.
现象¶
短脚本写完业务逻辑后, 脚本到达 if __name__ == "__main__": 末尾不退出, 卡住几秒到几分钟才被 OS kill 或被用户 Ctrl-C 强退. agent 框架 / cron job / CI 跑大量这种脚本会:
- 进程数累积 → OOM
- 资源 (FD / memory) 不释放
- 后续任务 blocking
根因¶
Futu Python SDK futu/common/sys_config.py 把 ALL_THREAD_DAEMON = False 设为默认. SDK 内部 spawn 的所有网络 / push 回调线程都是 non-daemon thread. Python 主线程结束时会等 non-daemon thread 自然退出, 但这些线程通常不会自然退 (持续循环 reactor / 等待新连接).
为何 SDK 默认 False: 历史决策保护 long-running quant 进程不被主线程意外退出连带 kill 后台心跳 / 订阅. 但对 short-script / agent 场景就反默认.
三层修复¶
来自外部 reviewer 实测验证, 三层兜底:
| 层级 | 位置 | 作用 |
|---|---|---|
| 1. SDK 配置 (根治) | 顶部 import futu 之前 |
SysConfig.set_all_thread_daemon(True). SDK 后续创建所有线程都 daemon, 主业务结束自动 exit |
| 2. 显式 cleanup (兜底) | ctx.close() 后 |
调 NetManager.default().stop() 显式 stop reactor |
| 3. 强退 (last resort) | 脚本末尾 | os._exit(0) 绕残余 non-daemon thread. 不 graceful 但 short-script 可接受 |
推荐: 用 futu_opend_rs_helper.py drop-in¶
我们提供了一个 ~50 LoC 的 helper 模块, 一次性自动应用层级 1 + 2 (层级 3 留给应用自己决定):
examples/python/futu_opend_rs_helper.py— helper 主体examples/python/example_usage.py— 使用 demoexamples/python/README.md— 速查
获取方式: 直接 copy 到你 Python 项目 (无需 pip install).
用法 A — 全局 helper (最少代码改动)¶
# 关键: 必须在 `from futu import ...` 之前 import
import futu_opend_rs_helper # noqa: F401 顶部导入即生效
from futu import OpenSecTradeContext, RET_OK, TrdEnv
ctx = OpenSecTradeContext(host="127.0.0.1", port=22221, is_encrypt=False)
ret, data = ctx.get_funds(trd_env=TrdEnv.SIMULATE, acc_id=28701)
if ret == RET_OK:
print(data)
ctx.close()
# 脚本到这立即退出, 不悬挂
用法 B — with futu_session(...) context manager (推荐, Pythonic)¶
from futu_opend_rs_helper import futu_session
from futu import OpenSecTradeContext, TrdEnv
with futu_session(
OpenSecTradeContext,
host="127.0.0.1",
port=22221,
is_encrypt=False,
) as ctx:
ret, data = ctx.get_funds(trd_env=TrdEnv.SIMULATE, acc_id=28701)
print(data)
# 退出 with 块自动 ctx.close() + NetManager.stop()
不要用 helper 的情况¶
-
24x7 long-running quant 策略: 不要 import 本 helper. SDK 默认
ALL_THREAD_DAEMON=False对长跑应用是保护, 防主线程意外退出连带 kill 后台心跳/订阅. 长跑应用应自己接 SIGTERM 显式 close + 显式管理 ctx 生命周期. -
绝对强退需求: 反馈方层级 3 的
os._exit(0)留给应用决定 — 跳过 atexit / destructor / logging flush. 本 helper 不强加.
SDK 侧理想做法 (我们没权改)¶
理想情况 Futu Python SDK 应:
ALL_THREAD_DAEMON默认True(one-shot scripts safer default)- README 标注 "long-running apps 设 False; one-shot scripts 留 True"
- 内置
atexit钩子自动 cleanupNetManager - 提供
with FutuClient(...)context manager 自动管理生命周期
如果你方便给 Futu SDK 团队提反馈或 PR, 长期对所有用户都有益.
兼容性¶
futu-api>= 7.x 验证 OK (2024-2026 版本)- Python >= 3.8
- daemon 端无要求 — futu-opend-rs / C++ FutuOpenD 都 work (Python SDK 跟 daemon 通过 binary protocol 走, 不区分 daemon 实现语言)