vike
Concepts

The ExecutionClient seam

The one trait every venue bridge implements — only submit and cancel are required, every state change comes back as an event, and one method exists precisely so a venue never overrides it.

Every venue bridge implements one trait: vike_exec::ExecutionClient, declared in crates/vike-exec/src/execution_engine/client.rs and isolated from the engine so the contract reads on its own. The engine holds it type-erased — Box<dyn ExecutionClient + Send> — and never knows which venue is behind it. That is the whole point: the core, the risk gate and the order state machine are written once, and a venue's job is to translate. Each page under venues is one implementation of what this page describes.

Two methods are required

fn submit(&mut self, request: &OrderRequest);
fn cancel(&mut self, client_order_id: &str);

They are the only order-mutating calls that leave the core, and both are fire-and-forget. Neither returns a result, because the venue's answer does not arrive on this call — it arrives later, as an event, through the ingest lane the adapter was handed at construction. That is true of every method on the trait: every state change comes back as a venue event, never as a return value.

Everything else has a default

methoddefault behaviourwho overrides it, and why
cancel_with_intentdrops the CancelIntent and calls cancela venue that meters its cancels. The engine calls this, never cancel, so an override sees every cancel the core issues
modifyno-op — the order keeps its termsa venue with an in-place amend. Takes the whole resting OrderRequest, not just its id, because some venues need side, quantity and price together
submit_batch / cancel_batchfan out to the per-order calla venue with a native batch endpoint
cancel_batch_with_intentdrops the intent and calls cancel_batcha venue that wants the whole batch, with its intent, as a single unit; Polymarket is the only declaration in the tree, for the latency of an emergency flatten
confirmno-opa venue with an order-status re-query. The stuck-order watchdog is its caller; on a client that cannot re-query, the watchdog's last-resort reject still backstops it
detachno-opadapters with something to release
poll_eventsNonein-process test clients only — a real adapter's events arrive over the ingest channel
on_barno-opthe paper exchange, which fills its resting book on each closed bar before the strategy runs
amend_semanticsNone — "ask the venue table"the paper exchange only, see below

Two contracts hide in that table. confirm must run its re-query off the core fold thread, on the adapter's own thread — it is the watchdog's way of prodding a wedged adapter into answering rather than only waiting on it, and a blocking re-query here would stall the fold it exists to protect. And the boxed impl delegates every method explicitly, because trait-object defaults would otherwise shadow a concrete client's overrides: a boxed intent-aware client that fell through to a default would silently report every cancel as unclassified.

Why a cancel carries an intent

CancelIntent is the one cross-venue classification of why a cancel is being issued: Routine (maker ladder churn), RiskOff (a declared flatten, market exit, dead-man trip or safe-state sweep), or Unspecified. It exists because a venue-side cancel budget is a real liveness hazard — a maker that spends its whole cancel bucket on requote churn is rate-limit-locked at the moment it needs to pull a book — and holding a reserve back is only possible if the shedding decision can tell the two apart.

Unspecified is the flatten-safe default, not a don't-care

A caller that cannot classify its cancel gets the treatment an emergency gets — fire it, never hold it back — because the failure modes are not symmetric: shedding a routine cancel costs one stale quote for one refill window, while shedding an emergency one leaves a live order resting in the situation the operator was trying to get out of. CancelIntent::may_be_shed is the one place that says only Routine may ever be held back, so a venue never re-derives the rule from a match and is never one _ => arm from inverting it. A venue that overrides cancel_with_intent must write cancel in terms of it, or a direct cancel call silently skips the override.

Where the adapter runs

The trait is synchronous and the venue is not, so an adapter owns a thread. The shared shape is crates/vike-bridge-core/src/exec_actor.rs's ExecActor: an mpsc channel plus a dedicated OS thread draining submit, cancel and shutdown, with a Drop/detach that sends shutdown and joins the thread — deterministic teardown, because a stranded feed thread is a crash on process exit. Each venue supplies only its command loop, and for the REST-driven crypto venues even that loop is shared. Venue replies are pushed back to the core with EventSender::blocking_send. Dukascopy is the one adapter that owns a child process instead — there is no REST execution API for that venue, so a Java sidecar drives execution over JSON lines on stdio. In both cases the core thread never waits on the wire.

The actor is also where the operator halt bites first: submit and modify consult the halt file sentinel before touching the command channel. While it exists, a submit the caller has not flagged reduce_only is refused with a synthesized terminal OrderRejected, and a reduce_only submit is admitted — and logged, because this boundary holds no position book and can only trust the flag. modify is blocked either way, since it can add size or chase price and reduce-only cannot be inferred from the request alone; nothing is synthesized there, the resting order simply keeps its terms. A cancel is deliberately never gated, and a cancel alone would not discharge the rule: it removes a resting order, whereas leaving a position means sending one, so a halt must let an operator reduce or exit, never trap a position. It works even when the runtime is wedged.

The emitter split

Who emits which event is fixed, not a per-venue choice. The Rust side emits OrderSubmitted synchronously at submit. The venue side emits OrderAccepted, OrderRejected, OrderCanceled, OrderFilled and OrderPartiallyFilled. A dead venue path — a transport that died mid-order, a sidecar that never answered — must synthesize a terminal OrderRejected rather than let the order vanish. The order lifecycle page carries the three rules and the harness that machine-checks them against every venue's real decoder.

The one method a venue adapter should never override

amend_semantics answers "what does my modify's quantity mean" — the new total, or the new remaining size — which is the fact ExecutionEngine::modify_order needs before it may net a partially filled order's executed lots out of the pre-trade projection. None means "consult vike_model::amend_semantics for the engine's venue string", which is the right answer for every real venue, because that table is where the per-venue fact is declared and tested.

It exists because the engine's venue string is a label, not a client. vike_mount::make_engine builds the engine with the real venue id even when the arming ceiling or the absent-credentials gate fell back to vike_paper::PaperExecutionClient, so a paper mount on a venue whose amend is in-place-total looks up that row while the paper book's modify assigns the amend quantity to the order's remaining size — netting there would let the gate assume q − filled while the book executes q. Declaring the convention on the client means it travels with the object that implements it and no mount can forget to pass it. The paper exchange is therefore the only override in the tree, and the method's own documentation says so.

On this page