Filters and Sorting
Exact documentation of the current filter and sorting behavior that the public app sends to the `/api/markets` endpoint.
This page is intentionally explicit and implementation-first. If the product or upstream APIs change, the current behavior described here can change with them.
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.