How we score
Every listed repo gets up to 100 points for public repo hygiene. The rubric, the scoring code and every pattern it uses are open source. Rubric version 1.1.
Hygiene, not performance. A grade says how well a repo is maintained and documented, and whether it has guardrails. It does not say a bot is profitable, safe, secure or suitable for you. We never look at returns, backtests, win rates, stars, sponsors or who wrote the code. Never inputs: stars, returns, backtests, win rates, sponsor status, author identity.
The 100 points
| Factor | Max | How points are earned |
| Risk limits and kill switch | 20 | 10 for position, loss or exposure limits in code or config (5 if only documented). 10 for a kill switch, trading-enabled flag, circuit breaker or human-approval gate in code or config (5 if only documented). |
| License | 15 | 15 for an OSI-approved SPDX license. 7 for a custom or unrecognized license file. 0 for no license. |
| Last commit on default branch | 15 | 15 within 30 days, 12 within 90, 8 within 180, 4 within 365, 0 older. |
| Tests | 15 | 10 for at least 3 real test cases in test files (5 for 1 or 2). 5 more if a GitHub Actions workflow runs the tests. |
| Paper or dry-run mode | 15 | 8 if the README or docs describe paper, sandbox or dry-run use. 7 if code or config exposes a paper, sandbox or dry-run switch. |
| README quality | 10 | Install steps 2, config or env vars 2, what it does or its strategy 1, risk disclaimer 2, length over 1,500 characters 3 (500 to 1,500 characters 1). |
| Broker detection | 10 | 6 if a broker SDK or API host is found in dependency files or code. 4 if the README names the broker. |
Grades and caps
A: 85 or more. B: 70. C: 55. D: 40. F: below 40.
- No paper or dry-run mode (paper factor earns 0): grade capped at C (69 points).
- No OSI-approved open source license (none, custom, or all rights reserved): grade capped at B (84 points). A repo you have no legal right to reuse cannot earn an A.
- Archived repositories show Archived instead of a grade. Repos under 90 days old get a "New" tag, because a young repo has had little time to prove upkeep.
Not applicable factors
- Tools that never place orders (kind: tool) have the risk and paper factors marked not applicable. The remaining points are rescaled to 100 and no cap applies.
- A reviewer may mark a factor not applicable with a written reason (for example a read-only MCP server).
Reviewed or automated
Scoring is automated, and automated scoring is noisy. Keyword matching finds a "max loss" in a comment or an example file as easily as in real code. So every seed repo was also read by a reviewer, and the review is shown next to the grade.
- Reviewed. Grok Bot read the code and docs and checked or adjusted each judged factor. Nothing was executed. This is not a human audit.
- Automated, review out of date. A reviewer checked this repo earlier, but the automated evidence has changed since. The grade shown is the automated one.
- Automated, not reviewed. Nobody has checked the evidence. The grade comes straight from the scanner and may be wrong.
Honest limits: reviews are done by Grok Bot reading code and docs. Nothing is executed or tested, and a review is not a human audit or a security audit. A review is tied to the automated points it saw. If the automated evidence changes later, the review is marked out of date and the automated grade is shown until it is checked again. Reviewer notes sit in data/reviews in the repo.
Run it yourself
git clone https://github.com/tazopicus-cmyk/optionsbot-index.git
cd optionsbot-index && npm ci
npm run score -- owner/repo # scores any public repo
npm test # scorer unit tests
The nightly refresh is a GitHub Action that uses only the built-in GITHUB_TOKEN. It reads public repo metadata and a shallow clone. It never reads sponsor data.
Patterns, verbatim
These regular expressions are copied from scorer/lib/patterns.mjs at build time. Comment-only lines are blanked before matching. Test files, docs, .github, vendor and build folders, lockfiles and files over 250 KB are skipped for code checks. Patterns use word boundaries so, for example, the word "future" can never match the broker Moomoo (whose SDK is called futu).
Risk limits (10 points)
| max position | \bmax(imum)?[_ -]?(open[_ -]?)?positions?\b |
| max loss | \bmax(imum)?[_ -]?(daily[_ -]?|weekly[_ -]?|trade[_ -]?)?loss\w* |
| max drawdown | \bmax(imum)?[_ -]?draw[_ -]?down\b|\bmax_dd\b |
| max contracts | \bmax(imum)?[_ -]?contracts?\b(?![_ -]?dte) |
| max notional or exposure | \bmax(imum)?[_ -]?(aggregate[_ -]|total[_ -]|open[_ -]|portfolio[_ -])?(notional|exposure|allocation|capital|margin|risk)\w* |
| max orders | \bmax(imum)?[_ -]?(orders?|trades?)([_ -]?per[_ -]?\w+)?\b |
| daily loss limit | \b(daily|weekly|portfolio)[_ -]?(loss|risk|drawdown)[_ -]?(limit|cap|max|stop)\w* |
| position limit | \bposition[_ -]?(limits?|size[_ -]?limit|sizing)\b |
| risk limit | \brisk[_ -]?(limits?|per[_ -]?trade|budget|cap|percent|pct)\b |
| stop loss | \bstop[_ -]?loss\b |
| buying power cap | \bbuying[_ -]?power[_ -]?(usage|limit|pct|percent|cap|reserve|ratio)\b|\bmargin[_ -]?(usage|limit|cap)\b |
Kill switch, enable flag, circuit breaker, approval gate (10 points)
| kill switch | \bkill[_ -]?switch\b |
| emergency stop | \bemergency[_ -]?(stop|shutdown|halt|exit)\b |
| circuit breaker | \bcircuit[_ -]?breaker\b |
| trading enabled flag | \btrading[_ -]?(enabled|disabled|halted|paused)\b|\b(enable|disable|allow)[_ -]?trading\b|\bTRADING_ENABLED\b|\bENABLE_TRADING\b|\bALLOW_TRADING\b |
| halt or pause | \b(halt|pause)[_ -]?(trading|all|new|orders)\b |
| human approval | \bhuman[_ -]?(in[_ -]the[_ -]loop|approval)\b|\b(require[sd]?|needs?)[_ -]?(manual[_ -]?|human[_ -]?|user[_ -]?)?(approval|confirmation)\b|\bapproval[_ -]required\b|\bmanual[_ -]approval\b|\bconfirm(ation)?[_ -]before\b|\bawait(s|ing)? (human |user )?approval\b |
| read-only mode | \bread[_ -]?only[_ -]?mode\b|\bREAD_ONLY\b |
Paper mode, documented (8 points)
| README or docs | \bpaper[ -](trading|account|accounts|mode|trade|trades|money|api|endpoint|environment|port|keys?)\b|\balpaca paper\b|\bdry[- ]?runs?\b|\bsandbox\b|\bsimulation mode\b|\bsimulated (orders|trading|mode)\b|--paper\b|\b\w*PAPER\w*\s*=|\bpaper\s*(=|:)\s*(true|false)\b|\bpaper (or|and|vs\.?) live\b|\blive (or|and) paper\b |
Paper mode, in code or config (7 points)
| dry-run flag | \bdry[_-]?run\b|\bDRY_RUN\b|\bDryRun\b |
| paper-mode flag | \bpaper[_-]?(trading|mode|account|trade|only|api)\b|\bis_paper\b|\bALPACA_PAPER\b|\bPAPER_TRADING\b|\bpaper\s*[=:]\s*(True|False|true|false)\b|["']paper["']\s*: |
| paper endpoint | \bpaper-api\.alpaca\.markets\b|\bcert\.tastyworks\.com\b|\bapi\.cert\.tastyworks\b|\bsandbox\.tradier\.com\b |
| IBKR paper port | \bport\W{0,8}(7497|4002)\b|\b(7497|4002)\b.{0,20}\bpaper\b|\bpaper\b.{0,20}\b(7497|4002)\b |
| sandbox flag | \bsandbox\b|\bis_test\s*=\s*True\b|\bsimulat(e|ion)[_-]mode\b |
Tests
| Test file path | (^|\/)(tests?|__tests__|spec|specs)\/|(^|\/)test_[^/]+\.py$|_test\.(py|go)$|\.(test|spec)\.(js|mjs|cjs|ts|tsx|jsx)$ |
| Test case | \bdef test_\w+|\b(it|test)\s*\(\s*['"`]|\bfunc Test\w+\(|#\[test\]|@Test\b|\bclass Test\w+|\bdescribe\s*\( |
| CI runs tests | \b(pytest|python -m pytest|python -m unittest|unittest discover|npm (run )?test|npm t\b|pnpm (run )?test|yarn test|bun test|go test|cargo test|vitest|jest|tox\b|nox\b|make test|uv run pytest|mvn test|gradle test|ctest)\b |
README checks
| install | \b(pip install|pip3 install|uv (sync|pip|add|run)|poetry install|npm (install|i)\b|pnpm (install|add)|yarn (add|install)|npx |bunx |docker (run|compose|build)|go install|cargo (install|build|run)|git clone|brew install|conda (install|env))\b|^#+\s*(install|installation|setup|getting started|quick ?start)\b |
| config | \.env\b|environment variables?|\bconfig(uration)?[ _-]?(file|options)\b|\bconfig\.(ya?ml|toml|json|ini)\b|\b[A-Z][A-Z0-9]+_(KEY|SECRET|TOKEN|ID)\b|^#+\s*(config|configuration|settings|environment)\b |
| strategy | \b(strateg(y|ies)|iron condors?|wheel|covered calls?|credit spreads?|cash[- ]secured puts?|straddles?|strangles?|calendar spreads?|0dte|vertical spreads?|theta|volatility)\b |
| toolsSection | ^#+\s*(tools|features|usage|available tools|what it does|how it works|overview)\b |
| disclaimer | \bdisclaimer\b|\bnot (financial|investment|trading|tax) advice\b|\brisk of loss\b|\bat your own risk\b|\beducational (purposes|use|only)\b|\bsubstantial risk\b|\bno warranty\b|\bcan lose\b|\bfinancial risk\b|\bnot (a )?recommendation\b |
Broker detection (word-bounded)
| Broker | Dependency files | Code | README |
| Alpaca | \balpaca[-_]py\b|\balpaca[-_]trade[-_]api\b|@alpacahq\/|\balpaca[-_]mcp[-_]server\b|\balpaca[-_]trade\b | ^\s*(from|import)\s+alpaca\b|\balpaca\.markets\b|@alpacahq\/|require\(['"]@alpacahq | \balpaca\b |
| tastytrade | \btastytrade\b|\btastyworks\b | ^\s*(from|import)\s+tastytrade\b|\btastyworks\.com\b|\btastytrade\.com\b | \btasty(trade|works)\b |
| Interactive Brokers | \bib[-_]insync\b|\bib[-_]async\b|\bibapi\b|@stoqey\/ib\b|\bib_gateway\b | ^\s*(from|import)\s+(ib_insync|ib_async|ibapi)\b|\bclientportal\b|require\(['"]@stoqey\/ib | \binteractive brokers\b|\bibkr\b|\bIB Gateway\b|\bTWS\b|\bib_insync\b|\bib_async\b |
| Charles Schwab | \bschwab[-_]py\b|\bschwabdev\b|\bschwab[-_]api\b | ^\s*(from|import)\s+(schwab|schwabdev)\b|\bapi\.schwabapi\.com\b|\bschwabdev\b | \bschwab\b|\bthinkorswim\b |
| Tradier | \btradier\b | \btradier\.com\b|\bapi\.tradier\b | \btradier\b |
| Robinhood | \brobin[-_]stocks\b|\brobinhood\b | ^\s*(from|import)\s+robin_stocks\b|\brobinhood\.com\b | \brobinhood\b |
| Derive | \bderive[-_]action[-_]signing\b|\bderive[-_]client\b | \bapi\.lyra\.finance\b|\bderive\.xyz\b|\bderive_client\b | \bderive\.xyz\b|\bderive_client\b|\blyra\.finance\b |
| Moomoo | \bmoomoo[-_]api\b|\bfutu[-_]api\b | ^\s*(from|import)\s+(moomoo|futu)\b | \bmoomoo\b|\bfutu\b|\bfutunn\b |
| Webull | \bwebull\b | \bwebull\.com\b|^\s*(from|import)\s+webull\b | \bwebull\b |
| TradeStation | \btradestation\b | \bapi\.tradestation\.com\b | \btradestation\b |
| E*TRADE | \bpyetrade\b | \bapi\.etrade\.com\b | \be\*?trade\b |
| Deribit | \bderibit\b | \bderibit\.com\b | \bderibit\b |
License detection
OSI-approved SPDX ids that earn 15 points: MIT, Apache-2.0, GPL-2.0, GPL-3.0, LGPL-2.1, LGPL-3.0, AGPL-3.0, BSD-2-Clause, BSD-3-Clause, ISC, MPL-2.0, Unlicense, 0BSD, Zlib, EPL-2.0, BSL-1.0, CC0-1.0, GPL-2.0-only, GPL-3.0-only, GPL-2.0-or-later, GPL-3.0-or-later, AGPL-3.0-only, AGPL-3.0-or-later, LGPL-2.1-only, LGPL-3.0-only, Artistic-2.0, EUPL-1.2, OFL-1.1. GitHub's license detection is used first. Text sniffing is the fallback.
| LicenseRef-Commons-Clause | \bCommons Clause\b |
| LicenseRef-All-Rights-Reserved | \ball rights reserved\b(?![\s\S]{0,400}Permission is hereby granted) |
| AGPL-3.0 | GNU AFFERO GENERAL PUBLIC LICENSE |
| LGPL-3.0 | GNU LESSER GENERAL PUBLIC LICENSE\s+Version 3 |
| GPL-3.0 | GNU GENERAL PUBLIC LICENSE\s+Version 3 |
| GPL-2.0 | GNU GENERAL PUBLIC LICENSE\s+Version 2 |
| Apache-2.0 | Apache License\s+Version 2\.0 |
| MPL-2.0 | Mozilla Public License,? (Version|v\.?) ?2\.0 |
| MIT | Permission is hereby granted, free of charge |
| ISC | Permission to use, copy, modify, and\/or distribute this software for any purpose |
| BSD-3-Clause | Redistribution and use in source and binary forms[\s\S]{0,600}Neither the name |
| BSD-2-Clause | Redistribution and use in source and binary forms |
| Unlicense | This is free and unencumbered software released into the public domain |
Questions and fixes
Think a factor is wrong? Open a correction. Think the rubric is wrong? Open an issue or a pull request on GitHub. Rubric changes are versioned and public.