This is an experimental, educational, open-source template for an autonomous, multi-agent trading firm built on Animus. It is NOT financial advice and is NOT a recommendation to trade any security or to deploy real money.
This software runs autonomous LLM agents that place REAL orders against your REAL brokerage account, spending your real money, without per-trade human approval. LLM agents can be wrong, hallucinate, or behave unexpectedly. YOU are solely and entirely responsible for everything the agents do — every order, trade, gain, and loss. The authors and contributors accept NO liability for any financial loss, account restriction, data loss, or any consequence whatsoever arising from use of this software. You can lose all of your money. Only deploy capital you can afford to lose entirely.
- This template ships
config/trading-mode.json={"dry_run": true}. In dry-run, NO real orders are ever placed. Keep it that way until you fully understand the code and the risks.- Nothing in this repository constitutes investment, legal, or tax advice. Trading securities involves substantial risk of loss. Past or simulated performance is not indicative of future results.
By using this template you accept full, unconditional responsibility for everything it does on your account. Read DISCLAIMER.md before proceeding.
An Animus-orchestrated autonomous trading firm: a small team of specialized AI agents that research, debate, propose, risk-check, and (only when you opt into live mode) place equity orders through Robinhood's MCP trading interface.
It is defined almost entirely in YAML and markdown:
- Multi-agent specialization — analysts feed a proposer, a risk officer with veto power gates the proposer, and a deterministic, code-only order boundary actually places the trade.
- Decision contracts between roles (
docs/TRADE-CONTRACT.md). - Institutional memory via a markdown journal.
- Hard rails that are code, not prompt text — every safety guard is a script or the Animus kernel failing a phase, never a model "promising" to behave.
┌──────────────────────┐
│ Portfolio Manager │ allocation, rebalancing, kill switch
└──────────┬───────────┘
┌────────────────┼────────────────┐
▼ ▼ ▼
┌────────────┐ ┌────────────┐ ┌────────────┐
│ Research │ │ Risk │ │ Execution │
│ Lead │ │ Officer │ │ Lead │
└─────┬──────┘ └────────────┘ └─────┬──────┘
│ │
┌─────────┼──────────┐ │
▼ ▼ ▼ ▼
Macro Sector Sentiment Order Exec
Analyst Analyst Analyst (deterministic
(news) (tech) (social/ command phase,
news tone) not an agent)
│
▼
Post-Trade
Reviewer
(writes journal)
See docs/ORG-CHART.md for each role's mandate,
docs/ARCHITECTURE.md for the stack, and
docs/TRADE-CONTRACT.md for the machine contract
between the Trade Proposer and the enforced gates.
Stay in dry-run. Every step below works fully in
dry_run: true. Do not flip to live until you have read the safety model and watched the firm run.
Plugin requirement: this template requires
animus-workflow-runner-defaultv0.4.6 or newer. Older versions have a bug where claude agent workflow phases (analyst and judgment phases) silently fail and never run. Install or upgrade before starting the daemon:animus plugin install launchapp-dev/animus-workflow-runner-default@v0.4.6 # or, to install all recommended defaults at once: animus plugin install-defaults
-
Install Animus. Follow the install guide at https://github.com/launchapp-dev/animus-cli. This template requires a recent Animus (OAuth
authorization_code+ budget enforcement support). -
Connect Robinhood (OAuth, no credentials in files). The
robinhood-tradingMCP server usesoauth: { flow: authorization_code }. Connect once:animus mcp auth robinhood-trading
This opens a browser login (OAuth 2.1 + PKCE, Dynamic Client Registration) and stores tokens in your OS keychain. The token never appears in YAML, logs, or agent argv — a local
animus-mcp-proxyinjects it upstream. -
Pin YOUR account. Open
config/account-scope.jsonand replace the placeholder000000000with your own Robinhood cash account number. Resolve it read-only via theget_accountsMCP tool — do not guess. The account scope-lock guard hard-fails any order whose account number is not this exact value. -
(Optional) Add FREE data API keys. The analysts run on a free data stack; two optional free providers enrich it. Keys live in the keychain, never in files:
animus secret set FINNHUB_API_KEY # free key: https://finnhub.io/register animus secret set ALPHAVANTAGE_API_KEY # free key: https://www.alphavantage.co/support/#api-key
With no keys set the firm still runs end-to-end (Robinhood quotes + web search); missing providers are simply skipped.
-
Run in dry-run first. Confirm
config/trading-mode.jsonis{"dry_run": true}, then:bash scripts/preflight.sh # verify required Animus plugins animus workflow config validate # must be clean animus daemon start # the firm runs; in dry-run it places NO real orders
In dry-run the
executephase logs aWOULD PLACE: …line and a journal entry instead of calling any order tool. -
Only go live when you understand the risks. Live trading is opt-in and irreversible per order. When — and only when — you have read every guard, watched several dry-run cycles, and accept that you may lose your money, set
config/trading-mode.jsonto{"dry_run": false}. Flipping it back to{"dry_run": true}re-arms the block immediately.Kill switch — stop everything, one command:
bash scripts/kill-switch.sh "reason" # marker + daemon pause + journal entry
The order boundary and every guard are fail-closed and code-enforced. We do not count prompt text as a rail.
-
The order boundary keeps the LLM out of order arguments. The
executephase is a deterministic COMMAND phase (scripts/place-order.sh→engine/place.py), not an agent. It reads the upstream-validatedstate/last-proposal.json, RE-VALIDATES every invariant at the boundary, and then constructs the exactplace_equity_orderarguments itself. The LLM never supplies the order arguments. -
Account scope-lock (INV1). The only account the firm may ever touch is the one pinned in
config/account-scope.json.account-scope-check.shhard-fails any order against a different account. Even a fully compromised agent prompt cannot route money to any other account. -
Settled-cash cap (INV2). This is a cash account: no margin, no leverage. A BUY may use at most
max_position_pct(default1.0) of settled cash, read live from Robinhood. Buying with unsettled sale proceeds is avoided (no good-faith violations). If a live settled-cash read fails on a live BUY, the guard fails closed (no order). -
Daily loss stop (INV3). When today's equity falls
daily_loss_pct(default0.20= 20%) below start-of-day, the kill switch trips and the firm halts for the day. -
Equities only (INV4). No options, crypto, futures, or event contracts — enforced upstream and re-checked at the order call. Read-only Robinhood agents also carry
tool_policy.denyon order tools as defense-in-depth. -
Kill switch (INV5).
scripts/kill-switch.sh "reason"halts everything; theKILL_SWITCH_ENGAGEDmarker persists on disk and blocks execution on restart until a human removes it. -
Dry-run floor (INV6).
scripts/live-guard.shfails the execute phase before any order wheneverconfig/trading-mode.jsonis{"dry_run": true}, and fails closed on ambiguity. This template defaults to dry-run.
Tuning these: the percent rails live in config/risk-limits.json
(daily_loss_pct, max_position_pct, instruments); scripts and the engine
read them at run time — no code edits.
The analysts run on a free, clone-and-run data stack — no Docker, no MCP
server installs, no pip install, no uvx/npx. The ingest layer
(engine/data.py) is Python stdlib only (urllib, json, ssl). A
deterministic command phase (fetch-market-data) writes one consolidated
snapshot to state/market-data.json that the analysts read as primary data,
falling back to web search when it is thin.
The free stack (all $0):
| Provider | Feeds | Auth | Free-tier limit |
|---|---|---|---|
| Robinhood (read-only MCP proxy) | live quotes + prior close | already authed via animus mcp auth robinhood-trading — no extra key |
n/a |
| Finnhub | company news, earnings calendar, analyst recommendation trends | FINNHUB_API_KEY (free) |
60 req/min |
AlphaVantage NEWS_SENTIMENT |
per-ticker pre-scored sentiment | ALPHAVANTAGE_API_KEY (free) |
25 calls/DAY (hard cap) |
Because of AlphaVantage's tight 25/day cap, the firm makes one batched
sentiment call per cycle over the top-N watchlist tickers
(top_n_sentiment, default 5). Finnhub earnings + recommendation trends
refresh at most daily.
Clone-and-run with zero keys: the firm runs end-to-end on a fresh clone
with no keys set. A missing key or unreachable provider is logged into the
snapshot's providers_skipped and skipped; the analysts fall back to web
search (web_search: true on all four analyst agents).
Providers are config-driven, not hardcoded — declared in
config/data-sources.json, dispatched per provider behind a uniform interface
in engine/data.py. config/data-sources.json carries OFF-by-default
placeholder slots for paid providers (polygon, fmp, alphavantage_premium)
with their key_env names documented. They are an upgrade path for a private
fork — flip enabled: true, add the matching dispatcher in engine/data.py,
and animus secret set <KEY>. The public template stays on the free stack.
API keys are never stored in files or committed. They are read from the
OS keychain (animus secret set …) or the environment only. This template
ships placeholders, never real values.
The firm splits models by role (.animus/workflows.yaml agent model:):
the consequential once-per-cycle judgment roles (trade-proposer,
risk-officer, portfolio-manager) run on a stronger model; the
high-frequency analysts run on a cheaper one. Per-cycle and per-phase LLM
budgets (workflows[trading-cycle].budget, plus inline phase caps) pause or
fail when exceeded. Honestly: on a small book, LLM cost is a meaningful
fraction of P&L — a full intraday session can run into the tens of dollars
before a single trade is placed. The weekly cost letter surfaces the real
number.
- The agents can lose money. With
max_position_pct: 1.0a single bad BUY can put the whole settled balance into one name. The daily stop caps the day's loss, not a single trade's, and the account can be ground down over many days. - Bad-but-in-bounds trades execute. The guards enforce scope, settlement, instrument, and loss limits — they do not judge whether a trade is good.
- Loss-stop granularity. The risk watchdog runs periodically; a fast drawdown can exceed the threshold between ticks. The stop is a backstop, not a tick-by-tick stop-loss.
- Caps bind only while the daemon runs. The kill-switch marker, however, persists on disk and blocks execution on restart.
- Stale offline marks. If a live broker read fails, the daily stop and
settled-cash cap fall back to
state/portfolio.json; a live BUY with no resolvable settled figure fails closed. - Settlement edge cases and broker accounting nuances are mitigated conservatively but not perfectly modeled.
.animus/workflows.yaml the firm's org definition (agents, phases, budgets, schedules)
prompts/ per-agent system prompts
scripts/ enforced rails + read-only market-data ingest
engine/ equity / settled-cash / P&L engine, read-only Robinhood bridge, market-data ingest, deterministic order boundary
config/ risk-limits.json, trading-mode.json (dry-run floor), account-scope.json (pinned account), data-sources.json
watchlist.txt example tickers — edit to your own
journal/ the post-trade reviewer writes here every cycle
docs/ org chart, architecture, journal format, trade contract
state/ runtime state (portfolio, last proposal) — gitignored
MIT. Provided "as is", without warranty of any kind. See the disclaimer at the top of this file.