Pro Features

FIX Protocol

Load-test FIX venues — sessions, orders, and market data

Overview

perfscale can drive FIX (Financial Information eXchange) venues under load: open initiator sessions, log on, stream orders/market-data requests, and measure round-trip latency alongside your HTTP/TCP/UDP metrics.

FIX is a paid capability (Scale and Enterprise plans). Tests that use the pro/fix* actions are rejected at creation time for Starter tenants.

Actions

pro/fix@v1 — run a session

Opens a TCP connection, logs on, sends the configured messages, logs out, and times the exchange. The step fails if logon is rejected or a transport/timeout error occurs; latency is recorded like any other action.

steps:
  - uses: pro/fix@v1
    with:
      host: fix.venue.com
      port: 9823
      begin_string: FIXT.1.1
      default_appl_ver_id: "9"        # FIX 5.0 SP2
      sender_comp_id: CLIENT
      target_comp_id: VENUE
      heart_bt_int: 30
      messages:
        - MsgType: NewOrderSingle
          ClOrdID: order-1
          Symbol: EURUSD
          Side: 1
          OrderQty: 100000
          OrdType: 1
ParamDefaultDescription
host + port / addressTarget (required, unless via connection)
connectionA connection profile (see below) supplying defaults for any field
sender_comp_id / target_comp_idSenderCompID (49) / TargetCompID (56), required
begin_stringFIX.4.4BeginString (8), e.g. FIXT.1.1
heart_bt_int30HeartBtInt (108)
reset_seq_numtrueResetSeqNumFlag (141)
default_appl_ver_idDefaultApplVerID (1137), for FIXT.1.1
username / passwordUsername (553) / Password (554)
tlsfalseConnect over TLS (encrypted gateway port)
tls_server_namehostSNI / hostname validated against the certificate
skipTLSVerifyfalseAccept any server certificate (self-signed staging only)
schemasession-onlyFIX data dictionary — a FIX44.xml URL / content / inline JSON; see below
messages[]App messages to send after logon
read_repliestrueRead one reply frame per app message
logouttrueSend Logout at the end
timeout10000Milliseconds for the whole session

pro/fix-config@v1 — reusable connection profile

Validates a connection profile (host/port/credentials/skipTLSVerify/schema) and produces it as a config object. Put it in a config file's before: block so many steps share one connection:

# config.yaml
vus: 50
duration: 5m
before:
  - uses: pro/fix-config@v1
    with:
      host: fix.venue.com
      port: 9823
      sender_comp_id: CLIENT
      target_comp_id: VENUE
      schema:
        fields: { AlgoTag: 7001 }
    outputs: venue
# test.yaml — inherits host/port/schema via connection
steps:
  - uses: pro/fix@v1
    with:
      connection: "${{ config.venue }}"
      messages:
        - MsgType: NewOrderSingle
          ClOrdID: "${uuid}"
          Symbol: EURUSD
          Side: 1
          OrderQty: 100000
          OrdType: 1

FIX schema (data dictionary)

A schema lets messages use field names instead of raw tags and fails fast when a required field is missing. The dictionary is loaded from the venue's own spec — a QuickFIX FIX44.xml, exactly what CoinAPI ships as DataDictionary=FIX44.xml — not hardcoded. Only the FIX session layer (standard header/trailer + admin messages) is built in, so a bare logon works with no schema and numeric tags always resolve.

Supply schema as a URL, as content fetched by the engine, or inline JSON:

before:
  # 1) a URL — fetched once at config-build time
  - uses: pro/fix-config@v1
    with:
      schema: https://raw.githubusercontent.com/quickfix/quickfix/master/spec/FIX44.xml

  # 2) fetch with the engine, then hand over the body
  - uses: std/http@v1
    with: { url: https://.../FIX44.xml }
    outputs: dict
  - uses: pro/fix-config@v1
    with:
      schema: "${{ dict.body }}"

Inline JSON also works, for a few custom fields layered on the session base:

schema:
  fields: { AlgoTag: 7001 }
  messages:
    NewOrderSingle: { required: [ClOrdID, Symbol, Side, OrderQty, OrdType] }

Once a dictionary is loaded, message keys may be field names or numeric tags, and MsgType values may be a name (NewOrderSingle) or a wire code (D).

Connecting to a live venue (CoinAPI)

CoinAPI's Market Data FIX is a concrete FIX 4.4 target: TLS gateway fix.coinapi.io:3303, SenderCompID = your API key, TargetCompID COINAPI_V2. Subscribe to a live trade stream:

# config.yaml — load CoinAPI's FIX44.xml, build the TLS connection profile
before:
  - uses: std/http@v1
    with: { url: https://raw.githubusercontent.com/quickfix/quickfix/master/spec/FIX44.xml }
    outputs: dict
  - uses: pro/fix-config@v1
    with:
      host: fix.coinapi.io
      port: 3303
      tls: true
      sender_comp_id: YOUR_COINAPI_KEY   # inject; do not commit
      target_comp_id: COINAPI_V2
      heart_bt_int: 10
      schema: "${{ dict.body }}"
    outputs: coinapi
# test.yaml — subscribe to trades
steps:
  - uses: pro/fix@v1
    with:
      connection: "${{ config.coinapi }}"
      messages:
        - MsgType: MarketDataRequest
          MDReqID: "perfscale-${uuid}"
          SubscriptionRequestType: 1     # subscribe
          MarketDepth: 1
          MDUpdateType: 1
          NoMDEntryTypes: 1
          MDEntryType: 2                 # trades
          NoRelatedSym: 1
          Symbol: COINBASE_SPOT_BTC_USD
      timeout: 15000

Dynamic messages (generating trades)

Field values may embed ${...} tokens expanded per send, and a message carries _repeat + _interval_ms — so one template streams a series of distinct trades:

messages:
  - MsgType: NewOrderSingle
    ClOrdID: "ord-${seq}"
    Symbol: "${choice(EURUSD|GBPUSD|USDJPY)}"
    Side: "${rand(1,2)}"
    OrderQty: "${rand(1000,100000)}"
    Price: "${randf(1.05,1.15,5)}"
    TransactTime: "${now}"
    _repeat: 100          # 100 orders…
    _interval_ms: 50      # …one every 50ms

Message variable tokens

TokenExpands to
${seq}Monotonic counter, unique per message send (shared by all fields in that message)
${uuid}Random 32-hex-character id
${now}Current UTC time, FIX format YYYYMMDD-HH:MM:SS.sss
${rand(a,b)}Random integer in [a, b]
${randf(a,b)} / ${randf(a,b,dp)}Random float in [a, b], dp decimals (default 2)
${choice(x|y|z)}Random pick among the options

These single-brace tokens are distinct from the engine's ${{ ... }} interpolation (config/variables/step outputs), which is resolved before the step runs.

Message directives

DirectiveDefaultDescription
_repeat1Send the message N times; tokens re-expand each time
_interval_ms (alias _interval)0Delay between repeats, in milliseconds

For sustained load, combine with the run config: vus concurrent sessions × the iteration loop × _repeat per iteration.

Output & assertions

The step output includes logon, sent, received, duration_ms, the parsed reply messages, and a joined raw body. Assert on it with a check:

    check:
      body_contains: "35=8"   # an ExecutionReport came back