Python SDK tips for futu-opend-rs¶
If you run Futu Python SDK (futu-api PyPI package) on top of a futu-opend-rs daemon for agents / one-shot scripts, you will likely hit a hang-on-exit issue. This is an SDK design choice, not a daemon bug.
This page explains the minimum fix + recommended pattern.
Symptom¶
Short script reaches end of if __name__ == "__main__": but does not exit. Hangs from seconds to minutes until OS-killed or user Ctrl-C. Agent frameworks / cron jobs / CI running many such scripts will:
- Accumulate processes → OOM
- Leak resources (FDs / memory)
- Block subsequent tasks
Root cause¶
Futu Python SDK's futu/common/sys_config.py defaults ALL_THREAD_DAEMON = False. All network / push-callback threads spawned by the SDK are non-daemon threads. Python's main thread waits for non-daemon threads to exit naturally on shutdown, but these threads typically don't (running reactor loops / waiting for new connections).
Why the SDK chose False: a historical decision protecting long-running quant strategies from accidental main-thread exit terminating heartbeat/subscription threads. But it backfires for short-script / agent scenarios.
Three-layer fix¶
Validated empirically by external reviewer, three-layer fallback:
| Layer | Location | Effect |
|---|---|---|
| 1. SDK config (root fix) | At the very top, before import futu |
SysConfig.set_all_thread_daemon(True). All SDK-spawned threads thereafter are daemon, exit when main business finishes. |
| 2. Explicit cleanup (belt + suspenders) | After ctx.close() |
Call NetManager.default().stop() to explicitly stop the reactor. |
| 3. Force-exit (last resort) | End of script | os._exit(0) bypasses any residual non-daemon threads. Not graceful but acceptable for short scripts. |
Recommended: futu_opend_rs_helper.py drop-in¶
We provide a ~50-LoC helper module that auto-applies layers 1 + 2 (layer 3 is left to the application):
examples/python/futu_opend_rs_helper.py— helper moduleexamples/python/example_usage.py— usage demoexamples/python/README.md— quickstart
How to get: just copy into your Python project (no pip install needed).
Pattern A — global helper (minimum code change)¶
# Critical: must import BEFORE `from futu import ...`
import futu_opend_rs_helper # noqa: F401 global side-effects on import
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()
# Script exits immediately, no hang
Pattern B — with futu_session(...) context manager (recommended, 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)
# Auto ctx.close() + NetManager.stop() on with-block exit
When NOT to use the helper¶
-
24x7 long-running quant strategies: do NOT import the helper. The SDK's default
ALL_THREAD_DAEMON=Falseprotects long-running apps from accidental main-thread exit terminating background heartbeat/subscription threads. Long-runners should explicitly handle SIGTERM and manage ctx lifecycle themselves. -
Absolute force-exit need: layer 3's
os._exit(0)is left to the application — it skips atexit / destructors / logging flush. This helper does not impose it.
What the SDK should do (out of our control)¶
Ideally the Futu Python SDK should:
- Default
ALL_THREAD_DAEMON = True(safer one-shot script default) - README annotation: "long-running apps set False; one-shot scripts leave True"
- Built-in
atexithook for automaticNetManagercleanup - Provide
with FutuClient(...)context manager for automatic lifecycle
If you can submit feedback / PR to the Futu SDK team, long-term benefit for all users.
Compatibility¶
futu-api>= 7.x verified (2024-2026 versions)- Python >= 3.8
- Daemon-side: no requirement — futu-opend-rs / C++ FutuOpenD both work (Python SDK talks to daemon via binary protocol, agnostic of daemon implementation language)