跳转至

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.pyALL_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 — 使用 demo
  • examples/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 钩子自动 cleanup NetManager
  • 提供 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 实现语言)