Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

IndexCheck

Know your risk before the market shows it to you.

A free, no-login portfolio stress-testing tool. Paste your holdings, watch them replayed through the four worst markets of the last 25 years, and read the damage in plain English.

FastAPI · React 19 · yfinance · pandas · NumPy · Claude


Table of contents


Introduction

Every investing app shows you what your portfolio did. Almost none honestly show you what it could lose. Real stress-testing tools exist, but they are built for financial advisors, hidden behind logins and sales calls, and priced in the thousands per year.

IndexCheck closes that gap for everyone else. You type in tickers and weights, and it replays that exact mix through the 2008 financial crisis, the 2020 COVID crash, the 2022 rate-hike selloff, and the 2000 dot-com bust, using real historical prices. It reports the deepest drawdown, how long recovery took, which holding hurt most, and how concentrated you really are. A language model then explains it all like a smart friend, not a whitepaper.

Note

IndexCheck is an educational tool. It does not give investment advice, make recommendations, or predict the future. Results are historical replays and statistics, not forecasts.


Key features

Feature What it does
Historical crash replay Runs your portfolio through 4 real crises using split- and dividend-adjusted closing prices.
Max drawdown and recovery Reports the deepest peak-to-trough fall and the time to climb back to breakeven.
Correlated Monte Carlo Projects 1,000 forward paths with a covariance-aware simulation (API today, chart UI next).
Concentration analysis Flags holdings that move together and scores diversification 0 to 100.
Plain-English narration Claude turns the raw statistics into a readable summary, with a template fallback when no key is set.
Honest coverage A holding with no history for a window is excluded and disclosed, never silently filled in.
Zero friction No account, no signup, no database. Your portfolio is never stored.

Tech stack

BackendPython · FastAPI · Uvicorn · yfinance · pandas · NumPy · slowapi · Anthropic SDK
FrontendReact 19 · Vite 7 · Tailwind CSS 4
AI layerClaude (claude-sonnet-4-20250514 by default)
DataYahoo Finance daily bars via yfinance

Architecture

Browser ──POST /analyze──▶  FastAPI  ──▶  scenarios.py   (historical replay)
   ▲                          │      ──▶  monte_carlo.py (correlated sims)
   │                          │      ──▶  correlation.py (diversification)
   │                          │      ──▶  narrate.py     (Claude explanation)
   └───────JSON results───────┘                │
                                    data.py (validated fetch + LRU cache)
                                               │
                                        Yahoo Finance

The backend is stateless: requests are computed in memory and returned. Price history is cached briefly by ticker (never by portfolio). Nothing about a user or a portfolio is persisted anywhere.


Project structure

Click to expand the full tree
.
├── backend/
│   ├── main.py            # FastAPI app: routes, middleware, security, validation
│   ├── config.py          # All env-driven config and safety limits
│   ├── data.py            # Ticker validation + yfinance fetch + bounded LRU cache
│   ├── scenarios.py       # Historical crisis replay and drawdown/recovery math
│   ├── monte_carlo.py     # Correlated geometric Brownian motion simulation
│   ├── correlation.py     # Correlation matrix + diversification score
│   ├── narrate.py         # Claude narration with deterministic fallback
│   ├── requirements.txt
│   └── .env.example
├── frontend/
│   ├── src/
│   │   ├── App.jsx                 # Router + landing page + results flow
│   │   ├── main.jsx                # Entry point, applies saved a11y preference
│   │   ├── index.css               # Design tokens, a11y (focus, skip link, high contrast)
│   │   ├── disclaimers.js          # Single source of truth for legal copy
│   │   ├── components/
│   │   │   ├── CrashCanvas.jsx     # Procedural candlestick crash animation
│   │   │   ├── PortfolioInput.jsx  # Add/remove holding rows
│   │   │   ├── ScenarioCard.jsx    # One scenario result with coverage disclosure
│   │   │   ├── NarrationPanel.jsx  # Typewriter plain-English read
│   │   │   ├── HowItWorks.jsx      # Methodology, named algorithms
│   │   │   └── Footer.jsx          # Disclaimer, legal links, contrast toggle
│   │   └── pages/
│   │       ├── Terms.jsx           # /terms
│   │       ├── Privacy.jsx         # /privacy
│   │       └── LegalLayout.jsx     # Shared legal page frame
│   ├── vercel.json                 # SPA rewrites for client routing
│   └── vite.config.js              # Dev proxy /api -> :8000
├── dev.sh                          # Start backend + frontend together
├── LICENSE
├── README.md
└── README.mdx
Folder Purpose
backend/ Stateless FastAPI service. Each analysis type is its own module.
frontend/ Vite + React single-page app. Institutional, editorial design.
frontend/src/components/ Presentational and interactive UI pieces.
frontend/src/pages/ Legal pages served via a tiny built-in router.

Installation

Important

You need Python 3.11+ and Node 18+. Start the backend first, then the frontend.

1. Backend

cd backend
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
cp .env.example .env        # optional: add ANTHROPIC_API_KEY for richer narration
.venv/bin/uvicorn main:app --reload --port 8000

Open http://127.0.0.1:8000/docs for the interactive API.

2. Frontend

cd frontend
npm install
npm run dev                 # http://localhost:5173, proxies /api -> :8000

Tip

After the one-time setup above, run ./dev.sh from the repo root to start the backend and frontend together (it stops the backend when you quit). No API key? The app still works fully. Narration falls back to clear, deterministic templates and every number is computed the same way.


Usage

  1. Open the app and scroll to Your portfolio.
  2. Add holdings as rows, for example AAPL 40, MSFT 30, SPY 20, CASH 10. Weights are relative and auto-normalized.
  3. Click Run the stress test.
  4. Read the results, worst crisis first: max drawdown, recovery time, biggest drag, and a concentration score.

Query the API directly:

curl -s -X POST http://127.0.0.1:8000/analyze \
  -H "Content-Type: application/json" \
  -d '{
        "holdings": [
          {"ticker": "SPY", "weight": 60},
          {"ticker": "QQQ", "weight": 40}
        ],
        "scenario": "2008 Financial Crisis",
        "horizon_years": 5
      }'
Sample response (trimmed)
{
  "scenario": {
    "scenario": "2008 Financial Crisis",
    "max_drawdown_pct": -44.97,
    "trough_date": "2009-03-09",
    "recovery": { "recovered": true, "years": 1.6 },
    "worst_holding": { "ticker": "SPY", "total_return": -0.3713 },
    "covered_weight": 1.0,
    "skipped_tickers": []
  },
  "correlation": { "diversification_score": 4, "high_correlation_pairs": [ ... ] },
  "monte_carlo": { "outcomes": { "5yr": { "median_return_pct": 186.09 } } }
}

API reference

Base URL in dev: http://127.0.0.1:8000

Method Endpoint Description
GET /health Liveness plus whether narration is live or fallback.
GET /scenarios List the four historical crisis windows.
POST /scenario Run one historical scenario against a portfolio.
POST /monte-carlo Forward projection: fan-chart bands and outcome stats.
POST /correlation Correlation matrix and diversification score.
POST /analyze All three analyses in one call. This is what the UI uses.

Request body (analysis routes):

{
  "holdings": [{ "ticker": "AAPL", "weight": 40 }],
  "scenario": "2008 Financial Crisis",
  "horizon_years": 5
}

Note

CASH is a supported synthetic holding that holds its value through any crash. Weights do not need to sum to 100; they are normalized for you.


Environment variables

All backend config is read from the environment. Only ANTHROPIC_API_KEY is sensitive, and it is never exposed to the frontend.

Variable Default Purpose
ANTHROPIC_API_KEY (unset) Enables Claude narration. Falls back to templates if unset.
ANTHROPIC_MODEL claude-sonnet-4-20250514 Model used for narration.
ALLOWED_ORIGINS local dev URLs Comma-separated CORS allowlist. Set to your deployed frontend.
MAX_HOLDINGS 30 Max holdings per request.
MAX_TICKER_LEN 10 Max ticker length.
MC_MAX_SIMS 2000 Monte Carlo simulation cap.
MC_SEED 42 Seed for reproducible simulations.
RATE_LIMIT_DEFAULT 60/minute Global per-IP limit.
RATE_LIMIT_ANALYZE 12/minute Per-IP limit on analysis routes.
YF_TIMEOUT_SECONDS 20 Timeout for market-data fetches.
ANTHROPIC_TIMEOUT_SECONDS 30 Timeout for narration calls.
PRICE_CACHE_MAX_ENTRIES 512 Bounded LRU price cache size.
MAX_REQUEST_BYTES 32768 Request body size cap.

Frontend: set VITE_API_URL to the deployed backend URL in production (otherwise the dev proxy handles /api).


How the numbers are made

No black box. Each method, by name:

  • Crash replay — a buy-and-hold weighted index, V(t) = Σ wᵢ · Pᵢ(t) / Pᵢ(0), with maximum drawdown measured against a running peak.
  • Recovery — first passage back to the prior peak, capped at six years.
  • Biggest drag — contribution weighted (weight × return), so a tiny position cannot outrank a large one.
  • Concentration — pairwise Pearson correlation of daily returns over two years; score is (1 − average correlation) × 100, credited for cash.
  • Monte Carlo — correlated geometric Brownian motion: per-asset drift and volatility from three years of log returns, cross-asset correlation applied via a Cholesky decomposition of the covariance matrix.

Warning

The Monte Carlo drift is estimated from the recent past, so after a long bull run the median projection leans optimistic. Treat simulated ranges as possibilities, not predictions.


Security and privacy

  • Stateless. No database, no accounts, no stored portfolios.
  • Strict input validation. Tickers are whitelisted (^[A-Z0-9][A-Z0-9.\-^=]{0,9}$), with caps on holdings, weights, and body size.
  • Rate limiting per IP via slowapi.
  • Security headers: nosniff, X-Frame-Options: DENY, strict CSP, no-store.
  • No error leakage. Internal exceptions return generic messages.
  • Secrets from env only. .env is gitignored; nothing sensitive reaches the browser bundle.

Caution

Market data comes from Yahoo Finance via the unofficial yfinance library. It has no SLA and its terms restrict redistribution. Fine for a personal project; revisit with a licensed data vendor before commercial scale.


Accessibility

  • Body text meets WCAG AA contrast; a high-contrast mode raises everything further.
  • High contrast activates automatically with the OS prefers-contrast setting, or manually via the footer toggle (the only thing stored locally).
  • Respects prefers-reduced-motion (the crash animation stops).
  • Visible keyboard focus rings, a skip-to-content link, labeled inputs, and semantic landmarks.

Contributing

Contributions are welcome.

  1. Fork and branch: git checkout -b feature/your-idea.
  2. Backend: keep modules single-purpose and preserve the verified reference number (60/40 SPY/QQQ through 2008 = −44.97%).
  3. Frontend: run npm run build before opening a PR; keep the design tokens and avoid em dashes in copy.
  4. Open a pull request describing the change and any new endpoints or env vars.

License

Released under the MIT License. Market data belongs to its respective providers.


Support and contact

Questions, bugs, or ideas: shricharansriram@berkeley.edu

Built as an educational tool. Not financial advice.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages