Documentation is in progress. Every page in this section describes the current implementation and may change as PolyLab evolves.
Using the Scanner

Filters and Sorting

In Progress Last updated 2026-03-10

Exact documentation of the current filter and sorting behavior that the public app sends to the `/api/markets` endpoint.

Current Implementation

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.

FilterUI fieldAPI paramCurrent semantics
Included categoriesfilters.includedTagsincluded_tagsANY-match. A market passes if it has at least one selected tag.
Excluded categoriesfilters.excludedTagsexcluded_tagsExclude-on-ANY. A market is removed if it has any excluded tag.
Min pricefilters.min_pricemin_priceprice >= min_price on the selected outcome row.
Max pricefilters.max_pricemax_priceprice <= max_price on the selected outcome row.
Max spreadfilters.max_spreadmax_spreadspread <= max_spread; UI displays cents, API uses fractions.
Min APRfilters.min_apr_percentmin_aprUI uses percent, API uses fraction; backend filters on computed APR when it is not null.
Min volumefilters.min_volumemin_volumevolume_usd >= min_volume.
Min liquidityfilters.min_liquiditymin_liquidityliquidity_usd >= min_liquidity.
Searchfilters.searchsearchSQL LIKE against question and outcome_name.
Not sooner thanfilters.min_hours_to_expiremin_hours_to_expireConverts to end_date >= now + hours.
Expires withinfilters.max_hours_to_expiremax_hours_to_expireConverts to end_date <= now + hours.
Include expiredfilters.include_expiredinclude_expiredWhen expiry filters are active and this is false, the backend also adds end_date >= now.
Min profitablefilters.min_profitablemin_profitableCount of holders on this outcome where ws.total_pnl > 0.
Min losing oppositefilters.min_losing_oppositemin_losing_oppositeCount 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 keyMeaning
volume_usdHigher or lower raw volume on the current outcome row
liquidity_usdHigher or lower raw liquidity on the current outcome row
end_dateEarlier or later expiration
priceLower or higher current outcome price
spreadLower or higher quoted spread
aprLower or higher computed APR
questionAlphabetical market question
yes_profitable_countCount of profitable wallets on YES
yes_losing_countCount of losing wallets on YES
yes_totalTotal sampled holder rows on YES
no_profitable_countCount of profitable wallets on NO
no_losing_countCount of losing wallets on NO
no_totalTotal 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.