# PolyLab > PolyLab is an independent Polymarket analytics platform, strategy scanner, and data intelligence engine. It scans prediction markets, tracks wallet-level Smart Money positioning, and calculates risk/yield metrics (APR, CLOB spreads, liquidity depth) to help traders and researchers identify edge and market dislocations. - Website: https://www.polylab.app - Documentation: https://www.polylab.app/docs - Live Scanner: https://www.polylab.app/app - Live Markets Markdown Feed: https://www.polylab.app/markets.md - Public API: https://api.polylab.app/api/markets - LLM Full Digest: https://www.polylab.app/llms-full.txt ## Key Concepts & Metrics - **Smart Money Flow & Ratings**: PolyLab samples the top 20 holders per outcome (YES/NO) from Polymarket's on-chain data and analyzes their lifetime realized historical profit/loss (P/L). Wallets with > $0 lifetime P/L are classified as profitable traders. A side is marked dominant when >= 12 of the top 20 holders on that side are historically profitable. - **Annualized Percentage Rate (APR)**: Annualized hold-to-maturity yield calculated as `((1.0 - best_ask) / best_ask) * (365.25 / days_to_resolution) * 100%`. Helps traders compare yield opportunities across prediction markets and fixed-income benchmarks. - **Spread & Liquidity Depth**: Difference between best ask and best bid (`best_ask - best_bid`) on Polymarket's Central Limit Order Book (CLOB), measuring transaction cost, market efficiency, and liquidity slippage. - **Strategy Playbooks**: Pre-configured scanner presets for different risk profiles: - *Safe Haven*: High confidence (>80% probability), tight bid-ask spread (<4%), positive annualized return. - *Smart Money Flow*: Markets where top profitable traders are clustered overwhelmingly on one side. - *Longshot / Asymmetric Alpha*: Low probability (<20%), low price entry with high liquidity and asymmetric upside. - *Yield Farmer*: High APR (>15%) on short-duration markets nearing resolution. ## Documentation Index - [Documentation](https://www.polylab.app/docs): Read the public PolyLab documentation for scanner workflows, data sources, pipeline behavior, and current metric methodology. - [Getting Started](https://www.polylab.app/docs/getting-started): Learn the first useful PolyLab workflow, what the scanner shows, and how to avoid the most common interpretation mistakes. - [How PolyLab Works](https://www.polylab.app/docs/how-polylab-works): Understand the current PolyLab pipeline from upstream market APIs through snapshots, enrichment, derived metrics, and public scanner output. - [Workflows](https://www.polylab.app/docs/workflows): Explore practical PolyLab scanning workflows based on current presets, filters, and sorting behavior in the public app. - [How to Screen Polymarket Markets by Price, Spread, Liquidity, and Expiry](https://www.polylab.app/docs/scanner/filters-and-sorting): The exact filter and sort semantics for screening Polymarket outcomes by price, spread, liquidity, volume, expiry, and smart money counts, including the underlying query parameters. - [Market Details and Holders](https://www.polylab.app/docs/scanner/market-details-and-holders): Understand the current PolyLab market detail surface, holder rows, aliases, and the limits of the YES/NO holder tables. - [AI Chat](https://www.polylab.app/docs/ai-chat): Understand how the current PolyLab AI chat works, including filter mapping, tag selection, market previews, confirmation tokens, and safety boundaries. - [Polymarket Metrics Explained: Price, Spread, Liquidity, and Implied Odds](https://www.polylab.app/docs/metrics/core-metrics): What price, implied probability, implied odds, spread, volume, liquidity, and expiration mean on Polymarket, and which values are raw upstream fields versus locally derived ones. - [How to Calculate APR on a Polymarket Position](https://www.polylab.app/docs/methodology/apr): The exact formula for annualized return on a Polymarket outcome, when APR cannot be computed, and why short-dated markets show misleadingly large numbers. - [How to Track Smart Money Wallets on Polymarket](https://www.polylab.app/docs/methodology/smart-money): How wallet-level smart money tracking on Polymarket actually works, the exact profit-and-loss counting rules PolyLab applies, and the sampling limits that constrain what the counts can tell you. - [How Often Polymarket Market and Smart Money Data Refreshes](https://www.polylab.app/docs/data/freshness-and-limitations): How current Polymarket data in PolyLab actually is, why a scanner snapshot can differ from the live Polymarket interface, and when those gaps are largest. - [Markets and Events Source](https://www.polylab.app/docs/sources/markets-and-events): Learn how PolyLab uses the Gamma markets and events APIs, which parameters it sends, and how upstream fields become scanner rows. - [Holders and Wallet PnL Source](https://www.polylab.app/docs/sources/holders-and-wallet-pnl): Learn how PolyLab fetches holders and wallet PnL, how aliases are captured, and why the current smart-money sample is intentionally limited. - [Refresh, Storage, and Snapshots](https://www.polylab.app/docs/pipeline/refresh-storage-and-snapshots): Understand the PolyLab refresh scheduler, storage tables, snapshot replacement process, and smart-money enrichment cadence. - [Upstream to PolyLab Field Map](https://www.polylab.app/docs/reference/upstream-field-map): Use this reference to map upstream Polymarket fields into the PolyLab database and scanner display fields. - [Scanner Query and Filter Contract](https://www.polylab.app/docs/reference/scanner-query-contract): Read the exact current query contract for the PolyLab scanner API, including filters, sorting, and smart-money count parameters. - [Polymarket Data API: Public Endpoints and Response Fields](https://www.polylab.app/docs/reference/public-api-contract): JSON endpoints for Polymarket market, tag, and holder data, with the response fields each one returns and the query contract they follow. - [FAQ](https://www.polylab.app/docs/faq): Read practical FAQ answers about PolyLab data freshness, methodology, smart-money interpretation, and scanner limitations. - [Access Model](https://www.polylab.app/docs/appendix/access-model): Read the public PolyLab access-model notes and how they relate to the scanner and documentation surface. - [Storage and Runtime Reference](https://www.polylab.app/docs/appendix/storage-and-runtime): Read the appendix reference for current PolyLab runtime jobs, storage tables, and snapshot assembly details. ## Public API Endpoints - `GET https://api.polylab.app/api/markets`: Query active Polymarket prediction markets with filtering by price, spread, APR, liquidity, and smart money. - `GET https://api.polylab.app/api/markets/{id}/holders`: Get top YES/NO holders with PnL statistics for a specific market. - `GET https://api.polylab.app/api/tags`: List all active category tags and their associated market counts. - `GET https://api.polylab.app/api/status`: Check service status and the timestamp of the latest data snapshot. ## Extended LLM Context - [Full Documentation & Methodology Digest](https://www.polylab.app/llms-full.txt): Comprehensive concatenated technical documentation and methodology for deep context ingestion. --- # PolyLab Full Technical Documentation Digest > This document contains the complete technical documentation, architecture specifications, metric formulas, and API contracts for PolyLab. ## Start Here > Documentation URL: https://www.polylab.app/docs Summary: Public documentation for the current PolyLab implementation, including scanner usage, upstream data sources, pipeline behavior, and derived metrics. ## Documentation PolyLab is a Polymarket market scanner built around periodic snapshots, derived metrics, and holder-based enrichment. This documentation is intentionally implementation-first: it explains the system that exists today, not a future idealized version. ## Start Here - [Getting Started](/docs/getting-started) explains the first useful scanner workflow and the quickest way to get signal out of the app. - [How PolyLab Works](/docs/how-polylab-works) gives the big-picture flow from Polymarket upstream APIs into the public scanner. - [Workflows](/docs/workflows) covers concrete scanning patterns such as Safe Haven, Sniper / Last Min, Coinflip, and Newsmaker. ## Using the Scanner - [AI Chat](/docs/ai-chat) explains how Ask PolyLab maps natural-language requests to filters, previews, and safe confirmed actions. - [Filters and Sorting](/docs/scanner/filters-and-sorting) documents the exact query semantics behind include/exclude tags, expiry windows, price, spread, APR, and smart-money dominance filters. - [Market Details and Holders](/docs/scanner/market-details-and-holders) explains the market row, detail panel, and the YES / NO holder tables. ## Understanding the Data - [Core Metrics](/docs/metrics/core-metrics) defines price, implied probability, spread, volume, liquidity, expiration, category, and implied odds. - [APR Methodology](/docs/methodology/apr) documents the exact APR formula used in the code and when the value is intentionally `null`. - [Smart Money Methodology](/docs/methodology/smart-money) documents the current counts-based holder logic, the PnL-sign rules, and the known limitations. - [Freshness and Limitations](/docs/data/freshness-and-limitations) documents refresh cadence, snapshot behavior, and why numbers can diverge from live Polymarket surfaces. ## Data Sources - [Markets and Events Source](/docs/sources/markets-and-events) covers the Gamma endpoints, request parameters, and the field enrichment flow. - [Holders and Wallet PnL Source](/docs/sources/holders-and-wallet-pnl) covers the holders and wallet PnL APIs, alias capture, and the current top-holder cap. ## Pipeline - [Refresh, Storage, and Snapshots](/docs/pipeline/refresh-storage-and-snapshots) explains the hourly scrape cycle, 6-hour smart-money enrichment, table roles, and snapshot replacement rules. ## Reference - [Upstream to PolyLab Field Map](/docs/reference/upstream-field-map) maps raw upstream fields to PolyLab storage and display fields. - [Scanner Query and Filter Contract](/docs/reference/scanner-query-contract) documents the public query parameters and exact semantics behind them. - [Public API Contract](/docs/reference/public-api-contract) documents the public endpoints that the scanner calls today. - [FAQ](/docs/faq) answers the practical questions users repeatedly ask about data, limitations, and interpretation. ## Appendix - [Access Model](/docs/appendix/access-model) explains how the public access positioning works today. - [Storage and Runtime Reference](/docs/appendix/storage-and-runtime) documents the SQLite tables, refresh scheduler, and runtime shaping behind the app. --- ## Start Here > Getting Started URL: https://www.polylab.app/docs/getting-started Summary: The fastest path from opening PolyLab to a useful market shortlist, with the exact things to watch first. ## What PolyLab is PolyLab is a scanner for Polymarket markets. It does not place orders, it does not stream a live order book, and it does not try to predict outcomes for you. It helps you search, rank, and compare markets using a snapshot of public market data plus holder and wallet-PnL enrichment. ## What PolyLab is not - It is not a brokerage interface. - It is not a live execution terminal. - It is not financial advice. - It is not a guarantee that a high-APR or high-dominance market is mispriced. ## First 10 minutes 1. Open `/app` and keep the default sort by volume. 2. Narrow the universe with tags, price range, and a spread ceiling. 3. Sort by one variable at a time so you know what is moving the list. 4. Open a market detail panel and verify the market link, category, dates, and holders context. 5. Cross-check timing and market structure on Polymarket before acting. ## Recommended first workflow ### Start with liquid markets Raise `Min Volume` and `Min Liquidity` first. This removes a large amount of thin and noisy inventory before you look at more interpretive metrics. ### Add a spread ceiling Use `Max Spread` early. Spread is one of the fastest ways to filter out markets that look interesting on paper but are costly to enter or exit. ### Use price range as a scenario filter Price in PolyLab is the displayed market price for a specific outcome. In practice: - `0.95` to `1.00` isolates very high-probability outcomes. - `0.45` to `0.55` isolates near-coinflip outcomes. - `0.00` to `0.15` isolates long shots. ### Use APR carefully APR is an annualized expression of the current `price` and time to expiry. It is a useful ranking hint, not a standalone decision rule. Read [APR Methodology](/docs/methodology/apr) before using it aggressively. ### Use Smart Money as context, not proof Smart-money fields in the scanner are counts derived from top holder rows plus wallet PnL sign. They are not a direct model of trader conviction. Read [Smart Money Methodology](/docs/methodology/smart-money) before treating them as signal. ## Common mistakes ### Treating snapshots like live tape PolyLab works from hourly market snapshots rather than a live order book stream. A number can already be stale by the time you see it. ### Reading counts as edge `yes_profitable_count` and related fields only mean that currently sampled holders on one side map to wallets with positive or negative tracked PnL. That is a much weaker claim than “smart money is betting this side”. ### Ignoring expiry context A market with attractive metrics but only a few hours left behaves very differently from a market with months left. Always pair price, spread, and APR with time to expiry. ## What to read next - [How PolyLab Works](/docs/how-polylab-works) - [Filters and Sorting](/docs/scanner/filters-and-sorting) - [Core Metrics](/docs/metrics/core-metrics) - [Freshness and Limitations](/docs/data/freshness-and-limitations) --- ## Start Here > How PolyLab Works URL: https://www.polylab.app/docs/how-polylab-works Summary: A high-level walkthrough of the current PolyLab data flow, from Polymarket upstream APIs to scanner rows and holder-based enrichment. ## End-to-end overview PolyLab is built around a periodic data pipeline: 1. Fetch active Polymarket markets and events from the Gamma API. 2. Normalize them into outcome-level rows in SQLite. 3. Enrich those rows with tags, category, icon, event slug, and canonical Polymarket URL. 4. Periodically fetch holder lists for each condition and wallet PnL for the wallets seen in those holder lists. 5. Compute derived display fields such as APR, implied odds, and counts-based smart-money context. 6. Serve the current snapshot through `/api/markets`, `/api/tags`, `/api/status`, and `/api/markets/{market_id}/holders`. ## Why the system is snapshot-based The scraper fetches the active market set, writes a fresh `active_market_outcomes` snapshot, and re-creates indices around that snapshot. This design favors a simple and predictable read path for the scanner over continuous tick-level updates. The tradeoff is important: - reads are simple and cheap - the scanner can stay fast on static hosting plus a small FastAPI backend - the displayed state is not a live feed ## What comes directly from Polymarket PolyLab directly reuses upstream values such as: - market question - outcomes and outcome prices - spread - volume - liquidity - start and end dates - event slug and images when available - event tags These are documented in detail in [Markets and Events Source](/docs/sources/markets-and-events). ## What PolyLab computes PolyLab also computes or derives several fields locally: - per-outcome rows from a market-level payload - Polymarket event URL from the resolved `event_slug` - primary category from the first available event tag - APR from current `price` and time remaining - implied odds as `1 / price` - counts of profitable and losing holders per YES / NO side - refresh timestamps shown in the UI ## What Smart Money means here In the current implementation, Smart Money is not a proprietary predictive model. It is a descriptive layer built from: - top holder rows from the holders endpoint - wallet PnL values from the wallet-PnL endpoint - simple PnL-sign logic such as `total_pnl > 0` and `total_pnl < 0` That means Smart Money fields are best read as contextual counts, not as proof of informed positioning. ## Where to go deeper - [Markets and Events Source](/docs/sources/markets-and-events) - [Holders and Wallet PnL Source](/docs/sources/holders-and-wallet-pnl) - [Refresh, Storage, and Snapshots](/docs/pipeline/refresh-storage-and-snapshots) - [Upstream to PolyLab Field Map](/docs/reference/upstream-field-map) --- ## Using the Scanner > Workflows URL: https://www.polylab.app/docs/workflows Summary: Concrete ways to use the current scanner, based on the real presets and filters exposed in the app today. ## Why workflows matter The scanner is flexible enough that the same market can look attractive or irrelevant depending on your objective. A workflow gives you a disciplined order: universe reduction first, interpretation second. ## Safe Haven ### Goal Find high-probability outcomes with enough liquidity to be worth checking. ### Current preset logic - `min_price = 0.95` - `max_price = 1.00` - `min_liquidity = 500` ### How to use it After applying the preset, add a spread ceiling. High probability does not mean efficient execution. If the spread is wide, the market may still be unattractive. ## YOLO ### Goal Look for very cheap long shots with at least some activity. ### Current preset logic - `max_price = 0.05` - `min_volume = 1000` - `sort_by = price` - `sort_dir = asc` ### What to watch Cheap outcomes naturally produce large headline APR values when expiry is close. That is one of the easiest places to over-read annualization. ## Warren Buffett ### Goal Look for high-confidence, liquid markets with tighter spreads while excluding crypto-heavy inventory. ### Current preset logic - `min_price = 0.90` - `min_liquidity = 50000` - `max_spread = 0.02` - excludes a hard-coded crypto tag list ### What to watch This preset is a policy choice implemented in the frontend, not a universal definition of “quality”. The exclusion logic depends entirely on the event tags present in the snapshot. ## Sniper / Last Min ### Goal Look for markets ending soon, sorted by nearest expiry. ### Current preset logic - `max_hours_to_expire = 24` - `include_expired = false` - `sort_by = end_date` - `sort_dir = asc` ### What to watch Near-expiry markets can change quickly. PolyLab may still show an hourly snapshot that no longer matches the live Polymarket state. ## Coinflip Club ### Goal Find near 50/50 markets. ### Current preset logic - `min_price = 0.45` - `max_price = 0.55` These are useful when you want the scanner to show ambiguous or balanced markets rather than consensus-heavy ones. ## Long Shot ### Goal Search underdogs that still have at least some tradeability. ### Current preset logic - `max_price = 0.15` - `min_liquidity = 500` ## Newsmaker ### Goal Pull the highest-volume markets to the top. ### Current preset logic - `min_volume = 100000` - `sort_by = volume_usd` - `sort_dir = desc` ## Smart-money review workflow 1. Start from a liquid list, not from a raw full snapshot. 2. Apply one of the non-smart-money workflows first. 3. Add `min_profitable` and `min_losing_opposite` only after the market universe is already manageable. 4. Open holder tables to see whether the dominance counts are concentrated or thin. 5. Read the holder data as context, not as proof. ## Supporting docs - [Filters and Sorting](/docs/scanner/filters-and-sorting) - [Smart Money Methodology](/docs/methodology/smart-money) - [Freshness and Limitations](/docs/data/freshness-and-limitations) --- ## Using the Scanner > How to Screen Polymarket Markets by Price, Spread, Liquidity, and Expiry URL: https://www.polylab.app/docs/scanner/filters-and-sorting Summary: 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. ## Related pages - [Workflows](/docs/workflows) - [Scanner Query and Filter Contract](/docs/reference/scanner-query-contract) - [Smart Money Methodology](/docs/methodology/smart-money) --- ## Using the Scanner > Market Details and Holders URL: https://www.polylab.app/docs/scanner/market-details-and-holders Summary: How to read the current market row and detail view, including holder tables, aliases, and PnL-sign counts. ## Market row Each scanner row is an outcome-level record, not a market-level aggregate. That row includes: - market question - outcome name - current price - spread - APR - volume - liquidity - expiration - category When you sort by price, spread, APR, volume, or liquidity, you are sorting outcome rows directly. ## Detail panel The detail panel is where the raw identifiers become useful: - `market_id` - `condition_id` - Polymarket `url` - `event_slug` - category - start and end dates - implied odds The detail panel is the right place to verify whether you are looking at the correct event and the correct side. ## Holder tables The public holder endpoint returns a list of holder rows for a specific condition: - `wallet_address` - `position_size` - `outcome_index` - `total_pnl` - `alias` - `wallet_tag` The frontend groups those rows into YES and NO views by `outcome_index`. ## What aliases mean Aliases come from the holders API payload when present. They are not guaranteed to exist, and an empty alias should be treated as missing metadata rather than a negative signal. ## What `wallet_tag` means PolyLab currently recognizes a known system / rewards wallet and marks it with `wallet_tag = SYSTEM`. That label is operational metadata, not a claim that every system-tagged wallet is informative for trading decisions. ## What holder counts do and do not mean ### What they mean Counts such as `yes_profitable_count` or `no_losing_count` are simple descriptive counts over the sampled holder rows joined with wallet PnL signs. ### What they do not mean - They do not show every holder in the market. - They do not prove intent. - They do not measure position age. - They do not know whether a wallet hedged elsewhere. - They do not tell you whether a profitable wallet is profitable because of this market. ## Known display limits - Holders are sampled from the current holders endpoint, not reconstructed from complete trading history. - Current holder fetching is capped to top holders per outcome; see [Holders and Wallet PnL Source](/docs/sources/holders-and-wallet-pnl). - Large position size and positive tracked PnL are different concepts and should not be merged in your interpretation. ## Related pages - [Holders and Wallet PnL Source](/docs/sources/holders-and-wallet-pnl) - [Smart Money Methodology](/docs/methodology/smart-money) - [Public API Contract](/docs/reference/public-api-contract) --- ## Using the Scanner > AI Chat URL: https://www.polylab.app/docs/ai-chat Summary: How Ask PolyLab turns natural-language requests into scanner filters, previews, and safe confirmed user actions. ## What Ask PolyLab does Ask PolyLab is a chat drawer inside the scanner. It does not trade, place orders, or decide what a user should buy. Its job is narrower: answer PolyLab product questions, translate a natural-language request into a scanner setup, show a market preview when filters are relevant, and prepare safe user actions for explicit confirmation when needed. The useful mental model is: 1. User asks a PolyLab question or requests a market universe. 2. Backend maps the request to either an informational answer or structured filters and optional actions. 3. Backend runs a preview query only when filters or actions need market results. 4. User reviews the response. 5. User can apply filters immediately, open a preview market, or confirm account-level actions such as watches. ## User flow ### Opening the chat The chat is available from the scanner toolbar as `Ask PolyLab`. It requires a signed-in user. If the user is not signed in, the frontend opens the login modal instead of sending a chat request. ### Sending a message When the user sends a message, the frontend posts to `POST /api/agent/chat` with: - the message text - the current signed-in `user_id` - the recent chat history - the current scanner view and filters - a small set of currently visible market ids - the Supabase bearer token in the `Authorization` header The endpoint verifies the Supabase session before doing any planning. If the token is missing or the user id does not match, the request is rejected. ### Receiving a response The response can include: - `reply`: short assistant text shown in the drawer - `filters`: sanitized scanner filter state - `agent_constraints`: extra constraints used by the preview, such as smart-money percentage - `preview`: up to 8 matching market outcomes plus the full preview count - `suggested_actions`: safe UI actions or a confirmation summary - `action_token`: a signed token for actions that require confirmation - `blocked_actions`: requests the backend refused, such as trading - `knowledge_sources` and `agent_metadata`: diagnostics for planner source, fallback, and context For product questions such as "what is PolyLab?", "how does Smart Money work?", or "why can data differ from live Polymarket?", the response can be answer-only. In that case the backend returns no actions and an empty preview instead of showing unrelated market matches. ## Backend planning flow ### Context first Before planning, the backend builds context from two sources: - current tag stats from the market database - the agent knowledge pack under `docs/agent` - generated public documentation pages under `frontend_deploy/docs` Those knowledge chunks tell the agent how PolyLab filters work, how to treat categories and tags, how Smart Money percentages map to counts, which actions are allowed, how the product works, and where the current data limitations are. ### Planner selection The planner is selected from environment configuration: - If OpenAI planner support is configured, the backend asks the LLM for a structured plan. - If the LLM provider is disabled, unavailable, times out, or returns invalid output, the backend falls back to the rule-based planner. The fallback planner still returns the same shape: filters, constraints, actions, blocked actions, and a short reply. ### Backend is the authority The model is not trusted to write directly. After a plan comes back, the backend: 1. sanitizes filter values 2. post-processes category searches into tags when possible 3. validates every action against an allowlist 4. rejects trading/order action types 5. runs the market preview itself 6. signs confirmable actions only after validation ## Filters, tags, and search The most important rule is that broad topics should use tags, while specific wording should use search. | User intent | Better mapping | Why | | --- | --- | --- | | "football markets with smart money over 75" | `includedTags` such as `Soccer` or `FIFA World Cup`, `search = ""`, smart-money threshold | Football is a category/topic. Tag matching gives the backend the right market universe. | | "weather markets ending today" | Weather tag, `include_expired = false`, expiry window ending at the current UTC day boundary | Weather is a broad topic and today is a time filter. | | "Biden market" | `search = "Biden"` | This is specific wording that should be matched against market text. | | "crypto politics markets" | `includedTags = ["Crypto", "Politics"]`, `search = ""` | Multiple broad topics should not be concatenated into full text. | | "tight spread liquid markets" | `max_spread` and `min_liquidity` | These are numeric scanner filters, not text search terms. | This is why a phrase like `soccer football` in the search box is not a good result. The scanner search behaves like text matching over market wording. For category alternatives, the agent should use `includedTags` and leave `search` empty when suitable tags exist. ## Smart Money mapping Smart Money in PolyLab is based on sampled holder rows and wallet PnL sign. A percentage request is converted to a count threshold against the sampled holder set. | Request | Internal interpretation | | --- | --- | | `smart money 70%+` | `smart_money_min_win_pct = 70`, count threshold around 14 out of 20 | | `sm nad 75` | `smart_money_min_win_pct = 75`, count threshold around 15 out of 20 | | `smart money over 80` | `smart_money_min_win_pct = 80`, count threshold around 16 out of 20 | The preview still comes from the backend query. The chat response should be read as research filtering, not investment advice. ## Applying filters `Apply filters` is a client-side action. It does not change the user account and does not require confirmation. When applied, the frontend: 1. switches the app into workspace mode 2. resets any active saved preset selection 3. applies the returned filter state 4. updates URL parameters 5. fetches the matching market rows 6. shows a short "Filters applied" feedback message On mobile, applying filters also closes the chat, closes the mobile filters sheet, and scrolls to the market results. ## Preview markets The response preview is intentionally small. It returns a useful sample of matching market outcomes plus a count. Clicking a preview market opens that market in the scanner when it is already loaded. If it is not currently loaded, the frontend first applies the agent filters and then lets the refreshed result set represent the requested universe. ## Confirmed actions Some actions affect user-owned data and require explicit confirmation. | Action family | Confirmation required | Notes | | --- | --- | --- | | Set scanner filters | No | Pure client UI state. | | Switch scanner view | No | Pure client UI state. | | Open market detail | No | Pure client UI state. | | Create, update, or delete watches | Yes | Backend signs an action token and executes only after confirmation. | | Create, update, or delete saved presets | Yes | User-owned data. | | Update notification settings | Yes | User-owned notification configuration. | | Mark alert read | Yes | User-owned alert state. | | Buy, sell, trade, place order, cancel order | Blocked | Trading and order actions are outside the agent boundary. | Confirmable actions are signed with a short-lived backend token. The confirm endpoint verifies the token signature, expiry, action payloads, and user id before executing anything. ## Example requests ### Find a smart-money category ```text find football markets with smart money over 75 ``` Expected behavior: use football-related tags, set a smart-money threshold, show preview markets, and offer `Apply filters`. ### Find expiring weather markets ```text show weather markets ending today with liquidity ``` Expected behavior: use the Weather tag, exclude expired outcomes, set an expiry window for today, and prefer liquidity filtering if the request asks for it. ### Prepare watches ```text watch the matching markets ``` Expected behavior: prepare watch actions for the preview or selected markets, then require explicit confirmation before creating any watches. ### Trading request ```text buy the best one ``` Expected behavior: block the trading action and redirect the user toward research/filtering. The agent cannot place or prepare orders. ## Current limitations - The chat is an early beta feature. - The planner can fall back to rule-based behavior when LLM planning is unavailable. - The preview is snapshot-based, not a live order book. - Search is still text matching; broad categories should be tags. - Smart Money is a holder/PnL context signal, not a prediction model. - The agent can prepare account actions only through the confirmation flow. ## Related pages - [Filters and Sorting](/docs/scanner/filters-and-sorting) - [Smart Money Methodology](/docs/methodology/smart-money) - [Market Details and Holders](/docs/scanner/market-details-and-holders) - [Freshness and Limitations](/docs/data/freshness-and-limitations) --- ## Understanding the Data > Polymarket Metrics Explained: Price, Spread, Liquidity, and Implied Odds URL: https://www.polylab.app/docs/metrics/core-metrics Summary: On Polymarket, price is the outcome's share price between $0 and $1 and doubles as its implied probability; implied odds are 1 / price; spread is the gap between best bid and best ask; liquidity and volume are separate measures of depth and past activity. This page gives the exact definition and source of each field. A Polymarket market resolves to $1 for the correct outcome and $0 for the rest, so an outcome trading at $0.63 is the market pricing that outcome at roughly 63% probability. The fields below are the ones the PolyLab scanner exposes, each labeled with whether it comes straight from Polymarket or is computed locally. ## Price Price is the stored `outcomePrices[i]` value for the current outcome row. PolyLab does not model or smooth it. It is the snapshot value fetched from the Gamma market payload. ## Implied probability In PolyLab, implied probability is effectively the same displayed number as `price` for the outcome row. There is no separate probability model layered on top of it. ## Implied odds Implied odds are a presentation transform in the frontend: `1 / price` If `price <= 0`, implied odds are not meaningful. ## Spread Spread is stored from the upstream `spread` field. The API and database use a fraction, while the UI often presents it in cents or percentage-like shorthand for easier scanning. ## Volume `volume_usd` is taken directly from the upstream market payload. It is useful for ranking activity, but it is not the same thing as near-term executable liquidity. ## Liquidity `liquidity_usd` is also taken directly from the upstream market payload. It is one of the most practical coarse filters in the scanner because it removes a large amount of thin inventory quickly. ## Expiration Expiration is stored as `end_date`. Time-to-expiry filters are computed at query time by comparing `end_date` with the current backend time. ## Category Category is not an upstream market field by itself. PolyLab maps the first available event tag into the `category` column as a convenient primary label. ## Outcome Each row is a specific outcome name from the market payload, usually YES or NO for binary markets. The scanner is designed around outcome rows, not around a single abstract market object. ## Derived versus raw fields | Field | Source | Notes | | --- | --- | --- | | `price` | Upstream Gamma market payload | Direct snapshot value | | `spread` | Upstream Gamma market payload | Direct snapshot value | | `volume_usd` | Upstream Gamma market payload | Direct snapshot value | | `liquidity_usd` | Upstream Gamma market payload | Direct snapshot value | | `end_date` | Upstream Gamma market payload | Direct snapshot value | | `category` | Derived from event tags | First available tag becomes primary category | | `url` | Derived locally | Built from `event_slug` as a Polymarket event URL | | `apr` | Derived locally | Computed from price and time remaining | | implied odds | Derived in UI | `1 / price` | ## How to combine these metrics Use the raw fields for universe reduction and the derived fields for context: - use volume and liquidity to find usable markets - use spread to filter execution risk - use price to target scenario bands - use APR and smart-money counts only after the market is already interesting ## Related pages - [APR Methodology](/docs/methodology/apr) - [Upstream to PolyLab Field Map](/docs/reference/upstream-field-map) - [Markets and Events Source](/docs/sources/markets-and-events) --- ## Understanding the Data > How to Calculate APR on a Polymarket Position URL: https://www.polylab.app/docs/methodology/apr Summary: APR on a Polymarket outcome is the annualized return from buying at the current price and holding to a $1 payout, calculated as ((1 / price) - 1) * (365 / days to resolution). It is a ranking heuristic, not a return forecast, and it inflates sharply as expiry approaches. Prediction market outcomes on Polymarket trade between $0 and $1 and settle at exactly $1 or $0. That makes an annualized yield comparison possible: buying a YES share at $0.92 that resolves in 30 days returns roughly 8.7% over those 30 days, which annualizes to about 106%. PolyLab computes this figure for every outcome row so markets with different expiry dates can be ranked against each other. ## Exact formula The current implementation computes APR with the formula: ```text ((1.0 / price) - 1.0) * (365.0 / days) ``` Where `days` is the positive number of days between the current snapshot time and `end_date`. ## Preconditions APR is only computed when price is greater than 0 and less than 1. The backend also requires: - `end_date` is present - `snapshot_at` is present - `end_date` is later than `snapshot_at` If those conditions do not hold, APR stays `null`. ## When APR is null APR is intentionally `null` when: - `price <= 0` - `price >= 1` - the market has no usable end date - the market is expired at the snapshot time - the date math fails to produce a positive duration ## Why the formula looks this way The formula expresses a simple annualized return from buying an outcome at `price` and assuming a terminal payout of `1.0` if that outcome resolves correctly. It is a ranking heuristic, not a simulation of fees, slippage, or partial exits. ## Short-duration annualization can overstate weak setups APR gets mechanically larger as `days` gets smaller. A market with only a few hours left can show a very large annualized number even if the real-world trade is mediocre or difficult to execute. This is why APR should almost never be read alone. Pair it with: - spread - liquidity - expiry - market structure ## Storage versus fallback computation The scraper writes APR into `active_market_outcomes` during snapshot creation. The API also contains a fallback APR expression so older database snapshots without an `apr` column can still serve the field. ## Interpretation guidance - Use APR as a sorting aid, not as a proof of value. - Ignore APR on rows that are obviously too close to expiry to execute comfortably. - Treat APR as especially fragile in thin markets and long-shot markets. ## Related pages - [Core Metrics](/docs/metrics/core-metrics) - [Freshness and Limitations](/docs/data/freshness-and-limitations) - [Scanner Query and Filter Contract](/docs/reference/scanner-query-contract) --- ## Understanding the Data > How to Track Smart Money Wallets on Polymarket URL: https://www.polylab.app/docs/methodology/smart-money Summary: Smart money tracking on Polymarket works by reading the public holder list for a market, looking up each wallet's lifetime realized profit and loss, and counting how many historically profitable wallets sit on each side. PolyLab samples the top 20 holders per outcome, so the counts are context for further research, not a signal. Polymarket position data is on-chain, so any wallet holding an outcome can be listed and its trading history reconstructed. The common approach is to rank holders by whether they have made money historically, then check whether those wallets cluster on one side of a market. This page documents exactly how PolyLab implements that, including where the method breaks down. ## What Smart Money means in PolyLab today In the current implementation, Smart Money is a descriptive enrichment layer built from sampled holder rows and wallet PnL sign. It is not a model of informed flow, not a prediction engine, and not a complete reconstruction of market positioning. ## Current count logic The scanner computes counts by joining holder rows with wallet stats and applying simple sign rules: - profitable means `ws.total_pnl > 0` - losing means `ws.total_pnl < 0` That produces fields such as: - `yes_profitable_count` - `yes_losing_count` - `yes_total` - `no_profitable_count` - `no_losing_count` - `no_total` ## Outcome-specific semantics For a YES row: - `min_profitable` checks holder rows on YES where `ws.total_pnl > 0` - `min_losing_opposite` checks holder rows on NO where `ws.total_pnl < 0` For a NO row, the opposite-outcome logic flips accordingly through `1 - amo.outcome_index`. ## Source limits matter ### Top 20 holders per outcome The current holders client caps the effective sample to top 20 holders per outcome. Even when the scraper requests a much larger limit upstream, the client reduces it to a per-outcome maximum of 20 and re-sorts locally. This is the single most important caveat in the smart-money layer. ### Wallet PnL is a separate source Wallet profitability is not inferred from the market itself. It comes from a separate wallet-PnL API and is then joined back onto sampled holder addresses. ### `smart_money_win_rate` is not a current public filter Background jobs still compute a `smart_money_win_rate` value into `market_smart_money_stats`, but that field is not exposed as a live public scanner filter in the current app contract. Public docs must not describe it as an active user-facing control. ## Known data anomalies - a profitable wallet can be profitable for reasons unrelated to the current market - a losing wallet can still be directionally correct here but wrong elsewhere - the same wallet may hedge or net across venues - aliases may be missing or stale - holder sampling may miss smaller but relevant wallets - a system wallet can appear in the data, and PolyLab marks a known system wallet explicitly ## system wallet PolyLab currently inserts a known rewards/system wallet and tags it with `wallet_tag = SYSTEM`. This is useful for operational transparency, but it is not evidence of directionality. ## Do not treat Smart Money counts as proof Use the counts as context only: - they can help you find markets worth opening - they can help you compare sides inside the same market - they cannot validate a trade by themselves ## Related pages - [Holders and Wallet PnL Source](/docs/sources/holders-and-wallet-pnl) - [Market Details and Holders](/docs/scanner/market-details-and-holders) - [Freshness and Limitations](/docs/data/freshness-and-limitations) --- ## Understanding the Data > How Often Polymarket Market and Smart Money Data Refreshes URL: https://www.polylab.app/docs/data/freshness-and-limitations Summary: PolyLab rebuilds its Polymarket market snapshot every hour and refreshes wallet-level smart money enrichment every 6 hours. Because those are separate cycles, scanner rows can pair fresh prices with older holder data, and both can diverge from the live Polymarket interface. Any third-party Polymarket tool reads the venue through periodic API calls rather than a live order book feed, so there is always a lag between what it shows and what Polymarket shows. This page states PolyLab's exact cadence and the specific conditions under which the two diverge most. ## Real refresh cadence Market snapshots refresh every hour. Smart Money analysis refreshes every 6 hours. Those are separate refresh layers. The scanner can therefore show fresh market rows paired with older holder-based enrichment. ## Hourly snapshot behavior The market scraper rebuilds the active outcome snapshot and tag table on each run. PolyLab therefore behaves like a scanner over hourly snapshots rather than tick-level live execution. ## Why numbers can differ from Polymarket There are several legitimate reasons for mismatch: - PolyLab may be showing the last completed hourly snapshot - holder enrichment may still reflect the last completed 6-hour smart-money cycle - PolyLab requests markets with `enableOrderBook=false` - categories and icons are resolved through event metadata and fallbacks - holder sampling only covers the top sampled addresses, not every participant ## Upstream API dependence PolyLab depends on multiple Polymarket upstream services. If one service is slower, rate-limited, or structurally changed, parts of the scanner can drift from each other temporarily. ## Snapshot consequences ### Fast-moving markets Near breaking news or close to expiry, the difference between an hourly snapshot and the live venue can be large. ### Sparse metadata If upstream event metadata is missing or changes shape, `event_slug`, icon, or category can fall back to less complete values. ### Smart-money lag The smart-money layer can legitimately lag market state because it runs less often and depends on additional upstream calls. ## Practical guidance - Cross-check execution decisions on live Polymarket. - Treat holder context as slower-moving than raw market fields. - Expect the largest divergences near expiry and during rapid news cycles. ## Related pages - [How PolyLab Works](/docs/how-polylab-works) - [Refresh, Storage, and Snapshots](/docs/pipeline/refresh-storage-and-snapshots) - [Holders and Wallet PnL Source](/docs/sources/holders-and-wallet-pnl) --- ## Data Sources > Markets and Events Source URL: https://www.polylab.app/docs/sources/markets-and-events Summary: Exact documentation of the Gamma market and event endpoints that feed the PolyLab market snapshot. ## Upstream endpoints PolyLab fetches market and event data from: - `https://gamma-api.polymarket.com/markets` - `https://gamma-api.polymarket.com/events` ## Current request shape The market fetcher pages through both endpoints with these parameters: | Endpoint | Parameters | | --- | --- | | `/markets` | `active=true`, `closed=false`, `archived=false`, `limit`, `offset`, `enableOrderBook=false` | | `/events` | `active=true`, `closed=false`, `archived=false`, `limit`, `offset` | ## Why `enableOrderBook=false` matters PolyLab is not fetching or storing a live order-book view in this path. The scanner is built around market snapshot fields, not an execution-grade order-book model. ## What comes from `/events` The events payload is used for: - `slug` - `icon` or `image` - `tags` Those values are then used to enrich market rows with: - `event_slug` - `icon_url` - `tag_label` rows in `market_tags` - primary `category` ## What comes from `/markets` The markets payload is used for: - market id - condition id - question - outcomes - outcome prices - spread - volume - liquidity - start and end date - market-level fallback slug and icon fields ## Event resolution and fallbacks PolyLab first looks for the first nested event in `m["events"][0]`. If that event can be resolved in the fetched event map, it uses the event-level slug, icon, and tags. If not, it falls back to: - nested event slug/icon directly - then market-level slug/icon ## Per-outcome expansion Markets are not stored one row per market. PolyLab expands the `outcomes` array and `outcomePrices` array into one row per outcome in `active_market_outcomes`. That is why the scanner naturally sorts and filters outcome rows. ## Related pages - [Upstream to PolyLab Field Map](/docs/reference/upstream-field-map) - [Refresh, Storage, and Snapshots](/docs/pipeline/refresh-storage-and-snapshots) - [Core Metrics](/docs/metrics/core-metrics) --- ## Data Sources > Holders and Wallet PnL Source URL: https://www.polylab.app/docs/sources/holders-and-wallet-pnl Summary: Exact documentation of the holders and wallet-PnL endpoints behind the current smart-money layer. ## Upstream endpoints PolyLab uses two additional Polymarket services for holder enrichment: - `https://data-api.polymarket.com/holders` - `https://user-pnl-api.polymarket.com/user-pnl` ## Holders endpoint behavior The holders client calls `/holders` with: - `market=` - `limit=` The implementation requests more than one outcome-specific sample so it can flatten both sides, but then applies a local cap. ## Effective cap: top 20 holders per outcome The current client reduces the requested holder limit to: `per_outcome_limit = min(limit, 20)` It then: 1. normalizes each holder row to use `proxyWallet` as `address` 2. normalizes `amount` into `positionSize` 3. sorts rows by position size 4. keeps only the top 20 rows for each outcome 5. re-sorts the flattened result This means the smart-money layer is based on a sampled top-holder view, not on complete holder coverage. ## Wallet PnL endpoint behavior The PnL client calls `/user-pnl` with: - `user_address=` If the response is a list, PolyLab reads the last point and uses `p` as the stored `total_pnl`. ## Alias capture Aliases come from the holders payload field `name` when present. When aliases are missing, PolyLab keeps `alias = null`. ## Retry behavior The holders and wallet-PnL clients both retry on network errors and rate limits. The goal is operational resilience, not a guarantee of completeness. ## What this means for interpretation - a missing wallet from the sample is not evidence of absence - a profitable sampled wallet may not be profitable because of this market - smart-money counts are only as complete as the sampled holder set and wallet-PnL coverage ## Related pages - [Smart Money Methodology](/docs/methodology/smart-money) - [Market Details and Holders](/docs/scanner/market-details-and-holders) - [Freshness and Limitations](/docs/data/freshness-and-limitations) --- ## Pipeline > Refresh, Storage, and Snapshots URL: https://www.polylab.app/docs/pipeline/refresh-storage-and-snapshots Summary: The current coordinated refresh pipeline, table roles, and snapshot lifecycle behind the public scanner. ## Coordinated refresh cycle The project runs a coordinated refresh job: 1. scrape market and event data 2. write a fresh market snapshot 3. optionally run smart-money enrichment if the 6-hour interval has elapsed The smart-money step only runs after a successful scrape. ## Cadence | Job | Current cadence | Purpose | | --- | --- | --- | | Market scrape | Every 60 minutes | Refresh active market snapshot and tags | | Smart-money enrichment | Every 6 hours | Refresh holders and wallet-PnL context | ## Snapshot replacement rules The scraper drops and rebuilds: - `active_market_outcomes` - `market_tags` This means each scrape creates a clean active snapshot instead of incrementally updating old rows. ## Storage tables | Table | Role | | --- | --- | | `active_market_outcomes` | Main outcome-level market snapshot | | `market_tags` | Tag rows attached to market ids | | `holders` | Sampled holder rows keyed by condition id | | `wallets_stats` | Wallet-level PnL and alias data | | `market_smart_money_stats` | Background `smart_money_win_rate` storage and timestamp | | `request_logs` | API request timing logs in the metrics database | ## How holders are refreshed For each distinct `condition_id` in the active snapshot, PolyLab fetches holders, deletes prior holder rows for that condition, and inserts the new sampled rows with a fresh `snapshot_at`. ## How wallet stats are refreshed PolyLab extracts unique wallet addresses from sampled holder rows, fetches wallet PnL, and upserts into `wallets_stats`. ## What survives between scrapes The smart-money-related tables are not dropped on every market scrape. They are updated by the smart-money job and can therefore be newer or older than the market snapshot depending on job timing. ## Why this architecture was chosen The current design keeps the read path straightforward: - the app reads from a compact snapshot table - tag logic stays simple - holder and wallet enrichment can be refreshed on a slower schedule The cost is that PolyLab is a snapshot system, not a live state mirror. ## Related pages - [How PolyLab Works](/docs/how-polylab-works) - [Markets and Events Source](/docs/sources/markets-and-events) - [Storage and Runtime Reference](/docs/appendix/storage-and-runtime) --- ## Reference > Upstream to PolyLab Field Map URL: https://www.polylab.app/docs/reference/upstream-field-map Summary: Field-by-field mapping from upstream Polymarket payloads into PolyLab storage, display, and derived fields. ## Market and event mapping | Upstream source | Upstream field | PolyLab field | Notes | | --- | --- | --- | --- | | Gamma market | `id` | `market_id` | Stored on each outcome row | | Gamma market | `conditionId` | `condition_id` | Used for holders lookup and grouping | | Gamma market | `question` | `question` | Main display title | | Gamma market | `outcomes[i]` | `outcome_name` | One stored row per outcome | | Gamma market | `outcomePrices[i]` | `price` | Parsed to float where possible | | Gamma market | `spread` | `spread` | Stored directly | | Gamma market | `volume` | `volume_usd` | Stored directly | | Gamma market | `liquidity` | `liquidity_usd` | Stored directly | | Gamma market | `startDate` | `start_date` | Stored directly | | Gamma market | `endDate` | `end_date` | Stored directly | | Event payload | `slug` | `event_slug` | Preferred when event resolution succeeds | | Event payload | `icon` or `image` | `icon_url` | Used in the UI | | Event payload | `tags[].label` | `market_tags.tag_label` | Also drives primary category | ## Derived fields | PolyLab field | Computed from | Notes | | --- | --- | --- | | `url` | `event_slug` | Built as `https://polymarket.com/event/{event_slug}` | | `category` | first available tag label | Convenience primary label | | `apr` | `price`, `end_date`, `snapshot_at` | Annualized heuristic | | implied odds | `price` | UI-only `1 / price` transform | | `yes_profitable_count` | holders + wallet PnL | Count where `outcome_index = 0` and `total_pnl > 0` | | `no_losing_count` | holders + wallet PnL | Count where `outcome_index = 1` and `total_pnl < 0` | ## Holder and wallet mapping | Upstream source | Upstream field | PolyLab field | Notes | | --- | --- | --- | --- | | Holders API | `proxyWallet` | `wallet_address` / normalized `address` | Stored after normalization | | Holders API | `amount` | `position_size` / normalized `positionSize` | Parsed to float | | Holders API | `outcomeIndex` | `outcome_index` | YES / NO side | | Holders API | `name` | `alias` | Optional alias | | Wallet PnL API | latest list item `p` | `wallets_stats.total_pnl` | Last observed PnL point | ## Important caveat This map documents the current implementation. It does not claim that every upstream payload will always preserve the same field names or shape. --- ## Reference > Scanner Query and Filter Contract URL: https://www.polylab.app/docs/reference/scanner-query-contract Summary: Public reference for the query parameters accepted by `GET /api/markets` and how the current backend interprets them. ## Endpoint `GET /api/markets` ## Query parameters | Param | Type | Meaning | | --- | --- | --- | | `included_tags` | comma-separated string | ANY-match include filter | | `excluded_tags` | comma-separated string | exclude-on-ANY filter | | `sort_by` | string | one of the supported sort keys | | `sort_dir` | string | `asc` or `desc` | | `limit` | integer | page size | | `offset` | integer | pagination offset | | `min_volume` | float | minimum `volume_usd` | | `min_liquidity` | float | minimum `liquidity_usd` | | `min_price` | float | minimum `price` | | `max_price` | float | maximum `price` | | `max_spread` | float | maximum `spread` fraction | | `min_apr` | float | minimum APR as a fraction | | `min_hours_to_expire` | integer | lower expiry bound in hours | | `max_hours_to_expire` | integer | upper expiry bound in hours | | `include_expired` | boolean | include expired rows when expiry filters are in play | | `search` | string | text search against `question` and `outcome_name` | | `min_profitable` | integer | minimum sampled profitable holders on this side | | `min_losing_opposite` | integer | minimum sampled losing holders on the opposite side | ## Supported sort keys | Key | | --- | | `volume_usd` | | `liquidity_usd` | | `end_date` | | `price` | | `spread` | | `apr` | | `question` | | `yes_profitable_count` | | `yes_losing_count` | | `yes_total` | | `no_profitable_count` | | `no_losing_count` | | `no_total` | ## Removed or non-public controls `smart_money_win_rate` is not part of the current public scanner query contract, even though a background table still stores that metric internally. ## Notes - the frontend adds a cache-buster `_t=` - tag params are sent as comma-separated strings for proxy safety - if `sort_by` is invalid, the backend falls back to `volume_usd` ## Related pages - [Filters and Sorting](/docs/scanner/filters-and-sorting) - [Public API Contract](/docs/reference/public-api-contract) --- ## Reference > Polymarket Data API: Public Endpoints and Response Fields URL: https://www.polylab.app/docs/reference/public-api-contract Summary: PolyLab serves Polymarket market rows, category tags, and wallet-level holder data over JSON endpoints at `api.polylab.app`, currently without an API key. The main endpoints are `/api/markets` for filterable outcome rows, `/api/markets/{market_id}/holders` for holder and profit-and-loss data, `/api/tags`, and `/api/status`. These are the endpoints the public PolyLab app itself calls, documented so they can be used directly. Base URL is `https://api.polylab.app`. They return the snapshot described in [Freshness and Limitations](/docs/data/freshness-and-limitations), not a live order book feed, and this page reflects the current implementation rather than a versioned stability promise. ## `GET /api/homepage-bootstrap` Returns the precomputed homepage payload used for the marketing homepage first paint. Important response fields include: | Field | Meaning | | --- | --- | | `generated_at` | Snapshot generation timestamp | | `market_last_updated` | Latest `snapshot_at` used when building the snapshot | | `smart_money_last_updated` | Latest smart-money refresh timestamp available to the snapshot | | `cache_max_age_ms` | Suggested client-side cache TTL for local bootstrap hydration | | `spotlight_markets` | Ready-to-render smart-money spotlight cards | | `playbook_previews` | Per-playbook preview rows keyed by preset id | ## `GET /api/app-bootstrap` Returns the precomputed first-paint payload for `/app`. Query params: | Param | Meaning | | --- | --- | | `view` | `scanner` or `smart` | | `preset` | Optional preset id such as `smart_money_edge` | Important response fields include: | Field | Meaning | | --- | --- | | `generated_at` | Snapshot generation timestamp | | `market_last_updated` | Latest `snapshot_at` used when building the snapshot | | `smart_money_last_updated` | Latest smart-money refresh timestamp available to the snapshot | | `view` | Resolved app view after preset application | | `active_preset_id` | Resolved preset id, or `null` for default bootstraps | | `filters` | Resolved initial filter state for the first paint | | `markets` | Initial market rows rendered before live revalidation | | `tags` | Initial tag stats used by include/exclude autocomplete | | `cache_key` | Storage key used by the frontend for bootstrap caching | ## `GET /api/tags` Returns tag statistics used by the include/exclude tag UI. | Field | Meaning | | --- | --- | | `tag_label` | Tag label | | `count` | Number of occurrences in the current snapshot | ## `GET /api/status` Returns the last known refresh timestamps used by the UI header. | Field | Meaning | | --- | --- | | `last_updated` | Latest `snapshot_at` in `active_market_outcomes` | | `smart_money_last_updated` | Latest `last_updated_at` in `market_smart_money_stats` | ## `GET /api/markets` Returns the main scanner rows and accepts the query parameters documented in [Scanner Query and Filter Contract](/docs/reference/scanner-query-contract). Important response fields include: | Field | Meaning | | --- | --- | | `market_id` | Market identifier | | `condition_id` | Condition identifier used for holders lookup | | `question` | Market question | | `outcome_name` | Row outcome | | `price` | Stored outcome price | | `apr` | Computed APR | | `spread` | Stored spread | | `volume_usd` | Stored volume | | `liquidity_usd` | Stored liquidity | | `start_date` | Start date | | `end_date` | End date | | `category` | Primary category from tags | | `icon_url` | Event image/icon | | `yes_profitable_count` | Sampled profitable YES wallets | | `yes_losing_count` | Sampled losing YES wallets | | `yes_total` | Total sampled YES holder rows | | `no_profitable_count` | Sampled profitable NO wallets | | `no_losing_count` | Sampled losing NO wallets | | `no_total` | Total sampled NO holder rows | ## `GET /api/markets/{market_id}/holders` Returns holder rows for the condition represented by the selected market detail view. | Field | Meaning | | --- | --- | | `wallet_address` | Normalized holder wallet | | `position_size` | Current sampled position size | | `outcome_index` | YES / NO side index | | `total_pnl` | Joined wallet PnL if available | | `alias` | Optional wallet alias | | `wallet_tag` | Optional operational tag such as `SYSTEM` | ## Contract stability The homepage and initial `/app` load are intentionally snapshot-backed for speed. Live `/api/markets` remains the source for interactive filtering, pagination, and silent revalidation after first paint. This reference documents what the current public app uses today. It should be read as a current implementation contract, not a long-term versioned API promise. --- ## Reference > FAQ URL: https://www.polylab.app/docs/faq Summary: Practical answers about PolyLab data quality, refresh timing, smart-money interpretation, and what the public scanner is meant to do. ## Is PolyLab financial advice? No. PolyLab is a research and scanning tool. ## Does PolyLab show live Polymarket data? No. It shows periodic snapshots and derived metrics. ## How often is the data updated? Market snapshots refresh every hour, while Smart Money analysis refreshes every 6 hours. ## Why can PolyLab disagree with Polymarket? Because the scanner is snapshot-based, depends on several upstream endpoints, and enriches rows locally. Fast market movement, metadata fallbacks, and holder sampling all contribute to differences. ## Does Smart Money mean insiders or whales? No. In the current implementation it means sampled holder rows joined with wallet PnL sign. That is a much narrower and more mechanical definition. ## Does PolyLab ingest every holder in a market? No. The current holders client effectively caps the sample to top 20 holders per outcome. ## Is `smart_money_win_rate` a public filter? No. It is still computed in background storage, but it is not a live public filter in the current scanner UI and API contract. ## Where should I validate a trade? On live Polymarket. Use PolyLab to shortlist and inspect. Use Polymarket to confirm the live state before acting. ## What should I read next? - [How PolyLab Works](/docs/how-polylab-works) - [Core Metrics](/docs/metrics/core-metrics) - [Freshness and Limitations](/docs/data/freshness-and-limitations) --- ## Appendix > Access Model URL: https://www.polylab.app/docs/appendix/access-model Summary: Public-facing notes on how the current PolyLab product is positioned and what this documentation is trying to make transparent. ## Why this page exists Most of the docs tree is about the scanner, data, and methodology. This appendix page exists to explain how the public product surface is framed today. ## Current public stance PolyLab is presented as an independent analyzer for Polymarket. The public docs are meant to reduce black-box interpretation by explaining: - what comes directly from Polymarket - what is computed locally - where the scanner is approximate - which parts of the product are still in progress ## Access and scope The public website exposes: - marketing pages - the public scanner app - the public docs section - custom-data information This page does not define pricing or entitlements. It exists as context for the documentation layer. ## Why it remains in the appendix Most users benefit more from methodology and scanner documentation than from access-model notes. That is why this page is kept as an appendix rather than a primary getting-started article. --- ## Appendix > Storage and Runtime Reference URL: https://www.polylab.app/docs/appendix/storage-and-runtime Summary: Appendix-level detail on the current runtime jobs, SQLite tables, and how the public scanner is materially assembled. ## Runtime components | Component | Current role | | --- | --- | | `gamma_client.py` | Fetch paginated markets and events from Gamma | | `scraper.py` | Build the active outcome snapshot and tags | | `smart_money_scraper.py` | Fetch holders, wallet PnL, and smart-money counts context | | `auto_refresh.py` | Coordinate hourly scrape and 6-hour smart-money runs | | `main.py` | Serve API routes and public static surfaces | ## Primary tables | Table | Current purpose | | --- | --- | | `active_market_outcomes` | Active outcome snapshot shown in the scanner | | `market_tags` | Tag lookup for include/exclude category filtering | | `holders` | Sampled holders keyed by condition id and outcome index | | `wallets_stats` | Wallet PnL, alias, and wallet tags | | `market_smart_money_stats` | Stored background `smart_money_win_rate` values and timestamps | ## Request logging The metrics database also stores `request_logs` for API timing and diagnostics. ## Indexing and read path The backend ensures indices on commonly queried fields such as: - `price` - `spread` - `volume_usd` - `liquidity_usd` - `apr` - `question` - `outcome_name` This is one reason the scanner can stay simple while still serving a large active snapshot. ## Why this matters publicly This appendix exists so users can understand where the app is doing straightforward storage and where it is applying interpretation layers. A transparent scanner is easier to trust, especially when it openly documents its own limits. ---