Athanor Quickstart

TL;DR

Define factors in a .at file, compile them to C++ with the Athanor CLI, wire them into a strategy, and read computed values through ref(). Athanor is a build-time compiler — no Athanor service runs in production.

Why a DSL for factors?

You could compute a spread factor in C++:

on(TOPIC<"bar://okx/BTCUSDT@1m">, [](const Tick<schema::BarRow>& tick) {
  double spread = tick.payload.close - tick.payload.open;
  // Store it somewhere, manage the window yourself...
});

This works for one factor. For ten factors with rolling windows, lagged inputs, and shared subexpressions, the manual C++ becomes verbose, error-prone, and hard to optimize.

Athanor lets you declare factors in a domain-specific language, then compiles them into optimized C++ that shares subexpressions, manages windows, and publishes typed factor:// resources automatically.

Note

Athanor is optional. If you prefer handwritten C++ for all your factor logic, Aurum works fine without it. Athanor exists for strategies where the number and complexity of factors makes manual implementation unwieldy.

Define two factors

Create factors.at:

factor spread :=
    Bar.close - Bar.open

factor spread_mean := ~window:20
    MEAN spread

Compile the definitions with the Athanor CLI included in the release:

mkdir -p generated
athanor compile --output generated/factors.hpp factors.at

The output is a C++ header under namespace aurum::alpha. Regenerate it after changing any input definition.

Tip

Treat the generated header as an artifact: review compiler diagnostics, but do not edit it by hand. Your next athanor compile will overwrite any manual changes.

Attach the factors

#include "generated/factors.hpp"
#include <aurum/sdk.hpp>
#include <aurum/venue/okx.hpp>

using namespace aurum;
using aurum::alpha::spread;
using aurum::alpha::spread_mean;

class FactorWatch final : public sdk::Application {
public:
  FactorWatch() : sdk::Application({.mode = sdk::RuntimeMode::LIVE}) {
    bind<Datafeed>(venue::okx::Bar("BTCUSDT", "1m"));

    (void)wire<"bar://okx/BTCUSDT@1m", spread, spread_mean>();

    on(TOPIC<"bar://okx/BTCUSDT@1m">, [](const auto&) {
      auto value =
          ref(TOPIC<"factor://spread_mean/btcusdt.okx@bar:1m">);
      if (value) log::info("spread mean={}", *value);
    });
  }
};

AURUM_EXPORT(FactorWatch)

Run the project with aurum run .. The factor resource becomes visible in the runtime catalog after the source is bound.

Warning

Windowed factors are unavailable until they have enough input. A 20-bar mean factor will return empty from ref() for the first 19 bars. Always check the optional before using the value — an empty optional is normal during warmup, not an error.

Continue with Factors in Strategies for window and lifecycle behavior.