Sentinel docs9 sections▾
Overview
Stack: Python 3.12 · FastAPI · React 19 · PostgreSQL · PyOD · Prophet · XGBoost · FinBERT · MLflow
What It Does
Sentinel ingests financial data from multiple public sources, engineers features, trains and serves ML models (anomaly detection, forecasting, sentiment), and exposes everything through a REST API and a Bloomberg-style React dashboard.
The backend is a single FastAPI process backed by PostgreSQL (SQLAlchemy 2.0). The frontend is a decoupled React SPA served separately.
What’s Real
Every pipeline, model, endpoint, and dashboard page listed in these docs runs today:
| Area | What’s implemented |
|---|---|
| Ingestion | Stocks (yfinance), crypto (CCXT), macro (FRED), news (newsdata.io) + FinBERT sentiment |
| Analysis data | Technical (86 fields) + Fundamental (71 fields) via hermes-plt |
| Feature engineering | Log returns, lags, rolling stats, RSI, Bollinger %B, volume ratios |
| Anomaly detection | Isolation Forest + LOF (PyOD), written to DB with severity labels |
| Forecasting | Prophet + XGBoost with walk-forward cross-validation (30/90-day horizons) |
| Sentiment | FinBERT scoring of news headlines |
| Portfolio | MPT, Black-Litterman, Kelly Criterion, Blended optimization |
| MLOps | PSI + Kolmogorov–Smirnov drift detection with email/Slack alerts |
| Model tracking | MLflow registry with metrics, params, and artifacts |
| Dashboard | 7 React pages: Overview, Anomalies, Forecasts, Technical, Fundamental, Portfolio, Sentiment |
| API | REST API at /api/v1/, OpenAPI docs at /api/docs |
| CLI | stl — ingest, ml-train, api-start, all |
Design Choices
- Decoupled frontend. React SPA on its own domain, API on another. CORS fully open for development.
- hermes-plt integration. Technical and fundamental analysis pull from the Hermes library for deep market data.
- CLI-driven orchestration. Ingestion and training are
stlcommands; no scheduler yet. - Environment-driven configuration. Everything lives in
config/settings.py, read from.env.
See Architecture for the full layout.
Architecture
┌──────────────────────────────┐
│ Backend │
│ (uvicorn → FastAPI :7860) │
│ │
Browser ──────►│ /api/v1/* FastAPI │
│ routers │
│ │
│ /api/docs Swagger UI │
└──────────────┬───────────────┘
│
┌──────────────▼───────────────┐
│ PostgreSQL (SQLAlchemy) │
│ Supabase / local / managed │
└──────────────────────────────┘
Frontend: React 19 + Vite 8 + TypeScript + Plotly.js
Deployed separately (Render free-tier)
Data sources: yfinance · CCXT · FRED · newsdata.io · hermes-plt
ML: PyOD · Prophet · XGBoost · FinBERT · scipy → MLflow registry
MLOps: drift detection (PSI + KS) → email/Slack alerts
Project Layout
| Path | Responsibility |
|---|---|
api/ |
FastAPI app factory, CORS, Pydantic schemas, 7 routers (v1) |
web/ |
React 19 + Vite 8 + TypeScript SPA (deployed separately) |
cli/ |
Typer CLI (stl) — ingest, ml-train, api-start, all |
ingestion/ |
Data collection: price, crypto, macro, news, analysis fetchers + feature engineer |
ml/ |
Anomaly detection, forecasting, sentiment, portfolio optimization, feature store, model registry |
mlops/ |
Drift detection (PSI + KS) with Slack/email alerts |
scheduler/ |
Job runner (not yet integrated) |
database/ |
SQLAlchemy models, CRUD, schema.sql + seed_data.sql |
config/ |
Settings + JSON logging |
Runtime Flow
- Ingest —
stl ingestpulls OHLCV, crypto, macro, news, and analysis data into PostgreSQL. - Train —
stl ml-trainfits anomaly/forecast/sentiment models and registers them in MLflow. - Serve —
stl api-startruns uvicorn on port 7860; frontend runs separately via Vite or is deployed to Render.
CLI (stl)
| Command | Action |
|---|---|
stl ingest |
Run full ingestion pipeline |
stl ml-train |
Run full training pipeline |
stl api-start |
Start uvicorn (port 7860) |
stl all |
Ingest → Train → Start API |
Getting Started
Requirements
- Python 3.12 (pinned via
.python-version) - uv for dependency management
- A reachable PostgreSQL (local, Docker, or Supabase pooler URL)
- Node.js 18+ (for the frontend)
Install Backend
git clone <repo-url> Sentinel
cd Sentinel
uv sync
Configure
cp .env.example .env
| Variable | Purpose |
|---|---|
DB_URL |
SQLAlchemy PostgreSQL URL (Supabase-ready, sslmode=require) |
FRED_API_KEY |
FRED macro data |
NEWSDATA_API_KEY |
News ingestion |
FINNHUB_API |
Finnhub (for hermes-plt) |
OPENSANCTIONS_API |
OpenSanctions (for hermes-plt) |
SEC_USERNAME / SEC_EMAIL |
SEC EDGAR (for hermes-plt) |
MLFLOW_URI |
MLflow tracking URI (default http://localhost:5000) |
LOG_LEVEL |
INFO / DEBUG / WARNING (default INFO) |
SMTP_SERVER/PORT/USER/PASSWORD |
Email drift alerts |
SLACK_WEBHOOK_URL |
Slack drift alerts |
Initialize Schema
Option A — Supabase: paste database/schema.sql then database/seed_data.sql into the SQL Editor.
Option B — any Postgres:
psql "$DB_URL" -f database/schema.sql && psql "$DB_URL" -f database/seed_data.sql
Run
stl all # ingest → train → start API
Or individually:
stl ingest
stl ml-train
stl api-start
Install Frontend
cd web
npm install
npm run dev # http://localhost:5173 (proxies /api/* to :7860)
Verify
| Service | URL |
|---|---|
| API Docs (Swagger) | http://localhost:7860/api/docs |
| Health | http://localhost:7860/api/health |
| Dashboard | http://localhost:5173 (dev) |
Data Ingestion
Files: ingestion/
Fetchers
| Fetcher | Source | Data |
|---|---|---|
price_fetcher.py |
yfinance | 10 US stocks — 10y daily OHLCV |
crypto_fetcher.py |
CCXT (Binance) | 5 crypto pairs — 365d daily OHLCV |
macro_fetcher.py |
FRED | 8 macro indicators (Fed Funds, CPI, Unemployment, GDP, Yield Curve, VIX, USD Index, Mortgage) |
news_fetcher.py |
newsdata.io + FinBERT | Business/finance news headlines — scored with FinBERT sentiment |
analysis_data.py |
hermes-plt | Technical snapshots (80+ fields) + Fundamental snapshots (71 fields) |
feature_engineer.py |
— | Computed features from stock prices |
Analysis Data
analysis_data.py uses the hermes-plt library for deep market data:
- Technical: 20 crypto symbols via
hr.ta_feature.get_technical()— OHLCV, returns, MAs, EMAs, volatility, order book, funding rate, open interest, composite scores - Fundamental: 18 US equities via
hr.fa_features.get_fundamentels()— income statement, balance sheet, cash flow, valuation ratios, macro indicators, earnings estimates
Feature Engineering
feature_engineer.py runs after stock price ingestion. For every ticker in the DB:
- Log returns
- Lag features (1d, 5d, 21d, 63d)
- Rolling statistics (21-day window): mean, std, skew
- RSI (14-period) via EWM
- Bollinger %B (20-day, 2 std)
- Volume ratio (20-day rolling average)
Pipeline Runner
ingestion_pipeline.py runs every stage in sequence with pass/fail reporting:
stl ingest
Or run individual stages:
python -m ingestion.price_fetcher
python -m ingestion.crypto_fetcher
python -m ingestion.macro_fetcher
python -m ingestion.news_fetcher
python -m ingestion.analysis_data
python -m ingestion.feature_engineer
Each pipeline exits non-zero on failure.
Machine Learning
Files: ml/
Anomaly Detection
anomaly_detector.py — Isolation Forest + Local Outlier Factor (PyOD ensemble).
- Features: log_return, volume_ratio, rolling_std_21, rsi_14, bb_pct_b
- Contamination: 5% expected anomalies
- Process: StandardScaler → IForest (100 estimators) + LOF (20 neighbors) → MinMax-normalize and average → severity labels (≥0.8 critical, ≥0.6 high, ≥0.4 medium, below 0.4 low)
- Default tickers: AAPL, MSFT, NVDA, TSLA, GOOGL
Forecasting
forecaster.py — Prophet + XGBoost blended 50/50.
- Horizons: 30 days and 90 days
- XGBoost features: lag_1d, lag_5d, lag_21d, lag_63d, rolling_mean_21, rolling_std_21, rsi_14, volume_ratio, bb_pct_b
- Cross-validation: Walk-forward CV (5 splits), MAE + RMSE
- Blending: Simple average of Prophet yhat and XGBoost predictions
Sentiment Analysis
sentiment_engine.py — aggregates historical sentiment scores.
- Daily averages with 7-day and 30-day rolling means
- Signal: 7d > 30d = BULLISH, 7d below 30d = BEARISH, else NEUTRAL
- Note: FinBERT scoring happens in ingestion, not here
Portfolio Optimization
portfolio_optimizer.py — four allocation methods:
| Method | Description |
|---|---|
| MPT | Mean-Variance Optimization — minimizes negative Sharpe via scipy SLSQP, long-only |
| Black-Litterman | Sentiment scores as investor views (scaled 0.05), equal-weight implied returns |
| Kelly Criterion | Fractional Kelly based on win rate and payoff ratio, clipped non-negative |
| Blended | 40% MPT + 40% BL + 20% Kelly |
Tickers: AAPL, MSFT, NVDA, TSLA, GOOGL. Risk-free rate: 5%, 252 trading days.
Feature Store
feature_store.py — in-memory dict cache of feature DataFrames per ticker, with DB fallback.
Model Registry
model_registry.py — MLflow integration: save, load, promote models with metrics/params/artifacts tracked per run.
Training Pipeline
stl ml-train
Two modes:
run_pipeline()— basic: warmup → anomalies → forecasting → sentiment → portfoliorun_pipeline_with_metrics()— enhanced: collects CV metrics, logs to MLflow (enabled viaWITH_METRICS=true)
MLOps & Drift
File: mlops/drift_detector.py (411 lines)
What It Does
Runs statistical tests per feature against a rolling baseline, catching distribution shifts before they degrade model performance.
Windows
| Window | Days | Purpose |
|---|---|---|
| Baseline | 180 days | Reference distribution |
| Production | 30 days | Current distribution |
Monitored Features (11)
| Feature | Description |
|---|---|
log_return |
Daily log return |
lag_1d |
1-day lag |
lag_5d |
5-day lag |
lag_21d |
21-day lag |
lag_63d |
63-day lag |
rolling_mean_21 |
21-day rolling mean |
rolling_std_21 |
21-day rolling std |
rolling_skew_21 |
21-day rolling skew |
rsi_14 |
RSI(14) |
bb_pct_b |
Bollinger %B |
volume_ratio |
20-day volume ratio |
Statistical Tests
| Test | Threshold | Trigger |
|---|---|---|
| PSI (Population Stability Index) | > 0.2 | Drift alert |
| KS (Kolmogorov–Smirnov) | p-value below 0.05 | Distribution shift |
Alerting
Drift triggers email + Slack alerts with detailed reports:
- Email: HTML-formatted, includes drifted features, PSI values, baseline vs production means
- Slack: Block Kit formatting via webhook
CLI
python mlops/drift_detector.py --tickers AAPL,MSFT --send-alerts
- Exit code 1 if drift detected (CI-friendly), 0 if stable
Roadmap
- Persist drift runs and expose via API
- Visualize drift in the dashboard
- Add Prometheus/Grafana observability
API Reference
Base: /api/v1 — interactive docs at /api/docs, health at /api/health
Health
| Method | Path | Description |
|---|---|---|
| GET | /api/health |
App + DB health check |
Prices (/api/v1/prices)
| Method | Path | Query | Description |
|---|---|---|---|
| GET | /compare |
tickers=AAPL,MSFT |
Latest quotes, multi-asset |
| GET | /{ticker} |
— | Latest price for one asset |
| GET | /{ticker}/history |
limit=90 |
OHLCV history |
Anomalies (/api/v1/anomalies)
| Method | Path | Query | Description |
|---|---|---|---|
| GET | /latest |
— | Most recent anomaly |
| GET | `` | ticker=&days=30 |
Anomalies for ticker |
| POST | /detect/{ticker} |
— | Trigger on-demand detection |
Forecasts (/api/v1/forecasts)
| Method | Path | Query | Description |
|---|---|---|---|
| GET | /compare |
tickers=&horizon=30 |
Multi-asset forecast comparison |
| GET | /{ticker} |
horizon=30 |
Prophet + XGBoost forecast |
Sentiment (/api/v1/sentiment)
| Method | Path | Query | Description |
|---|---|---|---|
| GET | /heatmap |
— | Latest sentiment per ticker |
| GET | /timeline |
ticker=&days= |
Sentiment history |
| GET | /{ticker} |
days=30 |
Sentiment for one ticker |
Portfolio (/api/v1/portfolio)
| Method | Path | Description |
|---|---|---|
| GET | /weights |
Latest optimized weights |
| GET | /optimize |
Re-run optimization |
| GET | /backtest |
Backtest (coming soon) |
Analysis (/api/v1/analysis)
| Method | Path | Description |
|---|---|---|
| GET | /technical/{symbol} |
Crypto technical snapshot (86 fields) |
| GET | /fundamental/{ticker} |
Stock fundamental snapshot (71 fields) |
Smoke Checks
curl http://localhost:7860/api/health
curl http://localhost:7860/api/v1/prices/AAPL/history?limit=5
curl http://localhost:7860/api/v1/analysis/technical/BTCUSDT
curl http://localhost:7860/api/v1/analysis/fundamental/AAPLDashboard
Files: web/src/pages/
The dashboard is a React 19 SPA with Plotly.js charts, lazy-loaded via React.lazy(). Uses a custom dark Bloomberg-style design system with IBM Plex Mono/Sans fonts.
Pages
| Page | Route | Contents |
|---|---|---|
| Overview | / |
OHLCV candlestick with Bollinger Bands + volume. KPI cards (Last Close, Daily Volume, RSI, 52W Range). Top Movers table (10 tickers, 24H change). Time range selector (7D/30D/90D/1Y). Auto-refreshes every 60s. |
| Anomalies | /anomalies |
Price chart with severity-coded anomaly markers. Anomaly score timeline (0-1, threshold at 0.7). Flagged Events table with severity badges. Severity filter (ALL/CRITICAL/HIGH/MEDIUM/LOW). CSV export. |
| Forecasts | /forecasts |
Historical + forecast line chart with 95% confidence band. KPI cards (YHAT, bounds, model, horizon). Forecast Comparison tool (enter tickers). Next 10 days table. Feature Importance bar chart. Model Registry card (MLflow). Disclaimer: “NOT INVESTMENT ADVICE”. |
| Technical | /technical |
Crypto-focused. Search from 20 Binance perpetual futures. Displays: Price Snapshot, Returns, Moving Averages (SMA 20/50/200, EMA 9/21/50), Volatility (20/60, ATR), Order Book (bid/ask, spread, imbalance), Funding & OI, Trade Activity, Composite Scores (Trend, Mean Reversion, Liquidity, Order Flow, Sentiment with progress bars). |
| Fundamental | /fundamental |
Stock-focused. Search from 18 US equities. Displays: Income Statement, Balance Sheet, Cash Flow, Valuation Ratios (P/E, P/S, P/B, EV/EBITDA), Balance Sheet Ratios (D/E, ROE, ROA), Earnings & Estimates, Shares, Macro Indicators (GDP, inflation, rates, unemployment). Company peers as badges. |
| Portfolio | /portfolio |
Weighted bar chart (4 methods). Allocation Treemap. Efficient Frontier scatter (Monte Carlo + method stars). KPIs: Sharpe, VaR (95%), Max Drawdown, Beta, Sortino. Weights table. Method selector: Blended, MPT, Black-Litterman, Kelly Criterion. |
| Sentiment | /sentiment |
Sentiment score timeline (positive/negative/neutral scatter + 3-pt MA). Gauge indicator (-1 to +1). Avg Sentiment bar chart. Latest News list with sentiment badges. KPI cards: Total, Positive, Negative, Neutral, Avg Score. |
Sidebar
- Health status indicator (LIVE/DEGRADED/OFFLINE) polled every 30s
- “Wake Up” button with 60s countdown (Render free-tier cold starts)
- Responsive: icon-rail at 980px, bottom tabs at 640px
Theme
Custom dark palette — IBM Plex Mono/Sans typography, terminal-style accents, Plotly chart theming via web/src/theme.ts.
Deployment
Backend
The backend runs as a single uvicorn process on Render free-tier (https://sentinel-ggi8.onrender.com).
- No Dockerfile — Render auto-detects Python and runs
uvicorn api.main:app - Health check on
/api/health - JSON logging with rotating file handler (fails silently on read-only containers)
Frontend
The React SPA is deployed separately on Render as a static site.
- Built with
npm run build→dist/ - In development: Vite dev server at
:5173with proxy to backend at:7860 - In production: hits
VITE_API_URLdirectly
Database
PostgreSQL is the only external dependency. Any of these work:
- Local Postgres
- Docker container
- Supabase hosted Postgres (pooler URL, sslmode=require)
All state lives in Postgres — ephemeral filesystem storage is fine for free-tier deployments.
Configuration
All config is environment-driven (config/settings.py, .env):
| Variable | Purpose |
|---|---|
DB_URL |
SQLAlchemy PostgreSQL URL |
FRED_API_KEY |
FRED macro data |
NEWSDATA_API_KEY |
News ingestion |
FINNHUB_API |
Finnhub (for hermes-plt) |
OPENSANCTIONS_API |
OpenSanctions (for hermes-plt) |
SEC_USERNAME / SEC_EMAIL |
SEC EDGAR (for hermes-plt) |
MLFLOW_URI |
MLflow tracking URI |
SMTP_* |
Email drift alerts |
SLACK_WEBHOOK_URL |
Slack drift alerts |
What’s Not Done Yet
- No Dockerfile (planned for Phase 4)
- No CI/CD (GitHub Actions workflow exists but is empty)
- No unit or integration tests
- No auth or rate limiting (auth code is commented out)
- No scheduled retraining (manual CLI only)