FixAlchemy.Parser (FIXAlchemy v0.2.2)

View Source

FIX protocol framing and decoding built on binary pattern matching.

parse/2 extracts complete messages from a TCP buffer using the declared BodyLength (tag 9), returning each message as a zero-copy sub-binary of the original wire bytes. Framing is verified against the checksum trailer position: a message whose BodyLength does not land on 10= is treated as garbled and skipped, resynchronizing on the next 8=FIX header, per the FIX session protocol's garbled-message rule.

FIX Version Support

Any begin string starting with FIX is accepted: FIX.4.x, FIXT.1.1 (the wire begin string for FIX 5.0+), and vendor variants.

Limits

BodyLength is accepted up to 10 digits, capped at 100MB per message. Anything beyond that is treated as garbled data.

Examples

iex> FixAlchemy.Parser.init()
iex> {buffer, messages} = FixAlchemy.Parser.parse(data)
iex> message_map = FixAlchemy.Parser.process_message(Enum.at(messages, 0), spec_name)

Summary

Functions

Calculate checksum for a FIX message body.

Validate a framed FIX message against its checksum trailer.

Cleanup is no longer needed with generated modules.

Extract just the MsgType (tag 35) from a raw FIX message via binary scan.

Initialize generated parser modules from the session's FIX dictionaries.

Parse FIX message data from a buffer.

Convert a raw FIX message to a structured map using generated parsers.

Split a raw FIX message into {tag, value} string pairs.

Functions

calculate_checksum(msg)

@spec calculate_checksum(binary()) :: binary()

Calculate checksum for a FIX message body.

Sums raw bytes modulo 256 and returns a 3-digit zero-padded string.

checksum_valid?(message)

@spec checksum_valid?(binary()) :: boolean()

Validate a framed FIX message against its checksum trailer.

The checksum is the byte sum modulo 256 of every byte before the 10= trailer, per the FIX specification.

cleanup(spec_name_or_nil \\ nil)

@spec cleanup(term()) :: :ok

Cleanup is no longer needed with generated modules.

Generated modules are compiled .beam files that persist across sessions. This function is kept for backward compatibility but does nothing.

extract_msg_type(message)

@spec extract_msg_type(binary()) :: binary() | nil

Extract just the MsgType (tag 35) from a raw FIX message via binary scan.

Avoids full field decoding — intended for fast routing decisions. Returns the msg_type string or nil if not found.

Examples

iex> FixAlchemy.Parser.extract_msg_type("8=FIX.4.4" <> <<1>> <> "9=5" <> <<1>> <> "35=A" <> <<1>> <> "10=000" <> <<1>>)
"A"

init(opts \\ [])

@spec init(keyword()) :: {binary(), [module()]}

Initialize generated parser modules from the session's FIX dictionaries.

Parses the dictionary XML and generates the modules used for field and message type lookups. A FIX 5.0 or later session splits its dictionary in two: :spec_file is then the FIXT transport dictionary, which defines the session layer only, and :app_spec_file the application dictionary that defines the business messages. Both are generated into one module set, the transport dictionary defining the session layer and the application dictionary everything else.

Options

  • :spec_file - Path to the FIX dictionary XML. Falls back to config :fix_alchemy, :spec_file, then to the FIX_SPEC_FILE environment variable. Required.
  • :app_spec_file - Path to the application dictionary XML, required when :spec_file is a FIXT transport dictionary. Falls back to config :fix_alchemy, :app_spec_file, then to the FIX_APP_SPEC_FILE environment variable.

Returns

  • {spec_name, modules} - Spec name for lookups and generated modules

Raises

  • Raises if no dictionary is configured, if a configured file is missing, or if parsing fails

Examples

iex> {spec_name, modules} = FixAlchemy.Parser.init(spec_file: "FIX44.xml")
iex> {spec_name, modules} =
...>   FixAlchemy.Parser.init(spec_file: "FIXT1.1.xml", app_spec_file: "FIX50SP2.xml")

parse(data, opts \\ [])

@spec parse(
  binary(),
  keyword()
) :: {binary(), [binary()]}

Parse FIX message data from a buffer.

Returns a tuple of {remaining_buffer, [complete_messages]}. Messages are returned as raw binaries, byte-identical to the wire data. Garbled data is skipped. Use process_message/2 to decode a message and checksum_valid?/1 to validate one.

Examples

iex> FixAlchemy.Parser.parse(data)
{"", ["8=FIX.4.4|9=...|10=123|"]}

process_message(message, spec_name)

@spec process_message(binary(), binary()) :: map()

Convert a raw FIX message to a structured map using generated parsers.

Uses generated modules from the spec for:

  • Field name resolution (Fields module)
  • Message structure parsing (Messages module)
  • Repeating group handling (Groups module)

Unknown tags are kept under their tag string (e.g. "9999"), known tags under their spec-derived atom name.

Parameters

  • message - Raw FIX message binary
  • spec_name - Spec name from init/1 (e.g., "fix44")

Examples

iex> Parser.process_message(msg, "fix44")
%{
  raw: <<...>>,
  msg_type: "X",
  symbol: "EUR/USD",
  no_md_entries: [
    %{md_entry_type: "0", md_entry_px: "1.0828"},
    %{md_entry_type: "1", md_entry_px: "1.0829"}
  ]
}

split_fields(message)

@spec split_fields(binary()) :: [{binary(), binary()}]

Split a raw FIX message into {tag, value} string pairs.

Tolerant of malformed segments: chunks without a = are skipped. Intended for fast handlers that read a few raw tags without full spec decoding.

Examples

iex> FixAlchemy.Parser.split_fields("55=EUR/USD" <> <<1>> <> "270=1.0828" <> <<1>>)
[{"55", "EUR/USD"}, {"270", "1.0828"}]