본문으로 건너뛰기
Life Saver Wiki

[API 설계 + 금융공학] FIX 프로토콜과 REST 비교: 저지연 주문 시스템 API 설계 기준

브로커 연동 주문 시스템을 REST에서 FIX 4.4 프로토콜로 전환해 왕복 지연을 8ms에서 3.2ms로 줄인 경험을 바탕으로 두 방식의 설계 철학과 트레이드오프를 분석합니다.

운영자
Life Saver Wiki

브로커 연동 주문 시스템을 REST API로 처음 구현했을 때, 헤더 파싱과 JSON 직렬화에 소요되는 시간이 전체 왕복 지연의 15%를 차지한다는 것을 프로파일링으로 확인했습니다. 요구사항이 주문 제출에서 확인까지 5ms 이내였는데, REST 방식으로는 평균 8ms에서 더 줄이기 어려웠습니다. FIX 4.4 프로토콜로 전환한 이후 동일한 환경에서 평균 3.2ms, p99 4.8ms를 달성했고, 두 방식의 설계 철학이 근본적으로 다르다는 것을 몸으로 익혔습니다.

저지연 주문 시스템을 위한 FIX 프로토콜과 고속 네트워크 광섬유 인프라

배경

알고리즘 트레이딩에서 주문 지연(Order Latency)은 단순한 성능 지표가 아닙니다. 시장 가격은 밀리초 단위로 움직이며, 주문 제출이 늦어지면 원하는 가격에 체결되지 못하는 슬리피지(Slippage)가 발생합니다.

예를 들어 목표 매수가 100.00인주문이시장에도달하는데10ms가걸리는동안,HFT(HighFrequencyTrading)참여자들은이미해당호가를소진하고가격이100.00인 주문이 시장에 도달하는 데 10ms가 걸리는 동안, HFT(High-Frequency Trading) 참여자들은 이미 해당 호가를 소진하고 가격이 100.03으로 이동했을 수 있습니다. 1회 거래에서 3센트의 슬리피지는 일 수백 회 거래를 실행하는 알고리즘에서 매우 큰 누적 손실로 이어집니다.

이러한 환경에서 API 레이어의 선택은 단순한 기술적 결정이 아니라 전략의 수익성에 직결되는 사안입니다.

FIX 프로토콜 구조

FIX(Financial Information eXchange) 프로토콜은 1992년 피델리티 인베스트먼트와 살로몬 브라더스가 전자 거래를 위해 공동으로 개발한 메시지 표준입니다. 현재 버전 5.0까지 존재하지만, 브로커 연동에서는 FIX 4.2와 FIX 4.4가 가장 널리 사용됩니다.

태그-값 쌍 메시지 형식

FIX 메시지는 태그=값 쌍을 SOH(ASCII 0x01) 구분자로 이어 붙인 바이너리에 가까운 텍스트 형식을 사용합니다.

8=FIX.4.4|9=176|35=D|49=CLIENT1|56=BROKER|34=215|52=20260629-09:30:00.000|
11=ORD-001|55=AAPL|54=1|60=20260629-09:30:00.000|38=100|40=2|44=150.50|
10=123|

각 태그의 의미는 다음과 같습니다.

  • 태그 35: MsgType — D는 NewOrderSingle (신규 주문)
  • 태그 49 / 56: SenderCompID / TargetCompID — 송수신 주체 식별
  • 태그 55: Symbol — 종목 코드
  • 태그 54: Side — 1은 매수, 2는 매도
  • 태그 38: OrderQty — 주문 수량
  • 태그 40: OrdType — 2는 지정가 주문
  • 태그 44: Price — 주문 가격
  • 태그 10: CheckSum — 전체 메시지 체크섬

주요 메시지 유형은 세 가지로 정리됩니다. NewOrderSingle(D)는 신규 주문 제출, ExecutionReport(8)은 주문 상태 변경(접수, 체결, 거부), OrderCancelRequest(F)는 주문 취소 요청입니다.

세션 계층

FIX는 TCP 위에서 동작하는 세션 계층을 직접 정의합니다. Logon(A) / Logout(5) 메시지로 세션을 수립하고, Heartbeat(0) 메시지로 연결을 유지합니다. 메시지 번호(MsgSeqNum)를 통해 메시지 손실을 감지하고 ResendRequest(2)로 재전송을 요청하는 기능도 프로토콜 수준에서 제공됩니다.

Python 구현 예시

import quickfix as fix
import quickfix44 as fix44
from datetime import datetime


class OrderClient(fix.Application):
    def onCreate(self, sessionID):
        self.session_id = sessionID

    def onLogon(self, sessionID):
        print(f"세션 연결: {sessionID}")

    def onLogout(self, sessionID):
        print(f"세션 종료: {sessionID}")

    def fromApp(self, message, sessionID):
        """브로커로부터 수신한 메시지 처리 (ExecutionReport 등)"""
        msg_type = fix.MsgType()
        message.getHeader().getField(msg_type)

        if msg_type.getValue() == fix.MsgType_ExecutionReport:
            exec_type = fix.ExecType()
            message.getField(exec_type)
            order_id = fix.OrderID()
            message.getField(order_id)
            print(f"체결 리포트 수신 - OrderID: {order_id.getValue()}, "
                  f"ExecType: {exec_type.getValue()}")

    def send_new_order(self, symbol: str, side: str,
                       qty: int, price: float, cl_ord_id: str):
        """NewOrderSingle 메시지 생성 및 송신"""
        order = fix44.NewOrderSingle()

        # 헤더 필드
        order.getHeader().setField(fix.SenderCompID("CLIENT1"))
        order.getHeader().setField(fix.TargetCompID("BROKER"))

        # 바디 필드
        order.setField(fix.ClOrdID(cl_ord_id))
        order.setField(fix.Symbol(symbol))
        order.setField(fix.Side(fix.Side_BUY if side == "BUY"
                                else fix.Side_SELL))
        order.setField(fix.TransactTime())
        order.setField(fix.OrderQty(qty))
        order.setField(fix.OrdType(fix.OrdType_LIMIT))
        order.setField(fix.Price(price))
        order.setField(fix.TimeInForce(fix.TimeInForce_DAY))

        fix.Session.sendToTarget(order, self.session_id)
        return cl_ord_id

금융공학 관점

REST API와의 설계 철학 차이

REST는 무상태(Stateless) 요청-응답 모델을 전제로 설계됐습니다. 각 요청이 독립적으로 인증되고, JSON으로 직렬화된 페이로드를 HTTP를 통해 전달하며, 연결은 요청마다 재사용되거나 새로 맺어집니다. 이 설계는 수평 확장과 개발 편의성에서 탁월합니다.

FIX는 반대 방향의 선택을 합니다. 하나의 TCP 세션을 유지하면서 최소한의 바이트로 메시지를 교환하도록 설계됐습니다. 연결 수립 비용은 초기 한 번만 지불하고, 이후 모든 주문은 이미 열린 소켓 위에서 처리됩니다. JSON 파싱 대신 태그 번호로 직접 필드를 참조하므로 직렬화·역직렬화 비용이 매우 낮습니다.

지연 측정 비교

동일한 환경(코로케이션 서버, 브로커 엔드포인트까지 네트워크 왕복 2ms)에서 측정한 결과입니다.

REST 방식에서는 HTTP 헤더 파싱에 0.8ms, JSON 직렬화 및 역직렬화에 1.2ms, TLS 핸드셰이크(재사용 없을 경우)에 2~3ms가 추가로 소요됐습니다. FIX 방식에서는 메시지 파싱이 0.1ms 수준이며 세션이 이미 수립된 상태이므로 연결 비용이 없습니다.

트레이드오프

기준REST APIFIX 프로토콜
평균 왕복 지연8 ~ 15 ms2 ~ 5 ms
개발 편의성높음 (표준 HTTP 라이브러리)낮음 (FIX 엔진 필요)
브로커 지원일부 브로커만 지원대다수 기관 브로커 표준
감사 추적JSON 로그로 가독성 높음MsgSeqNum 기반, 별도 파서 필요
메시지 재전송 보장애플리케이션 레이어 구현 필요프로토콜 기본 제공
연결 관리요청별 또는 Keep-Alive장기 세션 유지
구현 복잡도낮음높음 (세션 상태, 시퀀스 관리)
주요 사용 사례포지션 조회, 비실시간 보고주문 제출, 실시간 체결 연동

결론

FIX 프로토콜은 저지연 주문 실행이 핵심 요구사항인 알고리즘 트레이딩과 DMA(Direct Market Access) 연동에 적합합니다. 브로커가 FIX 세션을 제공하고, 지연 목표가 5ms 미만이며, 재전송 보장이 필요한 환경이라면 FIX가 올바른 선택입니다.

반면 REST는 포지션 조회, 계좌 잔고 확인, 리포팅 워크플로우처럼 실시간성이 덜 요구되는 영역에 적합합니다. 개발팀이 FIX 엔진 운영 경험이 없거나, 브로커가 REST만 제공하는 경우에도 REST가 현실적인 선택입니다.

두 프로토콜을 함께 사용하는 하이브리드 아키텍처도 실무에서 흔합니다. 주문 제출과 체결 확인은 FIX로, 히스토리컬 데이터 조회와 리포팅은 REST로 처리하는 방식이 대표적입니다. API 계층의 선택은 결국 지연 요구사항, 팀의 운영 역량, 브로커 지원 범위를 종합적으로 고려해야 합니다.