Skip to content
All strategies

Strategies

Poke-for-Bargain Liquidity Provision After Forced-Selling Shocks: Stepped Maker Bids Below the Close of a >=3-Sigma High-Volume Down Hour, 12h Exit (OKX USDT perps ADA/DOGE/AVAX/DOT, 1H, long-only)

Outcome: Abandoned

OkxForcedSellingSteppedMakerBids

Outcome Summary

The strategy placed stepped maker bids after unusually large, high-volume down hours. Its sandbox run closed 294 positions averaging -0.378% per position. QA found two stop-related mismatches, so verification failed and the run was abandoned before a full backtest or optimization.

Hypothesis

Classification: (a), but only partly. Zero's FMZ post (2015) describes how to EXECUTE a buy, not WHEN to buy. Instead of lifting the ask, you post a bid deep inside the spread and raise it step by step ($1.05 -> $1.06 -> ... -> the ask) until an impatient counterparty hits it. On its own that gives no edge: it saves part of the spread and the maker/taker fee gap, about 0.03-0.05% per fill on an OKX perp. That is far too small to back a strategy, and lesson L167 rules out porting a sub-second tick loop as it……Show moreShow less

Classification: (a), but only partly. Zero's FMZ post (2015) describes how to EXECUTE a buy, not WHEN to buy. Instead of lifting the ask, you post a bid deep inside the spread and raise it step by step ($1.05 -> $1.06 -> ... -> the ask) until an impatient counterparty hits it. On its own that gives no edge: it saves part of the spread and the maker/taker fee gap, about 0.03-0.05% per fill on an OKX perp. That is far too small to back a strategy, and lesson L167 rules out porting a sub-second tick loop as it stands. So we keep the mechanism and restate it at bar resolution, attached to a moment when the post's premise is strongest: impatient sellers pay for immediacy. On OKX USDT swaps the trigger is a 1-HOUR bar whose log return is below -k x the trailing 30-day hourly sigma, on volume at least 3x the trailing 7-day median hourly volume. That is a forced/impatient selling hour (stop-outs, liquidations). After the trigger we act as the bargain-poker. Over the next 3 bars we post a LIMIT (maker) buy that starts deep and steps up toward the market: bar 1 bid = close x exp(-1.0 x sigma x depth_scale), bar 2 = close x exp(-0.5 x sigma x depth_scale), bar 3 = the trigger close. Each unfilled bid is cancelled at the bar's close and replaced with the next, higher one (the post's cancel-and-raise loop). If nothing fills by bar 3 we skip the event and never chase with a market order, so a fill only happens when sellers come down to us. Exit by market after hold_bars (12h), or earlier on a 3-sigma-equivalent stop. Instruments: ADAUSDT.OKX (primary), DOGEUSDT.OKX, AVAXUSDT.OKX, DOTUSDT.OKX. Each one triggers independently, one position per instrument, a 24h cooldown per instrument, and equal risk per event. Universe capped at 4 names (lesson L173). Source credit: Zero, 'High-frequency trading strategy: Poke for bargain', https://www.fmz.com/bbs-topic/41. source_ref: the paragraph that starts with the $1.05 -> $1.06 -> $1.07 stepped-bid description.

verification_loop: Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] The sandbox recorded 294 closed positions with an average return of -0.378% per position. The full backtest should assess whether rebounds cover trading costs; this result does not determine the correctness verdict. - [critical] The hypothesis specifies a 3-sigma stop, but the fixed configuration uses 4.0. This changes both the exit threshold and risk-based position size. (line 123) — fix: Set the fixed stop_sigma parameter to 3.0 and use it consistently for sizing and exits. - [critical] The code checks whether the hourly low breached the stop only after the bar closes, then submits a market exit. It therefore does not enforce the stated stop at the breached level. (line 124) — fix: Place a reduce-only stop-market order when the bid fills, and cancel it before a time-based exit.

Implementation

Long-only liquidity provision on OKX USDT perps (ADA primary; DOGE/AVAX/DOT extra legs, 1H). Each instrument triggers on its own when an hour's log return is <= -shock_k x the trailing 720-hour sigma and its volume is >= 3x the trailing 168-hour median. It then posts a stepped post-only maker bid ladder over 3 bars: trigger close x exp(-1.0*sigma*depth_scale), then x exp(-0.5*sigma*depth_scale), then the trigger close. Each unfilled bid is cancelled and raised; after step 3 the event is skipped, never chased. Exit is a market order after hold_bars hours or a 4-sigma stop. Risk is 1% per event, capped at 25% notional per name and 60% total, with a 24h cooldown per name.

Iteration 2 fix for sandbox_timeout. Signal, entry, exit and sizing logic are unchanged. (1) The 168-bar volume median was statistics.median(), which re-sorts the window on every bar. It is now an incrementally maintained sorted window (bisect insort/remove), so per-bar cost is O(log w) and does not depend on the backtest span. (2) Instrument-id lookups in _gross_notional now use a cached key->InstrumentId dict instead of a generator scan. Measured locally: BacktestRunner on ADA+DOGE/AVAX/DOT OKX 1H took 228s……Show moreShow less

Iteration 2 fix for sandbox_timeout. Signal, entry, exit and sizing logic are unchanged. (1) The 168-bar volume median was statistics.median(), which re-sorts the window on every bar. It is now an incrementally maintained sorted window (bisect insort/remove), so per-bar cost is O(log w) and does not depend on the backtest span. (2) Instrument-id lookups in _gross_notional now use a cached key->InstrumentId dict instead of a generator scan. Measured locally: BacktestRunner on ADA+DOGE/AVAX/DOT OKX 1H took 228s wall over 2024-05..2026-08. cProfile on a shorter window shows the strategy's own code at ~1.5s cumulative of 281s total. About 260s is ParquetDataCatalog file globbing (spec.glob/find, about 2.4M stat calls in parquet.py get_file_list_from_data_cls, 216s in the two bars() queries). That is catalog-size plumbing, not per-bar strategy work. Per lesson L155(c): if the sandbox times out again, the cause is catalog query cost and not this code, so it needs an infrastructure fix (catalog glob or a pre-filtered file list) rather than another code iteration. Note: some step-3 post-only bids at the trigger close are rejected because they would cross. That matches the hypothesis (no chasing; the event is skipped).

Verification Results

Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] The sandbox recorded 294 closed positions with an average return of -0.378% per position. The full backtest should assess whether rebounds cover trading costs; this result does not determine the correctness verdict. - [critical] The hypothesis specifies a 3-sigma stop, but the fixed configuration uses 4.0. This changes both the exit threshold and risk-based position size. (line 123) — fix: Set the fixed stop_sigma parameter to 3.0 and use it consistently for sizing and exits. - [critical] The code checks whether the hourly low breached the stop only after the bar closes, then submits a market exit. It therefore does not enforce the stated stop at the breached level. (line 124) — fix: Place a reduce-only stop-market order when the bid fills, and cancel it before a time-based exit.

Iteration History

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

Match the stop to the hypothesis and enforce it with an order when a bid fills.

After two iterations, Layer 4 QA verification failed: the configured stop was 4 sigma instead of the proposed 3 sigma, and the code submitted a market exit only after a bar breached the stop. Later stages were not reached.

The strategy tried to buy forced-selling shocks in four OKX USDT perpetuals using stepped maker bids.

The sandbox recorded 294 closed positions averaging -0.378% per position. No full backtest or optimization report is present.

Abandon Reason

verification_loop: Verification failed (Layer 4 — QA review) [class=hypothesis_mismatch]: - [edge_concern] The sandbox recorded 294 closed positions with an average return of -0.378% per position. The full backtest should assess whether rebounds cover trading costs; this result does not determine the correctness verdict. - [critical] The hypothesis specifies a 3-sigma stop, but the fixed configuration uses 4.0. This changes both the exit threshold and risk-based position size. (line 123) — fix: Set the fixed stop_sigma parameter to 3.0 and use it consistently for sizing and exits. - [critical] The code checks whether the hourly low breached the stop only after the bar closes, then submits a market exit. It therefore does not enforce the stated stop at the breached level. (line 124) — fix: Place a reduce-only stop-market order when the bid fills, and cancel it before a time-based exit.

Analysis

高频交易策略之Poke for bargain

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