Resolves the #181 design pass (option c: ledger declaration, no lockstep mechanism). The aura-new templates (scaffold.rs) are the canonical authoring shape and evolve with the engine; the cycle-0102 demo-project fixture is an intentionally frozen known-good twin for the load-boundary tests. Deliberately NOT lockstep-guarded: no consumer requires the copies to match (skeptic-verified — nothing treats the fixture as what aura new emits), each is e2e-guarded on its fitness for purpose (build/load/ charter/deterministic run; blueprint wiring is pinned in neither, stated honestly after an adversarial skeptic pass refuted the stronger claim), and an equality guard would convert deliberate template improvements into forced churn of the frozen fixture — the same cross-purpose coupling that rules out regenerating the fixture from the scaffolder. closes #181, refs #180
aura
aura is a "game engine for traders." It is a Rust framework — and a playground — for authoring trading strategies as composable dataflow nodes, backtesting them deterministically and massively in parallel, validating them (sweep / Monte-Carlo / walk-forward), and freezing a validated strategy into a standalone bot with a broker connection. Its differentiator is the World: a program that constructs and orchestrates whole families of backtests, not the single run.
This README is a discoverability map of the shipped aura CLI — what commands
exist and what they do. It deliberately keeps per-flag detail light; the two
authoritative sources for exact syntax are always:
aura <command> --help— the exhaustive flag grammar.- The design ledger (
docs/design/INDEX.md) and glossary (docs/glossary.md) — the concepts and invariants behind the surface.
Build & run
aura is a Cargo workspace. Build everything and you get one binary named aura:
cargo build --workspace # binary at target/debug/aura
cargo build --workspace --release # optimized, at target/release/aura
Invoke it as aura <command> … (examples below use the plain name).
Concepts in one breath
- Blueprint — a serialized signal graph as data: a param-generic
price → biasnode graph. A blueprint file (blueprint.json) is the unit a downstream consumer loads and drives verbs over. - Open vs. closed — a blueprint with unbound (free) numeric knobs is open; one with every knob bound is closed. Different verbs want different states (see each verb below).
- Family — the set of runs one verb produces over a blueprint (a sweep grid, a Monte-Carlo seed set, a walk-forward window sequence). Families are persisted in a content-addressed store and can be listed, ranked, and reproduced.
- Axis — one named, sweepable knob of a blueprint (e.g.
graph.fast.length), bound with a comma-separated value list.
Running & orchestrating a loaded blueprint
These verbs all take the blueprint file as their first positional argument
and drive a family of runs over it. (Each verb also has a legacy built-in form
selected by --strategy/--harness instead of a .json path, for the
engine's own bundled example strategies — see --help.)
| Command | Purpose |
|---|---|
aura run <bp.json> |
Run one backtest of a closed blueprint and print its report. An open (free-knob) blueprint is refused with a clean error — bind it, or use sweep. |
aura sweep <bp.json> --list-axes |
Discover the blueprint's open, sweepable knobs. Prints each as <name>:<kind>. Run this first to learn the axis names. |
aura sweep <bp.json> --axis <name>=<csv> [--axis …] |
Run a grid family over the named axes and persist it. |
aura mc <bp.json> --seeds <n> |
Run a Monte-Carlo family of n seeded realizations. Wants a closed blueprint (the inverse of sweep). |
aura walkforward <bp.json> --axis <name>=<csv> [--axis …] |
Run an in-sample-refit walk-forward family — rolling optimize + out-of-sample test across windows, stitched into one verdict + parameter stability. --select chooses the per-window objective. |
aura runs families |
List every persisted family (id, kind, member count). |
aura runs family <id> [rank <metric>] |
List one family's members, optionally ranked best-first by an R metric (e.g. sqn_normalized, expectancy_r). |
aura reproduce <family-id> |
Re-derive every member of a persisted family from the content-addressed store and check it is bit-identical (the C18/C1 determinism guarantee). |
Important contract — every open knob is mandatory. On sweep and
walkforward, the knobs enumerated by --list-axes are all required: you must
supply an --axis for each open knob. There is no default value — pin a knob you
don't want to vary with a single-value axis (--axis name=4). Omitting one is a
clean error naming the missing knob, not a silent default.
Use aura <cmd> --help for the full data-window (--real/--from/--to),
naming (--name/--trace), and selection (--select) flags.
Authoring & introspecting topology
A blueprint is authored declaratively from a JSON op-list: an ordered list of by-identifier construction ops replayed against the standard node vocabulary.
| Command | Purpose |
|---|---|
aura graph build |
Read an op-list document on stdin, build the blueprint, and print its canonical JSON to stdout (no trailing newline — it is the content-addressed artifact). |
aura graph introspect --vocabulary |
List every node type available to op-lists. |
aura graph introspect --node <T> |
Show one type's input ports, output fields, and param paths (with the bind-value form). |
aura graph introspect --unwired |
Read a partial op-list on stdin and list its still-open interior slots. |
aura graph introspect --content-id |
Read an op-list on stdin and print the SHA-256 content id of the blueprint it would build. |
aura graph introspect --identity-id |
Read an op-list on stdin and print the SHA-256 identity id — the topology's debug-name-blind id, so two documents (or an op-script and a Rust-built blueprint) expressing the same topology print the same id. Combinable with --content-id (both ids, one per line, content id first). |
Op-list shape
Each element is one op, tagged by "op". Node params are bound with the typed
Scalar form — a tagged, capitalized-kind object: {"I64": 5}, {"F64": 1.5}, {"Bool": true}. A worked example (an SMA-crossover → bias signal):
[
{"op":"source","role":"price","kind":"F64"},
{"op":"add","type":"SMA","name":"fast","bind":{"length":{"I64":2}}},
{"op":"add","type":"SMA","name":"slow","bind":{"length":{"I64":4}}},
{"op":"add","type":"Sub"},
{"op":"add","type":"Bias"},
{"op":"feed","role":"price","into":["fast.series","slow.series"]},
{"op":"connect","from":"fast.value","to":"sub.lhs"},
{"op":"connect","from":"slow.value","to":"sub.rhs"},
{"op":"connect","from":"sub.value","to":"bias.signal"},
{"op":"expose","from":"bias.bias","as":"bias"}
]
Piping this document into aura graph build emits a blueprint.json you can
then feed to run / sweep / mc / walkforward above:
aura graph build < crossover.ops.json > crossover.bp.json
aura sweep crossover.bp.json --list-axes
The op kinds are source, input, add, feed, connect, and expose. See
aura graph introspect --node <T> for a type's exact ports and the op-script
grammar in the design ledger for the full semantics.
Where to go deeper
aura <command> --help— exhaustive, always-current flag grammar.docs/design/INDEX.md— the design ledger: architecture, invariants, the World.docs/glossary.md— canonical terminology (blueprint, family, axis, sweep / Monte-Carlo / walk-forward, R, content-id, …).