Skip to content
All strategies

Strategies

Hummingbot SimplePMM (faithful port): symmetric 10 bp maker quotes around ETHUSDT mid, cancel-and-replace every 15 s, fixed 0.01 ETH per side (ETHUSDT.BINANCE USD-M, quote ticks)

Outcome: Abandoned

EthSimplePmmMakerQuotes

Outcome Summary

EthSimplePmmMakerQuotes tried to port Hummingbot's SimplePMM script to the ETHUSDT.BINANCE USD-M perp at leverage 1.0, quoting a 10 bp maker bid and ask every 15 s. The perp was chosen over spot so that both sides could be quoted from a flat start, and because spot's 0.10% maker fee would cancel the whole spread. The developer rewrote the cancel-and-replace step as in-place order amends to cut engine events, but the run still got stuck at coding with 3 timeouts over 3 iterations. It was abandoned before any backtest, optimization or analyst review took place, so no performance data exists.

Hypothesis

A faithful statement of Hummingbot's 'SimplePMM' script (BotCamp Cohort Sept 2022, Apache-2.0; credit: hummingbot, https://github.com/hummingbot/hummingbot/blob/9af100d6822da7d2d0291a906c730ef172284ee2/scripts/simple_pmm.py). It is a real trading strategy: a pure passive market maker with no signal, no inventory control, no stop and no position limit. Rules in the source, with defaults exactly as written: trading_pair ETH-USDT; order_amount = 0.01 ETH; bid_spread = 0.001 (10 bp); ask_spread = 0.001 (10 bp);……Show moreShow less

A faithful statement of Hummingbot's 'SimplePMM' script (BotCamp Cohort Sept 2022, Apache-2.0; credit: hummingbot, https://github.com/hummingbot/hummingbot/blob/9af100d6822da7d2d0291a906c730ef172284ee2/scripts/simple_pmm.py). It is a real trading strategy: a pure passive market maker with no signal, no inventory control, no stop and no position limit. Rules in the source, with defaults exactly as written: trading_pair ETH-USDT; order_amount = 0.01 ETH; bid_spread = 0.001 (10 bp); ask_spread = 0.001 (10 bp); order_refresh_time = 15 seconds; price_type = 'mid' (MidPrice; 'last' switches the reference to LastTrade). On every tick, when create_timestamp <= now: (1) cancel ALL active orders on the pair; (2) ref_price = mid price (or last trade price if price_type == 'last'); (3) build a LIMIT BUY of order_amount at ref_price*(1 - bid_spread) and a LIMIT SELL of order_amount at ref_price*(1 + ask_spread), both flagged is_maker=True; (4) run the budget checker with all_or_none=True (each order is either kept at full size or dropped if the account can't afford it; there is no partial resizing); (5) place the surviving orders; (6) set create_timestamp = now + order_refresh_time. did_fill_order only logs; fills do NOT change quoting. Inventory accumulates freely in whichever direction fills land. VENUE CHOICE (the one adaptation): the source runs on binance_paper_trade spot, where the paper account starts with BOTH ETH and USDT, so both sides are always quoted. On our BINANCE_SPOT CASH account the strategy starts with USDT only, so the sell quote would be dropped by the budget check until a buy fills, and the bot would turn into a one-sided dip-buyer. Spot maker fee is also 0.10% per side, so a full 10 bp + 10 bp round trip earns 0.20% gross against 0.20% of maker fees: zero edge by construction. The Binance USD-M perp (ETHUSDT.BINANCE, MARGIN, maker 0.02%) keeps both sides quotable from a flat start, matching the source's two-sided behaviour, with leverage 1.0. ETHUSDT is chosen because the source names ETH-USDT; the mechanism is venue/pair-specific, so the corpus ETH/BINANCE concentration is accepted here and noted.

Iteration 2 fixes the Layer-3 sandbox timeout (lesson L155 option (c): per-bar cost). Three changes, quoting rules untouched: (1) all tick subscriptions are explicitly off; the strategy quotes off bar closes only, so no tick stream is loaded. (2) Bar interval goes from 1-MINUTE to 5-MINUTE. The 15 s refresh is shorter than any bar, so the port requotes on every bar either way; 5m bars cut the sandbox bar count and the order churn by about 2-5x. The trade-off is that quotes rest 5 minutes between refreshes instead……Show moreShow less

Iteration 2 fixes the Layer-3 sandbox timeout (lesson L155 option (c): per-bar cost). Three changes, quoting rules untouched: (1) all tick subscriptions are explicitly off; the strategy quotes off bar closes only, so no tick stream is loaded. (2) Bar interval goes from 1-MINUTE to 5-MINUTE. The 15 s refresh is shorter than any bar, so the port requotes on every bar either way; 5m bars cut the sandbox bar count and the order churn by about 2-5x. The trade-off is that quotes rest 5 minutes between refreshes instead of 15 s. (3) Cancel-all-and-replace becomes an in-place modify_order of the still-resting quote on each side. In a simulated book that is economically identical (same side, new price, new size), but it takes 2 engine events per side instead of about 5. A filled or partly filled side gets a fresh order, and a side dropped by the budget check is cancelled. Per-bar work is O(open orders) = O(1), with no history scans. Other platform adaptations are unchanged: the reference is the bar close (last trade) because there is no L1 mid at bar level; order_amount is 1% of equity rather than a fixed 0.01 ETH; and the perp venue keeps both sides quotable from a flat start. Honest caveat: 10 bp quotes on 5m bars get filled on adverse moves, with no inventory control, so the edge is likely thin after the 0.02% maker fees.

coding_stuck_3_timeouts

Implementation

Faithful port of Hummingbot SimplePMM on ETHUSDT.BINANCE USD-M perp: every refresh, retire all quotes and post a post-only LIMIT BUY at ref*(1-10bp) and LIMIT SELL at ref*(1+10bp), each 1% of equity, with an all-or-none margin budget check; fills never change quoting, inventory accumulates freely.

Iteration History

sandbox_timeout: Verification failed (Layer 3 — sandbox backtest): smoke test exceeded the 300s wall-clock limit on a ~20000-bar window (30 days of ETHUSDT.BINANCE-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).

sandbox_timeout: Verification failed (Layer 3 — sandbox backtest): smoke test exceeded the 300s wall-clock limit on a ~20000-bar window (69 days of ETHUSDT.BINANCE-5-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

A market maker that reprices every 15 s produces a very large number of order events on quote-tick data, so a future attempt should first confirm that the code can finish within the coding and verification time limits (for example with fewer requotes or coarser data).

It was abandoned at the coding stage with reason 'coding_stuck_3_timeouts' after 3 iterations. Backtest review, optimization, analyst review and risk review were never reached.

A faithful port of Hummingbot's SimplePMM: a pure passive market maker posting symmetric 10 bp post-only maker quotes around the ETHUSDT reference price on the Binance USD-M perp. Quotes were cancelled and replaced every 15 seconds, with no signal, inventory control, stop or position limit.

There are no performance numbers. The backtest report is empty and no optimization report exists, so the strategy never produced a return, Sharpe ratio or trade count.

Analysis

SimplePMM

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