polymarket-scanner
Polymarket Inefficiency Scanner
Private, read-only Crystal scanner for real Polymarket market-relationship analysis.
Safety Defaults
- No real order placement.
- No wallet or private-key configuration.
- No authenticated trading endpoints.
- Dashboard/API binds to
127.0.0.1by default. - Production runtime uses public read-only Polymarket Gamma listings and CLOB books.
- Fixture data is limited to specs and explicitly injected test helpers.
- Trading-critical values use
PolyScan::Fixedfixed-point integer math.
Run
shards install
crystal spec
crystal build src/poly_scan.cr
./poly_scan --config config/app.example.yml --once
./poly_scan --config config/app.example.yml --serve
Then open:
http://127.0.0.1:8765/http://127.0.0.1:8765/api/healthhttp://127.0.0.1:8765/api/opportunitieshttp://127.0.0.1:8765/api/paper-trades
For a smaller real-data probe:
./poly_scan --config config/live.example.yml --serve
Then open http://127.0.0.1:8765/api/markets. The app uses public read-only Gamma market listings for discovery and public CLOB /book?token_id=... responses for executable book data. It does not configure a wallet, private key, auth token, or order-placement endpoint.
Migrations
Database schema changes are managed with Micrate. App startup automatically applies pending migrations from db/migrations before opening the configured SQLite database.
For manual migration work, install the official micrate CLI and pass the SQLite connection with DATABASE_URL:
DATABASE_URL=sqlite3://./data/poly_scan.example.db micrate status
DATABASE_URL=sqlite3://./data/poly_scan.example.db micrate up
DATABASE_URL=sqlite3://./data/poly_scan.example.db micrate down
Configuration
The app loads YAML from --config PATH, or from POLY_SCAN_CONFIG when no --config argument is provided. If neither is set, it uses config/app.example.yml.
Trading-critical decimal values must be quoted strings with up to six decimal places, for example "0.005000". They are parsed into fixed-point integer atoms by PolyScan::Fixed; do not write these values as YAML floats.
Unknown configuration keys are rejected at startup. This applies to both top-level options and nested sections such as http, paper_trading, and telegram.
Top-level options:
| Option | Default | Description |
|---|---|---|
bind_host |
"127.0.0.1" |
Dashboard/API bind host. Non-loopback binds are rejected unless POLY_SCAN_ALLOW_PUBLIC_BIND=true is set. |
port |
8765 |
Dashboard/API TCP port. |
database_path |
"data/poly_scan.db" |
SQLite database file path. Parent directories are created automatically. |
gamma_base_url |
"https://gamma-api.polymarket.com" |
Base URL for the read-only Gamma listing client. |
clob_base_url |
"https://clob.polymarket.com" |
Base URL for the read-only CLOB book client. |
relationships_path |
"config/relationships.example.yml" |
Manual relationship-rule YAML file. |
market_limit |
25 |
Maximum real Polymarket listings to fetch from Gamma market APIs. |
book_limit |
50 |
Maximum real CLOB token books to fetch from /book. Binary markets can use two token books each. |
scan_size |
"1.000000" |
Fixed-point size used for executable VWAP checks and paper-trade legs. Must be positive. |
taker_fee_bps |
0 |
Taker fee in basis points. Fee is computed on min(price, 1-price) * size. |
max_book_age_ms |
300000 |
Maximum book age before stale-book risk and stale penalties apply. |
max_spread |
"0.080000" |
Spread threshold for wide-spread risk and SpreadAnomaly signals. |
min_depth |
"1.000000" |
Minimum ask depth used by spread anomaly checks. |
slippage_buffer |
"0.005000" |
Fixed-point buffer subtracted from gross edge. |
uncertainty_penalty |
"0.003000" |
Fixed-point penalty subtracted from gross edge. |
resolution_penalty |
"0.002000" |
Fixed-point resolution-risk penalty subtracted from gross edge. |
stale_book_penalty |
"0.010000" |
Fixed-point penalty applied when a book is stale. |
low_confidence_threshold |
"0.600000" |
Confidence below this threshold adds the low-confidence risk flag. |
high_fee_threshold |
"0.010000" |
Per-leg fee at or above this threshold adds the high-fees risk flag. |
imbalance_ratio |
"3.000000" |
Depth ratio threshold for BookImbalance supporting signals. |
HTTP options under http:
| Option | Default | Description |
|---|---|---|
timeout_ms |
5000 |
Connect, read, and write timeout for read-only HTTP calls. |
max_retries |
2 |
Number of retries after the first HTTP attempt. |
retry_backoff_ms |
250 |
Base exponential backoff delay. |
retry_jitter_ms |
125 |
Random jitter added to retry backoff. |
rate_limit_per_minute |
60 |
Conservative per-client request pacing. Must be positive. |
Paper trading options under paper_trading:
| Option | Default | Description |
|---|---|---|
enabled |
true |
Creates local paper trades from generated opportunities. This never places real orders. |
Telegram options under telegram:
| Option | Default | Description |
|---|---|---|
enabled |
false |
Enables the alert scaffold. Current implementation logs readiness/skips and does not send network alerts. |
bot_token_env |
unset | Name of the environment variable containing the bot token. The token itself is not stored in YAML. Example configs use "POLY_SCAN_TELEGRAM_BOT_TOKEN". |
chat_id_env |
unset | Name of the environment variable containing the chat ID. The chat ID itself is not stored in YAML. Example configs use "POLY_SCAN_TELEGRAM_CHAT_ID". |
Environment overrides:
| Variable | Description |
|---|---|
POLY_SCAN_CONFIG |
Config file path used when --config is not provided. |
POLY_SCAN_BIND_HOST |
Overrides bind_host. Public binds still require POLY_SCAN_ALLOW_PUBLIC_BIND=true. |
POLY_SCAN_PORT |
Overrides port. |
POLY_SCAN_DATABASE_PATH |
Overrides database_path. |
POLY_SCAN_RELATIONSHIPS_PATH |
Overrides relationships_path. |
POLY_SCAN_MARKET_LIMIT |
Overrides market_limit. |
POLY_SCAN_BOOK_LIMIT |
Overrides book_limit. |
POLY_SCAN_ALLOW_PUBLIC_BIND |
Set to true to permit a non-127.0.0.1 bind. Leave unset for private local use. |
Legacy runtime fixture keys (data_source, gamma_fixture_path, and clob_books_path) are rejected in production config. Parser fixtures remain available to specs.
Example:
POLY_SCAN_DATABASE_PATH=tmp/dev.db \
POLY_SCAN_RELATIONSHIPS_PATH=config/relationships.example.yml \
./poly_scan --config config/app.example.yml --serve
Relationship Rules
Manual relationship rules live in config/relationships.example.yml and support:
implicationmutually_exclusiveexhaustive_groupcomplementcorrelated_group
Production rules must reference token IDs present in the loaded real Polymarket listings. The scanner does not infer exhaustiveness from market titles. Use verified_exhaustive: true only for manually checked exactly-one groups.
Rule fields:
| Field | Required | Applies To | Description |
|---|---|---|---|
id |
Yes | All rules | Stable unique rule ID. Used in opportunity IDs and storage. |
type |
Yes | All rules | One of the supported relationship types above. |
description |
No | All rules | Human-readable note explaining the manual rule source or reasoning. |
from_token_id |
Yes | implication |
Antecedent YES token. In A implies B, this is A. |
to_token_id |
Yes | implication |
Consequent YES token. In A implies B, this is B. |
token_ids |
Type-dependent | Group rules | YES token IDs for mutually_exclusive, exhaustive_group, complement, and correlated_group. |
confidence |
No | All rules | Quoted fixed-point confidence factor. Defaults to "0.500000". |
quality |
No | All rules | Quoted fixed-point rule-quality factor. Defaults to "0.500000". |
verified_exhaustive |
No | exhaustive_group |
Set true only after manual exactly-one verification. Defaults to false. |
polymarket-scanner
- 0
- 0
- 0
- 0
- 2
- 4 days ago
- May 22, 2026
Fri, 22 May 2026 12:02:29 GMT