Entry-gate chain for the pmv2 auto-trade engine (Phase P2.4, branch autotrade-foundation). Implemented in crates/polymarket-fetch/src/pmv2/autotrade/gates.rs, registered via pub mod gates; in autotrade/mod.rs.
For Agents
All five gates return
GateOutcome. The engine runs them in sequence before callingengine::ingest. Gates are pure functions — zero I/O. See pmv2-autotrade-engine-design for where each gate sits in the overall safety architecture (§ “Economic gates”).
GateOutcome
pub enum GateOutcome {
Proceed,
Skip(String),
}Every gate returns this. Skip carries a human-readable reason for logging.
Gates
1. liveness_gate
pub fn liveness_gate(view: &MarketStatusView, min_secs_to_resolution: i64) -> GateOutcomeRejects on any of:
- Market is resolved, closed, or inactive
- Market is not accepting orders
- Resolution time is unknown or the epoch placeholder (
end_time < 2000) - Seconds to resolution is below
min_secs_to_resolution - Market is resolution-proposed (
resolution_proposedset) — reason"market in resolution"(added 2026-06-17, commit8b3c451; catches a UMA-proposedmarket that is stillclosed=false/acceptingOrders=truebut whose result is already in — see autotrade-resolution-proposed-entry-guard)
Fail-closed on unknown end time — a market with no resolvable end date is always skipped. Entry-only: the resolution-proposed rejection does NOT make a market “terminal” for exits — a held position must stay sellable.
2. category_gate
pub fn category_gate(filter: Option<&CategoryFilter>, category: Option<&str>) -> GateOutcomeDelegates directly to crate::pmv2::category::passes_filter(&CategoryFilter, Option<&str>) -> bool. When filter is None, returns Proceed unconditionally (no filter configured = allow all). Reuses the same filter logic as /topbets eq:/neq: bucket filtering — no new filter code.
CategoryFilter and passes_filter are at crate::pmv2::category, confirmed by import resolution during implementation.
3. price_bounds_gate
pub fn price_bounds_gate(price: Decimal, min_entry: Decimal, max_entry: Decimal) -> GateOutcomeSkips if price < min_entry or price > max_entry. Enforces the [min_entry_price, max_entry_price] config bounds described in the design (no edge near 0 or 1). Also reused on the live best_ask by the ask-band gate in plan_context (commit 824a22d) so a market that collapsed after the copied trader entered is rejected — the cohort gate otherwise validates only the trader’s frozen reference price. See autotrade-resolution-proposed-entry-guard.
4. ev_floor_gate
#[allow(clippy::cast_precision_loss)]
pub fn ev_floor_gate(size_usd: f64, est_round_trip_fee_usd: f64, min_edge_bps: i64) -> GateOutcomeComputes edge = size_usd × (min_edge_bps as f64) / 10_000.0. Proceeds only if edge > est_round_trip_fee_usd. The #[allow(clippy::cast_precision_loss)] suppresses the i64 as f64 cast lint — acceptable because min_edge_bps will always be a small config value (single digits to low hundreds).
The caller supplies est_round_trip_fee_usd via the est_round_trip_cost_usd helper (see below) so the gate itself stays pure.
5. chase_gate
pub fn chase_gate(reference_price: Decimal, best_ask: Decimal, max_premium_bps: i64) -> GateOutcomeCap = reference_price × (1 + max_premium_bps / 10_000). Skips if best_ask > cap. reference_price is the price frozen at first detection (carried on SignalAction::Enter { reference_price } in autotrade-signal-type) — this prevents chasing a price that has moved against you since the signal fired.
Helper
pub const ROUND_TRIP_COST_RATE: f64 = 0.02;
pub fn est_round_trip_cost_usd(size_usd: f64) -> f64 {
size_usd * ROUND_TRIP_COST_RATE
}Round-trip cost approximation (buy fee + sell fee + 2× expected slippage ≈ 2%). Callers pass this into ev_floor_gate. The constant is pub so it can be overridden or inspected in tests.
Test coverage
9/9 unit tests pass. Clippy clean (pedantic + nursery, the cast_precision_loss allow is intentional).
Gate script note
scripts/gate.shpassed clean. The intermediateexit=1observed during development wasrtk proxyintercepting-- -D warningsas a filename (the known rtk mangling issue — see ci). Confirmed by runningrtk proxy cargo clippy --workspace --all-targets -- -D warningsdirectly, which finished clean.
Related
- autotrade-resolution-proposed-entry-guard — adds the
resolution_proposedrejection toliveness_gateand reusesprice_bounds_gateon the live ask (the “buying an already-decided market” fix) - pmv2-autotrade-engine-design — full engine design; this file implements § “Economic gates (around frozen
decide_copy, never inside)” - autotrade-signal-type —
SignalAction::Enter { reference_price }feeds thechase_gatereference price - autotrade-authz-module — sibling P2.2 module (authorization layer)
- pmv2-topbets-categories-filters —
CategoryFilterandpasses_filterorigin;category_gatereuses these directly - ci — gate script + the rtk
-D warningsmangling gotcha