Skip to content

Common Pitfalls Quick Reference (UX-06/07/10)

This page answers three categories of commonly-hit but previously-undocumented issues: cross-surface parameter format differences, sim vs real error code differences, and unlock pwd_md5 field ambiguity. Added in v1.4.60+.

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.60, 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)
  • P0 verification: must pass real at least once (C++ OpenD alignment)
  • External reviewer regression: recommend testing sim + real both

Since v1.4.51, daemon enforces sim vs real consistency

  • trd_env validation required (v1.4.51 BUG-7)
  • acc_id existence validation required (v1.4.51 BUG-8)
  • 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.60+ 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)");
}