Bots — overview
Bots are LEXX’s flagship feature: strategy engines that run server-side, 24/7, placing and
managing orders for you. The API gives you full control — create, configure, start, stop,
command and monitor — everything the Terminal UI can do. Managing bots requires a
trade-tier key. The terminal and the knowledge base call bots “strategies” — same
thing; these docs say “bot”, matching the API.
The seven types
| Type | In one line | Trades? |
|---|---|---|
FIBO |
Fibonacci grid built for the “Aftershock” strategy — buys on retracement levels, sells one level up, averages into breakeven | ✅ |
CHANNEL |
Horizontal price channels — three flavors: GRID (order grid), LEVELS (manual levels), SCALE (Fibonacci-hybrid template) | ✅ |
SQUEEZE |
Squeeze (price-drop) trading — buys a sharp drop from the previous candle’s binding, sells on the bounce | ✅ |
INTRADAY |
Bounce or breakout of a level/trendline you define, with laddered take-profits | ✅ |
ALGO |
Named execution primitives: bracket, TP/SL pair, TWAP | ✅ |
ALARM |
Price/RSI alert — fires a notification when a level is crossed | 🔕 service |
NOTIFY |
Market scanner — notifies when pairs match volume/price filters | 🔕 service |
Lifecycle
POST /bots → bot created (status NEW)
POST /bots/{id}/runs → running (status STARTED, runId assigned)
…POST /bots/{id}/runs/{runId}/message — command it while it runs…
DELETE /bots/{id}/runs/{runId}?cleanup=… → stopped (STOPPED / ERROR)
PATCH /bots/{id} → reconfigure (only while stopped)
DELETE /bots/{id} → gone forever (history included)
In code, with the reference client:
// create → start → … → stop
const { data: bot } = await lexx.request("POST", "/bots", {
body: { type: "FIBO", settings: { /* per-type config — see the type's page */ } },
headers: { "X-Idempotency-Key": crypto.randomUUID() },
});
const { data: started } = await lexx.request("POST", `/bots/${bot.id}/runs`, {
headers: { "X-Idempotency-Key": crypto.randomUUID() },
});
console.log(started.status, started.runId); // "STARTED", current run id
// … live events arrive on the WebSocket `botStatus` channel …
await lexx.request("DELETE", `/bots/${bot.id}/runs/${started.runId}`, {
query: { cleanup: "cancelAllOrders" },
headers: { "X-Idempotency-Key": crypto.randomUUID() },
});
Key rules:
- Statuses:
NEW(never started) →STARTED→STOPPED/ERROR. While running the bot carries arunId— a new one for every start. - Stopping options:
cleanup=leaveAllOrders|cancelBuyOrders|cancelAllOrders;action=stop_and_selladditionally market-sells remaining position;force=truestops a run stuck in a transient state. - Reconfiguring:
PATCH /bots/{id}with{config, options?, metadata?, restart?}works only on a stopped bot (409otherwise);restart: truestarts it again right after the update. The response contains{bot, old}— the new and previous versions. Note:X-Idempotency-Keyis not honored on PATCH. - Deleting: running bots must be stopped first, or pass
force=trueto stop all runs and delete in one call. - Listing:
GET /botsreturns all your bots, filter only by?type=; not paginated.
Batch operations — up to 20 bots per request
| Call | Body |
|---|---|
Start many — POST /bots/batch/runs |
{ "bots": ["id1", "id2", …] } |
Stop many — DELETE /bots/batch/runs |
{ "bots": [["botId","runId"], …], "cleanup"?, "action"?, "force"? } |
Delete many — DELETE /bots/batch |
{ "bots": ["id1", "id2", …] } |
The response is always an array of per-bot results {id, success, runId?, error?, bot?} —
one entry per requested bot, successes and failures mixed. Batch calls count as one request
against the rate limit.
The create request
{
"type": "FIBO", ← authoritative bot type
"settings": { … }, ← per-type config; settings.exchange is REQUIRED
"options": { … }, ← optional runtime options (type-specific)
"metadata": "…", ← optional string for your own tracking
"tag": "scalping" ← optional label ≤ 50 chars, for grouping
}
- The top-level
typeis authoritative; atypeinsidesettingsis optional and, if present, must match. - The gateway validates only
typeandsettings.exchangeat creation — deep validation happens when the bot is built/started, so a config mistake typically surfaces as a start failure orERRORstatus, not as a400on create. - If your API key has the
allowedExchangesrisk shield, it is enforced on the exchange inside botsettingstoo.
Runtime commands — POST /bots/{id}/runs/{runId}/message
Talk to a running bot without stopping it. Body: { "message": "...", "params": { … } }.
| Command | params |
|---|---|
add_stoploss / update_stoploss |
A stop-loss object — required: strategy (e.g. breakeven, price_percent_buy), trigger ({type: "kline" | "exchange", value?}), order (e.g. {type: "STOP_MARKET", stopPrice: "…"}); optional: disabled, complete, snoozeDuration, maxStops. A stop whose trigger has already fired is rejected |
delete_stoploss |
Same shape — the bot matches the existing stop to remove |
A successful command returns data: null — effects show up in the bot’s behavior and the
botStatus stream.
Config building blocks
The per-type pages document only their specific fields; these blocks are shared.
Which shape does each type use?
Watch the differences — they are the #1 source of config errors:
| Type | symbol form |
timeFrame units |
Trader fields (deposit/orders/stop) |
|---|---|---|---|
| FIBO | object {base, quote} |
milliseconds | ✅ |
| CHANNEL | object {base, quote} |
milliseconds (optional — omitted = every tick) | ✅ |
| SQUEEZE | object {base, quote} |
milliseconds | ✅ |
| INTRADAY | object {base, quote} |
— (no timeFrame field) | ✅ |
| ALGO | string "BTCUSDT" |
minutes | ✅ |
| ALARM | object {base, quote} + ticker |
milliseconds | — (service bot) |
| NOTIFY | string (optional — omit to scan all pairs) | milliseconds, required, minimum 300000 (5 min) | — (service bot) |
deposit — how much capital the bot may use
{ "value": "500", "asset": "USDT", "strategy": "fixed", "allocate": "auto" }
| Field | Meaning |
|---|---|
value |
Amount (string). Interpreted per strategy |
asset |
Denomination — quote asset on spot, margin asset on futures |
strategy |
fixed — value is an absolute amount · percent — value is a % of available balance (optionally bounded by min/max) |
allocate |
reserve — reserve the balance · auto — automatic · no_reserve — no reservation |
stop — stop-loss rules (single object or array)
{ "strategy": "breakeven", "disabled": true, "complete": true,
"trigger": { "type": "kline" }, "order": { "type": "MARKET" } }
| Field | Meaning |
|---|---|
strategy |
Rule identifier. Values seen across verified configs: breakeven, price, price_percent_buy, trend_percent_price, timeout_buy, fixed, trailing |
trigger.type |
kline (candles) · exchange (exchange price feed) · time (at a timestamp) · timeout (after a duration) |
trigger.value |
Level in Fn.Format or a duration for timeout |
trigger.edge / .closed / .timeFrame |
Candle edge; use closed candles only; candle timeframe |
order |
Exit order: {type: MARKET | LIMIT} (INTRADAY extends it with stopPrice, workingType, correction, …) |
complete |
true = closes the whole position |
disabled |
Rule defined but inactive (common pattern: a pre-configured breakeven you enable later via update_stoploss) |
snoozeDuration / maxStops |
Cooldown (ms) between firings / max firings per run |
orders — how the bot places its buy/sell orders
{ "buy": { "type": "LIMIT", "price": 0.3, "size": 1 },
"sell": { "type": "LIMIT", "price": 0.5, "priceStrategy": "fixed" } }
type LIMIT/MARKET; price — typically a % offset for limit orders (0/omit = market);
size — lots per order; priceStrategy (sell side only) — fixed | dynamic | trailing.
timeout — lifetime and inactivity rules (single object or array)
{ "type": "ttl", "value": 604800000 }
value is milliseconds. The engine recognizes only type: "ttl" (total lifetime;
7 d = 604800000, 30 d = 2592000000) — any other type string is silently ignored
(per the spec), so there are no timeout-level “inactivity actions”.
filters — entry conditions (all must pass)
{ "volume": { "minVal": 500000, "percentChange": 5 },
"price": { "minVal": 0.01, "percentChange": -3 },
"market": { "rsi": 65, "trend": "up" },
"blacklist": ["LUNAUSDT", "USTUSDT"] }
Fn.Format — price lines
LEXX encodes levels and trendlines as an object with exactly two {timestamp: price}
entries — two points define a line:
{ "1743300000000": 94.08, "1746000000000": 96.50 } ← rising trendline
{ "1743300000000": 60, "1746000000000": 60 } ← horizontal level at 60
Keys are string Unix-millisecond timestamps; values are numbers. Used in stop-loss triggers, ALARM ranges and the INTRADAY trend.
Common trader fields
profitAsset (accumulate profits in this asset), short (futures short-selling),
positionSide (LONG/SHORT/BOTH — hedge mode),
marginType (ISOLATED/CROSSED), onetime (stop after the first full cycle),
noticeLevel (notification verbosity 0–3), name (display name), range (price-range
constraints, {type, min?, max?} — types seen: price, kline_percent, scaled_price,
trend_percent_price).
Reading a bot back
PublicBot is an open object: guaranteed are only id, type, exchange. status and
runId appear per lifecycle; the config may live under settings, config or options
depending on the bot; there is no guaranteed createdAt/updatedAt. Internal fields
(userId, credentials, worker info) are stripped and never present.
Live lifecycle events (BOT_STARTED, BOT_STOPPED, BOT_ERROR, BOT_UPDATE) stream on the
botStatus WebSocket channel. Orders placed by
a bot are marked creator: "BOT" and their IDs end with _{runId}
(Orders).
Verified against API spec v2.0.0 · 2026-07-11