Skip to main content

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.

tests PyPI License: MIT Python 3.12+

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:

  1. roster.yaml — declare your traders, a narrator, and an optional allocator, each with its universe, starting capital, and broker-enforced safety rails (max_order_notional, max_orders_per_day, daily_drawdown_halt_pct, allowed_universe).
  2. .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.yaml loader), 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

midas_core-0.1.1.tar.gz (291.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

midas_core-0.1.1-py3-none-any.whl (181.0 kB view details)

Uploaded Python 3

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

Hashes for midas_core-0.1.1.tar.gz
Algorithm Hash digest
SHA256 1cf3183e381984ccc98b2564eec49bf899067f46a5799ab5cc4d8c7380d3f90b
MD5 1e6d5f56e3f27160ae338155ac1fe39a
BLAKE2b-256 d2405d542727154c18b197e9f6dbcf393271fbafa04376c006434c439c95b589

See more details on using hashes here.

Provenance

The following attestation bundles were made for midas_core-0.1.1.tar.gz:

Publisher: publish.yml on w2ur/midas-core

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for midas_core-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 43f2f52976eca25e3ebd034e8379b1c25e3f48aefaa8e55c8ae76dbe4f8012e3
MD5 3ab9eade87c34f1e3db53c1e171415ef
BLAKE2b-256 402694df9cff59fb32c84e9666751feea5b37e27372c15bc6065db7f43274542

See more details on using hashes here.

Provenance

The following attestation bundles were made for midas_core-0.1.1-py3-none-any.whl:

Publisher: publish.yml on w2ur/midas-core

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page