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 everynext()callOtherwise, 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#
Always recover after reconnection: Use
AccountQueryRequestto get the correct next UserRefNumUse persistence in production: Enable persistence to maintain strict monotonicity across restarts
Thread safety: Only use the generator from the IOContext’s thread, or use
IOContext::post()Never reuse values: Each order must have a unique, strictly increasing UserRefNum
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);
}