Skip to content

shing1211/futuapi4go

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

go get github.com/shing1211/futuapi4go@v0.5.4

v0.5.4 New Features

// 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:

// 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

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):

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):

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:

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

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

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

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

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

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

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

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

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

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

// 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

// 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):
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):
// 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

// 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

// 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

// 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

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)

// 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

// 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

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

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

// 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

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

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.

⚠️ Trading Disclaimer: This SDK is a software utility. Trading financial instruments carries significant risk. Always test thoroughly in simulate mode before using real funds.

About

Implemenation of Futu Opend API SDK with golang

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages