Common Pitfalls Quick Reference (UX-06/07/10/QOT)¶
This page answers four categories of commonly-hit but previously-undocumented issues: cross-surface parameter format differences, sim vs real error code differences, unlock
pwd_md5field ambiguity, and quote rights / delayed snapshot boundaries. Added in v1.4.84+.
1. Cross-surface parameter format (UX-07)¶
The same field has different names / formats across surfaces. Mixing up causes silent failures.
code / symbol field¶
| Surface | Field | Format | Example |
|---|---|---|---|
REST /api/* |
code |
With market prefix | "US.MSFT" / "HK.00700" |
gRPC futu.Trade |
code |
With market prefix | Same |
CLI futucli |
--code |
Without prefix (with --market) |
--market US --code MSFT |
MCP futu_* tools |
symbol |
With market prefix | "US.MSFT" |
market / trd_market field¶
| Surface | Field | Type | Values |
|---|---|---|---|
| REST / gRPC | trd_market |
int32 |
1=HK, 2=US, 3=CN, 4=HKCC, 5=Futures, 6=SG, 15=JP |
| CLI | --market |
String | HK / US / CN / HKCC / Futures / SG / JP |
| MCP | market |
String | Same as CLI |
Cross-surface conversion checklist¶
- REST → CLI:
code="US.MSFT" trd_market=2→--market US --code MSFT - CLI → MCP:
--market US --code MSFT→symbol="US.MSFT" - MCP → REST:
symbol="US.MSFT"→code="US.MSFT" trd_market=2
Since v1.4.84, daemon strips prefixes uniformly¶
All entry-level functions (derive_security_type / derive_sec_market /
is_futures_code / is_option_code / parse_option_dte / cache key) now
automatically strip HK. / US. prefixes at entry. So with or without
prefix works.
But we recommend following surface conventions (table above) to avoid regressions if daemon behavior changes in future.
2. sim vs real error code differences (UX-06)¶
Important: sim accounts and real accounts may return different errors for the same invalid request. Testing on sim does not guarantee real will work.
Typical differences¶
| Scenario | sim behavior | real behavior | Lesson |
|---|---|---|---|
Wrong symbol format (US.MSFT pre-v1.4.56) |
Both formats returned -1 generic | With-prefix fails / without-prefix works | sim couldn't catch prefix bug |
| trd_env mismatch (real acc + trd_env=0) | Pre-v1.4.50: not validated | Backend rejected | sim false pass |
Non-existent acc_id (999999) |
Returned empty list | Backend "Nonexisting acc_id" | sim misled "no assets" |
| Futures prefix mis-identification (pre-v1.4.56-57) | Both possible returned -1 | Exact 110005 field mismatch | sim couldn't catch |
Rules¶
- Development: use sim (safe, no real money)
- Critical trade verification: must pass real at least once (C++ OpenD alignment)
- Regression verification: recommend testing both sim + real
Since v1.4.51, daemon enforces sim vs real consistency¶
- trd_env validation required (since v1.4.51)
- acc_id existence validation required (since v1.4.51)
- camelCase / snake_case normalize (v1.4.45 serde drop defense)
But cannot eliminate 100% — backend-level sim vs real are different service paths.
3. unlock_trade.pwd_md5 field ambiguity (UX-10)¶
UnlockTradeReq.pwd_md5 field name suggests MD5 hash, but is unclear on:
- Is it a hash, or plaintext?
- If hash, hash of what? Lowercase hex or uppercase?
The truth¶
Must be MD5 hash of trade password, lowercase hex, 32 characters.
Why MD5 (aligned with C++ OpenD)¶
- Backend historical design: cipher protocol uses
pwd_md5, not plaintext - Rust daemon aligned since
v1.4.6+ - If you send plaintext, backend signature check fails and returns generic error (won't tell you the field format is wrong)
How to generate¶
CLI helper (recommended):
Python:
Rust:
Common mistakes¶
- ❌ Plaintext password (like
"fuTUnn!ryA88") → daemon returns generic error, unlock didn't succeed - ❌ MD5 uppercase hex (like
"0FE6...") → backend check fails (different byte repr) - ❌ SHA1 / SHA256 instead of MD5 → fails
- ❌ Hash with salt → fails (backend doesn't salt)
- ✅ Pure MD5, 32-char lowercase hex (like
"a1b2c3d4e5f6789012345678901234ab")
Future v1.4.84+ may add stricter error hints¶
Currently daemon passes invalid pwd_md5 to backend; backend returns generic error. Future may add entry-check:
if pwd_md5.len() != 32 || !pwd_md5.chars().all(|c| c.is_ascii_hexdigit()) {
return error("unlock_trade.pwd_md5 must be 32-char lowercase hex (MD5 hash)");
}
4. Quote rights and delayed snapshot boundaries¶
quote-rights / user-info show the current account's quote-right overview by
market and product category. They help you determine whether the account has
realtime quote rights, but different APIs handle "no realtime right" differently.
Path differences¶
| Path | Behavior |
|---|---|
snapshot / /api/snapshot / futu_get_snapshot |
One-shot snapshot. When the backend provides delayed snapshots, this path may return available delayed data without realtime rights. |
quote / subscribe / orderbook |
Realtime quote paths. They require the corresponding realtime right and do not automatically fall back to delayed quotes. |
quote-rights / user-info |
Display paths. They show current rights, but do not imply every product category has the same delayed-data shape. |
Common misunderstandings¶
- "snapshot returns data, so subscribe should too": not necessarily. Snapshot is a one-shot request; subscribe / quote / orderbook are realtime paths and have stricter permission gates.
- "crypto rights fields exist, so every
CC.*symbol should return": not necessarily. The symbol must still exist in static security data. Unknown symbols return an error instead of an empty success. - "delayed data fields should be identical across products": not necessarily. Stocks, options, futures, crypto and other products may expose different delayed fields; trust the actual snapshot response shape.
Recommended flow¶
- Run
futucli quote-rightsorfutucli user-infoto inspect realtime rights. - If realtime rights are absent, use
snapshotfor available delayed snapshots. - If you need streaming pushes, order book, or realtime quote, upgrade the corresponding market / product realtime right.
Related docs¶
guide/cli.md— CLI command listguide/rest.md— REST API endpointsguide/mcp.md— MCP tools listchangelog.md— Version history