Strategy reference
Every parameter of all five strategy families — Grid, DCA, Indicator, Trailing Stop and Custom — what the engine does with each one, and the behaviours that surprise people.
Five strategy families ship. Each one is a class the engine constructs from the
bot's strategyConfig and asks for a signal on every tick. This page is the
parameter reference; the schema below is exactly what
GET /api/trading-bot/strategy-schema returns and exactly what the create
endpoint validates against.
Configuration is validated when a bot is created and again when its config is
edited. Fields marked required below are refused if absent, with the message
Missing required field: <name>.
Grid — GRID
Places a ladder of rungs across a price band. Buy rungs below the market, sell rungs above it, and the spread between adjacent rungs is the profit per fill.
| Field | Type | Required | What it does |
|---|---|---|---|
upperPrice |
number | yes | Top of the band |
lowerPrice |
number | yes | Bottom of the band. Must be below upperPrice |
gridCount |
number (2–100) | yes | Number of levels |
amountPerGrid |
number (≥1) | yes | Order size at each level |
gridType |
arithmetic | geometric |
yes | Even price steps, or even percentage steps |
initialBuy |
boolean | no | Buy once at start so the bot holds inventory to sell into |
sellAllOnStop |
boolean | no | Liquidate the ladder's inventory when the bot stops |
restingOrders |
boolean | no | false makes the bot take with marketable limits instead of resting on the book |
Two behaviours are worth knowing before someone reports them as bugs.
The ladder builds one rung per tick. A 25-level grid takes about 25 ticks — a little under two minutes at the default 5-second interval — to be fully placed. Rung state is read back from the bot's own order and trade rows on every tick rather than held in memory, so a rung whose order was refused is retried rather than silently lost, and a rung that filled can never be placed twice.
A grid can look like it only buys. A sell rung is only considered when the bot
actually holds inventory to sell. A new grid starting in the upper half of its band
works downwards to the buy rungs first; enable initialBuy if you want it to hold
inventory from the outset. If price has left the band downward, the bot is fully
invested and there is nothing above the market to sell into.
gridCount × amountPerGrid must fit inside the allocation, and each rung's
notional must clear Minimum Trade Amount. A grid with many small rungs on a
small allocation is the single most common cause of "my bot never trades".
DCA — DCA
Buys on a schedule, optionally gated by a price filter.
| Field | Type | Required | What it does |
|---|---|---|---|
interval |
hourly | daily | weekly | biweekly | monthly |
yes | The cadence |
intervalHours |
number | no | Custom hour count, used with hourly |
amount |
number (≥1) | yes | Size of each buy |
amountType |
fixed | percentage |
yes | A flat figure, or a share of the allocation |
maxBuys |
number | no | Stop after this many buys |
priceCondition.enabled |
boolean | no | Turn the filter on |
priceCondition.type |
below_ma | rsi_oversold | below_price |
no | Which filter |
priceCondition.value |
number | no | The filter's threshold |
The schedule is reconstructed from the bot's own trade history at startup — the last buy and the total buy count both come from the rows — so a restart does not reset a DCA bot's cadence.
The schedule only advances when a trade actually executes. A buy that the
price filter held, or that the risk manager refused, does not consume the interval
and does not consume a maxBuys slot. All three filters hold rather than pass
when candle data cannot be read, which is the safe direction.
Indicator — INDICATOR
Enters and exits on technical indicators computed over closed candles.
| Field | Type | Required | What it does |
|---|---|---|---|
timeframe |
1m | 5m | 15m | 1h | 4h | 1d |
yes | Candle size every indicator is computed on |
indicators |
object | yes | The indicator set — at least one must be enabled |
signalMode |
any | all |
yes | Fire when any indicator agrees, or only when all do |
entryAmount |
number (≥1) | yes | Position size on entry |
exitMode |
indicator | take_profit | both |
no | What closes the position |
The indicator set:
| Indicator | Parameters |
|---|---|
rsi |
enabled, period, overbought, oversold |
macd |
enabled, fastPeriod, slowPeriod, signalPeriod |
bollingerBands |
enabled, period, stdDev |
ma |
enabled, type (SMA/EMA), period, crossType (price_cross/ma_cross), secondPeriod |
The in-progress candle is dropped before any indicator is computed, and every comparison is made against the previous closed bar. That is what makes a crossing signal mean anything — an implementation that compared live values would fire on every tick while one line sat above another.
The practical consequence: on a 1-hour timeframe an indicator bot produces at most one signal per hour regardless of the 5-second tick. Use a shorter timeframe if you want faster reaction, and expect more noise.
Trailing Stop — TRAILING_STOP
Enters once, then follows the price up with a stop that never moves down.
| Field | Type | Required | What it does |
|---|---|---|---|
trailPercent |
number (0.1–50) | yes | How far behind the peak the stop sits |
activationPercent |
number (≥0) | no | Profit the position must reach before the trail arms |
entryMode |
market | limit |
yes | Entry order type |
entryAmount |
number (≥1) | yes | Position size |
entryCondition.type |
immediate | indicator | price_level |
no | When to enter |
entryCondition.value |
number | no | The condition's threshold |
Until the position is up by activationPercent, the trail is not armed and the
ordinary stop-loss is what protects it. The peak only ever ratchets upward, so the
stop can never drift down — a position that falls straight from entry is closed by
the stop-loss, never by an un-armed trail.
The terminal draws the entry, the peak and the chasing stop, which is the fastest way to see where the stop actually sits.
Custom — CUSTOM
Runs the IF/THEN rules produced by the Strategy Builder.
| Field | Type | What it does |
|---|---|---|
rules |
array | The builder's rule list — the format the product actually writes |
symbol |
string | The market the strategy was designed against |
timeframe |
string | The candle size conditions are evaluated on |
riskManagement |
object | stopLoss, takeProfit, maxPositionSize carried with the strategy |
nodes / connections / entryAmount |
array / array / number | The legacy node-graph format, still accepted |
CUSTOM has no fixed required list because it accepts either format; the
validator checks whichever one was supplied. See the
Strategy Builder for the rule shape and every
message the validator can produce.
Order types
A strategy declares each signal as MARKET or LIMIT. Both can be disabled
platform-wide on the settings screen, and a disabled type is refused before any
other risk check runs — it needs no database round trip and could never become
allowable further down.
A LIMIT signal with a usable price rests on the book by default. Setting
restingOrders: false in the strategy config forces marketable limits instead,
which is a legitimate order type — it just is not what a grid wants, since a grid's
sell rung is the half of the strategy that books the spread.
Validating before you save
The Algo panel mirrors these schemas client-side so a user sees the error before the request is made, but the backend check is the one that counts.