Portfolio  ›  Projects  ›  AI Stock Predictor
Python Streamlit Data Science scikit-learn

AI Stock Predictor,
31-Module Analysis Workbench

Twenty tabs of market analysis in one Streamlit app: technical indicators, news sentiment, fundamentals, backtesting, Monte Carlo, options chains, crypto and portfolio tooling — all running on a data source that needs no API key.

◆ Local Prototype · runs, demo + live modes
Context: Personal research project  ·  Role: Author, then recovery engineer
Starting point: A working app with API keys in source control and no offline mode
This pass: 8 issues fixed · demo/live split · 30-check test suite · credentials moved to .env
31Modules
20Tabs
7,888Lines of Python
30/30Checks passing
⚠️ Read This First 📊 Module Inventory
Disclaimer

An experimental research tool, not financial advice

⚠️ Do not trade on this

The signals this system produces come from heuristic scoring — a hand-chosen 40% technical / 30% sentiment / 30% fundamental weighting — and a model fitted to past prices. They are not predictions of future returns.

There is no backtested profitability figure, no claimed accuracy, and no live-trading validation anywhere in this project, because none has been produced. The scoring weights and signal thresholds were chosen by hand; they were not fitted, tuned or cross-validated against outcomes.

This disclaimer is not confined to this page. It is pinned permanently in the running app's sidebar, so no tab can be screenshotted without it.

📈 About the ML tab's accuracy number

The ML tab reports a cross-validation score on whatever data you load. That is an in-sample fit statistic, not a forward-return accuracy, and reading it as "the model is right X% of the time about the future" would be wrong. The app labels it accordingly.

Architecture

31 modules — not 35

This project is usually described as a "35-module platform". Counting the files in modules/ gives 34, and three of those are not modules at all. The real figure is 31 importable modules plus app.py.

🔍 What the other three are

tabs_new.py, tabs_new2.py and tabs_new3.py cannot be imported. Their own headers say "paste this content at the BOTTOM of app.py"; they reference st without importing Streamlit and call st.tabs() at module scope. That content is already present in app.py from line 1119 — they are superseded working copies.

They were kept on disk rather than deleted, and the test suite now asserts they are fragments, so nobody mistakes them for live modules later. Correcting the count downward was the honest option; the alternative was letting a number stand that the directory does not support.

#ModuleRole
1data_fetcheryfinance OHLCV + company info; demo/live mode selection
2technicalsRSI, MACD, Bollinger, MAs, Stochastic, ATR, support/resistance
3sentimentVADER over news headlines; RSS fallback with no key
4fundamentalsP/E, revenue growth, margin, ROE scoring
5scorerWeighted final score → signal
6ml_modelRandom Forest + Gradient Boosting on engineered features
7backtesterRSI / MA Crossover / MACD / Buy & Hold, with stops and targets
8monte_carloGeometric Brownian Motion price-path simulation
9risk_calculatorPosition sizing, R:R, portfolio risk, scenarios
10strategy_builderUser-defined rule strategies, persisted to disk
11screenerMulti-criteria universe screening
12multiframeMulti-timeframe agreement analysis
13correlationReturn correlation, clustering, diversification score
14portfolioPosition tracking and P&L
15watchlistWatchlist persistence and scanning
16journalTrade journal with win/loss statistics
17alertsPrice/indicator alert rules and triggers
18notifierDesktop toast + SMTP email delivery
19voice_alertsOffline text-to-speech briefings
20live_dashboardLive quotes, intraday, index summary
21market_summaryMarket mood, buy/sell lists, daily briefing
22heatmapSector performance heatmap
23earningsEarnings dates and history
24dividendsDividend history and yield scanning
25insider_trackerInsider transactions and institutional holders
26optionsOptions chain, expiries, summary metrics
27cryptoCrypto quotes, scanning, indicators
28news_aggregatorSector/ticker news aggregation
29ai_chatOptional Anthropic-backed chat; local fallback without a key
30exporterExcel / CSV export
31themeTheme configuration and chart palettes
tabs_new, tabs_new2, tabs_new3Paste fragments, not modules — already inlined in app.py
Data Sources

Demo and live, never confused

SourceKey requiredUsed for
yfinanceNoPrimary — OHLCV, company info, options, crypto
Public RSSNoNews for sentiment (default path)
NewsAPIOptionalRicher news; free tier 100/day
Alpha VantageOptionalSecondary quotes; free tier 25/day
AnthropicOptionalAI Chat tab only; local fallback without it

The app is fully functional with no keys and an empty .env. yfinance requires no account, which is why it is the primary source rather than a paid feed.

The two modes

📡

Live mode

  • STOCK_DEMO_MODE=false (default)
  • Fetches current data from yfinance
  • Sidebar shows a green LIVE MODE badge
  • Quotes may be delayed up to 15 minutes
🗃️

Demo mode

  • STOCK_DEMO_MODE=true
  • Reads a recorded snapshot from data/demo/
  • Sidebar shows a red DEMO MODE badge
  • Per-analysis banner names the capture date

✅ Demo data is real, recorded market data — not synthetic

The snapshot is captured through the same yfinance path the live app uses, by scripts/capture_demo_snapshot.py, and stamped with its capture date. Bundled: AAPL, AMZN, MSFT, NVDA, TSLA — 251 rows each, captured 2026-08-11.

No fabricated prices exist anywhere in this project. There is no code path that returns snapshot data without setting the is_demo flag, and the test suite asserts that a missing snapshot returns an error rather than inventing values.

Credentials

Keys were in the source tree

The most valuable part of this pass had nothing to do with forecasting.

#IssueFix
1API keys edited directly into config.pyAll keys read from environment / .env; .env.example added
2anthropic_key.json and notifier_config.json held a plaintext API key and a Gmail app password, with no ignore file.gitignore added covering both plus all runtime state
3ai_chat.load_api_key preferred the file over the environmentEnvironment now wins, so .env can override a stale file
4pyttsx3 imported by voice_alerts but absent from requirementsAdded, along with python-dotenv
5No offline mode — unusable without internetDemo mode over a real recorded snapshot
6Nothing on screen distinguished live from stale datais_demo threaded through; sidebar badge + per-analysis banner
7No standing risk disclaimerPermanent sidebar disclaimer
8No teststests/test_smoke.py, 30 checks
Verification

Measured, not asserted

CheckResult
All 31 modules import cleanlyPass
Smoke suite, demo mode30 / 30
Smoke suite, live mode27 / 27
Streamlit servesHTTP 200
/_stcore/healthok
Missing snapshot returns error, not fabricated dataPass
End-to-end on the real snapshotAAPL, 251 rows → RSI 44.27 (Neutral) → HOLD, score 50.3, risk MEDIUM

The three extra checks in demo mode are snapshot-specific assertions that cannot run against a live feed.

💻 Run it

python -m venv .venv && .venv\Scripts\activate
pip install -r requirements.txt
copy .env.example .env          # optional — every key is optional

streamlit run app.py
python -m tests.test_smoke      # no network, no keys needed
Technologies

Actual stack

Interface
Streamlit 1.35Plotly20 tabs
Data
yfinancepandasNumPyfeedparser
Analysis
scikit-learnVADERMonte CarloRandom Forest
Practice
30-check smoke suite.env credentialsLabelled data provenance
Output

Actual application output

Rendered by the application's own charting code on the bundled demo snapshot — 251 rows of AAPL, captured 2026-08-11. The demo-data label is produced by the app, not added to the screenshot afterwards.

AI Stock Predictor candlestick chart of AAPL over one year with Bollinger Bands and 50/200-day moving averages, labelled as demo snapshot data
AAPL · 1Y · Bollinger Bands + MA50/MA200Demo snapshot, 251 rows — the label is part of the app's output

The honest version of a forecasting demo

Financial tooling makes overclaiming easy and consequential. The engineering worth showing here is the demo/live split and the credential cleanup, not a made-up accuracy number.