🦀 miniFIX Tutorial | Part 01

September 15, 2025 by CryptoPatrick Rust Tutorials

Getting Started with miniFIX: Decoding FIX Messages

Introduction

The Financial Information eXchange (FIX) protocol is the backbone of electronic trading in financial markets. If you’re building trading systems in Rust, miniFIX provides a modern, type-safe way to work with FIX messages. In this post, we’ll explore how to decode FIX messages using miniFIX’s powerful decoding capabilities.

What You’ll Learn

  • How to set up a basic FIX decoder
  • Working with different field types and type inference
  • Handling repeating groups in FIX messages
  • Flexible separator support (SOH vs pipe-delimited)

Setting Up the Decoder

First, let’s create a FIX decoder for the FIX 4.2 specification:

use minifix::prelude::*;
use minifix::tagvalue::Decoder;

fn main() {
    let fix_dictionary = Dictionary::fix42();
    let mut fix_decoder = Decoder::new(fix_dictionary);
    
    // Support pipe-delimited messages for easier testing
    fix_decoder.config_mut().separator = b'|';
}

The decoder creation is an expensive operation, so you should create it once at the beginning of your program and reuse it throughout your FIX session.

Decoding a Market Data Message

Let’s decode a market data incremental refresh message:

const FIX_MESSAGE: &[u8] = b"8=FIX.4.2|9=196|35=X|49=A|56=B|34=12|52=20100318-03:21:11.364|262=A|268=2|279=0|269=0|278=BID|55=EUR/USD|270=1.37215|15=EUR|271=2500000|346=1|279=0|269=1|278=OFFER|55=EUR/USD|270=1.37224|15=EUR|271=2503200|346=1|10=171|";

let msg = fix_decoder
    .decode(FIX_MESSAGE)
    .expect("Invalid FIX message");

Type-Safe Field Access

miniFIX provides excellent type inference and multiple ways to access the same field:

// Access as raw bytes
assert_eq!(msg.get(fix42::BEGIN_STRING), Ok(b"FIX.4.2"));
assert_eq!(msg.get(fix42::MSG_TYPE), Ok(b"X"));

// Access as typed enums
assert_eq!(
    msg.get(fix42::MSG_TYPE),
    Ok(fix42::MsgType::MarketDataIncrementalRefresh)
);

// Access standard fields
assert_eq!(msg.get(fix42::SENDER_COMP_ID), Ok(b"A"));
assert_eq!(msg.get(fix42::TARGET_COMP_ID), Ok(b"B"));
assert_eq!(msg.get(fix42::MSG_SEQ_NUM), Ok(12));

// You can also use tag numbers directly
assert_eq!(msg.get(49), Ok("A"));

Working with Repeating Groups

FIX messages often contain repeating groups. miniFIX makes working with these intuitive:

// Access the market data entries group
let md_entries = msg.group(fix42::NO_MD_ENTRIES).unwrap();
assert_eq!(md_entries.len(), 2);

// Iterate over all entries
for entry in md_entries.entries() {
    assert_eq!(entry.get(fix42::CURRENCY), Ok(b"EUR"));
}

// Access specific entries
let md0 = md_entries.get(0).unwrap();
assert_eq!(
    md0.get(fix42::MD_UPDATE_ACTION),
    Ok(fix42::MdUpdateAction::New)
);
assert_eq!(md0.get(fix42::MD_ENTRY_ID), Ok(b"BID"));
assert_eq!(md0.get(fix42::SYMBOL), Ok(b"EUR/USD"));

// Multiple type representations for numeric fields
assert_eq!(md0.get(fix42::MD_ENTRY_PX), Ok(1.37215f32));
assert_eq!(md0.get(fix42::MD_ENTRY_PX), Ok(b"1.37215"));
assert_eq!(md0.get(fix42::MD_ENTRY_SIZE), Ok(2_500_000));

Key Benefits

  1. Type Safety: Strong typing prevents common errors when accessing fields
  2. Flexibility: Multiple ways to access the same data (raw bytes, parsed types, etc.)
  3. Performance: Efficient parsing with minimal allocations
  4. Standards Compliance: Full support for FIX specifications
  5. Developer Experience: Intuitive API with excellent type inference

Complete Example

use minifix::prelude::*;
use minifix::tagvalue::Decoder;

const FIX_MESSAGE: &[u8] = b"8=FIX.4.2|9=196|35=X|49=A|56=B|34=12|52=20100318-03:21:11.364|262=A|268=2|279=0|269=0|278=BID|55=EUR/USD|270=1.37215|15=EUR|271=2500000|346=1|279=0|269=1|278=OFFER|55=EUR/USD|270=1.37224|15=EUR|271=2503200|346=1|10=171|";

fn main() {
    let fix_dictionary = Dictionary::fix42();
    let mut fix_decoder = Decoder::new(fix_dictionary);
    fix_decoder.config_mut().separator = b'|';
    
    let msg = fix_decoder
        .decode(FIX_MESSAGE)
        .expect("Invalid FIX message");

    // Basic field access
    println!("Message Type: {:?}", msg.get(fix42::MSG_TYPE));
    println!("Symbol: {:?}", msg.get(fix42::SYMBOL));
    
    // Process repeating groups
    let md_entries = msg.group(fix42::NO_MD_ENTRIES).unwrap();
    for (i, entry) in md_entries.entries().enumerate() {
        println!(
            "Entry {}: {} @ {} ({})",
            i,
            String::from_utf8_lossy(entry.get(fix42::MD_ENTRY_ID).unwrap()),
            entry.get::<f32>(fix42::MD_ENTRY_PX).unwrap(),
            entry.get::<i32>(fix42::MD_ENTRY_SIZE).unwrap()
        );
    }
}

Next Steps

Now that you’ve learned the basics of FIX message decoding, you might want to explore:

  • Encoding FIX messages
  • Working with streaming FIX data
  • Custom field definitions and code generation
  • Async networking with Tokio

miniFIX provides a solid foundation for building robust, type-safe FIX applications in Rust. The combination of performance, safety, and developer experience makes it an excellent choice for financial technology applications.


Next: Part 02 - Streaming FIX Message Decoding

'I write to understand as much as to be understood.' —Elie Wiesel
(c) 2024 CryptoPatrick