An evidence-checked financial catalyst research baseline that connects news to measurable market reactions.
CatalystLens answers a concrete market question: which company-specific events plausibly explain an unusual price move?
The system aligns time-stamped synthetic news with price, benchmark, and volume series; ranks pre-labelled candidate catalysts; computes an event study; produces a citation-checked impact brief; and exposes the processing trace. It runs with a deterministic lexical baseline by default and can optionally serve a Hugging Face model with a PEFT/LoRA adapter.
Implemented scope: the default application path uses a three-asset synthetic fixture and lexical relevance ranking. It does not claim vector retrieval or production RAG. Generated citation IDs are validated against retrieved evidence, and unsupported or irrelevant requests trigger a safe abstention.
All issuers, news, prices, metrics, and claims in this repository are synthetic. They exist to make the engineering workflow reproducible. CatalystLens does not provide investment advice or claim live-market performance.
Choose one of three synthetic assets and ask a market-impact question. CatalystLens returns:
- ranked, pre-labelled catalysts classified as guidance, contract, credit, rating, regulation, or product news;
- the initiating and reinforcing sources, including short evidence excerpts;
- cumulative abnormal return relative to the asset’s benchmark;
- event-day volume z-score and post-event volatility change;
- a cited narrative generated by the deterministic baseline or a locally served LoRA adapter;
- explicit citation-validation and abstention state;
- processing details for fixture loading, retrieval, event study, and citation-ID validation.
The UI is a market-research workstation rather than a general chat screen: the price response, benchmark, catalyst timing, and evidence are visible at the same time.
synthetic news fixture ─> lexical relevance filter ─> evidence selection
price / volume fixture ──────────────────────────────────┐
├─> event study
benchmark fixture ───────────────────────────────────────┘
│
├─> grounded impact brief
local base model + optional LoRA adapter ────────────────┘
│
FastAPI <─ typed response ──────┤
│ │
├─ /health ├─ evidence register
├─ /metrics └─ validation trace
└─ structured request logs
The static web demo remains usable without the API. When NEXT_PUBLIC_API_BASE_URL is set, research requests are sent to FastAPI.
For asset return rᵢ,t, benchmark return rₘ,t, and an estimated beta βᵢ, abnormal return is:
ARᵢ,t = rᵢ,t − βᵢ rₘ,t
CARᵢ,[−1,+3] = Σ ARᵢ,t
The current deterministic fixture stores beta and computes CAR over the configured event window. Volume shock is the event-day volume relative to the pre-event mean and population standard deviation:
volume z-score = (event volume − pre-event mean) / pre-event standard deviation
Volatility change compares the standard deviation of returns in the reaction window with the pre-event sample. These calculations are deliberately small and inspectable; a production implementation would estimate the market model on a longer clean window, test alternative benchmarks, account for overlapping events, and report confidence intervals.
The default generator is extractive and deterministic. Fixture narratives are stored as claims with explicit evidence IDs. Every non-abstaining sentence must cite an ID present in the retrieved evidence set; otherwise the service replaces it with an abstention. This validates citation presence and referential integrity, not semantic entailment.
The optional model path uses:
- Qwen2.5-0.5B-Instruct as the laptop-friendly default base model;
- Hugging Face Transformers for local inference;
- PEFT LoRA with rank 16, alpha 32, and dropout 0.05;
- instruction examples for catalyst attribution, quantification, citation, uncertainty, and abstention;
- a separate validation split and a fixed random seed.
When an adapter exists at artifacts/catalyst-lora and CATALYSTLENS_MODEL_PROVIDER is set to huggingface, the API loads it automatically. If the adapter is absent, it serves the configured base model and identifies that state in the response.
The training samples are intentionally synthetic and small. They demonstrate the complete fine-tuning and serving path; they are not evidence of production alpha or broad market generalisation.
Primary endpoints:
| Method | Endpoint | Purpose |
|---|---|---|
| GET | /health | Service, model, and dataset readiness |
| GET | /api/v1/market-assets | Available asset and benchmark metadata |
| POST | /api/v1/market-analyze | Catalyst attribution and event-study response |
| GET | /metrics | Prometheus-format request counters and p95 latency |
| GET | /docs | Interactive OpenAPI documentation |
Example request:
curl -X POST http://localhost:8000/api/v1/market-analyze \
-H 'Content-Type: application/json' \
-d '{
"asset_id": "astr",
"question": "Did guidance explain the unusual price and volume move?",
"event_id": "astr-guidance"
}'Abbreviated response:
{
"asset_id": "astr",
"ticker": "ASTR",
"summary": "The move is most consistent with a guidance-led re-rating [astr-guidance].",
"metrics": {
"cumulative_abnormal_return": 6.5,
"volume_z_score": 4.29,
"volatility_change_pct": 405.1,
"beta": 1.18,
"event_window": "[-1, +3]"
},
"evidence": [
{
"id": "astr-guidance",
"category": "Guidance",
"source": "Asteron investor update",
"confidence": 94
}
],
"model_provider": "extractive",
"citation_validation": {
"valid": true,
"cited_ids": ["astr-guidance"],
"unsupported_ids": [],
"uncited_claim_count": 0,
"abstained": false
}
}Exact calculated values are returned by the current fixture and may change when the synthetic series is revised.
Requirements:
- Node.js 22.13 or newer
- Python 3.11–3.14
- Docker Desktop only if using the full stack
Install:
npm ci
python3 -m venv .venv
.venv/bin/python -m pip install -e './api[dev]'
cp .env.example .envStart the API:
.venv/bin/uvicorn app.main:app --app-dir api --reload --port 8000Start the web app in another terminal:
npm run devOpen http://localhost:3000 and the API documentation at http://localhost:8000/docs.
docker compose up --buildThis starts the web app on http://localhost:8080 and FastAPI on http://localhost:8000. The default stack deliberately contains no unused vector database: retrieval is the documented deterministic lexical baseline.
Install the training dependencies:
.venv/bin/python -m pip install -e './api[training]'
cd api
../.venv/bin/python training/train_lora.pyThen set:
CATALYSTLENS_MODEL_PROVIDER=huggingface
CATALYSTLENS_ADAPTER_PATH=artifacts/catalyst-lora
Restart the API. The health and analysis responses expose whether the loaded generator is extractive, huggingface-base, or huggingface-lora.
For a quick qualitative check:
cd api
../.venv/bin/python training/infer_adapter.pyRun the complete local checks:
npm run lint
npm run build
.venv/bin/ruff check api
cd api
../.venv/bin/python -m pytest tests
../.venv/bin/python -m evaluation.evaluateThe current synthetic regression suite contains 12 answerable paraphrases and six deliberately irrelevant questions. It reports:
| Metric | Result |
|---|---|
| Event recall at 2 | 1.000 |
| Mean reciprocal rank | 1.000 |
| Evidence precision | 0.750 |
| Impact-direction accuracy | 1.000 |
| Citation-ID validity | 1.000 |
| Safe-abstention accuracy | 1.000 |
These are regression checks on 18 controlled fixture cases, not performance estimates for real news or prices. CI treats retrieval, citation validity and safe abstention as release gates.
CI runs frontend lint/build, Python lint/tests/evaluation, and three container builds on each push and pull request. The Pages workflow deploys the static demonstration from main.
- Unknown asset IDs return 404 rather than silently broadening the search.
- Pydantic validates identifiers, questions, and response bounds.
- Every request receives an X-Request-ID.
- Logs are structured JSON and include route, status, and duration.
- Prometheus-format metrics expose request count, failure count, and rolling p95 latency.
- Every generated sentence must cite an evidence ID returned for that request.
- Unsupported citations, uncited sentences and irrelevant questions return a safe abstention.
- The interface labels its dataset synthetic and does not emit buy, sell, target-price, or portfolio-allocation advice.
For real feeds, add source licensing, exchange-calendar alignment, corporate-action adjustment, duplicate-story clustering, delayed-data controls, and secrets management before deployment.
app/ React market-research interface
components/ reusable UI primitives
api/app/main.py FastAPI routes and operational middleware
api/app/market.py event-study calculations and catalyst ranking
api/app/generation.py deterministic and local LoRA generators
api/data/market_events.json synthetic news, price, benchmark, and volume data
api/app/grounding.py citation-ID validation and fail-closed abstention
api/evaluation/ retrieval, citation, direction, and abstention gates
api/training/ PEFT/LoRA training and inference scripts
api/tests/ API, market-metric, and compatibility tests
.github/workflows/ CI and GitHub Pages deployment
compose.yaml local web and API stack
Dockerfile* lightweight API, full ML API, and web images
This showcase contains source code, synthetic fixtures, tests, documentation, and generated interface assets only. It contains no credentials, customer or employer data, licensed market data, trained model weights, private endpoints, or production configuration. .env.example documents variable names with non-sensitive local defaults; real environment files remain untracked.
CatalystLens demonstrates research engineering and model operations, not a trading strategy or a production RAG system. It does not backtest a portfolio, model transaction costs, or claim that news causally determines returns. Its event attribution is a transparent fixture baseline: evidence and benchmark-relative reaction are shown together so a human can challenge the conclusion.
A production roadmap would add licensed live news and adjusted prices, point-in-time ticker mapping, multi-event deconfliction, learned relevance scoring, confidence calibration, persistent research sessions, and monitoring by catalyst class.
