polymarket-scanner

Version, currently main branch1 version
  • main branchlatestMay 22, 2026

github.com/krthr/polymarket-scanner

No description declared in shard.yml.

0 stars
0 dependents
License: MIT

Installation

# Add this to your shard.yml
dependencies:
  polymarket-scanner:
    github: krthr/polymarket-scanner
    branch: main

main is a branch, not a release, so this tracks it rather than pinning a version.

Then run:

shards install

shard.yml

Crystal
>= 1.20.2
License
MIT
Author
Codex
Target
  • poly_scan from src/poly_scan.cr

Dependencies

Runtime Dependencies

  • micrate*github: amberframework/micrate, branch: master
  • sqlite3*github: crystal-lang/crystal-sqlite3

README

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.1 by 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::Fixed fixed-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/health
  • http://127.0.0.1:8765/api/opportunities
  • http://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:

OptionDefaultDescription
bind_host"127.0.0.1"Dashboard/API bind host. Non-loopback binds are rejected unless POLY_SCAN_ALLOW_PUBLIC_BIND=true is set.
port8765Dashboard/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_limit25Maximum real Polymarket listings to fetch from Gamma market APIs.
book_limit50Maximum 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_bps0Taker fee in basis points. Fee is computed on min(price, 1-price) * size.
max_book_age_ms300000Maximum 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:

OptionDefaultDescription
timeout_ms5000Connect, read, and write timeout for read-only HTTP calls.
max_retries2Number of retries after the first HTTP attempt.
retry_backoff_ms250Base exponential backoff delay.
retry_jitter_ms125Random jitter added to retry backoff.
rate_limit_per_minute60Conservative per-client request pacing. Must be positive.

Paper trading options under paper_trading:

OptionDefaultDescription
enabledtrueCreates local paper trades from generated opportunities. This never places real orders.

Telegram options under telegram:

OptionDefaultDescription
enabledfalseEnables the alert scaffold. Current implementation logs readiness/skips and does not send network alerts.
bot_token_envunsetName 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_envunsetName 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:

VariableDescription
POLY_SCAN_CONFIGConfig file path used when --config is not provided.
POLY_SCAN_BIND_HOSTOverrides bind_host. Public binds still require POLY_SCAN_ALLOW_PUBLIC_BIND=true.
POLY_SCAN_PORTOverrides port.
POLY_SCAN_DATABASE_PATHOverrides database_path.
POLY_SCAN_RELATIONSHIPS_PATHOverrides relationships_path.
POLY_SCAN_MARKET_LIMITOverrides market_limit.
POLY_SCAN_BOOK_LIMITOverrides book_limit.
POLY_SCAN_ALLOW_PUBLIC_BINDSet 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:

  • implication
  • mutually_exclusive
  • exhaustive_group
  • complement
  • correlated_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:

FieldRequiredApplies ToDescription
idYesAll rulesStable unique rule ID. Used in opportunity IDs and storage.
typeYesAll rulesOne of the supported relationship types above.
descriptionNoAll rulesHuman-readable note explaining the manual rule source or reasoning.
from_token_idYesimplicationAntecedent YES token. In A implies B, this is A.
to_token_idYesimplicationConsequent YES token. In A implies B, this is B.
token_idsType-dependentGroup rulesYES token IDs for mutually_exclusive, exhaustive_group, complement, and correlated_group.
confidenceNoAll rulesQuoted fixed-point confidence factor. Defaults to "0.500000".
qualityNoAll rulesQuoted fixed-point rule-quality factor. Defaults to "0.500000".
verified_exhaustiveNoexhaustive_groupSet true only after manual exactly-one verification. Defaults to false.