FIX User Guide¶
The nasdaq_protocols.fix module provides a client-side implementation of the FIX
protocol, including the session layer. Refer FIX API Reference for more information
on the API.
This package only provides the core implementation of the FIX protocol (message serialization, deserialization, validation and session handling). It does not define any actual FIX messages – those are application/dictionary specific and must be defined by the user of this package (for example, by generating them from a FIX dictionary). The session layer operates generically on the standard header/body tags and message types, so the same session works for any conforming message set.
Follow the steps in Create New Project Guide to create a new python package for the application specific messages.
Connecting to a FIX server¶
import asyncio
from nasdaq_protocols import fix
# `Fix44Session`/`Fix50Session` are provided by the library; the login message type
# is defined by your application/dictionary.
from nasdaq_protocols.fix.session import Fix44Session
async def main():
login_msg = ... # build your application Logon message
session = await fix.connect_async(
('hostname', 1234),
login_msg,
Fix44Session,
)
# ... use the session ...
await session.close()
if __name__ == '__main__':
asyncio.run(main())
Session-level sequence recovery¶
FixSession always behaves as a compliant FIX client on the receiver side: it checks
for a sequence gap once, at login, and transparently performs the session-layer recovery
protocol before handing messages to your application. This is core functionality and cannot
be disabled.
session = await fix.connect_async(
('hostname', 1234),
login_msg,
lambda: Fix44Session(
expected_seq_num=42, # the next inbound MsgSeqNum you expect from the server
client_heartbeat_interval=10,
server_heartbeat_interval=10,
),
)
expected_seq_num defaults to None: the session bootstraps itself from the
MsgSeqNum of the Logon response, so whatever the server sends first is accepted as
the baseline. Pass an explicit value (e.g. derived from your persisted sequence store on a
reconnect) to detect a gap right from the login handshake.
A FIX session runs over a single TCP connection, which guarantees in-order, gap-free
delivery for the life of that connection – a real gap can only arise from a disconnect and
reconnect. Because of this, gap detection happens only once, during login: the session
compares the MsgSeqNum the server reports on its Logon response against
expected_seq_num. If the server’s sequence is higher, messages were missed on a prior
connection and recovery kicks in; once recovery completes, messages flow straight through to
your application with no further per-message sequence bookkeeping.
What the session does automatically:
No gap at login: the
Logonresponse’s ownMsgSeqNummatches (or bootstraps)expected_seq_num; every subsequent message is checked against the expected sequence (a cheap O(1) comparison) and delivered to youron_msg_coroin order.Gap detected at login: the
Logonresponse reports aMsgSeqNumahead ofexpected_seq_num. The session sends an open-endedResendRequest(BeginSeqNo=expected_seq_num, EndSeqNo=0), asking the server to replay everything from the missing sequence onward. The optionalon_gaphook is invoked with(expected_seq_num, received_seq).No buffering: the session never buffers out-of-order traffic. Any message that arrives ahead of
expected_seq_num(for example, live traffic racing the in-flightResendRequest) is dropped silently before it is ever queued for your application – because theResendRequestis open-ended, the server’s replay naturally re-delivers that same message later (markedPossDupFlag=Y), in the correct position, once the gap has been filled.Gap fill / replay: a
SequenceResetwithGapFillFlag=Yadvances the expected sequence toNewSeqNo(the server may collapse a run of housekeeping messages, e.g. severalHeartbeats, into a singleGapFill); aSequenceResetwithout the gap-fill flag forces the expected sequence toNewSeqNo.Duplicates: a message below the expected sequence carrying
PossDupFlag=Yis a replayed duplicate and is dropped silently.Fatal sequence error: a message below the expected sequence without
PossDupFlag=Yis a fatal condition – the session sends aLogoutand closes.TestRequest: an inbound
TestRequestis always answered with aHeartbeatechoing theTestReqID– the standard FIX flow.
This filtering happens at the point messages are received off the wire, before they are
ever handed to the dispatcher – so your application only ever sees messages in strict
sequence order, and on_msg_coro/receive_msg() behave exactly as documented by the
base session, regardless of whether sequence recovery is in progress.
There is no cap on how long the server can take to complete the replay; if it never does, the normal heartbeat-timeout logic closes the session as a safety net.
Note
This is Phase 1 (receiver side). Responder-side replay – replaying our own previously
sent messages when the server sends us a ResendRequest – requires a persistent
outbound message store and is planned as future work.
Sending messages with an explicit sequence¶
send_msg auto-stamps the outbound MsgSeqNum from the session counter. Two keyword-only
parameters let you override that when producing recovery messages:
# Normal send: MsgSeqNum is taken from the session's outbound counter.
session.send_msg(msg)
# Send with an explicit sequence number; the auto counter is NOT advanced.
session.send_msg(msg, seq_num=100)
# Mark a message as a possible duplicate: sets PossDupFlag and OrigSendingTime.
session.send_msg(msg, seq_num=100, poss_dup=True)