Skip to content

Rodrigo-Palma/quantlens

Repository files navigation

QuantLens — AI Quant Analyst for the Brazilian Stock Market (B3)

CI Python License: MIT Ruff

Open-source AI agent that pulls B3 market data, computes quantitative signals (momentum, volatility, RSI) and explains the thesis in plain language — grounded by retrieval (RAG) and protected by output guardrails and an offline eval suite. Local-first: runs on a local LLM (Ollama) with no API key, swappable to a paid API.

demo

Why

Most stock screeners output numbers. QuantLens turns quant signals into a reasoned, evaluated explanation an investor can actually read — combining classic quantitative finance with a modern LLM stack. It is built as an end-to-end engineering showcase: data → quant → RAG → LLM → guardrails/evals → API → demo.

Features

  • 📈 Quant engine — RSI, momentum, annualized volatility (pure, unit-tested)
  • 📚 RAG — BM25 retrieval over a local knowledge base, grounding the explanation
  • 🤖 LLM explanation — local model via Ollama by default; swappable to a paid API
  • 🛡️ Guardrails — rejects investment-advice / guarantee phrasing
  • 🧪 Offline evals — faithfulness + guardrail checks gated in CI (no network/LLM)
  • 🚀 FastAPI service + Streamlit demo, containerized
  • 🔧 LoRA fine-tuning script to specialize the explainer

Architecture

[yfinance]                              public market data
     │
     ▼
[Quant Engine]  ── RSI, momentum, annualized volatility
     │  structured signals
     ▼
[RAG]  ── BM25 over local knowledge base (glossary)  →  grounding context
     │
     ▼
[LLM]  ── local Ollama model (swappable to a paid API)
     │  natural-language explanation
     ▼
[Guardrails + Evals]  ── no-advice checks / faithfulness  (evals run in CI)
     │
     ▼
[FastAPI]  ──>  [Streamlit demo]

Results / Benchmarks

Latency of the offline path (no network, no LLM), measured with make bench — reproducible in CI and on any machine (Python 3.13 · arm64 · 2000 iters). Numbers are honest and small on purpose: the dominant cost in production is the LLM call, benchmarked separately by hardware/model.

Stage (offline, no LLM) p50 p95 p99
RSI(14) 303 µs 358 µs 433 µs
Momentum + volatility 98 µs 119 µs 137 µs
BM25 retrieval (k=2) 13 µs 14 µs 18 µs
Rule-based explanation <1 µs 1 µs 1 µs
Guardrail validation 1 µs 1 µs 1 µs
Offline pipeline (end-to-end, no LLM) 441 µs 600 µs 797 µs
Quality layer Result
Offline eval suite (faithfulness + guardrails) 4/4 pass
Guardrail recall (advice phrasing blocked) 4/4
Guardrail false positives (clean text blocked) 0/3
Test suite 10 passed · 74% line coverage

Reproduce: make bench (latency + quality) and make test (coverage).

Architecture decisions

The non-obvious trade-offs are recorded as short ADRs in docs/adr/: BM25 vs embeddings, local-first LLM, rule-based guardrails vs an LLM judge, and offline deterministic evals. Each states the decision, the rejected alternatives, and the downside that was accepted.

Quickstart

git clone https://github.com/Rodrigo-Palma/quantlens.git
cd quantlens
uv sync --extra dev
cp .env.example .env            # defaults to a local Ollama model (no key)
uv run uvicorn quantlens.api.main:app --reload
curl "http://localhost:8000/analyze?ticker=PETR4"

Interactive docs at http://localhost:8000/docs. The explanation uses your local Ollama model (qwen3:32b by default); if Ollama is not running it falls back to a deterministic, guardrail-safe summary.

Development

make install   # uv sync --extra dev
make lint      # ruff check
make type      # mypy
make test      # pytest + coverage
make evals     # offline eval suite (faithfulness + guardrails)
make bench     # latency + quality benchmark (reproduces the table above)

Demo

uv sync --extra demo
uv run streamlit run app/streamlit_app.py

Fine-tuning (LoRA)

Specialize the explainer on synthetic signal→explanation pairs:

uv sync --extra finetune
uv run python scripts/finetune_lora.py --max-samples 64 --epochs 1

Defaults are CPU-friendly for a smoke run; use a GPU and a larger base model for real training.

Deploy

docker compose up --build      # serves the API on :8000

Roadmap

  • v0.1 — MVP: ticker → quant signals → explanation
  • v0.2 — RAG grounding over a local knowledge base
  • v0.3 — output guardrails + offline eval suite gated in CI
  • v0.4 — LoRA fine-tuning script + containerized serving + demo

Limitations & next steps

Honest boundaries of the current system, and where I'd take it next:

  • Retrieval is lexical (BM25). Great for a small glossary; it misses semantic paraphrase. Next: embeddings + a vector store once the corpus justifies the operational weight (ADR 0001).
  • Evals gate the deterministic floor, not the LLM's own output. CI proves the fallback is faithful and safe; it does not score the generated text. Next: an LLM-graded faithfulness/grounding harness (ragas-style) as a non-blocking nightly job (ADR 0004).
  • Guardrails are a deterministic deny-list. Strong as a hard gate, blind to novel phrasings. Next: an advisory LLM-judge layer on top of the deterministic one (ADR 0003).
  • Signals are descriptive, not predictive. RSI/momentum/volatility are classic indicators, not a backtested strategy. Next: a backtest module with walk-forward evaluation — and it would stay descriptive, never advice.
  • Single-ticker, synchronous requests. Next: batch/portfolio analysis and a caching layer for the yfinance fetch (the only network-bound hop).

Disclaimer

Educational / engineering project. Not investment advice. Uses public data only.

Author

Rodrigo Stachlewski Palma — Senior Data & AI Engineer (Azure Data Scientist & Fabric certified). LinkedIn · GitHub

About

Production-grade local-LLM quant analyst for the B3 stock market: RAG, guardrails, measured offline evals.

Topics

Resources

License

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors