FIX API Development for Crypto Exchange

We design and develop full-cycle blockchain solutions: from smart contract architecture to launching DeFi protocols, NFT marketplaces and crypto exchanges. Security audits, tokenomics, integration with existing infrastructure.
Showing 1 of 1 servicesAll 1306 services
FIX API Development for Crypto Exchange
Complex
~1-2 weeks
FAQ
Blockchain Development Services
Blockchain Development Stages
Latest works
  • image_website-b2b-advance_0.png
    B2B ADVANCE company website development
    1214
  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Website development for BELFINGROUP
    852
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1041
  • image_logo-advance_0.png
    B2B Advance company logo design
    561
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    823

FIX Protocol Integration

FIX (Financial Information eXchange) is a standard protocol for financial data exchange used since 1992. Most institutional traders, prime brokers, and HFT firms require FIX API to connect to an exchange. Without FIX, the exchange is closed for professional market participants.

What is FIX and Why It's Needed

FIX is a text protocol over TCP. A message looks like a set of tag=value fields separated by SOH character (0x01):

8=FIX.4.4 | 9=178 | 35=D | 49=CLIENT1 | 56=EXCHANGE | 34=123 | 52=20240115-14:30:00.000 | 
11=ORDER-001 | 55=BTC/USD | 54=1 | 38=0.1 | 40=2 | 44=42000 | 59=1 | 10=087 |

Key tags:

  • 35=D — message type (D = New Order Single)
  • 55 — instrument symbol
  • 54 — side (1=Buy, 2=Sell)
  • 38 — quantity
  • 40 — order type (1=Market, 2=Limit, 3=Stop)
  • 44 — price (for limit)
  • 59 — Time in Force (0=DAY, 1=GTC, 3=IOC, 4=FOK)

FIX is preferred for institutionals for several reasons:

  • Standard protocol — their systems already know FIX
  • Low latency: more efficient than HTTP/JSON
  • Reliable session model: automatic recovery after disconnects, message sequencing

FIX Server Architecture

QuickFIX/Go — Primary Choice

QuickFIX — reference implementation of FIX engine, ports for Go, Java, C++, Python. Go version (quickfixgo) is a good foundation for production exchange.

import (
    "github.com/quickfixgo/quickfix"
    "github.com/quickfixgo/quickfix/field"
    "github.com/quickfixgo/quickfix/fix44"
    "github.com/quickfixgo/quickfix/fix44/newordersingle"
)

type FIXApplication struct {
    orderEngine    *OrderEngine
    sessionManager *SessionManager
}

// OnCreate — called when FIX session is created
func (app *FIXApplication) OnCreate(sessionID quickfix.SessionID) {
    log.Info("FIX session created", "sessionID", sessionID)
}

// OnLogon — called on successful logon
func (app *FIXApplication) OnLogon(sessionID quickfix.SessionID) {
    log.Info("FIX client logged on", "sessionID", sessionID)
    app.sessionManager.SetOnline(sessionID)
}

// OnLogout — called on disconnect
func (app *FIXApplication) OnLogout(sessionID quickfix.SessionID) {
    log.Info("FIX client logged out", "sessionID", sessionID)
    app.sessionManager.SetOffline(sessionID)
}

// FromApp — handle incoming messages from client
func (app *FIXApplication) FromApp(msg *quickfix.Message, sessionID quickfix.SessionID) quickfix.MessageRejectError {
    msgType, err := msg.Header.GetString(field.NewMsgType())
    if err != nil {
        return err
    }
    
    switch msgType {
    case "D":  // New Order Single
        return app.handleNewOrder(msg, sessionID)
    case "F":  // Order Cancel Request
        return app.handleCancelOrder(msg, sessionID)
    case "G":  // Order Cancel/Replace Request (amend)
        return app.handleAmendOrder(msg, sessionID)
    case "H":  // Order Status Request
        return app.handleStatusRequest(msg, sessionID)
    }
    
    return quickfix.NewMessageRejectError("Unknown message type", 35, nil)
}

Handling New Order Single (D)

func (app *FIXApplication) handleNewOrder(msg *quickfix.Message, sessionID quickfix.SessionID) quickfix.MessageRejectError {
    nos := newordersingle.New(
        field.NewClOrdID(""),
        field.NewSide(0),
        field.NewTransactTime(time.Now()),
        field.NewOrdType(0),
    )
    
    if err := quickfix.Unmarshal(msg, &nos); err != nil {
        return err
    }
    
    // Parse fields
    clOrdID, _ := nos.GetClOrdID()
    symbol, _ := nos.GetSymbol()
    sideInt, _ := nos.GetSide()
    ordType, _ := nos.GetOrdType()
    qty, _ := nos.GetOrderQty()
    price, _ := nos.GetPrice()
    tif, _ := nos.GetTimeInForce()
    
    // Convert FIX types to internal
    order := Order{
        ClientOrderID: string(clOrdID),
        Pair:          normalizePair(string(symbol)),
        Side:          fixSideToInternal(sideInt),
        Type:          fixOrdTypeToInternal(ordType),
        Quantity:      decimal.NewFromFloat(float64(qty)),
        Price:         decimal.NewFromFloat(float64(price)),
        TimeInForce:   fixTIFToInternal(tif),
    }
    
    // Send Execution Report — Pending New
    app.sendExecReport(sessionID, order, ExecTypeNew, OrdStatusPendingNew)
    
    // Place order in matching engine
    trades, err := app.orderEngine.PlaceOrder(order)
    if err != nil {
        app.sendExecReport(sessionID, order, ExecTypeRejected, OrdStatusRejected)
        return nil
    }
    
    // Send Execution Reports for each fill
    for _, trade := range trades {
        app.sendFillReport(sessionID, order, trade)
    }
    
    // If remainder — New or PartiallyFilled
    if order.RemainingQty().IsPositive() {
        status := OrdStatusNew
        if len(trades) > 0 {
            status = OrdStatusPartiallyFilled
        }
        app.sendExecReport(sessionID, order, ExecTypeNew, status)
    }
    
    return nil
}

Execution Report (8) — Order Response

func (app *FIXApplication) sendExecReport(sessionID quickfix.SessionID, order Order, 
                                            execType ExecType, ordStatus OrdStatus) {
    report := fix44executionreport.New(
        field.NewOrderID(order.ID),
        field.NewExecID(generateExecID()),
        field.NewExecType(fix44.ExecType(execType)),
        field.NewOrdStatus(fix44.OrdStatus(ordStatus)),
        field.NewSymbol(denormalizePair(order.Pair)),
        field.NewSide(fix44.Side(internalSideToFIX(order.Side))),
        field.NewLeavesQty(order.RemainingQty().InexactFloat64(), 8),
        field.NewCumQty(order.FilledQty.InexactFloat64(), 8),
        field.NewAvgPx(order.AvgPrice().InexactFloat64(), 8),
    )
    
    report.SetClOrdID(order.ClientOrderID)
    report.SetOrderQty(order.Quantity.InexactFloat64(), 8)
    report.SetTransactTime(time.Now())
    
    quickfix.SendToTarget(report.ToMessage(), sessionID)
}

FIX Session Model

FIX session maintains message sequencing. Each message has MsgSeqNum (34). On disconnect and reconnect:

  • Client restores session with last known SeqNum
  • Server can send ResendRequest (2) to request missed messages
  • Or send SequenceReset (4) if history storage not supported

Configuration of QuickFIX session:

func createFIXSettings() *quickfix.Settings {
    settings := quickfix.NewSettings()
    
    globalSection := quickfix.NewSessionSettings()
    globalSection.Set("FileStorePath", "./fix-sessions")  // session storage for resend
    globalSection.Set("FileLogPath", "./fix-logs")
    settings.GlobalSettings().SetGlobalSection(globalSection)
    
    sessionSection := quickfix.NewSessionSettings()
    sessionSection.Set(quickfix.BeginString, "FIX.4.4")
    sessionSection.Set(quickfix.SenderCompID, "EXCHANGE")
    sessionSection.Set(quickfix.TargetCompID, "CLIENT1")
    sessionSection.Set("HeartBtInt", "30")         // heartbeat every 30 sec
    sessionSection.Set("ReconnectInterval", "5")   // reconnect after 5 sec
    sessionSection.Set("StartTime", "00:00:00")
    sessionSection.Set("EndTime", "00:00:00")      // no daily reset for crypto
    
    return settings
}

Authentication and Security

FIX 4.4 has no built-in authentication. Standard approaches:

  • IP whitelist: only allowed IPs can connect to FIX port
  • TLS: connection encryption (FIX over SSL)
  • Logon (35=A) with password: field 96 (RawData) or custom tag for API key + signature
func (app *FIXApplication) FromAdmin(msg *quickfix.Message, sessionID quickfix.SessionID) quickfix.MessageRejectError {
    msgType, _ := msg.Header.GetString(field.NewMsgType())
    
    if msgType == "A" {  // Logon
        // Verify custom tag with API signature
        apiKey, _ := msg.Body.GetString(9001)       // custom tag
        signature, _ := msg.Body.GetString(9002)    // custom tag
        timestamp, _ := msg.Body.GetString(9003)    // custom tag
        
        if !app.auth.Verify(apiKey, signature, timestamp) {
            return quickfix.NewMessageRejectError("Authentication failed", 58, nil)
        }
        
        app.sessionManager.SetAPIKey(sessionID, apiKey)
    }
    
    return nil
}

Market Data via FIX

FIX 4.4 supports market data subscriptions:

  • 35=V — Market Data Request (subscribe to ticker, orderbook)
  • 35=W — Market Data Snapshot/Full Refresh
  • 35=X — Market Data Incremental Refresh

FIX Versions:

Version Application
FIX 4.2 Legacy systems, widely supported
FIX 4.4 Modern standard for most exchanges
FIXT 1.1 + FIX 5.0 Separation of transport and application, niche cases
FIX/FAST Binary compression for market data, ultra-low latency

For crypto exchange: FIX 4.4 is optimal. Most institutional clients support it, documentation is extensive.

Testing

FIX integration tested with FIX clients:

  • QuickFIX Executor: basic FIX client for testing
  • ATDL: Algorithmic Trading Definition Language specification
  • Load testing: 1000+ orders/sec via FIX

Development Timeline

  • Basic FIX 4.4 server (New Order, Cancel, Execution Reports): 4–6 weeks
  • Market Data feed via FIX: 2–3 weeks
  • TLS + authentication + IP whitelist: 1–2 weeks
  • Drop Copy: 1–2 weeks
  • Testing and documentation: 2–3 weeks
  • Full production-ready FIX integration: 2–3 months