Closing a spot position is not a singleDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/backtest-kit/wallet-manager/llms.txt
Use this file to discover all available pages before exploring further.
SELL order — it is a three-step sequence that must be followed in strict order. On Binance spot, any open order locks the asset it needs: a resting SELL holds the base coin, and a resting BUY holds USDT. Sending a new SELL on top of those locks either triggers an insufficient balance rejection or silently fills for less than the full position. commitCancel (and the onOrderCloseCommit hook in the broker adapter example) avoids both failure modes by cancelling every open order on the symbol and confirming the book is empty before a single exit order is placed.
The Three Steps
Cancel all open orders
The function fetches The design intentionally tolerates partial failures per round. A cancel
that races with an exchange-side fill (the order is already gone when the
cancel arrives) returns an error, but the next round’s
binance.openOrders(symbol) and cancels each order
one by one, sleeping 1 second between cancels to avoid rate-limit bursts.
Individual cancel errors are tolerated — if one cancel fails, the loop
marks the sweep as incomplete (isOk = false) and continues to the next
order. The outer loop retries the entire sweep up to 10 rounds. Only
after a full round with no remaining orders (or no errors) does the sweep
exit. If the last round still produced an error, that error is re-thrown
as a transient failure.openOrders fetch
will simply not include that order — so the sweep converges regardless.Verify the book is clean
Even after every cancel call returns successfully, the exchange may take a
moment to settle. The verify loop polls This second check is not redundant. It guards against race conditions where
the exchange acknowledges a cancel but the order remains visible in
binance.openOrders(symbol) up to
10 times (1-second sleep before each poll). As soon as the list is
empty, it clears the error and breaks. If any orders are still present
after 10 polls, it throws "Order not canceled" as a transient failure —
the broker adapter engine will retry the entire close.openOrders for a brief window, and it also catches cases where a cancel
silently failed without throwing. The try/catch inside the loop tolerates
transient openOrders fetch errors — a network blip during verification
sets error and retries rather than propagating immediately.Sell the entire free balance
With the book confirmed empty, The function returns the average fill price on success, or
commitCancel reads the free coin balance
from binance.account(), subtracts the maker fee and one minQty unit as
a safety buffer, and places a LIMIT SELL at price × 0.999 — slightly
below market to favour a fast fill. From here the standard
poll-with-timeout fallback takes
over: poll every 10 s for up to 10 rounds, then cancel and market-sell the
remainder if the limit has not filled.0 when there
is nothing to sell (see dust handling below).Dust Handling
Before placing the sell, the function checks whether the computedquantity is at or below the exchange’s minQty filter:
0. This happens when the free balance is so small (dust) that it cannot form a valid order — the position is effectively already exited. The broker adapter treats a 0 return as a clean close confirmation.
Why Sell the Entire Free Balance
commitCancel does not sell just the engine’s tracked position size — it sells the entire free coin balance. This is intentional. A real account may hold fractions of the coin bought outside the engine (orphan tranches from manual orders, leftover fills from previous sessions). Selling the full free balance ensures the exit is total regardless of what else is on the account, and it sweeps those orphans in the same transaction rather than leaving residue that would trigger the insufficient balance guard on future buys.
The same logic applies in onOrderCloseCommit in the broker adapter example:
Position close follows the commitCancel flow: cancel every open order on the symbol, then sell the entire free coin balance to cash — which also sweeps any orphan tranches bought outside the engine.