FIX.Message (fix_message v0.1.0)

Copy Markdown

A FIX message with promoted, canonical header fields.

The header fields that every consumer touches are struct fields and are the source of truth:

  • :begin_string — BeginString(8), the protocol version
  • :msg_type — MsgType(35), the message discriminator
  • :seq_num — MsgSeqNum(34), as an integer
  • :sender_comp_id — SenderCompID(49)
  • :target_comp_id — TargetCompID(56)
  • :sending_time — SendingTime(52), as a UTCTimestamp binary
  • :poss_dup_flag — PossDupFlag(43), as a boolean
  • :orig_sending_time — OrigSendingTime(122), as a UTCTimestamp binary

These are the fields the session layer reads or writes itself: it stamps SendingTime on every send, and rewrites PossDupFlag/OrigSendingTime when resending. Timestamps stay binaries because their precision varies and round-trip fidelity matters.

The remaining fields keep the message's wire structure: :header holds the unpromoted header fields (sub and location IDs, OnBehalfOf/DeliverTo routing, ...) and :body holds the message body, each in wire order. to_fix/1 encodes the promoted fields first, then :header, then :body. The derived fields BodyLength(9) and CheckSum(10) are never stored; to_fix/1 computes them when encoding.

:raw preserves the original wire bytes of a parsed message. FIX requires resending original messages on ResendRequest, and message stores and audit logs want exact bytes — re-encoding from fields cannot guarantee byte equality. It is nil for locally built messages.

Example

iex> message = %FIX.Message{
...>   begin_string: "FIX.4.4",
...>   msg_type: "0",
...>   seq_num: 3,
...>   sender_comp_id: "SENDER",
...>   target_comp_id: "TARGET",
...>   body: [{112, "TEST"}]
...> }
iex> raw = FIX.Message.to_fix(message)
iex> {:ok, parsed, ""} = FIX.Message.parse(raw)
iex> {parsed.msg_type, parsed.seq_num, parsed.body}
{"0", 3, [{112, "TEST"}]}
iex> parsed.raw == raw
true

Summary

Functions

Parses one complete FIX message off the front of binary.

Encodes message as a FIX wire string.

Types

fields()

@type fields() :: [{tag(), binary()}]

t()

@type t() :: %FIX.Message{
  begin_string: binary() | nil,
  body: fields(),
  header: fields(),
  msg_type: binary() | nil,
  orig_sending_time: binary() | nil,
  poss_dup_flag: boolean() | nil,
  raw: binary() | nil,
  sender_comp_id: binary() | nil,
  sending_time: binary() | nil,
  seq_num: pos_integer() | nil,
  target_comp_id: binary() | nil
}

tag()

@type tag() :: pos_integer()

Functions

parse(binary, dictionary \\ FIX.Dictionary.FIX44)

@spec parse(binary(), module()) ::
  {:ok, t(), rest :: binary()}
  | :incomplete
  | {:error, FIX.Parser.frame_error()}

Parses one complete FIX message off the front of binary.

Frames and tokenizes via FIX.Parser.parse_message/2, then promotes the canonical header fields onto the struct. Promotion is positional: the parser splits the message into header, body, and trailer sections, and only fields in the header section are promoted — a header tag appearing after the body has started stays in :body. Unpromoted header fields land in :header. BodyLength(9) and the trailer (CheckSum(10)) are validated by the parser and then discarded. The consumed wire bytes are kept in :raw.

Returns {:ok, message, rest} where rest is the remainder of the buffer, :incomplete when more bytes are needed, or {:error, reason}. A message whose MsgSeqNum(34) is not a positive integer, or whose PossDupFlag(43) is not Y or N, is {:error, :garbled}.

to_fix(message)

@spec to_fix(t()) :: binary()

Encodes message as a FIX wire string.

Assembles the header from the promoted struct fields — in the order BeginString(8), BodyLength(9), MsgType(35), SenderCompID(49), TargetCompID(56), MsgSeqNum(34), PossDupFlag(43), SendingTime(52), OrigSendingTime(122) — followed by :header and :body verbatim, and computes BodyLength(9) and CheckSum(10).

:begin_string and :msg_type are required; nil promoted header fields are omitted. :raw is ignored — encoding always reflects the struct's current data.