Creating and configuring a market
The four-step create wizard, every field it writes and the validation that rejects it, plus the price controls that only appear afterwards — price mode, bias, forced phase, volatility and the target price.
A market maker attaches one-to-one to a market, and a market is a venue plus
an id — the pair (marketType, marketId) carries the unique index. A second
attempt on the same market is refused with "AI Market Maker already exists for
this market"; the same pair on the other venue is a different market and is
allowed.
Creation happens in a four-step wizard at /admin/ai/market-maker/market/create. Everything else — price mode, bias, phase, volatility, the running-state controls — lives on the market's own Configuration tab afterwards.
Choosing the venue
Step 1 asks which venue first, because it decides which markets exist. The selector appears only when this install has both.
| Ecosystem (spot) | Futures (perpetual) | |
|---|---|---|
| What the pool holds | Base and quote inventory | Quote currency only, as margin |
| What a fill does | Moves both balances | Opens or closes a position |
| Who owns the orders | Each bot, settled against the pool | One shared pool account |
| Leverage | None — orders are funded, not margined | 1x by default, up to 125x |
| Rebalance | Available | Refused — there is no ratio to rebalance |
| Liquidation | Not possible | Yes, on the same terms as any trader |
That last row is the one to read twice. A futures maker posts real margin and holds a real position, so a large enough adverse move liquidates it exactly as it would liquidate a customer. That is deliberate: the money is the operator's either way, and an unliquidatable position would accrue a loss nothing bounds.
It buys when the market sells to it and sells when the market buys from it, so at any moment it is carrying inventory in whichever direction the market last ran — and that inventory is marked against a price it does not control. The maker closes a position before it opens the opposite one, so it never holds a long and a short at once, but it does hold one of them. Size the pool for a move against you, not for the spread you expect to earn.
Leverage
A futures market maker has one extra field, on step 3: futures leverage.
It ships at 1x, which is a risk decision rather than a placeholder. At 1x the pool posts the full notional of every order and the position cannot be liquidated by anything short of the price reaching zero. At 10x a 10% adverse move takes the whole allocation. Raise it deliberately or not at all.
The field is hidden on ecosystem markets and the endpoint refuses it there, because a maker on that venue funds its orders from pool balances rather than posting margin — there is nothing for a leverage to divide.
What creation actually produces
One request writes four things in a single transaction:
- the market maker row, status STOPPED;
- a pool with zero balances on both sides;
- six bots, all PAUSED — Scalper Alpha, Scalper Beta, Swing Trader, Accumulator, Distributor and Market Maker;
- a
CONFIG_CHANGEhistory entry recording the parameters it was created with.
Nothing trades. The market is created stopped on purpose: it has to be funded and started deliberately.
Step 1 — Select market
The list offers markets on the selected venue that do not already have a market maker. The target price field is pre-filled from the market's own metadata price where one exists.
Step 2 — Price settings
- Pick the ecosystem market to attach to
Two inputs: a target price and a range, expressed as a percentage either side of it. The wizard converts that percentage into the two absolute bounds the API stores.
priceRangeLow = targetPrice x (1 - percent/100)
priceRangeHigh = targetPrice x (1 + percent/100)The API validates the result, and every one of these is a hard refusal:
| Rule | Message |
|---|---|
| All three prices above zero | "Target price must be greater than 0" |
priceRangeLow < priceRangeHigh |
"Price range low must be less than price range high" |
| Target inside the range | "Target price must be within the price range" |
| Deviation from target at most 50% on each side | "Price range deviation from target must be within 50%" |
No value below 0.00000001 |
"Price values must be at least 0.00000001 (8 decimal places)" |
The price process is contained inside the band you configure — but the restoring force only engages in the outer 20% of it. Once it is pulling, the direction of the next move becomes progressively predictable, which on a synthetic market is an exploitable edge and on one that settles binary options is a free bet.
That is why the dashboard reports at range edge as its own state rather than lumping it in with "inside the band": you can still fix it by widening the range, before the price actually leaves. A range too tight for the configured volatility also produces a startup warning in the server log naming the edge an observer would hold.
The band is measured geometrically, not linearly. On a range of 1 to 100 the centre is 10, not 50.5 — so a market sitting exactly on its anchor reads as centred rather than as 90% of the way to the floor.
Step 3 — Trading configuration
Aggression is a 1–10 slider that maps onto the three levels the API stores:
| Slider | Level | Bot risk tolerance | Bot average order size | Bot daily trade budget |
|---|---|---|---|---|
| 1–3 | CONSERVATIVE |
0.3 | 50 | 1,200 |
| 4–7 | MODERATE |
0.5 | 100 | 3,500 |
| 8–10 | AGGRESSIVE |
0.8 | 200 | 9,000 |
Those figures seed the six bots at creation and are editable per bot afterwards. They are sized from measured density, not from nominal arithmetic: a MODERATE market prints roughly 3,500 trades a day, one every 25 seconds on average, about every 10 seconds across the US/EU overlap and every 35 seconds overnight.
Older builds seeded 50/100/200 daily trades, which a market spent inside the
first hour of each UTC day and then printed nothing at all until midnight. The
allowance is now paced across the whole day along an activity curve, so an old
market with an old number is strictly better off — the same allowance simply
lasts until midnight. If it reads as abandoned rather than conservative, raise
maxDailyTrades on its bots.
Real Liquidity (0–100) is the share of each trade's size that is placed as a genuine resting order in the Ecosystem book, backed by the pool. Zero means bot-to-bot only — a price series and a tape with no user-fillable orders and no money at risk. Start there.
Max daily volume is a hard trade gate, not a target. A market that reaches it
stops quoting until the daily UTC reset. Its counter is currentDailyVolume, and
it is zeroed by the daily reset job — so it is volume today, never a rolling 24
hours.
Step 4 — Review
The review step restates what will be created. Creating writes the transaction described above and lands you on the market's detail screen.
After creation: the Configuration tab
- Open the market row from the Markets list
These controls do not exist in the wizard. Each is its own endpoint, each writes a history entry, and — since v6.1.5 — each applies to the running market immediately rather than at the next full reload.
Price mode
- AUTONOMOUS — the price process alone. No external reference.
- FOLLOW_EXTERNAL — tethered to an external symbol at
correlationStrength(0–100). - HYBRID — the same tether at half that strength. It is a blend, not a peg.
externalSymbol is required for both non-autonomous modes; the request is
refused without it, and a symbol the active exchange provider does not list is
refused too. The external price is fetched on a slow cached cadence, off the
price step, so a slow exchange can never stall a tick.
On an autonomous market the range is absolute bounds: the price stays inside that box.
On a following market it is a tracking tolerance. The band is re-centred on the reference at the width you configured and travels with it, so the width is how far price may stray from the reference — a narrower range tracks more tightly, a wider one lets the market keep more of its own character. You do not need to widen it to "cover" where the reference might go.
The tether is still a slow restoring drift rather than a peg: at 100% strength it takes about a day to close half of any remaining gap. Full detail in Following an external price.
The first time a tether engages, the server log states the estimated directional edge a client watching both feeds would hold over the next hour. That is a legitimate configuration for an ordinary market and a poor one for a market that also settles binary options. Use AUTONOMOUS there.
Market bias
BULLISH, BEARISH or NEUTRAL, with a strength of 0–100. Bias influences how
the market moves between phases; it is not a guaranteed direction.
Volatility
Accepted ranges are 0.1–20 for base volatility, 0.5–2.0 for the
multiplier and 0.8–0.999 for momentum decay. At least one of the three must
be present or the request is refused.
Volatility is integrated against wall-clock time, not against tick count, so a market's realised volatility matches what you configured regardless of how often the engine happens to tick, and a restart resumes on the correct distribution.
Market phase
Four Wyckoff-style phases — ACCUMULATION, MARKUP, DISTRIBUTION,
MARKDOWN. The market transitions between them on its own; Market Phase
Control forces one, bypassing the normal transition rules, and the response
says so.
A forced phase now reaches the live market and expires when told to. Its volatility multiplier is applied by the engine only — an older build applied it twice, once in the engine and once written back onto the market's own volatility setting, so a forced phase ran at roughly 2.25x the volatility intended.
Target price
Must sit inside the configured range or the request is refused with the range quoted back. A move of more than 5% is flagged as a large change but still allowed.
The price sync job warns when a target price has drifted more than 10% from its external reference, at most once per market per hour.
Reset daily counters
Zeroes currentDailyVolume and the bots' dailyTradeCount immediately rather
than waiting for the UTC reset. Use it to bring a market that has spent its
budget back to quoting inside the same day — but understand that you are handing
out a second full allowance, and the budget existed for a reason.
Editing the core fields later
The same fields the wizard writes are editable from the Configuration tab:
target price, both range bounds, aggression, max daily volume, volatility
threshold, pauseOnHighVolatility and real liquidity percent. Validation is
identical to creation.
Only fields that actually changed are recorded in history. An earlier build
compared a DECIMAL column (which arrives from the database as a string)
against a number, so every save wrote a CONFIG_CHANGE for fields nobody had
touched — and a market with a 9–10 range could not be edited at all, because
"9" > "10" is true as text.
Deleting a market maker
Deleting stops the market through the engine first, waits up to ten seconds for the engine to confirm, cancels the bots' open ecosystem orders so locked funds return, and only then removes the row. The pool, the bots and the entire history cascade with it.
Deleting a funded market pays the pool out first, and it does so silently: whatever the pool still holds in either currency is credited to the platform's wallet — the Super Admin's, whoever clicked — in the same transaction that zeroes the pool, and the delete goes on from there. The confirmation dialog says so, but nothing asks you to confirm the amount. If you want to see the money move, or to land it somewhere you can check, withdraw the pool yourself before you delete.
The payout goes to the platform wallet and not to you because the delete runs
under delete.ai.market_maker.market, a permission that says nothing about
money — an admin granted it to retire markets must not be paid a pool's balance
for doing so. Before v6.2.5 it was. See Funding the pool
for whose wallet that is and why.