Fullscreen TUI stock screener inspired by Benjamin Graham
Project description
👤 Who Is Benjamin Graham?
Benjamin Graham (1894-1976) is widely considered the father of value investing. He promoted disciplined stock selection based on financial strength, earnings quality, and buying at a discount to intrinsic value.
This project applies a practical version of Graham-style screening in a fullscreen terminal UI.
Past performance does not guarantee future results.
Investing involves risk.
This application does not provide investment advice.
Over 20 years, among professional equity investors, more than 90% of funds underperform the market. Stock picking must therefore be approached with great caution.
Classical Graham-style rules:
- Adequate company size
- Avoid very small companies with fragile access to financing and limited reporting quality.
- In practice, this is often implemented with minimum revenue or market-cap thresholds.
- Strong financial condition
- Balance-sheet resilience is central: healthy liquidity and controlled leverage.
- Typical checks include current ratio, debt versus current assets, and debt service capacity.
- Earnings stability
- Prefer businesses with positive earnings over a long period, avoiding repeated deficits.
- Stability reduces downside risk and improves confidence in valuation inputs.
- Long dividend record
- Graham historically favored companies paying regular dividends for many years (often ~20 years).
- A long dividend history acts as a discipline signal for management and cash generation.
- Earnings growth
- Look for sustained, not one-off, profit growth over multi-year windows.
- CAGR-like approaches are commonly used to smooth noisy year-to-year moves.
- Moderate price/earnings ratio
- Classical references often mention a cap around 15x earnings.
- The spirit is paying a reasonable multiple, not maximum growth premiums.
- Moderate price/book ratio
- Classical references often mention a cap near 1.5x book value.
- Combined with P/E, this aims to avoid overpaying for low-quality balance sheets.
Reference:
🎬 Demo
https://github.com/user-attachments/assets/13609476-83ff-4abb-b83a-05ceda12e6ac
🚀 Installation
🧰 Prerequisites
- Python 3.11+
- Internet access (for yfinance and optional LLM calls)
🐧 Ubuntu
sudo apt install pipx
pipx ensurepath
pipx install .
graham
🍎 macOS
brew install pipx
pipx ensurepath
pipx install .
graham
🪟 Windows
py -m pip install --user pipx
py -m pipx ensurepath
pipx install .
graham
🔁 Alternative (pip)
python -m pip install .
graham
💻 Usage
grahamlaunches the fullscreen TUI.graham --helpshows a minimal CLI help.
The app uses one input box at the bottom:
- slash commands (
/help,/scan, ...) - free prompt mode (if a ticker is selected, it behaves like
/explain <ticker> "...")
📊 Product Flow
Pipeline:
- Load a universe from
universes/*.txt - Compute fundamentals once
- Refresh prices every
Xseconds - Recompute Margin of Safety (MoS)
- Rank by:
scoredescendingMoSdescendingP/Eascending
Ranking columns:
rank | ticker | company | score | rating | price | as_of | V | MoS | P/E | P/B | dividend
Score formula:
PASS / scored_criteriaN/Acriteria are excluded from the denominator
🧠 Graham Logic
7 implemented criteria:
- S&P earnings/dividend rating >= B
- Not available in yfinance ->
N/A - Ignored in score if
N/A
- Not available in yfinance ->
- Total debt / current assets < 1.10
- Current ratio > 1.50
- Positive EPS growth over ~5 years with no deficit (best effort if data is partial)
- P/E <= 9.0
- P/B < 1.20
- Dividends required by default (
dividendRate > 0)
This app intentionally uses stricter default thresholds for criteria 5 and 6 (P/E <= 9.0, P/B < 1.20) to stay conservative.
Intrinsic value formula:
V = EPS * (8.5 + 2g) * 4.4 / YYconfigurable (default4.4)g = EPS CAGRif available, otherwise0MoS = (V - price) / price
Important: the Graham formula should be used with caution in modern markets. Accounting standards, sector composition, intangible assets, and interest-rate regimes have changed significantly since the original framework.
Robustness policy:
- Missing data => show
N/Awith an explanatory note. - Never crash by design (errors are captured and logged when possible).
🧭 Slash Commands
/help/universes/indices [name](examples:sp500,msci_world,msci_emerging,dax40,nikkei225,csi300)/languages/lang [language-code]/model [none|model-name]/universe [sample|world|usa|emerging_markets|china|india|germany|europe|france|japan|custom:path]/default-universe [name|custom:path]/scan [--top N] [--min-score N] [--refresh SECONDS]/screen TICKERS_CSV/explain [TICKER] [optional question]/rating GREEN ORANGE/export [csv|json]
✨ Autocompletion
Context-aware autocompletion in the input overlay:
/-> command list/lang-> common language codes (en,fr,es,de, ...)/model->none+ model examples/universe-> available universes +custom:path/default-universe-> available presets/export->csv/json/scan->--top,--min-score,--refresh
Keyboard:
↑↓move in suggestionsTABcompleteENTERaccept
🤖 Optional LLM
Default model: none
nonemeans no LLM API call- if a model is set,
/explaincan calllitellm - if LLM call fails, the app logs the error and falls back to a deterministic template
/modelaccepts any valid provider model ID; the built-in suggestion list contains verified official IDs.- Your selected model is persisted in
~/.graham/config.json.
Environment variables (depending on provider):
OPENAI_API_KEYANTHROPIC_API_KEYGEMINI_API_KEY
Examples:
/model gpt-5.2
/model claude-opus-4-5
/model gemini-3-pro-preview
Configure API keys in your shell before launching graham:
export OPENAI_API_KEY="sk-..."
export ANTHROPIC_API_KEY="..."
export GEMINI_API_KEY="..."
graham
🌍 Display Language
- Default display language is English (
en). - List supported language codes:
/languages
- Change language at runtime with:
/lang fr
- Translation uses
deep-translator(Google Translate backend), not LLMs. - If translation fails for any reason, the app keeps English text (safe fallback).
- Your chosen language is persisted in
~/.graham/config.json.
🎯 Score Rating
- The ranking now includes a visual rating badge:
🟢if score is above the green threshold🟠if score is between orange and green thresholds🔴otherwise
- Configure thresholds at runtime:
/rating 0.80 0.60
You can also pass percentages:
/rating 80 60
🌐 Universe Presets
Available universe files now include:
sampleworldusaemerging_marketschinaindiagermanyeuropefrancejapan
Set and persist your default universe:
/default-universe world
Your latest selected universe is saved in ~/.graham/config.json and automatically reused on next launch.
List all universes with metadata:
/universes
Load a supported index and fetch constituents via yfinance:
/indices sp500
🗂️ Project Structure
graham/
main.py
tui.py
graham.py
commands.py
llm.py
universes/
sample.txt
tests/
test_graham.py
🧪 Tests
pytest
🚀 Releases
Release process (GitHub + PyPI): see RELEASING.md.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file graham_agent-0.1.1.tar.gz.
File metadata
- Download URL: graham_agent-0.1.1.tar.gz
- Upload date:
- Size: 31.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
888c6eec0baadedc656c2b58688d2a98b7fe8821136792be39418f7fb173bb04
|
|
| MD5 |
1846e317b9da5a9c630c2d26e3f6b6cb
|
|
| BLAKE2b-256 |
7350073167427a2140f064b2a7ffe7c9496499336c6b359a9a6896caf553ffbe
|
Provenance
The following attestation bundles were made for graham_agent-0.1.1.tar.gz:
Publisher:
release.yml on fdelbrayelle/graham
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
graham_agent-0.1.1.tar.gz -
Subject digest:
888c6eec0baadedc656c2b58688d2a98b7fe8821136792be39418f7fb173bb04 - Sigstore transparency entry: 975804217
- Sigstore integration time:
-
Permalink:
fdelbrayelle/graham@063234d586948c68ff237bcf374cac8864d41c88 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/fdelbrayelle
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@063234d586948c68ff237bcf374cac8864d41c88 -
Trigger Event:
push
-
Statement type:
File details
Details for the file graham_agent-0.1.1-py3-none-any.whl.
File metadata
- Download URL: graham_agent-0.1.1-py3-none-any.whl
- Upload date:
- Size: 29.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f19b4917b7b1380feb4bfac36dc0c799998380f3807ead701d7e98c4cf8a9187
|
|
| MD5 |
e4d20c3a53e86eabe4285063bc6ca15f
|
|
| BLAKE2b-256 |
bdd3f0177861fb6504473911a6186c9574d8d177af7a152e10e0a17de7f1f0ed
|
Provenance
The following attestation bundles were made for graham_agent-0.1.1-py3-none-any.whl:
Publisher:
release.yml on fdelbrayelle/graham
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
graham_agent-0.1.1-py3-none-any.whl -
Subject digest:
f19b4917b7b1380feb4bfac36dc0c799998380f3807ead701d7e98c4cf8a9187 - Sigstore transparency entry: 975804225
- Sigstore integration time:
-
Permalink:
fdelbrayelle/graham@063234d586948c68ff237bcf374cac8864d41c88 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/fdelbrayelle
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@063234d586948c68ff237bcf374cac8864d41c88 -
Trigger Event:
push
-
Statement type: