Multi-agent paper-trading framework: LLM agents propose trades, a separate broker validates and executes them.
Project description
name: "midas-core" tagline_fr: "Un moteur Brain/Hands open source pour agents IA en paper trading — la sécurité vit dans le broker, pas dans le prompt." tagline_en: "An open Brain/Hands engine for paper-trading AI agents — safety enforced by the broker, not the prompt." facts_fr: "Licence MIT, Python 3.12+, 15 codes de garde-fou du broker, desk de démo exécutable, pip install midas-core." facts_en: "MIT license, Python 3.12+, 15 broker safety-rail reason codes, a runnable demo desk, pip install midas-core."
midas-core
An open, multi-agent paper-trading fund-manager framework built on a Brain/Hands split: agents author decisions; a broker enforces the safety rails and executes them. This repo is the reusable core — the engine, the config-driven roster/allocator model, a runnable demo desk, and the deterministic Hands pipeline. Bring your own agents.
Paper-trading by default. This is a framework, not a service: it never executes against your broker keys, never pools funds, never gives per-user advice. See DISCLAIMER.md.
Why this exists
- Safety lives in the broker, not the prompt. The persona is aspirational; the broker is
enforcing. Every order is checked at fill time against a fixed set of 15 rejection/cancel
reason codes —
MAX_ORDER_NOTIONAL,MAX_ORDERS_PER_DAY,TICKER_NOT_IN_UNIVERSE,INSUFFICIENT_CASH,DAILY_DRAWDOWN_HALT, … — so a coaxed or confused agent still can't slip an oversized or out-of-universe trade through. - Reproducible by construction. Dependencies are a fully-pinned lockfile; prices and index
universes are committed to git; a trading session makes no outbound HTTP calls. Every fill is
stamped with
executed_sha, the git HEAD it executed against —git checkout <sha>re-derives the exact order and price store the broker saw. - Proven on a live desk. A 10-agent desk has run this engine every weekday since 2026-04-17, narrated publicly at midas.revah.paris. This repo is a one-way code mirror of that desk's engine — the same code, without the private data ledger.
- Conditional orders, not just market orders. Agents can defer a trade until a price level is crossed. Expiry is mandatory; a separate watcher process fires the order when the condition is met and applies the same order-level rails as a market fill.
Architecture — Brain / Hands
The Brain (agents) only ever writes to disk; it holds no credentials. The Hands (the paper broker, a separate watcher process) reads that outbox, enforces the rails, and executes. The boundary is a directory of append-only JSONL files, so a real-money broker is a drop-in swap for the paper one behind the same contract.
flowchart LR
A["Agents (Brain)"] -->|"author orders"| O["outbox/*.jsonl"]
O --> B{"paper broker: safety rails"}
B -->|"filled"| I["inbox/*.jsonl"]
B -->|"rejected + reason code"| I
I -->|"apply_trade"| P["portfolios/*.json"]
A -.->|"conditional order"| PE["pending/order_id.json"]
PE --> W["check_triggers watcher"]
W -.->|"price level crossed"| B
Quickstart — reach a real fill
This drives the full loop end-to-end: bootstrap prices, hand-author two orders (one clears, one is deliberately oversized to trip a rail), run the broker, and inspect the fills.
python -m venv .venv && source .venv/bin/activate
pip install midas-core
# Materialise the packaged demo desk (roster.yaml, .claude/agents/demo-*.md,
# data/universes/*.json) into a directory you name — no git clone required.
midas init-demo ./my-desk
export MIDAS_DATA_DIR=$PWD/my-desk
# One-time: open a USD 10,000 portfolio for demo-momentum.
python -c "
from engine.config import get_config
from engine.portfolio import PortfolioManager
pm = PortfolioManager(base_dir=get_config().portfolios_dir)
pm.initialize('demo-momentum', initial_capital=10000, currency='USD')
"
# Bootstrap the price store from yfinance (the one networked step; the live desk
# populates this store from a cron so trading sessions stay HTTP-free).
midas fetch-ohlcv --symbols SPY,QQQ,IWM,VTV,SCHD,BRK-B,URTH --history-days 90
Author two orders — append-only JSONL, one per line — to
$MIDAS_DATA_DIR/data/orders/outbox/<TODAY>.jsonl (<TODAY> = today's YYYY-MM-DD). Every
field is required, including a non-empty reasoning (no silent trades); shares must be
strictly positive (Midas is long-only):
{"order_id": "ord_demo_001", "ts": "2026-04-17T20:00:00Z", "agent_id": "demo-momentum", "action": "BUY", "ticker": "SPY", "shares": 3, "reasoning": "Trend intact above the 50-day; adding core exposure.", "currency": "USD"}
{"order_id": "ord_demo_002", "ts": "2026-04-17T20:00:00Z", "agent_id": "demo-momentum", "action": "BUY", "ticker": "SPY", "shares": 10, "reasoning": "Deliberately oversized clip to demonstrate the notional rail.", "currency": "USD"}
demo-momentum's max_order_notional is 5000. With SPY near $740 the first order ($2,200)
clears; the second ($7,400) breaches the cap. Run the broker:
midas fill-day
# fill-day: 1 filled, 1 rejected out of 2
The inbox is the durable Hands-side record of both outcomes:
cat $MIDAS_DATA_DIR/data/orders/inbox/<TODAY>.jsonl
{"order_id": "ord_demo_001", "status": "filled", "fill_price": 738.18, "notional_base": 2214.54, "fees": 1.25, "reason": null}
{"order_id": "ord_demo_002", "status": "rejected", "fill_price": null, "notional_base": null, "fees": null, "reason": "MAX_ORDER_NOTIONAL"}
The filled order mutated data/portfolios/demo-momentum/portfolio.json (cash debited, a SPY
position opened); the rejected one changed nothing but its reason. Exact prices depend on the day
you fetch — the shape (one fill, one rejection, a debited portfolio) is the point.
The full walkthrough — the portfolio JSON, the executed_sha provenance stamp, and the roster
schema — is in ./examples/demo-desk/README.md.
From source (contributors)
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt # pinned lockfile (incl. pytest + hypothesis)
pip install -e . # the midas-core package + the `midas` CLI
# Point the engine at a working copy of the demo desk.
cp -R examples/demo-desk /tmp/my-desk
export MIDAS_DATA_DIR=/tmp/my-desk
pytest -q
Run your own desk
The whole cast is config. Copy the demo desk and edit two things:
roster.yaml— declare your traders, anarrator, and an optionalallocator, each with its universe, starting capital, and broker-enforcedsafetyrails (max_order_notional,max_orders_per_day,daily_drawdown_halt_pct,allowed_universe)..claude/agents/*.md— the persona behind each id: its voice and its strategy.
Nothing else is hardcoded. The full roster schema (every field and its default) is documented in
./examples/demo-desk/README.md.
What you need
| To run… | Requirements |
|---|---|
| Deterministic pipeline — fills, baselines, backtests | Python 3.12+ only. No API keys, no accounts. |
| LLM trading sessions — personas, journals, narration | Runs under Claude Code (scripts/daily_session.py). |
The deterministic pipeline is headless and fully tested; you can drive a desk end-to-end with no model at all by hand-authoring orders, exactly as the quickstart does.
Honest framing
- Paper-trading by default. Fills are simulated against a committed price store. No output is a track record, evidence of an edge, or investment advice.
- Framework, not a service. midas-core never executes against your broker keys, never holds credentials, never pools or manages anyone's funds, and gives no per-user advice. A real-money broker would be your code, swapped in behind the same outbox/inbox contract.
- Full terms: DISCLAIMER.md.
Relationship to the live desk
This repository is a one-way code mirror, synced from the operator's private live repo (the
source of truth) by a manifest tool. The live desk's data ledger — its real portfolios, journals,
and daily narrative — stays private and is not in this repo; the bundled examples/demo-desk
is a synthetic fixture. Because of the mirror direction, code PRs against synced trees can't be
merged — please open an issue instead. See CONTRIBUTING.md.
Layout
engine/— types, market data, bt adapter, paper broker, config (roster.yamlloader), universes.scripts/— session orchestration (daily_session.py), the conditional-order watcher (check_triggers.py), backtest runners, universe refresh.examples/demo-desk/— the forkable reference desk.data/{strategies,universes}/— generic strategy specs + index constituents.
License
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file midas_core-0.1.1.tar.gz.
File metadata
- Download URL: midas_core-0.1.1.tar.gz
- Upload date:
- Size: 291.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1cf3183e381984ccc98b2564eec49bf899067f46a5799ab5cc4d8c7380d3f90b
|
|
| MD5 |
1e6d5f56e3f27160ae338155ac1fe39a
|
|
| BLAKE2b-256 |
d2405d542727154c18b197e9f6dbcf393271fbafa04376c006434c439c95b589
|
Provenance
The following attestation bundles were made for midas_core-0.1.1.tar.gz:
Publisher:
publish.yml on w2ur/midas-core
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
midas_core-0.1.1.tar.gz -
Subject digest:
1cf3183e381984ccc98b2564eec49bf899067f46a5799ab5cc4d8c7380d3f90b - Sigstore transparency entry: 2329106670
- Sigstore integration time:
-
Permalink:
w2ur/midas-core@9f0a65bdf582adc0997d0a362e1fedbf9a9a3bb3 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/w2ur
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9f0a65bdf582adc0997d0a362e1fedbf9a9a3bb3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file midas_core-0.1.1-py3-none-any.whl.
File metadata
- Download URL: midas_core-0.1.1-py3-none-any.whl
- Upload date:
- Size: 181.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
43f2f52976eca25e3ebd034e8379b1c25e3f48aefaa8e55c8ae76dbe4f8012e3
|
|
| MD5 |
3ab9eade87c34f1e3db53c1e171415ef
|
|
| BLAKE2b-256 |
402694df9cff59fb32c84e9666751feea5b37e27372c15bc6065db7f43274542
|
Provenance
The following attestation bundles were made for midas_core-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on w2ur/midas-core
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
midas_core-0.1.1-py3-none-any.whl -
Subject digest:
43f2f52976eca25e3ebd034e8379b1c25e3f48aefaa8e55c8ae76dbe4f8012e3 - Sigstore transparency entry: 2329106708
- Sigstore integration time:
-
Permalink:
w2ur/midas-core@9f0a65bdf582adc0997d0a362e1fedbf9a9a3bb3 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/w2ur
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@9f0a65bdf582adc0997d0a362e1fedbf9a9a3bb3 -
Trigger Event:
push
-
Statement type: