Skip to content
All strategies

Strategies

Hummingbot PMM V1 (pure market making, by hummingbot) — faithful port on BTCUSDT.BINANCE_SPOT, 1 buy + 1 sell limit level at mid ±1%, 30s refresh, 60s post-fill delay, inventory skew OFF (source defaults)

Outcome: Abandoned

HummingbotPmmV1BtcSpot

Outcome Summary

This hypothesis tried to port Hummingbot's PMM V1 symmetric limit-order market maker onto Binance spot BTCUSDT. The only platform changes were forced ones: fixed sizing at 25% of starting equity, a refresh on every 1-minute bar, and spot balance checks. The strategy never got out of coding: after 3 iterations the run was abandoned with reason 'coding_stuck_3_timeouts'. No backtest, optimization or analyst verdict was ever produced.

Hypothesis

Faithful translation of Hummingbot's PMM V1 controller (credit: hummingbot, Apache-2.0). It is a real tradeable strategy (a symmetric limit-order market maker), not a utility. Source defaults: connector 'binance' (SPOT connector) and trading_pair 'BTC-USDT', so we use BTCUSDT.BINANCE_SPOT (CASH account, long-only inventory, maker fee 0.10% per fill). Defaults exactly as written: order_amount = 1 (base asset), buy_spreads = [0.01], sell_spreads = [0.01] (one level per side, level ids 'buy_0' and 'sell_0'),……Show moreShow less

Faithful translation of Hummingbot's PMM V1 controller (credit: hummingbot, Apache-2.0). It is a real tradeable strategy (a symmetric limit-order market maker), not a utility. Source defaults: connector 'binance' (SPOT connector) and trading_pair 'BTC-USDT', so we use BTCUSDT.BINANCE_SPOT (CASH account, long-only inventory, maker fee 0.10% per fill). Defaults exactly as written: order_amount = 1 (base asset), buy_spreads = [0.01], sell_spreads = [0.01] (one level per side, level ids 'buy_0' and 'sell_0'), order_refresh_time = 30 s, order_refresh_tolerance_pct = -1 (disabled, so refresh everything), filled_order_delay = 60 s, inventory_skew_enabled = False, target_base_pct = 0.5, inventory_range_multiplier = 1.0, price_ceiling = -1 and price_floor = -1 (both disabled). Reference price = mid price. On our platform, with bars only, mid = close of the latest 1-MINUTE bar (the finest bar we have with multi-year history; order-book mid is not reproducible over years). Each order is a plain LIMIT order (post-only is not in the source, so it is not added). PLATFORM ADAPTATIONS (only these, all forced): (1) Sizing. 1 BTC is at or above the whole $100k starting capital, so order_amount is set ONCE at start to a fixed base quantity equal to 0.25 × starting equity / first close (about 25% of equity per level) and then kept FIXED in base units, as the source does. Parameter 'order_amount_equity_frac' = 0.25. (2) Clock. The 30 s refresh and 60 s fill delay are shorter than or equal to one 1m bar. Refresh therefore happens at every bar close (cancel any unfilled resting order older than 30 s, i.e. every bar), and a level that filled cannot re-quote until 60 s after the fill (it skips the next bar close). (3) Spot balance. Hummingbot's spot connector rejects a sell larger than the base balance and a buy larger than the quote balance. So the sell_0 order is placed only if base held >= order_amount (otherwise the level is skipped that bar), and buy_0 only if free USDT >= order_amount × price × 1.001. The backtest starts with 100% USDT, so the first round trip must begin with a buy fill. The source has no stop-loss, take-profit or time exit. None is added, because adding one would stop it being a faithful port.

Iteration 2 fix for the Layer 2 failure. The mock cache used in the synthetic tests has no orders_inflight() or account_for_venue() method. The code now looks both up with getattr and skips them when they are missing. The real NautilusTrader cache has both methods, so backtest and paper behaviour do not change. Nothing else was changed. The platform adaptations are the same as before: the bar close stands in for the order-book mid, refresh and fill delay run on the bar clock, the fixed base order amount is set from equity, and each side is gated on the spot balance. Concern about the edge: the spreads are 1% and each fill pays a 0.10% maker fee, so the strategy only makes money if BTC swings back and forth across a range of about 2%. In a one-way trend it is left holding BTC or cash on one side.

coding_stuck_3_timeouts

Implementation

Faithful port of Hummingbot PMM V1 (pure market making) on BTCUSDT spot, using the 1-minute bar close as the mid price. It keeps one GTC limit buy at mid × (1 − 1%) and one GTC limit sell at mid × (1 + 1%). Every bar it cancels the resting quotes and posts new ones, because the 30 s refresh is shorter than one bar. After a fill, that side waits 60 s before quoting again. The order amount is a fixed base quantity set once at start: 25% of starting equity divided by the first close. Inventory skew is off and the price bands are disabled, as in the source. A buy is placed only if there is enough free USDT, and a sell only if enough BTC is held.

Iteration History

Verification failed (Layer 2 — synthetic scenarios): Parameters used: ['buy_spread', 'price_floor', 'sell_spread', 'price_ceiling', 'filled_order_delay', 'order_refresh_time', 'quote_balance_buffer', 'order_amount_equity_frac'] Check that __init__ sets all attributes from self.parameters.get(). - steady_uptrend: AttributeError: '_MockCache' object has no attribute 'orders_inflight' (bar timestamp: 1735689660000) - steady_downtrend: AttributeError: '_MockCache' object has no attribute 'orders_inflight' (bar timestamp: 1735689660000) - flat_ranging: AttributeError: '_MockCache' object has no attribute 'orders_inflight' (bar timestamp: 1735689660000) - volatility_spike: AttributeError: '_MockCache' object has no attribute 'orders_inflight' (bar timestamp: 1735689660000) - zero_volume: AttributeError: '_MockCache' object has no attribute 'orders_inflight' (bar timestamp: 1735689660000) - price_gap: AttributeError: '_MockCache' object has no attribute 'orders_inflight' (bar timestamp: 1735689660000)

sandbox_timeout: Verification failed (Layer 3 — sandbox backtest): smoke test exceeded the 300s wall-clock limit on a ~20000-bar window (30 days of BTCUSDT.BINANCE_SPOT-1-MINUTE-LAST-EXTERNAL). This almost always means per-bar work that scales with history — e.g. rescanning the full funding/supplementary series, or rebuilding a list and calling min()/sorted() inside calculate_signal()/on_bar() on every bar. Precompute sorted timestamp arrays ONCE in __init__ and use bisect, or cache lookups keyed by timestamp, so per-bar cost is O(log n) not O(n).

Outcome Summary

Order-management-heavy ports (cancel/re-quote on every bar, waiting for cancel acknowledgements, spot balance checks) are complex enough to time out the coding stage, so a future attempt should start from a simpler, smaller implementation that can finish within the coding time limit.

The run was abandoned in the coding stage with reason 'coding_stuck_3_timeouts' after 3 iterations, so verification, backtest review, optimization, analyst review and risk review were never reached.

A faithful port of Hummingbot's PMM V1 pure market maker on BTCUSDT.BINANCE_SPOT 1-minute bars, quoting one buy and one sell limit order at mid ±1%, refreshing every bar, waiting 60 s after a fill before re-quoting, with inventory skew off and no stop-loss, take-profit or time exit.

There is no performance data: the backtest report is empty and no optimization report exists, so no return, Sharpe or trade count was recorded.

Analysis

PMMV1

Backtest and paper results are hypothetical. Trading involves risk of loss.