Skip to content
All strategies

Strategies

Hummingbot GridStrike port: long-side static grid of 190 post-only buy levels from 0.38 to 0.75, each closed by a post-only take-profit 0.1% above its fill, whole grid flattened at market if mid falls to 0.35, re-armed when price is back in the band (WLDUSDT.OKX USDT swap, 1-MINUTE, net position, 2x margin, 35% of equity grid budget)

Outcome: Abandoned

HummingbotWldOkxSwapGridStrike

Outcome Summary

HummingbotWldOkxSwapGridStrike ported hummingbot's GridStrike as a long-only 190-level buy grid on WLDUSDT.OKX at 2x leverage, corrected from an earlier attempt that failed on a spot venue. After 3 iterations, the backtest showed negative expectancy: an average trade return of -0.19% of notional, and in the full report a -15.8% return, Sharpe -0.30 and 39% max drawdown over 426 trades. Most equity swings came from unrealized inventory that tracked WLD's direction rather than from grid profits. The analyst abandoned it at backtest review because the frozen 0.1% take-profit is structurally consumed by costs, so optimization and the later stages (analyst, risk review, promotion) were never reached.

Hypothesis

Faithful port of "GridStrike" by hummingbot (hummingbot/hummingbot, controllers/generic/grid_strike.py, commit 9af100d, Apache-2.0), together with the GridExecutor it creates. The strategy and its default values are the author's. Any performance the author claims is unverified; our backtest with real OKX fees decides. Market: the source defaults are connector 'okx', pair WLD-USDT, leverage 20, HEDGE position mode. We trade the OKX USDT perpetual swap WLDUSDT.OKX (MARGIN account, long-capable, 1-MINUTE bars from……Show moreShow less

Faithful port of "GridStrike" by hummingbot (hummingbot/hummingbot, controllers/generic/grid_strike.py, commit 9af100d, Apache-2.0), together with the GridExecutor it creates. The strategy and its default values are the author's. Any performance the author claims is unverified; our backtest with real OKX fees decides. Market: the source defaults are connector 'okx', pair WLD-USDT, leverage 20, HEDGE position mode. We trade the OKX USDT perpetual swap WLDUSDT.OKX (MARGIN account, long-capable, 1-MINUTE bars from 2023-07). A previous attempt froze leverage 20 and HEDGE mode on WLDUSDT.OKX_SPOT, a 1x CASH venue with netting accounts, and failed Layer 1.5. This version fixes that. (1) Position mode: the controller sets side=BUY, so every grid level opens a long and every level's take-profit is a reduce-only sell. No opposing short leg ever exists, so the NET position (the sum of the filled, not yet closed levels) is exactly what HEDGE mode would hold on the long side. Substitution named: netting account instead of hedge mode, no economic difference for a one-sided grid. (2) Leverage: the source's 20x only sets margin. Exposure is set by total_amount_quote. We run the account at leverage 2.0, within the conservative profile's 5x cap, and set exposure through the grid budget below. (3) Sizing: the source's total_amount_quote=1000 USDT is an account-specific example. We set total_amount_quote = 35% of account equity, measured when each grid executor is created (~$35,000 on $100k). With every level filled, notional is 0.35x equity. The worst case is the grid filled all the way from 0.75 down to 0.38 (average entry ~0.565) and then flattened at the 0.35 limit_price, a ~38% loss on 0.35x notional, so ~13% of equity. That keeps the worst trade under the profile's 15% cap. WLD 1x volatility is ~110-130%/yr, so 0.35x contributes ~40-45%. min_order_amount_quote is scaled with the budget, as 0.5% of total_amount_quote (the source's 5/1000), so the grid has the source's geometry: 190 levels. CONTROLLER RULE (determine_executor_actions, every bar): mid = bar close (stand-in for the mid price). If no grid executor is active AND start_price <= mid <= end_price (inclusive), create a new grid executor with the config below. Otherwise do nothing. Only one executor is ever active. GRID GENERATION (GridExecutor._generate_grid_levels, at executor creation, using the mid at that moment): min_quote = min_order_amount_quote x 1.05 (the source's 5% margin over the minimum notional; size rounded up to the venue's size increment). grid_range = (end_price - start_price) / start_price. min_step = max(min_spread_between_orders, price_increment / mid). max_possible_levels = floor(total_amount_quote / min_quote). n_levels = min(max_possible_levels, floor(grid_range / min_step)). base_per_level = max(min_base, total_amount_quote / (mid x n_levels)), quantized to the size increment. quote_per_level = base_per_level x mid, and n_levels = min(n_levels, floor(total_amount_quote / quote_per_level)), at least 1. Level prices are linear from start_price to end_price inclusive (numpy-style linspace of n_levels points). If n_levels == 1, the single level is at (start+end)/2. Each level gets amount quote_per_level, side BUY and take_profit = triple_barrier take_profit 0.001 (coerce_tp_to_step defaults to False, so the TP is not widened to the step). With the defaults this gives 190 levels ~0.00196 apart (0.26% to 0.52% of price). LEVEL STATES: NOT_ACTIVE -> OPEN_ORDER_PLACED -> OPEN_ORDER_FILLED -> CLOSE_ORDER_PLACED -> COMPLETE. A COMPLETE level resets to NOT_ACTIVE, so the grid keeps recycling. A level whose open order is cancelled or rejected also returns to NOT_ACTIVE. Execution is all-limit and post-only (LIMIT_MAKER = maker fee 0.02% on OKX) except the limit_price flatten, which is a market order (taker 0.05%). Bar-level adaptation of the seconds-based throttle: the source lets one open order be placed every order_frequency=3 s, max_orders_per_batch=1, and never more than max_open_orders=2 resting open orders at once. On 1-minute bars (20 throttle periods per bar) this becomes: on each bar, top up resting buy orders until 2 are open, filling the nearest eligible levels. Named substitution.

Negative expectancy after costs on the development period (Jul 2023 to Feb 2026). This verdict covers this faithful port of hummingbot GridStrike on WLDUSDT.OKX: a static BUY grid at 0.38-0.75 with 190 levels, a 0.1% take-profit per level and a 0.35 flatten. Average trade return is -0.19% of notional against the OKX floor of 0.15% (value -0.19% vs floor 0.15%). Profit factor is 0.97, Sharpe -0.04, total return -1.9%, over 163 closed long position cycles; summed closed-trade P&L is -$10. Drawdown is not the……Show moreShow less

Negative expectancy after costs on the development period (Jul 2023 to Feb 2026). This verdict covers this faithful port of hummingbot GridStrike on WLDUSDT.OKX: a static BUY grid at 0.38-0.75 with 190 levels, a 0.1% take-profit per level and a 0.35 flatten. Average trade return is -0.19% of notional against the OKX floor of 0.15% (value -0.19% vs floor 0.15%). Profit factor is 0.97, Sharpe -0.04, total return -1.9%, over 163 closed long position cycles; summed closed-trade P&L is -$10. Drawdown is not the reason: it was 13.3%, inside the 25% limit. The structural cause is the contract's fixed take-profit of 0.001. Each lot earns 0.1% gross, the maker round trip costs 0.04% on OKX, and in 125 of 163 closes the booked P&L is negative even though the exit was above the entry, so modelled costs absorb what is left. QA's concern is borne out. take_profit, spacing and sizing are frozen in the research contract. The tunables (start_price, end_price, max_open_orders) change only where the band sits and how many orders rest at once, not the per-lot edge, so optimization cannot lift it above costs. The rest of the equity movement is unrealized inventory that tracks WLD's direction. A wider take-profit would be a different strategy from the author's defaults, not an iteration of this port.

Implementation

Hummingbot GridStrike port on WLDUSDT.OKX swap (1-minute bars): one-sided BUY grid with ~190 linear levels from start_price to end_price, built when the close is inside the band. Each bar tops up post-only buy limits on the nearest free levels until max_open_orders are resting. Each filled level gets a post-only reduce-only take-profit sell at level*(1+0.001). If the close falls to limit_price or below, all orders are cancelled and the position is closed at market; the grid re-arms when price is back inside the band. Grid budget is 35% of equity when the grid is created, min order is 0.5% of the budget, account leverage is 2x.

Iteration 3 fixes the Layer 1.5 failure 'conflicting top-level and parameter leverage' and moves the port to the amended hypothesis. Venue is now WLDUSDT.OKX swap with leverage 2.0 at top level and in parameters, the parameter names are the research plan's (total_amount_quote_pct_equity, min_order_amount_quote_frac, min_order_margin, order_frequency_s, activation_bounds=None), and _optimization_plan is the research plan verbatim. Per the hypothesis, the only flatten is limit_price, so the old upper-band drain was removed. The 3 s, batch-1 throttle becomes a per-bar top-up to max_open_orders. The bar close stands in for mid. Leverage only adds margin headroom in position_size; exposure is set by the budget.

Verification Results

Optional, for the Research Lead or the next revision: add the source's end_price exit (cancel, flatten remaining lots at market, set grid_active=False) so the port matches the executor exactly. No change is needed to pass this review.

The pinned GridExecutor (grid_executor.py, control_triple_barrier) has a take_profit_condition after the limit_price check: for side BUY, `mid_price > config.end_price` closes the executor with CloseType.TAKE_PROFIT. With keep_position=False, any remaining lots are then closed and the controller waits until mid is back in the band before creating a new executor. The hypothesis lists limit_price as the only flatten, and the code follows the hypothesis, so this is not a mismatch with the spec. The practical effect is small: by the time mid is above 0.75, almost every lot's 0.1% take-profit (at most ~0.7508) has already filled. What differs is that the port keeps the old grid, sized to the equity at its creation, with its top two buys resting. The source would rebuild a new grid sized to the equity at re-entry.

Keep a set of the lot indices that have a live buy_id, sell_id or quantity, and loop only over that set. Keep the free-level candidates for the nearest-level search in a sorted structure, e.g. bisect around the current price. The logic stays the same.

Runtime: on every 1-minute bar, _reconcile_lots, _place_lot_take_profits and _place_grid_buys each loop over all ~190 levels and do cache.order lookups. Over ~1.7M bars of history, the sandbox projects ~5,000 s per baseline backtest (runtime_review_required=true). That is expensive for a walk-forward with at least 25 trials per window.

None required.

min_bars_required() overrides the base method and returns 1. This is deliberate and consistent with the contract's warmup_bars=1, since a grid needs no indicator history. Noted only because Layer 1 flagged the override.

Each grid cycle earns a 0.1% take-profit against a 0.04% maker round trip, so the net is about 0.06% of the lot's notional. That is below the analyst's 0.10-0.15% per-trade floor, so the full backtest's avg_trade_return_pct will probably fail the floor unless the floor is applied net of maker fees. The P&L is many small wins (the 0.1% take-profits) against a rare large loss (the 0.35 flatten). Each limit_price event costs about 13% of equity in the worst case. Judge the result on drawdown and the frequency of limit_price events, not on win rate. On bar data, a touch fills a limit order with probability 0.7-0.95 and queue position is not modelled. Fills at an exact 0.1% take-profit are therefore probably optimistic relative to a real post-only queue. The source decides every 3 s; this port decides every 1 minute (20x). The mechanism is resting limit orders, which the engine can simulate. It is not a speed or queue race, so lesson L169 is noted here but does not block.

Backtest Review

Sharpe
-0.30
Total return
-0.16%
Max drawdown
0.39%
Trades
426
Win rate
13.6%
Profit factor
0.74

The trades match the port. All 163 are long. Lot sizes are about 300-500 WLD (about $180, which is 35% of equity spread over 190 levels). Entries rest at grid levels and exits come about 0.1% above, mostly within 1-10 minutes. The grid stays idle while price is outside the 0.38-0.75 band, which explains the empty months from 2023 to early 2025.

Risk stayed contained. Max drawdown is 13.3%, against the contract's 25% and buy-and-hold WLD's fall of more than 80% from its 2023-24 levels, so the result is not just market exposure. There was no liquidation.

Net expectancy is negative on a usable sample. A trade here is a full open-to-flat cycle of the net position, and there are 163 of them. The average trade return is -0.19% of notional, against an OKX floor of 0.15%. Profit factor is 0.97, Sharpe -0.04, total return -1.9%, and summed closed-trade P&L is -$10.

The per-lot economics leave nothing after costs. The take-profit is a fixed 0.1%, the maker round trip costs 0.04%, and modelled costs bring the net near zero. In 125 of the 163 closed trades the exit price is above the entry price, yet the price P&L is negative. For example, 261 WLD bought at 0.7011 and sold at 0.7018 is +$0.18 gross, but it is booked as -$0.007 with $0.073 commission. That is consistent with an extra modelled cost per fill (impact is folded into the result) roughly equal to the remaining edge. I have not verified the mechanism.

Most of the equity movement is unrealized inventory. Dec 2025 was -7.4%, Jan 2026 +11.5% and Feb 2026 -11.4%, while realized P&L is about $0. The month-to-month result follows WLD's direction, not the grid spread.

Activity is concentrated: 109 of the 163 closed trades fall in Feb 2026.

Iteration History

Verification failed (Layer 1.5 — hypothesis/config consistency) [class=hypothesis_mismatch]: - optimization_plan conflicting top-level and parameter leverage

Verification failed (Layer 3 — sandbox backtest): Invalid `value` for quantity: 0.0009999999999994458 was rounded to zero due to size increment 0.001 and size precision 3 Bar type used: WLDUSDT.OKX_SPOT-1-MINUTE-LAST-EXTERNAL, Bars processed: 0 Ensure your strategy produces trades with the given data and parameters.

Outcome Summary

A grid whose fixed per-level take-profit (0.1%) is close to round-trip fees plus modelled impact cannot clear the per-trade cost floor, and tuning the band or order count will not change that. A viable version needs a wider per-level spread, which makes it a different strategy from the author's defaults.

The backtest review abandoned it before optimization. Average trade return was -0.19% of notional, below the 0.15% OKX floor. The fixed 0.1% take-profit leaves almost nothing after the 0.04% maker round trip and modelled impact costs, and the take-profit, spacing and sizing were frozen in the research contract. The tunable band and order-count parameters could not raise the per-lot edge.

A faithful port of hummingbot's GridStrike on the WLDUSDT.OKX USDT swap: a long-only static grid of about 190 post-only buy levels between 0.38 and 0.75, each closed by a post-only take-profit 0.1% above its fill, with the whole grid flattened at market if price falls to 0.35. It ran at 2x account leverage with a grid budget of 35% of equity.

In the backtest report (Jul 2023 to Oct 2026, 1-minute bars), the strategy returned -15.8% with a Sharpe of -0.30, a 39.0% max drawdown, a 13.6% win rate, a profit factor of 0.74 and an average trade return of -0.19% of notional over 426 long trades. The backtest review cites a different, smaller sample (Jul 2023 to Feb 2026, 163 closed trades): -1.9% total return, Sharpe -0.04, profit factor 0.97, 13.3% drawdown and the same -0.19% average trade return.

Analysis

GridStrike

Analysis

grid_strike.py

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