Run stock screens with the crible CLI
Inside the repo, run every command with uv run crible … (run uv sync once
if the environment is missing). Outside the repo, crible is an installable
tool — uv auto-provisions the required Python 3.12, and bootstrap pulls the
FULL published dataset (zero crawl), so three commands stand up a working
screener anywhere:
uv tool install git+https://github.com/maxgfr/crible
crible --data-dir ~/.crible-data bootstrap
crible --data-dir ~/.crible-data screen "piotroski_f >= 7" --sort -composite_rank
(One-shot alternative: uvx --from git+https://github.com/maxgfr/crible crible ….)
The global --data-dir option (before the subcommand) selects the dataset
location. Default: $CRIBLE_DATA_DIR, else ./data relative to the
current directory — outside the repo, ALWAYS pass --data-dir (or export
CRIBLE_DATA_DIR) so bootstrap and screen see the same dataset.
0. Make sure data exists
uv run crible status # universe count, coverage, snapshot present?
If there is no snapshot yet, pull the published open dataset (zero crawl, ~30 s) instead of crawling:
uv run crible bootstrap # refuses to overwrite an existing data/; --force to re-pull
1. Discover what is filterable
uv run crible fields # every column + its type (number|string)
uv run crible presets # ready-made screens with their editable DSL
The field list IS the whitelist: any column it prints can be used in a query.
An unknown field fails with a "did you mean …?" hint when a close match
exists — but there are NO aliases: roe, per, pe do not resolve. Map
finance shorthand to exact names first (crible fields | grep -i equity):
P/E → price_to_earnings_ratio, ROE → return_on_equity,
market cap → market_cap, dividend yield → weighted_dividend_yield.
2. The filter DSL
field op value, combined with AND / OR / NOT and parentheses.
Operators: > >= < <= = != <> and field IN (v1, v2, …).
Strings are single-quoted: country = 'FR'. Numbers are bare: piotroski_f >= 7.
A blank query is valid and means NO filter — crible screen "" --sort -composite_rank screens the whole covered universe (the hosted UI's default).
uv run crible screen "piotroski_f >= 7 AND country IN ('FR','DE')" \
--sort -composite_rank --limit 20
uv run crible screen "return_on_equity > 0.15" --format csv # csv to stdout
uv run crible screen --preset quality # run a preset by id
uv run crible export "altman_z > 2.99 AND price_to_book_ratio < 1.5" --out results.csv
--sort takes -field for descending, field (or +field) for ascending,
comma-separated for multi-key (--sort "country,-piotroski_f").
3. Field semantics you must get right
- Ratios are decimals, not percent: ROE 15 % is
return_on_equity > 0.15. - Scores:
piotroski_fis 0–9 (≥7 strong);altman_z> 2.99 = safe zone, < 1.81 = distress;beneish_m> -1.78 flags possible earnings manipulation (sobeneish_m < -1.78selects the clean ones). - Distress & forensics (newer):
zmijewski_scoreandohlson_o< 0 = safe (> 0 = modelled distress);montier_ccounts 0–6 red flags (≤1 clean);dechow_f< 1 = below-normal misstatement risk, ≥ 1.85 substantial;mohanram_gis a PARTIAL 0–6 growth-quality score (6 of the paper's 8 signals — no published cutoff transfers). - GARP & cash:
peg_ratio(positive-only; ≤ 1 = growth at a reasonable price),rule_of_40(revenue growth + FCF margin, ≥ 0.4 passes),shareholder_yield(dividends + net buybacks / market cap),sloan_accruals(lower/negative is better),cash_conversion_cycle(days, lower is better — negative means suppliers finance operations). - TTM:
ttm_revenue/ttm_net_income/ttm_operating_cashflow/ttm_free_cash_flow+price_to_earnings_ttm/price_to_sales_ttm/ttm_fcf_yield— the last four discrete quarters (crawled symbols + audited US issuers reporting discrete 10-Q quarters). - Momentum (price-derived, latest period):
return_6m,return_12_1(12-month skipping the last month),high_52w_proximity(1.0 = at the 52-week high),volatility_1y(annualized). - Ranks (
composite_rank,quality_rank,value_rank,momentum_rank) are 0–100 percentiles within region×sector peer groups;return_6mis the momentum input (decimal). - 3-year trajectory:
revenue_cagr_3y/net_income_cagr_3y(compound, decimals;peg_ratiodivides by exactly the latter). - The list above is a compass, not a catalog —
crible fieldsprints every filterable column. - Identity fields:
countryis ISO-2 uppercase ('FR'),regionis one of'europe' | 'us' | 'world',sectoris GICS-like ('Information Technology','Health Care', …). - Growth: every fundamental/ratio has a
_growthcompanion (YoY decimal), e.g.revenue_growth > 0.10. - NULLs never match: a company missing an input simply drops out of any
filter on it — nothing is imputed.
missing_inputs/rank_missing_pillarsexplain why a value is NULL.
4. Recipes
# Quality + value in Europe, best composite first
uv run crible screen "region = 'europe' AND piotroski_f >= 7 AND earnings_yield > 0.08" \
--sort -composite_rank --limit 25
# Cheap and safe: low P/B, Altman safe zone, no manipulation red flag
uv run crible screen "price_to_book_ratio < 1.5 AND altman_z > 2.99 AND beneish_m < -1.78"
# One company's full detail: filter on symbol
uv run crible screen "symbol = 'AIR.PA'" --format csv
When a screen returns 0 rows, loosen one clause at a time (coverage is bounded:
crible status shows how many companies are crawled) — do not conclude "no
company matches" from a thin dataset.
5. Operating crible (managing the data lifecycle)
Every command below is keyless. Pick the path that matches the setup.
Consume-only (fastest — no crawl): pull the published open dataset.
uv run crible bootstrap # ~30 s; refuses to overwrite an existing data/
uv run crible bootstrap --force # re-pull the latest layer
uv run crible bootstrap --repo owner/name # pull from a fork's published dataset
Uninstall / start over: delete the dataset directory (marker-guarded so a
mistyped --data-dir never removes an arbitrary folder; re-bootstrap anytime).
crible --data-dir ~/.crible-data clean # interactive confirmation
crible --data-dir ~/.crible-data clean --yes # for scripts
Crawl your own universe:
uv run crible ingest --bootstrap # load the universe from FinanceDatabase (once)
uv run crible ingest --once --limit 50 # one bounded crawl cycle
uv run crible ingest --loop # continuous crawl loop
uv run crible ingest --once --fetch-gleif # also mirror GLEIF ISIN→LEI (unlocks audited EU)
Nightly bounded refresh (the real dataset run — audited enrichment):
uv run crible refresh --deadline 9000 # keyless pass: ESEF + EDGAR enrich, self-heal GLEIF, mirror ECB FX; prints a JSON report
Flags: --edgar-bulk (download companyfacts.zip ~1.4 GB and ingest ALL
resolved US issuers), --fsds-quarters N (backfill deep US history from the N
most recent SEC FSDS quarters), --esef-limit / --edgar-limit (per-run
caps, default 25), --no-fetch-gleif / --no-fetch-fx (skip those steps),
--max-minutes N (WHOLE-RUN wall-clock guard: enrichment stages stop early so
compute+publish always run; partial passes resume next run; 0 = unbounded).
Prices (dump-based, no API):
uv run crible import-prices huggingface # US OHLCV dump, plain HTTPS
uv run crible import-prices path/to/stooq.zip # worldwide (manual dl from stooq.com/db/h/)
uv run crible import-prices huggingface --max-age-days 1 # skip if imported < 1 day ago
uv run crible stooq-download d_world_txt --import # auto-fetch the captcha-gated Stooq bulk (clears PoW + OCR), then import
uv run crible solve-captcha shot.png # standalone captcha OCR helper (optional 'captcha' extra)
Distillate = last close + as-of date + trailing 6-month return in
data/prices-latest.parquet; the windowed series lands in data/prices/.
Rebuild & publish:
uv run crible compute # build + atomically publish the wide snapshot (incremental: skips the republish when nothing changed)
uv run crible export-site --out site-data --min-symbols 50 # write the static artifacts the hosted screener serves (refuses below --min-symbols)
docker compose up runs the continuous crawl+compute loop as a service.
After any raw change, crible compute is what refreshes ratios/ranks. Check
price_asof in results — staleness is reported honestly, never imputed.
6. Use from agents (MCP)
crible mcp serves a READ-ONLY MCP tool surface over stdio — screen
(the same DSL; blank = whole universe), fields, presets, company,
status. No crawl/refresh tool is exposed. Register it in the active host
after bootstrap. Codex uses its native MCP registry:
codex mcp add crible --env CRIBLE_DATA_DIR=$HOME/.crible-data -- crible mcp
Claude Code uses its corresponding registry:
claude mcp add crible -e CRIBLE_DATA_DIR=$HOME/.crible-data -- crible mcp