A NEUTRAL ("No Clear Setup") recommendation means the engine found no clear
directional trade, yet such setups could still qualify and even be crowned the
top pick purely on momentum rank (e.g. an extended momentum leader with a far,
5%-probability target). A NEUTRAL signal isn't actionable, so it shouldn't
qualify.
New `exclude_neutral` activation flag (default on): setup_qualifies drops setups
whose recommended_action is NEUTRAL. It lives in the shared gate, so it flows
through the dashboard's qualified/top-pick selection, the track record's
qualified stats, and the backtest (which computes recommended_action and gates on
meets_core). Toggleable in Admin → Settings → Activation; the frontend mirror and
activationSummary ("directional") match.
Re-run the backtest after enabling to confirm it holds/improves expectancy.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Signal Dashboard
Investing-signal platform for NASDAQ stocks. Surfaces the best trading opportunities through weighted multi-dimensional scoring — technical indicators, support/resistance quality, sentiment, fundamentals, and momentum — with asymmetric risk:reward scanning.
Philosophy: Don't predict price. Find the path of least resistance, key S/R zones, and asymmetric R:R setups.
How It Works
Scheduled pipelines turn raw prices into a ranked, gated list of tradeable setups. Everything downstream of OHLCV is recomputed from stored data, so each refresh is cheap and idempotent. Job timing is cron-based and configurable in Admin → Jobs (default timezone Europe/Berlin).
Daily Load — the full refresh
Once a day (default 07:00). Steps run in dependency order, each consuming the previous step's fresh output:
- OHLCV — fetch the latest daily bars for every tracked ticker (Alpaca); new tickers backfill ~5 years.
- Sentiment — fetch sentiment for the names that matter and are stale (> 5 days): top-pick feeders (momentum leaders with a tradeable long setup), the watchlist, and open paper trades, plus a top-N-by-composite discovery net. Runs before the scan so the scan sees fresh sentiment.
- R:R Scan — recompute S/R zones, the 5-dimension scores and long/short setups (ATR stops, S/R targets) for every ticker, and attach each ticker's 12‑1 momentum percentile.
- Outcome Eval — resolve setups that hit target/stop or expired (default 30 trading days) and close paper trades that hit a level.
- Market Regime — recompute the regime index (breadth/trend).
- Regime Monitor — observational early-warning snapshot (VIX, credit spreads via FRED); feeds nothing else.
A failing step is logged; the pipeline continues with the next.
Intraday — light refresh
Hourly across the US session (Mon–Fri): only OHLCV → Outcome Eval, to keep prices current and close paper trades intraday. No scan/sentiment — the dashboard recomputes live R:R from the latest price, so fresh prices are enough.
Other jobs
Fundamentals (weekly, early Monday) · Alerts (hourly, Telegram) · Backtest (weekly) · Ticker-universe sync (daily). Deep history backfill and event study are manual-only (Admin → Jobs).
From score to "top pick"
- Composite score — technical, S/R-quality, sentiment, fundamental and momentum sub-scores (0–100) combine into a weighted composite (weights configurable; missing dimensions re-normalize).
- Setups — the scanner builds long/short setups with ATR stops and S/R targets, then adds a confidence score, conflict flags and a target reach-probability.
- Activation gate — a setup qualifies only if it clears the R:R and confidence floors and ranks in the top momentum percentile of the universe (the validated edge is long-only momentum).
- Top pick — the highest-momentum qualified setup; highlighted on the Dashboard and labelled on the ticker page.
Key Use Cases
- Find today's best long setup. On the Dashboard, the Top Setups table lists qualified setups ranked by momentum with the #1 flagged "Top pick". Each row opens the ticker page for the chart, scores, S/R targets and entry/stop.
- Track a trade you took. Mark a setup as a paper trade: it's marked-to-market against the latest close, auto-closed on stop/target, and its sentiment stays fresh while open. Signals → Track Record shows the realized edge.
Stack
| Layer | Tech |
|---|---|
| Backend | Python 3.12+, FastAPI, Uvicorn, async SQLAlchemy, Alembic |
| Database | PostgreSQL (asyncpg) |
| Scheduler | APScheduler — daily & intraday pipelines, fundamentals, alerts, regime, backtest |
| Frontend | React 18, TypeScript, Vite 5 |
| Styling | Tailwind CSS 3 with custom glassmorphism design system |
| State | TanStack React Query v5 (server), Zustand (client/auth) |
| Charts | Canvas 2D candlestick chart with S/R overlays |
| Routing | React Router v6 (SPA) |
| HTTP | Axios with JWT interceptor |
| Data providers | Alpaca (OHLCV); OpenAI / Gemini / DeepSeek / xAI (sentiment, pluggable); Fundamentals chain: FMP → Finnhub → Alpha Vantage; FRED (regime); Telegram (alerts) |
Features
Backend
- Ticker registry with full cascade delete
- Universe bootstrap for
sp500,nasdaq100,nasdaq_allvia admin endpoint - OHLCV price storage with upsert and validation
- Technical indicators: ADX, EMA, RSI, ATR, Volume Profile, Pivot Points, EMA Cross
- Support/Resistance detection with strength scoring and merge-within-tolerance
- Sentiment analysis with time-decay weighted scoring
- Fundamental data tracking (P/E, revenue growth, earnings surprise, market cap)
- 5-dimension scoring engine (technical, S/R quality, sentiment, fundamental, momentum) with configurable weights
- Risk:Reward scanner — long and short setups, ATR-based stops, S/R-based targets, configurable R:R threshold (default 1.5:1)
- Activation gate — qualifies setups on a momentum-percentile floor plus R:R/confidence (validated long-only edge); ranks the rest by expected value
- Recommendation layer — directional confidence, conflict detection, per-target reach-probability
- Paper trading — take a setup, mark-to-market vs. latest close, auto-close on stop/target, realized track record + outcome evaluation
- Market-regime index + FRED early-warning monitor (VIX, credit spreads); weekly backtest + manual event study
- Telegram alerts (e.g. regime-quadrant changes)
- User-curated watchlist (cap: 20), enriched with composite score, R:R and S/R summary
- JWT auth with admin role, configurable registration, user access control
- Cron-scheduled pipelines (admin-configurable) with per-job enable/disable and live status monitoring
- Admin panel: user management, data cleanup, job control, system settings
Frontend
- Glassmorphism UI with frosted glass panels, gradient text, ambient glow effects, mesh gradient background
- Interactive candlestick chart (Canvas 2D) with hover tooltips showing OHLCV values
- Support/Resistance level overlays on chart (top 6 by strength, dashed lines with labels)
- Data freshness bar showing availability and recency of each data source
- Watchlist with composite scores, R:R ratios, and S/R summaries
- Ticker detail page: chart, scores, sentiment breakdown, fundamentals, technical indicators, S/R table
- Rankings table with configurable dimension weights
- Trade scanner showing detected R:R setups
- Admin page: user management, job status with live indicators, enable/disable toggles, data cleanup, system settings
- Protected routes with JWT auth, admin-only sections
- Responsive layout with mobile navigation
- Toast notifications for async operations
Pages
| Route | Page | Access |
|---|---|---|
/login |
Login | Public |
/register |
Register | Public (when enabled) |
/ |
Dashboard — top setups, open trades, regime (default) | Authenticated |
/market |
Market — watchlist + rankings tabs | Authenticated |
/signals |
Signals — scanner + track record tabs | Authenticated |
/regime |
Market Regime | Authenticated |
/ticker/:symbol |
Ticker Detail | Authenticated |
/admin |
Admin Panel | Admin only |
Legacy routes redirect: /watchlist → /market, /rankings → /market?tab=rankings, /scanner → /signals, /performance → /signals?tab=track.
API Endpoints
All under /api/v1/. Interactive docs at /docs (Swagger) and /redoc.
| Group | Endpoints |
|---|---|
| Health | GET /health |
| Auth | POST /auth/register, POST /auth/login |
| Tickers | POST /tickers, GET /tickers, DELETE /tickers/{symbol} |
| OHLCV | POST /ohlcv, GET /ohlcv/{symbol} |
| Ingestion | POST /ingestion/fetch/{symbol} |
| Indicators | GET /indicators/{symbol}/{type}, GET /indicators/{symbol}/ema-cross |
| S/R Levels | GET /sr-levels/{symbol} |
| Sentiment | GET /sentiment/{symbol} |
| Fundamentals | GET /fundamentals/{symbol} |
| Scores | GET /scores/{symbol}, GET /rankings, PUT /scores/weights |
| Trades | GET /trades, GET /trades/{symbol}, GET /trades/{symbol}/history, GET /trades/activation, GET /trades/performance |
| Paper Trades | GET /paper-trades, POST /paper-trades, POST /paper-trades/{id}/close |
| Market / Regime | GET /market/regime, GET /regime/monitor, GET/PUT /regime/config, GET /regime/history, GET /regime/event-study, GET/PUT /regime/fundamentals, GET /backtest/report |
| Jobs | GET /jobs/running |
| Watchlist | GET /watchlist, POST /watchlist/{symbol}, DELETE /watchlist/{symbol} |
| Admin | GET /admin/users, POST /admin/users, PUT /admin/users/{id}/access, PUT /admin/users/{id}/password, PUT /admin/settings/registration, GET /admin/settings, PUT /admin/settings/{key}, GET/PUT /admin/settings/recommendations, GET/PUT /admin/settings/ticker-universe, POST /admin/tickers/bootstrap, POST /admin/data/cleanup, GET /admin/jobs, POST /admin/jobs/{name}/trigger, PUT /admin/jobs/{name}/toggle, GET /admin/pipeline/readiness |
Development Setup
Prerequisites
- Python 3.12+
- PostgreSQL (via Homebrew on macOS:
brew install postgresql@17) - Node.js 18+ and npm
Backend Setup
# Create and activate virtual environment
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
# Configure environment
cp .env.example .env
# Edit .env with your values (see Environment Variables below)
# Start PostgreSQL and create database
brew services start postgresql@17
createdb stock_data_backend
createuser stock_backend
# Run migrations
alembic upgrade head
# Start the backend
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
A default admin/admin account is created on first startup. Open http://localhost:8000/docs for Swagger UI.
Frontend Setup
cd frontend
npm install
npm run dev
Open http://localhost:5173 for the Signal Dashboard. The Vite dev server proxies /api/v1/ requests to the backend at http://127.0.0.1:8000.
Frontend Build
cd frontend
npm run build # TypeScript check + production build → frontend/dist/
npm run preview # Preview the production build locally
Tests
# Backend tests (in-memory SQLite — no PostgreSQL needed)
pytest tests/ -v
# Frontend tests
cd frontend
npm test
Environment Variables
Configure in .env (copy from .env.example):
| Variable | Required | Default | Description |
|---|---|---|---|
DATABASE_URL |
Yes | — | PostgreSQL connection string (postgresql+asyncpg://...) |
JWT_SECRET |
Yes | — | Random secret for JWT signing |
JWT_EXPIRY_MINUTES |
No | 60 |
JWT token expiry |
ALPACA_API_KEY |
For OHLCV | — | Alpaca Markets API key |
ALPACA_API_SECRET |
For OHLCV | — | Alpaca Markets API secret |
GEMINI_API_KEY |
For sentiment | — | Google Gemini API key |
GEMINI_MODEL |
No | gemini-2.0-flash |
Gemini model name |
OPENAI_API_KEY |
For sentiment (OpenAI path) | — | OpenAI API key |
OPENAI_MODEL |
No | gpt-4o-mini |
OpenAI model name |
OPENAI_SENTIMENT_BATCH_SIZE |
No | 5 |
Micro-batch size for sentiment collector |
FMP_API_KEY |
Optional (fundamentals) | — | Financial Modeling Prep API key (first provider in chain) |
FINNHUB_API_KEY |
Optional (fundamentals) | — | Finnhub API key (fallback provider) |
ALPHA_VANTAGE_API_KEY |
Optional (fundamentals) | — | Alpha Vantage API key (fallback provider) |
FRED_API_KEY |
Optional (regime) | — | FRED key for the regime monitor (VIX, credit spreads) |
TELEGRAM_BOT_TOKEN |
Optional (alerts) | — | Telegram bot token for alerts (can also be set in Admin) |
TELEGRAM_CHAT_ID |
Optional (alerts) | — | Telegram chat id for alerts |
DATA_COLLECTOR_FREQUENCY |
No | daily |
OHLCV collection schedule (legacy — see note below) |
SENTIMENT_POLL_INTERVAL_MINUTES |
No | 30 |
Sentiment polling interval |
FUNDAMENTAL_FETCH_FREQUENCY |
No | weekly |
Fundamentals fetch cadence |
RR_SCAN_FREQUENCY |
No | daily |
R:R scanner schedule |
FUNDAMENTAL_RATE_LIMIT_RETRIES |
No | 3 |
Retries per ticker on fundamentals rate-limit |
FUNDAMENTAL_RATE_LIMIT_BACKOFF_SECONDS |
No | 15 |
Base backoff seconds for fundamentals retry (exponential) |
DEFAULT_WATCHLIST_AUTO_SIZE |
No | 10 |
Auto-watchlist size |
DEFAULT_RR_THRESHOLD |
No | 1.5 |
Minimum R:R ratio for setups |
DB_POOL_SIZE |
No | 5 |
Database connection pool size |
LOG_LEVEL |
No | INFO |
Logging level |
Note: Pipeline timing (daily / intraday / fundamentals cron, timezone) is configured at runtime in Admin → Jobs and stored in the DB — the
*_FREQUENCYenv vars are legacy fallbacks for the few jobs still on interval triggers (alerts, universe sync).
Production Deployment (Debian 12)
1. Install dependencies
sudo apt update && sudo apt install -y python3.12 python3.12-venv postgresql nginx nodejs npm
2. Create service user
sudo useradd -r -s /usr/sbin/nologin stockdata
3. Deploy application
sudo mkdir -p /opt/stock-data-backend
# Copy project files to /opt/stock-data-backend
cd /opt/stock-data-backend
python3.12 -m venv .venv
source .venv/bin/activate
pip install .
4. Configure
sudo cp .env.example /opt/stock-data-backend/.env
sudo chown stockdata:stockdata /opt/stock-data-backend/.env
# Edit .env with production values (strong JWT_SECRET, real API keys, etc.)
5. Database
DB_NAME=stock_data_backend DB_USER=stock_backend DB_PASS=strong_password ./deploy/setup_db.sh
6. Build frontend
cd frontend
npm ci
npm run build
7. Systemd service
sudo cp deploy/stock-data-backend.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now stock-data-backend
8. Nginx reverse proxy
sudo cp deploy/nginx.conf /etc/nginx/sites-available/stock-data-backend
sudo ln -s /etc/nginx/sites-available/stock-data-backend /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
Nginx serves the frontend static files from frontend/dist/ and proxies /api/v1/ to the backend.
9. SSL (recommended)
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d signal.thiessen.io
Verify
curl https://signal.thiessen.io/api/v1/health
Project Structure
app/
├── main.py # FastAPI app, lifespan, router wiring
├── config.py # Pydantic settings from .env
├── database.py # Async SQLAlchemy engine + session
├── dependencies.py # DI: DB session, auth guards
├── exceptions.py # Exception hierarchy
├── middleware.py # Global error handler → JSON envelope
├── cache.py # LRU cache with per-ticker invalidation
├── scheduler.py # APScheduler job definitions
├── models/ # SQLAlchemy ORM models
├── schemas/ # Pydantic request/response schemas
├── services/ # Business logic layer
├── providers/ # External data provider integrations
└── routers/ # FastAPI route handlers
frontend/
├── index.html # SPA entry point
├── vite.config.ts # Vite config with API proxy
├── tailwind.config.ts # Tailwind + glassmorphism theme
├── package.json
└── src/
├── App.tsx # Route definitions
├── main.tsx # React entry point
├── api/ # Axios API client modules (one per resource)
├── components/
│ ├── admin/ # User table, job controls, settings, data cleanup
│ ├── auth/ # Protected route wrapper
│ ├── charts/ # Canvas candlestick chart
│ ├── layout/ # App shell, sidebar, mobile nav
│ ├── rankings/ # Rankings table, weights form
│ ├── scanner/ # Trade table
│ ├── ticker/ # Sentiment panel, fundamentals, indicators, S/R overlay
│ ├── ui/ # Badge, toast, skeleton, score card, confirm dialog
│ └── watchlist/ # Watchlist table, add ticker form
├── hooks/ # React Query hooks (one per resource)
├── lib/ # Types, formatting utilities
├── pages/ # Page components (Login, Register, Dashboard, Market, Signals, Regime, Ticker, Admin)
├── stores/ # Zustand auth store
└── styles/ # Global CSS with glassmorphism classes
deploy/
├── nginx.conf # Reverse proxy + static file serving
├── setup_db.sh # Idempotent DB setup script
└── stock-data-backend.service # systemd unit
tests/
├── conftest.py # Fixtures, strategies, test DB
├── unit/ # Unit tests
└── property/ # Property-based tests (Hypothesis)