Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

182 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Stock Analysis โ€” Gemma 4 Edition

Refactored from the original FinBERT + BART notebook into a small Python package that runs Gemma 4 through either HuggingFace Transformers on a single Google Colab GPU or a local Ollama server, with multi-agent Bull-vs-Bear reasoning and a backtest that uses the same deterministic signal as the live pipeline.

Status: Phase 1 complete ยท 84 tests passing ยท all Critical and Major review findings resolved (see CODE_REVIEW.md).


Phase 1 features โ€” "Make it credible"

Feature Module Why it matters
๐Ÿ—ฃ๏ธ Multi-agent Bull vs Bear debate src/agents.py Two Gemma calls argue opposite sides; a third judges. Surfaces both upside and downside, reduces overconfidence.
โฎ๏ธ Backtesting framework src/backtest.py Replays the pipeline over the past ~21 trading days and reports hit rate / Sharpe / max drawdown โ€” you can finally measure the system.
๐ŸŒ Gradio web UI webapp.py One command (python webapp.py) โ†’ public *.gradio.live link valid 72 h. Tabs for live analysis + backtesting.
โš–๏ธ Deterministic aggregation src/preprocessing.py A single aggregate_polarity() function is used by both live and backtest paths so the two are numerically consistent.
๐Ÿงช Mock-based integration tests tests/ 84 tests, no GPU / Ollama server / API keys required โ€” runs offline.

Pipeline

INPUT (symbol, horizon)
   โ”‚
   โ”œโ”€โ–บ Stage 1  Data collection         (yfinance daily + intraday + News API)
   โ”œโ”€โ–บ Stage 2  Pre-processing          (clean โ€ข TF-IDF dedup โ€ข time-decay
   โ”‚                                     โ€ข price features on DAILY series:
   โ”‚                                     SMA/EMA/RSI/ATR/pct_1d/pct_5d/pct_30d)
   โ”œโ”€โ–บ Stage 3  Per-news scoring        (Gemma 4 โ†’ "scores" array, index-aligned)
   โ”œโ”€โ–บ          Aggregation             (preprocessing.aggregate_polarity
   โ”‚                                     โ€” single source of truth)
   โ”œโ”€โ–บ Stage 4  Synthesis & advice
   โ”‚   โ”‚
   โ”‚   โ”œโ”€โ–บ (default) src/agents.py      Bull โ†’ Bear โ†’ Judge debate
   โ”‚   โ””โ”€โ–บ (--no-debate)  src/stages.py single-call synthesize
   โ”‚
   โ””โ”€โ–บ Stage 5  Presentation            (stdout โ€ข Plotly โ€ข Gradio)

Every stage talks to the LLM through one small abstraction layer (src/llm/), so the engine is a config switch โ€” no pipeline or UI code changes:

  • LLM_BACKEND=hf โ€” a local open-weight model (Gemma 4 / Phi-4 / Qwen / โ€ฆ) via HuggingFace transformers, 4-bit, single GPU. Loaded once per session via functools.lru_cache.
  • LLM_BACKEND=ollama โ€” a local model served by Ollama and called through http://localhost:11434/api/chat. The Ollama daemon owns model loading and quantization, so the Python process does not import PyTorch.

See LLM backends below.


Project layout

.
โ”œโ”€โ”€ main.py                  โ† CLI: `analyze` and `backtest` subcommands
โ”œโ”€โ”€ webapp.py                โ† Gradio app (analyze + backtest tabs)
โ”œโ”€โ”€ demo_ui.py               โ† Mock-data Plotly preview (no GPU / no keys needed)
โ”œโ”€โ”€ config.py                โ† env-driven settings
โ”œโ”€โ”€ pytest.ini
โ”œโ”€โ”€ requirements.txt
โ”œโ”€โ”€ requirements-dev.txt
โ”œโ”€โ”€ .env.example
โ”œโ”€โ”€ .gitignore
โ”œโ”€โ”€ README.md                โ† this file
โ”œโ”€โ”€ CODE_REVIEW.md           โ† Phase 1 review (all Critical/Major resolved)
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ apis.py              โ† Stage 1: yfinance (daily + intraday) + News API
โ”‚   โ”œโ”€โ”€ preprocessing.py     โ† Stage 2: cleaning, dedup, time-decay, features,
โ”‚   โ”‚                          aggregate_polarity, stance_from_polarity
โ”‚   โ”œโ”€โ”€ llm/                 โ† LLM abstraction (pluggable backends)
โ”‚   โ”‚   โ”œโ”€โ”€ __init__.py      โ†   generate / generate_json dispatcher + JSON repair
โ”‚   โ”‚   โ”œโ”€โ”€ hf_local.py      โ†   local model (Gemma/Phi/Qwen, 4-bit) loader
โ”‚   โ”‚   โ””โ”€โ”€ ollama_local.py  โ†   local Ollama REST API adapter
โ”‚   โ”œโ”€โ”€ stages.py            โ† Stage 3 + (alt) single-call Stage 4
โ”‚   โ”œโ”€โ”€ agents.py            โ† (default) Bull / Bear / Judge debate
โ”‚   โ”œโ”€โ”€ backtest.py          โ† Replays the pipeline on past data
โ”‚   โ”œโ”€โ”€ pipeline.py          โ† Orchestrator โ†’ AnalysisResult
โ”‚   โ””โ”€โ”€ display.py           โ† Text / Plotly / ipywidgets renderers
โ””โ”€โ”€ tests/
    โ”œโ”€โ”€ conftest.py          โ† Mocks for yfinance + NewsAPI + LLM
    โ”œโ”€โ”€ test_apis.py         โ† yfinance schema normalisation + wrapper contracts
    โ”œโ”€โ”€ test_preprocessing.py
    โ”œโ”€โ”€ test_llm.py          โ† JSON extractor + generate_json retry
    โ”œโ”€โ”€ test_ollama_backend.py โ† Ollama HTTP adapter + dispatcher
    โ”œโ”€โ”€ test_stages.py         โ† structured score contract + title restoration
    โ”œโ”€โ”€ test_pipeline.py
    โ”œโ”€โ”€ test_backtest.py
    โ””โ”€โ”€ test_webapp.py

Setup (Google Colab via VSCode extension)

  1. Open the project in VSCode and connect to a Colab runtime. Pick Runtime โ†’ Change runtime type โ†’ GPU (T4 / L4 / A100).
  2. Install requirements on the Colab runtime:
    pip install -q -r requirements.txt
  3. Create .env next to main.py (copy from .env.example):
    cp .env.example .env
    Fill in:
    • NEWS_API_KEY
    • LLM_BACKEND (hf for Transformers on a CUDA GPU, ollama for a local Ollama server โ€” see LLM backends)
    • HF_TOKEN (when LLM_BACKEND=hf; also accept the model licence on Hugging Face for gated models like Gemma)
    • MODEL_ID (optional override, e.g. google/gemma-4-4b-it, microsoft/phi-4)
    • GEMMA_ATTN_IMPL (optional, default eager)

Usage

CLI

# Live analysis with Bull vs Bear debate (default)
python main.py analyze AAPL

# Same but force single-call synthesis (faster, less depth)
python main.py analyze AAPL --no-debate

# Open the Plotly candlestick after analysis
python main.py analyze TSLA --show-chart

# Machine-readable output
python main.py analyze NVDA --json

# Backtest the last 21 trading days (capped at 22 by the News API free tier)
python main.py backtest AAPL --days 21 --show-chart

# Backtest with JSON output (for piping into another script)
python main.py backtest NVDA --json > nvda_backtest.json

Web UI (Gradio)

python webapp.py

This prints a *.gradio.live link (valid 72 h) you can open on any device. The UI has two tabs:

  • Analyze โ€” symbol, horizon, debate on/off โ†’ stance + Bull case + Bear case + per-news table + Plotly candlestick.
  • Backtest โ€” symbol + days slider (7โ€“22) โ†’ metrics table + equity curve
    • per-day decision log.

A concurrency queue (max_size=8) is enabled so a second user clicking "Analyze" while Gemma is busy gets queued instead of timing out.

Inside a Colab notebook cell

from src.display import launch_widget_ui
launch_widget_ui()         # ipywidgets form: symbol + horizon + debate toggle

โ€ฆor programmatically:

from src.pipeline import analyze
from src.backtest import backtest
from src.display import render_advice_text, render_backtest_text

result = analyze("AAPL", horizon="1w")
print(render_advice_text(result))

bt = backtest("AAPL", backtest_days=21)
print(render_backtest_text(bt))

Local preview without a GPU (Mac/Windows/Linux)

demo_ui.py ships a self-contained Plotly preview with mock data so you can inspect the look-and-feel of the dashboard without a GPU or API keys:

pip install plotly pandas numpy
python demo_ui.py AAPL          # opens demo_preview.html in your browser

LLM backends (Hugging Face vs Ollama)

The pipeline is engine-agnostic. Pick the backend with LLM_BACKEND; the CLI, Gradio UI, Plotly charts, and backtest all behave identically either way โ€” only the reasoning quality and where it runs change.

LLM_BACKEND=hf โ€” local open-weight model (default)

Runs any instruction-tuned causal LM with a chat template on a single GPU (4-bit). Set the repo with MODEL_ID:

LLM_BACKEND=hf MODEL_ID=google/gemma-4-12b-it python main.py analyze AAPL
LLM_BACKEND=hf MODEL_ID=microsoft/phi-4        python main.py analyze AAPL

Needs HF_TOKEN for gated models (e.g. Gemma) and a CUDA GPU (Colab T4/L4/A100). GEMMA_MODEL_ID is still honoured as a fallback for MODEL_ID.

LLM_BACKEND=ollama โ€” local Ollama server

Calls Ollama's non-streaming /api/chat endpoint. Ollama owns model loading, quantization, and device placement; the Python application only sends prompts over HTTP. requests is already included in requirements.txt, so no extra Python SDK is needed.

# Recommended local setup (Python 3.10+):
python3.11 -m venv .venv
source .venv/bin/activate
python -m pip install -r requirements-ollama.txt

# Start the Ollama app or daemon, then download the model once.
ollama serve
ollama pull gemma4:12b

LLM_BACKEND=ollama OLLAMA_MODEL=gemma4:12b \
  python main.py analyze AAPL

Available settings:

  • OLLAMA_BASE_URL โ€” default http://localhost:11434
  • OLLAMA_MODEL โ€” default gemma4:12b
  • OLLAMA_TIMEOUT โ€” default 600 seconds
  • OLLAMA_NUM_CTX โ€” default 8192; larger values consume more memory
  • OLLAMA_KEEP_ALIVE โ€” default 10m
  • OLLAMA_THINK โ€” default false; enable only after measuring quality/latency

Caveats

  • Keep Ollama bound to localhost unless the remote endpoint is protected by appropriate network controls and authentication.
  • Stop a resident Ollama model before loading a large HuggingFace model on the same machine if RAM/VRAM is constrained: ollama stop gemma4:12b.
  • Keep a backtest run on one backend so its metrics stay comparable.

Tests

# Install dev dependencies (pytest + everything in requirements.txt)
pip install -r requirements-dev.txt

# Run the whole suite
pytest

# Run a single file or filter by name
pytest tests/test_preprocessing.py -v
pytest -k "aggregate_polarity"

The suite uses monkeypatch fixtures (autouse=True in tests/conftest.py) to stub yfinance, News API, and both LLM backends โ€” every test runs offline with no GPU, Ollama server, or API keys.


Backtest interpretation

Metric Meaning
hit_rate Of days where the pipeline took a position (BUY or SELL), what fraction matched the next-day direction.
cumulative_return What 1 unit of capital would have grown to over the period if you followed every signal (long on BUY, short on SELL, flat on HOLD).
annualised_sharpe Daily mean / daily std ร— โˆš252. >1 is decent, >2 is rare.
max_drawdown Worst peak-to-trough loss along the equity curve.

Caveats:

  • The free News API tier only exposes ~30 days of history. --days is hard-capped at 22 trading days (= 30 โˆ’ 7 lookback โˆ’ 1 next-day). Passing a larger value prints a warning and silently caps.
  • No transaction costs are modelled. Real trading would subtract spread + fees.
  • Sample size is small โ€” treat as a sanity check, not statistical proof.
  • Same aggregation as live. backtest and analyze both call preprocessing.aggregate_polarity(), so a number you see in the backtest is exactly what the live system would compute on the same news.

Configuration knobs

All thresholds, weights, and window sizes are exposed in config.py under PipelineConfig:

Knob Default Purpose
stance_threshold 0.15 min `
impact_weights {low:0.5, medium:1.0, high:1.5} per-news weighting
time_decay_half_life_days 3.5 sentiment half-life
news_top_k 10 max news fed to Gemma
news_dedup_threshold 0.85 TF-IDF cosine for dedup
yfinance_daily_period "6mo" daily OHLCV window for features/backtest
yfinance_intraday_period "5d" recent intraday history window
yfinance_intraday_interval "5m" intraday candle interval
min_rsi_samples 15 strict RSI window
max_backtest_days 22 News API free-tier safe cap

VRAM expectations (LLM_BACKEND=hf, 4-bit NF4)

Model โ‰ˆ VRAM Fits on Colab
Gemma 4 ยท 4B 2.5 GB T4 โœ…
Gemma 4 ยท 12B (default) 7 GB T4 โœ…
Gemma 4 ยท 27B 15 GB A100 โœ… (L4 marginal)
Phi-4 ยท 14B 9 GB T4 โœ…

Override the default via env var:

export MODEL_ID=google/gemma-4-4b-it     # or microsoft/phi-4, Qwen/Qwen2.5-14B-Instruct, ...

โš ๏ธ Verify the exact Hugging Face id on the model card and update MODEL_ID (or config.py) accordingly. For Ollama, use its model tag instead, for example OLLAMA_MODEL=gemma4:12b.


What was removed from the original

Removed Why
FinBERT (yiyanghkust/finbert-tone) Gemma 4 covers per-news polarity + nuance
BART (facebook/bart-large-cnn) Gemma 4 handles summary + advice
Sentence-BERT embeddings (get_embeddings) Was dead code; dedup now via TF-IDF
Fine-tuning on IMDB Domain mismatch + Colab Pro not suited
Two parallel UI handlers (before/after) Collapsed into one
Model explanation/ (28 .md) Coverage already lives in module docstrings
Tutorial PNG screenshots Out-of-date with the new UI

Roadmap

Phase 1 โ€” "Make it credible" โ† complete

  • Multi-agent Bull vs Bear debate
  • Backtesting framework
  • Gradio web UI
  • Unified live/backtest aggregation
  • Mock-based integration tests
  • All Critical + Major review findings resolved

Phase 2 โ€” "Make it smarter" โ† next

  • RAG over historical news + past predictions (BGE-M3 + FAISS)
  • Time-series forecast (Chronos / TimesFM)
  • Reddit + WSB sentiment (PRAW)
  • SEC filings ingest (EDGAR)

Phase 3 โ€” "Make it production"

  • Watchlist + scheduled Telegram alerts
  • PDF report export
  • Earnings call audio (Whisper)
  • Portfolio optimiser (PyPortfolioOpt)

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages