A self-hosted algorithmic trading bot executing trades on Alpaca.
- Backend: FastAPI + SQLAlchemy async (SQLite) + APScheduler — runs on port 8000
- Frontend: React 18 + TypeScript + Vite — dev server on port 5173, proxied to
/api
| Feature | Description |
|---|---|
| Dashboard | Account equity and buying power |
| Positions | Live open positions from Alpaca |
| Trades | Order history with FIFO P&L tracking |
| Strategies | Create and manage automated trading strategy configs per symbol |
| Watchlist | Symbols to passively monitor for opportunities |
| Opportunities | Automatically detected signals awaiting approval before trading begins |
Three built-in strategies are available:
| Strategy | Logic | Default Params |
|---|---|---|
moving_average |
BUY when fast SMA crosses above slow SMA; SELL on reverse crossover | fast_period=10, slow_period=30 |
rsi |
BUY when RSI < 30 (oversold); SELL when RSI > 70 (overbought) | period=14, oversold=30, overbought=70 |
mean_reversion |
BUY when price drops below the lower Bollinger Band; SELL when it exceeds the upper band | window=20, num_std=2.0 |
The opportunity system acts as a human-in-the-loop gate between passive market scanning and active automated trading — no symbol is traded without explicit user approval.
- The user adds symbols to the Watchlist.
- Every 30 minutes (
SCAN_INTERVAL_MINUTES), the scanner runs all three strategies against every watchlist symbol that does not already have an activeStrategyConfig. - When any strategy emits a BUY or SELL signal, an
Opportunityis saved (deduplicated: no duplicate pending opportunities per symbol+strategy pair). - The user reviews pending opportunities on the
/watchlistpage:- Approve → creates an enabled
StrategyConfigwith default params; the bot starts trading on the next scheduler tick. - Dismiss → marks the opportunity as actioned with no trade placed; the scanner may resurface it on the next run if the signal persists.
- Approve → creates an enabled
Two recurring background jobs run independently:
| Job | Interval env var | Default | What it does |
|---|---|---|---|
run_strategies |
BOT_INTERVAL_MINUTES |
5 min | Evaluates all enabled StrategyConfig rows; places orders on BUY/SELL signals |
scan_opportunities |
SCAN_INTERVAL_MINUTES |
30 min | Scans untracked watchlist symbols; saves new opportunities |
run_strategies includes:
- Market hours gating — equities skipped outside NYSE hours; crypto runs 24/7
- Position-aware gating — won't BUY if already holding; won't SELL if no position
- Duplicate signal prevention — skips if an open order for the same side already exists
- Position sizing —
risk_pct% of account equity, minimum 1 unit
cd backend
cp .env.example .env # fill in your Alpaca credentials
pip install -r requirements.txt
uvicorn app.main:app --reloadcd frontend
npm install
npm run devSee backend/.env.example for the full list.
| Variable | Default | Description |
|---|---|---|
ALPACA_API_KEY |
— | Alpaca API key |
ALPACA_SECRET_KEY |
— | Alpaca secret key |
ALPACA_PAPER |
true |
Use paper trading (true/false) |
ALPACA_DATA_FEED |
iex |
Market data feed: iex (free) or sip (paid) |
BOT_INTERVAL_MINUTES |
5 |
How often run_strategies fires |
SCAN_INTERVAL_MINUTES |
30 |
How often scan_opportunities fires |
DATABASE_URL |
sqlite+aiosqlite:///./stocks.db |
SQLAlchemy async database URL |