FixAlchemy.Parser (FIXAlchemy v0.3.0)

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.

Dictionaries belong to the session that uses them, not to the application, so connections to venues speaking different dialects run side by side.

Options

  • :spec_file - Path to the FIX dictionary XML. Required.
  • :app_spec_file - Path to the application dictionary XML, required when :spec_file is a FIXT transport dictionary.

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"}]