A fully local autonomous stock trading agent that uses a local LLM (via Ollama), Alpaca paper trading, and MCP tool servers. A multi-agent investment committee performs deep research, debates the best move, and rebalances the paper portfolio toward 30-day PnL — up to 10 minutes per cycle.
Market intelligence is provided by a news analysis pipeline that fetches news, groups articles into market events, and stores structured summaries in a shared SQLite event store. One process runs news and/or trading: a single shot, or a daily loop at 12:00 America/New_York. Cycles skip immediately when the US cash session is closed (weekdays 09:30–16:00 ET).
main.py (--news-once | --trade-once | --once | --loop | --clean)
├── news/pipeline.py
│ ├── news/collector.py (APIs + RSS feeds)
│ ├── news/analyzer.py (dedupe, group, LLM enrich)
│ └── store/events.py → data/events.db
└── agent/orchestrator.py
├── agent/research.py (Phase 1: Alpaca data + event store query)
├── agent/deliberation.py (Phase 2: 4 traders + chair debate)
├── agent/personas.py (trader personalities)
├── agent/risk.py (pre-trade validation)
├── agent/decision.py (PortfolioDecision parser)
├── util/llm_client.py (Ollama via OpenAI SDK)
└── servers/manager.py (Alpaca MCP only)
└── servers/alpaca.py → alpaca-mcp-server (stdio)
News cycle: resolves a dynamic watchlist from Alpaca (held positions + gainers/losers + event tickers, same as the trading agent); fetches from RSS (first-class), NewsAPI, Finnhub, Alpha Vantage, and Marketaux; deduplicates articles; groups into market events; LLM-enriches each event with summary, sentiment, importance, and tickers. Skips immediately if the US cash session is closed.
Trading cycle: reads portfolio data from Alpaca and market events from the store. Each cycle writes a decision journal (proposals, fills, later 1d/5d/30d marks) into the event store. Skips immediately if the US cash session is closed.
| Requirement | Notes |
|---|---|
| Python 3.11+ | |
| Ollama | Must be running: ollama serve |
| An Ollama model | ollama pull qwen2.5:7b (minimum); Qwen3+ recommended for committee |
| Alpaca paper account | app.alpaca.markets |
| News API keys (optional) | RSS runs without keys; add API keys for broader coverage |
Paper API keys must start with PK; live keys start with AK. The trading agent validates the prefix against ALPACA_PAPER_TRADE at startup.
cd auto-trade
python -m venv .venv
# Windows
.venv\Scripts\activate
# macOS / Linux
source .venv/bin/activate
pip install -r requirements.txt
pre-commit installThis installs a gitleaks pre-commit hook that scans every commit for secrets (API keys, tokens, etc.) before it lands in git.
cp .env.example .envEdit .env with your credentials. See .env.example for all options.
ollama pull qwen2.5:7b
ollama serveWith no flags, python main.py prints help and exits. Pick one mode:
python main.py --news-once # one news cycle, then exit
python main.py --trade-once # one trade cycle, then exit
python main.py --once # news, then trade, then exit
python main.py --loop # news then trade, every day at 12:00 America/New_York
python main.py --clean # delete data/events.db and logs/*.log, then exit--once and --loop always run news then trading sequentially (the next step
starts when the previous one finishes). If the US cash session is closed when a
cycle starts, that cycle logs SKIP – market closed and returns immediately.
--clean deletes the event store (data/events.db and SQLite sidecars) and *.log files in logs/.
Each agent writes human-readable cycle logs to a single file. Cycles are separated by banner lines. Warnings and errors go to stderr only (nothing printed for normal operation).
| File | Service |
|---|---|
logs/news.log |
Sources fetched, article counts, LLM enrichment |
logs/trading.log |
Alpaca calls, event store query, committee deliberation, orders |
auto-trade/
├── agent/
│ ├── decision.py # PortfolioDecision + persona parsers
│ ├── deliberation.py # Multi-round committee loop
│ ├── orchestrator.py # Cycle coordinator, execution, logging
│ ├── personas.py # Trader + chair prompts
│ ├── research.py # Alpaca research + event store query
│ ├── risk.py # Pre-trade validation
│ └── workflow.py # Shared MCP helpers
├── news/
│ ├── analyzer.py # Dedupe, group, LLM enrich
│ ├── collector.py # Multi-source fetch orchestration
│ ├── feeds.py # RSS feed configuration
│ ├── pipeline.py # One news cycle
│ └── sources/ # NewsAPI, Finnhub, RSS, etc.
├── store/
│ ├── db.py # SQLite + WAL mode
│ └── events.py # Event store CRUD and queries
├── servers/
│ ├── alpaca.py # Alpaca MCP subprocess launcher
│ └── manager.py # MCPManager: Alpaca session only
├── config/
│ └── settings.py # Pydantic settings (reads .env)
├── data/
│ └── events.db # Shared event store (gitignored)
├── logs/
│ ├── news.log # News cycle log (human-readable)
│ └── trading.log # Trading cycle log (human-readable)
├── main.py # Single entry point
├── .env.example
├── requirements.txt
└── README.md
- Paper trading only by default. Set
ALPACA_PAPER_TRADE=falseand use live keys to trade with real money — do so at your own risk. - The agent places limit orders from the latest NBBO when a quote is available, with
time_in_force=day, and keeps fractional qty on fractionable names. - Run
python main.py --news-once(while the cash session is open) to populate the event store before the first--trade-once. - RSS feeds run without API keys; optional API keys extend coverage.
- Model quality matters for JSON-only persona output.
qwen2.5:7bis the documented minimum; larger models (e.g. Qwen3) work better withENABLE_THINKING=true. - Buy orders are capped by
max_position_pctusing latest bar close prices from research. - The optimization horizon is fixed at 30 days in code (
HORIZON_DAYSinagent/personas.py).