Hummingbot PMMSimpleController (by hummingbot, Apache-2.0) faithful port: 2+2-level symmetric pure market making on WLDUSDT.BINANCE 1m, 1%/2% spreads around mid, per-level triple-barrier executors (SL 3%, limit TP 2%, 45-min time limit, 1.5%/0.3% trailing stop), 5-min refresh, 15-s cooldown, 20x
Outcome: Abandoned
HummingbotPmmSimpleWld
Outcome Summary
After four iterations, HummingbotPmmSimpleWld tested symmetric passive market making with per-level exits on WLDUSDT one-minute bars. The backtest produced 10,546 closed net positions, Sharpe -6.733, and a -100% return ending in liquidation. Backtest review abandoned the strategy for measured negative expectancy; its fully frozen parameter plan provided no authorized optimization path.
Faithful translation of hummingbot's controllers/market_making/pmm_simple.py (PMMSimpleController, commit 9af100d6822da7d2d0291a906c730ef172284ee2, author: hummingbot, Apache-2.0; credit goes to the original authors). The controller file only overrides get_executor_config(). Every trading rule comes from the defaults in MarketMakingControllerBase / MarketMakingControllerConfigBase and from the PositionExecutor triple barrier, and each one is named below so the developer can translate them line for line. The……Show moreShow less
Faithful translation of hummingbot's controllers/market_making/pmm_simple.py (PMMSimpleController, commit 9af100d6822da7d2d0291a906c730ef172284ee2, author: hummingbot, Apache-2.0; credit goes to the original authors). The controller file only overrides get_executor_config(). Every trading rule comes from the defaults in MarketMakingControllerBase / MarketMakingControllerConfigBase and from the PositionExecutor triple barrier, and each one is named below so the developer can translate them line for line. The source makes no performance claims; our backtest with real fees decides.
MARKET (source default): connector binance_perpetual, trading_pair WLD-USDT, mapped to WLDUSDT.BINANCE (USD-M perpetual, maker 0.02% / taker 0.05%) on 1-MINUTE bars. Binance has 1m history for WLD back to 2023-07. BINANCE is over-represented in the corpus. A faithful port still keeps it, because the source's default connector and pair are Binance perpetual WLD-USDT. WLD itself is rarely used in the corpus, and the mechanism is under-represented on several counts: it trades long and short, works on a short horizon, and makes markets by providing passive liquidity on both sides with a triple barrier on each order.
LEVELS: buy_spreads [0.01, 0.02] and sell_spreads [0.01, 0.02] give four level ids: buy_0, buy_1, sell_0 and sell_1. The optimization plan accepts only scalars, so these lists are declared as the scalar parameters buy_spread_0=0.01, buy_spread_1=0.02, sell_spread_0=0.01 and sell_spread_1=0.02. buy_amounts_pct and sell_amounts_pct default to None, which means equal distribution: total_amount_quote is split 50% buy side / 50% sell side, then equally across each side's levels, so each level gets 25% (scalars level_amount_pct_buy_0 = buy_1 = sell_0 = sell_1 = 0.25). The reference price is the mid price with spread_multiplier = 1, because pmm_simple has no custom processed_data. Level price = reference_price * (1 - spread) for buys and reference_price * (1 + spread) for sells. Level amount (base) = level quote amount / level price.
SIZING (platform adaptation): the source's total_amount_quote default is 100 USDT on a 20x account. On our $100k engine, total_amount_quote = total_notional_equity_fraction (1.0) x get_account_equity() at executor creation. Each level is therefore 25% of equity notional, and the maximum one-sided exposure is 50% of equity (two levels on one side). config.leverage = 20 is the source default and within the BINANCE 20x cap; it only sets margin. Round quantities with make_qty, guard price > 0, cap notional at the computed value, and skip any level whose notional is below the $5 minimum.
EXECUTION SUBSTITUTIONS (from context.execution_capabilities):
(1) hedge_mode is unsupported (the source's position_mode default is HEDGE), and per_executor_deadlines are unsupported. The equivalent used here: the strategy keeps an internal ledger of up to four VIRTUAL executors, one per level id. Each has its own entry price, filled quantity, creation timestamp and barriers. Every virtual entry and exit is sent as a real order on the single NETTING position. The venue's net position therefore always equals the sum of the open virtual executors, and account PnL and fees equal the sum of the executors' PnL and fees, since in hedge mode each fill would also have been a separate order paying the same fee. Only margin usage and position reporting differ from hedge mode.
(2) Mid price: this design uses no quote ticks, so the reference price is the latest 1m bar close.
(3) Hummingbot ticks about once per second; we evaluate on each 1m bar close. executor_refresh_time of 300 s is 5 bars, and cooldown_time of 15 s rounds up to 1 bar, so a closed level may re-quote at the next bar close.
Limit entries and the limit take-profit rest in the simulated venue and fill intrabar when a bar trades through their price. The stop loss is an intrabar protective stop: a reduce-oriented stop_market order on the net position, matched inside the bar by the platform's simulated venue and mirrored live. Time-limit and trailing exits are market orders at the close of the bar that detects them.
Retry fix: the previous contract declared buy_spreads/sell_spreads as lists in optimization_plan.fixed, which the plan validator rejects because fixed values must be finite scalars. The lists are now declared as per-level scalars (buy_spread_0, buy_spread_1, sell_spread_0, sell_spread_1, level_amount_pct_*), all at the source's default values. Market, venue and mechanism are unchanged: hummingbot's own default connector and pair, binance_perpetual WLD-USDT, mapped to WLDUSDT.BINANCE. Non-numeric source settings are stated in the text instead of in fixed: take_profit_order_type LIMIT, position_mode HEDGE (replaced by virtual executors on a netting position) and price_type mid. The empty tunable object makes this a faithful fixed-default experiment.
Negative expectancy in the measured WLDUSDT one-minute faithful-port baseline: PF 0.542 and negative average net-position returns culminated in account liquidation. This is an economic rejection supported by realized losses, not drawdown alone or a predicted optimization failure. The frozen plan has no tunable parameters. The evidence does not establish that all market-making mechanisms or the original Hummingbot implementation are unviable.
Implementation
Hummingbot PMMSimpleController translation on WLDUSDT.BINANCE minute bars, with two passive levels per side and independent virtual-executor triple barriers. Closing executors reserve their levels until confirmed closing fills, cancellation acknowledgements and late-fill compensation finish. Failed protection initiates tracked shutdown.
Preserves the previous implementation's signal, instrument, sizing, spreads, barriers and frozen parameter plan. Addresses the first QA defect by retaining executor exposure through partial or rejected closes, tracking signed compensation debt through confirmed fills, retrying terminally failed orders on subsequent control cycles and preventing level reuse until every outstanding order settles. Addresses the second defect by reconciling missing or resized barriers each control cycle and initiating tracked……Show moreShow less
Preserves the previous implementation's signal, instrument, sizing, spreads, barriers and frozen parameter plan. Addresses the first QA defect by retaining executor exposure through partial or rejected closes, tracking signed compensation debt through confirmed fills, retrying terminally failed orders on subsequent control cycles and preventing level reuse until every outstanding order settles. Addresses the second defect by reconciling missing or resized barriers each control cycle and initiating tracked shutdown after barrier rejection, denial, expiration or failed construction. Ownership is registered before order submission to handle synchronous callbacks. Existing platform adaptations remain: bar close substitutes for mid, minute controls approximate source tick timing, and real orders implement virtual hedge executors on a NETTING account. Net-position reporting and virtual-executor economics require separate assessment; the negative truncated sandbox does not establish a correctness verdict. No independent verification was run. The required outbox file could not be written because this workspace permits read-only filesystem access; this response contains the complete task-bound JSON.
Verification Results
After terminal acknowledgements, schedule a guarded reconciliation that restores protection or progresses tracked shutdown promptly. Avoid recursive submission loops on repeated rejection.
When partial fills change the required barrier quantity, both protective orders are canceled. If cancellation acknowledgements arrive asynchronously, _release clears their identifiers but does not immediately reconcile replacement barriers. Protection can consequently remain absent until the next minute control cycle. A rejected barrier similarly initiates shutdown without immediately progressing it from the rejection callback.
Retain completed virtual-executor records with actual fill prices, quantities, commissions and timestamps, and reconcile aggregate realized plus unrealized PnL to the account before interpreting executor-level statistics.
The virtual ledger tracks exposure and settlement but does not retain completed executor PnL, commissions or holding durations. The sandbox explicitly reports closed NETTING positions, which can combine or offset multiple virtual executors. Its trade count, win rate and average trade return therefore cannot directly describe the hypothesis's per-executor outcomes.
The truncated sandbox averaged -0.21524% net return per closed net position, with substantial modeled impact. Evaluate full-history execution costs and virtual-executor economics separately; net-position trades are not individual executor round trips. Sandbox losses do not establish a correctness failure.
Backtest Review
Sharpe
-6.73
Total return
-100.00%
Max drawdown
100.00%
Trades
10546
Win rate
56.5%
Profit factor
0.54
Trade detail confirms both directions and short holding periods, consistent with the intended two-sided execution pattern; net-position records do not verify individual virtual-executor economics.
The primary input audit reports 1,639,440 bars without gaps or duplicates, and account-level accounting reconciles.
Measured negative expectancy: 10,546 closed net positions, profit factor 0.542, average net-position return -0.2148%, and Sharpe -6.733. QA's economic concern is borne out at account level.
The account lost 100% and was liquidated on 2023-11-13. Available history extends into 2026, but the economic track record ended after approximately 113 days.
Modeled impact totals $213,010 and commissions $58,703. These costs are material; the report does not establish that losses are exclusively caused by fees or identify a verified implementation defect.
The registered plan freezes every trading parameter and contains no tunable parameters, so optimization offers no authorized improvement path.
closed net positions, not virtual-executor round trips
Analysis
Code↔hypothesis misalignment found by the semantic auditor — the code does NOT implement the hypothesis. Re-code the strategy to implement the hypothesis EXACTLY (instrument, timeframe, direction, the named edge/mechanic, sizing). Concrete issues: Orphaned exposure: the code comments say a fill that arrives for an executor that is already closed (a same-bar race between TP and SL) gets neutralised at market. But the sibling-cancel loop in on_order_filled, along with _close_executor and _place_barriers, calls……Show moreShow less
Code↔hypothesis misalignment found by the semantic auditor — the code does NOT implement the hypothesis. Re-code the strategy to implement the hypothesis EXACTLY (instrument, timeframe, direction, the named edge/mechanic, sizing). Concrete issues: Orphaned exposure: the code comments say a fill that arrives for an executor that is already closed (a same-bar race between TP and SL) gets neutralised at market. But the sibling-cancel loop in on_order_filled, along with _close_executor and _place_barriers, calls self._omap.pop(other) right after self._cancel(other). In the NautilusTrader backtest the cancel command is queued and only processed after the current bar has been worked through the matching engine (open, high, low, close). So in a bar where the TP limit fills on the high and the stop triggers on the low, the stop still fills. When it does, on_order_filled finds tag None and returns early, so the neutralising market order is never sent. Because the stop is reduce_only=False, that fill opens an opposite position at 20x with no ledger entry and no barriers. The net position then stops equalling the sum of the virtual executors, which breaks the netting-ledger equivalence the hypothesis requires. The -100% return and 100% drawdown are consistent with these leaked positions building up.
## Library refinements (from the knowledge library; test them, do not assume them)
The library's market-making material says bar data cannot settle intrabar fill order. It names inventory risk as the main threat to a symmetric quoter, especially quoting against a trend (单边成交/逆势挂单). It recommends inventory limits with price skew (价格倾斜), volatility-scaled quotes, a short-term trend gate that cancels the risky side, and modest size. The first fix is to make the ledger safe on bars, closing the orphan-fill leak behind the -100% run; the other refinements reduce the left-skewed loss profile (60% win rate, PF 0.53).
1. [exit] Bar-safe barrier resolution and per-bar ledger reconciliation: reduce_only=True on all stop_market orders; keep cancelled order ids flagged 'cancel_pending' until OrderCanceled or OrderFilled arrives so a late fill is neutralised at market; when one bar touches both TP and SL, book STOP_LOSS; at each bar close, correct any net-position vs ledger gap of >= 1 size increment at market; stop quoting for 60 bars after more than 3 corrections within 1440 bars (source: 基于逐笔成交的高频回测系统兼论K线回测的缺陷 p.1)
2. [filter] Short-term trend gate: cancel and skip the counter-trend side: if ret15 > +1.0% and close > EMA30: no sell levels, cancel their unfilled entries; if ret15 < -1.0% and close < EMA30: same for buy levels; in either state, close losing counter-trend executors at market (source: 浅谈数字货币做市策略(2):盘口策略 p.1; 数字货币高频策略详细入门 p.1; 加止损可以控制风险……那么,古尔丹,代价是什么呢? p.1)
3. [sizing] Inventory cap plus quote skew: max_inventory = 0.25 of equity (no new orders on the side that would add to it); ref = close * (1 - 0.01 * inv / 0.25) (source: 浅谈数字货币做市策略(2):盘口策略 p.1; 行业大佬揭秘算法交易:发明者量化平台做市商策略 p.1)
4. [parameter] Volatility-scaled spreads and barriers: spread_0 = max(0.01, 2*ATR60/close), spread_1 = 2*spread_0, TP = spread_0, SL = 3*spread_0, trailing activation = 1.5*spread_0, frozen at executor creation (source: 行业大佬揭秘算法交易:发明者量化平台做市商策略 p.1; 浅谈数字货币做市策略(2):盘口策略 p.1)
5. [sizing] Cut total quoted notional to 0.25x equity: total_notional_equity_fraction = 0.25 (source: 行业大佬揭秘算法交易:发明者量化平台做市商策略 p.1; 基于逐笔成交的高频回测系统兼论K线回测的缺陷 p.1)
Iteration History
Verification failed (Layer 3 — sandbox backtest): Invalid `value` for quantity: 7.275957614183426e-12 was rounded to zero due to size increment 0.00000001 and size precision 8
Bar type used: WLDUSDT.BINANCE-1-MINUTE-LAST-EXTERNAL, Bars processed: 0
Ensure your strategy produces trades with the given data and parameters.
No change is needed for correctness. Optionally, count neutralise ('n') fills in diagnostics so the analyst can see how often the race occurs.
The TP limit and SL stop are submitted inside on_order_filled. In the simulated venue they can match within the same 1m bar that filled the entry, using bar OHLC with no knowledge of the intrabar path. When the TP and SL prices both lie inside one bar's range, the venue's matching order decides the result. The code handles the race safely: the second fill is neutralised at market, so the ledger stays equal to the net position. The cost is an extra market round trip, and the executor PnL in such bars is approximate.
Optionally log per-executor realized PnL and close_type, keyed by eid, so the analyst can judge the economics executor by executor.
Virtual executors are netted into a single NETTING position. The trade ledger therefore records net-position open/close cycles (106 closed positions from 252 fills in the sandbox), not one entry per executor. Long and short executors that are open at the same time offset each other in the reported positions. Per-trade statistics such as avg_trade_return_pct and win rate are per net-position cycle, not per hummingbot executor.
None.
Static analysis flagged the min_bars_required override. The base template documents this method as 'Override as needed', so the override is deliberate and harmless: the strategy needs only a previous close.
Verification failed (Layer 4 — QA review) [class=code_defect]: - [edge_concern] The truncated sandbox averaged -0.21968% per closed net position, with substantial modeled impact. Assess full-history economics and virtual-executor accounting separately; these results do not determine correctness. - [critical] _close_executor removes the executor and cancels its protection before confirming the market close. Closing fills tagged 'c' are ignored, and rejected or denied closing orders cannot recover the removed……Show moreShow less
Verification failed (Layer 4 — QA review) [class=code_defect]:
- [edge_concern] The truncated sandbox averaged -0.21968% per closed net position, with substantial modeled impact. Assess full-history economics and virtual-executor accounting separately; these results do not determine correctness.
- [critical] _close_executor removes the executor and cancels its protection before confirming the market close. Closing fills tagged 'c' are ignored, and rejected or denied closing orders cannot recover the removed executor. A failed or incomplete close therefore leaves untracked exposure while the level can create another executor. Failed compensating orders tagged 'n' likewise leave exposure without recovery. (line 257) — fix: Retain a closing executor until confirmed fills exhaust its remaining quantity. Track closing and compensating quantities, handle rejection and incomplete execution, and reconcile outstanding exposure before releasing the level.
- [critical] Rejection, denial or expiration of a protective stop only clears ex['sl'] through _release. _control_executor never recreates missing barriers. Consequently, an executor whose stop submission fails can remain exposed until its time limit or trailing exit, without the required 3% protective stop. A failed take-profit is similarly never restored. (line 352) — fix: Reconcile required barriers on each control cycle and handle failed submissions explicitly. Restore missing protection or close the executor through a tracked shutdown procedure; do not continue treating an unprotected executor as healthy.
Retain a closing executor until confirmed fills exhaust its remaining quantity. Track closing and compensating quantities, handle rejection and incomplete execution, and reconcile outstanding exposure before releasing the level.
_close_executor removes the executor and cancels its protection before confirming the market close. Closing fills tagged 'c' are ignored, and rejected or denied closing orders cannot recover the removed executor. A failed or incomplete close therefore leaves untracked exposure while the level can create another executor. Failed compensating orders tagged 'n' likewise leave exposure without recovery.
Reconcile required barriers on each control cycle and handle failed submissions explicitly. Restore missing protection or close the executor through a tracked shutdown procedure; do not continue treating an unprotected executor as healthy.
Rejection, denial or expiration of a protective stop only clears ex['sl'] through _release. _control_executor never recreates missing barriers. Consequently, an executor whose stop submission fails can remain exposed until its time limit or trailing exit, without the required 3% protective stop. A failed take-profit is similarly never restored.
Outcome Summary
This baseline’s account-level losses justify rejection, but net-position results do not establish individual virtual-executor economics or invalidate the original Hummingbot implementation.
Backtest review returned abandon because realized losses demonstrated negative expectancy, and the frozen plan contained no tunable parameters. Optimization, post-optimization analysis, and risk review were not reached.
Port Hummingbot’s PMMSimpleController to WLDUSDT Binance perpetual one-minute bars, quoting two buy and two sell levels at 1%/2% spreads with per-level triple-barrier exits.
The backtest recorded 10,546 closed net positions, a -100% return, Sharpe -6.733, and profit factor 0.542. Average net-position return was -0.2148%, and the account was liquidated on 2023-11-13 after approximately 113 days.
Analysis
PMMSimpleController
Backtest and paper results are hypothetical. Trading involves risk of loss.