Skip to content

Case: LLM as quant research assistant

Let Claude read your live market data and account state, have it write analysis. No order placement.

Requirements

  • Claude Desktop can see real-time quotes + candles + fundamentals for Tencent / Apple / Tesla, etc.
  • Claude can list your positions + today's orders + historical fills across all accounts.
  • All reads go through MCP; Claude writes nothing.
  • Full audit retained.

Key strategy

Read-only scopes only, no trade:*:

futucli gen-key \
  --id claude-analyst \
  --scopes qot:read,acc:read \
  --expires 90d \
  --bind-this-machine
  • No trade scope → even if Claude decides to place an order, futu_place_order is rejected with missing scope trade:real.
  • Machine-bound → copying keys.json elsewhere doesn't work.
  • 90-day expiry → periodic rotation.

Start

# gateway
futu-opend \
  --login-account 12345678 --login-pwd "$FUTU_PWD" \
  --rest-port 22222 \
  --rest-keys-file ~/.config/futu/keys.json

# MCP (stdio, spawned by Claude Desktop)
# see Claude Desktop config below

Claude Desktop config

~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "futu": {
      "command": "/usr/local/bin/futu-mcp",
      "args": [
        "--gateway", "127.0.0.1:11111",
        "--keys-file", "/Users/you/.config/futu/keys.json",
        "--audit-log", "/Users/you/Library/Logs/futu-mcp-audit.jsonl"
      ],
      "env": {
        "FUTU_MCP_API_KEY": "fc_xxxxxxxxxxxxxxxxxx"
      }
    }
  }
}

Try asking Claude

"Show me the latest quotes for Tencent and Apple, and walk me through today's market."

Claude will call futu_get_quote + futu_get_snapshot + futu_get_kline, then synthesize the analysis.

"What do I hold? What trades executed today?"

Claude calls futu_list_accountsfutu_get_positionsfutu_get_deals.

"Place a buy order for 100 shares of Tencent."

Claude reports the tool call failed with missing scope trade:real. Expected — this key is read-only.

Audit queries

# What did Claude look at this week?
jq 'select(.key_id=="claude-analyst" and .iface=="mcp")' \
  ~/Library/Logs/futu-mcp-audit.jsonl | tail -100

# Rejected calls (insufficient scope)
jq 'select(.key_id=="claude-analyst" and .outcome=="reject")' \
  ~/Library/Logs/futu-mcp-audit.jsonl

Upgrade path

Want Claude to place orders? Not recommended. If you insist:

  1. Generate a separate key with trade:simulate only, run for 1 week.
  2. Configure very tight limits (--max-order-value 10000 --max-orders-per-minute 1).
  3. Only after no surprises, consider trade:real — and still keep strict limits.
  4. Monitor futu_auth_limit_rejects_total{key_id="claude-real"} with long-running alerts.

LLMs will make mistakes; guardrails are mandatory.