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.
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:
- 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 (SPIVA). Stock picking must therefore be approached with great caution.
Capture.video.du.2026-02-21.12-45-28.mp4
- Python 3.11+
- Internet access (for market data providers and optional LLM calls)
sudo apt install pipx
pipx ensurepath
pipx install .
grahambrew install pipx
pipx ensurepath
pipx install .
grahampy -m pip install --user pipx
py -m pipx ensurepath
pipx install .
grahampython -m pip install .
grahamgrahamlaunches 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> "...")
Market data provider (optional):
- default:
yfinance - alternative:
defeatbeta-apiwithGRAHAM_MARKET_DATA_PROVIDER=defeatbeta - if
defeatbeta-apiis unavailable or incomplete, the app falls back toyfinanceautomatically
- Stock screener with ranking, details, and live output log.
- Graham-style scoring with intrinsic value (
V) and margin of safety (MoS). - Dynamic universes and index loaders (
/universe,/indices) with persisted defaults. - Real-time price refresh with sortable table (
score,price,as_of,MoS,P/E, ...). - Rich command UX: autocompletion, prompt history, keyboard shortcuts, quick ticker/company search.
- Economic moat analysis via
/moat TICKERwith model-aware prompts and Markdown-rendered output. - Optional LLM workflows for
/explainand/moatwith deterministic fallback when unavailable. - Multi-language display support (
/lang) persisted in~/.graham/config.json. - Export scan results to
csvorjson.
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
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).
/help/keys/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]/moat TICKER/rating GREEN ORANGE/export [csv|json]
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/moat-> current universe tickers
Keyboard:
F1show keyboard shortcutsββmove in suggestions (or prompt history when no suggestion list is visible)TABcompleteENTERacceptCtrl+Lclear output log panelCtrl+Rsearch/filter by ticker or company in the top ranking table
Mouse:
- click a block in the output log to copy it
- click the details/criteria panel to copy the full details block
- on Linux, install
wl-copy(Wayland) orxclip/xselfor reliableCtrl+Vpaste
Default model: none
nonemeans no LLM API call- if a model is set,
/explainand/moatcan calllitellm /moatasks the model to answer in the language currently configured with/lang- 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.- for maximum compatibility, you can set explicit IDs like
provider/model(example:openai/gpt-5,anthropic/claude-sonnet-4-5,gemini/gemini-2.5-pro) - Your selected model is persisted in
~/.graham/config.json.
Environment variables (depending on provider):
OPENAI_API_KEYANTHROPIC_API_KEYGEMINI_API_KEYorGOOGLE_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- 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.
- 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
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
graham/
main.py
tui.py
graham.py
commands.py
llm.py
universes/
sample.txt
tests/
test_graham.py
pytestRelease process (GitHub + PyPI): see RELEASING.md.