Skip to content

Harvester ASK-side liquidity research — 2026-05-06 ​

Question ​

For a resting BUY at price ≤ 0.95 (typical harvester entry — DIRECTIONAL_MAX_ENTRY_PRICE=0.98, DIRECTIONAL_MIN_ENTRY_PRICE=0.97, DIRECTIONAL_ENTRY_PRICE_BASE=best-bid, +1¢ offset → real fill price clusters 0.94–0.98), how often did the captured order books actually offer matching ask liquidity? What entry knobs would translate to non-zero fill probability?

Method ​

Custom Rust binary crates/pm-analytics/src/bin/book_depth_analyzer.rs:

  • Streams ws_market.jsonl.zst frame-by-frame (multi-frame zstd, no whole-file load).
  • Per asset_id, reconstructs ASK orderbook from WsBookSnapshot (full replace) + WsBookDelta side=Sell (per-level mutate).
  • At every book mutation, computes cumulative depth at price ≤ {0.93, 0.94, 0.95, 0.96, 0.97, 0.98, 0.99}.
  • Accumulates per-asset stats: max depth, mean depth when present, fillable-time = cumulative seconds when depth ≥ DIRECTIONAL_MIN_SIZE_SHARES=5.
  • Time-delta cap = 60 s/sample (one-shot long gaps shouldn't inflate counts).

Two independent capture sessions analysed (different days, different active markets):

Session idws_market sizeframesunique assetsspan
1778021322240182207-directional596 MB34,036,1334,259~9.7 h
1778073881466125560-directional186 MB10,522,2051,292~3.5 h

Total: ~13 h of live Polymarket WS book traffic, ~5,500 unique assets touched.

Headline finding ​

Harvester at price ≤ 0.95 will almost never fill. Across 13 h of book history covering 5,551 assets:

ThresholdSession A fillable hrsSession B fillable hrsA: % assets ever fillableB: % assets ever fillable
≤ 0.930.0070.0000.5%0.4%
≤ 0.940.0070.0000.6%0.4%
≤ 0.950.0100.0060.7%0.8%
≤ 0.960.0210.0160.9%1.5%
≤ 0.970.0880.0941.6%3.4%
≤ 0.980.1840.2192.1%5.6%
≤ 0.990.2610.3283.0%7.4%

Read this row by row:

  • At ≤ 0.95: across all 4259 assets in session A there were 37 cumulative seconds of fillable depth — that's the entire window in which any harvester order at ≤0.95 could have transacted with the visible ask side. Median market sees 0 seconds.
  • Each step up (0.95 → 0.96 → 0.97) doubles or triples both the asset-coverage and the cumulative time. The flow lives between 0.97 and 0.99, not below 0.95.
  • The single-asset depth distribution (full table in harvester-depth-1778021322.md) shows that when liquidity does appear, it tends to be substantial (mean depth 50–1044 shares when present); it just appears for sub-second flashes.

Implications for strategy knobs ​

What would NOT work ​

  • DIRECTIONAL_MAX_ENTRY_PRICE=0.95 with entry_price_base=best-bid + 1¢ → place at ~0.94, hope ask drops to 0.94: probability ≈ 0.5–0.7% of assets ever, median 0 seconds per asset. Confirms the production observation: bot places ~50 orders, sits in spread, fills 0 in hours.
  • DIRECTIONAL_MAX_ENTRY_PRICE=0.95 + entry_mode=market (cross book): would fill instantly, but at ~0.97 ask → out-of-bounds at the max_entry_price filter → still rejected at evaluate_signal step.
  • entry_price_base=best-ask − 1¢ at the existing 0.95 cap: same problem — the −1¢ improvement still has to land at ≤ 0.95.

What COULD work ​

  • DIRECTIONAL_MAX_ENTRY_PRICE=0.99 — opens the window where ~3-7% of assets show fillable depth. ~0.3 asset-hours per 10 h of capture = roughly 1 fill per asset per few hours of typical market. Realistic floor for a harvester to actually transact.
  • entry_mode=market + MAX_ENTRY_PRICE=0.99 — taker fills at best_ask which is mostly in 0.97–0.99 range. Eats ~1¢ slippage vs 0.97 mid but guarantees execution. Subtract Polymarket taker fee (the active fee table — verify via clob_client::get_market; this analysis does not derive PnL).
  • Price-base shift to best-ask − 0¢ (post_only=true) — improver order, joins the spread on the buy side, more likely to attract aggressive sellers when ask retreats. Still subject to depth window.

Subtle: depth distribution shape ​

When fillable depth appears at ≤ 0.95, it's not "1 share at 0.95, fills our 5-share order partially". The mean depth when present is 50–1044 shares (many top assets show >1000 max). This means: when the window opens it opens for substantial volume, just briefly. A taker order would consume a lot; a passive maker rarely sees the window persist long enough to be hit.

The capture frame rate (sub-second, 34M snapshots over 10 h ≈ 1 frame per ms on average) means we can resolve sub-second windows. The ~0.01 asset-hours total of fillable depth at ≤ 0.95 across 4259 assets = about 30 seconds spread across 30 markets per 10 hours, i.e. each "fillable window" is on the order of seconds, opening a few times per market per day.

Recommendation ​

Three concrete tests to escalate:

  1. Knob raise: DIRECTIONAL_MAX_ENTRY_PRICE=0.99, MIN_ENTRY_PRICE=0.97, entry_price_base=best-bid, +1¢, post_only=true. Run a capture session, replay, count fills. Expectation: 1-3 fills per asset per 24 h, modest PnL footprint per fill (high probability outcomes have low PnL margin).
  2. Market-cross knob: entry_mode=market, MAX_ENTRY_PRICE=0.99. Replay, measure slippage. Expectation: every admitted candidate fills at ~best_ask = 0.97-0.99 immediately. Lose ~1¢ on improvement but guaranteed exec.
  3. Variant C revisit: entry_price_base=best-ask, offset=-1¢, post_only=true with the higher cap (0.99). Improver pattern: become the new best_bid by 1¢ above old, attract aggressive sellers. Replay measures hit-rate.

Tests #1 and #2 are config-only — single-variant sweeps via grids/ toml + pm-analytics sweep. Test #3 same.

Caveats / known limitations of this analysis ​

  • Snapshot-only sessions: both sampled sessions show deltas=0 per asset. Polymarket WS market channel emits full snapshots more often than the schema admits incremental deltas — the analyzer correctly treats each snapshot as full replace, but a delta-heavy session would have richer mid-snapshot resolution. Conclusion does not change.
  • Asset selection bias: capture file contains every asset the bot subscribed to during that session, which is biased toward markets the bot already had orders or candidates on (weather binaries with extreme probabilities). General Polymarket market depth at ≤ 0.95 may differ on, e.g., presidential or sports markets — this analysis does NOT generalise to them.
  • No PnL projection: this study measures fill probability under given price-range knobs. Translating "fillable-time" into "expected USD/hour" requires combining with strategy fees, exit-price model, and adverse-selection cost — separate work.
  • Time-delta cap of 60 s prevents single-frame gaps from inflating fill-time when a market falls quiet. Sessions with longer typical between-snapshot gaps would under-count proportionally; the cap is conservative.

Reproduce ​

sh
cargo build -p pm-analytics --bin book-depth-analyzer

scp target/debug/book-depth-analyzer deploy@<vps>:/tmp/

ssh deploy@<vps> '
  sudo /tmp/book-depth-analyzer \
    --captures-root ~/pm-backtest/instances/<instance>/data/captures \
    --session <session_id> \
    --thresholds 0.93,0.94,0.95,0.96,0.97,0.98,0.99 \
    --reference 0.95 \
    --min-fill-size 5
'

The binary is sized ~350 MB (debug). Production-friendly version would build with --release (~25 MB) when the run is recurring.

Artefacts ​