flatten
Close one venue-and-symbol net position with a reduce-only market order sized at apply time — previewed by default, sent only against a confirming call that carries the preview_token.
flatten closes the net position in one (venue, symbol) book. It mints no size of its own: the
quantity is the position, resolved when the intent is applied. Annotated destructiveHint: true
like every write tool, it previews unless a call carries both confirm: true and the
preview_token its own preview returned, and needs vike-cli mcp --node <host:port> plus
VIKE_TRADEHUB_CONTROL_KEY.
Arguments
| Parameter | Type | Required | Notes |
|---|---|---|---|
venue | string | yes | Venue id |
symbol | string | yes | Instrument symbol |
reason | string | no | Rationale for the node's audit trail |
confirm | boolean | no | Must be the JSON boolean true and be accompanied by preview_token — either one missing returns a preview |
preview_token | string | no | The token this tool's preview returned. Fires once, expires after 60s, bound to the command it previewed |
There is no quantity argument, and that is the design rather than an omission. OrderIntent::Flatten
closes the (venue, symbol) net position with a reduce-only market order for |position|,
resolved at apply time, and is a no-op when flat. If you want to take off part of a position,
that is a reduce-only submit_order, not this tool.
The preview
Called without confirm, the write arm builds the command and returns preview_of(...) —
will_execute: false, the resolved wire_command, the advisory guardrail, the node's own
dry-run when there is a node to ask, the echoed reason, the confirm note, and a
preview_token. Nothing is sent, and with no node configured nothing is opened. The guardrail
is Guardrail::NoSize (within_limits: true, "no order size to check for this verb"): the client
cannot size a flatten either, because the position it will close lives on the node.
Closing the position takes a second call carrying both confirm: true and that token.
Either one on its own returns a fresh preview instead — not an error — so will_execute is the
field that tells you whether the flatten was actually issued. The token fires once, expires after
60 seconds and is bound to this (venue, symbol);
Live node has the four rules.
The preview shows the intent, never the resulting order
Because the quantity is resolved at apply time on the node, the wire_command in the preview names
only the venue and symbol. Nothing on this side can tell you how big the resulting market order
will be. node_snapshot is what shows you the position before you
confirm — and the fill afterwards.
What the node does with it
lower_command maps WireCommand::Flatten to the core's OrderIntent::Flatten with an empty
coid. That is the account-wide-verb convention: the acknowledgement has no order id to echo,
because the order does not exist yet. The accepted response says so in its own note — an empty
client_order_id means an account-wide verb.
ControlLimits::vet consumes a rate token and stops there. Its doc lists flatten among the
risk-reducing verbs — cancel, flatten, mass-cancel, market-exit, trading-state — that are never
size-capped. A flatten cannot be refused for being too large, which is the right way round for the
only verb whose whole job is getting smaller.
It works from a halted core
The reduce-only shape is what gets flatten past the kill switch when nothing else gets through.
OrderIntent::MarketExit documents the rule for the compound verb built out of this one: the
RiskGate kill switch admits a position-covered reduce under Halted
(vike_model::is_covered_reduce), and that is exactly the shape a flatten mints — side opposite
the position, quantity |position|. The same property makes it permitted under
TradingState::Reducing. Admission at that arm is all it buys: the rest of
RiskGate::check_inner still runs, so an armed max_notional_per_order or max_total_exposure
denies an oversized flatten in any state, and under Halted the verdict is re-taken on the
lot-rounded size — a |position| of 1.8 on a 1.0 lot rounds up to 2.0, which would flip
the position, so that leg comes back denied with the reason halted.
That correction was made because the reverse had been true for the verb's whole life: the gate
denied every order under Halted, reduce_only included, so the exits were disarmed in precisely
the situations that reach Halted on their own. A kill switch must stop you opening risk, never
trap you in it. Risk and arming sets out the order the gates are
consulted in, and set_trading_state is the tool that moves
the switch.
Outcomes
Server::execute awaits this command's own ticket for two seconds. accepted returns
sent: true with an empty client_order_id; a refusal is an error result carrying the node's
reason; a timeout returns outcome: "unknown"; a dropped control connection is an error result
saying the outcome is unknown and it may have executed. For both ambiguous cases, call
node_snapshot before retrying — a repeated flatten against
a book that is already flat is a no-op, but a repeat issued while the first is still in flight is
not obviously one.
To empty one venue or the whole account rather than one book, use
mass_cancel for the resting orders and
market_exit for both at once.
Flatten the BTCUSDT position on binance.
modify
Change one resting order's quantity, price, or both — previewed by default and executed only against the preview_token that preview returned, and refused at a node with a notional ceiling when a quantity increase names no price.
market_exit
The panic button — cancel every live order, then flatten every open one-way position (hedge-mode legs are skipped), optionally scoped to one venue; two-call gated like every other write tool, on confirm plus a preview_token.