Разработка FIX API для криптобиржи

Проектируем и разрабатываем блокчейн-решения полного цикла: от архитектуры смарт-контрактов до запуска DeFi-протоколов, NFT-маркетплейсов и криптобирж. Аудит безопасности, токеномика, интеграция с существующей инфраструктурой.
Показано 1 из 1 услугВсе 1306 услуг
Разработка FIX API для криптобиржи
Сложная
~1-2 недели
Часто задаваемые вопросы
Направления блокчейн-разработки
Этапы блокчейн-разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1258
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1170
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    873
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1092
  • image_logo-advance_0.png
    Разработка логотипа компании B2B Advance
    563
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    830

Интеграция FIX-протокола

FIX (Financial Information eXchange) — стандартный протокол обмена финансовыми данными, используемый с 1992 года. Большинство институциональных трейдеров, prime brokers и HFT-фирм требуют FIX API для подключения к бирже. Без FIX биржа закрыта для профессиональных участников рынка.

Что такое FIX и зачем он нужен

FIX — это текстовый протокол над TCP. Сообщение выглядит как набор тег=значение полей, разделённых символом SOH (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 |

Ключевые теги:

  • 35=D — тип сообщения (D = New Order Single)
  • 55 — символ инструмента
  • 54 — сторона (1=Buy, 2=Sell)
  • 38 — количество
  • 40 — тип ордера (1=Market, 2=Limit, 3=Stop)
  • 44 — цена (для limit)
  • 59 — Time in Force (0=DAY, 1=GTC, 3=IOC, 4=FOK)

FIX предпочтителен для институционалов по нескольким причинам:

  • Стандартный протокол — их системы уже умеют работать с FIX
  • Низкая latency: бинарно эффективнее чем HTTP/JSON
  • Надёжная сессионная модель: автоматическое восстановление после разрывов, message sequencing

Архитектура FIX сервера

QuickFIX/Go — основной выбор

QuickFIX — reference implementation FIX engine, порты для Go, Java, C++, Python. Go версия (quickfixgo) — хорошая база для production биржи.

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 — вызывается при создании FIX сессии
func (app *FIXApplication) OnCreate(sessionID quickfix.SessionID) {
    log.Info("FIX session created", "sessionID", sessionID)
}

// OnLogon — вызывается при успешном логоне
func (app *FIXApplication) OnLogon(sessionID quickfix.SessionID) {
    log.Info("FIX client logged on", "sessionID", sessionID)
    app.sessionManager.SetOnline(sessionID)
}

// OnLogout — вызывается при разрыве соединения
func (app *FIXApplication) OnLogout(sessionID quickfix.SessionID) {
    log.Info("FIX client logged out", "sessionID", sessionID)
    app.sessionManager.SetOffline(sessionID)
}

// FromApp — обработка входящих сообщений от клиента
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)
}

Обработка 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
    }
    
    // Парсим поля
    clOrdID, _ := nos.GetClOrdID()
    symbol, _ := nos.GetSymbol()
    sideInt, _ := nos.GetSide()
    ordType, _ := nos.GetOrdType()
    qty, _ := nos.GetOrderQty()
    price, _ := nos.GetPrice()
    tif, _ := nos.GetTimeInForce()
    
    // Конвертируем FIX типы в внутренние
    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),
    }
    
    // Отправляем Execution Report — Pending New
    app.sendExecReport(sessionID, order, ExecTypeNew, OrdStatusPendingNew)
    
    // Размещаем ордер в matching engine
    trades, err := app.orderEngine.PlaceOrder(order)
    if err != nil {
        app.sendExecReport(sessionID, order, ExecTypeRejected, OrdStatusRejected)
        return nil
    }
    
    // Отправляем Execution Reports по каждому fill
    for _, trade := range trades {
        app.sendFillReport(sessionID, order, trade)
    }
    
    // Если есть остаток — New или PartiallyFilled
    if order.RemainingQty().IsPositive() {
        status := OrdStatusNew
        if len(trades) > 0 {
            status = OrdStatusPartiallyFilled
        }
        app.sendExecReport(sessionID, order, ExecTypeNew, status)
    }
    
    return nil
}

Execution Report (8) — ответ на ордер

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

FIX сессия поддерживает sequencing сообщений. Каждое сообщение имеет MsgSeqNum (34). При разрыве соединения и переподключении:

  • Клиент восстанавливает сессию с последним известным SeqNum
  • Сервер может отправить ResendRequest (2) для запроса пропущенных сообщений
  • Или отправить SequenceReset (4) если хранение истории не поддерживается
// Конфигурация QuickFIX сессии
func createFIXSettings() *quickfix.Settings {
    settings := quickfix.NewSettings()
    
    globalSection := quickfix.NewSessionSettings()
    globalSection.Set("FileStorePath", "./fix-sessions")  // хранение сессий для 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 каждые 30 сек
    sessionSection.Set("ReconnectInterval", "5")   // переподключение через 5 сек
    sessionSection.Set("StartTime", "00:00:00")
    sessionSection.Set("EndTime", "00:00:00")      // нет дневного сброса для крипто
    
    return settings
}

Аутентификация и безопасность

FIX 4.4 не имеет встроенной аутентификации. Стандартные подходы:

  • IP whitelist: только разрешённые IP могут подключиться к FIX порту
  • TLS: шифрование соединения (FIX over SSL)
  • Logon (35=A) с password: поле 96 (RawData) или кастомный тег для API key + подписи
func (app *FIXApplication) FromAdmin(msg *quickfix.Message, sessionID quickfix.SessionID) quickfix.MessageRejectError {
    msgType, _ := msg.Header.GetString(field.NewMsgType())
    
    if msgType == "A" {  // Logon
        // Верифицируем кастомный тег с API подписью
        apiKey, _ := msg.Body.GetString(9001)       // кастомный тег
        signature, _ := msg.Body.GetString(9002)    // кастомный тег
        timestamp, _ := msg.Body.GetString(9003)    // кастомный тег
        
        if !app.auth.Verify(apiKey, signature, timestamp) {
            return quickfix.NewMessageRejectError("Authentication failed", 58, nil)
        }
        
        app.sessionManager.SetAPIKey(sessionID, apiKey)
    }
    
    return nil
}

Market Data через FIX

FIX 4.4 поддерживает market data подписки:

  • 35=V — Market Data Request (подписка на ticker, orderbook)
  • 35=W — Market Data Snapshot/Full Refresh
  • 35=X — Market Data Incremental Refresh
func (app *FIXApplication) handleMDRequest(msg *quickfix.Message, sessionID quickfix.SessionID) quickfix.MessageRejectError {
    mdReqID, _ := msg.Body.GetString(field.NewMDReqID())
    subscriptionType, _ := msg.Body.GetInt(field.NewSubscriptionRequestType())
    
    symbols := extractSymbols(msg)
    
    switch subscriptionType {
    case 1:  // Subscribe
        app.subscribeMD(sessionID, mdReqID, symbols)
    case 2:  // Unsubscribe
        app.unsubscribeMD(sessionID, mdReqID)
    }
    
    return nil
}

Drop Copy

Drop Copy — копирование всех исполнений на дополнительную FIX сессию (например, для compliance или risk monitoring системы клиента):

// При каждом fill — отправляем Drop Copy подписчикам
func (app *FIXApplication) onFill(trade Trade) {
    primarySession := app.getSessionByUser(trade.TakerUserID)
    app.sendFillReport(primarySession, trade)
    
    // Drop copy для risk/compliance систем клиента
    if dropCopySession := app.getDropCopySession(trade.TakerUserID); dropCopySession != nil {
        app.sendFillReport(dropCopySession, trade)
    }
}

FIX версии

Версия Применение
FIX 4.2 Legacy системы, широко поддерживается
FIX 4.4 Современный стандарт для большинства бирж
FIXT 1.1 + FIX 5.0 Разделение транспорта и приложения, нишевые случаи
FIX/FAST Бинарная компрессия для market data, ultra-low latency

Для криптобиржи: FIX 4.4 оптимален. Большинство institutional клиентов его поддерживают, документация обширная.

Тестирование

FIX интеграция тестируется с FIX клиентами:

  • QuickFIX Executor: базовый FIX client для тестирования
  • ATDL (Algorithmic Trading Definition Language): спецификация алготрейдинговых параметров
  • Нагрузочное тестирование: 1000+ ордеров/сек через FIX

Сроки разработки

  • Базовый FIX 4.4 сервер (New Order, Cancel, Execution Reports): 4–6 недель
  • Market Data feed через FIX: 2–3 недели
  • TLS + аутентификация + IP whitelist: 1–2 недели
  • Drop Copy: 1–2 недели
  • Тестирование и документация: 2–3 недели
  • Полная production-ready FIX интеграция: 2–3 месяца