Basic#

A strategy is the implementation of an asynchronous interface

The strategy receives update events and can decide to issue order management requests to the framework.

Note

Requests will never block!

The strategy should maintain state so it knows what to do when it later receives events relating to an order management request.

The framework can be live trading or simulation.

It is the responsibility of the framework to

Important

The strategy is not aware if it is being used for live trading or simulation. This makes it much easier to reason about backtest results: the heavy lifting must to be done by the simulator, it must appear to the strategy as if it was a real gateway connected to a real market.

The following only shows code snippets. You should consult the code on GitHub for more complete samples.

Handler#

The strategy must implement the roq::client::Handler interface

#include "roq/client.hpp"

class Strategy : public roq::client::Handler {
  // ...
};

Only required methods have to be implemented.

For example, if you only need roq::ReferenceData and roq::TopOfBook

#include "roq/client.hpp"

class Strategy : public roq::client::Handler {
 public:
  // ...
  void operator()(const roq::Event<roq::ReferenceData>&) override;
  void operator()(const roq::Event<roq::TopOfBook>&) override;
};

Note

All message types are wrapped using the roq::Event template. The purpose is to uniquely associate a raw message with its origin and history.

This example shows how updates can be very efficiently routed based on the source index (an integer). The assumption here is that the strategy must update the two instruments and then compare current state, possibly to benefit from arbitrage.

template <typename T>
void dispatch(const Event<T>& event) {
  auto source = event.message_info.source;
  switch (source) {
    case 0: {
      _instrument_0(event);  // update first instrument
      break;
    }
    case 1: {
      _instrument_1(event);  // update second instrument
      break;
    }
    default:
      assert(false);  // unexpected
  }
  update();  // review current state and possibly initiate arbitrage
}

void Strategy::operator()(const roq::Event<roq::TopOfBook>& event) {
  dispatch(event);
}

Constructor#

The strategy constructor must accept an roq::client::Dispatcher as the first argument.

class Strategy : public roq::client::Handler {
 public:
  Strategy(roq::client::Dispatcher& dispatcher)
      : _dispatcher(dispatcher) {
    // note!
    //   the dispatcher will throw an exception if it is being used
    //   at construction time.
  }
  // ...

 private:
  roq::client::Dispatcher& _dispatcher;
};

Important

The dispatcher interface should never be used at construction time.

The constructor may accept further arguments

class Strategy : public roq::client::Handler {
 public:
  Strategy(
      roq::client::Dispatcher& dispatcher,  // always first
      const std::string_view& some_parameter,
      double another_parameter);
  // ...
};

Dispatcher#

The dispatcher interface is used to bridge requests to the framework

In this example we create an order to sell 0.1 units at the price of 11500. The order should be sent as a limit order and execution must be immediate or else the order should be cancelled (IOC). The order will be identified by the result of calling next_order_id().

void Strategy::foo() {
  roq::CreateOrder create_order {
    .account = "some_account",
    .order_id = next_order_id(),
    .exchange = "some_exchange",
    .symbol = "some_symbol",
    .side = roq::Side::SELL,
    .quantity = 0.1,
    .order_type = roq::OrderType::LIMIT,
    .price = 11500.0,
    .time_in_force = roq::TimeInForce::IOC,
    .position_effect = roq::PositionEffect::UNDEFINED,
    .execution_instruction = roq::ExecutionInstruction::CANCEL_IF_NOT_BEST,
    .stop_price = std::numeric_limits<double>::quiet_NaN(),
    .max_show_quantity = std::numeric_limits<double>::quiet_NaN(),
    .order_template = {},
  };
  uint8_t source = 0;  // connection index
  try {
    _dispatcher.send(
        create_order,
        source);
  } catch (roq::Exception&) {
    // something failed
  }
};

Important

The order_id is local to the strategy and must subsequently be used by the strategy to refer to the order. For example, when sending a cancellation request. Each gateway will inform the strategy of previously seen order_id’s. This is notified via the roq::DownloadEnd event type. This topic will be discussed further in the advanced section.

Important

The dispatcher requires you to provide a source argument. This is a connection index used to efficiently route the request to the relevant gateway. The use of an index is purely based on performance considerations: the alternative would be to identify the gateway by some string representation thereby requiring string matching and lookup.

Important

Although the dispatch interface is asynchronous, it is possible that the framework can throw an exception. This may happen if the framework immediately knows that a request can not be forwarded. For example, if a connection has not yet been established.

Note

You can safely assume the order request was never sent, if you do catch an exception.

The example just shown will normally return immediately and the strategy will then later receive updates relating to the order.

The first update event will likely be roq::OrderAck

void Strategy::operator()(const roq::Event<roq::OrderAck>& event) {
  const auto& order_ack = static_cast<const roq::OrderAck&>(event);
  if (order_ack.error == roq::Error::UNDEFINED) {
    // no error detected
  } else {
    // something is not right
  }
}

The first ack should originate from the gateway: the order request was either accepted (and forwarded) or rejected (with an error code).

Note

An ack may never arrive if the gateway was disconnected right after the order request was sent. In this case you should instead receive a roq::Connection event indicating the disconnect.

The second ack may originate from the exchange.

Important

There are no guarantees that the exchange will ack before it sends any other updates relating to the order. In fact, there are no guarantees that the strategy will even receive an ack from the exchange (message could be lost, etc.).

The possibly third update could be roq::OrderUpdate. Here we use the roq::is_order_complete() function to check if the order is working on the market or not.

void Strategy::operator()(const roq::Event<roq::OrderUpdate>& event) {
  const auto& order_update = static_cast<const roq::OrderUpdate&>(event);
  if (roq::is_order_complete(order_update)) {
    // order could be completed, cancelled, rejected, etc.
  } else {
    // order is working on the market
  }
}

Important

Again: no guarantees that an order update will be received.

Other relevant update events are roq::TradeUpdate and roq::PositionUpdate

void Strategy::operator()(const roq::Event<roq::TradeUpdate>& event) {
  // ...
}
void Strategy::operator()(const roq::Event<roq::PositionUpdate>& event) {
  // ...
}

Configuration#

The framework does not allow dynamic subscriptions. All symbols and accounts must therefore be specified up-front.

The configuration interface doesn’t impose any constraints on how you manage these subsciptions: the framework will ask for you to dispatch the subscriptions, when the information is required.

First, you should implement your configuration by deriving from roq::client::Config

class MyConfig : public roq::client::Config {
  // ...
};

There is only one method to implement roq::client::Config::dispatch(). When called, this method should dispatch all subscriptions to the provided handler interface, roq::client::Config::Handler.

class MyConfig : public roq::client::Config {
 protected:
  void dispatch(roq::client::Config::Handler&) const override {
    // ...
  }
};

There are two kinds of subscriptions: roq::client::Symbol and roq::client::Account.

For example

class MyConfig : public roq::client::Config {
 protected:
  void dispatch(roq::client::Config::Handler& handler) const override {
    constexpr std::string_view = "deribit";
    handler(
        Symbol {
          .regex = "BTC-.*",
          .exchange = deribit,
        });
    handler(
        Symbol {
          .regex = "ETH-.*",
          .exchange = deribit,
        });
    handler(
        Account {
          .regex = "MY_ACCOUNT",  // the *internal* name used by the gateway
        });
  }
};

Live Trading#

The roq-client package (closed source) provides a framework for live trading through roq::client::Trader.

You will have to provide a configuration (described in the previous section) and a list of unix domain sockets to connect to

MyConfig config;

std::vector<std::string_string_view> connections {
  "/home/user/deribit.sock",
};

roq::client::Trader(
    config,
    connections).dispatch<Strategy>();

Simulation#

The roq-client package (closed source) provides a framework for simulation through roq::client::Simulator.

The most trivial simulation can replay event logs without any support for order matching

MyConfig config;

std::vector<std::string_string_view> connections {
  "/home/user/event_logs/2020-W31/deribit/1596198613995.roq",
};

roq::client::Simulator(
    config,
    connections).dispatch<Strategy>();

Note

For this example, any attempt to use the order management functions will result in an exception.

Replaying events only have value if we can capture and analyse what’s happening. For this, there’s the roq::client::Collector interface. It is a reduced version of the strategy handler interface (mainly leaving out events related to transport and control).

A very simple example collecting a snapshot of top of book every second

class MyCollector : public roq::client::Collector {
 public:
  explicit MyCollector(const std::string_view& symbol)
      : _symbol(symbol) {
  }

  void print() const {
    // note! you need a bit more logic if you want to sort the output here
    for (const auto& iter : _top_of_book) {
      auto line = fmt::format(
          "{},{},{}",
          iter.first,
          iter.second.first,
          iter.second.second);
      printf("%s\n", line.c_str());
    }
  }

 protected:
  void operator()(const roq::Event<roq::TopOfBook>& event) {
    const auto& top_of_book = static_cast<roq::TopOfBook>(event);
    // drop if symbol doesn't match
    if (_symbol.compare(top_of_book.symbol) != 0)
      return;
    // nearest second
    auto time = std::chrono::duration_cast<std::chrono::seconds>(
        event.message_info.receive_time_utc);
    _top_of_book[time] = std::make_pair(
        top_of_book.layer.bid_price,
        top_of_book.layer.ask_price);
  }

 private:
  std::string _symbol;
  std::unordered_map<
      std::chrono::seconds,
      std::pair<double, double> > _top_of_book;
};

We can now extend our example

MyConfig config;

std::vector<std::string_string_view> connections {
  "/home/user/event_logs/2020-W31/deribit/1596198613995.roq",
};

MyCollector collector("BTC-PERPETUAL");

roq::client::Simulator(
    config,
    connections,
    collector).dispatch<Strategy>();

collector.print();

Finally, let’s deal with order matching. The roq::Matcher interface allows us to implement a market place. The default implementation (with FIFO matching) will suffice for most markets

Our example now looks like this

MyConfig config;

std::vector<std::string_string_view> connections {
  "/home/user/event_logs/2020-W31/deribit/1596198613995.roq",
};

MyCollector collector("BTC-PERPETUAL");

auto market_data_latency = std::chrono::milliseconds{1};
auto order_manager_latency = std::chrono::milliseconds{1};
auto matcher = client::detail::SimulationFactory::create_matcher(
    "simple",
    FLAGS_exchange,
    market_data_latency,
    order_manager_latency);

roq::client::Simulator(
    config,
    connections,
    collector,
    *matcher).dispatch<Strategy>();