PureFix.Generator
0.4.1-beta
dotnet tool install --global PureFix.Generator --version 0.4.1-beta
dotnet new tool-manifest
dotnet tool install --local PureFix.Generator --version 0.4.1-beta
#tool dotnet:?package=PureFix.Generator&version=0.4.1-beta&prerelease
nuke :add-package PureFix.Generator --version 0.4.1-beta
PureFix
A high-performance, pure C# FIX protocol engine for .NET.
Project Status: Beta
| Component | Maturity | Notes |
|---|---|---|
| Message Parser | ✅ Production | Extensively tested against 20+ brokers across asset classes |
| Type Generation | ✅ Production | Stable API, battle-tested with custom dictionaries |
| Session Handling | 🟡 Beta | Robust for normal operation, reconnection, and sequence recovery |
| Resend/Gap Fill | 🟡 Beta | Works in common scenarios; edge cases under continued testing |
Parser Testing Coverage
The message parser has been validated in production drop-copy scenarios (read-only, no order entry) across:
- Asset Classes: FX Spot, FX Options, FX Swaps, NDF, NDS, Forwards, CDS, CDX, Government Bonds, Futures
- Message Types: ExecutionReport (8), Allocation (J), TradeCaptureReport (AE), and session messages
- Protocols: FIX 4.2, 4.4, 5.0 SP2, including FpML extensions
- Brokers: 20+ counterparties with broker-specific dictionary customizations
Test Coverage
| Metric | Coverage |
|---|---|
| Line Coverage | 68.8% |
| Branch Coverage | 54.1% |
| Method Coverage | 71.9% |
Coverage excludes generated FIX type assemblies. Run ./scripts/coverage.sh for local report.
Session Handling Status
Session management handles standard scenarios reliably:
- ✅ Normal logon/logout with heartbeat
- ✅ Reconnection after socket drop or sleep/wake
- ✅ Sequence number persistence and recovery
- ✅ ResetSeqNumFlag handling (initiator, acceptor, both sides)
- ✅ Logon sequence mismatch retry (client catches up to server)
- 🟡 ResendRequest/GapFill edge cases (race conditions under testing)
Recommendation: For order-sending clients, enable
ResendGapFillOnly: trueuntil resend handling is fully production-validated.
Performance
PureFix uses a lazy parsing architecture that minimizes allocations:
Stage 1: Tokenization (buffer to view)
Tokenizes raw bytes into an indexed view. Structure analysis is deferred until needed:
| Message Type | Fields | Size | Time | Allocated (pooled) |
|---|---|---|---|---|
| Heartbeat | ~10 | 131 B | 645 ns | 112 B |
| Logon | ~22 | 214 B | 1.0 μs | 112 B |
| QuoteRequest | ~30 | 334 B | 1.5 μs | 112 B |
| OrderCancelReject | ~370 | 3.9 KB | 17 μs | 1.2 KB |
| ExecutionReport | ~646 | 6.6 KB | 29 μs | 3.2 KB |
Storage and views are pooled and reused.
Simple field-by-tag access (e.g., view.GetString(tag)) uses O(n) linear scan - ideal for session messages, drop copy handlers, and routing where only a few fields are needed.
Structure parsing is triggered on-demand only when accessing components or repeating groups via GetView() or GetGroupInstance().
Stage 2: Field Extraction (view to typed message)
Extracts field values from the view into typed message objects:
| Message Type | Extract Time | Allocated (new) | Allocated (pooled) |
|---|---|---|---|
| Heartbeat | 4.7 ns | 40 B | 0 B |
| Logon | 5.9 ns | 112 B | 0 B |
| QuoteRequest | 5.7 ns | 96 B | 0 B |
| OrderCancelReject | 7.6 ns | 216 B | 0 B |
| ExecutionReport | 27 ns | 1,480 B | 0 B |
Message objects can be pooled and reused via Reset() for zero-allocation extraction.
Zero-Allocation View Access
For routing, filtering, and validation scenarios, the span-based API eliminates per-field allocation entirely:
| Pattern | String API | Span API | Speed | Allocation |
|---|---|---|---|---|
| Single field check | 11.2 ns / 24 B | 8.7 ns / 0 B | 23% faster | 0 B |
| Message routing | 11.3 ns / 24 B | 9.2 ns / 0 B | 18% faster | 0 B |
| Prefix check | 22.0 ns / 40 B | 9.3 ns / 0 B | 57% faster | 0 B |
| Two-field validation | 26.6 ns / 64 B | 14.5 ns / 0 B | 45% faster | 0 B |
// Zero-allocation message type check
if (view.IsTagEqual(35, "AE"u8)) // vs view.GetString(35) == "AE"
{
// handle Trade Capture Report
}
// Zero-allocation routing
switch (view.MatchTag(35, "0"u8, "A"u8, "5"u8))
{
case 0: HandleHeartbeat(view); break;
case 1: HandleLogon(view); break;
case 2: HandleLogout(view); break;
}
// Zero-allocation prefix check
if (view.TagStartsWith(8, "FIXT"u8))
{
// FIXT 1.1 protocol
}
// Raw span access for custom parsing
ReadOnlySpan<byte> clOrdId = view.GetSpan(11);
See docs/msgview-span-api.md for full API reference.
Benchmarks: .NET 9.0 on AMD Ryzen 9 7950X. See CI benchmarks.
Why PureFix?
vs QuickFIX/n
| Aspect | PureFix | QuickFIX/n |
|---|---|---|
| Implementation | Pure C# | C++ core with C# wrapper |
| Debugging | Full source debugging | Limited - crosses native boundary |
| Custom Types | Generate types for your dictionary | Generic field accessors only |
| Field Extraction | Nanosecond extraction, poolable messages | Allocates per field access |
| Code Generation | First-class tool (purefix-gen) |
Limited/manual |
Key Features
- Strongly-typed messages - Generate C# classes from any FIX dictionary with full IntelliSense
- Custom dictionary support - Generate types for broker-specific extensions, not just vanilla FIX
- Pooled message extraction - Reuse message objects for zero-allocation field extraction
- Pure C# - Debug everything, no native interop boundaries
- Session management - Logon, heartbeat, sequence numbers, gap fill handled automatically
- File & memory stores - Session persistence with QuickFix-compatible file format
- TLS support - Secure connections out of the box
Session Resilience
PureFix sessions are designed to survive network interruptions, including laptop sleep/wake cycles. When a timeout is detected, the session attempts recovery before terminating:
[18:56:12] sending test req. state = ActiveNormalSession
[18:56:12] state transition: ActiveNormalSession -> AwaitingProcessingResponseToTestRequest
[18:56:14] Session timeout (attempt 1/3). Attempting recovery - resetting timeout window.
[18:56:16] state transition: AwaitingProcessingResponseToTestRequest -> ActiveNormalSession
[18:56:16] [355,AE]: TradeCaptureReport received - session continues normally
After a 7-minute laptop sleep, the session:
- Detects the timeout and sends a TestRequest
- Uses the recovery window (3 attempts) instead of immediate termination
- Receives the peer's response when the network returns
- Resumes normal message flow without reconnection
This eliminates unnecessary session drops during brief network interruptions or system sleep events.
Client Safety: ResendGapFillOnly
For client/initiator sessions, we strongly recommend enabling ResendGapFillOnly in your session configuration:
{
"resendGapFillOnly": true
}
When the counterparty sends a ResendRequest, this setting ensures PureFix responds with a SequenceReset-GapFill instead of replaying stored messages. This prevents accidental re-execution of old orders if the counterparty requests message replay.
Why this matters: If a client replays a stored NewOrderSingle from hours ago, the broker may execute it as a new order - causing duplicate fills and potential losses.
Note: Message replay functionality exists but is still in alpha testing. Until fully validated,
ResendGapFillOnly: trueis the safest option for order-sending clients.
Getting Started
Want to see it in action? Check out the standalone demo - a complete working example with Trade Capture that you can run immediately.
Quick Start
# Install packages
dotnet add package PureFix.Transport
# Install code generator
dotnet tool install purefix-gen
# Generate types from your dictionary
purefix-gen -D FIX44.xml -g -p Generated --nuget --namespace MyApp.Fix
Send a typed message
var order = new NewOrderSingle
{
ClOrdID = "order-123",
Instrument = new Instrument { Symbol = "AAPL" },
Side = SideValues.Buy,
OrderQty = 100,
OrdType = OrdTypeValues.Market
};
await session.Send(MsgTypeValues.NewOrderSingle, order);
Receive with type safety
protected override Task OnApplicationMsg(string msgType, IMessageView view)
{
if (msgType == MsgType.ExecutionReport)
{
var exec = (ExecutionReport)messageFactory.ToFixMessage(view)!;
Console.WriteLine($"Fill: {exec.Instrument?.Symbol} {exec.LastQty} @ {exec.LastPx}");
}
return Task.CompletedTask;
}
Documentation
| Resource | Description |
|---|---|
| Standalone Demo | Full working example with Trade Capture |
| NuGet Package | Package documentation |
| Type Generation | How dictionaries become C# types |
Packages
| Package | Purpose |
|---|---|
| PureFix.Transport | Session management, TCP, stores |
| PureFix.Types | Standard FIX types and utilities |
| PureFix.Types.Core | Base interfaces |
Supported FIX Versions
- FIX 4.0 - 4.4
- FIX 5.0, 5.0 SP1, 5.0 SP2
- Custom/broker-specific dictionaries
Related Projects
- jspurefix - TypeScript/JavaScript FIX engine (~3k weekly downloads)
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
This package has no dependencies.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.4.1-beta | 96 | 8/9/2026 |
| 0.4.0-beta | 88 | 8/9/2026 |
| 0.3.1-beta | 80 | 8/3/2026 |
| 0.3.0-beta | 81 | 8/3/2026 |
| 0.2.14-beta | 75 | 7/20/2026 |
| 0.2.13-beta | 79 | 6/27/2026 |
| 0.2.12-beta | 91 | 4/5/2026 |
| 0.2.10-beta | 109 | 1/26/2026 |
| 0.2.9-beta | 95 | 1/25/2026 |
| 0.2.8-beta | 92 | 1/23/2026 |
| 0.2.7-beta | 96 | 1/21/2026 |
| 0.2.6-beta | 85 | 1/20/2026 |
| 0.2.5-beta | 97 | 1/20/2026 |
| 0.2.4-beta | 97 | 1/19/2026 |
| 0.2.3-beta | 92 | 1/18/2026 |
| 0.2.2-beta | 89 | 1/17/2026 |
| 0.2.1-beta | 90 | 1/16/2026 |
| 0.2.0-beta | 88 | 1/15/2026 |
| 0.1.19-alpha | 93 | 1/14/2026 |
| 0.1.18-alpha | 87 | 1/13/2026 |