ARCHITECTURE.md: system overview, data flow, SQLite schema, guard rails, risk gates, wheel flow, guide for adding new strategies. RUNBOOK.md: day-to-day ops, config reference, troubleshooting, safe shutdown, upgrade procedure. CHANGELOG.md: v0.1.0 + v0.1.1 (limit_price fix). .env.example: credentials removed, URL /v2 suffix stripped.
alpaclaudia
Automated Alpaca paper-trading bot with a Next.js dashboard.
Strategy coverage today:
- Wheel — sells cash-secured puts on a configurable universe, rolls into covered calls after assignment.
- Trailing stops — on every long equity position (opt-in).
Safety first: by default BOT_MODE=dry — the bot plans and logs intents but submits nothing to Alpaca. Flip to live only after you've reviewed the intent log.
finhacks/
├── bot/ # Python 3.11+ trading bot (alpaca-py, SQLite state)
├── dashboard/ # Next.js 14 dashboard (read-only view of bot + Alpaca)
├── systemd/ # user-unit templates for loop + daily report
├── data/ # SQLite DB lives here (gitignored)
├── logs/ # bot logs (gitignored)
└── docs/ # extra docs
Quick start (paper)
# 0. prerequisites: Python 3.11+, Node 20+, a paper Alpaca account
# 1. credentials
cp .env.example .env
# edit .env and set ALPACA_API_KEY, ALPACA_API_SECRET
# leave BOT_MODE=dry for the first few runs
# 2. bot
cd bot
python -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/python -m alpaclaudia status # smoke test
.venv/bin/python -m alpaclaudia tick # one dry iteration
# 3. dashboard
cd ../dashboard
cp .env.example .env.local
# set ALPACA_API_KEY + ALPACA_API_SECRET (same paper creds)
npm install
npm run dev # http://localhost:3030
# 4. schedule (optional, systemd user units)
# see systemd/README.md
How a tick runs
scheduler.tick()
├─ snapshot account / positions / orders (Alpaca)
├─ record_tick() → SQLite
├─ plan_wheel() ── produces OrderIntent[]
├─ plan_trailing_stops() ── produces OrderIntent[]
├─ risk.check_*() ── per intent; blocked → logged, never submitted
└─ executor.submit_intent() ── no-op in dry-run; else Alpaca REST
Everything the bot considers ends up in data/alpaclaudia.db::order_intents, whether submitted, blocked, or dry-run. The dashboard reads this table verbatim, so you can audit the bot's reasoning independently of Alpaca.
Going live (on paper — still "paper" at Alpaca)
After reviewing a couple of dry runs:
# in .env
BOT_MODE=live
ALPACA_ENV=paper # still paper account — don't touch this unless you mean it
ALPACA_ENV=live is a separate, explicit guard that flips the SDK to the production endpoint. Don't set it unless you really want real money at stake.
Daily Discord report
Set DISCORD_WEBHOOK_URL in .env and enable the timer:
systemctl --user enable --now alpaclaudia-report.timer
It fires Mon–Fri at 22:30 local time (≈30 min after NYSE close if you're in Europe).
Risk invariants (code in bot/alpaclaudia/risk.py)
- CSPs require
strike * 100 * qty ≤ cash − equity * MIN_CASH_BUFFER_PCT. - CSP collateral per symbol capped at
MAX_POSITION_PCTof equity. - Covered calls only sell above cost basis — never locking in a loss.
- Covered calls require ≥
qty*100underlying shares already held. - Trailing stops only on long equity positions we own.
Tests: cd bot && .venv/bin/pytest.