C++ SDK

Orders

Order types, placement, cancellation, and management with C++17/20 examples

Orders

The order subsystem provides type-safe order construction, validation, and lifecycle management with zero-copy serialization for HFT applications.

Order Types

Supported Order Types

TypeEnumDescription
LimitOrderType::LimitExecute at specified price or better
MarketOrderType::MarketExecute immediately at best available price
StopOrderType::StopTrigger market order when price reaches stop
Stop LimitOrderType::StopLimitTrigger limit order when price reaches stop
IcebergOrderType::IcebergHidden quantity with visible portion
PegOrderType::PegTrack best bid/ask with offset

Order Structure

#include <lxdex/order.hpp>

namespace lxdex {

struct Order {
    OrderId id;                      // Server-assigned
    std::string symbol;              // "BTC-USD"
    Side side;                       // Buy/Sell
    OrderType type;                  // Limit/Market/Stop/...
    Price price;                     // Fixed-point price
    Quantity quantity;               // Fixed-point quantity
    Quantity filled_quantity;        // Amount filled
    TimeInForce time_in_force;       // GTC/IOC/FOK/DAY
    OrderStatus status;              // Pending/Active/Filled/...
    Timestamp created_at;
    Timestamp updated_at;

    // Optional fields
    std::optional<Price> stop_price;
    std::optional<Quantity> visible_quantity;  // Iceberg
    std::optional<Price> peg_offset;           // Peg orders
    std::optional<std::string> client_order_id;
};

enum class Side : uint8_t { Buy = 0, Sell = 1 };

enum class OrderType : uint8_t {
    Limit = 0,
    Market = 1,
    Stop = 2,
    StopLimit = 3,
    Iceberg = 4,
    Peg = 5
};

enum class TimeInForce : uint8_t {
    GTC = 0,  // Good Till Cancelled
    IOC = 1,  // Immediate Or Cancel
    FOK = 2,  // Fill Or Kill
    DAY = 3   // Day Order
};

enum class OrderStatus : uint8_t {
    Pending = 0,
    Active = 1,
    PartiallyFilled = 2,
    Filled = 3,
    Cancelled = 4,
    Rejected = 5,
    Expired = 6
};

}  // namespace lxdex

Fixed-Point Arithmetic

Prices and quantities use fixed-point representation to avoid floating-point errors.

#include <lxdex/types.hpp>

// Price: 8 decimal places (satoshi precision)
// Quantity: 8 decimal places

// Construction from integer (raw value)
Price price{50000'00000000};  // $50,000.00

// Construction from double (lossy, use with care)
Price price = Price::from_double(50000.00);

// Construction from string (exact)
Price price = Price::from_string("50000.00");

// Arithmetic
Price spread = ask - bid;
Quantity total = quantity * Price{2};

// Comparison
if (bid >= Price{49000'00000000}) {
    // ...
}

// Output
std::cout << price.to_string() << '\n';  // "50000.00000000"
std::cout << price.to_double() << '\n';  // 50000.0

Order Builder Pattern

Basic Order Builder

#include <lxdex/order.hpp>

using namespace lxdex;

// Limit buy order
auto order = OrderBuilder()
    .symbol("BTC-USD")
    .side(Side::Buy)
    .type(OrderType::Limit)
    .price(Price::from_string("50000.00"))
    .quantity(Quantity::from_string("1.5"))
    .time_in_force(TimeInForce::GTC)
    .build();

// Market sell order
auto market_order = OrderBuilder()
    .symbol("ETH-USD")
    .side(Side::Sell)
    .type(OrderType::Market)
    .quantity(Quantity::from_string("10.0"))
    .build();

Advanced Order Builder

// Stop-limit order
auto stop_limit = OrderBuilder()
    .symbol("BTC-USD")
    .side(Side::Sell)
    .type(OrderType::StopLimit)
    .price(Price::from_string("48000.00"))      // Limit price
    .stop_price(Price::from_string("49000.00")) // Trigger price
    .quantity(Quantity::from_string("0.5"))
    .time_in_force(TimeInForce::GTC)
    .build();

// Iceberg order (hidden quantity)
auto iceberg = OrderBuilder()
    .symbol("BTC-USD")
    .side(Side::Buy)
    .type(OrderType::Iceberg)
    .price(Price::from_string("50000.00"))
    .quantity(Quantity::from_string("100.0"))        // Total quantity
    .visible_quantity(Quantity::from_string("1.0")) // Shown in book
    .time_in_force(TimeInForce::GTC)
    .build();

// Peg order (tracks best bid/ask)
auto peg = OrderBuilder()
    .symbol("BTC-USD")
    .side(Side::Buy)
    .type(OrderType::Peg)
    .peg_offset(Price::from_string("-0.01"))  // 1 cent below best ask
    .quantity(Quantity::from_string("5.0"))
    .build();

Compile-Time Validation

// Using C++20 concepts for compile-time validation
template<typename T>
concept ValidOrder = requires(T t) {
    { t.symbol } -> std::convertible_to<std::string_view>;
    { t.side } -> std::same_as<Side&>;
    { t.quantity } -> std::same_as<Quantity&>;
    requires t.quantity.raw() > 0;
};

// Builder with validation
auto order = OrderBuilder()
    .symbol("BTC-USD")
    .side(Side::Buy)
    .type(OrderType::Limit)
    .price(Price::from_string("50000.00"))
    .quantity(Quantity::from_string("1.0"))
    .validate()  // Throws if invalid
    .build();

Placing Orders

Synchronous Placement

#include <lxdex/client.hpp>
#include <lxdex/order.hpp>

Client client({.endpoints = {"wss://api.lux.network/ws"}});
client.connect();

// Place order (blocking)
auto result = client.place_order(
    OrderBuilder()
        .symbol("BTC-USD")
        .side(Side::Buy)
        .type(OrderType::Limit)
        .price(Price::from_string("50000.00"))
        .quantity(Quantity::from_string("1.0"))
        .build()
);

if (result) {
    std::cout << "Order placed: " << result->id << '\n';
    std::cout << "Status: " << to_string(result->status) << '\n';
} else {
    std::cerr << "Error: " << result.error().message << '\n';
}

Asynchronous Placement

// Future-based async
auto future = client.place_order_async(order);

// Do other work...

// Check result
if (future.wait_for(std::chrono::microseconds{100}) == std::future_status::ready) {
    auto result = future.get();
    // Process...
}

Callback-Based (Lowest Latency)

// Zero-allocation callback
client.place_order_callback(order, [](std::expected<Order, Error> result) {
    // Called on I/O thread - keep fast!
    if (result) {
        // Order confirmed
    }
});

Batch Order Placement

std::vector<Order> orders;
orders.reserve(100);

for (int i = 0; i < 100; ++i) {
    orders.push_back(
        OrderBuilder()
            .symbol("BTC-USD")
            .side(Side::Buy)
            .type(OrderType::Limit)
            .price(Price{50000'00000000 - i * 100000000})  // Price ladder
            .quantity(Quantity{1'00000000})
            .build()
    );
}

// Place all orders in single request
auto results = client.place_orders_batch(orders);

for (std::size_t i = 0; i < results.size(); ++i) {
    if (results[i]) {
        std::cout << "Order " << i << " placed: " << results[i]->id << '\n';
    } else {
        std::cerr << "Order " << i << " failed: " << results[i].error().message << '\n';
    }
}

Cancelling Orders

Cancel Single Order

// By order ID
auto result = client.cancel_order(order_id);
if (result) {
    std::cout << "Order cancelled\n";
}

// Cancel with expected state (optimistic)
auto result = client.cancel_order(order_id, OrderStatus::Active);

Cancel Multiple Orders

// Cancel specific orders
std::vector<OrderId> order_ids = {id1, id2, id3};
auto results = client.cancel_orders(order_ids);

// Cancel all orders for symbol
auto results = client.cancel_all_orders("BTC-USD");

// Cancel all orders
auto results = client.cancel_all_orders();

Cancel-Replace (Atomic Modify)

// Atomically cancel and place new order
auto result = client.cancel_replace(
    existing_order_id,
    OrderBuilder()
        .symbol("BTC-USD")
        .side(Side::Buy)
        .type(OrderType::Limit)
        .price(Price::from_string("49999.00"))  // New price
        .quantity(Quantity::from_string("1.0"))
        .build()
);

if (result) {
    std::cout << "Order modified: " << result->id << '\n';
}

Order Queries

Get Single Order

auto result = client.get_order(order_id);
if (result) {
    const auto& order = *result;
    std::cout << "Symbol: " << order.symbol << '\n';
    std::cout << "Status: " << to_string(order.status) << '\n';
    std::cout << "Filled: " << order.filled_quantity.to_string()
              << " / " << order.quantity.to_string() << '\n';
}

Get Open Orders

// All open orders
auto orders = client.get_open_orders();

// Open orders for symbol
auto orders = client.get_open_orders("BTC-USD");

// With pagination
auto orders = client.get_open_orders({
    .symbol = "BTC-USD",
    .limit = 100,
    .offset = 0
});

Get Order History

auto orders = client.get_order_history({
    .symbol = "BTC-USD",
    .start_time = std::chrono::system_clock::now() - std::chrono::hours{24},
    .end_time = std::chrono::system_clock::now(),
    .status = {OrderStatus::Filled, OrderStatus::Cancelled},
    .limit = 1000
});

Order Updates (Real-Time)

Subscribe to Order Updates

client.subscribe_orders([](const OrderUpdate& update) {
    std::cout << "Order " << update.order_id
              << " " << to_string(update.event_type) << '\n';

    switch (update.event_type) {
        case OrderEventType::Created:
            // New order acknowledged
            break;
        case OrderEventType::PartialFill:
            std::cout << "  Filled: " << update.fill_quantity.to_string()
                      << " @ " << update.fill_price.to_string() << '\n';
            break;
        case OrderEventType::Filled:
            // Order completely filled
            break;
        case OrderEventType::Cancelled:
            // Order cancelled
            break;
        case OrderEventType::Rejected:
            std::cerr << "  Reason: " << update.reject_reason << '\n';
            break;
    }
});

Order Update Structure

struct OrderUpdate {
    OrderId order_id;
    std::string symbol;
    OrderEventType event_type;
    OrderStatus status;
    Quantity filled_quantity;      // This fill
    Price fill_price;              // This fill price
    Quantity cumulative_quantity;  // Total filled
    Timestamp timestamp;
    std::optional<std::string> reject_reason;
    std::optional<TradeId> trade_id;
};

enum class OrderEventType : uint8_t {
    Created,
    PartialFill,
    Filled,
    Cancelled,
    Rejected,
    Expired,
    Modified
};

C++20 Coroutines

#include <lxdex/client.hpp>
#include <lxdex/coro.hpp>

lxdex::Task<void> trading_loop(lxdex::Client& client) {
    // Place order
    auto order = co_await client.place_order_coro(
        OrderBuilder()
            .symbol("BTC-USD")
            .side(Side::Buy)
            .type(OrderType::Limit)
            .price(Price::from_string("50000.00"))
            .quantity(Quantity::from_string("1.0"))
            .build()
    );

    if (!order) {
        std::cerr << "Order failed: " << order.error().message << '\n';
        co_return;
    }

    std::cout << "Order placed: " << order->id << '\n';

    // Wait for fill
    while (order->status != OrderStatus::Filled) {
        co_await lxdex::sleep(std::chrono::milliseconds{10});
        order = co_await client.get_order_coro(order->id);
    }

    std::cout << "Order filled!\n";
}

int main() {
    lxdex::Client client({.endpoints = {"wss://api.lux.network/ws"}});
    client.connect();

    // Run coroutine
    lxdex::sync_wait(trading_loop(client));
}

C++17 Compatibility

#include <lxdex/order.hpp>

// C++17: Use optional and variant instead of expected
#include <optional>
#include <variant>

// Place order returns variant<Order, Error>
auto result = client.place_order(order);

if (auto* placed = std::get_if<Order>(&result)) {
    std::cout << "Order placed: " << placed->id << '\n';
} else if (auto* error = std::get_if<Error>(&result)) {
    std::cerr << "Error: " << error->message << '\n';
}

// Or use visitor
std::visit(overloaded{
    [](const Order& order) {
        std::cout << "Order: " << order.id << '\n';
    },
    [](const Error& error) {
        std::cerr << "Error: " << error.message << '\n';
    }
}, result);

Order Validation

Client-Side Validation

#include <lxdex/validation.hpp>

auto order = OrderBuilder()
    .symbol("BTC-USD")
    .side(Side::Buy)
    .type(OrderType::Limit)
    .price(Price::from_string("50000.00"))
    .quantity(Quantity::from_string("1.0"))
    .build();

// Validate before sending
auto validation = validate_order(order);
if (!validation.is_valid()) {
    for (const auto& error : validation.errors()) {
        std::cerr << error << '\n';
    }
    return;
}

client.place_order(order);

Validation Rules

RuleDescription
SymbolExistsSymbol must be valid trading pair
PriceRangePrice within min/max for symbol
QuantityRangeQuantity within min/max for symbol
TickSizePrice must be multiple of tick size
LotSizeQuantity must be multiple of lot size
NotionalValueOrder value >= minimum notional

Performance Tips

Pre-Allocated Order Pool

#include <lxdex/memory/pool.hpp>

// Pre-allocate order pool
lxdex::ObjectPool<Order> order_pool(10000);

// Get order from pool (no heap allocation)
auto* order = order_pool.acquire();
*order = OrderBuilder()
    .symbol("BTC-USD")
    .side(Side::Buy)
    .type(OrderType::Limit)
    .price(Price{50000'00000000})
    .quantity(Quantity{1'00000000})
    .build();

client.place_order(*order);

// Return to pool
order_pool.release(order);

Zero-Copy Order Submission

// Direct wire format construction
alignas(64) std::array<char, 256> buffer;

auto size = serialize_order_wire(buffer.data(), buffer.size(),
    "BTC-USD",
    Side::Buy,
    OrderType::Limit,
    50000'00000000,  // Price
    1'00000000       // Quantity
);

client.send_raw(std::span{buffer.data(), size});

Latency Measurement

auto start = std::chrono::steady_clock::now();

auto result = client.place_order(order);

auto end = std::chrono::steady_clock::now();
auto latency = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start);

std::cout << "Order latency: " << latency.count() << "ns\n";

Error Handling

Order Rejection Reasons

CodeReasonDescription
INSUFFICIENT_BALANCENot enough fundsDeposit more or reduce size
PRICE_OUT_OF_RANGEPrice too far from marketAdjust price closer to market
QUANTITY_TOO_SMALLBelow minimum sizeIncrease quantity
RATE_LIMIT_EXCEEDEDToo many requestsImplement backoff
MARKET_CLOSEDTrading haltedWait for market open
SELF_TRADEWould match own orderAdjust price or cancel existing

Retry Logic

template<typename F>
auto retry_with_backoff(F&& func, int max_retries = 3) {
    using ResultType = std::invoke_result_t<F>;

    for (int attempt = 0; attempt < max_retries; ++attempt) {
        auto result = func();

        if (result) {
            return result;
        }

        if (result.error().code == ErrorCode::RateLimited) {
            auto delay = std::chrono::milliseconds{100 * (1 << attempt)};
            std::this_thread::sleep_for(delay);
            continue;
        }

        // Non-retryable error
        return result;
    }

    return ResultType{std::unexpected{Error{ErrorCode::Timeout, "Max retries exceeded"}}};
}

// Usage
auto result = retry_with_backoff([&] {
    return client.place_order(order);
});