# RoaringKitty Tracker

A local, browser-based stock screening dashboard with three independent screeners, a watchlist, and a live price chart — all powered by **yfinance** and served from a tiny Python HTTP server. No paid APIs, no cloud services, no account required.

![Dashboard](https://img.shields.io/badge/dashboard-localhost%3A8765-7b6cff?style=flat-square)
![Python](https://img.shields.io/badge/python-3.9%2B-blue?style=flat-square)
![License](https://img.shields.io/badge/license-MIT-green?style=flat-square)

---

## Screeners

| Tab | Strategy | Pillars |
|-----|----------|---------|
| 🐱 **Roaring Kitty** | Deep contrarian value — beaten-down, hated, cheap | Value · Health · Sentiment · Quality |
| 📈 **Kulamägi** | Breakout momentum — strong prior move, tight base near 52W high | Momentum · Structure · Quality · Liquidity |
| 🐻 **Burry** | Fundamental deep value — high ROE, strong cash flow, conservative balance sheet, cheap entry | Quality · Capital · Safety · Value |
| 🎩 **Buffett** | Quality compounders — 5Y median ROIC, durable FCF, low leverage, sensible FCF yield (SEC 10-K data, 10-18yr history) | Quality · Cash · Moat · Balance · Valuation |

---

## Philosophy

Each screener encodes a different answer to the same question: *where does the
market misprice, and why?*

### 🐱 Roaring Kitty — mispricing by abandonment

Keith Gill's GameStop thesis (2019-2021) was not "meme stocks go up." It was
classic deep value with a sentiment overlay: find a business the market has
**left for dead** — huge drawdown, heavy short interest, analyst sells — whose
**balance sheet says it isn't dying**. When the narrative is maximally bearish,
the price embeds the worst case; anything less bad becomes upside, and
crowded shorts turn that upside convex.

The pillars map onto that logic directly:

- **Value (35)** — P/B, P/S, EV/EBITDA, net cash / market cap: is the worst
  case already paid for? Net cash is the heart of it — GME traded near its
  cash pile in 2019.
- **Financial health (25)** — margins, FCF, leverage, current ratio: can it
  survive long enough for sentiment to turn? Contrarianism without solvency
  is just catching knives.
- **Sentiment & crowding (25)** — short % of float, drawdown, put/call, beta:
  is it *hated enough*? Bell curves, not maximums — the most-shorted name is
  often correctly shorted; the sweet spot is heavily but not terminally hated.
- **Quality & momentum (15)** — ROE, revenue growth, analyst consensus,
  relative return: a pulse. Some fundamental reason the pessimism could be
  wrong.

The guardrails (distress cap, dilution penalty) encode the failure mode:
value traps that dilute or default before the thesis plays out.

**A caution from this repo's own history:** running `bt_optimizer.py
--update_config` rewrites the pillar weights to whatever maximized in-sample
Sharpe. At one point that cut Value to ~9% and turned the screener into a
quality/health scan wearing an RK costume — top names had *higher* P/B than
the market median. Optimized weights and philosophical weights are different
tools: the optimizer tells you what worked in one historical window; the
35/25/25/15 baseline states an investment thesis. If the optimizer's answer
contradicts the thesis, that is information about the period, not permission
to abandon the thesis silently.

### 🐻 Burry — mispricing by neglect

Burry's early letters: buy "ick" — unglamorous businesses at low multiples of
book and earnings with strong returns on capital and conservative financing.
No sentiment overlay; the edge is simply that nobody bothers to look.

### 📈 Kulamägi — mispricing by underreaction

Momentum/breakout: strong prior move, tight consolidation near 52-week highs,
institutional-grade liquidity. The edge is behavioral — markets underreact to
regime changes in strong companies.

### 🎩 Buffett — mispricing of durability

The market prices most businesses as if high returns on capital mean-revert.
For a small set with genuine moats they don't. 5Y median ROIC × FCF yield
finds businesses where durability is underpriced — "wonderful company at a
fair price" (1989 letter).

---

## Quick Start (one command)

```bash
bash run_all.sh                    # all data pulls + all four screeners (resumable)
bash run_all.sh --with-backtests   # also validate with walk-forward backtests
bash run_all.sh --finalize-only    # rescore from cache, zero network
bash run_all.sh --serve            # start the dashboard when done
```

First run: several hours (yfinance throttling dominates). Subsequent runs: ~30-45 min.
Everything is resumable — Ctrl+C and rerun continues where it left off.

For a public Hetzner and Cloudflare deployment, see [deploy/README.md](deploy/README.md).

## Manual Quick Start

### 1. Clone the repo

```bash
git clone https://github.com/YOUR_USERNAME/RoaringKittyTracker.git
cd RoaringKittyTracker
```

### 2. Launch (auto-installs dependencies)

```bash
bash launch.sh
```

This creates a `.venv`, installs Python packages, and opens the dashboard at `http://localhost:8765/dashboard.html`.

> **Windows users:** Run the steps manually — see [Manual Setup](#manual-setup) below.

### 3. Run a screener

Open a second terminal in the same folder:

```bash
source .venv/bin/activate   # macOS / Linux

# Quick test — 4 tickers defined in config.yaml
python rk_tracker.py --config config.yaml

# Full US universe (~7 400 tickers, resumes if interrupted)
python run_universe.py --universe_csv us_universe.csv --resume
```

Refresh the dashboard — scores update on every page load.

---

## Screener Commands

Each screener reads from the shared yfinance cache (`data/raw/`), so the second and third screeners are fast for already-cached tickers.

### Roaring Kitty

```bash
# Quick test
python rk_tracker.py --config config.yaml

# Full universe
python run_universe.py --universe_csv us_universe.csv --resume

# Rebuild scores without re-fetching
python rk_tracker.py --config config.yaml --finalize_only
```

### Kulamägi Breakout

```bash
python kulamagi_tracker.py --config config.yaml --universe_csv us_universe.csv --resume
python kulamagi_tracker.py --config config.yaml --finalize_only
```

### Burry Deep Value

```bash
python burry_tracker.py --config config.yaml --universe_csv us_universe.csv --resume
python burry_tracker.py --config config.yaml --finalize_only
```

### Buffett Quality Compounders

Uses SEC 10-K data (10-18 years of history, free XBRL API) instead of yfinance
fundamentals. Two-stage: hard gates (FCF consistency, leverage, dilution,
no financials, ≥$1B cap), then percentile-ranked pillars.

```bash
python download_sec_fundamentals.py --universe_csv us_universe.csv   # ~20 min, 30d cache
python buffett_tracker.py --config config.yaml --universe_csv us_universe.csv --resume
python buffett_tracker.py --config config.yaml --finalize_only
```

### Backtests

```bash
python bt_engine.py --strict_pit    # RK screener — PIT fundamentals + transaction costs
python bt_buffett.py --top_n 20     # Buffett screener — SEC filed-date PIT
python bt_optimizer.py              # weight search (optimizes net-of-cost Sharpe)
```

Backtest caveat: the universe is today's listings, so delisted stocks are
missing (survivorship bias). Treat results as comparisons between configs,
not achievable returns.

> **Tip:** Run all three screeners sequentially. Each reuses the same cached data, so the second and third runs only compute scores — no extra API calls.

---

## Updating the Universe

`us_universe.csv` ships with ~7 400 US-listed tickers (sourced February 2026). To refresh it:

```bash
python download_us_universe_marketcap.py
```

This pulls the current list from yfinance and overwrites `us_universe.csv`.

---

## Dashboard Features

- **Three screeners** — switch between RK, Kulamägi, and Burry tabs; each remembers its own sort, filter, selected stock, and chart settings across refreshes
- **Watchlist** — star any ticker with ☆; the Watchlist tab shows starred stocks from all screeners with a source badge
- **Market cap slider** — log-scale filter from $10M to $1T+
- **Price chart** — 2-year candlestick chart with MA20 / MA50 / MA200, expand toggle, and a timeline scrubber
- **Company info** — sector, analyst targets, 52W range, description, and governance risk scores (sourced from the yfinance cache)
- **Light / dark mode** — toggle in the header; preference is saved
- **Spacebar navigation** — jump to the next stock without touching the mouse
- **Persistent state** — every setting is saved per screener tab in `localStorage`

---

## Manual Setup

```bash
# Create and activate virtual environment
python3 -m venv .venv
source .venv/bin/activate          # macOS / Linux
# .venv\Scripts\activate           # Windows

# Install dependencies
pip install -r requirements.txt

# Start the dashboard server
python serve.py
```

Then open `http://localhost:8765/dashboard.html` in your browser.

---

## Configuration

Edit `config.yaml` to adjust weights, the test universe, and screener-specific settings:

```yaml
# Scoring pillar weights for the Roaring Kitty screener (must sum to 100)
scoring_weights:
  value:              35
  financial_health:   25
  sentiment_crowding: 25
  quality_momentum:   15

# Burry screener weights
burry:
  scoring_weights:
    quality: 35
    capital: 25
    safety:  25
    value:   15

# Kulamägi screener settings
kulamagi:
  min_dollar_volume: 20000000   # $20M minimum daily dollar volume
  scoring_weights:
    momentum:  40
    structure: 35
    quality:   15
    liquidity: 10
```

---

## Project Structure

```
RoaringKittyTracker/
├── dashboard.html              # Single-file web dashboard
├── serve.py                    # Local HTTP server (port 8765)
├── rk_tracker.py               # Roaring Kitty screener + shared utilities
├── kulamagi_tracker.py         # Kulamägi breakout screener
├── burry_tracker.py            # Burry deep value screener
├── run_universe.py             # Convenience wrapper for full RK universe run
├── download_us_universe_marketcap.py  # Refresh the ticker universe
├── config.yaml                 # All weights and settings
├── us_universe.csv             # ~7 400 US-listed tickers
├── requirements.txt
├── launch.sh                   # One-command setup + launch (macOS/Linux)
└── data/
    ├── raw/                    # yfinance cache (info + history per ticker)
    └── outputs/                # Scored CSVs served by the dashboard
```

---

## Data & Privacy

All data is fetched directly from Yahoo Finance via the `yfinance` library and cached locally in `data/raw/`. Nothing is sent to any external server. The dashboard runs entirely on `localhost`.

The `data/` directory is excluded from version control (see `.gitignore`) — each user builds their own local cache by running the screeners.

---

## Requirements

- Python 3.9 or higher
- Internet access for the initial data fetch (yfinance calls Yahoo Finance)
- A modern browser (Chrome, Firefox, Safari, Edge)

---

## License

MIT — do whatever you want with it.
