Every error that crosses the broker boundary — from a flaky network hop to an exchange verdict — is classified before it reaches the trading engine. The classification is binary and permanent: an error is either transient (retry it) or rejected (do not retry, ever). Getting this wrong in either direction is expensive. This page covers the two error types, how they are mapped from ccxt exceptions, theDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/backtest-kit/ai-trading-mcp/llms.txt
Use this file to discover all available pages before exploring further.
cancelOrderSafe fill-race handler, and the HTTP envelope that carries all of this to the agent.
Two Error Categories
The trading engine in backtest-kit defines two error classes that the broker adapter uses to signal outcome:OrderTransientError
Network errors, rate limits, and temporary exchange unavailability. The engine treats these as recoverable and retries with bounded backoff up to
CC_ORDER_CLOSE_RETRY_ATTEMPTS. The position remains open while retries are in flight.Examples: RequestTimeout, ExchangeNotAvailable, DDoSProtection (all ccxt.NetworkError subclasses).OrderRejectedError
Exchange verdicts — bad parameters, insufficient funds, invalid symbol, duplicate order ID. These are permanent. Retrying wastes time and can make the situation worse. The engine does not retry these; it records the rejection and moves on.Examples:
InsufficientFunds, InvalidOrder, BadSymbol, min-notional violations (all ccxt.ExchangeError subclasses).Mapping ccxt Exceptions
ThetoTypedError function in modules/live.module.ts is the single translation point. Every exception that escapes the adapter passes through it:
NetworkError covers anything that went wrong before the exchange processed the request; ExchangeError covers anything the exchange explicitly rejected. Anything that is neither — an unexpected runtime exception — passes through unwrapped so it surfaces as a raw error with its original stack trace.
The cancelOrderSafe Fill-Race Handler
Cancelling an order that filled in the window between the last status poll and the cancel call is not an error — it is a fill. Binance signals this with -2011, and a naive adapter that propagates any cancel exception as a failure has just dropped a real entry.
cancelOrderSafe handles this race explicitly:
"closed", the order filled — return "filled" to the caller, which then confirms the entry and places brackets. If it is anything else, re-throw the original cancel error typed through toTypedError. The fill-race path is handled in both the entry phase (buyLimitGuaranteed) and the close phase.
The Original Error Always Reaches the Engine
In the close flow, the adapter runscancelSweepAndVerify before attempting the sell. If the sell itself fails, the sweep has already happened — brackets are down, the book is clean, and the engine’s soft-SL takes over for the next tick’s retry. The error that caused the failure is re-thrown through toTypedError, so the engine receives a properly typed verdict:
unwindPosition. When bracket placement fails and the adapter needs to emergency-unwind, it runs cancel-sweep → verify → market-sell, but always re-throws the original error — the error that caused the bracket failure, not any error from the unwind itself. If the unwind also fails, it is silently swallowed so the original verdict still reaches the engine typed correctly.
The HTTP Error Envelope
The MCP bridge (@backtest-kit/mcp) wraps every operation result in a flat envelope before returning it to the stdio MCP server:
error string means the operation succeeded. A non-empty error string is the engine’s exact message, relayed to the agent as an isError: true MCP tool result.
HTTP 200 does not mean the trade succeeded. The HTTP layer only confirms that the bridge received and processed the request. Always inspect the
error field in the response envelope — a 200 with a non-empty error means the trade was rejected or failed.What the Agent Sees
The agent receives error information as structuredisError MCP tool results. The message is the engine’s exact error string — not a generic wrapper. This means the agent can read the rejection reason and reason about it:
| Scenario | Error the agent sees |
|---|---|
open_position on a symbol not in CC_SYMBOL_LIST | Engine rejects with a clear “symbol not in whitelist” message before the broker is called |
open_position when a position or pending signal already exists for that symbol | Duplicate-open rejection — engine validates before the broker touches the exchange |
close_position when nothing is open | ”nothing to close” error — returned before any cancel or sell attempt |
Network error during Telegram get_status fetch | The 90-second timeout tears down the GramJS connection; agent sees "timeout fetching feed messages" and the connection rebuilds on the next call |
Insufficient USDT to meet minNotional | OrderRejectedError from the broker: "free USDT X.XX → cost Y.YY < minNotional Z (SYMBOL) — вход пропущен" |
| Short position requested on spot | OrderRejectedError: "SpotBrokerAdapter: short position is not supported on spot" |