UserRefNum Management#

UserRefNum is a critical field in OUCH 5.0 that must be unique and strictly increasing for each order sent on a given OUCH port. The library provides UserRefNumGenerator to manage this requirement automatically.

Overview#

UserRefNum serves as a transaction identifier. For a given OUCH port:

  • Must be unique for each order

  • Must be strictly increasing throughout the trading day

  • Begins at 1

  • The exchange ignores orders with UserRefNum lower than the last processed one (treats them as retransmissions)

Basic Usage#

Getting the Generator#

auto& refnum_gen = session.get_user_refnum_generator();

Generating UserRefNum#

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

Each call to next() returns a value greater than the previous one.

UserRefNum Recovery#

After reconnection, you must recover the correct UserRefNum to avoid sending orders with numbers that are too low (which the exchange will reject).

Using AccountQueryRequest#

The recommended way to recover UserRefNum:

// After login, send AccountQueryRequest
AccountQueryRequest query;
session.send_message(&query);

// In AccountQueryResponse handler:
void on_account_query_response(Session* s, std::uint64_t seq, const AccountQueryResponse* msg) {
    UInt32 next_user_ref_num = msg->get_next_user_ref_num();
    s->get_user_refnum_generator().reset(next_user_ref_num);
    std::cout << "UserRefNum recovered: next value is " << next_user_ref_num << std::endl;
}

Manual Recovery#

If you know the next UserRefNum value (e.g., from persistent storage):

auto& refnum_gen = session.get_user_refnum_generator();
refnum_gen.reset(known_next_value);

Persistence#

For production applications, you may want to persist UserRefNum across application restarts to ensure strict monotonicity even after crashes.

Enabling Persistence#

Settings settings;
// ... other settings ...

UserRefNumGenerator::PersistenceSettings persistence;
persistence.filepath_ = "./user_refnum.dat";
persistence.use_memory_mapped_ = true;  // Use memory-mapped file (faster)
persistence.auto_save_on_update_ = false;  // Save on destruction only
persistence.sync_on_save_ = false;  // Don't sync to disk on every save
settings.user_refnum_persistence_ = persistence;

Session session(io_ctx, settings);

Persistence Behavior#

  • UserRefNum is automatically saved to the specified file

  • On startup, the generator resumes from the last saved value

  • If auto_save_on_update_ is enabled, saves occur on every next() call

  • Otherwise, saves occur on shutdown/destruction

  • If the file doesn’t exist, starts from initial_user_ref_num_

  • Uses memory-mapped file by default (faster) with fallback to traditional I/O

Thread Safety#

Important: UserRefNumGenerator is not thread-safe by default. It must be used from the IOContext’s thread.

Thread-Safe Usage#

If you need to generate UserRefNum from other threads, use IOContext::post():

// From another thread
io_ctx->post([&session]() {
    auto& refnum_gen = session.get_user_refnum_generator();
    UInt32 user_ref_num = refnum_gen.next();  // Safe: now on IOContext's thread
    // Use user_ref_num...
});

API Reference#

UserRefNumGenerator#

class UserRefNumGenerator {
public:
    // Generate next UserRefNum
    UInt32 next();
    
    // Get current value (next value that will be returned by next())
    UInt32 current() const;
    
    // Reset to a specific value
    void reset(UInt32 value);
    
    // Validate that value is >= next expected value
    bool validate(UInt32 value) const;
    
    // Get next expected value (same as current)
    UInt32 next_expected() const;
    
    // Persistence management
    bool enable_persistence(const PersistenceSettings& settings);
    void disable_persistence();
    bool is_persistence_enabled() const;
    std::string get_persistence_filepath() const;
    
    // Manual save/load (one-time operations)
    void save_to_file(const std::string& filepath) const;
    bool load_from_file(const std::string& filepath);
};

next()#

UInt32 next();

Returns the next UserRefNum value and increments the internal counter. The returned value is guaranteed to be strictly greater than the previous call.

Returns: Next UserRefNum value

Example:

UInt32 ref1 = refnum_gen.next();  // e.g., 1
UInt32 ref2 = refnum_gen.next();  // e.g., 2
UInt32 ref3 = refnum_gen.next();  // e.g., 3

reset()#

void reset(UInt32 value);

Resets the generator to start from the specified value. The next call to next() will return value + 1.

Parameters:

  • value: The value to reset to

Example:

refnum_gen.reset(100);
UInt32 next = refnum_gen.next();  // Returns 101

current()#

UInt32 current() const;

Returns the current value (next value that will be returned by next()). Useful for inspection.

Returns: Current UserRefNum value (same as next_expected())

validate()#

bool validate(UInt32 value) const;

Validates that the provided value is >= the next expected UserRefNum. Useful for validating UserRefNum values received from external sources (e.g., Account Query Response with NextUserRefNum).

Parameters:

  • value: UserRefNum value to validate

Returns: true if value >= next_expected(), false otherwise

Example:

UInt32 next_user_ref_num = msg->get_next_user_ref_num();
if (refnum_gen.validate(next_user_ref_num)) {
    refnum_gen.reset(next_user_ref_num);
} else {
    std::cerr << "Invalid UserRefNum: " << next_user_ref_num << std::endl;
}

next_expected()#

UInt32 next_expected() const;

Returns the next expected UserRefNum value (same as current()). Useful for validation.

Returns: Next expected UserRefNum value

Best Practices#

  1. Always recover after reconnection: Use AccountQueryRequest to get the correct next UserRefNum

  2. Use persistence in production: Enable persistence to maintain strict monotonicity across restarts

  3. Thread safety: Only use the generator from the IOContext’s thread, or use IOContext::post()

  4. Never reuse values: Each order must have a unique, strictly increasing UserRefNum

  5. Handle wraparound: UserRefNum is a 32-bit unsigned integer, so it can wrap around after 4 billion orders (unlikely in practice)

Example: Complete Recovery Flow#

// After login
void on_established(Session* s) {
    // Request account information to recover UserRefNum
    AccountQueryRequest query;
    s->send_message(&query);
}

// Handle response
void on_account_query_response(Session* s, std::uint64_t seq, const AccountQueryResponse* msg) {
    UInt32 next_user_ref_num = msg->get_next_user_ref_num();
    s->get_user_refnum_generator().reset(next_user_ref_num);
    
    std::cout << "Recovered UserRefNum: next value is " << next_user_ref_num << std::endl;
    
    // Now safe to send orders
    send_order(s);
}

void send_order(Session* s) {
    auto& refnum_gen = s->get_user_refnum_generator();
    UInt32 user_ref_num = refnum_gen.next();
    
    EnterOrder order;
    order.set_user_ref_num(user_ref_num);
    // ... set other fields ...
    
    s->send_message(&order);
}