Strategies and presets
PMM and XEMM controllers — every parameter the YAML generator consumes, what each one does to a running bot, the six seeded profiles, and how to tune a spread ladder and a cross-exchange profitability target.
A preset is a row, not a file. The Strategy Studio stores a family, a pair, the connectors and a parameter object; the YAML a user downloads — and the YAML a server-run instance is launched with — is generated from that row on demand. That is why editing a preset and restarting an instance is the entire deployment process, and why a preset can never drift from what the panel shows.
Controllers, not V1 strategies
Everything here is Strategy V2. Two families ship:
| Family | Emits | controller_type |
Runs on |
|---|---|---|---|
pmm |
controller_name: bicrypto_pmm |
market_making |
bicrypto (spot) or bicrypto_perpetual |
xemm |
controller_name: xemm_multiple_levels |
generic |
maker on bicrypto, taker on an external venue |
Both are started through a script config that names the controller — see
Connecting a bot. The V1 create wizard cannot see either
of them.
The six seeded profiles
Presets are named for market conditions, not for pairs, because the market is chosen per deployment. All six ship published.
| Profile | Ladder (buy = sell) | Refresh | Sized for |
|---|---|---|---|
| PMM — Tight | 5, 15, 30 bps | 8s | A genuinely deep book. On a thin one these sit at the top and get picked off |
| PMM — Balanced | 20, 50, 100 bps | 12s | The default when you do not yet know how deep your book is |
| PMM — Wide | 50, 150, 300 bps | 25s | A new listing, a low-volume pair, or a market you are seeding alone |
| PMM — Perpetual (3x) | 20, 50, 100 bps | 12s | The Balanced ladder on a perpetual contract at 3× |
| XEMM — Hedged on Binance | targets 10 and 20 bps | — | A thin book against Binance depth |
| XEMM — Hedged on MEXC | targets 10 and 20 bps | — | The same, hedging on MEXC |
They are conservative starting points sized for a modest account. Set
totalAmountQuote to your own size before running any of them, and recompute the
XEMM targets against your own fee schedule.
PMM parameters
These are the Studio's field names. The generator renders them to the snake_case keys the controller reads, shown in the second column.
| Studio field | YAML key | What it does |
|---|---|---|
market |
selects connector_name |
spot → bicrypto; perp → bicrypto_perpetual |
totalAmountQuote |
total_amount_quote |
Total capital the ladder is allowed to work, in quote currency |
buySpreads / sellSpreads |
buy_spreads / sell_spreads |
One entry per level, as a fraction — 0.0005 is 5 bps from the mid |
buyAmountsPct / sellAmountsPct |
buy_amounts_pct / sell_amounts_pct |
How totalAmountQuote is split across those levels |
executorRefreshTime |
executor_refresh_time |
Seconds before an unfilled level is cancelled and re-quoted |
cooldownTime |
cooldown_time |
Seconds a level waits after being filled before quoting again |
stopLoss |
stop_loss |
Triple-barrier stop, as a fraction of entry |
takeProfit |
take_profit |
Triple-barrier target |
timeLimit |
time_limit |
Seconds a position may stay open before it is closed regardless |
leverage |
leverage |
Perpetual only. Forced to 1 on spot |
| — | position_mode |
Always ONEWAY — the only mode the Futures engine implements |
buySpreads and buyAmountsPct must have the same length, and so must the sell
pair. The validator refuses a mismatch with a 422 rather than silently quoting a
level with no size, or sizing a level that does not exist. The same validation
runs again on export, so a hand-edited database row cannot emit malformed YAML
either.
Defaults applied when a field is omitted: executorRefreshTime 8,
cooldownTime 2, stopLoss 0.02, takeProfit 0.005, timeLimit 3600.
XEMM parameters
| Studio field | YAML key | What it does |
|---|---|---|
takerConnector |
taker_connector |
The venue each fill is hedged on. Required, and must be connected inside Hummingbot itself |
takerPair |
taker_trading_pair |
Optional. Defaults to the maker pair, so the hedge follows whichever market the instance selects |
buyLevels / sellLevels |
buy_levels_targets_amount / sell_levels_targets_amount |
A list of [targetProfitability, amountQuote] pairs, one per maker level |
minProfitability |
min_profitability |
Lower edge of the band. Default 0.0008 |
maxProfitability |
max_profitability |
Upper edge. Default 0.003, and must be greater than the minimum |
A level is re-quoted whenever its live edge drifts outside the
min_profitability … max_profitability band.
Set it below your fee-plus-slippage floor and you lose money on every hedge. Set it too high and no level ever qualifies, so the bot quotes and never trades — which reads exactly like a broken connector.
target_profitability ≥ taker_fee + maker_fee_or_rebate + 2 × slippage_estimateWorked example — a 0.05% maker rebate, 0.02% taker fee on the hedge venue and a
5 bps slippage estimate: 0.0002 + (-0.0005) + 2 × 0.0005 = 0.0007, a 7 bps
floor. The seeded targets start at 10 bps, which clears that example but is not a
substitute for computing your own.
Hummingbot needs its own credentials for the taker venue. Connecting
bicrypto here does nothing for the hedge leg; run connect binance (or mexc,
or whichever) inside Hummingbot as well, and fund that account above the
per-level amounts.
Tuning PMM
Run Balanced first, watch these four numbers for an hour, then move to Tight or Wide.
| Metric | Target | If it is off |
|---|---|---|
| Fill rate | 10–30% | Too low → tighten spreads. Too high → widen; you are underpricing |
| Inventory swing | Under 20% of total | Higher → reduce the allocation on the outer levels |
| Refresh interval | Around executorRefreshTime ±20% |
Much lower → you are being cancelled and replaced too often; check volatility |
| Cancel/replace ratio | Under 2× the fill rate | High → raise executorRefreshTime, or lean on cooldownTime |
Sensible spread ranges by market depth:
| Market | Spread range |
|---|---|
| BTC, ETH, major stablecoin pairs | 0.0003 – 0.001 (3–10 bps) |
| Mid-cap alts | 0.001 – 0.005 (10–50 bps) |
| Long tail | 0.005 – 0.02 (50–200 bps) |
And refresh cadence: 4–8s on liquid pairs, 8–15s mid-cap, 15–60s long tail. Anything faster than a few seconds is mostly spending rate-limit budget on cancel/replace traffic — the spot book publishes snapshots roughly every 200ms, so an 8-second refresh already absorbs fills without a storm.
The main risk of pure market making is inventory drift. In a trending market one side fills repeatedly and you accumulate the losing asset. Watch the swing figure and rebalance; the triple-barrier stop bounds a single position, not the inventory the ladder accumulates across many.
For perpetuals, leverage is checked against what the market actually offers. A
rejection reads leverage 7x is not offered on BTC-USDT. Available: 1, 5, 10
rather than an opaque refusal on every order. Keep it at 1–3 unless you have
liquidation alerting wired up.
The Strategy Studio
Admin → Hummingbot → Strategy Studio at /admin/hb/strategies.
-
Choose a family — PMM or XEMM. The form changes shape accordingly, and within PMM again on spot versus perpetual, which is why creating and editing happen in a dedicated dialog rather than the generic table form.
-
Set the connectors and the default pair — a connector identifier must be lowercase letters, digits and underscores. That charset check is what stops a crafted connector name injecting structure into the YAML it lands in.
-
Enter the parameters — validated on save, and validated again on export.
-
Preview the YAML — the rendered controller config, exactly as a user would download it, including the header that tells them the two filenames to save it under.
-
Publish it —
draftpresets are private.publishedpresets appear to users at/hb/strategies. Each preset carries a version, so you can tell which revision someone downloaded.
Presets are also what your own server-run instances run: link one to an instance, and its YAML is regenerated into the checkout on every start.
The seeder only inserts presets that are missing by name, so it never overwrites your changes on a later update — and never restores one you deleted. Deleting a preset an instance is linked to sets that instance's preset to null rather than orphaning a running bot; the instance must then be pointed at a new profile before it will start.
Writing your own controller
bicrypto_pmm is Hummingbot's stock market-making controller with defaults tuned
for this platform, plus the one method that base class leaves for its subclasses
to write — see the first warning below. If you write your own, put it in
controllers/<controller_type>/ inside the checkout — for a market maker that is
controllers/market_making/. Hummingbot resolves a controller by name within the
folder matching its type.
MarketMakingControllerBase builds the ladder for you, but get_executor_config
is abstract, not a default: the base raises because only your controller knows
what one level should become. A subclass that leaves it alone still starts
cleanly, logs started successfully, reaches ready and holds a normal memory
footprint — and quotes nothing at all, while NotImplementedError repeats in the
bot log once per tick. Implement it and return a PositionExecutorConfig.
Only fields the base controller actually consumes take effect. bid_spreads
instead of buy_spreads is not an error — it is accepted, ignored, and the bot
quotes with defaults nobody chose. Match the field names on the config class
exactly.
Every generated preset also carries an id. Hummingbot's
ControllerConfigBase.id is required with no default, so a config without one
dies in validation before the bot ever reaches an exchange with
1 validation error … id / Field required. The generator derives it from the
filename stem, or from the instance id when the supervisor writes the file.