A Basic Algo Trading System In Rust: Part III

Paul Folbrecht
Rustaceans
Published in
4 min readJun 30, 2024

This part is dedicated to certain Rust-specific ancillaries I’ve ignored thus far in the interests of space.

We’ll cover the following:

  • Config
  • Tests
  • Serialization
  • MongoDB
  • Dependency Injection (Revisited)

Config

The config format is still pretty simple; here is an example:

What impressed me out of the gate about Rust’s config crate was the for-free overriding ability. With no effort, one can define a base config that will be overridden — in whole or in part — with another (specific to environment, etc.)

The code:

You can see that I’ve specified “config/default” as the base config, with “config/local” as the optional override (which is where my Tradier tokens live).

The only other interesting aspect of this code is that I chose to deserialize into a temporary set of structs to do the work of translating the raw config artifacts — a pair of symbols and capital arrays per strategy — into the nicer format of a map.

For this purpose I again made use of From implementations to turn the transient forms into the exposed interface.

(Is it possible to do such a translation directly in the deserialization with the library? Perhaps; I didn’t explore that, as this method is simple and straightforward.)

Tests

I’ve found unit testing in Rust to be a joy thus far.

Perhaps you are familiar with one of Erlang creator Joe Armstrong’s comments regarding object-oriented programming:

Because the problem with object-oriented languages is they’ve got all this implicit environment that they carry around with them. You wanted a banana but what you got was a gorilla holding the banana and the entire jungle.

I have seen many an OO system’s test harnesses fall into this trap, also known as the Uninvited Guest anti-pattern. It arises from the way OO programs tend to be constructed, which is a direct consequence of the fact that OO purposefully entangles the orthogonal concerns of data and functionality. Before you know it, you are feeling this:

Refactorings become painful: A change to a harness for the benefit of one test breaks others, etc.

It’s worth pointing out that this generally just isn’t a concern with the way you’ll construct tests for idiomatic Rust code.

In addition to that, I love how everything regarding unit (& integration) testing is so simple and straightforward, how everything just works with no wasted effort, and how fast it is — pretty much like everything Rust.

Here are a couple examples:

We might expect domain-level unit tests to be simple (no/few dependencies); here’s a service test suite which includes mocking service dependencies:

Serialization

I needed to write several custom serde codecs, for either or both HTTP/BSON serialization. Here is one of them:

Note that serde doesn’t use reflection or anything with a runtime penalty: It’s done using traits and macros, entirely at compile-time, and is fast.

Here is an example of usage:

Note that the parameter to the serde macro is actually the module name.

It’s also worth mentioning that core, where these codecs are housed, is a library crate, and can be published and used standalone. This is a consequence of the project’s use of the workspace pattern.

Mongo

The persistence layer’s code has been covered.

To use the project locally, one must also have a MongoDB instance running at the URL pointed to by the mongo_url config property.

Mongo is very simple to install and run locally. After doing so, the shell can be used to easily manage and query the database:

(Eventually, the project will be containerized and runnable on AWS, with the mongo instance running in its own container.)

Dependency Injection (Revisited)

I covered my reasoning about and path for DI in Part I. Now that the system has achieved a non-trivial level of functionality, let’s take a look at what constructing and wiring services & their dependencies looks like in practice:

It feels a bit archaic to have to hand-code this type of thing, which a dependency-injection library would do for us. On the other than, the manual rather than declarative style makes for complete transparency.

Ok, that’s it for the boring stuff. Next, backtesting.

--

--