# Futu API for Golang **Repository Path**: shing1211/futuapi4go ## Basic Information - **Project Name**: Futu API for Golang - **Description**: Futu API (OpenD version: 10.4.6408) Protobuf Go SDK implementation. - **Primary Language**: Go - **License**: Apache-2.0 - **Default Branch**: main - **Homepage**: https://gitee.com/shing1211/futuapi4go - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-07 - **Last Updated**: 2026-05-02 ## Categories & Tags **Categories**: Uncategorized **Tags**: 富途牛牛, futuapi, Bot, algo, opend ## README # futuapi4go

Go License Version Futu Proto Version

> **Go-native. Type-safe. Production-ready.** The most complete and ergonomic Go SDK for Futu OpenAPI — market data, trading, real-time push, and more. ## Install ```bash go get github.com/shing1211/futuapi4go@v0.5.4 ``` ## v0.5.4 New Features ```go // Fluent API - cleaner access cli.Quote().GetBasicQot(ctx, securities) cli.Trade().PlaceOrder(ctx, req) cli.System().GetGlobalState(ctx) // Historical K-line at specific time points resp, err := cli.Quote().GetHistoryKLPoints(ctx, &qot.GetHistoryKLPointsRequest{ Securities: securities, Times: []string{"2024-01-01 09:30:00"}, }) // Quota usage quota, _ := cli.System().GetUsedQuota(ctx) ``` If upgrading from v0.2.x, note these changes: ```go // v0.2.x (DEPRECATED) client.QuerySubscription(cli) client.UnlockTrading(cli, pwd) client.GetQuote(cli, int32(constant.Market_US), "NVDA") // v0.5.1 (NEW) client.QuerySubscription(ctx, cli) client.UnlockTrading(ctx, cli, pwd) client.GetQuote(ctx, cli, constant.Market_US, "NVDA") // no cast needed! // For timeout control: ctx, cancel := cli.WithTimeout(5 * time.Second) defer cancel() ``` ## Your First Trade ```go package main import ( "context" "fmt" "os" "os/signal" "syscall" "github.com/shing1211/futuapi4go/client" "github.com/shing1211/futuapi4go/pkg/constant" "github.com/shing1211/futuapi4go/pkg/push" chanpkg "github.com/shing1211/futuapi4go/pkg/push/chan" ) func main() { cli := client.New() defer cli.Close() if err := cli.Connect("127.0.0.1:11111"); err != nil { fmt.Fprintf(os.Stderr, "Failed to connect: %v\n", err) os.Exit(1) } // Note: US stocks require subscription before GetQuote works // Get a one-shot quote quote, err := client.GetQuote(context.Background(), cli, constant.Market_US, "NVDA") if err != nil { fmt.Fprintf(os.Stderr, "Failed to get quote: %v\n", err) os.Exit(1) } fmt.Printf("US.NVDA: price=%.2f open=%.2f high=%.2f low=%.2f vol=%d\n", quote.Price, quote.Open, quote.High, quote.Low, quote.Volume) // Set up channel listeners for real-time data quoteCh := make(chan *push.UpdateBasicQot, 100) stopQuote := chanpkg.SubscribeQuote(cli, constant.Market_US, "NVDA", quoteCh) defer stopQuote() // Set up multiple K-line handlers klHandlers := map[constant.KLType]func(*push.UpdateKL){ constant.KLType_K_1Min: func(kl *push.UpdateKL) { for _, bar := range kl.KLList { fmt.Printf("1MIN KL: %s C=%.2f V=%d\n", *bar.Time, *bar.ClosePrice, *bar.Volume) } }, constant.KLType_K_Day: func(kl *push.UpdateKL) { for _, bar := range kl.KLList { fmt.Printf("DAY KL: %s O=%.2f H=%.2f L=%.2f C=%.2f V=%d\n", *bar.Time, *bar.OpenPrice, *bar.HighPrice, *bar.LowPrice, *bar.ClosePrice, *bar.Volume) } }, } stopKLines := chanpkg.SubscribeKLines(cli, constant.Market_US, "NVDA", klHandlers) defer stopKLines() // Graceful shutdown on Ctrl+C sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) for { select { case q := <-quoteCh: fmt.Printf("QUOTE [%s]: price=%.2f vol=%d\n", q.Security.GetCode(), q.CurPrice, q.Volume) case <-sig: fmt.Println("Shutting down...") return } } } ``` > **Note:** US stocks require subscribing before `GetQuote` works. HK stocks don't need subscription. ## Package Map | Package | What it's For | |---------|--------------| | `client` | High-level wrappers — the recommended entry point | | `internal/client` | TCP connection, packet I/O, auto-reconnect, keep-alive | | `pkg/qot` | All market data APIs (quotes, K-lines, order book, tick data...) | | `pkg/trd` | All trading APIs (orders, positions, funds, history...) | | `pkg/sys` | System APIs (global state, user info) | | `pkg/push` | Parse push notification payloads | | `pkg/push/chan` | Subscribe to real-time data via Go channels | | `pkg/breaker` | Circuit breaker — protect trading from cascading failures | | `pkg/logger` | Structured logging, text + JSON, leveled (Debug/Info/Warn/Error) | | `pkg/util` | Code parsing (`HK.00700` ↔ market+code), market helpers | | `pkg/constant` | Python-style constants (`Market_HK`, `TrdSide_Buy`, `KLType_K_Day`...) | | `pkg/pb/*` | 78 protobuf-generated types for all Futu OpenAPI messages | ## Key Features in Depth ### Channel-Based Real-Time Push Stop polling. Let data come to you: #### Single data type (channel-based): ```go import ( chanpkg "github.com/shing1211/futuapi4go/pkg/push/chan" ) // Quote updates stream into the channel ch := make(chan *push.UpdateBasicQot, 100) stop := chanpkg.SubscribeQuote(cli, constant.Market_HK, "00700", ch) defer stop() for q := range ch { fmt.Printf("QUOTE [%s]: price=%.2f vol=%d\n", q.Security.GetCode(), q.CurPrice, q.Volume) } ``` #### Multiple K-line types (callback-based with `SubscribeKLines`): ```go import ( chanpkg "github.com/shing1211/futuapi4go/pkg/push/chan" ) // Subscribe to 1-minute and daily K-lines with separate handlers handlers := map[constant.KLType]func(*push.UpdateKL){ constant.KLType_K_1Min: func(kl *push.UpdateKL) { for _, bar := range kl.KLList { fmt.Printf("1MIN KL: %s C=%.2f V=%d\n", *bar.Time, *bar.ClosePrice, *bar.Volume) } }, constant.KLType_K_Day: func(kl *push.UpdateKL) { for _, bar := range kl.KLList { fmt.Printf("DAY KL: %s O=%.2f H=%.2f L=%.2f C=%.2f V=%d\n", *bar.Time, *bar.OpenPrice, *bar.HighPrice, *bar.LowPrice, *bar.ClosePrice, *bar.Volume) } }, } stop := chanpkg.SubscribeKLines(cli, constant.Market_HK, "00700", handlers) defer stop() ``` ### Circuit Breaker for Trading Protect your trading bot from cascading failures: ```go cb := breaker.New( breaker.WithThreshold(5), breaker.WithCooldown(30*time.Second), ) result, err := cb.Do(func() (interface{}, error) { return client.PlaceOrder(cli, accID, market, "00700", side, orderType, price, qty) }) if err == breaker.ErrOpen { fmt.Println("Trading suspended — too many failures") } ``` ### Structured Logging ```go l := logger.New( logger.WithLevel(logger.LevelDebug), logger.WithFormat(logger.FormatJSON), ) l.Info("connected", "addr", "127.0.0.1:11111", "conn_id", 42) l.Warn("order rejected", "code", "HK.00700", "reason", "insufficient funds") ``` ### Code Helpers ```go import "github.com/shing1211/futuapi4go/pkg/util" // "HK.00700" → market=1, code="00700" mkt, code := util.ParseCode("HK.00700") // Back again formatted := util.FormatCode(mkt, code) // "HK.00700" // Market conversion between quote and trading markets secMkt := util.MarketToTrdSecMarket[mkt] ``` ## Client Options ```go cli := client.New( client.WithDialTimeout(10*time.Second), client.WithAPISetTimeout(30*time.Second), client.WithKeepAliveInterval(30*time.Second), client.WithReconnectInterval(5*time.Second), client.WithMaxRetries(3), client.WithLogLevel(logger.LevelInfo), ) // Default to simulate trading (safe by default) cli = cli.WithTradeEnv(constant.TrdEnv_Simulate) ``` ## Full API Reference Every exported function with working examples and quick-reference tables. > All examples assume `cli := client.New(); cli.Connect("127.0.0.1:11111")`. --- ### Connection & Client ```go cli := client.New( client.WithDialTimeout(10*time.Second), client.WithAPISetTimeout(30*time.Second), client.WithKeepAliveInterval(30*time.Second), client.WithReconnectInterval(5*time.Second), client.WithMaxRetries(3), ) cli = cli.WithTradeEnv(constant.TrdEnv_Simulate) // safe default cli.Connect("127.0.0.1:11111") fmt.Println(cli.GetConnID()) // connection ID fmt.Println(cli.GetLoginUserID()) // Futu user ID fmt.Println(cli.GetServerVer()) // OpenD version fmt.Println(cli.IsEncrypt()) // AES encryption enabled? ``` | Function | Signature | Description | |---|---|---| | `New` | `New(opts ...Option) *Client` | Create client; defaults to simulate trading | | `Connect` | `Connect(addr string) error` | Connect to OpenD | | `Close` | `Close()` | Disconnect | | `WithTradeEnv` | `WithTradeEnv(trdEnv constant.TrdEnv) *Client` | Set real (`TrdEnv_Real`) or simulate (`TrdEnv_Simulate`) | | `WithTradeMarket` | `WithTradeMarket(trdMkt constant.TrdMarket) *Client` | Set default trading market | | `RegisterHandler` | `RegisterHandler(protoID uint32, h func(uint32, []byte))` | Register push handler | | `GetConnID` | `GetConnID() uint64` | Connection ID from OpenD | | `GetServerVer` | `GetServerVer() int32` | OpenD server version | | `GetLoginUserID` | `GetLoginUserID() uint64` | Logged-in Futu user ID | | `IsEncrypt` | `IsEncrypt() bool` | True if connection uses AES encryption | | `CanSendProto` | `CanSendProto(protoID uint32) bool` | Check if proto is available | | `EnsureConnected` | `EnsureConnected() error` | Return error if not connected | | `Inner` | `Inner() *futuapi.Client` | Access internal client (advanced) | | `WithContext` | `WithContext(ctx context.Context) *Client` | Attach context to client | | `Context` | `Context() context.Context` | Get client's context | | `GetConn` | `GetConn() *futuapi.Conn` | Access underlying connection (advanced) | --- ### Market Data — Queries #### GetQuote — real-time price snapshot ```go quote, err := client.GetQuote(context.Background(), cli, constant.Market_US, "NVDA") if err != nil { log.Fatal(err) } fmt.Printf("NVDA: %.2f (open=%.2f high=%.2f low=%.2f vol=%d)\n", quote.Price, quote.Open, quote.High, quote.Low, quote.Volume) ``` #### GetKLines — latest K-line bars ```go klines, err := client.GetKLines(context.Background(), cli, constant.Market_HK, "00700", constant.KLType_K_Day, 100) for _, kl := range klines { fmt.Printf("%s O=%.2f H=%.2f L=%.2f C=%.2f\n", kl.Time, kl.Open, kl.High, kl.Low, kl.Close) } ``` #### GetOrderBook — bid/ask depth ```go book, err := client.GetOrderBook(context.Background(), cli, constant.Market_HK, "00700", 10) for i, b := range book.Bids { fmt.Printf("Bid[%d]: %.2f x %d\n", i, b.Price, b.Volume) } for i, a := range book.Asks { fmt.Printf("Ask[%d]: %.2f x %d\n", i, a.Price, a.Volume) } ``` #### GetSecuritySnapshot — multi-stock snapshot ```go securities := []*qotcommon.Security{ {Market: ptrInt32(constant.Market_HK), Code: ptrStr("00700")}, {Market: ptrInt32(constant.Market_HK), Code: ptrStr("09988")}, } snapshots, err := client.GetSecuritySnapshot(context.Background(), cli, securities) for _, s := range snapshots { fmt.Printf("%s: %.2f\n", s.Security.GetCode(), s.CurPrice) } ``` #### GetCapitalFlow / GetCapitalDistribution ```go flows, err := client.GetCapitalFlow(context.Background(), cli, constant.Market_HK, "00700") for _, f := range flows { fmt.Printf("InFlow=%.2f MainInFlow=%.2f\n", f.InFlow, f.MainInFlow) } dist, err := client.GetCapitalDistribution(context.Background(), cli, constant.Market_HK, "00700") if dist != nil { fmt.Printf("MainInflow=%.2f BigInflow=%.2f\n", dist.MainInflow, dist.BigInflow) } ``` | Function | Signature | Description | |---|---|---| | `GetQuote` | `GetQuote(ctx, c, market, code) (*Quote, error)` | Real-time quote for one security | | `GetKLines` | `GetKLines(ctx, c, market, code, klType, num) ([]KLine, error)` | Latest K-line bars (up to `num`) | | `GetOrderBook` | `GetOrderBook(ctx, c, market, code, num) (*OrderBook, error)` | Bid/ask depth, `num` levels per side | | `GetTicker` | `GetTicker(ctx, c, market, code, num) ([]Ticker, error)` | Tick-by-tick trades, last `num` | | `GetRT` | `GetRT(ctx, c, market, code) ([]RT, error)` | Intraday time-share data | | `GetBroker` | `GetBroker(ctx, c, market, code, num) ([]Broker, []Broker, error)` | Broker queue (bid, ask) | | `GetStaticInfo` | `GetStaticInfo(ctx, c, market, code) ([]StaticInfo, error)` | Static security info (name, type, lot size) | | `GetSecuritySnapshot` | `GetSecuritySnapshot(ctx, c, securities) ([]*Snapshot, error)` | Full snapshot for multiple securities | | `GetMarketState` | `GetMarketState(ctx, c, market, code) (int32, error)` | Trading status (open/closed/auction...) | | `GetCapitalFlow` | `GetCapitalFlow(ctx, c, market, code) ([]CapitalFlow, error)` | Capital flow (inflow/outflow) | | `GetCapitalDistribution` | `GetCapitalDistribution(ctx, c, market, code) (*CapitalDistribution, error)` | Capital distribution (super/big/mid/small) | | `GetOwnerPlate` | `GetOwnerPlate(ctx, c, market, code) ([]string, error)` | Plates the security belongs to | | `GetPlateSet` | `GetPlateSet(ctx, c, market) ([]Plate, error)` | List plates (industry/region/concept) | | `GetPlateSecurity` | `GetPlateSecurity(ctx, c, market, plateCode) ([]StaticInfo, error)` | Securities in a plate | | `GetReference` | `GetReference(ctx, c, market, code, refType) ([]StaticInfo, error)` | Related securities (warrants, etc.) | | `GetIpoList` | `GetIpoList(ctx, c, market) ([]IpoData, error)` | Upcoming/ongoing IPOs | | `GetFutureInfo` | `GetFutureInfo(ctx, c, code) ([]FutureInfo, error)` | Futures contract info | | `GetSuspend` | `GetSuspend(ctx, c, securities, begin, end) ([]*SuspendInfo, error)` | Suspension dates | | `GetCodeChange` | `GetCodeChange(ctx, c, securities) ([]*CodeChangeInfo, error)` | Code change history | | `GetHoldingChangeList` | `GetHoldingChangeList(ctx, c, market, code, category, begin, end) ([]*HoldingChangeInfo, error)` | Director/holder changes | | `GetOptionExpirationDate` | `GetOptionExpirationDate(ctx, c, market, code) ([]OptionExpiration, error)` | Option expiry dates | | `GetOptionChain` | `GetOptionChain(ctx, c, market, code, indexType, optType, cond, begin, end) ([]*OptChain, error)` | Full option chain | | `GetWarrant` | `GetWarrant(ctx, c, market, code, begin, num, sort, asc, optType, issuer, status) ([]*WarrantData, error)` | Warrant list | | `StockFilter` | `StockFilter(ctx, c, market, begin, num) ([]*StockFilterResult, error)` | Filter stocks by criteria | | `GetPriceReminder` | `GetPriceReminder(ctx, c, market, code) ([]*PriceReminderInfo, error)` | Get price alerts | | `SetPriceReminder` | `SetPriceReminder(ctx, c, market, code, op, type, freq, value, note) (int64, error)` | Add/update/delete price alert | --- ### Market Data — Historical K-Lines ```go // Fetch all daily K-lines for a date range (auto-paginated) klines, err := client.RequestHistoryKL(context.Background(), cli, constant.Market_HK, "00700", constant.KLType_K_Day, "2024-01-01", "2025-01-01", ) // Fetch with custom page size (max 1000 per page) client.HistoryKLPaginationDelay = 500 * time.Millisecond klines, err = client.RequestHistoryKLWithLimit(context.Background(), cli, constant.Market_HK, "00700", constant.KLType_K_Day, "2024-01-01", "2025-01-01", 500, // max per page ) // Check quota usage quota, err := client.RequestHistoryKLQuota(context.Background(), cli) fmt.Printf("Used=%d Remain=%d\n", quota.UsedQuota, quota.RemainQuota) // Get rehab (split/dividend) factors rehab, err := client.RequestRehab(context.Background(), cli, constant.Market_HK, "00700") ``` | Function | Signature | Description | |---|---|---| | `RequestHistoryKL` | `RequestHistoryKL(ctx, c, mkt, code, klType, start, end) ([]KLine, error)` | Auto-paginated historical K-lines | | `RequestHistoryKLWithLimit` | `RequestHistoryKLWithLimit(ctx, c, mkt, code, klType, start, end, maxPerPage) ([]KLine, error)` | With configurable page size | | `RequestHistoryKLQuota` | `RequestHistoryKLQuota(ctx, c) (*HistoryKLQuotaInfo, error)` | API quota usage | | `RequestRehab` | `RequestRehab(ctx, c, market, code) ([]*RehabInfo, error)` | Rehabilitation (split/dividend) factors | | `GetTradeDate` | `GetTradeDate(ctx, c, market, start, end) ([]string, error)` | Market trade dates | | `RequestTradeDate` | `RequestTradeDate(ctx, c, market, start, end, code) ([]string, error)` | Trade dates for a specific security | --- ### Real-Time Subscriptions #### Subscribe — receive push data for one or more types ```go // Subscribe to multiple data types at once // Note: US stocks require subscription before GetQuote works err := client.Subscribe(context.Background(), cli, constant.Market_US, "NVDA", []constant.SubType{ constant.SubType_Quote, constant.SubType_Ticker, constant.SubType_K_1Min, }) ``` #### Channel-based subscription — `chanpkg` (recommended) ##### Single K-line type (channel-based): ```go quoteCh := make(chan *push.UpdateBasicQot, 100) tickerCh := make(chan *push.UpdateTicker, 100) orderBookCh := make(chan *push.UpdateOrderBook, 100) rtCh := make(chan *push.UpdateRT, 100) brokerCh := make(chan *push.UpdateBroker, 100) klCh := make(chan *push.UpdateKL, 100) stopQuote := chanpkg.SubscribeQuote(cli, constant.Market_US, "NVDA", quoteCh) stopTicker := chanpkg.SubscribeTicker(cli, constant.Market_US, "NVDA", tickerCh) stopOB := chanpkg.SubscribeOrderBook(cli, constant.Market_US, "NVDA", orderBookCh) stopRT := chanpkg.SubscribeRT(cli, constant.Market_US, "NVDA", rtCh) stopBroker := chanpkg.SubscribeBroker(cli, constant.Market_US, "NVDA", brokerCh) stopKL := chanpkg.SubscribeKLine(cli, constant.Market_US, "NVDA", constant.KLType_K_1Min, klCh) defer stopQuote() defer stopTicker() defer stopOB() defer stopRT() defer stopBroker() defer stopKL() sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) for { select { case q := <-quoteCh: fmt.Printf("QUOTE [%s]: price=%.2f vol=%d\n", q.Security.GetCode(), q.CurPrice, q.Volume) case t := <-tickerCh: if len(t.TickerList) > 0 { fmt.Printf("TICKER: %.2f x %d\n", t.TickerList[0].GetPrice(), t.TickerList[0].GetVolume()) } case ob := <-orderBookCh: if len(ob.OrderBookBidList) > 0 && len(ob.OrderBookAskList) > 0 { fmt.Printf("ORDERBOOK: bid=%.2f ask=%.2f\n", ob.OrderBookBidList[0].GetPrice(), ob.OrderBookAskList[0].GetPrice()) } case rt := <-rtCh: if len(rt.RTList) > 0 { fmt.Printf("RT: %.2f avg=%.2f\n", rt.RTList[0].GetPrice(), rt.RTList[0].GetAvgPrice()) } case b := <-brokerCh: if len(b.BidBrokerList) > 0 { fmt.Printf("BROKER: %s pos=%d\n", b.BidBrokerList[0].GetName(), b.BidBrokerList[0].GetPos()) } case kl := <-klCh: for _, bar := range kl.KLList { fmt.Printf("KL: %s C=%.2f V=%d\n", *bar.Time, *bar.ClosePrice, *bar.Volume) } case <-sig: fmt.Println("Shutting down...") return } } ``` ##### Multiple K-line types (callback-based with `SubscribeKLines`): ```go // Define handlers for different K-line periods handlers := map[constant.KLType]func(*push.UpdateKL){ constant.KLType_K_1Min: func(kl *push.UpdateKL) { for _, bar := range kl.KLList { fmt.Printf("1MIN KL: %s C=%.2f V=%d\n", *bar.Time, *bar.ClosePrice, *bar.Volume) } }, constant.KLType_K_5Min: func(kl *push.UpdateKL) { for _, bar := range kl.KLList { fmt.Printf("5MIN KL: %s C=%.2f V=%d\n", *bar.Time, *bar.ClosePrice, *bar.Volume) } }, constant.KLType_K_Day: func(kl *push.UpdateKL) { for _, bar := range kl.KLList { fmt.Printf("DAY KL: %s O=%.2f H=%.2f L=%.2f C=%.2f V=%d\n", *bar.Time, *bar.OpenPrice, *bar.HighPrice, *bar.LowPrice, *bar.ClosePrice, *bar.Volume) } }, } stopKLines := chanpkg.SubscribeKLines(cli, constant.Market_HK, "00700", handlers) defer stopKLines() // Wait for Ctrl+C to exit sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) <-sig fmt.Println("Shutting down...") ``` | Function | Signature | Description | |---|---|---| | `Subscribe` | `Subscribe(ctx, c, market, code, []SubType) error` | Subscribe to one or more push types | | `Unsubscribe` | `Unsubscribe(ctx, c, market, code, []int32) error` | Unsubscribe specific types | | `UnsubscribeAll` | `UnsubscribeAll(ctx, c) error` | Unsubscribe everything | | `QuerySubscription` | `QuerySubscription(ctx, c) (*GetSubInfoResponse, error)` | Current subscription status | | `RegQotPush` | `RegQotPush(ctx, c, market, code, subtypes, rehabTypes, isReg, isFirst) error` | Register/unregister push | | `chanpkg.SubscribeQuote` | `(cli, market, code, ch) stopFunc` | Quote push via channel | | `chanpkg.SubscribeKLine` | `(cli, market, code, KLType, ch) stopFunc` | K-line push via channel | | `chanpkg.SubscribeTicker` | `(cli, market, code, ch) stopFunc` | Ticker push via channel | | `chanpkg.SubscribeOrderBook` | `(cli, market, code, ch) stopFunc` | Order book push via channel | | `chanpkg.SubscribeRT` | `(cli, market, code, ch) stopFunc` | RT push via channel | | `chanpkg.SubscribeBroker` | `(cli, market, code, ch) stopFunc` | Broker push via channel | | `chanpkg.SubscribePriceReminder` | `(cli, ch) stopFunc` | Price reminder push via channel | | `GetSubInfo` | `GetSubInfo(ctx, c) (*SubInfo, error)` | Subscription info (quota, types) | --- ### Push Parsing — decode raw push payloads ```go // Quote updates cli.RegisterHandler(constant.ProtoID_Qot_UpdateBasicQot, func(pid uint32, body []byte) { pq, err := client.ParsePushQuote(body) if err != nil || pq == nil { return } fmt.Printf("[%s] %.2f\n", pq.Code, pq.CurPrice) }) // K-line updates cli.RegisterHandler(constant.ProtoID_Qot_UpdateKL, func(pid uint32, body []byte) { pk, err := client.ParsePushKLine(body) if err != nil || pk == nil { return } fmt.Printf("[%s KL] %.2f\n", pk.Code, pk.Close) }) // Order updates (requires SubAccPush) cli.RegisterHandler(constant.ProtoID_Trd_UpdateOrder, func(pid uint32, body []byte) { ou, err := client.ParsePushOrderUpdate(body) if err != nil || ou == nil { return } fmt.Printf("Order %d: status=%d\n", ou.OrderID, ou.OrderStatus) }) ``` | Function | Signature | Description | |---|---|---| | `ParsePushQuote` | `ParsePushQuote(body) (*PushQuote, error)` | Decode quote push (ProtoID 3005) | | `ParsePushKLine` | `ParsePushKLine(body) (*PushKLine, error)` | Decode K-line push (3007) | | `ParsePushOrderBook` | `ParsePushOrderBook(body) (*PushOrderBook, error)` | Decode order book push (3013) | | `ParsePushTicker` | `ParsePushTicker(body) (*PushTicker, error)` | Decode ticker push (3011) | | `ParsePushRT` | `ParsePushRT(body) (*PushRT, error)` | Decode RT push (3009) | | `ParsePushBroker` | `ParsePushBroker(body) (*PushBroker, error)` | Decode broker push (3015) | | `ParsePushOrderUpdate` | `ParsePushOrderUpdate(body) (*PushOrderUpdate, error)` | Decode order update push (2208) | | `ParsePushOrderFill` | `ParsePushOrderFill(body) (*PushOrderFill, error)` | Decode fill push (2218) | --- ### Trading — Account & Funds ```go // List all accounts accounts, err := client.GetAccountList(context.Background(), cli) for _, acc := range accounts { fmt.Printf("AccID=%d Env=%d Markets=%v\n", acc.AccID, acc.TrdEnv, acc.TrdMarketAuthList) } // Unlock trading (required before placing orders) if err := client.UnlockTrading(context.Background(), cli, "your_md5_password"); err != nil { log.Fatal(err) } // Quick funds for first account funds, err := client.GetFunds(context.Background(), cli, 0) fmt.Printf("Power=%.2f Cash=%.2f Assets=%.2f\n", funds.Power, funds.Cash, funds.TotalAssets) // Full account info with per-currency and per-market breakdown funds, err = client.GetAccountInfo(context.Background(), cli, accID, constant.TrdMarket_HK) for _, ci := range funds.CashInfoList { fmt.Printf("Currency=%d Cash=%.2f Available=%.2f\n", ci.Currency, ci.Cash, ci.AvailableBalance) } // Max tradable quantities before placing an order max, err := client.GetMaxTrdQtys(context.Background(), cli, accID, constant.TrdMarket_HK, "00700", constant.OrderType_Normal, 350.0) fmt.Printf("MaxCashBuy=%.2f MaxSell=%.2f\n", max.MaxCashBuy, max.MaxPositionSell) // AccTradingInfo — includes initial margin requirements info, err := client.GetAccTradingInfo(context.Background(), cli, accID, constant.TrdMarket_HK, "00700", constant.OrderType_Normal, 350.0) fmt.Printf("MaxBuy=%.2f LongIM=%.2f\n", info.MaxCashBuy, info.LongRequiredIM) ``` | Function | Signature | Description | |---|---|---| | `GetAccountList` | `GetAccountList(ctx, c) ([]Account, error)` | All trading accounts | | `UnlockTrading` | `UnlockTrading(ctx, c, pwdMD5) error` | Unlock trading with MD5-hashed password | | `GetFunds` | `GetFunds(ctx, c, accID) (*Funds, error)` | Quick funds for first account | | `GetAccountInfo` | `GetAccountInfo(ctx, c, accID, market) (*Funds, error)` | Full funds with multi-currency/multi-market breakdown | | `GetMaxTrdQtys` | `GetMaxTrdQtys(ctx, c, accID, market, code, orderType, price) (*MaxTrdQtysInfo, error)` | Maximum buy/sell quantities | | `GetAccTradingInfo` | `GetAccTradingInfo(ctx, c, accID, market, code, orderType, price) (*AccTradingInfo, error)` | Max quantities + initial margin | | `GetOrderFee` | `GetOrderFee(ctx, c, accID, market, orderIDExList) ([]*OrderFeeInfo, error)` | Fee breakdown per order | | `GetMarginRatio` | `GetMarginRatio(ctx, c, accID, market, securities) ([]*MarginRatioInfo, error)` | Margin ratios for securities | | `SubAccPush` | `SubAccPush(ctx, c, accIDList) error` | Subscribe to account push notifications | --- ### Trading — Orders ```go // Place a buy limit order result, err := client.PlaceOrder(context.Background(), cli, accID, constant.TrdMarket_HK, // trading market "00700", // code constant.TrdSide_Buy, // side constant.OrderType_Normal, // limit order 350.0, // price 100, // quantity ) fmt.Printf("OrderID=%d OrderIDEx=%s\n", result.OrderID, result.OrderIDEx) // Modify order price and quantity _, err = client.ModifyOrder(context.Background(), cli, accID, constant.TrdMarket_HK, result.OrderID, // order ID constant.ModifyOrderOp_Normal, // modify (not cancel) 355.0, 200) // new price, new qty // Cancel all open orders err = client.CancelAllOrder(context.Background(), cli, accID, constant.TrdMarket_HK, constant.TrdEnv_Real) // Active orders orders, err := client.GetOrderList(context.Background(), cli, accID) for _, o := range orders { fmt.Printf("Order %d: %s %s @ %.2f qty=%.0f status=%d\n", o.OrderID, o.Code, o.TrdSide, o.Price, o.Qty, o.OrderStatus) } // Historical orders hist, err := client.GetHistoryOrderList(context.Background(), cli, accID, constant.TrdMarket_HK, "2024-01-01", "2025-12-31") // Order fills fills, err := client.GetOrderFillList(context.Background(), cli, accID) for _, f := range fills { fmt.Printf("Fill %d: %s @ %.2f x %.0f\n", f.FillID, f.Code, f.Price, f.Qty) } // Cash flow flows, err := client.GetFlowSummary(context.Background(), cli, accID, constant.TrdMarket_HK, "", 0) // date="" means today; direction 0=all, 1=in, 2=out for _, f := range flows { fmt.Printf("%s %s: %.2f\n", f.ClearingDate, f.CashFlowType, f.CashFlowAmount) } ``` | Function | Signature | Description | |---|---|---| | `PlaceOrder` | `PlaceOrder(ctx, c, accID, market, code, side, orderType, price, qty) (*PlaceOrderResult, error)` | Place a new order | | `ModifyOrder` | `ModifyOrder(ctx, c, accID, market, orderID, op, price, qty) (*ModifyOrderResponse, error)` | Modify price/qty or cancel | | `CancelAllOrder` | `CancelAllOrder(ctx, c, accID, market, trdEnv) error` | Cancel all open orders | | `ReconfirmOrder` | `ReconfirmOrder(ctx, c, accID, market, orderID, reason) (*ReconfirmOrderResult, error)` | Reconfirm order requiring verification | | `GetOrderList` | `GetOrderList(ctx, c, accID) ([]Order, error)` | Active (open) orders | | `GetHistoryOrderList` | `GetHistoryOrderList(ctx, c, accID, market, start, end) ([]Order, error)` | Historical orders | | `GetOrderFillList` | `GetOrderFillList(ctx, c, accID) ([]OrderFill, error)` | Today's order fills | | `GetHistoryOrderFillList` | `GetHistoryOrderFillList(ctx, c, accID, market) ([]OrderFill, error)` | Historical fills | | `GetFlowSummary` | `GetFlowSummary(ctx, c, accID, market, date, direction) ([]*FlowSummaryInfo, error)` | Cash flow entries | --- ### Trading — Positions ```go positions, err := client.GetPositionList(context.Background(), cli, accID) for _, p := range positions { fmt.Printf("%s: qty=%.0f cost=%.2f cur=%.2f pnl=%.2f (%.2f%%)\n", p.Code, p.Quantity, p.CostPrice, p.CurPrice, p.PnL, p.PnLRate) } ``` | Function | Signature | Description | |---|---|---| | `GetPositionList` | `GetPositionList(ctx, c, accID) ([]Position, error)` | Current positions with P&L | --- ### User Security (Watchlist) ```go // List all watchlist groups groups, err := client.GetUserSecurityGroup(context.Background(), cli) for _, g := range groups { fmt.Printf("Group: %s (type=%d)\n", g.Name, g.GroupType) } // Get securities in a group infos, err := client.GetUserSecurity(context.Background(), cli, "My Watchlist") // Add/remove securities from a group err = client.ModifyUserSecurity(context.Background(), cli, "My Watchlist", constant.ModifyUserSecurityOp_Add, // or _Del constant.Market_US, []string{"NVDA", "AAPL"}) ``` | Function | Signature | Description | |---|---|---| | `GetUserSecurityGroup` | `GetUserSecurityGroup(ctx, c) ([]UserSecurityGroup, error)` | All watchlist groups | | `GetUserSecurity` | `GetUserSecurity(ctx, c, groupName) ([]StaticInfo, error)` | Securities in a group | | `ModifyUserSecurity` | `ModifyUserSecurity(ctx, c, groupName, op, market, codes) error` | Add/delete securities from group | --- ### System ```go // Global connection state state, err := client.GetGlobalState(context.Background(), cli) fmt.Printf("QotLogined=%v TrdLogined=%v ServerBuild=%d\n", state.QotLogined, state.TrdLogined, state.ServerBuildNo) // User info user, err := client.GetUserInfo(context.Background(), cli) fmt.Printf("UserID=%d Nick=%s APILevel=%s\n", user.UserID, user.NickName, user.ApiLevel) ``` | Function | Signature | Description | |---|---|---| | `GetGlobalState` | `GetGlobalState(ctx, c) (*GlobalState, error)` | OpenD connection and login state | | `GetUserInfo` | `GetUserInfo(ctx, c) (*UserInfo, error)` | Futu account user info | | `GetDelayStatistics` | `GetDelayStatistics(ctx, c) (*DelayStatistics, error)` | Latency stats | --- ### Circuit Breaker ```go import "github.com/shing1211/futuapi4go/pkg/breaker" cb := breaker.New( breaker.WithThreshold(5), breaker.WithCooldown(30*time.Second), breaker.WithOnOpen(func() { fmt.Println("Circuit OPENED") }), ) // Wrap any API call result, err := cb.Do(func() (interface{}, error) { return client.PlaceOrder(...) }) if err == breaker.ErrOpen { fmt.Println("Trading suspended — circuit is open") } // Or for void-returning calls err = cb.DoVoid(func() error { return client.PlaceOrder(...) }) // Manual control fmt.Printf("State=%s Failures=%d\n", cb.State(), cb.Failures()) cb.Reset() // close the circuit ``` | Function | Signature | Description | |---|---|---| | `breaker.New` | `New(opts ...Option) *Breaker` | Create circuit breaker | | `breaker.Do` | `(b *Breaker) Do(fn func() (interface{}, error)) (interface{}, error)` | Execute with protection | | `breaker.DoVoid` | `(b *Breaker) DoVoid(fn func() error) error` | Execute void function | | `breaker.State` | `(b *Breaker) State() State` | Current state (Closed/Open/HalfOpen) | | `breaker.Allow` | `(b *Breaker) Allow() bool` | Check if request is allowed | | `breaker.RecordSuccess` | `(b *Breaker) RecordSuccess()` | Record a success | | `breaker.RecordFailure` | `(b *Breaker) RecordFailure()` | Record a failure | | `breaker.Reset` | `(b *Breaker) Reset()` | Reset to closed | | `breaker.Stats` | `(b *Breaker) Stats() Stats` | Diagnostic info | | `breaker.ErrOpen` | `var` | Error returned when circuit is open | --- ### Structured Logging ```go import ( "github.com/shing1211/futuapi4go/pkg/logger" futulogger "github.com/shing1211/futuapi4go/pkg/logger" ) l := futulogger.New( futulogger.WithLevel(futulogger.LevelDebug), futulogger.WithFormat(futulogger.FormatJSON), // or FormatText ) l.Info("connected", "addr", "127.0.0.1:11111", "conn_id", 42) l.Warn("order rejected", "code", "HK.00700", "reason", "insufficient funds") l.Error("connection lost", "err", err) // Package-level defaults logger.SetLevel(logger.LevelInfo) logger.Info("hello", "key", "value") ``` | Function | Signature | Description | |---|---|---| | `logger.New` | `New(opts ...Option) *Logger` | Create logger instance | | `logger.Info/Debug/Warn/Error` | `(l *Logger) Info(msg, fields...)` | Log at specific level | | `logger.Fatal` | `(l *Logger) Fatal(msg, fields...)` | Log and exit | | `logger.SetLevel` | `SetLevel(lvl Level)` | Set global level | | `logger.SetFormat` | `SetFormat(fmt Format)` | Set text (`FormatText`) or JSON (`FormatJSON`) | | `logger.SetOutput` | `SetOutput(w io.Writer)` | Set output destination | --- ### Constants Reference ```go // Markets (quote) constant.Market_HK // 1 — Hong Kong constant.Market_US // 11 — United States constant.Market_SH // 21 — Shanghai A-share constant.Market_SZ // 22 — Shenzhen A-share constant.Market_SG // 31 — Singapore constant.Market_JP // 41 — Japan constant.Market_AU // 51 — Australia // Trading markets constant.TrdMarket_HK // 1 constant.TrdMarket_US // 2 constant.TrdMarket_CN // 3 constant.TrdMarket_Futures // 5 // Trading environment constant.TrdEnv_Simulate // 0 (default — safe) constant.TrdEnv_Real // 1 // Trading sides constant.TrdSide_Buy // 1 constant.TrdSide_Sell // 2 constant.TrdSide_SellShort // 3 constant.TrdSide_BuyBack // 4 // Order types constant.OrderType_Normal // 1 — limit order (recommended) constant.OrderType_Market // 2 — market order constant.OrderType_Stop // 10 — stop market constant.OrderType_StopLimit // 11 — stop limit // Modify order operations constant.ModifyOrderOp_Normal // 1 — modify price/qty constant.ModifyOrderOp_Cancel // 2 — cancel order // K-line types (for GetKLines / RequestHistoryKL) constant.KLType_K_1Min // 1 constant.KLType_K_5Min // 2 constant.KLType_K_15Min // 3 constant.KLType_K_30Min // 4 constant.KLType_K_60Min // 5 constant.KLType_K_Day // 6 constant.KLType_K_Week // 7 constant.KLType_K_Month // 8 // Subscription types (for Subscribe / chanpkg) constant.SubType_Quote // 1 constant.SubType_OrderBook // 2 constant.SubType_Ticker // 4 constant.SubType_RT // 5 constant.SubType_Broker // 14 constant.SubType_K_1Min // 11 constant.SubType_K_5Min // 7 constant.SubType_K_15Min // 8 constant.SubType_K_30Min // 9 constant.SubType_K_60Min // 10 constant.SubType_K_Day // 6 constant.SubType_K_Week // 12 constant.SubType_K_Month // 13 constant.SubType_K_Quarter // 15 constant.SubType_K_Year // 16 constant.SubType_K_3Min // 17 // Rehab (price adjustment) constant.RehabType_None // 0 — no adjustment constant.RehabType_Forward // 1 — forward (QFQ) constant.RehabType_Backward // 2 — backward (BQF) // Plate set types constant.PlateSetType_Industry // 1 constant.PlateSetType_Region // 2 constant.PlateSetType_Concept // 3 // Market states constant.MarketState_Morning // 3 — morning session constant.MarketState_Afternoon // 5 — afternoon session constant.MarketState_Closed // 6 — market closed constant.MarketState_PreMarketBegin // 7 — US pre-market // Price reminder constant.PriceReminderOpAdd // 1 — add alert constant.PriceReminderOpUpdate // 2 — update alert constant.PriceReminderOpDelete // 3 — delete alert ``` ## Build & Test ```bash go build ./... # Compile everything go vet ./... # Lint go test ./... # Run the full test suite go test -race ./... # Race detector ``` ## Architecture ``` futuapi4go/ ├── client/ # Public high-level API (recommended) ├── internal/client/ # TCP connection, packet I/O, reconnect, keep-alive ├── pkg/ │ ├── qot/ # Market data — quotes, K-lines, order book, tick data... │ ├── trd/ # Trading — orders, positions, funds, history... │ ├── sys/ # System — global state, user info │ ├── push/ # Push notification parsers │ ├── push/chan/ # Channel-based push delivery │ ├── breaker/ # Circuit breaker pattern │ ├── logger/ # Structured leveled logging │ ├── util/ # Code parsing, market helpers │ ├── constant/ # Python-style constants + String() methods │ └── pb/ # 78 protobuf-generated types (v10.4.6408) ├── api/proto/ # Original .proto definitions (v10.4.6408) └── test/ # Test suite with examples ``` ## Build & Test ```bash go build ./... # Compile everything go vet ./... # Lint go test ./... # Run the full test suite go test -race ./... # Race detector ``` ## License Apache License 2.0 — see [LICENSE](LICENSE). > ⚠️ **Trading Disclaimer**: This SDK is a software utility. Trading financial instruments carries significant risk. Always test thoroughly in simulate mode before using real funds.