Platform Architecture¶
TL;DR
Aurum splits a trading application into four product surfaces — Strategy, CLI, Forge, and Terminal — connected by two protocols (SSH for deployment, REST/WebSocket for operations). Each surface can fail independently. This page explains why the boundaries exist, how data flows between them, and what happens when a boundary fails.
Why this architecture?¶
Before Aurum, a trading system was typically built as a single process that did everything: connected to exchanges, ran strategy logic, recorded results, and served a dashboard. This worked until it didn’t. When the dashboard crashed, it took the strategy with it. When the exchange connection flapped, there was no record of what happened. When you wanted to deploy a new version, you had to stop everything.
Tip
If you’re new to Aurum, read this page once to build your mental model, then return to it when you’re debugging a production issue. The failure-boundaries section at the bottom is the most practically useful part.
Aurum separates these concerns into four surfaces with explicit failure contracts. The guiding principle is: a process can fail without taking its neighbors with it, and every failure leaves evidence.
Follow the data¶
A BTC/USDT one-minute bar arrives at the OKX WebSocket. Here is the journey it takes through Aurum:
Exchange WebSocket
|
v
venue::okx adapter ← decodes exchange protocol into Aurum schema
|
v
Engine MessageBus ← orders events by logical time
|
+--> on(TOPIC<"bar://okx/BTCUSDT@1m">) ← your strategy handler
|
+--> Runtime WebSocket ← live data for Terminal and API clients
|
+--> Kronos journal drain ← writes to ClickHouse for durable history
|
v
ClickHouse
|
v
Forge history API ← Terminal queries history through Forge
Each arrow in this diagram is a boundary. At each boundary, Aurum makes a deliberate choice about what can fail and how that failure is surfaced.
The four surfaces¶
.at definitions --Athanor--> generated C++
|
venue packages --> C++23 strategy + SDK --> aurum CLI
| |
local runtime +--SSH--> Forge
|
Terminal <-- REST / WebSocket
Strategy and SDK¶
A strategy is your executable. It derives from sdk::Application, binds
venue or paper devices, reacts to typed topics, and submits typed effects. The
SDK supplies the runtime, scheduling, resource catalog, query model, telemetry,
and live WebSocket surface inside that process.
Note
A strategy is an active program that declares its own event sources and effects. It is not a collection of framework-owned callbacks. Aurum calls your handlers when data arrives; your code owns the control flow.
The SDK is designed around a core insight: trading strategies are event-driven, not request-driven. Market data arrives on the exchange’s schedule, not yours. An order fill can arrive seconds after submission. A timer fires at a configured interval. The SDK’s job is to order these events correctly and deliver them to your handlers with minimal overhead between the network and your logic.
Athanor is optional and runs before the C++ compile step. Its generated factor plugins execute inside the same strategy process as handwritten code — there is no Athanor service in production.
CLI and Forge¶
The aurum CLI has two independent roles:
Local development: it creates, compiles, and runs an installed SDK project on your machine.
Remote delivery: it delivers immutable project versions to a named Forge target over SSH and invokes lifecycle operations there.
These roles share a binary but not a network path. A local aurum run . does
not touch SSH. A remote aurum push does not need a local compiler.
Tip
The CLI and Terminal use different Forge addresses. The CLI uses an SSH
target (user@host:port). Terminal uses an HTTPS URL
(https://host/api/v1). They are separate by design: deployment access
and browser access should use different authentication and network policies.
Forge is the public server boundary. It has three internal data planes:
Plane |
Interface |
Purpose |
|---|---|---|
Control |
REST instance endpoints |
Inventory, health, lifecycle, and logs. |
Live |
Runtime REST and WebSocket |
Capabilities, resource discovery, current values, and subscriptions. |
History |
Forge history REST |
Run-scoped summaries and durable resource rows. |
These are related signals, not one shared state flag. Forge health can be
good while aurumd is unreachable; live subscriptions can work while history
is unavailable. The product surfaces expose these distinctions so clients do
not invent system state from one convenient signal.
Note
Forge uses aurumd for inventory and lifecycle, the runtime process for
live data, and ClickHouse for durable history. Terminal — and any external
integration — connects only to the Forge API. It does not connect directly to
aurumd, a runtime process, or ClickHouse.
Terminal¶
Terminal is a browser API client. It receives one public Forge API base URL and derives runtime WebSocket URLs from the same address. It does not need the CLI’s SSH target, a daemon socket, a strategy port, or a database address.
This separation enables a critical operational pattern: deployment access, browser access, and internal service networking can use different policies. A developer pushing code over SSH does not need the same network path as an operator viewing a dashboard over HTTPS.
Terminal has three views for each project, each answering a different question:
Question |
View |
Primary source |
|---|---|---|
How did the strategy perform? |
Summary |
Historical aggregation |
What is the runtime publishing now? |
Inspect |
Catalog + WebSocket |
Why is data missing or an order stuck? |
Debug |
Runtime queries, events, logs |
Failure boundaries¶
This is the most important section on this page. Each surface boundary can fail independently. Understanding these failure modes is the difference between diagnosing an incident in 30 seconds and 30 minutes.
Failure |
What it means |
|---|---|
Forge API is healthy, |
The API process is running but the control-plane daemon is down. Lifecycle commands will fail; inventory may be stale; running strategies are unaffected. |
Process is running, runtime endpoint is degraded |
The OS process is alive but the Engine WebSocket is not accepting connections. Live data and queries are unavailable; logs and process state are still reachable. |
Live subscriptions work, history is unavailable |
The runtime is publishing; ClickHouse or the history query path is failing. Terminal charts show live data but no backfill. |
Project is stopped, historical runs remain |
A stopped project retains all previous |
One capability is absent, the rest of the project is healthy |
For example, a factor server has no ledger. This is the correct
contract, not a failure. Terminal shows |
Warning
Do not treat one signal as proof of another. A running process does not
imply a healthy runtime. A successful /health response does not imply
aurumd is reachable. A capability set to false can be the correct
contract for that project — inspect capabilityErrors before treating it
as a failure.
Operator interfaces expose these distinctions so clients do not invent state from one convenient signal. When debugging, start at the boundary closest to the symptom and work inward:
1. Terminal shows no data → check Forge /health and /doctor
2. Forge API is healthy → check instance status
3. Instance process is running → check runtime endpoint
4. Runtime is available → check the specific capability
5. Capability is true → check the specific resource key
Design principles¶
These principles explain why the architecture looks this way. They are not rules to memorize — they are the reasoning behind decisions you will encounter throughout the documentation.
Compile-time over runtime. Push errors to build time wherever possible. Topic keys are validated at compile time. Schema mismatches are compile-time errors. The cost of a build failure is paid once by the developer; the cost of a runtime data error is paid by every operator who touches the system.
Separate signals, don’t synthesize state. Each data plane reports its own truth. The API does not guess whether a project is “healthy” — it reports process state, runtime state, and capability flags separately. Clients combine them according to their own operational model.
Immutable releases. A published name@version is permanently registered
on Forge. You cannot overwrite it. Increment the version and push a new
release. This means every deployment is auditable and every version is
rollback-capable.
The narrowest interface that works. Forge exposes one public API. Terminal
uses it. External integrations use it. No one connects directly to aurumd,
a runtime port, or ClickHouse. This keeps the integration surface small enough
to version, secure, and test.
What Aurum is not¶
Aurum is not an exchange gateway SDK. It does not normalize every venue into one abstraction — it adapts venue protocols into Aurum schemas and gives you the typed result.
Aurum is not a backtesting framework with a built-in data feed. It replays your own recorded data through your own strategy process. There is no shared historical market database.
Aurum is not a low-code platform. Strategies are C++23. Factors are Athanor
.at definitions that compile to C++. The abstractions are in the SDK, not
in a configuration language.
Next steps¶
To understand how strategies process events, read Reactive Application Model.
To understand resources and the catalog, read Resources, Live Data, and History.
To understand the design rationale, read Design Philosophy.
To build your first strategy, start with Quickstart.