| name | FIX Antenna .NET Core |
| description | This skill should be used when the user asks to "create a FIX session", "build a FIX acceptor", "build a FIX initiator", "send a NewOrderSingle", "handle ExecutionReport", "set up drop copy", "configure fixengine.properties", "implement FIX session recovery", "add a FIX repeating group", or mentions Epam.FixAntenna.NetCore, IFixSession, IFixSessionListener, IFixMessageListener, FixServer, FixMessage, SessionParametersBuilder, ForceSeqNumReset, or FIXT 1.1 in a .NET / C# context. Skip for QuickFIX/n or other FIX libraries — APIs differ. |
| version | 0.2.0 |
FIX Antenna .NET Core
Guidance for coding agents working in projects that depend on Epam.FixAntenna.NetCore. This skill is verified against version 1.2.3.
What FIX Antenna .NET Core is
A high-performance FIX protocol engine for .NET Standard 2.0+, maintained by EPAM / B2Bits, open-sourced under Apache 2.0. It implements FIX 4.0 – 5.0 and FIXT 1.1 session and application layers.
- Repository:
https://github.com/epam/fix-antenna-net-core
- NuGet:
Epam.FixAntenna.NetCore
- Official docs: https://github.com/epam/fix-antenna-net-core/tree/main/Docs — indexed and cross-mapped in
references/official-docs.md. Consult the bundled references/ first; fetch upstream pages only when the topic isn't covered there (see "When in doubt").
What it is NOT
- Not QuickFIX/n. APIs differ. Do not translate QuickFIX/n samples and rename types — the session model, message construction, and config keys are different.
- Not a broker connectivity SDK. It speaks FIX. It does not normalize venue quirks or ship pre-built drop-in connectors per venue.
- Not an OMS or EMS. It gives session, message, persistence, recovery. Order state machine and business logic are out of scope.
Mental model
Four concepts. Master these or every generation will be wrong.
Configuration ──► Session ──► IFixSessionListener (one class, two callbacks)
(file/code) │ ├─ OnSessionStateChange(SessionState)
│ └─ OnNewMessage(FixMessage) ← inherited from IFixMessageListener
│
└────► Storage (durable seq + log, engine-managed)
| Concept | Type(s) | Role |
|---|
| Configuration | Config, SessionParameters, fixengine.properties | Where session params, storage, TLS live. Prefer file-based for production. |
| Server / acceptor host | FixServer, IFixServerListener | Listens for inbound connections. Per-session NewFixSession(IFixSession) callback decides accept/reject. |
| Session | IFixSession | One FIX session. Owns sequence numbers, heartbeats, logon/logout, resend. |
| Session listener | IFixSessionListener : IFixMessageListener | Implements BOTH OnSessionStateChange(SessionState) (declared on IFixSessionListener) AND OnNewMessage(FixMessage) (inherited from IFixMessageListener). Register via session.SetFixSessionListener(...). Receives application messages only — admin messages (35=A,0,1,2,3,4,5) never reach it. |
| Admin-message observers (optional) | IFixMessageListener | Session-level (admin) messages are consumed by the engine and NOT passed to the main listener. To observe inbound admin traffic (log Logons, watch 35=3 rejects, recovery flows), register via session.AddInSessionLevelMessageListener(...). |
| Message | FixMessage : ITagList | Add tags with AddTag(int, value), read with GetTagValueAsString(int) etc. |
| Tags (constants) | Epam.FixAntenna.Constants.Fix44.Tags | Static class of tag-number const int fields per FIX version. |
⚠️ Inheritance trap. IFixSessionListener extends IFixMessageListener. The compiler will demand that any class implementing IFixSessionListener provide BOTH methods (OnSessionStateChange and OnNewMessage). Implement both on the same class. This is true in 1.2.3 and in 2.x master — same shape, different declaration site.
Hard rules — DO and DON'T
These are non-negotiable for production-quality code. Agents repeatedly violate these; this section exists to stop that.
DO
- Use the dictionary. Validate every inbound message against the FIX dictionary. Reject malformed messages at session layer; do not attempt business processing on a structurally invalid message.
- Treat FIX as binary-typed, not text. Read with
FixMessage.GetTagValueAsLong(Fix44.Tags.OrderQty), not int.Parse(message.ToString().Split('|')...).
- Separate session handling from business logic.
OnNewMessage should dispatch to a business handler that does NOT know about FIX wire format. The listener is a boundary.
- Use a persistent storage factory in production. Set
storageFactory = Epam.FixAntenna.NetCore.FixEngine.Storage.FilesystemStorageFactory (default, durable) or MmfStorageFactory (memory-mapped, higher throughput). Never InMemoryStorageFactory for a live session — it's for tests. Persistent storage is what makes gap fill / resend / restart recovery work.
- Acknowledge state transitions explicitly.
OnSessionStateChange will be called many times per session lifecycle (WaitingForLogon → LogonReceived → Connected → Disconnected → Reconnecting, etc.). Branch via SessionState.IsDisconnected(state), not on string compares.
- Dispose sessions. Always
session.Dispose() after disconnect.
- Know the difference between
AddTag and Set. AddTag(tag, value) appends a new occurrence — calling it twice for the same tag on the same message yields a malformed (duplicate-tag) outbound. Set(tag, value) (inherited from ExtendedIndexedStorage, available on every FixMessage) updates the tag in place and adds it if absent — every Set overload delegates to UpdateValue(..., IndexedStorage.MissingTagHandling.AddIfNotExists), so Set is already the "replace if present, add if missing" call. Reach for UpdateValue(tag, value, IndexedStorage.MissingTagHandling.DontAddIfNotExists) only when you want the opposite — update only if the tag already exists, otherwise leave the message untouched (the call returns -1). (The enum is nested in IndexedStorage — fully-qualify it, or using static Epam.FixAntenna.NetCore.Message.IndexedStorage;.) Use AddTag for fresh outbound construction; use Set when mutating an existing message (e.g., echoing an inbound back with one field changed).
- Engine fills the header and trailer. BeginString (8), BodyLength (9), MsgSeqNum (34), MsgType (35), SenderCompID (49), SendingTime (52), TargetCompID (56), CheckSum (10) are engine-managed.
DON'T
- Don't hardcode sequence numbers. Never write
session.OutSeqNum = 1 on every start, never use SetSequenceNumbers(...) from app code unless explicitly implementing manual recovery tooling. The engine and persistent storage manage this; touching it breaks recovery and triggers session-level rejects.
- Don't ignore Resend Requests. If a counterparty asks for messages N–M, the engine handles replay automatically if persistent storage is configured. Don't write custom resend logic.
- Don't parse FIX wire format with
Split('|') or Split(''). Use FixMessage's tag-typed accessors.
- Don't treat ExecutionReport as fire-and-forget.
ExecType (150) and OrdStatus (39) define a state machine.
- Don't swallow session-level rejects (msg type 3). Means the previous outbound was malformed. Log loudly.
- Don't bypass admin messages. Logon (A), Logout (5), Heartbeat (0), TestRequest (1), ResendRequest (2), SequenceReset (4), Reject (3) are handled by the engine. Don't construct them from app code.
- Don't catch and continue on listener exceptions silently. Log loudly, then either rethrow or disconnect.
- Don't invent enum values.
ForceSeqNumReset has exactly three values: Always, OneTime, Never. There is no OnLogon.
- Don't reference
SessionState.LoggedOn. It does not exist in 1.2.3. The fully-logged-on state is SessionState.Connected (verify with SessionState.IsConnected(state)).
API surface
The full verified API surface lives in references/api-reference.md: namespaces, session construction (acceptor/initiator/listener), FixMessage read/write, SessionState and ForceSeqNumReset enums, useful SessionParameters properties, the "Other IFixSession methods worth knowing" table, and repeating-group iteration.
Load that reference before generating non-trivial FIX Antenna code. The signatures there were verified by reflection against 1.2.3.
Critical pitfalls captured in references/api-reference.md
- Single-arg
SendMessage(content) is NOT interchangeable with two-arg SendMessage(msgType, content) on a raw new FixMessage(). With no MsgType (35) it throws ArgumentException: Message type(35 tag) cannot be null at serialization; with 35 set but no header it serializes without 8/9/34/49/52/56 (header updates use DontAddIfNotExists) and crashes at the storage layer with "Tag 34 is missing or has invalid value". Use the two-arg overload for fresh construction, or PrepareMessage(...).
IFixSession.Connect() is polymorphic by role: opens TCP + sends Logon on an initiator; accepts a pending inbound on an acceptor (inside IFixServerListener.NewFixSession).
IFixSessionListener inherits IFixMessageListener — implement BOTH OnSessionStateChange and OnNewMessage on the same class.
SessionState.LoggedOn does not exist. Use SessionState.Connected / SessionState.IsConnected(state).
ForceSeqNumReset values: only Always, OneTime, Never. No OnLogon.
- CompID property casing:
SenderCompId / TargetCompId (lowercase d) — the properties-file key, in contrast, is senderCompID / targetCompID (uppercase).
AddTag appends; Set replaces-or-adds. Two AddTag(sameTag, ...) calls = malformed outbound. Set(tag, value) updates in place and adds the tag if absent (every overload delegates to UpdateValue(..., MissingTagHandling.AddIfNotExists)), so Set is the "replace if present, add if missing" call — don't reach for UpdateValue just to get that behavior. Use UpdateValue(tag, value, IndexedStorage.MissingTagHandling.DontAddIfNotExists) only for update-only-if-present (returns -1, no-op when absent). The enum is nested in IndexedStorage (namespace Epam.FixAntenna.NetCore.Message).
- Repeating groups on a fresh
FixMessage require RawFixUtil.IndexRepeatingGroup(msg, FixVersion.Fix44, "<msgType>") before AddRepeatingGroup(leadingTag), otherwise it throws "no info about FIX version in message".
Namespaces (quick)
using Epam.FixAntenna.NetCore.Configuration;
using Epam.FixAntenna.NetCore.FixEngine;
using Epam.FixAntenna.NetCore.FixEngine.Session.Util;
using Epam.FixAntenna.NetCore.Message;
using Epam.FixAntenna.Constants.Fix44;
Configuration: fixengine.properties
The keys documented in this section and in references/12-perf-profiles.md cover the common cases — use them as-is, no lookup needed. For any other key, do not invent it: the complete 1.2.3 key index (every recognized key, default, scope) is bundled in references/config-keys.md. The upstream Docs/Configuration.md is only needed for long-form prose on a key already found in the index.
The shipped Epam.FixAntenna.NetCore NuGet's SessionParametersBuilder reads a specific property-file layout (verified against epam/fix-antenna-net-core/Samples/ConnectToGateway/fixengine.properties):
- List sessions with the key
sessionIDs (comma-separated).
- Per-session properties are prefixed
sessions.<id>.<key>.
- Shared defaults can live under
sessions.default.<key>.
- The session-type key is
sessionType with values initiator | acceptor — NOT connectionType.
# Process-wide settings (no session prefix)
storageFactory = Epam.FixAntenna.NetCore.FixEngine.Storage.FilesystemStorageFactory
storageDirectory = ./logs
# Autoreconnect is controlled SOLELY by autoreconnectAttempts.
# There is NO `enableAutoreconnect` property — agents commonly invent this
# (the engine ignores it silently). Values for autoreconnectAttempts:
# -1 = no autoreconnect (Config.NoAutoreconnect)
# 0 = infinite (Config.InfinityAutoreconnect)
# N = N attempts
autoreconnectAttempts = 0
autoreconnectDelayInMs = 2000
# Session list
sessionIDs = session1
# Defaults shared across all sessions
sessions.default.fixVersion = FIX.4.4 # FIX.4.0..FIX.5.0SP2 or FIXT.1.1+appVersion
sessions.default.heartbeatInterval = 30
# Per-session
sessions.session1.sessionType = initiator # initiator | acceptor
sessions.session1.senderCompID = BUYSIDE
sessions.session1.targetCompID = VENUE
sessions.session1.host = fix.example.com # initiator only
sessions.session1.port = 9876
sessions.session1.forceSeqNumReset = Never # Always | OneTime | Never
# Optional Logon credentials (tags 553/554)
sessions.session1.username = TRADER
sessions.session1.password = pass
⚠️ Silent failure trap. The engine accepts unknown property keys with no warning. A typo (sessions= instead of sessionIDs=, or connectionType instead of sessionType) yields an empty session list at runtime, not a config error. Use the exact keys above.
⚠️ For an acceptor, FixServer reads the process-wide port at the top of the properties file (no per-session host/port required since sessions are created dynamically as counterparties connect). Verified in Samples/EchoServer/fixengine.properties:
# default listening port
port = 4000
⚠️ CompID mirroring. A FIX session pair must have inverse CompIDs — each side's senderCompID equals the other's targetCompID:
Side A (initiator): senderCompID = BUYSIDE, targetCompID = VENUE
Side B (acceptor): senderCompID = VENUE, targetCompID = BUYSIDE
If the pairing doesn't mirror, Logon is refused — and the engine doesn't surface this as a config error. It surfaces as a refused connection or a session-level reject (35=3) at runtime. When writing a gateway against a counterparty whose config is fixed (e.g., the engine's shipped Samples/EchoServer), copy their CompIDs and swap them.
⚠️ The Config class exposes 141 string constants for property keys — see Config.StorageFactory, Config.TradePeriodBegin, Config.SslCertificate, etc. Use these in code for compiler-checked key names. But not every recognised property key has a Config.X constant. Common per-session keys parsed by literal string in SessionParameters.cs — senderCompID, targetCompID, host, fixVersion, appVersion, heartbeatInterval, username, password, sessionType (which lives on SessionParametersBuilder.SessionTypeProp, not Config) — have no corresponding Config.X constant in 1.2.3. Reaching for Config.SenderCompID or Config.Username from code is a compile error. Use the literal string for those, or write a small Keys static class in the project.
Mini-glossary (terms agents confuse)
| Term | Meaning | Common mistake |
|---|
| Admin message | Session-layer: Logon (A), Logout (5), Heartbeat (0), TestRequest (1), ResendRequest (2), SequenceReset (4), Reject (3). | Treating admin messages as app messages. The engine handles them. |
| Application message | Business-layer: NewOrderSingle (D), ExecutionReport (8), MarketDataRequest (V), etc. | Generating admin messages from app code. |
| Session-level reject (3) | Peer couldn't parse/validate the outbound message. | Treating as a business reject. Means the local engine sent malformed FIX. |
| Business reject (j) | Outbound parsed; peer business logic refused it. | Same as above in reverse. |
| Gap fill | A SequenceReset (4) with GapFillFlag (123) = Y, used to fill admin-message slots during a resend. | Sending gap fill for app messages — replay them instead. |
| Sequence reset | A SequenceReset (4) with GapFillFlag = N. Force-resets the counterparty's expected next seq num. Operational. | Using as a routine recovery tool. |
| Resend Request (2) | Counterparty asking for retransmission of messages N..M. | Ignoring it. Engine handles it if storage is persistent. |