Skip to content

gRPC

:33333 exposes a generic Request(proto_id, body) RPC plus a streaming SubscribePush.

Service definition

crates/futu-grpc/proto/futu.proto:

service FutuOpenD {
  rpc Request(FutuRequest) returns (FutuResponse);
  rpc SubscribePush(SubscribePushRequest) returns (stream PushEvent);
}

message FutuRequest {
  uint32 proto_id = 1;
  bytes  body     = 2;   // protobuf-encoded
}

message FutuResponse {
  int32  ret_type = 1;
  string ret_msg  = 2;
  uint32 proto_id = 3;
  bytes  body     = 4;
}

Call examples

# without auth
grpcurl -plaintext -d '{"proto_id":1002}' \
  localhost:33333 futu.service.FutuOpenD/Request

# with auth
grpcurl -plaintext \
  -H 'authorization: Bearer fc_xxxx...' \
  -d '{"proto_id":1002}' \
  localhost:33333 futu.service.FutuOpenD/Request
import grpc
from futu_pb2 import FutuRequest
from futu_pb2_grpc import FutuOpenDStub

creds = grpc.metadata_call_credentials(
    lambda ctx, cb: cb((("authorization", "Bearer fc_xxxx..."),), None)
)
channel = grpc.secure_channel("localhost:33333", grpc.ssl_channel_credentials())
stub = FutuOpenDStub(channel)

# encode proto_id=3004 (QOT_GET_BASIC_QOT) body
body = build_get_basic_qot_request(code="00700")  # protobuf encode
resp = stub.Request(FutuRequest(proto_id=3004, body=body))
conn, _ := grpc.Dial("localhost:33333", grpc.WithInsecure())
client := futu.NewFutuOpenDClient(conn)

md := metadata.Pairs("authorization", "Bearer fc_xxxx...")
ctx := metadata.NewOutgoingContext(context.Background(), md)

resp, _ := client.Request(ctx, &futu.FutuRequest{
    ProtoId: 1002,
})

proto_id → scope mapping

Range Required scope
1xxx system (InitConnect / KeepAlive / ...) none
3xxx quote qot:read
2005 UnlockTrade trade:real
2202 / 2205 / 2237 place / modify / confirm order trade:real
2001 / 2008 / 2101 / 2102 / ... account read acc:read
Others fail-closed reject (catch-all trade:real)

Limits

  • Auth layer: trade:real requests pass through the generic rate + hours gate
  • Handler layer (v1.2+): 2202 PlaceOrder / 2205 ModifyOrder decode the body and run fine-grained market / symbol / value / side / daily checks

See Auth & limits for details.

Streaming pushes

grpcurl -plaintext \
  -H 'authorization: Bearer fc_xxxx...' \
  -d '{}' \
  localhost:33333 futu.service.FutuOpenD/SubscribePush

Each PushEvent has an event_type (quote / notify / trade) and a body (protobuf).

v1.1+: the server filters pushes by the client key's scope — a qot:read-only key does not receive trade events (order/fill callbacks). Filtered events increment the futu_ws_filtered_pushes_total counter.

Error-code mapping

tonic Status Meaning
Unauthenticated Bearer missing / invalid / expired
PermissionDenied Scope insufficient
ResourceExhausted Limit tripped (REST equivalent: 429)
InvalidArgument Wrong proto_id or body decode failed
Unavailable Gateway disconnected