System design

Architecture.

AURA splits into a live trading service and a walk-forward parameter optimizer — sharing a codebase, never sharing an event loop or a database write path. Below: the per-cycle lifecycle, the risk gating sequence, and the optimization loop that promotes parameters into production.

  1. 01

    Data ingestion

    A coverage-aware provider chain (historical archive → multiple tail feeds → CoinGecko fallback) merges into a per-symbol Parquet cache. Freshness short-circuits and provider-lookback padding stabilize backtests without hammering live APIs.

  2. 02

    Signal generation

    Universe is regenerated daily under a momentum / SMA / drawdown screen. A composite scoring function ranks candidates; robust voting across multiple historical offsets favours stable rank over single-snapshot noise. A two-cycle regime hysteresis suppresses bull / sideways / bear flips at the boundary.

  3. 03

    Risk management

    Three concentric gates — a kill-switch on rolling drawdown, per-position stop-loss / take-profit, and a portfolio-level cost-benefit check — must clear before any capital moves. The sync guard prevents phantom kill-switch trips when a transient broker error returns an empty position set.

  4. 04

    Execution engine

    A fully synchronous, single-threaded loop builds intents from the rebalance plan, hashes each into a deterministic clientOrderId for idempotent retries, and routes them through CCXT to Binance Spot. Sell-clamp logic retries on transient balance-fetch failures rather than aborting the sell.

  5. 05

    Logging & persistence

    Every cycle writes events, intents, orders, and snapshots to embedded SQLite. Permanent diagnostic logs ([BOOT], [RISK_TICK], [SL_CHECK], [SYNC_GUARD]) surface any silent regression in journalctl alone. Stop-loss reachability is preserved across restarts via entry-price recovery from snapshots.

Diagram · 01

Per-cycle lifecycle.

Data flows left to right through the score → regime → selector → portfolio → risk-gate → execute pipeline. Every cycle ends in SQLite. The risk gate is non-bypassable.

flowchart LR A([Market data\nproviders])-->B[Provider chain\nbinance · vision · tiingo · ...] B-->C[Per-symbol\nParquet cache] C-->D{{Universe screen\nmomentum · SMA · drawdown}} D-->E[Composite score\nrobust voting] E-->F{{Regime detector\nhysteresis}} F-->G[Asset selector\noverlap + improvement gates] G-->H[Portfolio manager\ntarget weights · cost gate] H-->I{Risk gate} I-- pass -->J[CCXT order\nclientOrderId hash] I-- block -->K[Hold / defensive rotation] J-->L[(SQLite persist\norders · snapshots · events)] K-->L classDef src fill:#0a0c16,stroke:#7dd3a3,stroke-width:1.5px,color:#f1f3f8; classDef proc fill:#11131f,stroke:#9aa3b8,color:#d5d9e3; classDef gate fill:#11131f,stroke:#e6c87a,stroke-width:1.5px,color:#e6c87a; classDef exec fill:#0a2818,stroke:#7dd3a3,stroke-width:1.5px,color:#7dd3a3; classDef store fill:#181b2a,stroke:#4b536b,color:#9aa3b8; class A,B,C src; class D,E,F,G,H proc; class I gate; class J,K exec; class L store;

Diagram · 02

Risk gating sequence.

Three sequential gates between any candidate position and the exchange. A failure at any gate either liquidates (kill-switch), exits the position (SL/TP), or holds (cost-benefit).

flowchart TD P[Position open\nor candidate]-->S1{Kill-switch\nrolling drawdown ≤ limit?} S1-- no -->X1[Liquidate all positions\ncooldown] S1-- yes -->S2{Per-position SL/TP\nthreshold breached?} S2-- yes -->X2[Stop-loss /\ntake-profit exit] S2-- no -->S3{Cost-benefit\nexpected gain > trade cost?} S3-- no -->H[Hold] S3-- yes -->E[Execute order] classDef start fill:#0a0c16,stroke:#9aa3b8,color:#d5d9e3; classDef gate fill:#11131f,stroke:#e6c87a,stroke-width:1.5px,color:#e6c87a; classDef bad fill:#2a0a0a,stroke:#ef4444,color:#ef4444; classDef good fill:#0a2818,stroke:#7dd3a3,color:#7dd3a3; classDef hold fill:#181b2a,stroke:#4b536b,color:#9aa3b8; class P start; class S1,S2,S3 gate; class X1,X2 bad; class E good; class H hold;

Diagram · 03

Walk-forward optimization loop.

Four staged grids, successive-halving budgets, and a benchmark-aware promotion gate. Parameters reach the live trader only after beating both Gold and SPY across the validation slate.

flowchart LR G([Parameter grid\nselector · risk · costs])-->S1[Stage 1\nturnover knobs] S1-->H1[(Successive halving\nkeep top ⅓)] H1-->S2[Stage 2\nregime sensitivity] S2-->S3[Stage 3\nkill-switch · SL · TP] S3-->S4[Stage 4\ndefensive overlay] S4-->W[Winner per window] W-->C{Beats Gold + SPY\non validation slate?} C-- no -->R[Reject · keep prior] C-- yes -->O[Write config_overrides.json\nhot-reload into live trader] O-->L[(Live trading service\nreads next cycle)] classDef grid fill:#0a0c16,stroke:#9aa3b8,color:#d5d9e3; classDef stage fill:#11131f,stroke:#7dd3a3,color:#f1f3f8; classDef gate fill:#11131f,stroke:#e6c87a,stroke-width:1.5px,color:#e6c87a; classDef good fill:#0a2818,stroke:#7dd3a3,color:#7dd3a3; classDef bad fill:#2a0a0a,stroke:#ef4444,color:#ef4444; classDef store fill:#181b2a,stroke:#4b536b,color:#9aa3b8; class G grid; class S1,S2,S3,S4,W stage; class C gate; class R bad; class O good; class H1,L store;

Invariants

Four properties we never compromise on.

Determinism
Same inputs → same plan. No threading, no event loops, no async surprises.
Idempotency
clientOrderId hashing means a retry after an ambiguous failure cannot double-fill.
Crash safety
Boot reconciliation refreshes any broker-acknowledged order whose fill callback was lost.
Hot reload
Risk thresholds and selector weights can be tuned between cycles without a service restart.

A more detailed internal reference, including module-level wiring and incident history, lives in ARCHITECTURE.md at the repository root.