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.

5 min readUpdated 3 August 2026pmm, xemm, controllers, presets, strategy-studio, tuning

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 spotbicrypto; perpbicrypto_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_estimate

Worked 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.

  1. 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.

  2. 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.

  3. Enter the parameters — validated on save, and validated again on export.

  4. 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.

  5. Publish itdraft presets are private. published presets 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.