match

package module
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Dec 14, 2025 License: GPL-3.0 Imports: 11 Imported by: 0

README ¶

Matching Engine SDK

A high-performance, in-memory order matching engine written in Go. Designed for crypto exchanges, trading simulations, and financial systems requiring precise and fast order execution.

🚀 Features

  • High Performance: Pure in-memory matching using efficient SkipList data structures ($O(\log N)$) for order queues.
  • Concurrency Safe: Built on the Actor model using Go channels to ensure thread safety without heavy lock contention.
  • Precision: Uses shopspring/decimal to handle prices and quantities, avoiding floating-point arithmetic errors.
  • Multiple Markets: Supports managing multiple trading pairs (e.g., BTC-USDT, ETH-USDT) within a single engine instance.
  • Comprehensive Order Types:
    • Limit: Buy or sell at a specific price or better.
    • Market: Buy or sell immediately at the best available price.
    • IOC (Immediate or Cancel): Try to fill immediately, cancel any unfilled portion.
    • FOK (Fill or Kill): Fill the entire order immediately or cancel it entirely.
    • Post Only: Ensure the order is added to the book as a Maker; cancels if it would cross the spread.
  • Order Management:
    • Cancel: Remove an active order.
    • Amend: Modify the price or quantity of an existing order. Handles priority logic (priority kept for size decrease, lost for price change/size increase).
  • Event Sourcing Ready: Generates detailed BookLog events (Open, Match, Cancel, Amend, Reject) allowing for full order book state reconstruction and downstream integration (e.g., WebSocket feeds, persistence).
  • Zero-Allocation Logging: Utilizes sync.Pool for log events to minimize GC pressure.

📦 Installation

go get github.com/0x5487/matching-engine

🛠 Usage

Quick Start
package main

import (
	"context"
	"fmt"
	"time"

	match "github.com/0x5487/matching-engine"
	"github.com/shopspring/decimal"
)

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

	// 1. Create a publish trader to handle order book events
	publishTrader := match.NewMemoryPublishTrader()

	// 2. Create and start the order book
	orderBook := match.NewOrderBook(publishTrader)
	go orderBook.Start()

	// 3. Add a limit buy order
	buyOrder := &match.Order{
		ID:       "buy-1",
		MarketID: "BTC-USDT",
		Type:     match.Limit,
		Side:     match.Buy,
		Price:    decimal.NewFromInt(50000),
		Size:     decimal.NewFromInt(1),
		UserID:   1001,
	}
	_ = orderBook.AddOrder(ctx, buyOrder)

	// 4. Add a limit sell order (this will match with the buy order)
	sellOrder := &match.Order{
		ID:       "sell-1",
		MarketID: "BTC-USDT",
		Type:     match.Limit,
		Side:     match.Sell,
		Price:    decimal.NewFromInt(50000),
		Size:     decimal.NewFromInt(1),
		UserID:   1002,
	}
	_ = orderBook.AddOrder(ctx, sellOrder)

	time.Sleep(100 * time.Millisecond)

	// 5. Check the results
	fmt.Printf("Total events: %d\n", publishTrader.Count())
	for i := 0; i < publishTrader.Count(); i++ {
		log := publishTrader.Get(i)
		fmt.Printf("[%s] OrderID: %s, Price: %s, Size: %s\n",
			log.Type, log.OrderID, log.Price, log.Size)
	}

	// 6. Get current order book depth
	depth, _ := orderBook.Depth(10)
	fmt.Printf("Bids: %d, Asks: %d\n", len(depth.Bids), len(depth.Asks))
}
Order Types
Type Description
Limit Buy/sell at a specific price or better
Market Execute immediately at best available price
IOC Fill immediately, cancel unfilled portion
FOK Fill entirely or cancel completely
PostOnly Add to book as maker only, reject if would cross
Order Management
// Cancel an order
orderBook.CancelOrder(ctx, "order-id")

// Amend an order (change price or size)
orderBook.AmendOrder(ctx, "order-id", newPrice, newSize)
Custom Event Handler

Implement PublishTrader interface to handle events your way:

type MyHandler struct{}

func (h *MyHandler) Publish(logs ...*match.BookLog) {
	for _, log := range logs {
		// Send to WebSocket, save to DB, publish to MQ, etc.
		fmt.Printf("Event: %s | OrderID: %s\n", log.Type, log.OrderID)
	}
}

Benchmark

Please refer to doc

Documentation ¶

Index ¶

Constants ¶

This section is empty.

Variables ¶

View Source
var (
	ErrInsufficientLiquidity = errors.New("there is not enough depth to fill the order")
	ErrInvalidParam          = errors.New("the param is invalid")
	ErrInternal              = errors.New("internal server error")
	ErrTimeout               = errors.New("timeout")
	ErrShutdown              = errors.New("order book is shutting down")
	ErrNotFound              = errors.New("not found")
)

Functions ¶

func NewBuyerQueue ¶

func NewBuyerQueue() *queue

NewBuyerQueue creates a new queue for buy orders (bids). The orders are sorted by price in descending order (highest price first).

func NewSellerQueue ¶

func NewSellerQueue() *queue

NewSellerQueue creates a new queue for sell orders (asks). The orders are sorted by price in ascending order (lowest price first).

Types ¶

type AggregatedBook ¶ added in v0.7.0

type AggregatedBook struct {

	// OnRebuild is called when a rebuild is needed (e.g., sequence gap detected).
	// The callback should return a snapshot from which the book will be rebuilt.
	// This must be set before calling Rebuild() or Replay() with gap detection.
	OnRebuild RebuildFunc
	// contains filtered or unexported fields
}

AggregatedBook maintains a simplified view of the order book, tracking only price levels and their aggregated sizes (depth). It is designed for downstream services that need to rebuild order book state from BookLog events received via message queue.

func NewAggregatedBook ¶ added in v0.7.0

func NewAggregatedBook() *AggregatedBook

NewAggregatedBook creates a new AggregatedBook instance with empty ask and bid sides.

func (*AggregatedBook) ApplySnapshot ¶ added in v0.7.0

func (ab *AggregatedBook) ApplySnapshot(snapshot *Snapshot) error

ApplySnapshot resets the aggregated book state from a snapshot. This clears all existing data and applies the snapshot's depth levels.

func (*AggregatedBook) Depth ¶ added in v0.7.0

func (ab *AggregatedBook) Depth(side Side, price decimal.Decimal) (decimal.Decimal, error)

Depth returns the aggregated size at a specific price level for the given side. Returns zero if the price level does not exist.

func (*AggregatedBook) Rebuild ¶ added in v0.7.0

func (ab *AggregatedBook) Rebuild() error

Rebuild triggers a manual rebuild by calling the OnRebuild callback. Returns an error if OnRebuild is not set or if the callback fails.

func (*AggregatedBook) Replay ¶ added in v0.7.0

func (ab *AggregatedBook) Replay(log *BookLog) error

Replay applies a BookLog event to update the aggregated book state. Events with LogType == LogTypeReject do not affect book state but still update the sequence ID. Returns an error if a sequence gap is detected and rebuild fails.

func (*AggregatedBook) SequenceID ¶ added in v0.7.0

func (ab *AggregatedBook) SequenceID() uint64

SequenceID returns the last processed sequence ID. Used for synchronization and gap detection during rebuild.

type AmendRequest ¶ added in v0.7.0

type AmendRequest struct {
	OrderID  string
	NewPrice decimal.Decimal
	NewSize  decimal.Decimal
}

type BookLog ¶ added in v0.7.0

type BookLog struct {
	SequenceID   uint64          `json:"seq_id"`
	TradeID      uint64          `json:"trade_id,omitempty"` // Sequential trade ID, only set for Match events
	Type         LogType         `json:"type"`               // Event type: open, match, cancel, amend, reject
	MarketID     string          `json:"market_id"`
	Side         Side            `json:"side"`
	Price        decimal.Decimal `json:"price"`
	Size         decimal.Decimal `json:"size"`
	Amount       decimal.Decimal `json:"amount,omitempty"` // Price * Size, only set for Match events
	OldPrice     decimal.Decimal `json:"old_price,omitempty"`
	OldSize      decimal.Decimal `json:"old_size,omitempty"`
	OrderID      string          `json:"order_id"`
	UserID       int64           `json:"user_id"`
	OrderType    OrderType       `json:"order_type,omitempty"` // Order type: limit, market, ioc, fok
	MakerOrderID string          `json:"maker_order_id,omitempty"`
	MakerUserID  int64           `json:"maker_user_id,omitempty"`
	RejectReason RejectReason    `json:"reject_reason,omitempty"` // Reason for rejection, only set for Reject events
	CreatedAt    time.Time       `json:"created_at"`
}

BookLog represents an event in the order book. SequenceID is a globally increasing ID for every event, used for ordering, deduplication, and rebuild synchronization in downstream systems. Use LogType to determine if the event affects order book state: - Open, Match, Cancel, Amend: affect order book state - Reject: does not affect order book state

type Depth ¶ added in v0.5.0

type Depth struct {
	UpdateID uint64       `json:"update_id"`
	Asks     []*DepthItem `json:"asks"`
	Bids     []*DepthItem `json:"bids"`
}

type DepthChange ¶ added in v0.7.0

type DepthChange struct {
	Side     Side
	Price    decimal.Decimal
	SizeDiff decimal.Decimal
}

DepthChange represents a change in the order book depth.

func CalculateDepthChange ¶ added in v0.7.0

func CalculateDepthChange(log *BookLog) DepthChange

CalculateDepthChange calculates the depth change based on the book log. It returns a DepthChange struct indicating which side and price level should be updated. Note: For LogTypeMatch, the side returned is the Maker's side (opposite of the log's side).

type DepthItem ¶ added in v0.5.0

type DepthItem struct {
	ID    uint32
	Price decimal.Decimal
	Size  decimal.Decimal
}

type DiscardPublishLog ¶ added in v0.7.0

type DiscardPublishLog struct {
}

DiscardPublishLog discards all logs, useful for benchmarking.

func NewDiscardPublishLog ¶ added in v0.7.0

func NewDiscardPublishLog() *DiscardPublishLog

NewDiscardPublishLog creates a new DiscardPublishLog.

func (*DiscardPublishLog) Publish ¶ added in v0.7.0

func (p *DiscardPublishLog) Publish(trades ...*BookLog)

Publish does nothing.

type LogType ¶ added in v0.7.0

type LogType string
const (
	LogTypeOpen   LogType = "open"
	LogTypeMatch  LogType = "match"
	LogTypeCancel LogType = "cancel"
	LogTypeAmend  LogType = "amend"
	LogTypeReject LogType = "reject"
)

type MatchingEngine ¶

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

MatchingEngine manages multiple order books for different markets.

func NewMatchingEngine ¶

func NewMatchingEngine(publishTrader PublishLog) *MatchingEngine

NewMatchingEngine creates a new matching engine instance.

func (*MatchingEngine) AddOrder ¶ added in v0.5.1

func (engine *MatchingEngine) AddOrder(ctx context.Context, order *Order) error

AddOrder adds an order to the appropriate order book based on the market ID. Returns ErrShutdown if the engine is shutting down.

func (*MatchingEngine) AmendOrder ¶ added in v0.7.0

func (engine *MatchingEngine) AmendOrder(ctx context.Context, marketID string, orderID string, newPrice decimal.Decimal, newSize decimal.Decimal) error

AmendOrder modifies an existing order in the appropriate order book. Returns ErrShutdown if the engine is shutting down.

func (*MatchingEngine) CancelOrder ¶

func (engine *MatchingEngine) CancelOrder(ctx context.Context, marketID string, orderID string) error

CancelOrder cancels an order in the appropriate order book. Returns ErrShutdown if the engine is shutting down.

func (*MatchingEngine) OrderBook ¶ added in v0.5.0

func (engine *MatchingEngine) OrderBook(marketID string) *OrderBook

OrderBook retrieves the order book for a specific market ID, creating it if it doesn't exist. Returns nil if the engine is shutting down.

func (*MatchingEngine) Shutdown ¶ added in v0.7.0

func (engine *MatchingEngine) Shutdown(ctx context.Context) error

Shutdown gracefully shuts down all order books in the engine. It blocks until all order books have completed their shutdown or the context is cancelled. Returns nil if all order books shut down successfully, or an aggregated error otherwise.

type MemoryPublishLog ¶ added in v0.7.0

type MemoryPublishLog struct {
	Trades []*BookLog
	// contains filtered or unexported fields
}

MemoryPublishLog stores logs in memory, useful for testing.

func NewMemoryPublishLog ¶ added in v0.7.0

func NewMemoryPublishLog() *MemoryPublishLog

NewMemoryPublishLog creates a new MemoryPublishLog.

func (*MemoryPublishLog) Count ¶ added in v0.7.0

func (m *MemoryPublishLog) Count() int

Count returns the number of logs stored.

func (*MemoryPublishLog) Get ¶ added in v0.7.0

func (m *MemoryPublishLog) Get(index int) *BookLog

Get returns the log at the specified index.

func (*MemoryPublishLog) Publish ¶ added in v0.7.0

func (m *MemoryPublishLog) Publish(trades ...*BookLog)

Publish appends logs to the in-memory slice.

type Message ¶ added in v0.5.1

type Message struct {
	Action  string
	Payload any
	Resp    chan *Response
}

type Order ¶

type Order struct {
	ID        string          `json:"id"`
	MarketID  string          `json:"market_id"`
	Side      Side            `json:"side"`
	Price     decimal.Decimal `json:"price"`
	Size      decimal.Decimal `json:"size"`                 // Base currency quantity (e.g., BTC amount)
	QuoteSize decimal.Decimal `json:"quote_size,omitempty"` // Quote currency amount (e.g., USDT), only for Market orders. Mutually exclusive with Size.
	Type      OrderType       `json:"type"`
	UserID    int64           `json:"user_id"`
	CreatedAt time.Time       `json:"created_at"`
}

type OrderBook ¶

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

OrderBook type

func NewOrderBook ¶

func NewOrderBook(publishTrader PublishLog) *OrderBook

NewOrderBook creates a new order book instance.

func (*OrderBook) AddOrder ¶ added in v0.5.0

func (book *OrderBook) AddOrder(ctx context.Context, order *Order) error

AddOrder submits an order to the order book asynchronously. Returns ErrShutdown if the order book is shutting down.

func (*OrderBook) AmendOrder ¶ added in v0.7.0

func (book *OrderBook) AmendOrder(ctx context.Context, id string, newPrice decimal.Decimal, newSize decimal.Decimal) error

AmendOrder submits a request to modify an existing order asynchronously.

func (*OrderBook) CancelOrder ¶

func (book *OrderBook) CancelOrder(ctx context.Context, id string) error

CancelOrder submits a cancellation request for an order asynchronously.

func (*OrderBook) Depth ¶ added in v0.5.0

func (book *OrderBook) Depth(limit uint32) (*Depth, error)

Depth returns the current depth of the order book up to the specified limit.

func (*OrderBook) Shutdown ¶ added in v0.7.0

func (book *OrderBook) Shutdown(ctx context.Context) error

Shutdown signals the order book to stop accepting new orders and waits for all pending orders to be processed. The method blocks until all orders are drained or the context is cancelled/timed out. Returns nil if shutdown completed successfully, or ctx.Err() if the context was cancelled.

func (*OrderBook) Start ¶ added in v0.5.0

func (book *OrderBook) Start() error

Start starts the order book loop to process orders, cancellations, and depth requests. Returns nil when Shutdown() is called and all pending orders are drained.

type OrderBookUpdateEvent ¶

type OrderBookUpdateEvent struct {
	Bids []*UpdateEvent
	Asks []*UpdateEvent
	Time time.Time
}

type OrderType ¶

type OrderType string
const (
	Market   OrderType = "market"
	Limit    OrderType = "limit"
	FOK      OrderType = "fok"       // Fill Or Kill
	IOC      OrderType = "ioc"       // Immediate Or Cancel
	PostOnly OrderType = "post_only" // be maker order only
	Cancel   OrderType = "cancel"    // the order has been canceled
)

type PublishLog ¶ added in v0.7.0

type PublishLog interface {
	Publish(...*BookLog)
}

PublishLog is an interface for publishing order book logs (trades, opens, cancels).

IMPORTANT: Implementations must either:

  1. Process logs synchronously before returning, OR
  2. Clone the BookLog data before returning

The caller recycles BookLog objects to a sync.Pool after Publish returns, so any asynchronous processing must work with cloned data.

type RebuildFunc ¶ added in v0.7.0

type RebuildFunc func() (*Snapshot, error)

RebuildFunc is the callback type for fetching a snapshot during rebuild. Implementations should fetch the current order book snapshot from external sources (e.g., Redis, Database, API) and return it for the AggregatedBook to apply.

type RejectReason ¶ added in v0.7.0

type RejectReason string

RejectReason represents the reason why an order was rejected.

const (
	RejectReasonNone             RejectReason = ""
	RejectReasonNoLiquidity      RejectReason = "no_liquidity"       // Market/IOC/FOK: No orders available to match
	RejectReasonPriceMismatch    RejectReason = "price_mismatch"     // IOC/FOK: Price does not meet requirements
	RejectReasonInsufficientSize RejectReason = "insufficient_size"  // FOK: Cannot be fully filled
	RejectReasonWouldCrossSpread RejectReason = "would_cross_spread" // PostOnly: Would match immediately
)

type Response ¶ added in v0.5.1

type Response struct {
	Error error
	Data  any
}

type Side ¶

type Side int8
const (
	Buy  Side = 1
	Sell Side = 2
)

type Snapshot ¶ added in v0.7.0

type Snapshot struct {
	SequenceID uint64       // The sequence ID at which this snapshot was taken
	Asks       []*DepthItem // Ask side depth levels, sorted by price ascending
	Bids       []*DepthItem // Bid side depth levels, sorted by price descending
}

Snapshot represents a point-in-time state of the order book. Used to initialize or reset the AggregatedBook during rebuild.

type UpdateEvent ¶

type UpdateEvent struct {
	Price string `json:"price"`
	Size  string `json:"size"`
}

Jump to

Keyboard shortcuts

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