Quick Start#

Get up and running with OUCH 5.0 in minutes. This guide walks you through creating a basic connection and sending your first order.

Basic Usage#

Here’s a complete example that demonstrates connection handling, error recovery, and order execution:

#include <nasdaq/ouch50/Session.h>
#include <nasdaq/ouch50/Messages.h>
#include <nasdaq/ouch50/net/IOContext.h>
#include <iostream>
#include <thread>
#include <chrono>
#include <atomic>

using namespace b2bits::nasdaq::ouch5;

int main() {
    // Create IO context
    auto io_ctx = std::make_shared<IOContext>(IOContext::Settings{"main", 0, false});
    io_ctx->start();

    // Configure session
    Settings settings;
    settings.transport_.username_ = "TRADER01";
    settings.transport_.password_ = "password123";
    settings.transport_.remote_a_ = Endpoint{"192.168.1.100", 64002};
    settings.initial_user_ref_num_ = 1;

    // Create session
    Session session(io_ctx, settings);

    // Track connection state and UserRefNum recovery
    std::atomic<bool> connected{false};
    std::atomic<bool> user_refnum_recovered{false};

    // Attach listeners and login atomically (prevents race conditions)
    // This must be called from a non-IOContext thread to avoid deadlock
    session.start_with_listeners("", 0, std::chrono::milliseconds(2000),
        // State change handler - detect disconnections and reconnect
        [&session, &connected, &user_refnum_recovered](Session* s, Session::State old_state, Session::State new_state) {
            if (new_state == Session::State::Established) {
                connected = true;
                std::cout << "Connected to exchange" << std::endl;
                
                // After reconnection, recover UserRefNum state
                if (old_state == Session::State::Disconnected) {
                    std::cout << "Reconnected - recovering UserRefNum state..." << std::endl;
                    user_refnum_recovered = false;
                    
                    // Send AccountQueryRequest to get NextUserRefNum from server
                    // Safe: message handlers run on IOContext's thread
                    AccountQueryRequest query;
                    std::error_code ec = s->send_message(&query);
                    if (ec) {
                        std::cerr << "Failed to send AccountQueryRequest: " << ec.message() << std::endl;
                    }
                }
            } else if (new_state == Session::State::Disconnected) {
                connected = false;
                user_refnum_recovered = false;
                std::cout << "Disconnected from exchange" << std::endl;
                
                // Attempt reconnection (from non-IOContext thread to avoid deadlock)
                if (old_state == Session::State::Established) {
                    std::cout << "Attempting reconnection..." << std::endl;
                    std::this_thread::sleep_for(std::chrono::seconds(1));
                    s->async_login("", 0, 
                        [](std::uint64_t seq, const std::error_code& ec) {
                            if (ec) {
                                std::cerr << "Reconnection failed: " << ec.message() << std::endl;
                            } else {
                                std::cout << "Reconnection successful, sequence: " << seq << std::endl;
                            }
                        },
                        std::chrono::milliseconds(2000));
                }
            }
        },
        // AccountQueryResponse handler - recover UserRefNum after reconnection
        [&session, &user_refnum_recovered](Session* s, std::uint64_t seq, const AccountQueryResponse* msg) {
            UInt32 next_user_ref_num = msg->get_next_user_ref_num();
            std::cout << "AccountQueryResponse: NextUserRefNum=" << next_user_ref_num << std::endl;
            
            // Reset UserRefNum generator to server's NextUserRefNum
            s->get_user_refnum_generator().reset(next_user_ref_num);
            std::cout << "UserRefNum generator reset to: " << next_user_ref_num << std::endl;
            
            // Mark messages up to NextUserRefNum as acknowledged (if buffering enabled)
            if (auto* buffer = s->get_message_buffer()) {
                buffer->mark_acknowledged_up_to(next_user_ref_num - 1);
            }
            
            // Retransmit any unacknowledged messages
            std::error_code ec = s->retransmit_unacknowledged();
            if (ec) {
                std::cerr << "Some messages failed to retransmit: " << ec.message() << std::endl;
            }
            
            user_refnum_recovered = true;
        },
        // OrderAccepted handler
        [](Session* s, std::uint64_t seq, const OrderAccepted* msg) {
            std::cout << "Order Accepted: UserRefNum=" << msg->get_user_ref_num()
                      << " Symbol=" << static_cast<std::string_view>(msg->get_symbol())
                      << " ClOrdID=" << static_cast<std::string_view>(msg->get_cl_ord_id())
                      << " OrderState=" << static_cast<char>(msg->get_order_state()) << std::endl;
            
            // Mark as acknowledged if buffering enabled
            if (auto* buffer = s->get_message_buffer()) {
                buffer->mark_acknowledged(msg->get_user_ref_num());
            }
        },
        // OrderExecuted handler - print detailed execution information
        [](Session* s, std::uint64_t seq, const OrderExecuted* msg) {
            UInt32 user_ref_num = msg->get_user_ref_num();
            UInt32 quantity = msg->get_quantity();
            Price price = msg->get_price();
            UInt64 match_number = msg->get_match_number();
            LiquidityFlag liquidity_flag = msg->get_liquidity_flag();
            
            std::cout << "Order Executed: UserRefNum=" << user_ref_num
                      << " Quantity=" << quantity
                      << " Price=" << price.to_double()
                      << " MatchNumber=" << match_number
                      << " LiquidityFlag=" << static_cast<char>(liquidity_flag) << std::endl;
            
            // Mark as acknowledged if buffering enabled
            if (auto* buffer = s->get_message_buffer()) {
                buffer->mark_acknowledged(user_ref_num);
            }
        },
        // Rejected handler - handle errors
        [](Session* s, std::uint64_t seq, const Rejected* msg) {
            std::cerr << "Order Rejected: UserRefNum=" << msg->get_user_ref_num()
                      << " Reason=" << static_cast<char>(msg->get_reason()) << std::endl;
        }
    );

    // Wait briefly before starting order flow
    std::this_thread::sleep_for(std::chrono::milliseconds(500));

    // Send order every second (runs indefinitely)
    while (true) {
        io_ctx->post([&session, &connected, &user_refnum_recovered]() {
            // Wait for connection and UserRefNum recovery
            if (!connected.load() || !user_refnum_recovered.load()) {
                std::cout << "Waiting for connection and UserRefNum recovery..." << std::endl;
                return;
            }
            
            // Now on IOContext's thread - safe to use Session
            auto& refnum_gen = session.get_user_refnum_generator();
            UInt32 user_ref_num = refnum_gen.next();

            EnterOrder order;
            order.set_user_ref_num(user_ref_num);
            order.set_side(Side::Buy);
            order.set_quantity(1000);
            order.set_symbol("AAPL");
            order.set_price(Price::from_double(150.50));
            order.set_time_in_force(TimeInForce::Day);
            order.set_display(DisplayFlag::Visible);
            order.set_capacity(Capacity::Agency);
            order.set_intermarket_sweep_elig('N');
            order.set_cross_type(CrossType::None);
            order.set_cl_ord_id("ORD001");

            std::error_code ec = session.send_message(&order);
            if (ec) {
                std::cerr << "Failed to send order: " << ec.message() << std::endl;
            } else {
                std::cout << "Order sent: UserRefNum=" << user_ref_num << std::endl;
            }
        });

        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    
    return 0;
}

Step-by-Step Explanation#

1. Create IO Context#

The IOContext manages the event loop and network operations:

auto io_ctx = std::make_shared<IOContext>(IOContext::Settings{"main", 0, false});
io_ctx->start();

2. Configure Session#

Set up connection parameters:

Settings settings;
settings.transport_.username_ = "TRADER01";
settings.transport_.password_ = "password123";
settings.transport_.remote_a_ = Endpoint{"192.168.1.100", 64002};
settings.initial_user_ref_num_ = 1;

3. Create Session#

Initialize the OUCH 5.0 session:

Session session(io_ctx, settings);

4. Attach Listeners and Login#

Attach listeners and login atomically to prevent race conditions. This must be called from a non-IOContext thread:

session.start_with_listeners("", 0, std::chrono::milliseconds(2000),
    // State change handler - handles connection/disconnection
    [&session](Session* s, Session::State old_state, Session::State new_state) {
        if (new_state == Session::State::Established) {
            // Connected - recover UserRefNum if reconnecting
            if (old_state == Session::State::Disconnected) {
                AccountQueryRequest query;
                s->send_message(&query);  // Request NextUserRefNum from server
            }
        } else if (new_state == Session::State::Disconnected) {
            // Disconnected - attempt reconnection
            s->async_login("", 0, [](std::uint64_t, const std::error_code&) {}, 
                          std::chrono::milliseconds(2000));
        }
    },
    // AccountQueryResponse handler - recover UserRefNum
    [](Session* s, std::uint64_t, const AccountQueryResponse* msg) {
        UInt32 next_user_ref_num = msg->get_next_user_ref_num();
        s->get_user_refnum_generator().reset(next_user_ref_num);
        s->retransmit_unacknowledged();  // Retransmit pending orders
    },
    // OrderExecuted handler - print execution details
    [](Session* s, std::uint64_t seq, const OrderExecuted* msg) {
        std::cout << "Executed: UserRefNum=" << msg->get_user_ref_num()
                  << " Qty=" << msg->get_quantity()
                  << " Price=" << msg->get_price().to_double()
                  << " MatchNumber=" << msg->get_match_number() << std::endl;
    }
);

Note: start_with_listeners() must be called from a non-IOContext thread to avoid deadlock.

5. Handle Connection Errors and Reconnection#

The state change handler monitors connection state and automatically handles reconnection:

  • On Disconnection: Detects when connection is lost and attempts reconnection using async_login()

  • On Reconnection: After successful reconnection, sends AccountQueryRequest to recover UserRefNum state

6. Recover UserRefNum After Reconnection#

After reconnection, you must recover the UserRefNum state to ensure strictly increasing reference numbers:

// In AccountQueryResponse handler (runs on IOContext thread)
void on_account_query_response(Session* s, std::uint64_t, const AccountQueryResponse* msg) {
    UInt32 next_user_ref_num = msg->get_next_user_ref_num();
    
    // Reset generator to server's NextUserRefNum
    s->get_user_refnum_generator().reset(next_user_ref_num);
    
    // Mark acknowledged messages (if buffering enabled)
    if (auto* buffer = s->get_message_buffer()) {
        buffer->mark_acknowledged_up_to(next_user_ref_num - 1);
    }
    
    // Retransmit unacknowledged messages
    s->retransmit_unacknowledged();
}

7. Send Order#

Send messages from the IOContext thread using IOContext::post():

// Post work to IOContext thread (required for thread safety)
io_ctx->post([&session]() {
    // Now on IOContext's thread - safe to use Session
    
    auto& refnum_gen = session.get_user_refnum_generator();
    UInt32 user_ref_num = refnum_gen.next();

    EnterOrder order;
    order.set_user_ref_num(user_ref_num);
    order.set_side(Side::Buy);
    order.set_quantity(1000);
    order.set_symbol("AAPL");
    order.set_price(Price::from_double(150.50));
    // ... set other fields ...

    std::error_code ec = session.send_message(&order);
    if (ec) {
        std::cerr << "Send failed: " << ec.message() << std::endl;
    }
});

Thread Safety Note: get_user_refnum_generator() and send_message() must be called from the IOContext’s thread.

8. Handle Order Executions#

The OrderExecuted message contains detailed execution information:

void on_order_executed(Session* s, std::uint64_t seq, const OrderExecuted* msg) {
    UInt32 user_ref_num = msg->get_user_ref_num();      // Order reference number
    UInt32 quantity = msg->get_quantity();               // Executed quantity
    Price price = msg->get_price();                      // Execution price
    UInt64 match_number = msg->get_match_number();       // Unique match identifier
    LiquidityFlag liquidity_flag = msg->get_liquidity_flag();  // Maker/Taker flag
    
    std::cout << "Execution: UserRefNum=" << user_ref_num
              << " Qty=" << quantity
              << " Price=" << price.to_double()
              << " MatchNumber=" << match_number
              << " LiquidityFlag=" << static_cast<char>(liquidity_flag) << std::endl;
}

Key Concepts#

Connection Management#

The library provides automatic connection state monitoring through state change handlers:

  • State::Established: Session is connected and ready for trading

  • State::Disconnected: Connection lost - implement reconnection logic

  • State::Connecting: Attempting to establish connection

Always monitor state changes to handle disconnections gracefully.

UserRefNum Recovery#

After reconnection, you must recover UserRefNum state to ensure strictly increasing reference numbers:

  1. Send AccountQueryRequest after reconnection

  2. Receive AccountQueryResponse with next_user_ref_num

  3. Reset the generator: generator.reset(next_user_ref_num)

  4. Retransmit unacknowledged messages if buffering is enabled

// After reconnection
AccountQueryRequest query;
session.send_message(&query);

// In AccountQueryResponse handler
void on_response(Session* s, std::uint64_t, const AccountQueryResponse* msg) {
    s->get_user_refnum_generator().reset(msg->get_next_user_ref_num());
    s->retransmit_unacknowledged();
}

UserRefNum#

Every order must have a unique, strictly increasing UserRefNum. The library provides a generator:

auto& refnum_gen = session.get_user_refnum_generator();
UInt32 user_ref_num = refnum_gen.next();  // Strictly increasing

Price Type#

Prices are handled using the Price type with 4 implied decimals:

Price price = Price::from_double(150.50);  // $150.50
double value = price.to_double();           // 150.50

Order Execution Details#

The OrderExecuted message provides complete execution information:

  • UserRefNum: Order reference number

  • Quantity: Executed quantity

  • Price: Execution price (Price type with 4 implied decimals)

  • MatchNumber: Unique match identifier

  • LiquidityFlag: Maker (‘M’) or Taker (‘T’) flag

Message Types#

  • Egress Messages (Client → Exchange): EnterOrder, ReplaceOrderRequest, CancelOrderRequest, AccountQueryRequest, etc.

  • Ingress Messages (Exchange → Client): OrderAccepted, OrderExecuted, OrderCanceled, AccountQueryResponse, Rejected, etc.

Note: See Terminology Glossary for mapping to official OUCH 5.0 terminology.

Next Steps#