Algo Framework

This article is meant to give you a quick overview of the new algo framework.

Note

A template project exists on GitHub demonstrating how to get started when building your own strategies. Everything shown in this article leans heavily on that template project.

The algo interfaces are almost similar to the lower level client framework and you should be able to upgrade existing strategies with little effort. Please reach out if you have trouble with this upgrade and/or need our help.

Important

This is an early version and we expect interfaces and implementations to change as we work through various strategies. Client feedback is also likely to result in changes. We will try to keep this article up to date with the most recent version of the algo framework.

Design

../../../_images/design.png

Strategy

An algo strategy implements event handlers to manage internal state and optionally send order actions to connected gateways.

A minimal implementation would look something like this:

struct MyStrategy final : public algo::Strategy {
  MyStrategy(
      algo::Strategy::Dispatcher &dispatcher,
      algo::OrderCache &order_cache,
      algo::strategy::Config const &)
      : dispatcher_{dispatcher}, order_cache_{order_cache} {
    // TODO use strategy config to create internal state management
    // TODO add additional constructor arguments to configure model parameters
  }

 protected:
  void operator()(Event<Connected> const &) override {}
  void operator()(Event<Disconnected> const &) override {}
  void operator()(Event<Ready> const &) override {}

  // TODO other event handlers...

  // note! event handlers for order management now include cached order state
  void operator()(Event<OrderAck> const &, cache::Order const &) override {}
  void operator()(Event<OrderUpdate> const &, cache::Order const &) override {}
  void operator()(Event<TradeUpdate> const &, cache::Order const &) override {}

 private:
  algo::Strategy::Dispatcher &dispatcher_;  // used to send order actions
  algo::OrderCache &order_cache_;           // used to fetch most recent order state
};

References

Config (Strategy)

The strategy config is a list of legs, each defining an instrument and (optional) parameters for order management.

A .toml representation could look something like this

[[legs]]
source=0
account="A1"
exchange="deribit"
symbol="BTC-PERPETUAL"
time_in_force="gtc"

[[legs]]
source=1
account="A1"
exchange="bybit"
symbol="BTCUSD"
time_in_force="gtc"

You can read a .toml file like this

auto strategy_config = algo::strategy::Config::parse_file(path);

References

Trader

You will have to implement a factory class allowing the trader application to mange the life-time of the strategy

struct MyFactory final : public client::Trader2::Factory {
  std::unique_ptr<algo::Strategy> create_strategy(
      algo::Strategy::Dispatcher &dispatcher,
      algo::OrderCache &order_cache,
      algo::strategy::Config const &config) const override {
    // note! this is where you can pass e.g. model parameters to your strategy
    return std::make_unique<MyStrategy>(dispatcher, order_cache, strategy_config);
  }

The trader application is then executed like this

MyFactory factory;

// note! settings are typically derived from command-line flags (see the template project for an example)
// note! params will typically be the list of command-line arguments (.sock paths)

client::Trader2::dispatch(settings, factory, config, params);

References

Matcher

The matcher interface is used to when simulating order matching.

Note

The simulator will take care of offsetting time and the queueing of events to simulate latency between strategy and exchange.

Although you can, you don’t have to implement your own matcher: the roq-algo project comes with some simple implementations

// typically the simulator factory method
auto create_matcher(auto &dispatcher, auto &order_cache, auto &config) {
  auto type = algo::matcher::Type::SIMPLE;  // note! very conservative assumptions
  return algo::matcher::Factory::create(type, dispatcher, order_cache, config);
}

References

Reporter

The reporter interfaces are used when implementing a specific reporting style.

Note

The reporter will see all events entering a strategy as well as all actions leaving the strategy. A specific reporter has full control over what has to be collected and what kind of derived statistics should be computed.

Although you can, you don’t have to implement your own reporter: the roq-algo project comes with some simple implementations

// typically the simulator factory method
auto create_repoter() {
  auto type = algo::reporter::Type::SUMMARY;  // note! sampled statistics + order/fill history
  return algo::reporter::Factory::create(type);
}

References

Config (Simulator)

The simulator config defines latencies by source and optionally the initial positions.

A .toml representation could look something like this

[[sources]]
market_data_latency_ms=5
order_management_latency_ms=10

[[sources.accounts.A1]]
exchange="deribit"
symbol="BTC-PERPETUAL"
long_position=3

[[sources]]
market_data_latency_ms=175
order_management_latency_ms=180

[[sources.accounts.A1]]
exchange="bybit"
symbol="BTCUSD"
short_position=3

You can read a .toml file like this

auto simulator_config = algo::simulator::Config::parse_file(path);

References

Simulator

Note

The simulator factory derives from the trader factory. You can

struct MyFactory final : public client::Simulator::Factory {
  std::unique_ptr<algo::Strategy> create_strategy(
      algo::Strategy::Dispatcher &dispatcher,
      algo::OrderCache &order_cache,
      algo::strategy::Config const &config) const override {
    // note! this is where you can pass e.g. model parameters to your strategy
    return std::make_unique<MyStrategy>(dispatcher, order_cache, config);
  }

  std::unique_ptr<algo::Matcher> create_matcher(
    algo::Matcher::Dispatcher &dispatcher, algo::OrderCache &order_cache, algo::matcher::Config const &config) const {
      auto type = algo::matcher::Type::SIMPLE;  // note! simple and conservative matching
      return algo::matcher::Factory::create(type, dispatcher, order_cache, config);
    }

    std::unique_ptr<algo::Reporter> Factory::create_reporter() const {
      auto market_data_source = algo::MarketDataSource::MARKET_BY_PRICE;  // note! we may need impact price for profit/loss calculations
      auto sample_frequency = 10s;
      auto config = algo::reporter::Summary::Config{
        .market_data_source = market_data_source,
        .sample_frequency = sample_frequency,
      };
      return algo::reporter::Summary::create(config);
    }

A simulator application is then executed like this

MyFactory factory;

// note! settings are typically derived from command-line flags (see the template project for an example)
// note! params is this list of command-line arguments, typically .sock paths

auto reporter = client::Simulator::dispatch(settings, factory, strategy_config, simulator_config, params);

References

Arbitrage (Example)

An arbitrage strategy (named Simple) was implemented to demo certain features of a real strategy. You could use this as the basis for starting your own development.

Important

We don’t recommend to use this strategy in production.

References

  • Arbitrage strategy on GitHub

Python

Binding exists allowing you to simulate strategies from Python. This is particularly useful when experimenting with model parameters where one can leverage the powerful data analysis tools from the Python eco-system.

One could also imagine using Python’s multi-processing when optimizing model parameters.

A Jupyter notebook was implemented to demo these capabilities.

References