A production-ready, open-source research terminal and backtester for systematic equity strategies. Features signal-based forecasting, ensemble models, and leakage-safe backtesting with walk-forward validation.
- Overview
- Disclaimer
- Features
- Quick Start
- Installation
- Configuration
- Usage
- API Reference
- Architecture
- Testing
- Troubleshooting
- Contributing
- License
A systematic trading research platform designed for backtesting signal-based strategies on US equities. The system implements a complete research workflow: data ingestion → feature engineering → signal generation → ensemble forecasting → risk management → leakage-safe backtesting → walk-forward evaluation.
Key Design Principles:
- Leakage-safe: Backtests only use data available at each point in time
- Production-ready: Comprehensive test suite (139+ tests), input validation, error handling
- Extensible: Clean architecture with provider abstraction, signal plugins, preset system
- Educational: Well-documented codebase suitable for learning systematic trading concepts
Data Source: Stooq (free, no API keys required). Provides daily end-of-day (EOD) historical data that may be delayed; no real-time quotes.
This software is for research and educational purposes only. Not financial advice.
Past performance does not guarantee future results. All trading involves risk of loss. The authors and contributors are not responsible for any financial losses. Use at your own risk.
Additionally, it is vital that you ensure the integrity of all math and data used - I am not perfect and this project likely includes potential mistakes or errors.
- 📊 Data Ingestion & Caching: Historical daily OHLCV data from Stooq with DuckDB caching for fast local access
- 📈 Signal Generation: Three signal types—momentum (trend-following), mean reversion, and regime filter (volatility/trend conditions)
- 🎯 Strategy Presets: Four pre-configured strategies (default, trend, mean_reversion, conservative) with tunable parameters
- 🔮 Ensemble Forecasting: Weighted signal combination with confidence scoring and regime-based filtering
- 🛡️ Risk Management: Volatility targeting, position sizing, leverage constraints, and drawdown stops
- ✅ Leakage-Safe Backtesting: Time-aware engine that prevents future data leakage, includes transaction costs and slippage
- 🔄 Walk-Forward Evaluation: Out-of-sample testing framework to avoid overfitting and validate strategy robustness
- 🌐 REST API: FastAPI backend with OpenAPI documentation, request logging, and comprehensive error handling
- 💻 Web UI: Modern React + TypeScript frontend with real-time charts, signal tables, and backtest visualization
- 🧪 Comprehensive Testing: 139+ tests covering logic correctness, security, edge cases, and provider failures
Windows:
.\start-dev.ps1macOS/Linux:
chmod +x start-dev.sh
./start-dev.shThis automatically starts both backend (port 8000) and frontend (port 8080) servers.
Access points:
- 🌐 Frontend UI: http://localhost:8080
- 🔌 Backend API: http://127.0.0.1:8000
- 📚 API Docs: http://127.0.0.1:8000/docs
Terminal 1 - Backend:
cd backend
python -m venv venv
# Windows: venv\Scripts\activate
# macOS/Linux: source venv/bin/activate
pip install -e ".[dev]"
python -m app.cli serveTerminal 2 - Frontend:
cd signal-compass
npm install
npm run dev- Python: 3.11 or higher
- Node.js: 18 or higher (and npm)
- Navigate to
backend/directory - Create virtual environment:
python -m venv venv source venv/bin/activate # Windows: venv\Scripts\activate
- Install dependencies:
This installs the package in editable mode with development dependencies (pytest, black, mypy).
pip install -e ".[dev]"
- Navigate to
signal-compass/directory - Install dependencies:
npm install
- Copy
backend/env.exampletobackend/.env - Default settings work for most use cases (no changes needed)
Key Settings:
DATA_PROVIDER=stooq- Data source (free, no API keys)API_PORT=8000- Backend server portDUCKDB_PATH=./data/trading.db- Local database pathTARGET_VOLATILITY=0.01- Target daily volatility (1% = 0.01)CORS_ORIGINS- Comma-separated frontend URLs (default includes common ports)
- Copy
signal-compass/.env.exampletosignal-compass/.env(if it exists) - Default
VITE_API_BASE_URL=http://127.0.0.1:8000works if backend runs on port 8000
Note: .env files are git-ignored. Never commit real credentials. Use .env.example files as templates.
PowerShell:
# Health check
Invoke-RestMethod -Uri "http://127.0.0.1:8000/health" -Method Get
# Get forecast with default preset
Invoke-RestMethod -Uri "http://127.0.0.1:8000/forecast?ticker=AAPL&preset=default" -Method Get
# Run backtest
Invoke-RestMethod -Uri "http://127.0.0.1:8000/backtest?ticker=AAPL&start=2020-01-01&end=2024-12-31&preset=default" -Method GetcURL:
# Health check
curl "http://127.0.0.1:8000/health"
# Get forecast with trend preset
curl "http://127.0.0.1:8000/forecast?ticker=AAPL&preset=trend"
# Get historical data
curl "http://127.0.0.1:8000/history?ticker=AAPL&start=2024-01-01&end=2024-01-10"Use the preset query parameter in /forecast and /backtest endpoints:
default- Balanced approach (equal signal weights, 30% regime influence, 10% threshold)trend- Momentum-focused (60% momentum, 20% mean reversion, 15% threshold)mean_reversion- Mean reversion-focused (20% momentum, 60% mean reversion, 8% threshold)conservative- Lower risk (equal weights, 20% regime, 20% threshold)
Example:
curl "http://127.0.0.1:8000/forecast?ticker=AAPL&preset=trend"Fetch historical data:
python -m app.cli fetch AAPL --start 2020-01-01 --end 2024-01-01Run backtest:
python -m app.cli backtest AAPL --start 2020-01-01 --end 2024-12-31Walk-forward evaluation:
python -m app.cli backtest AAPL --start 2020-01-01 --end 2024-12-31 --walkforwardAll endpoints return JSON with consistent metadata:
as_of- Request timestamp (UTC ISO format)data_source- Provider name (e.g., "stooq")is_delayed- Boolean indicating data stalenessstaleness_seconds- Seconds since last bar's market close, ornulllast_bar_date- Most recent bar date (YYYY-MM-DD)warnings- Array of warning messages (always a list)
| Method | Endpoint | Description |
|---|---|---|
GET |
/health |
Health check and data source status |
GET |
/history?ticker=AAPL&start=2020-01-01&end=2024-01-01 |
Historical OHLCV bars |
GET |
/signals?ticker=AAPL&start=2020-01-01&end=2024-01-01 |
Signal history with scores and confidence |
GET |
/forecast?ticker=AAPL&preset=default |
Latest forecast with direction, confidence, and position sizing |
GET |
/backtest?ticker=AAPL&start=2020-01-01&end=2024-12-31&preset=default |
Run backtest and return metrics + equity curve |
GET |
/tickers/search?q=AAPL |
Search tickers by symbol prefix (offline CSV lookup) |
Interactive API documentation available at http://127.0.0.1:8000/docs
┌─────────────┐ ┌──────────────┐ ┌─────────────┐
│ CLI/API │────▶│ Core Engine │────▶│ Data Layer │
└─────────────┘ └──────────────┘ └─────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌───────────┐ ┌──────────┐ ┌─────────────┐
│ Signals │ │Ensemble │ │ Backtester │
└───────────┘ └──────────┘ └─────────────┘
- Data Layer: Provider abstraction (Stooq), DuckDB caching, data normalization, ticker canonicalization
- Features: Momentum (returns, MA slopes, breakouts), mean reversion (z-scores, Bollinger bands), volatility/regime (realized vol, trend strength)
- Signals:
MomentumSignal,MeanReversionSignal,RegimeFilterSignal- each returns score [-1,1] and confidence [0,1] - Ensemble: Weighted signal combination with regime-based filtering. Direction based on scores only; confidence used for position sizing
- Portfolio: Volatility targeting (daily units), position sizing, risk constraints (leverage, drawdown stops)
- Backtest: Leakage-safe engine with point-in-time data access, transaction costs, slippage, comprehensive metrics
- Data Flow: Request → DataFetcher → Provider (Stooq) → Cache (DuckDB) → Normalization
- Feature Engineering: Bars → Compute momentum/mean reversion/volatility features
- Signal Generation: Features + Bars → Generate signals (score, confidence) for each date
- Ensemble Forecasting: Signals → Weighted combination → Forecast (direction, confidence)
- Position Sizing: Forecast + Volatility → Volatility-targeted position size
- Backtesting: Historical Bars + Ensemble → Execute trades point-in-time → Compute metrics
- Type: End-of-day (EOD) historical data via CSV downloads
- Coverage: US stocks and ETFs
- Ticker format: Accepts
AAPLorAAPL.us(normalized internally;.ussuffix recommended for US stocks) - Update frequency: Daily after market close (data may be delayed by 1+ days)
- Rate limit: 1 request/second (enforced automatically)
All API responses include staleness metadata:
is_delayed: Boolean (typicallytruefor EOD data)staleness_seconds: Seconds since last bar's market close (4:00 PM ET = 20:00 UTC), ornullif data is current/futurelast_bar_date: Most recent bar date (YYYY-MM-DD)warnings: Array of data quality or availability messages
How it works: Daily bars are treated as closing at 20:00 UTC (4:00 PM ET). Staleness is computed as: current UTC time - (last_bar_date + 20:00 UTC).
End date clamping: If a requested end date exceeds available data, the system automatically clamps to the latest available bar. Check last_bar_date in responses to see the actual end date used.
The project includes a comprehensive test suite with 139+ tests covering:
- ✅ Logic Correctness: Data leakage prevention, math correctness, signal calculations
- 🔒 Security: Input validation, injection attack prevention, error message sanitization
- 🧪 Edge Cases: Empty data, NaN handling, extreme values, missing columns
- 🔌 Provider Failures: Network errors, timeouts, rate limits, partial data
- 📊 Financial Correctness: P&L calculations, transaction costs, risk constraints
- 🔄 Integration: End-to-end workflows, API contracts, frontend-backend communication
Run all tests:
cd backend
python -m pytest -vRun critical tests only:
python -m pytest tests/test_leakage.py tests/test_math_correctness.py tests/test_ensemble_fixes.py tests/test_critical_correctness.py -vRun with coverage:
python -m pytest --cov=app --cov-report=htmlTests use mock data providers for reproducibility and run offline by default.
CORS errors: Add frontend URL to CORS_ORIGINS in backend/.env (default includes http://localhost:8080 and common ports)
Connection refused: Verify backend is running: http://127.0.0.1:8000/health
Wrong VITE_API_BASE_URL: Ensure signal-compass/.env has VITE_API_BASE_URL=http://127.0.0.1:8000 matching backend port
Port conflicts: Change API_PORT in backend/.env and update VITE_API_BASE_URL in signal-compass/.env
Ticker not found: Try adding .us suffix (e.g., AAPL.us) for US stocks
Missing dependencies: Reinstall backend (pip install -e ".[dev]") or frontend (npm install)
Data not loading: Check warnings field in API responses for data availability issues
Contributions welcome! Please ensure:
- ✅ Code follows existing style (type hints, docstrings)
- ✅ Tests pass (
pytest) - ✅ No data leakage in backtests (critical - see
test_leakage.py) - ✅ Security best practices (input validation, no secrets in code)
- ✅ Edge cases handled gracefully
Development workflow:
- Fork the repository
- Create a feature branch
- Make changes with tests
- Run full test suite:
pytest -v - Submit pull request
- ✅ No secrets committed: All configuration via
.envfiles (git-ignored) - ✅ Free data sources only: Uses Stooq (no paid APIs or API keys required)
- ✅
.gitignoreconfigured: Excludes build artifacts, local DBs, logs,.envfiles - ✅ Template files:
.env.examplefiles provided for setup - ✅ Input validation: API endpoints validate and sanitize all inputs
- ✅ Comprehensive testing: Security tests included in test suite
Windows (PowerShell):
git status
git ls-files | Select-String -Pattern "(\.env$|\.db$|\.sqlite$|egg-info/)"
cd backend && python -m pytest -qmacOS/Linux:
git status
git ls-files | grep -E "(\.env$|\.db$|\.sqlite$|egg-info/)"
cd backend && python -m pytest -qShould return empty results for tracked artifacts check.
MIT License - see LICENSE file for details.
Inspired by systematic quantitative research workflows. Built for educational and research purposes.