Automated quantitative trading system for QQQ using RSI mean reversion with multi-timeframe confirmation.
A production-style automated trading engine that executes long and short positions on QQQ based on RSI(14) oversold/overbought signals. The system uses a layered, multi-timeframe approach: 45-minute bars trigger entries, while 1-hour and 4-hour confirmations adjust position sizing. ATR-based trailing stops, breakeven rules, and risk gates (daily loss limit, max trades, cooldown after loss) manage downside. Designed to run autonomously via macOS launchd during market hours.
Key characteristics: One position at a time, RSI-only signal logic, configurable risk parameters, dry-run and paper-trading support, structured logging, and preflight checks before execution.
┌─────────────────────────────────────────────────────────────────────────┐
│ MAIN LOOP │
│ (main.py) → preflight → engine.run_cycle() every 15s │
└─────────────────────────────────────────────────────────────────────────┘
│
┌────────────────────────────┼────────────────────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ PREFLIGHT │ │ TRADING ENGINE │ │ EXECUTION │
│ Market hours │ │ Position mgmt │ │ Alpaca orders │
│ DRY_RUN check │ │ Entries/Exits │ │ Position sync │
│ Mode vs URL │ │ Trailing stop │ │ Close position │
└─────────────────┘ └─────────────────┘ └─────────────────┘
│ │ │
│ ┌─────────────┼─────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ SIGNALS │ │ SIZING │ │ RISK │ │
│ │ RSI long │ │Confidence│ │ Daily PnL│ │
│ │ RSI short│ │ Risk pct │ │ Max trades│ │
│ │ Exits │ │ Shares │ │ Cooldown │ │
│ └──────────┘ └──────────┘ └──────────┘ │
│ │ │ │ │
└──────────────┴─────────────┴─────────────┴──────────────┘
│
┌──────────────┼──────────────┐
▼ ▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ DATA │ │INDICATORS│ │ CONFIG │
│ Bars API │ │ RSI(14) │ │ Thresholds│
│ Retries │ │ ATR(14) │ │ Risk params│
│ Validate │ │ NaN safe │ │ Market hrs│
└──────────┘ └──────────┘ └──────────┘
Flow: Each cycle, the engine checks exits (trailing stop, RSI-based) then entries. Entry requires 45-min RSI oversold (long) or overbought (short); 1H/4H RSI adds confidence tiers for position sizing. Position state is the single source of truth, synced from the broker on startup and after operations.
| Goal | Implementation |
|---|---|
| Single source of truth | Position state synced from broker; one open position at a time |
| Fail-safe defaults | DRY_RUN=True, market-hours only, preflight blocks live trading on config mismatch |
| Modularity | Signals, sizing, risk, execution, data, and indicators are separate modules with clear boundaries |
| Observability | Structured logs per cycle (status, indicators, block reasons, decision summary) |
| Robustness | Bar fetch retries, NaN/inf handling in indicators, validation before entry |
| Reversibility | No strategy changes in “safe improvements”; config-driven thresholds |
| Module | Responsibility |
|---|---|
main.py |
Entry point, market-open wait, preflight, main loop, market-close shutdown |
engine.py |
TradingEngine and Position: cycle orchestration, entry/exit logic, trailing stop, breakeven |
signals.py |
check_long_entry, check_short_entry, check_long_exit, check_short_exit (RSI-based) |
sizing.py |
calculate_confidence_long/short, calculate_risk_percent, calculate_position_size |
risk.py |
RiskManager: daily PnL, max trades, cooldown after loss, daily reset (market TZ) |
execution.py |
get_current_position, submit_buy_order, submit_sell_order, close_position |
data.py |
get_bars_for_timeframe (retries), validate_bars_for_indicators |
indicators.py |
get_latest_rsi, get_latest_atr (Wilder RSI, ATR) |
preflight.py |
Market hours, DRY_RUN safety, mode vs URL consistency |
config.py |
Strategy and risk constants (RSI thresholds, ATR mults, risk %, market hours) |
alpaca_client.py |
Alpaca REST client for bars, account, positions, orders |
- Instrument: QQQ
- Signal: RSI(14) on 45-minute bars; Wilder smoothing
- Long entry: 45-min RSI ≤ 30 (oversold)
- Short entry: 45-min RSI ≥ 70 (overbought)
- Long exit: RSI ≥ 70 or trailing stop
- Short exit: RSI ≤ 30 or trailing stop
- Position sizing: Confidence 1–3 from 1H/4H confirmation; risk = base + confidence tier, capped
- Stops: Initial 2× ATR, trail 1.5× ATR, breakeven at 1× ATR
- Risk: Max daily loss %, max trades/day, 60-min cooldown after loss
-
Install dependencies
pip install -r requirements.txt
-
Environment
Create
.envwith:ALPACA_API_KEY– Alpaca API keyALPACA_SECRET_KEY– Alpaca secret keyALPACA_BASE_URL–https://paper-api.alpaca.markets(paper) orhttps://api.alpaca.markets(live)ALPACA_DATA_FEED–siporiexMODE–paperorlive
-
Strategy config (optional)
Editsrc/config.pyfor thresholds, risk %, and market hours.
python -m src.mainThe engine polls every 15 seconds, checks entries/exits, manages positions with trailing stops, enforces risk controls, and logs to logs/trading/.
DRY_RUN: Default DRY_RUN=True; no orders are sent. Set DRY_RUN=False for live trading.
Scheduling: Use the macOS launchd plist or cron examples in docs/SCHEDULER_SETUP.md for automated runs at market open.
python -m unittest discover -s tests -v
python -m src.test_preflight| Path | Purpose |
|---|---|
logs/trading/trading_YYYYMMDD.log |
Daily trading log |
logs/trading/bot_scheduler.log |
Scheduler output |
logs/trading/launchd_*.log |
launchd stdout/stderr |
Unlicensed. Use at your own risk.