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
AccountQueryRequestto 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:
Send
AccountQueryRequestafter reconnectionReceive
AccountQueryResponsewithnext_user_ref_numReset the generator:
generator.reset(next_user_ref_num)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#
Session API - Learn about session management
Message Types - Explore all message types
Examples - See complete working examples
Best Practices - Learn recommended patterns