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
create an instance of
roq::client::Dispatcher
,create the strategy (which must derive from
roq::client::Handler
),dispatch events to the strategy, and
receive and manage requests from the strategy.
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>();