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.
Renders message as a human-readable string, for logs and debugging.
Types
@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 }
@type tag() :: pos_integer()
Functions
@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}.
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.
Example
iex> message = %FIX.Message{
...> begin_string: "FIX.4.4",
...> msg_type: "0",
...> seq_num: 3,
...> sender_comp_id: "SENDER",
...> target_comp_id: "TARGET",
...> sending_time: "20260727-12:30:00",
...> body: [{112, "TEST"}]
...> }
iex> message |> FIX.Message.to_fix() |> String.replace(<<0x01>>, "|")
"8=FIX.4.4|9=60|35=0|49=SENDER|56=TARGET|34=3|52=20260727-12:30:00|112=TEST|10=160|"
Renders message as a human-readable string, for logs and debugging.
Encodes via to_fix/1, then replaces the SOH field delimiters with |,
the conventional display form for FIX messages. The result is not valid
wire data — use to_fix/1 for that — and is not reversible when a field
value itself contains a |.
This function backs the String.Chars implementation, so interpolating
a message ("got: #{message}") produces the same display form.
Unlike to_fix/1, never raises: a message missing :begin_string or
:msg_type renders whatever fields are present, without the derived
BodyLength(9) and CheckSum(10).
Examples
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> FIX.Message.to_string(message)
"8=FIX.4.4|9=39|35=0|49=SENDER|56=TARGET|34=3|112=TEST|10=160|"
iex> FIX.Message.to_string(%FIX.Message{msg_type: "0", seq_num: 1})
"35=0|34=1|"