exchanges

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 25, 2026 License: MIT Imports: 10 Imported by: 0

README

exchanges

English | 中文

A unified Go SDK for interacting with multiple cryptocurrency exchanges.

Provides both low-level SDK clients (REST + WebSocket) and high-level adapters implementing a common Exchange interface — a Go-native CCXT alternative.

Features

  • Unified Interface — One API to rule them all. Switch exchanges by changing one line.
  • Full Market Coverage — Perpetual Futures, Spot, and Margin trading support.
  • Dual Transport — REST for queries; WebSocket for real-time streaming and low-latency order placement.
  • Built-in Safety — Exchange-specific request protection, rate-limit error mapping, order validation, and slippage protection.
  • Local State Management — WebSocket-maintained orderbooks, position/order tracking, and balance sync.
  • Production-Ready — Battle-tested in quantitative trading systems handling thousands of orders daily.

Supported Exchanges

Exchange Perp Spot Margin Quote Currencies Default
Binance USDT, USDC USDT
OKX USDT, USDC USDT
Aster USDT, USDC USDC
Nado USDT USDT
Lighter USDC USDC
Hyperliquid USDC USDC
Bitget USDT, USDC USDT
StandX DUSD DUSD
GRVT USDT USDT
EdgeX USDC USDC
Decibel USDC USDC
Exchange Notes
  • Bitget currently supports the classic private API surface only.
  • Bitget defaults to OrderModeREST. Explicit OrderModeWS is opt-in and requires Bitget to enable classic WebSocket trade access for the API key.
  • Decibel is perp-only in this repository. It uses authenticated REST/WebSocket reads plus Aptos-signed on-chain trading writes, with credentials api_key + private_key + subaccount_addr.

Adding New Exchanges

Repository-specific guidance for new adapters lives in docs/contributing/adding-exchange-adapters.md.

Treat that guide as the source of truth for package layout, capability claims, private-stream readiness, and live test wiring. It is intentionally repo-owned guidance, not a reusable global skill.

Installation

go get github.com/QuantProcessing/exchanges

Design Philosophy

1. Adapter Pattern

Every exchange implements the same Exchange interface. Your strategy code never touches exchange-specific APIs:

// This function works with ANY exchange — Binance, OKX, Hyperliquid, etc.
func getSpread(ctx context.Context, adp exchanges.Exchange, symbol string) (decimal.Decimal, error) {
    ob, err := adp.FetchOrderBook(ctx, symbol, 1)
    if err != nil {
        return decimal.Zero, err
    }
    return ob.Asks[0].Price.Sub(ob.Bids[0].Price), nil
}
2. Symbol Convention

All methods accept a base currency symbol (e.g. "BTC", "ETH"). The adapter handles conversion to exchange-specific formats internally based on the configured quote currency:

You Pass Binance (USDT) Binance (USDC) OKX (USDT) Hyperliquid
"BTC" "BTCUSDT" "BTCUSDC" "BTC-USDT-SWAP" "BTC"
3. Two-Layer Architecture
┌─────────────────────────────────────────────────────┐
│  Your Strategy / Application                        │
├─────────────────────────────────────────────────────┤
│  Adapter Layer (exchanges.Exchange interface)        │  ← Unified API
│    binance.Adapter / okx.Adapter / nado.Adapter     │
├─────────────────────────────────────────────────────┤
│  SDK Layer (low-level REST + WebSocket clients)      │  ← Exchange-specific
│    binance/sdk/ / okx/sdk/ / nado/sdk/              │
└─────────────────────────────────────────────────────┘
  • Adapter Layer: Implements exchanges.Exchange. Handles symbol mapping, order validation, slippage logic, and state management.
  • SDK Layer: Thin REST/WebSocket clients that map 1:1 to exchange API endpoints. You can use these directly for maximum flexibility.

Quick Start

Basic Usage
package main

import (
    "context"
    "fmt"

    exchanges "github.com/QuantProcessing/exchanges"
    "github.com/QuantProcessing/exchanges/binance"
    "github.com/shopspring/decimal"
)

func main() {
    ctx := context.Background()

    // Create a Binance perpetual adapter (defaults to USDT market)
    adp, err := binance.NewAdapter(ctx, binance.Options{
        APIKey:    "your-api-key",
        SecretKey: "your-secret-key",
        // QuoteCurrency: exchanges.QuoteCurrencyUSDC, // uncomment for USDC market
    })
    if err != nil {
        panic(err)
    }
    defer adp.Close()

    // Fetch ticker
    ticker, err := adp.FetchTicker(ctx, "BTC")
    if err != nil {
        panic(err)
    }
    fmt.Printf("BTC price: %s\n", ticker.LastPrice)

    // Fetch order book
    ob, err := adp.FetchOrderBook(ctx, "BTC", 5)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Best bid: %s, Best ask: %s\n",
        ob.Bids[0].Price, ob.Asks[0].Price)

    // Place a limit order
    order, err := adp.PlaceOrder(ctx, &exchanges.OrderParams{
        Symbol:   "BTC",
        Side:     exchanges.OrderSideBuy,
        Type:     exchanges.OrderTypeLimit,
        Price:    ticker.Bid,
        Quantity: decimal.NewFromFloat(0.001),
    })
    if err != nil {
        panic(err)
    }
    fmt.Printf("Order placed: %s\n", order.OrderID)
}
Market Order with Slippage Protection
// Market order with 0.5% slippage protection
// Internally converts to LIMIT IOC at (ask * 1.005) for buys
order, err := adp.PlaceOrder(ctx, &exchanges.OrderParams{
    Symbol:   "ETH",
    Side:     exchanges.OrderSideBuy,
    Type:     exchanges.OrderTypeMarket,
    Quantity: decimal.NewFromFloat(0.1),
    Slippage: decimal.NewFromFloat(0.005), // 0.5%
})
Convenience Functions
// One-liner market order
order, err := exchanges.PlaceMarketOrder(ctx, adp, "BTC", exchanges.OrderSideBuy, qty)

// One-liner limit order
order, err := exchanges.PlaceLimitOrder(ctx, adp, "BTC", exchanges.OrderSideBuy, price, qty)

// Market order with slippage
order, err := exchanges.PlaceMarketOrderWithSlippage(ctx, adp, "BTC", exchanges.OrderSideBuy, qty, slippage)
WebSocket Streaming
// Real-time order book (locally maintained)
err := adp.WatchOrderBook(ctx, "BTC", func(ob *exchanges.OrderBook) {
    fmt.Printf("BTC bid: %s ask: %s\n", ob.Bids[0].Price, ob.Asks[0].Price)
})

// Pull latest snapshot anytime (zero-latency, no API call)
ob := adp.GetLocalOrderBook("BTC", 5)

// Real-time ticker
adp.WatchTicker(ctx, "BTC", func(t *exchanges.Ticker) {
    fmt.Printf("Price: %s\n", t.LastPrice)
})

// Real-time order updates (fills, cancellations)
adp.WatchOrders(ctx, func(o *exchanges.Order) {
    fmt.Printf("Order %s: %s\n", o.OrderID, o.Status)
})

// Real-time position updates
adp.WatchPositions(ctx, func(p *exchanges.Position) {
    fmt.Printf("%s: %s %s @ %s\n", p.Symbol, p.Side, p.Quantity, p.EntryPrice)
})
Using PerpExchange Extensions
// Type assert for perp-specific features
if perp, ok := adp.(exchanges.PerpExchange); ok {
    // Set leverage
    perp.SetLeverage(ctx, "BTC", 10)

    // Get positions
    positions, _ := perp.FetchPositions(ctx)
    for _, p := range positions {
        fmt.Printf("%s: %s %s\n", p.Symbol, p.Side, p.Quantity)
    }

    // Get funding rate
    fr, _ := perp.FetchFundingRate(ctx, "BTC")
    fmt.Printf("Funding rate: %s\n", fr.FundingRate)
}
LocalState (Unified State Management)
// LocalState wraps any Exchange adapter — auto-subscribes to WS streams,
// maintains orders/positions/balance, and provides fan-out event subscriptions.
state := exchanges.NewLocalState(adp, nil)
err := state.Start(ctx) // REST snapshot + auto WatchOrders/WatchPositions + periodic refresh

// Read state anytime (thread-safe, zero-latency)
pos, ok := state.GetPosition("BTC")
order, ok := state.GetOrder("order-123")
balance := state.GetBalance()

// Fan-out event subscriptions (multiple consumers supported)
sub := state.SubscribeOrders()
defer sub.Unsubscribe()
go func() {
    for order := range sub.C {
        fmt.Printf("Order update: %s %s\n", order.OrderID, order.Status)
    }
}()

// Place order with integrated tracking — no need for separate WatchOrders
result, err := state.PlaceOrder(ctx, &exchanges.OrderParams{
    Symbol:   "BTC",
    Side:     exchanges.OrderSideBuy,
    Type:     exchanges.OrderTypeMarket,
    Quantity: decimal.NewFromFloat(0.001),
})
defer result.Done()
filled, err := result.WaitTerminal(30 * time.Second) // blocks until FILLED/CANCELLED/REJECTED
Switching Exchanges
// Binance — USDT market (default)
adp, _ := binance.NewAdapter(ctx, binance.Options{
    APIKey: os.Getenv("BINANCE_API_KEY"), SecretKey: os.Getenv("BINANCE_SECRET_KEY"),
})

// Binance — USDC market
adpUSDC, _ := binance.NewAdapter(ctx, binance.Options{
    APIKey: os.Getenv("BINANCE_API_KEY"), SecretKey: os.Getenv("BINANCE_SECRET_KEY"),
    QuoteCurrency: exchanges.QuoteCurrencyUSDC,
})

// OKX — same interface, different constructor
adp, _ := okx.NewAdapter(ctx, okx.Options{
    APIKey: os.Getenv("OKX_API_KEY"), SecretKey: os.Getenv("OKX_SECRET_KEY"),
    Passphrase: os.Getenv("OKX_PASSPHRASE"),
})

// Hyperliquid — wallet-based auth (USDC only)
adp, _ := hyperliquid.NewAdapter(ctx, hyperliquid.Options{
    PrivateKey: os.Getenv("HYPERLIQUID_PRIVATE_KEY"), AccountAddr: os.Getenv("HYPERLIQUID_ACCOUNT_ADDR"),
})

// All adapters expose the exact same Exchange interface
ticker, _ := adp.FetchTicker(ctx, "BTC")
Quote Currency

Each adapter supports a QuoteCurrency option that determines which quote currency market to connect to. If omitted, the exchange-specific default is used (CEX → USDT, DEX → USDC).

// Available quote currencies
exchanges.QuoteCurrencyUSDT // "USDT"
exchanges.QuoteCurrencyUSDC // "USDC"
exchanges.QuoteCurrencyDUSD // "DUSD" (StandX only)

Passing an unsupported quote currency returns an error at construction time:

// This will fail: Hyperliquid only supports USDC
_, err := hyperliquid.NewAdapter(ctx, hyperliquid.Options{
    QuoteCurrency: exchanges.QuoteCurrencyUSDT, // error!
})
// err: "hyperliquid: unsupported quote currency "USDT", supported: [USDC]"

API Reference

Exchange Interface (Core)

Every adapter implements these methods:

Category Method Description
Identity GetExchange() Returns exchange name (e.g. "BINANCE")
GetMarketType() Returns "perp" or "spot"
Close() Closes all connections
Symbol FormatSymbol(symbol) Converts "BTC" → exchange format
ExtractSymbol(symbol) Converts exchange format → "BTC"
ListSymbols() Returns all available symbols
Market Data FetchTicker(ctx, symbol) Latest price, bid/ask, 24h volume
FetchOrderBook(ctx, symbol, limit) Order book snapshot (REST)
FetchTrades(ctx, symbol, limit) Recent trades
FetchKlines(ctx, symbol, interval, opts) Candlestick/OHLCV data
Trading PlaceOrder(ctx, params) Place order (market/limit/post-only)
CancelOrder(ctx, orderID, symbol) Cancel a single order
CancelAllOrders(ctx, symbol) Cancel all open orders for a symbol
FetchOrderByID(ctx, orderID, symbol) Get a single order by ID, including terminal orders when supported
FetchOrders(ctx, symbol) List all visible orders for a symbol
FetchOpenOrders(ctx, symbol) List all open orders
Account FetchAccount(ctx) Full account: balance + positions + orders
FetchBalance(ctx) Available balance only
FetchSymbolDetails(ctx, symbol) Precision & min quantity rules
FetchFeeRate(ctx, symbol) Maker/taker fee rates
Orderbook WatchOrderBook(ctx, symbol, cb) Subscribe to WS orderbook (blocks until ready)
GetLocalOrderBook(symbol, depth) Read local WS-maintained orderbook
StopWatchOrderBook(ctx, symbol) Unsubscribe
Streaming WatchOrders(ctx, cb) Real-time order updates
WatchPositions(ctx, cb) Real-time position updates
WatchTicker(ctx, symbol, cb) Real-time ticker
WatchTrades(ctx, symbol, cb) Real-time trades
WatchKlines(ctx, symbol, interval, cb) Real-time klines

FetchOrderByID, FetchOrders, and FetchOpenOrders are intentionally separate: single-order lookup should not be implemented by scanning open orders, and FetchOrders is broader than FetchOpenOrders.

PerpExchange Interface (extends Exchange)
Method Description
FetchPositions(ctx) Get all open positions
SetLeverage(ctx, symbol, leverage) Set leverage for a symbol
FetchFundingRate(ctx, symbol) Current funding rate
FetchAllFundingRates(ctx) All funding rates
ModifyOrder(ctx, orderID, symbol, params) Modify an open order (price/qty)
SpotExchange Interface (extends Exchange)
Method Description
FetchSpotBalances(ctx) Per-asset balances (free/locked)
TransferAsset(ctx, params) Transfer between spot/futures accounts
OrderParams
type OrderParams struct {
    Symbol      string          // Base symbol: "BTC", "ETH"
    Side        OrderSide       // OrderSideBuy or OrderSideSell
    Type        OrderType       // OrderTypeMarket, OrderTypeLimit, OrderTypePostOnly
    Quantity    decimal.Decimal // Order quantity
    Price       decimal.Decimal // Required for LIMIT orders
    TimeInForce TimeInForce     // GTC (default), IOC, FOK
    ReduceOnly  bool            // Reduce-only order
    Slippage    decimal.Decimal // If > 0, MARKET → LIMIT IOC with slippage
    ClientID    string          // Client-defined order ID
}
Backpack ClientID Note

Backpack requires a numeric clientId in the valid uint32 range. If you want to set OrderParams.ClientID yourself, do not pass UUIDs, timestamps, or any value larger than 4294967295.

Use the package helper instead:

import "github.com/QuantProcessing/exchanges/backpack"

params := &exchanges.OrderParams{
    Symbol:   "BTC",
    Side:     exchanges.OrderSideBuy,
    Type:     exchanges.OrderTypeLimit,
    Quantity: qty,
    Price:    price,
    ClientID: backpack.GenerateClientID(),
}

If ClientID is left empty, the Backpack adapter generates a safe one automatically.

Error Handling
order, err := adp.PlaceOrder(ctx, params)
if err != nil {
    // Structured error matching
    if errors.Is(err, exchanges.ErrInsufficientBalance) {
        // Handle insufficient balance
    }
    if errors.Is(err, exchanges.ErrMinQuantity) {
        // Handle below minimum quantity
    }
    if errors.Is(err, exchanges.ErrRateLimited) {
        // Handle rate limit according to your own retry/backoff policy
    }

    // Access exchange-specific details
    var exErr *exchanges.ExchangeError
    if errors.As(err, &exErr) {
        fmt.Printf("[%s] Code: %s, Message: %s\n", exErr.Exchange, exErr.Code, exErr.Message)
    }
}

Available sentinel errors: ErrInsufficientBalance, ErrRateLimited, ErrInvalidPrecision, ErrOrderNotFound, ErrSymbolNotFound, ErrMinNotional, ErrMinQuantity, ErrAuthFailed, ErrNetworkTimeout, ErrNotSupported.

Rate Limit Error Handling

When an exchange returns a rate-limit error, the SDK wraps it as a structured ExchangeError with ErrRateLimited as the cause. The error flows through the entire call chain:

Your Code (caller)
  → adapter.PlaceOrder()     // transparent pass-through (return nil, err)
    → client.Post()          // returns exchanges.NewExchangeError(..., ErrRateLimited)

Basic detection — use errors.Is() to check for rate limiting:

order, err := adp.PlaceOrder(ctx, params)
if errors.Is(err, exchanges.ErrRateLimited) {
    log.Warn("rate limited, backing off...")
    time.Sleep(5 * time.Second)
}

Extract exchange-specific details — use errors.As() for the full error context:

var exErr *exchanges.ExchangeError
if errors.As(err, &exErr) && errors.Is(err, exchanges.ErrRateLimited) {
    fmt.Printf("Exchange: %s\n", exErr.Exchange) // "BINANCE", "GRVT", "LIGHTER", etc.
    fmt.Printf("Code:     %s\n", exErr.Code)     // "-1003", "1006", "429"
    fmt.Printf("Message:  %s\n", exErr.Message)  // Original error message
}

Recommended retry pattern — exponential backoff:

func placeOrderWithRetry(ctx context.Context, adp exchanges.Exchange, params *exchanges.OrderParams) (*exchanges.Order, error) {
    maxRetries := 3
    for i := 0; i < maxRetries; i++ {
        order, err := adp.PlaceOrder(ctx, params)
        if err == nil {
            return order, nil
        }
        if !errors.Is(err, exchanges.ErrRateLimited) {
            return nil, err // Non-rate-limit error, fail immediately
        }
        backoff := time.Duration(1<<uint(i)) * time.Second // 1s, 2s, 4s
        log.Warnf("rate limited (attempt %d/%d), retrying in %v", i+1, maxRetries, backoff)
        select {
        case <-time.After(backoff):
        case <-ctx.Done():
            return nil, ctx.Err()
        }
    }
    return nil, fmt.Errorf("rate limited after %d retries", maxRetries)
}

Design note: The library deliberately does not implement automatic retry or backoff. Rate-limit handling strategy (fixed delay, exponential backoff, circuit breaker, etc.) is a business-level decision that callers should own.


Built-in Safety Features

Rate Limiting

Rate-limit detection is implemented at the SDK layer for every supported exchange:

Exchange Detection Signal Error Code Details
Binance HTTP 429/418, code -1003/-1015, header X-Mbx-Used-Weight -1003, -1015 Weight-based + order count tracking
Aster Same as Binance (Binance-family fork) -1003, -1015 Same X-Mbx-* header support
OKX HTTP 429, code 50011/50061 50011, 50061 Per-endpoint rate limits
Hyperliquid HTTP 429, message-based detection 429 Message content matching
EdgeX HTTP 429, code/message-based detection 429 Custom error code/message
GRVT HTTP 429, error code 1006 1006 Per-instrument tracking
Lighter HTTP 429 429 Weight-based (60 req/min standard)
Nado HTTP 429 429 1200 req/min per IP
StandX HTTP 429 429 Retry-After header support

All rate-limit errors are wrapped as ExchangeError with ErrRateLimited as the unwrappable cause. Use errors.Is(err, exchanges.ErrRateLimited) for detection — see Rate Limit Error Handling for detailed usage.

IP Ban Detection & Recovery

If an exchange returns explicit ban or throttle errors (for example HTTP 418/429), the SDK surfaces them as structured exchange errors so callers can decide whether to retry, back off, or pause.

Order Validation

Before sending orders, adapters automatically:

  • Round price to symbol's price precision
  • Truncate quantity to symbol's quantity precision
  • Validate minimum quantity and notional constraints

Logger

All adapters accept an optional Logger for structured logging:

// Compatible with *zap.SugaredLogger
logger := zap.NewProduction().Sugar()
adp, _ := binance.NewAdapter(ctx, binance.Options{
    APIKey: "...", SecretKey: "...",
    Logger: logger,
})

If no logger is provided, NopLogger is used. The interface:

type Logger interface {
    Debugw(msg string, keysAndValues ...any)
    Infow(msg string, keysAndValues ...any)
    Warnw(msg string, keysAndValues ...any)
    Errorw(msg string, keysAndValues ...any)
}

Project Structure

exchanges/                  Root package — interfaces, models, errors, utilities
├── exchange.go             Core Exchange / PerpExchange / SpotExchange interfaces
├── models.go               Unified data types (Order, Position, Ticker, etc.)
├── errors.go               Sentinel errors + ExchangeError type
├── base_adapter.go         Shared adapter logic (orderbook, validation, common helpers)
├── local_state.go          LocalOrderBook interface + unified LocalState manager
├── event_bus.go            Generic EventBus[T] for fan-out pub/sub
├── log.go                  Logger interface + NopLogger
├── testsuite/              Adapter compliance test suite
├── binance/                Binance adapter + SDK
│   ├── options.go          Options{APIKey, SecretKey, QuoteCurrency, Logger}
│   ├── perp_adapter.go     Perp adapter (Exchange + PerpExchange)
│   ├── spot_adapter.go     Spot adapter (Exchange + SpotExchange)
│   └── sdk/                Low-level REST & WebSocket clients
├── okx/                    OKX (same structure)
├── aster/                  Aster
├── nado/                   Nado
├── lighter/                Lighter
├── hyperliquid/            Hyperliquid
├── standx/                 StandX
├── grvt/                   GRVT (build tag: grvt)
└── edgex/                  EdgeX (build tag: edgex)

Testing

The repository uses a layered verification model. Plain go test ./... is not the canonical gate because some packages include live exchange coverage that depends on credentials or longer-running WebSocket sessions.

Copy the example environment file and fill in your credentials when you need live/private verification:

cp .env.example .env

Run the default quick gate:

go test -short ./...

Run a focused short verification for one exchange:

scripts/verify_exchange.sh backpack
scripts/verify_exchange.sh okx
scripts/verify_exchange.sh hyperliquid

Run the full regression suite:

GOCACHE=/tmp/exchanges-gocache bash scripts/verify_full.sh

verify_full.sh loads .env from the repository root, preserves already-exported shell variables, and manages RUN_FULL=1 internally. It also accepts these legacy aliases:

  • EDGEX_PRIVATE_KEY -> EDGEX_STARK_PRIVATE_KEY
  • NADO_SUB_ACCOUNT_NAME -> NADO_SUBACCOUNT_NAME
  • OKX_SECRET_KEY -> OKX_API_SECRET
  • OKX_PASSPHRASE -> OKX_API_PASSPHRASE

Run the soak suite for longer-lived subscription checks:

GOCACHE=/tmp/exchanges-gocache RUN_SOAK=1 bash scripts/verify_soak.sh

The current soak suite runs 3-minute stream checks for the designated packages. RUN_FULL and RUN_SOAK are script-managed toggles for the dedicated verification entrypoints above; they are not the default repository gate.

License

MIT

Documentation

Overview

Package exchanges provides a unified Go SDK for interacting with multiple cryptocurrency exchanges. It offers both low-level SDK clients (REST + WebSocket) and high-level adapters implementing a common Exchange interface.

Quick Start

adp, err := binance.NewAdapter(ctx, binance.Options{
    APIKey:    "your-api-key",
    SecretKey: "your-secret-key",
})
if err != nil {
    log.Fatal(err)
}
defer adp.Close()

ticker, err := adp.FetchTicker(ctx, "BTC")

Architecture

The package is organized into several layers:

  • Root package (exchanges): Unified interfaces, models, errors, and utilities
  • Exchange packages (binance/, okx/, ...): Exchange-specific adapters and SDK clients
  • Testsuite package: Adapter compliance test suite

Logger

All adapters accept an optional Logger interface for structured logging. The interface is compatible with *zap.SugaredLogger. If no logger is provided, NopLogger is used (all output discarded).

Symbol Convention

All Exchange methods accept a base currency symbol (e.g. "BTC", "ETH"). The adapter handles conversion to exchange-specific formats internally.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrInsufficientBalance = errors.New("insufficient balance")
	ErrRateLimited         = errors.New("rate limited")
	ErrInvalidPrecision    = errors.New("invalid precision")
	ErrOrderNotFound       = errors.New("order not found")
	ErrSymbolNotFound      = errors.New("symbol not found")
	ErrMinNotional         = errors.New("below minimum notional")
	ErrMinQuantity         = errors.New("below minimum quantity")
	ErrAuthFailed          = errors.New("authentication failed")
	ErrNetworkTimeout      = errors.New("network timeout")
	ErrNotSupported        = errors.New("not supported")
)

Functions

func CountDecimalPlaces

func CountDecimalPlaces(s string) int32

CountDecimalPlaces returns how many significant decimal places a string representation has. Trailing zeros are not counted: "0.00010" → 4, "0.10" → 1.

func DerivePartialFillStatus added in v0.1.3

func DerivePartialFillStatus(order *Order)

DerivePartialFillStatus adjusts order status to PARTIALLY_FILLED when the exchange reports status as NEW but FilledQuantity > 0. Exchanges like Binance, OKX, and Lighter natively report PARTIALLY_FILLED; this helper is for exchanges that don't (Hyperliquid, Nado, StandX, GRVT, EdgeX).

func FloorToPrecision

func FloorToPrecision(value decimal.Decimal, precision int32) decimal.Decimal

FloorToPrecision truncates (floors) a decimal value to the given number of decimal places.

func FormatDecimal

func FormatDecimal(d decimal.Decimal) string

FormatDecimal formats a decimal to string, stripping trailing zeros.

func GenerateID

func GenerateID() string

GenerateID returns a unique numeric string safe for concurrent use. Values stay within [1, 2^48-1] range required by Lighter; other exchanges accept any numeric string.

func Register

func Register(name string, ctor AdapterConstructor)

Register adds an exchange adapter constructor to the global registry. Call this from each exchange adapter's init() function:

func init() {
    exchanges.Register("BINANCE", func(ctx context.Context, mt exchanges.MarketType, opts map[string]string) (exchanges.Exchange, error) {
        return NewPerpAdapter(ctx, Options{
            APIKey:    opts["api_key"],
            SecretKey: opts["secret_key"],
        })
    })
}

func RegisteredExchanges

func RegisteredExchanges() []string

RegisteredExchanges returns the names of all registered exchanges.

func RoundToPrecision

func RoundToPrecision(value decimal.Decimal, precision int32) decimal.Decimal

RoundToPrecision rounds a decimal value to the given number of decimal places.

func RoundToSignificantFigures

func RoundToSignificantFigures(value decimal.Decimal, sigFigs int32) decimal.Decimal

RoundToSignificantFigures rounds a decimal to n significant figures.

func ValidateAndFormatParams

func ValidateAndFormatParams(params *OrderParams, details *SymbolDetails) error

ValidateAndFormatParams validates and formats order parameters using cached symbol details. It rounds price and truncates quantity to the correct precision.

Types

type Account

type Account struct {
	TotalBalance     decimal.Decimal `json:"total_balance"`
	AvailableBalance decimal.Decimal `json:"available_balance"`
	Positions        []Position      `json:"positions"`
	Orders           []Order         `json:"orders"` // Open orders
	UnrealizedPnL    decimal.Decimal `json:"unrealized_pnl"`
	RealizedPnL      decimal.Decimal `json:"realized_pnl"`
}

Account represents a trading account summary.

type AccountType

type AccountType string

AccountType represents an account type for asset transfers.

const (
	AccountTypeSpot    AccountType = "SPOT"    // Spot account
	AccountTypePerp    AccountType = "PERP"    // Perpetual futures account
	AccountTypeUnified AccountType = "UNIFIED" // Unified account
)

type AdapterConstructor

type AdapterConstructor func(ctx context.Context, marketType MarketType, opts map[string]string) (Exchange, error)

AdapterConstructor is the function signature that each exchange registers. It receives an options map and creates an adapter for the requested market type.

func LookupConstructor

func LookupConstructor(name string) (AdapterConstructor, error)

LookupConstructor finds a registered constructor by name.

type BaseAdapter

type BaseAdapter struct {
	Name       string
	MarketType MarketType

	Logger Logger // Logger for this adapter
	// contains filtered or unexported fields
}

BaseAdapter is designed to be embedded in specific exchange adapters. It provides a unified implementation for common adapter requirements: - Connection tracking (WS Market, WS Order, WS Account) - Local OrderBook mapping and readiness waiting - Symbol detail caching - Automatic order validation and slippage handling

For local state management (Orders, Positions, Balance), use LocalState which wraps the Exchange adapter externally.

func NewBaseAdapter

func NewBaseAdapter(name string, marketType MarketType, logger Logger) *BaseAdapter

NewBaseAdapter creates a new initialized BaseAdapter

func (*BaseAdapter) ApplySlippage

func (b *BaseAdapter) ApplySlippage(
	ctx context.Context,
	params *OrderParams,
	fetchTicker func(ctx context.Context, symbol string) (*Ticker, error),
) error

ApplySlippage converts a MARKET order with Slippage>0 into a LIMIT IOC order. The fetchTicker function is injected by the concrete adapter.

func (*BaseAdapter) GetExchange

func (b *BaseAdapter) GetExchange() string

GetExchange returns the exchange name

func (*BaseAdapter) GetLocalOrderBook

func (b *BaseAdapter) GetLocalOrderBook(symbol string, depth int) *OrderBook

GetLocalOrderBook returns the standard OrderBook struct from the local WS-maintained orderbook. This satisfies the Exchange interface requirement.

func (*BaseAdapter) GetLocalOrderBookImplementation

func (b *BaseAdapter) GetLocalOrderBookImplementation(symbol string) (LocalOrderBook, bool)

GetLocalOrderBookImplementation returns the underlying LocalOrderBook implementation

func (*BaseAdapter) GetMarketType

func (b *BaseAdapter) GetMarketType() MarketType

GetMarketType returns the market type

func (*BaseAdapter) GetOrderMode

func (b *BaseAdapter) GetOrderMode() OrderMode

GetOrderMode returns the current order mode (defaults to WS).

func (*BaseAdapter) GetSymbolDetail

func (b *BaseAdapter) GetSymbolDetail(symbol string) (*SymbolDetails, error)

GetSymbolDetail returns the cached detail for a symbol

func (*BaseAdapter) IsAccountConnected

func (b *BaseAdapter) IsAccountConnected() bool

IsAccountConnected checks if account WS is connected

func (*BaseAdapter) IsMarketConnected

func (b *BaseAdapter) IsMarketConnected() bool

IsMarketConnected checks if market WS is connected

func (*BaseAdapter) IsOrderConnected

func (b *BaseAdapter) IsOrderConnected() bool

IsOrderConnected checks if order WS is connected

func (*BaseAdapter) IsRESTMode

func (b *BaseAdapter) IsRESTMode() bool

IsRESTMode returns true if orders should use REST/HTTP transport.

func (*BaseAdapter) ListSymbols

func (b *BaseAdapter) ListSymbols() []string

ListSymbols returns all symbols in the symbol details cache. These are base currency symbols (e.g. "BTC", "ETH") loaded at adapter init.

func (*BaseAdapter) MarkAccountConnected

func (b *BaseAdapter) MarkAccountConnected()

MarkAccountConnected marks the account websocket as connected

func (*BaseAdapter) MarkMarketConnected

func (b *BaseAdapter) MarkMarketConnected()

MarkMarketConnected marks the market websocket as connected

func (*BaseAdapter) MarkOrderConnected

func (b *BaseAdapter) MarkOrderConnected()

MarkOrderConnected marks the order websocket as connected

func (*BaseAdapter) RemoveLocalOrderBook

func (b *BaseAdapter) RemoveLocalOrderBook(symbol string)

RemoveLocalOrderBook removes a local orderbook

func (*BaseAdapter) SetLocalOrderBook

func (b *BaseAdapter) SetLocalOrderBook(symbol string, ob LocalOrderBook)

SetLocalOrderBook registers an instantiated LocalOrderBook implementation

func (*BaseAdapter) SetOrderMode

func (b *BaseAdapter) SetOrderMode(mode OrderMode)

SetOrderMode sets whether order operations use WS or REST transport.

func (*BaseAdapter) SetSymbolDetails

func (b *BaseAdapter) SetSymbolDetails(details map[string]*SymbolDetails)

SetSymbolDetails replaces the entire symbol details cache

func (*BaseAdapter) ValidateOrder

func (b *BaseAdapter) ValidateOrder(params *OrderParams) error

ValidateOrder validates and auto-formats order params using cached symbol details. Call this at the start of PlaceOrder in every adapter.

func (*BaseAdapter) WaitOrderBookReady

func (b *BaseAdapter) WaitOrderBookReady(ctx context.Context, symbol string) error

WaitOrderBookReady waits for a specific subscribed orderbook to be ready.

type EventBus added in v0.2.0

type EventBus[T any] struct {
	// contains filtered or unexported fields
}

EventBus provides fan-out event distribution. Multiple subscribers can listen concurrently; each receives all published events.

func NewEventBus added in v0.2.0

func NewEventBus[T any]() *EventBus[T]

NewEventBus creates a new EventBus.

func (*EventBus[T]) Close added in v0.2.0

func (b *EventBus[T]) Close()

Close removes all subscribers and closes all channels.

func (*EventBus[T]) Publish added in v0.2.0

func (b *EventBus[T]) Publish(event *T)

Publish sends an event to all current subscribers (non-blocking). If a subscriber's channel is full, the event is dropped for that subscriber.

func (*EventBus[T]) Subscribe added in v0.2.0

func (b *EventBus[T]) Subscribe() *Subscription[T]

Subscribe creates a new subscription that receives all published events. The returned channel is buffered (capacity 64).

type Exchange

type Exchange interface {
	// === Identity ===
	GetExchange() string
	GetMarketType() MarketType
	Close() error

	// === Symbol Mapping ===
	// FormatSymbol converts a base symbol (e.g. "BTC") to exchange-specific format.
	FormatSymbol(symbol string) string
	// ExtractSymbol converts an exchange-specific symbol back to base symbol.
	ExtractSymbol(symbol string) string
	// ListSymbols returns all symbols supported by this adapter.
	ListSymbols() []string

	// === Market Data (REST) ===
	FetchTicker(ctx context.Context, symbol string) (*Ticker, error)
	FetchOrderBook(ctx context.Context, symbol string, limit int) (*OrderBook, error)
	FetchTrades(ctx context.Context, symbol string, limit int) ([]Trade, error)
	FetchKlines(ctx context.Context, symbol string, interval Interval, opts *KlineOpts) ([]Kline, error)

	// === Trading ===
	PlaceOrder(ctx context.Context, params *OrderParams) (*Order, error)
	CancelOrder(ctx context.Context, orderID, symbol string) error
	CancelAllOrders(ctx context.Context, symbol string) error
	FetchOrderByID(ctx context.Context, orderID, symbol string) (*Order, error)
	FetchOrders(ctx context.Context, symbol string) ([]Order, error)
	FetchOpenOrders(ctx context.Context, symbol string) ([]Order, error)

	// === Account ===
	FetchAccount(ctx context.Context) (*Account, error)
	FetchBalance(ctx context.Context) (decimal.Decimal, error)
	FetchSymbolDetails(ctx context.Context, symbol string) (*SymbolDetails, error)
	FetchFeeRate(ctx context.Context, symbol string) (*FeeRate, error)

	// === Local OrderBook (WS-maintained) ===
	// WatchOrderBook subscribes to orderbook updates and maintains a local copy.
	// The callback is called on every update; pass nil for pull-only mode.
	// This method blocks until the initial snapshot is synced.
	WatchOrderBook(ctx context.Context, symbol string, cb OrderBookCallback) error
	GetLocalOrderBook(symbol string, depth int) *OrderBook
	StopWatchOrderBook(ctx context.Context, symbol string) error

	// === WebSocket Streaming ===
	Streamable
}

Exchange is the primary interface for strategy developers. It provides a unified, CCXT-inspired API for interacting with any exchanges.

Symbol convention: all methods accept a **base currency** symbol (e.g. "BTC", "ETH"). The adapter handles conversion to echange-specific formats internally.

Method naming convention: Fetch* = REST query, Watch* = WebSocket subscription.

type ExchangeError

type ExchangeError struct {
	Exchange string // Exchange name, e.g. "BINANCE"
	Code     string // Exchange-specific error code
	Message  string // Original error message from exchange
	Err      error  // Sentinel error for errors.Is matching
}

ExchangeError wraps an exchange-specific error with a sentinel cause. Use errors.Is(err, adapter.ErrInsufficientBalance) for structured handling.

func NewExchangeError

func NewExchangeError(exchange, code, message string, sentinel error) *ExchangeError

NewExchangeError creates a new ExchangeError.

func (*ExchangeError) Error

func (e *ExchangeError) Error() string

func (*ExchangeError) Unwrap

func (e *ExchangeError) Unwrap() error

type FeeRate

type FeeRate struct {
	Maker decimal.Decimal `json:"maker"`
	Taker decimal.Decimal `json:"taker"`
}

FeeRate represents maker/taker fee rates for a symbol.

type FundingRate

type FundingRate struct {
	Symbol               string          `json:"symbol"`
	FundingRate          decimal.Decimal `json:"funding_rate"`
	FundingIntervalHours int64           `json:"funding_interval_hours"`
	FundingTime          int64           `json:"funding_time"`
	NextFundingTime      int64           `json:"next_funding_time"`
	UpdateTime           int64           `json:"update_time"`
}

FundingRate represents the funding rate for a perpetual futures symbol.

type Interval

type Interval string

Interval represents the candlestick/kline time period.

const (
	Interval1m  Interval = "1m"
	Interval3m  Interval = "3m"
	Interval5m  Interval = "5m"
	Interval15m Interval = "15m"
	Interval30m Interval = "30m"
	Interval1h  Interval = "1h"
	Interval2h  Interval = "2h"
	Interval4h  Interval = "4h"
	Interval6h  Interval = "6h"
	Interval8h  Interval = "8h"
	Interval12h Interval = "12h"
	Interval1d  Interval = "1d"
	Interval3d  Interval = "3d"
	Interval1w  Interval = "1w"
	Interval1M  Interval = "1M"
)

type IsolatedMarginAccount

type IsolatedMarginAccount struct {
	Assets            []IsolatedMarginSymbol `json:"assets"`
	TotalAssetBTC     decimal.Decimal        `json:"total_asset_btc"`
	TotalLiabilityBTC decimal.Decimal        `json:"total_liability_btc"`
	TotalNetAssetBTC  decimal.Decimal        `json:"total_net_asset_btc"`
}

IsolatedMarginAccount represents an isolated margin account summary.

type IsolatedMarginAsset

type IsolatedMarginAsset struct {
	Asset         string          `json:"asset"`
	BorrowEnabled bool            `json:"borrow_enabled"`
	Borrowed      decimal.Decimal `json:"borrowed"`
	Free          decimal.Decimal `json:"free"`
	Interest      decimal.Decimal `json:"interest"`
	Locked        decimal.Decimal `json:"locked"`
	NetAsset      decimal.Decimal `json:"net_asset"`
	TotalAsset    decimal.Decimal `json:"total_asset"`
}

IsolatedMarginAsset represents a single asset in an isolated margin account.

type IsolatedMarginSymbol

type IsolatedMarginSymbol struct {
	Symbol         string              `json:"symbol"`
	BaseAsset      IsolatedMarginAsset `json:"base_asset"`
	QuoteAsset     IsolatedMarginAsset `json:"quote_asset"`
	MarginLevel    decimal.Decimal     `json:"margin_level"`
	MarginRatio    decimal.Decimal     `json:"margin_ratio"`
	IndexPrice     decimal.Decimal     `json:"index_price"`
	LiquidatePrice decimal.Decimal     `json:"liquidate_price"`
	LiquidateRate  decimal.Decimal     `json:"liquidate_rate"`
	Enabled        bool                `json:"enabled"`
}

IsolatedMarginSymbol represents an isolated margin trading pair.

type Kline

type Kline struct {
	Symbol    string          `json:"symbol"`
	Interval  Interval        `json:"interval"`
	Open      decimal.Decimal `json:"open"`
	High      decimal.Decimal `json:"high"`
	Low       decimal.Decimal `json:"low"`
	Close     decimal.Decimal `json:"close"`
	Volume    decimal.Decimal `json:"volume"`    // Base currency volume
	QuoteVol  decimal.Decimal `json:"quote_vol"` // Quote currency volume
	Timestamp int64           `json:"timestamp"` // Open time in milliseconds
}

Kline represents a single candlestick/OHLCV bar.

type KlineCallback

type KlineCallback func(*Kline)

type KlineOpts

type KlineOpts struct {
	Start *time.Time
	End   *time.Time
	Limit int
}

KlineOpts provides optional parameters for FetchKlines.

type Level

type Level struct {
	Price    decimal.Decimal `json:"price"`
	Quantity decimal.Decimal `json:"quantity"`
}

Level represents a single price level in the order book.

type LocalOrderBook

type LocalOrderBook interface {
	// GetDepth returns the sorted top `limit` depth levels.
	// Bids are sorted descending (highest price first).
	// Asks are sorted ascending (lowest price first).
	GetDepth(limit int) ([]Level, []Level)

	// WaitReady blocks until the orderbook is initialized or the timeout expires.
	WaitReady(ctx context.Context, timeout time.Duration) bool

	// Timestamp returns the Unix millisecond timestamp of the last update.
	Timestamp() int64
}

LocalOrderBook interface standardizes the output of locally maintained orderbooks. This allows the BaseAdapter and upper logic layers to consume depth data uniformly, regardless of whether the internal sync uses delta snapshots, buffer timestamps, or gapless polling.

Each exchange implements this interface in its own orderbook.go file because synchronization protocols differ (Binance: diff+snapshot, Nado: gap detection, OKX: checksum validation, etc.).

type LocalState added in v0.2.0

type LocalState struct {
	// contains filtered or unexported fields
}

LocalState provides unified local state management by wrapping an Exchange adapter. It automatically maintains Orders, Positions, and Balance via WebSocket streams, and provides fan-out event subscriptions and integrated order tracking.

Usage:

state := exchanges.NewLocalState(adp, logger)
state.Start(ctx)

// Query state
pos, _ := state.GetPosition("BTC")

// Place order with tracking
result, _ := state.PlaceOrder(ctx, params)
defer result.Done()
filled, _ := result.WaitTerminal(30 * time.Second)

func NewLocalState added in v0.2.0

func NewLocalState(adp Exchange, logger Logger) *LocalState

NewLocalState creates a new LocalState wrapping the given Exchange adapter.

func (*LocalState) Close added in v0.2.0

func (s *LocalState) Close()

Close stops the LocalState and releases resources.

func (*LocalState) GetAllOpenOrders added in v0.2.0

func (s *LocalState) GetAllOpenOrders() []Order

GetAllOpenOrders returns copies of all open orders.

func (*LocalState) GetAllPositions added in v0.2.0

func (s *LocalState) GetAllPositions() []Position

GetAllPositions returns copies of all positions.

func (*LocalState) GetBalance added in v0.2.0

func (s *LocalState) GetBalance() decimal.Decimal

GetBalance returns the last known balance.

func (*LocalState) GetOrder added in v0.2.0

func (s *LocalState) GetOrder(orderID string) (*Order, bool)

GetOrder returns a copy of the order if found in local state.

func (*LocalState) GetPosition added in v0.2.0

func (s *LocalState) GetPosition(symbol string) (*Position, bool)

GetPosition returns a copy of the position for the symbol.

func (*LocalState) PlaceOrder added in v0.2.0

func (s *LocalState) PlaceOrder(ctx context.Context, params *OrderParams) (*OrderResult, error)

PlaceOrder places an order and returns an OrderResult with a subscription that receives only this order's status updates.

Usage:

result, err := state.PlaceOrder(ctx, params)
defer result.Done()
filled, err := result.WaitTerminal(30 * time.Second)

func (*LocalState) Start added in v0.2.0

func (s *LocalState) Start(ctx context.Context) error

Start initializes local state and subscribes to WebSocket streams. It performs: REST snapshot → WatchOrders → WatchPositions → periodic refresh.

func (*LocalState) SubscribeOrders added in v0.2.0

func (s *LocalState) SubscribeOrders() *Subscription[Order]

SubscribeOrders returns a subscription for all order update events. Call sub.Unsubscribe() when done.

func (*LocalState) SubscribePositions added in v0.2.0

func (s *LocalState) SubscribePositions() *Subscription[Position]

SubscribePositions returns a subscription for all position update events.

type Logger

type Logger interface {
	Debugw(msg string, keysAndValues ...any)
	Infow(msg string, keysAndValues ...any)
	Warnw(msg string, keysAndValues ...any)
	Errorw(msg string, keysAndValues ...any)
}

Logger is the logging interface used throughout the library. It is compatible with *zap.SugaredLogger out of the box.

Usage:

// With zap:
logger := zap.NewProduction().Sugar()
adp, err := binance.NewAdapter(ctx, binance.Options{Logger: logger})

// With NopLogger (default when no logger is provided):
adp, err := binance.NewAdapter(ctx, binance.Options{})
var NopLogger Logger = nopLogger{}

NopLogger discards all log output. This is the default when no logger is provided.

type Manager

type Manager struct {
	// contains filtered or unexported fields
}

Manager manages multiple exchange adapters

func NewManager

func NewManager() *Manager

NewManager creates a new adapter manager

func (*Manager) CloseAll

func (m *Manager) CloseAll() error

CloseAll closes all adapters

func (*Manager) GetAdapter

func (m *Manager) GetAdapter(name string) (Exchange, error)

GetAdapter gets the adapter for a specific exchange

func (*Manager) GetAllAdapters

func (m *Manager) GetAllAdapters() []Exchange

GetAllAdapters returns all registered adapters

func (*Manager) GetExchangeNames

func (m *Manager) GetExchangeNames() []string

GetExchangeNames returns sorted list of registered exchange names

func (*Manager) Register

func (m *Manager) Register(name string, adp Exchange)

Register registers an adapter

type MarginAccount

type MarginAccount struct {
	MarginLevel       decimal.Decimal `json:"margin_level"`
	TotalAssetBTC     decimal.Decimal `json:"total_asset_btc"`
	TotalLiabilityBTC decimal.Decimal `json:"total_liability_btc"`
	TotalNetAssetBTC  decimal.Decimal `json:"total_net_asset_btc"`
	UserAssets        []MarginAsset   `json:"user_assets"`
}

MarginAccount represents a cross-margin account summary.

type MarginAsset

type MarginAsset struct {
	Asset    string          `json:"asset"`
	Borrowed decimal.Decimal `json:"borrowed"`
	Free     decimal.Decimal `json:"free"`
	Interest decimal.Decimal `json:"interest"`
	Locked   decimal.Decimal `json:"locked"`
	NetAsset decimal.Decimal `json:"net_asset"`
}

MarginAsset represents a single asset in a margin account.

type MarginType

type MarginType string
const (
	MarginTypeCrossed  MarginType = "CROSSED"
	MarginTypeIsolated MarginType = "ISOLATED"
	MarginTypeCash     MarginType = "CASH"
)

type MarketType

type MarketType string

MarketType represents the type of trading market.

const (
	MarketTypeSpot MarketType = "spot" // Spot trading
	MarketTypePerp MarketType = "perp" // Perpetual futures
)

type ModifyOrderParams

type ModifyOrderParams struct {
	Quantity decimal.Decimal
	Price    decimal.Decimal
}

ModifyOrderParams specifies parameters for modifying an existing order.

type Order

type Order struct {
	OrderID        string          `json:"order_id"`
	Symbol         string          `json:"symbol"`
	Side           OrderSide       `json:"side"`
	Type           OrderType       `json:"type"`
	Quantity       decimal.Decimal `json:"quantity"`
	Price          decimal.Decimal `json:"price,omitempty"`
	Status         OrderStatus     `json:"status"`
	FilledQuantity decimal.Decimal `json:"filled_quantity"`
	Timestamp      int64           `json:"timestamp"`
	Fee            decimal.Decimal `json:"fee,omitempty"`
	ClientOrderID  string          `json:"client_order_id,omitempty"`
	ReduceOnly     bool            `json:"reduce_only,omitempty"`
	TimeInForce    TimeInForce     `json:"time_in_force,omitempty"`
}

Order represents a trading order with its current state.

func PlaceLimitOrder

func PlaceLimitOrder(ctx context.Context, adp Exchange, symbol string, side OrderSide, price, qty decimal.Decimal) (*Order, error)

PlaceLimitOrder is a convenience function for placing a limit order.

func PlaceMarketOrder

func PlaceMarketOrder(ctx context.Context, adp Exchange, symbol string, side OrderSide, qty decimal.Decimal) (*Order, error)

PlaceMarketOrder is a convenience function for placing a market order.

func PlaceMarketOrderWithSlippage

func PlaceMarketOrderWithSlippage(ctx context.Context, adp Exchange, symbol string, side OrderSide, qty, slippage decimal.Decimal) (*Order, error)

PlaceMarketOrderWithSlippage is a convenience function for placing a market order with slippage protection.

type OrderBook

type OrderBook struct {
	Symbol    string  `json:"symbol"`
	Bids      []Level `json:"bids"` // Sorted descending by price
	Asks      []Level `json:"asks"` // Sorted ascending by price
	Timestamp int64   `json:"timestamp"`
}

OrderBook represents a snapshot of the order book.

type OrderBookCallback

type OrderBookCallback func(*OrderBook)

type OrderMode

type OrderMode string

OrderMode controls whether trading operations (PlaceOrder, CancelOrder, etc.) use WebSocket or REST/HTTP transport.

const (
	// OrderModeWS uses WebSocket for order operations (default, lower latency).
	OrderModeWS OrderMode = "ws"
	// OrderModeREST uses REST/HTTP for order operations (no persistent connection needed).
	OrderModeREST OrderMode = "rest"
)

type OrderParams

type OrderParams struct {
	Symbol      string
	Side        OrderSide
	Type        OrderType // MARKET or LIMIT
	Quantity    decimal.Decimal
	Price       decimal.Decimal // Required for LIMIT; ignored for MARKET (unless Slippage > 0)
	TimeInForce TimeInForce     // Default: GTC for LIMIT
	ReduceOnly  bool
	Slippage    decimal.Decimal // If > 0 and Type == MARKET, auto-applies slippage logic
	ClientID    string          // Client-defined order ID
}

OrderParams is the unified parameter struct for PlaceOrder.

type OrderResult added in v0.2.0

type OrderResult struct {
	Order *Order               // Initial order snapshot
	Sub   *Subscription[Order] // Filtered updates for this order only
}

OrderResult holds the result of PlaceOrder along with a filtered subscription for tracking this specific order's lifecycle.

func (*OrderResult) Done added in v0.2.0

func (r *OrderResult) Done()

Done releases the subscription resources. Always call this when finished.

func (*OrderResult) WaitTerminal added in v0.2.0

func (r *OrderResult) WaitTerminal(timeout time.Duration) (*Order, error)

WaitTerminal blocks until the order reaches a terminal state (FILLED, CANCELLED, or REJECTED) or the timeout expires.

type OrderSide

type OrderSide string

OrderSide represents the direction of an order (buy or sell).

const (
	OrderSideBuy  OrderSide = "BUY"
	OrderSideSell OrderSide = "SELL"
)

type OrderStatus

type OrderStatus string

OrderStatus represents the state of an order.

const (
	OrderStatusPending         OrderStatus = "PENDING"
	OrderStatusPartiallyFilled OrderStatus = "PARTIALLY_FILLED"
	OrderStatusFilled          OrderStatus = "FILLED"
	OrderStatusCancelled       OrderStatus = "CANCELLED"
	OrderStatusRejected        OrderStatus = "REJECTED"
	OrderStatusNew             OrderStatus = "NEW"
	OrderStatusUnknown         OrderStatus = "UNKNOWN"
)

type OrderType

type OrderType string

OrderType represents the type of an order.

const (
	OrderTypeLimit            OrderType = "LIMIT"
	OrderTypeMarket           OrderType = "MARKET"
	OrderTypeStopLossLimit    OrderType = "STOP_LOSS_LIMIT"
	OrderTypeTakeProfitLimit  OrderType = "TAKE_PROFIT_LIMIT"
	OrderTypeStopLossMarket   OrderType = "STOP_LOSS_MARKET"
	OrderTypeTakeProfitMarket OrderType = "TAKE_PROFIT_MARKET"
	OrderTypePostOnly         OrderType = "POST_ONLY"
	OrderTypeUnknown          OrderType = "UNKNOWN"
)

type OrderUpdateCallback

type OrderUpdateCallback func(*Order)

type PerpExchange

type PerpExchange interface {
	Exchange
	FetchPositions(ctx context.Context) ([]Position, error)
	SetLeverage(ctx context.Context, symbol string, leverage int) error
	FetchFundingRate(ctx context.Context, symbol string) (*FundingRate, error)
	FetchAllFundingRates(ctx context.Context) ([]FundingRate, error)
	ModifyOrder(ctx context.Context, orderID, symbol string, params *ModifyOrderParams) (*Order, error)
}

PerpExchange extends Exchange with perpetual futures capabilities. Use type assertion: if perp, ok := adp.(adapter.PerpExchange); ok { ... }

type Position

type Position struct {
	Symbol            string          `json:"symbol"`
	Side              PositionSide    `json:"side"`
	Quantity          decimal.Decimal `json:"quantity"`
	EntryPrice        decimal.Decimal `json:"entry_price"`
	UnrealizedPnL     decimal.Decimal `json:"unrealized_pnl"`
	RealizedPnL       decimal.Decimal `json:"realized_pnl"`
	LiquidationPrice  decimal.Decimal `json:"liquidation_price,omitempty"`
	Leverage          decimal.Decimal `json:"leverage,omitempty"`
	MaintenanceMargin decimal.Decimal `json:"maintenance_margin,omitempty"`
	MarginType        string          `json:"margin_type,omitempty"` // ISOLATED or CROSSED
}

Position represents an open position in a perpetual futures market.

type PositionSide

type PositionSide string

PositionSide represents the direction of a position.

const (
	PositionSideLong  PositionSide = "LONG"
	PositionSideShort PositionSide = "SHORT"
	PositionSideBoth  PositionSide = "BOTH" // For one-way mode
)

type PositionUpdateCallback

type PositionUpdateCallback func(*Position)

type QuoteCurrency added in v0.1.2

type QuoteCurrency string

QuoteCurrency represents the quote/settlement currency of a trading pair.

const (
	QuoteCurrencyUSDT QuoteCurrency = "USDT"
	QuoteCurrencyUSDC QuoteCurrency = "USDC"
	QuoteCurrencyDUSD QuoteCurrency = "DUSD"
)

type SpotBalance

type SpotBalance struct {
	Asset  string          `json:"asset"`  // Currency symbol, e.g. "BTC", "USDT"
	Free   decimal.Decimal `json:"free"`   // Available balance
	Locked decimal.Decimal `json:"locked"` // Frozen/locked balance
	Total  decimal.Decimal `json:"total"`  // Total balance (Free + Locked)
}

SpotBalance represents the balance of a single asset in a spot account.

type SpotExchange

type SpotExchange interface {
	Exchange
	FetchSpotBalances(ctx context.Context) ([]SpotBalance, error)
	TransferAsset(ctx context.Context, params *TransferParams) error
}

SpotExchange extends Exchange with spot-specific capabilities.

type Streamable

type Streamable interface {
	WatchOrders(ctx context.Context, cb OrderUpdateCallback) error
	WatchPositions(ctx context.Context, cb PositionUpdateCallback) error
	WatchTicker(ctx context.Context, symbol string, cb TickerCallback) error
	WatchTrades(ctx context.Context, symbol string, cb TradeCallback) error
	WatchKlines(ctx context.Context, symbol string, interval Interval, cb KlineCallback) error
	StopWatchOrders(ctx context.Context) error
	StopWatchPositions(ctx context.Context) error
	StopWatchTicker(ctx context.Context, symbol string) error
	StopWatchTrades(ctx context.Context, symbol string) error
	StopWatchKlines(ctx context.Context, symbol string, interval Interval) error
}

Streamable provides WebSocket streaming capabilities. All Watch methods accept a callback. Not all exchanges support all stream types.

type Subscription added in v0.2.0

type Subscription[T any] struct {
	C <-chan *T // Read-only channel for the consumer
	// contains filtered or unexported fields
}

Subscription represents a single subscriber's channel for receiving events. Call Unsubscribe() to stop receiving events and release resources.

func (*Subscription[T]) Unsubscribe added in v0.2.0

func (s *Subscription[T]) Unsubscribe()

Unsubscribe removes this subscription and closes the channel.

type SymbolDetails

type SymbolDetails struct {
	Symbol            string          `json:"symbol"`
	PricePrecision    int32           `json:"price_precision"`    // Decimal places for price
	QuantityPrecision int32           `json:"quantity_precision"` // Decimal places for quantity
	MinQuantity       decimal.Decimal `json:"min_quantity"`       // Minimum order quantity
	MinNotional       decimal.Decimal `json:"min_notional"`       // Minimum notional value (price * qty)
}

SymbolDetails provides trading rules and precision for a symbol.

type Ticker

type Ticker struct {
	Symbol     string          `json:"symbol"`
	LastPrice  decimal.Decimal `json:"last_price"`
	IndexPrice decimal.Decimal `json:"index_price"`
	MarkPrice  decimal.Decimal `json:"mark_price"`
	MidPrice   decimal.Decimal `json:"mid_price"`
	Bid        decimal.Decimal `json:"bid"` // Best Bid
	Ask        decimal.Decimal `json:"ask"` // Best Ask
	Volume24h  decimal.Decimal `json:"volume_24h"`
	QuoteVol   decimal.Decimal `json:"quote_vol"` // Quote Volume
	High24h    decimal.Decimal `json:"high_24h"`
	Low24h     decimal.Decimal `json:"low_24h"`
	Timestamp  int64           `json:"timestamp"`
}

Ticker represents real-time market data for a symbol.

type TickerCallback

type TickerCallback func(*Ticker)

type TimeInForce

type TimeInForce string

TimeInForce specifies how long an order remains active.

const (
	TimeInForceGTC TimeInForce = "GTC" // Good Till Cancel
	TimeInForceIOC TimeInForce = "IOC" // Immediate Or Cancel
	TimeInForceFOK TimeInForce = "FOK" // Fill Or Kill
	TimeInForcePO  TimeInForce = "PO"  // Post Only
)

type Trade

type Trade struct {
	ID        string          `json:"id"`
	Symbol    string          `json:"symbol"`
	Price     decimal.Decimal `json:"price"`
	Quantity  decimal.Decimal `json:"quantity"`
	Side      TradeSide       `json:"side"`
	Timestamp int64           `json:"timestamp"` // Milliseconds
}

Trade represents a single market trade.

type TradeCallback

type TradeCallback func(*Trade)

type TradeSide

type TradeSide string

TradeSide 成交方向 (for market trades)

const (
	TradeSideBuy  TradeSide = "buy"
	TradeSideSell TradeSide = "sell"
)

type TransferParams

type TransferParams struct {
	Asset       string          // Currency symbol
	Amount      decimal.Decimal // Transfer amount
	FromAccount AccountType     // Source account type
	ToAccount   AccountType     // Destination account type
}

Directories

Path Synopsis
sdk
sdk
sdk
sdk
internal
mbx
Package mbx provides shared rate-limit tracking and error mapping for Binance-family exchanges (Binance, Aster) that use the X-Mbx-* header convention for communicating request weight and order count usage.
Package mbx provides shared rate-limit tracking and error mapping for Binance-family exchanges (Binance, Aster) that use the X-Mbx-* header convention for communicating request weight and order count usage.
sdk
sdk
okx
sdk
sdk

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL