# Barter: one Rust engine, six crates, and a disabled-by-default switch

> Barter is a Rust ecosystem for algorithmic trading, split into an engine, an instrument model, a market-data streamer, an execution client and a low-level integration layer. The detail that matters most is in the example rather than the feature list: the system is built with `TradingState::Disabled`, the audit consumer is spawned, and only then is trading switched on.

**barter-rs/barter-rs** — Open-source Rust framework for building event-driven live-trading & backtesting systems

- Repository: https://github.com/barter-rs/barter-rs
- Website: https://github.com/orgs/barter-rs/repositories
- Stars: 2,328 · Forks: 383
- Language: Rust
- License: MIT
- Published: 2026-09-30 · Updated: 2026-09-30 · Language: en
- Canonical page: https://hysenlabs.com/projects/barter-rs-barter-rs

## The engine boots with trading switched off

Almost every line in the documented example is plumbing. Three of them are the reason to read it.

The first is that the system is constructed in a disabled state:

```rust
SystemBuilder::new(args)
    .engine_feed_mode(EngineFeedMode::Iterator)
    .audit_mode(AuditMode::Enabled)
    .trading_state(TradingState::Disabled)
    .build::<EngineEvent, DefaultGlobalData, DefaultInstrumentMarketData>()?
    .init_with_runtime(tokio::runtime::Handle::current())
    .await?;
```

The second is that the audit receiver is taken and a consumer task is spawned before anything else happens. And the third is that trading is switched on afterwards, as a separate, deliberate statement:

```rust
system.trading_state(TradingState::Enabled);
```

Read together they define a default. A misconfigured system, a bad instrument list, a wrong endpoint, an exchange that returns something the client did not expect: none of those start an order going, because enabling trading is an explicit act that happens after the system is already running and already observable. That is a design decision rather than a configuration option, and for a library whose purpose is to place orders with somebody's money it is the decision that matters most.

The shutdown path follows the same logic, and in the same order:

```rust
system.cancel_orders(InstrumentFilter::None);
system.close_positions(InstrumentFilter::None);
let (engine, _shutdown_audit) = system.shutdown().await?;
```

Cancel outstanding orders first, then flatten, then shut down. Not the reverse. The consequence for you as a user is that the safe sequence is the documented one and you should not improvise a shorter version on the day you are tired.

## The same engine runs against mock execution or a real exchange

The claim that Barter backtests on a near-identical system to live trading is structural rather than aspirational, and the mechanism is two traits.

`Barter-Data` streams public market data and is extensible through a `MarketStream` interface. `Barter-Execution` streams private account data and executes orders and is extensible through an `ExecutionClient` interface. The engine itself knows neither of them directly; it consumes events from whatever is behind those interfaces. So the same `SystemBuilder` code that runs against an exchange in the example runs against mock execution with live market data, which is what the example is titled.

That is a much better property than a claim that backtests are accurate. Most backtest divergence comes from the execution path: the backtest fills at the last price, the live system gets a partial fill and a rejection, and the two codebases quietly grow apart. Here there is one code path and the difference is which implementation you inject, so a divergence has to be introduced deliberately.

The limits of the claim are the limits of the interfaces. A mock execution component can only be as faithful as the author of that mock understood the exchange's semantics, and no interface can express an exchange quirk the interface designer did not know about. The order of the shutdown calls is a small illustration of the same point: the framework knows orders should be cancelled before positions are closed, which is a real convention, but it is a convention the framework chose, not one it discovered.

The backtest side is stated in one line, thousands of concurrent backtests, with utilities for running them. That is a useful number for parameter sweeps. It says nothing about how many concurrent live systems the same machine can run, and anyone reading it as a capacity figure for production will be disappointed.

## Six crates carrying six different version numbers

The ecosystem is a Cargo workspace with six members, and the split follows a sensible boundary.

`Barter` is the engine with the state management system. `Barter-Instrument` holds the exchange, instrument and asset data structures. `Barter-Data` streams public market data. `Barter-Execution` streams private account data and places orders. `Barter-Integration` provides low-level frameworks for REST and WebSocket work. `Barter-Macro` is the procedural macro crate.

Now look at the versions, because they are the interesting part. In the workspace dependency block, barter-integration is at 0.12, barter-instrument at 0.3, barter-data at 0.13, barter-execution at 0.9 and barter-macro at 0.2.1. The engine itself has releases at 0.13 and 0.14, both cut on the same day as a macro release.

Independent versioning across a library ecosystem is the right call, because the integration layer has no reason to move in lockstep with the instrument model. The cost lands in your manifest: six pins to choose, and no inference available. A Barter 0.14 upgrade does not imply a corresponding data crate, and you will read the changelog of each one you bump.

The release tooling in the tree, a `release-plz` configuration, is what makes that work in practice, since it bumps and tags each crate on its own. And `rust-toolchain.toml` plus `rustfmt.toml` pin the compiler and the formatting, which for a workspace where six crates must agree on a trait definition is not optional.

One small inconsistency to be aware of: the MIT badge in the README points at the licence file on the `develop` branch, while the default branch is `main`. Both are almost certainly the same text, and it is worth knowing which branch you are reading when you check a licence.

## Indexed state, and why a trading engine wants O(1) lookups

Two claims in the feature list are performance claims: direct index lookups, and a centralised cache-friendly state management system with constant-time lookups using indexed data structures. The example shows the API, which is one constructor:

```rust
let instruments = IndexedInstruments::new(instruments);
```

That object is then passed to both the market stream initialiser and the system arguments, which tells you it is the index everything else looks things up through.

The reasoning is straightforward if you think about what a market data handler does. A strategy receives an order book update and needs to know whether it cares about that instrument. If the answer requires a scan of every instrument in the system, then every update costs a scan, and the cost is paid on every message across every exchange connection you have open. With a direct index the same question is one lookup, and the engine's per-message work becomes proportional to the number of instruments you actually trade rather than the number you know about.

The dependency choices in the workspace are consistent with that claim, and they are worth reading as evidence rather than as boilerplate. A vector-backed map avoids a separate allocation per entry, which is what makes an indexed collection cheap to update in place. An insertion-ordered map preserves ordering where iteration order matters for reporting. An inline string type avoids a heap allocation for the short identifiers an exchange uses for symbols and sides. A fast non-cryptographic hash is there for the cases where a hash map is unavoidable. A lock rather than a standard mutex, and a decimals crate rather than floating point, which is the subject of its own section.

The asynchronous story is the same shape: Tokio is declared with an explicit feature list rather than default features, and an iterator-based engine feed mode is offered alongside a channel-based one. An iterator feed removes a channel hop from the hot path when the caller can supply events synchronously, which is exactly the sort of choice that only matters if you already decided the hot path is worth optimising.

## Decimal money, Rustls TLS, HMAC signatures

Three dependency decisions carry more weight than their size suggests.

Money is decimal. The workspace uses a decimal arithmetic crate, and the example declares a risk-free rate as a decimal literal through a macro rather than a float. For a framework that computes profit and loss, Sharpe, Sortino and drawdown, that is the difference between a reported figure that is reproducible and one that drifts with accumulation order. Floating point is not wrong for money, it is just unpredictable, and the first person to have their backtest numbers disagree with production will be the person who finds out.

TLS does not go through the system OpenSSL. Both the HTTP client and the WebSocket client are declared with rustls rather than native TLS, and the WebSocket one uses webpki roots. The operational consequence is concrete: no CA bundle to keep current inside a container image, and no class of upgrade where the container works and the production host does not. For something that is going to be deployed as a bot on a small VM, that removes a recurring chore.

Requests are signed with an HMAC and a SHA-2 implementation, encoded through hex or base64. That is the shape of exchange REST authentication, so the framework carries the primitives and leaves the per-venue details to the integration layer. Alongside them are query-string and form encoders, which is the other half of talking to exchange APIs, since most of them describe orders as form-encoded bodies.

Read together, the list says the framework is opinionated about correctness in arithmetic and about dependencies that drag in system state, and unopinionated about venue specifics, which it routes through its own low-level integration crate.

## The audit stream is how you watch a live system

A trading engine you cannot observe is a trading engine you cannot turn off. That is what the audit machinery is for, and it is more than logging.

Three features build on it. An audit mode is enabled in the example, and the engine sends audit events to a receiver the caller takes ownership of. An engine state replica manager processes that audit stream to feed non-hot-path monitoring components, which is how a user interface or a chat bot can show you the live state without being inside the engine's task graph. And engine commands can be issued from an external process to initiate actions such as closing all positions, opening orders or cancelling orders.

Alongside the commands, the same feature list notes you can turn algorithmic trading on and off from an external process while it keeps processing market and account data. That distinction is the one that matters. A bot that has to be killed to be paused is a bot you will leave running because pausing is inconvenient. A bot that accepts a disable command, keeps ingesting data and acknowledges the state change is a bot you will actually pause.

The same observability shows up after the fact. Trading summaries are generated with the metrics you would expect, profit and loss, Sharpe, Sortino and drawdown, and the example produces a daily summary as part of its shutdown path.

The limitation is the mirror image of the design. The audit stream is what the engine knows about itself, so anything your strategy does outside the engine's own command set is invisible to it. If you write a component that places orders itself rather than through the execution client, you have left the audited system, and the framing above stops applying to that part of your strategy.

## Where Barter is the wrong tool

Four cases.

If you are looking for a strategy, this is the wrong repository. The example plugs in `DefaultStrategy` and `DefaultRiskManager`, and the claim is that pluggable strategy and risk components facilitate most strategies, which means you supply the logic. A research loop in a language with an interactive notebook will find or rule out an idea far faster than a typed, compiled, event-driven one.

If you want a single library rather than an ecosystem, six crates with independent versions and six pins is more surface than a one-crate alternative. The boundaries are the right ones for a framework other people extend, and they are more than a person with one exchange integration needs.

If you need a finished product, there is no user interface, no configuration layer, no alerting and no deployment story here. The framework gives you the mechanisms to build those, which is a different deliverable.

And if you are put off by pre-1.0 versions, note that barter-instrument is at 0.3. Under the usual semantic versioning promise that means interfaces may break in any release, and a workspace where the engine is at 0.14 and the instrument model at 0.3 is one where the last crate is the least settled.

The alternative worth naming explicitly is writing the loop yourself on Tokio, which is what the dependency list shows is entirely feasible: the primitives are the same ones, and the framework is essentially a set of decisions about state, feed modes, audit and shutdown order. What you gain by using Barter instead is those decisions already made and documented, including the ones about starting disabled and cancelling before flattening. What you lose is the ability to change them without forking.

## Conclusion

Adopt Barter if you have a strategy that needs the same code path in backtest and live, and follow the documented shutdown order, cancelling orders before closing positions and only then calling `system.shutdown()`. Do not adopt it to search for an edge, because the documented example plugs in `DefaultStrategy` and `DefaultRiskManager` and you bring the logic, so a research loop elsewhere will be faster. Pin each of the six crates separately, since they version independently and barter-instrument sits at 0.3 while barter is at 0.14, and read the audit stream section before you connect an exchange, because that is what lets you observe and stop a live system from outside it.

## FAQ

### What is the Barter Rust framework?

Barter is an algorithmic trading ecosystem of Rust libraries for building live-trading, paper-trading and back-testing systems. It is split into the Barter engine, Barter-Instrument for exchange, instrument and asset types, Barter-Data for public market data, Barter-Execution for private account data and order execution, and Barter-Integration for low-level REST and WebSocket work.

### How does Barter backtest on the same system it trades live?

Public market data arrives through a MarketStream interface and private account data and orders go through an ExecutionClient interface, and the engine is built with the same SystemBuilder either way. Swapping a mock execution component for a live one is what turns the documented paper-trading example into a live system, so there is one code path rather than two.

### Does Barter start trading as soon as it connects?

No. The documented example builds the system with TradingState::Disabled, initialises it, spawns a consumer for the engine audit stream, and only then sets the trading state to enabled. On shutdown it cancels orders before closing positions and then calls shutdown.

### Which exchanges or venues does Barter support?

The README does not enumerate them. Extensibility comes from the MarketStream interface for public data and the ExecutionClient interface for private data and orders, with Barter-Integration providing the low-level REST and WebSocket frameworks. The example initialises a multi-exchange market stream requesting public trades and level-one order books.

### What does the Barter workspace depend on?

Tokio for asynchronous I/O with an explicit feature list, reqwest and tokio-tungstenite with rustls rather than system OpenSSL, a decimal crate for monetary values, a vector-backed map and ordered map plus an inline string type for the state structures, and HMAC with SHA-2 for signing exchange requests.

### Can I monitor or stop a running Barter system from outside it?

Yes. An engine state replica manager processes the engine audit stream to feed non-hot-path monitoring components, and engine commands can be issued from an external process to close all positions, open orders or cancel orders. Trading can also be switched on and off from an external process while the system keeps processing market and account data.

## Sources

- [barter-rs/barter-rs on GitHub](https://github.com/barter-rs/barter-rs)
- [License: MIT](https://github.com/barter-rs/barter-rs/blob/main/LICENSE)
- [Project website](https://github.com/orgs/barter-rs/repositories)
- [README](https://github.com/barter-rs/barter-rs/blob/main/README.md)
- [Releases](https://github.com/barter-rs/barter-rs/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/barter-rs-barter-rs
