How to Screen Polymarket Markets by Price, Spread, Liquidity, and Expiry
Polymarket has thousands of open outcomes at any time, so finding tradable ones means filtering on price band, bid-ask spread, liquidity depth, and time to resolution. This page documents the exact filter and sort semantics the PolyLab scanner applies and the query parameters behind each one.
Filter semantics
The scanner frontend serializes the current filter state into query parameters for GET /api/markets. The backend then applies SQL conditions over the current snapshot plus holder-enrichment tables.
| Filter | UI field | API param | Current semantics |
|---|---|---|---|
| Included categories | filters.includedTags | included_tags | ANY-match. A market passes if it has at least one selected tag. |
| Excluded categories | filters.excludedTags | excluded_tags | Exclude-on-ANY. A market is removed if it has any excluded tag. |
| Min price | filters.min_price | min_price | price >= min_price on the selected outcome row. |
| Max price | filters.max_price | max_price | price <= max_price on the selected outcome row. |
| Max spread | filters.max_spread | max_spread | spread <= max_spread; UI displays cents, API uses fractions. |
| Min APR | filters.min_apr_percent | min_apr | UI uses percent, API uses fraction; backend filters on computed APR when it is not null. |
| Min volume | filters.min_volume | min_volume | volume_usd >= min_volume. |
| Min liquidity | filters.min_liquidity | min_liquidity | liquidity_usd >= min_liquidity. |
| Search | filters.search | search | SQL LIKE against question and outcome_name. |
| Not sooner than | filters.min_hours_to_expire | min_hours_to_expire | Converts to end_date >= now + hours. |
| Expires within | filters.max_hours_to_expire | max_hours_to_expire | Converts to end_date <= now + hours. |
| Include expired | filters.include_expired | include_expired | When expiry filters are active and this is false, the backend also adds end_date >= now. |
| Min profitable | filters.min_profitable | min_profitable | Count of holders on this outcome where ws.total_pnl > 0. |
| Min losing opposite | filters.min_losing_opposite | min_losing_opposite | Count of holders on the opposite outcome where ws.total_pnl < 0. |
Tag behavior
Included tags are OR logic
If you select multiple included tags, a market only needs one of them to pass. This is not an AND intersection filter.
Excluded tags are hard blocks
If any excluded tag is attached to a market, the market is removed from the result set.
Tags come from event-level data
Tags are resolved from the associated event payload, not invented by PolyLab locally. If the event metadata changes upstream, the market can move in or out of a tag-based filter.
Sort options
The backend only supports the following sort_by values today:
| Sort key | Meaning |
|---|---|
volume_usd | Higher or lower raw volume on the current outcome row |
liquidity_usd | Higher or lower raw liquidity on the current outcome row |
end_date | Earlier or later expiration |
price | Lower or higher current outcome price |
spread | Lower or higher quoted spread |
apr | Lower or higher computed APR |
question | Alphabetical market question |
yes_profitable_count | Count of profitable wallets on YES |
yes_losing_count | Count of losing wallets on YES |
yes_total | Total sampled holder rows on YES |
no_profitable_count | Count of profitable wallets on NO |
no_losing_count | Count of losing wallets on NO |
no_total | Total sampled holder rows on NO |
Important interpretation notes
Price filters act on outcome rows
PolyLab stores one row per outcome. Price filters do not operate on the whole market abstractly; they operate on the concrete outcome row currently being returned.
APR filters ignore null
If APR is missing for a row, that row cannot satisfy min_apr. This matters for expired markets, zero-price rows, or cases where the date math does not produce a positive duration.
Smart-money filters are view-specific in the frontend
The app only sends min_profitable and min_losing_opposite while the Smart view is active. They are real API parameters, but the default non-smart view does not send them.