Skip to content

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_md5 field 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 MSFTsymbol="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):

echo -n "your-trade-password" | md5sum
# Or macOS:
echo -n "your-trade-password" | md5

Python:

import hashlib
pwd_md5 = hashlib.md5(b"your-trade-password").hexdigest()

Rust:

use md5::{Md5, Digest};
let pwd_md5 = format!("{:x}", Md5::digest(b"your-trade-password"));

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.
  1. Run futucli quote-rights or futucli user-info to inspect realtime rights.
  2. If realtime rights are absent, use snapshot for available delayed snapshots.
  3. If you need streaming pushes, order book, or realtime quote, upgrade the corresponding market / product realtime right.