Skip to content

Known gaps

slick-sim is early-stage. This page lists everything found in the current source that is unimplemented, half-wired, or behaves differently from what the surrounding code implies. It exists so the rest of the documentation can describe the intended design without having to hedge every paragraph, and so you do not spend an afternoon debugging something that was never finished.

Each entry says what it is, where it lives, and what it means for you.

Venues

CME, Eurex and ICE are not implemented

There is no CmeExchange, no Eurex adapter, and no ICE adapter, despite Venue::CME and Venue::ICE existing in types.hpp and README.md describing exchange-adapter namespaces for all three. A zero-byte src/exchange/exch_cme.hpp used to stand in for the first of these; it has been removed, since an empty header is not a plan.

The Venue enumerators stay regardless — a Venue is an identity written into every market-data frame, not a claim that an adapter exists, so to_venue("cme") still resolves.

What it means: only coinbase and hyperliquid are usable venue keys in the config.

An unknown venue key is rejected at startup

This used to be a gap: main.cpp dispatched on the config key with an if/else, and anything else constructed a plain Exchange that bound no ports, populated no publisher, and — because the base start() had no loop — processed at most one request before its thread exited. Silently.

Both halves are now closed. The loop lives in Exchange::start(), and keys resolve through the venue registry rather than a hard-coded chain, with no generic fallback. An enabled key with no registered adapter now fails immediately and names what the binary does carry:

[ERROR] No venue adapter for exchanges."kraken". This build has: coinbase, hyperliquid.
        Check the spelling, or configure with -DSLICK_SIM_ENABLE_<VENUE>=ON.

"enabled": false is checked before the lookup, so a disabled block needs no adapter — which is how the committed sample's cme entry keeps working.

What it means: a typo or a compiled-out venue is now a startup failure rather than an exchange that appears to run. Note that the same message appears if a venue adapter was built but failed to register itself, which is what the ExchangeRegistryTest suite guards against.

Order and market-data plumbing

REST order handlers block the gateway's event loop

Every REST handler that needs a reply from the matching engine waits for it by polling response_queue_ in a loop with a 100 microsecond sleep, up to a 5 second timeout. That loop runs on the gateway's only uWebSockets event-loop thread, so while one request waits, no other client's request on that gateway is served at all - not even one that would answer instantly.

The batch handlers are worse, because they poll once per entry rather than once per request: handle_batch_cancel and Hyperliquid's process_order_action wait inside the loop over the batch, so a ten-order batch can hold the loop for ten separate timeouts.

What it means: REST throughput is one in-flight request per gateway, and a single order that the engine never answers stalls every other client for five seconds. It is not a correctness problem - requests are still served first-come-first-served, since the request and response queues are both FIFO and the exchange thread is single-threaded - but it puts a hard ceiling on concurrency.

order_gateway::PendingResponses is the correlation half of the fix: a handler registers what it is waiting for and returns, and the reply completes the HTTP response when it arrives. The uWebSockets half - holding an HttpResponse past handler return, cancelling on abort, and the per-request timeout timer - is deliberately not done yet, because nothing exercises the REST gateway over HTTP and those are precisely the parts that fail as a use-after-free rather than a wrong answer.

MDUpdateType::ORDER is never published

Symbol::onOrderUpdate fills md_order_update_cache_, but every call site clears the cache behind a // TODO: publish MD order update comment rather than calling publishMDOrderUpdate. The function exists and is correct; nothing invokes it.

What it means: no order-by-order (L3) market data reaches clients. Only level data does.

L3 order books are stubs

OrderBookImpl<OrderBookType::L3>::populateL2SubscriptionResponse, populateMDBookUpdate and populateL2Snapshot are empty function bodies with "stub implementation for testing" comments. Only the L2 specialisations do real work — and both adapters call createOrderBook<OrderBookType::L2>().

What it means: OrderBookType::L3 is only useful in tests. Do not select it for a live venue.

Pro-rata matching does not exist

MatchingEngine::Type declares ProRata, and matching_engines_ is sized by MatchingEngine::Type::__count__, but pro_rata_matching_engine.hpp is a zero-byte file.

What it means: every symbol on every venue uses FifoMatchingEngine.

The historical-data feed does not exist

alpaca_historical_data_feed.hpp is a zero-byte file. src/md_feed/ now holds only the MDFeed interface — every implementation lives with its venue under src/venues/ — so a replay feed would be a new venue-independent implementation of that same two-method interface.

What it means: historical replay is an intended operating mode — matching client orders against recorded market data — but no feed implements it. Live WebSocket feeds are the only source of market data today.

A md_feeds entry whose type is neither coinbase_live_ws nor hyperliquid_live_ws is silently ignored, which leaves the venue running self-contained. That is a valid mode, so a typo in type degrades quietly rather than erroring — worth checking the startup log if you expected live data and got an empty book.

Exchange::processMdData() and the market-data thread are commented out

Exchange declares md_thread_ and md_feed_, and start() has a commented-out block that would launch a dedicated market-data thread. stop() still joins md_thread_ — dead code, since nothing ever starts that thread.

What it means: market data is processed on the same thread as order requests. See the threading model in Architecture.

Configuration and packaging

The default config filename does not match the committed sample

With no command-line argument, main.cpp looks for slick-sim.json in the current directory (main.cpp:126). The sample that ships with the repo is config/slick_sim.json — different directory, and an underscore rather than a hyphen.

What it means: always pass the config path explicitly: slick-sim config/slick_sim.json.

QuickFIX, SBE and the TCP gateway are built but never used

tcp_order_gateway.cpp, fix_parser.cpp, sbe_parser.cpp, json_parser.cpp and the ~180 generated CME iLink3 SBE headers under src/order_gateway/sbe/ all compile into the order_gateway target, but no exchange ever constructs a TcpOrderGateway. main.cpp still carries roughly 90 lines of commented-out scaffolding (print_protocol_info, per-client protocol selection) from when that path was live.

What it means: the code is still dead, but it is no longer a mandatory build cost. The whole cluster sits behind SLICK_SIM_ENABLE_TCP_GATEWAY, which defaults to ON so existing builds are unchanged. Configuring with -DSLICK_SIM_ENABLE_TCP_GATEWAY=OFF drops those four sources, the sbe/ include path, and the find_package(quickfix CONFIG REQUIRED) that made QuickFIX a hard dependency for code that never runs — so QuickFIX need not be installed at all.

The Doxygen API reference deliberately excludes the SBE headers

Not a defect — a documentation decision worth knowing. Doxyfile.in sets EXCLUDE = src/order_gateway/sbe, because ~180 machine-generated classes would bury every hand-written slick::sim type in the class index.

What it means: generated SBE message classes will not appear in the API reference. Read the headers directly, or the schema at src/order_gateway/sbe/schemas/cme_official/ilinkbinary.xml.