Tape events and statistics¶
Enable the gateway's --enable-desktop-phase2-reads explicitly. The feature defaults OFF and live backend behavior is unverified. All three operations require qot:read.
| Operation | REST POST | MCP | CLI |
|---|---|---|---|
| History | /api/tape-reading-events |
futu_get_tape_reading_events |
tape-reading-events |
| Statistics | /api/tape-reading-statistics |
futu_get_tape_reading_statistics |
tape-reading-statistics |
| Subscription | /api/set-tape-reading-subscription |
futu_set_tape_reading_subscription |
set-tape-reading-subscription |
Generic gRPC uses the same public messages. Explicit extension_version: 1 is required. All three CLI commands use --ws-url, --api-key / FUTU_API_KEY, and --c2s-json. MCP uses --tape-ws-url / FUTU_MCP_TAPE_WS_URL for its authenticated native binary WebSocket transport. Use the actual configured listener URL, not the REST /ws address or a guessed port. Credentials enter only the Authorization handshake header.
Reads¶
Tape markets are HK=1, US=2, CN=3, SG=6, CA=7, AU=8, JP=9, MY=10, KR=11. These differ from other quote market enums; 0 is not “all markets”.
event_list_type selects market (1), stock_id_list (2), or category_id (3). Use actual security/category identifiers. Explicit iterator and size are required. Preserve the returned cursor: response cursor 0 is terminal, an absent cursor is not. An empty ID selection may return a local empty result marked local_only, without inventing a backend cursor or acknowledgment.
Optional begin_time/end_time are seconds; zero is treated as absent. filter_event_type preserves integer types, and an empty filter uses backend user preferences. History retains raw envelopes and integer projections for known payloads. decode_state is decoded, unknown_type, or malformed_payload; unknown formats are not guessed.
Statistics use {"extension_version":1,"stock_market":1}. An absent statistics object remains absent, not a zero snapshot. Integer units and optional-field presence remain part of the public message contract.
Market-restricted callers receive only explicitly authorized data. Explicit market requests can fail before a query. If any history row is unclassifiable or unauthorized, the whole page fails; rows and cursors are not silently rewritten. A CN aggregate is not authorized by SH or SZ alone. Local permission errors have nonzero ret_type, err_code=403, and no s2c; backend business failures preserve their complete s2c.retcode. CLI and MCP preserve the full error response.
Subscription lifetime¶
{"extension_version":1,"stream_id":"market-watch","operation":1,"sub_types":[1],"stock_market":1,"enable_statistics":true,"enable_events":true}
Operations: Add=1, Remove=2, Clear=3, SetDelivery=4. Target classes: market=1, category=2, self-selected stocks=3, single stocks=4, using stock_market, category_id, self_selected_stock_ids, and single_stock_ids respectively. First Add requires both delivery booleans. Omitted preferences retain existing values; false disables delivery. SetDelivery requires an existing stream. Empty Remove targets clear only the selected classes in this stream.
Clear accepts only stream identity and version. Responses distinguish confirmed, pending, rejected, unavailable and local-only effects; shared reference-count hits are not new backend acknowledgments. Cancellation removes local interest even offline or with the feature disabled, preventing unwanted replay. Physical connection closure releases its subscriptions. Stable REST/gRPC logical streams require explicit Clear.
CLI Add continuously prints events, statistics and gaps until Ctrl-C clears its own stream and closes the connection. Each CLI invocation owns a separate connection; another process cannot modify it by reusing the same stream name.
Modern MCP provides private futu://tape/… resources, read through resources/read, with update hints through subscriptions/listen. Legacy MCP sends logging notifications and releases subscriptions when its service closes. A stream's modern/legacy delivery mode is immutable; stop it before creating it in another mode. Resource operations recheck the caller. Clear releases the resource. Transport termination or dropped_events>0 requires history resynchronization. Existing frontend bounds are reused: a 64-event queue, 128 shared resource permits, and a four-hour resource lifetime. These are not backend subscription limits.
REST and gRPC delivery¶
An authenticated REST /ws client sends {"action":"tape-attach","stream_id":"market-watch"} to bind its own logical stream. tape-detach only detaches delivery. Actual messages contain type=tape, stream_id, proto_id, and body_b64 holding the public Update protobuf.
Set gRPC SubscribePushRequest.stream_id to bind the current stable session's stream. Use the same Bearer and grpc-session-id as its subscription requests. Omission retains the existing broadcast interface. Closing the receiving stream detaches delivery; explicitly Clear the logical subscription.
REST/gRPC attachment begins with a transport diagnostic containing gap=true, reason=attachment_started, and resynchronize=true. It does not claim continuity before attachment or invent lost counts/owner versions. gRPC uses event_type=gap and reserved proto_id=0 for this diagnostic. Actual Update messages retain their real identifiers, owner/stream versions and explicit gap counts. Overflow never silently turns the event stream into a latest-value snapshot.
Integer units¶
Prices use 1/1000 currency units; price-change and cumulative-change fields use 0.001 percentage points. Interval multiple uses 0.001 multiples, diff_price uses 0.01 percentage points, and optional interval volume is the last three minutes of shares. Other event volume is shares; event time and statistics update time are Unix seconds. Preserve optional fields and integer precision.
Public command diagnostics contain direction, raw result code, state and remote epoch, not another caller's targets or aggregate delivery preferences. Effective targets/preferences belong only to the current stream.
Legacy MCP clients must allow Info-level logging notifications to receive the stream. Every gap requires resynchronization. A zero count on diagnostics such as source_changed or delivery_disabled does not establish continuity; counts cover measured drops only.