slick-sim is under active development. The limitations below include unfinished code paths and
behaviour that differs from the intended design, with source references and consequences for clients.
There is no CME, Eurex or ICE adapter, although Venue::CME and Venue::ICE exist in
types.hpp. 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.
The processing 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.
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.
Batch handlers poll once per entry, which compounds the delay:
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.
Symbol::onOrderUpdate fills md_order_update_cache_ for every venue, and
Exchange::flushMarketDataCaches publishes it as an ORDER frame - but only for an adapter that sets
publish_order_updates_, and no in-tree adapter does. This used to be a gap for every venue: the
cache was cleared behind a // TODO: publish MD order update and nothing reached md_queue.
What it means: Coinbase and Hyperliquid clients still receive level data only. Their adapters clear the cache on their own feed paths too, so turning the flag on for them would need those paths to publish rather than clear.
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.
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.
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.
An unrecognised md_feeds[].type is silently ignored, leaving the venue
self-contained. If you expected
live data but see an empty book, check the feed type and startup log.
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.
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.
fix_parser.cpp,
json_parser.cpp
and the MessageParser factory are an older generic parsing layer that nothing constructs at
runtime. They sit behind SLICK_SIM_ENABLE_FIX_JSON_PARSERS, off by default, in their own
slick_sim_message_parsers target - the only thing in the project that needs QuickFIX. main.cpp
still carries roughly 90 lines of commented-out scaffolding (print_protocol_info, per-client protocol
selection) from when that path was live.
TcpOrderGateway itself is no longer part of this: it was rebuilt as a transport base for venues on
a binary session protocol, and needs only slick-socket. The generated SBE headers and schema that
used to live under src/order_gateway/sbe/ were removed.
What it means: QuickFIX need not be installed unless you turn the parsers on.