Files
spark/src/server/sparkd/exchange_sim/ExchangeSimApplication.cpp
T

767 lines
27 KiB
C++

#include "ExchangeSimApplication.h"
#include "lib/md/SecurityDB.h"
#include "IDGenerator.h"
#include "lib/config/config.h"
#include "lib/config/protos/ExchangeSimCfg.pb.h"
#include "lib/domain_model/Security.h"
#include "lib/logging/logging.h"
#include "lib/md/MBOProcessor.h"
#include "lib/md/MDProducer.h"
#include "lib/md/Shm/FixedSecurity.h"
#include "lib/md/Shm/MDStructure.h"
#include "lib/md/get_symbols.h"
#include "lib/orderman/orderman_utils/fix_order_codec.h"
#include "lib/persistence/SqliteStore.h"
#include "lib/utils/Dispatcher.h"
#include "lib/utils/datetime.h"
#include "server/sparkd/exchange_sim/book/Order.h"
#include "server/sparkd/exchange_sim/book/OrderMatcher.h"
#include "server/sparkd/exchange_sim/book/book_transform.h"
#include <boost/date_time/posix_time/posix_time.hpp>
#include <fmt/format.h>
#include <mutex>
#include <quickfix/fix42/ExecutionReport.h>
#include <quickfix/fix42/OrderCancelReject.h>
#include <quickfix/fix42/OrderCancelReplaceRequest.h>
#include <sstream>
namespace bts {
namespace ex_sim {
GCC_DIAG_OFF("-Wdeprecated")
GCC_DIAG_OFF("-Wignored-qualifiers")
api::Side convert(const FIX::Side &side) {
switch (side) {
case FIX::Side_BUY:
return api::SIDE_BUY;
case FIX::Side_SELL:
return api::SIDE_SELL;
default:
throw std::logic_error("Unsupported Side, use buy or sell");
}
}
FIX::Side convert(api::Side side) {
switch (side) {
case api::SIDE_BUY:
return FIX::Side(FIX::Side_BUY);
case api::SIDE_SELL:
return FIX::Side(FIX::Side_SELL);
default:
throw std::logic_error("Unsupported Side, use buy or sell");
}
}
api::orderman::NativeOrderType convert(const FIX::OrdType &ordType) {
switch (ordType) {
case FIX::OrdType_LIMIT:
return api::orderman::NATIVE_ORDER_TYPE_LIMIT;
case FIX::OrdType_MARKET:
return api::orderman::NATIVE_ORDER_TYPE_MARKET;
default:
throw std::logic_error("Unsupported Order Type, use limit or market");
}
}
FIX::OrdType convert(api::orderman::NativeOrderType type) {
switch (type) {
case api::orderman::NATIVE_ORDER_TYPE_LIMIT:
return FIX::OrdType(FIX::OrdType_LIMIT);
case api::orderman::NATIVE_ORDER_TYPE_MARKET:
return FIX::OrdType(FIX::OrdType_MARKET);
default:
throw std::logic_error("Unsupported Order Type, use limit or market");
}
}
FIX::ManualOrderIndicator
convert(const api::orderman::ManualOrderIndicator &moi) {
switch (moi) {
case api::orderman::MANUAL_ORDER_INDICATOR_YES:
return FIX::ManualOrderIndicator(true);
default:
return FIX::ManualOrderIndicator(false);
}
}
api::orderman::ManualOrderIndicator
convert(const FIX::ManualOrderIndicator &moi) {
if (moi.getValue())
return api::orderman::MANUAL_ORDER_INDICATOR_YES;
else
return api::orderman::MANUAL_ORDER_INDICATOR_NO;
}
struct ExchangeSimApplication::MDConsumerRec {
api::Security security;
utils::TimeStamp last_read;
};
inline FIX::OrderID exchange_key(const std::string &clOrdID) {
return FIX::OrderID(clOrdID.substr(3));
}
struct ExchangeSimApplication::PImpl {
PImpl(md::MBOWriter *mbo)
: producer(co2::PLATFORM_SIM), md_sn(1),
timer(utils::Dispatcher::background(), 200,
[=]() { producer.heartbeat(); }),
do_merge_with_md(config::get<config::ExchangeSim>().merge_with_md()),
store(new persistence::SqliteStore("ex_sim_orders")), lock(),
mbo(mbo) {}
IDGenerator m_generator;
md::MDProducer producer;
std::map<std::string, MDConsumerRec> md_consumer_recs;
std::map<std::string, md::MDProducerHandle> md_producer_handles;
uint32_t md_sn;
utils::DispatcherTimer timer;
std::map<std::string, ex_sim::OrderMatcher> matchers;
bool do_merge_with_md;
std::unique_ptr<persistence::IStore> store;
std::mutex lock;
md::MBOWriter *mbo;
};
ExchangeSimApplication::ExchangeSimApplication(md::MBOWriter *mbo)
: me(new PImpl(mbo)) {
auto previous_book = me->store->retrieve_all();
auto all = me->store->retrieve_all()["books"];
for (auto const &kvp : all) {
OrderBook book;
std::vector<Order> out;
if (utils::pb::from_string<OrderBook>(kvp.second, &book)) {
for (const auto &o : book.order())
me->matchers[o.sec_desc()].add_new(o, &out);
} else
LOG(ExSim, Info, "Could not recover simulator orders for {}",
kvp.first);
}
me->timer.start();
}
ExchangeSimApplication::~ExchangeSimApplication() {}
void ExchangeSimApplication::onCreate(const FIX::SessionID &) {}
void ExchangeSimApplication::onLogon(const FIX::SessionID &) {}
void ExchangeSimApplication::onLogout(const FIX::SessionID &) {}
void ExchangeSimApplication::toAdmin(FIX::Message &, const FIX::SessionID &) {}
void ExchangeSimApplication::toApp(
FIX::Message &, const FIX::SessionID &) throw(FIX::DoNotSend) {}
void ExchangeSimApplication::fromAdmin(
const FIX::Message &,
const FIX::SessionID &) throw(FIX::FieldNotFound, FIX::IncorrectDataFormat,
FIX::IncorrectTagValue, FIX::RejectLogon) {}
void ExchangeSimApplication::fromApp(
const FIX::Message &message,
const FIX::SessionID &sessionID) throw(FIX::FieldNotFound,
FIX::IncorrectDataFormat,
FIX::IncorrectTagValue,
FIX::UnsupportedMessageType) {
std::lock_guard<std::mutex> l(me->lock);
crack(message, sessionID);
}
void ExchangeSimApplication::onMessage(const FIX42::NewOrderSingle &message,
const FIX::SessionID &) {
FIX::SenderCompID senderCompID;
FIX::TargetCompID targetCompID;
FIX::SenderSubID senderSubID;
FIX::ClOrdID clOrdID;
FIX::Account account;
FIX::SecurityDesc sec_desc;
FIX::Side side;
FIX::OrdType ordType;
FIX::Price price;
FIX::OrderQty orderQty;
FIX::TimeInForce timeInForce(FIX::TimeInForce_DAY);
FIX::ManualOrderIndicator moi;
message.getHeader().get(senderCompID);
message.getHeader().get(targetCompID);
message.getHeader().get(senderSubID);
message.get(clOrdID);
message.get(account);
message.get(sec_desc);
message.get(side);
message.get(ordType);
if (ordType == FIX::OrdType_LIMIT)
message.get(price);
message.get(orderQty);
message.getFieldIfSet(timeInForce);
message.getField(moi);
LOG(ExSim, Info, "Got new order: {}", clOrdID.getString());
try {
if (timeInForce != FIX::TimeInForce_DAY &&
timeInForce != FIX::TimeInForce_IMMEDIATE_OR_CANCEL)
throw std::logic_error("Unsupported TIF, use Day or IOC");
auto &matcher = me->matchers[sec_desc];
if (matcher.get_order(clOrdID))
throw std::logic_error("Duplicate order");
ex_sim::Order order;
order.set_clordid(clOrdID);
order.set_sec_desc(sec_desc);
order.set_account(account);
order.set_owner(senderCompID);
order.set_target(targetCompID);
order.set_sender_sub_id(senderSubID);
order.set_side(convert(side));
order.set_type(convert(ordType));
if (orderQty.getValue() == 0)
throw std::logic_error("Quantity must be greater than 0");
if (order.type() == api::orderman::NATIVE_ORDER_TYPE_LIMIT)
order.set_price(price);
else if (order.type() == api::orderman::NATIVE_ORDER_TYPE_MARKET) {
auto book = matcher.get_book();
if (order.side() == api::SIDE_BUY) {
if (book.asks.size())
order.set_price(book.asks.begin()->first.value());
else
throw std::logic_error("No sell market");
} else {
if (book.bids.size())
order.set_price(book.bids.begin()->first.value());
else
throw std::logic_error("No buy market");
}
}
order.set_quantity(orderQty);
order.set_moi(convert(moi));
order.set_open_quantity(order.quantity());
order.set_executed_quantity(0);
order.set_avg_executed_price(0);
order.set_last_executed_price(0);
order.set_last_executed_quantity(0);
*order.mutable_create_time() = utils::pb::pb_now();
*order.mutable_last_update_time() = utils::pb::pb_now();
send_execution_report(order, FIX::OrdStatus_NEW);
std::vector<Order> updates;
matcher.add_new(order, &updates);
for (auto const &o : updates)
fillOrder(o);
if (updates.empty() &&
timeInForce == FIX::TimeInForce_IMMEDIATE_OR_CANCEL) {
auto order_cpy = order;
order_cpy.set_open_quantity(0);
send_execution_report(order_cpy, FIX::OrdStatus_CANCELED);
matcher.cancel(clOrdID);
}
update_md(sec_desc);
} catch (std::exception &e) {
rejectOrder(senderCompID, senderSubID, targetCompID, clOrdID, sec_desc,
side, account, e.what());
}
}
void ExchangeSimApplication::onMessage(
const FIX42::OrderCancelReplaceRequest &message, const FIX::SessionID &) {
FIX::Price price;
FIX::OrderQty orderQty;
FIX::ClOrdID clOrdID;
FIX::SecurityDesc sec_desc;
FIX::Side side;
FIX::SenderCompID senderCompID;
FIX::SenderSubID senderSubID;
FIX::TargetCompID targetCompID;
FIX::Account account;
message.getHeader().get(senderCompID);
message.getHeader().get(senderSubID);
message.getHeader().get(targetCompID);
message.get(clOrdID);
message.get(price);
message.get(orderQty);
message.get(sec_desc);
message.get(side);
message.get(account);
LOG(ExSim, Info, "Got order change: {} {}@{}", clOrdID.getString(), orderQty,
price);
try {
auto &matcher = me->matchers[sec_desc];
auto order = matcher.get_order(clOrdID);
if (!order) {
LOG(ExSim, Info, "Receive cancel/replace for unknown order {}",
clOrdID);
throw std::logic_error("Unknown order");
} else {
if (order->executed_quantity() >= orderQty) {
auto order_cpy = *order;
order_cpy.set_open_quantity(0);
send_execution_report(order_cpy, FIX::OrdStatus_CANCELED);
matcher.cancel(clOrdID);
} else {
auto order_cpy = *order;
order_cpy.set_price(price);
order_cpy.set_quantity(orderQty);
*order_cpy.mutable_last_update_time() = utils::pb::pb_now();
send_execution_report(order_cpy, FIX::OrdStatus_REPLACED);
std::vector<Order> updates;
matcher.update(clOrdID, Price(price), Quantity(orderQty), &updates);
for (auto const &o : updates)
fillOrder(o);
}
update_md(sec_desc);
}
} catch (std::exception &e) {
rejectOrder(senderCompID, senderSubID, targetCompID, clOrdID, sec_desc,
side, account, e.what());
}
}
void ExchangeSimApplication::onMessage(const FIX42::OrderCancelRequest &message,
const FIX::SessionID &) {
FIX::OrigClOrdID origClOrdID;
FIX::SecurityDesc sec_desc;
FIX::Side side;
FIX::SenderCompID senderCompID;
FIX::TargetCompID targetCompID;
FIX::SenderSubID senderSubID;
message.getHeader().get(senderCompID);
message.getHeader().get(senderSubID);
message.getHeader().get(targetCompID);
message.get(origClOrdID);
message.get(sec_desc);
message.get(side);
LOG(ExSim, Info, "Got order cancellation for: {}", origClOrdID.getString());
try {
auto &matcher = me->matchers[sec_desc];
auto order = matcher.get_order(origClOrdID);
if (!order) {
LOG(ExSim, Info, "Receive cancel for unknown order {}",
origClOrdID.getString());
FIX42::OrderCancelReject rej;
rej.set(FIX::ClOrdID(origClOrdID.getString()));
rej.set(FIX::Text("Unknown order"));
rej.getHeader().set(FIX::TargetSubID(senderSubID.getValue()));
try {
FIX::Session::sendToTarget(rej, targetCompID, senderCompID);
LOG(ExSim, Info, "Rejected cancel for order {}",
origClOrdID.getString());
} catch (FIX::SessionNotFound &) {
}
} else {
auto order_cpy = *order;
order_cpy.set_open_quantity(0);
send_execution_report(order_cpy, FIX::OrdStatus_CANCELED);
matcher.cancel(origClOrdID);
update_md(sec_desc);
}
} catch (std::exception &) {
}
}
void ExchangeSimApplication::rejectOrder(const Order &order) {
send_execution_report(order, FIX::OrdStatus_REJECTED);
}
void ExchangeSimApplication::fillOrder(const Order &order) {
send_execution_report(order, order.open_quantity() == 0
? FIX::OrdStatus_FILLED
: FIX::OrdStatus_PARTIALLY_FILLED);
}
void ExchangeSimApplication::send_execution_report(const Order &order, char status) {
FIX::TargetCompID targetCompID(order.owner());
FIX::SenderCompID senderCompID(order.target());
FIX::TargetSubID(order.sender_sub_id());
FIX42::ExecutionReport fixOrder;
fixOrder.set(exchange_key(order.clordid()));
fixOrder.set(FIX::ExecID(me->m_generator.genExecutionID()));
fixOrder.set(FIX::ExecTransType(FIX::ExecTransType_NEW));
fixOrder.set(FIX::ExecType(status));
fixOrder.set(FIX::OrdStatus(status));
fixOrder.set(FIX::Account(order.account()));
fixOrder.set(FIX::SecurityDesc(order.sec_desc()));
fixOrder.set(FIX::Side(convert(order.side())));
fixOrder.set(FIX::LeavesQty(order.open_quantity()));
fixOrder.set(FIX::CumQty(order.executed_quantity()));
fixOrder.set(FIX::AvgPx(order.avg_executed_price()));
fixOrder.set(FIX::ClOrdID(order.clordid()));
fixOrder.set(FIX::OrderQty(order.quantity()));
fixOrder.set(FIX::TimeInForce(FIX::TimeInForce_DAY));
fixOrder.set(FIX::OrdType(convert(order.type())));
fixOrder.set(FIX::Price(order.price()));
fixOrder.set(FIX::OrderQty(order.quantity()));
fixOrder.setField(convert(order.moi()));
boost::posix_time::ptime now =
boost::posix_time::second_clock::universal_time();
fixOrder.set(FIX::TransactTime(
FIX::UtcTimeStamp(now.time_of_day().hours(), now.time_of_day().minutes(),
now.time_of_day().seconds(), now.date().day(),
now.date().month(), now.date().year()),
true));
if (status == FIX::OrdStatus_FILLED ||
status == FIX::OrdStatus_PARTIALLY_FILLED) {
fixOrder.set(FIX::LastShares(order.last_executed_quantity()));
fixOrder.set(FIX::LastPx(order.last_executed_price()));
}
fixOrder.getHeader().set(FIX::TargetSubID(order.sender_sub_id()));
try {
FIX::Session::sendToTarget(fixOrder, senderCompID, targetCompID);
} catch (FIX::SessionNotFound &) {
}
}
void ExchangeSimApplication::rejectOrder(
const FIX::SenderCompID &sender, const FIX::SenderSubID &sender_sub_id,
const FIX::TargetCompID &target, const FIX::ClOrdID &clOrdID,
const FIX::SecurityDesc &symbol, const FIX::Side &side,
const std::string &account, const std::string &message) {
FIX::TargetCompID targetCompID(sender.getValue());
FIX::SenderCompID senderCompID(target.getValue());
FIX42::ExecutionReport fixOrder;
fixOrder.set(exchange_key(clOrdID.getString()));
fixOrder.set(FIX::ExecID(me->m_generator.genExecutionID()));
fixOrder.set(FIX::ExecTransType(FIX::ExecTransType_NEW));
fixOrder.set(FIX::ExecType(FIX::ExecType_REJECTED));
fixOrder.set(FIX::OrdStatus(FIX::ExecType_REJECTED));
fixOrder.set(symbol);
fixOrder.set(FIX::Account(account));
fixOrder.set(side);
fixOrder.set(FIX::LeavesQty(0));
fixOrder.set(FIX::Price(0));
fixOrder.set(FIX::OrderQty(0));
fixOrder.set(FIX::CumQty(0));
fixOrder.set(FIX::LastShares(0));
fixOrder.set(FIX::AvgPx(0));
fixOrder.set(FIX::LastPx(0));
fixOrder.set(FIX::TimeInForce(FIX::TimeInForce_DAY));
fixOrder.set(clOrdID);
fixOrder.set(FIX::Text(message));
fixOrder.set(FIX::OrdType(FIX::OrdType_LIMIT));
fixOrder.setField(FIX::ManualOrderIndicator(true));
fixOrder.getHeader().set(FIX::TargetSubID(sender_sub_id.getValue()));
boost::posix_time::ptime now =
boost::posix_time::second_clock::universal_time();
fixOrder.set(FIX::TransactTime(
FIX::UtcTimeStamp(now.time_of_day().hours(), now.time_of_day().minutes(),
now.time_of_day().seconds(), now.date().day(),
now.date().month(), now.date().year()),
true));
try {
FIX::Session::sendToTarget(fixOrder, senderCompID, targetCompID);
} catch (FIX::SessionNotFound &) {
}
}
// for the passed sec_desc, generate the book from the order matcher
// and used the associated producer to publish the MD to the world
void ExchangeSimApplication::update_md(std::string const &sec_desc) {
auto it = me->md_producer_handles.find(sec_desc);
if (it != me->md_producer_handles.end()) {
auto &handle = it->second;
auto &matcher = me->matchers[sec_desc];
auto order_book = matcher.get_order_book("SYNTH");
order_book.set_sec_desc(sec_desc);
persist_book(order_book);
auto book = matcher.get_book();
LOG(ExSim, Diag, "Current book {}:\n {}", sec_desc, book);
auto price_factor = pow(10, handle.security().display_factor);
const auto cmax = md::COCONUT_MAX_DEPTH;
std::array<md::PriceSize, cmax> bids_temp = {}, asks_temp = {};
auto bids_end = book.bids.size() > cmax
? std::next(book.bids.begin(), cmax)
: book.bids.end();
std::transform(book.bids.begin(), bids_end, bids_temp.begin(),
[&](const auto &bs) {
return md::PriceSize_from_double<precision>(
bs.first.value() * price_factor, bs.second.value());
});
auto asks_end = book.asks.size() > cmax
? std::next(book.asks.begin(), cmax)
: book.asks.end();
std::transform(book.asks.begin(), asks_end, asks_temp.begin(),
[&](const auto &bs) {
return md::PriceSize_from_double<precision>(
bs.first.value() * price_factor, bs.second.value());
});
handle.update_direct(utils::TimeStamp::now(), bids_temp, asks_temp,
me->md_sn++);
auto new_trades = matcher.get_new_trades();
for (auto &t : new_trades) {
handle.add_trade(
utils::TimeStamp::now(),
md::PriceSize_from_double<ex_sim::precision>(
t.price.value() * price_factor, t.quantity.value()),
datetime::ll_now(),
t.bid_aggressor ? co2::AGGRESSOR_SIDE_BUY
: (t.ask_aggressor ? co2::AGGRESSOR_SIDE_SELL
: co2::AGGRESSOR_SIDE_NONE));
}
// publish mbo
auto mbo = me->mbo;
if (mbo) {
// get the list of trades
auto ob = matcher.get_order_book(nullptr);
auto snap = mbo->allocate_snapshot();
for (const auto &order : ob.order()) {
auto ek = exchange_key(order.clordid());
md::SnapshotOrder sno{
md::ExchangeOrderKey(std::atoll(ek.getString().c_str())),
md::IntPrice_from_double<precision>(order.price() *
price_factor),
(int)order.open_quantity()};
if (order.side() == api::SIDE_BUY) {
snap->bids.push_back(sno);
} else if (order.side() == api::SIDE_SELL) {
snap->asks.push_back(sno);
}
}
mbo->publish_snapshot(SecurityKey(it->second.key()), &snap);
}
}
}
std::string get_sim_sec_desc(api::Security const &src_sec) {
return std::string("SIM:") + src_sec.glbx().security_desc();
}
void negate_id(api::Security *sec) {
sec->set_id(-sec->id());
if (sec->has_spread()) {
for (auto &l : *sec->mutable_spread()->mutable_legs())
negate_id(l.mutable_security());
}
}
void ExchangeSimApplication::persist_book(OrderBook const &b) {
auto existing = me->store->retrieve_one("books", b.sec_desc());
auto val = utils::pb::to_string(b);
if (existing.first)
me->store->update("books", b.sec_desc(), val);
else
me->store->insert("books", b.sec_desc(), val);
}
void ExchangeSimApplication::on_new_security(api::Security const &s) {
std::lock_guard<std::mutex> l(me->lock);
if (!s.has_synthetic()) {
md::with_security_like(s, [this, s](auto &fs) {
auto s2 = s;
*s2.mutable_glbx()->mutable_security_desc() = get_sim_sec_desc(s);
s2.set_exchange(api::VENUE_SIM);
negate_id(&s2);
md::convert_security(s2, fs, true);
md::MDProducerHandle handle = me->producer.add_security(fs);
auto sec_desc = securities::descriptor(s2);
me->md_producer_handles.insert(make_pair(sec_desc, handle));
me->md_consumer_recs.insert(
make_pair(s.glbx().security_desc(),
MDConsumerRec{s2, utils::TimeStamp::now()}));
});
}
}
void ExchangeSimApplication::on_settlement(md::Security const &sec,
md::IntPrice price) {
auto sec_desc = get_sim_sec_desc(sec);
auto iter = me->md_producer_handles.find(sec_desc);
if (iter != me->md_producer_handles.end()) {
auto &handle = iter->second;
handle.update_settlement(utils::TimeStamp::now(), price,
datetime::today());
}
}
void ExchangeSimApplication::on_high(md::Security const &sec, md::Price price) {
auto sec_desc = get_sim_sec_desc(sec);
auto iter = me->md_producer_handles.find(sec_desc);
if (iter != me->md_producer_handles.end()) {
auto &handle = iter->second;
handle.update_session_high(utils::TimeStamp::now(), price);
}
}
void ExchangeSimApplication::on_low(md::Security const &sec, md::Price price) {
auto sec_desc = get_sim_sec_desc(sec);
auto iter = me->md_producer_handles.find(sec_desc);
if (iter != me->md_producer_handles.end()) {
auto &handle = iter->second;
handle.update_session_low(utils::TimeStamp::now(), price);
}
}
void ExchangeSimApplication::on_open(md::Security const &sec, md::Price price) {
auto sec_desc = get_sim_sec_desc(sec);
auto iter = me->md_producer_handles.find(sec_desc);
if (iter != me->md_producer_handles.end()) {
auto &handle = iter->second;
handle.update_opening(utils::TimeStamp::now(), price, datetime::today());
}
}
void ExchangeSimApplication::on_close(md::Security const &sec,
md::Price price) {
auto sec_desc = get_sim_sec_desc(sec);
auto iter = me->md_producer_handles.find(sec_desc);
if (iter != me->md_producer_handles.end()) {
auto &handle = iter->second;
handle.update_closing(utils::TimeStamp::now(), price, datetime::today());
}
}
void ExchangeSimApplication::on_depth(
md::Security const &sec, md::PriceSize bids[md::COCONUT_MAX_DEPTH],
md::PriceSize asks[md::COCONUT_MAX_DEPTH],
std::array<md::PriceSize, md::COCONUT_MAX_IMPLIED_DEPTH> *implied_bids,
std::array<md::PriceSize, md::COCONUT_MAX_IMPLIED_DEPTH> *implied_asks) {
std::lock_guard<std::mutex> l(me->lock);
if (sec.has_synthetic())
return;
if (!me->do_merge_with_md)
return;
auto it = me->md_consumer_recs.find(sec.glbx().security_desc());
bts_assert(it != me->md_consumer_recs.end());
auto &matcher = me->matchers[get_sim_sec_desc(sec)];
Book previous_md_book = matcher.get_book();
// populate Book structure with new bids, asks coming from outside
Book current_md_book;
for (size_t i = 0; i < md::COCONUT_MAX_DEPTH; i++) {
auto b = securities::encode_exchange_price(sec, Price(bids[i].price()));
if (!os::is_nan(b.value()))
current_md_book.bids[b] = Quantity(bids[i].size());
auto a = securities::encode_exchange_price(sec, Price(asks[i].price()));
if (!os::is_nan(a.value()))
current_md_book.asks[a] = Quantity(asks[i].size());
}
// from the differences between current_md_book and previous_md_book
// we generate a set of orders that we apply to our order book
merge_with_order_book(it->second.security.glbx().security_desc(),
previous_md_book, current_md_book);
// pass on implied market unchanged
if (implied_bids || implied_asks) {
auto iter = me->md_producer_handles.find(get_sim_sec_desc(sec));
if (iter != me->md_producer_handles.end()) {
auto &handle = iter->second;
handle.update_implied(utils::TimeStamp::now(), *implied_bids,
*implied_asks);
}
}
}
void ExchangeSimApplication::merge_with_order_book(std::string const &sec_desc,
Book const &src,
Book const &dest) {
auto &matcher = me->matchers[sec_desc];
auto src_book = matcher.get_book();
auto transform = compute_book_transform(src, dest);
LOG(ExSim, Diag, "Book transformation for {}\n source:\n {} dest:\n{}",
sec_desc, src, dest);
for (auto const &op : transform) {
LOG(ExSim, Diag, "Adding synthetic book operation for {}. {} {} {}@{}",
sec_desc,
op.op == BookOp::ORDER ? std::string("Order") : std::string("Cancel"),
op.side == api::SIDE_BUY ? std::string("Buy") : std::string("Sell"),
op.quantity.value(), op.price.value());
switch (op.op) {
case BookOp::ORDER: {
ex_sim::Order order;
order.set_clordid(
fmt::format("synth-{}", me->m_generator.genOrderID()));
order.set_sec_desc(sec_desc);
order.set_account("SYNTH");
order.set_owner("SYNTH");
order.set_target("SYNTH");
order.set_sender_sub_id("SYNTH");
order.set_side(op.side);
order.set_type(api::orderman::NATIVE_ORDER_TYPE_LIMIT);
order.set_price(op.price.value());
order.set_quantity(op.quantity.value());
order.set_moi(api::orderman::MANUAL_ORDER_INDICATOR_NO);
order.set_open_quantity(order.quantity());
order.set_executed_quantity(0);
order.set_avg_executed_price(0);
order.set_last_executed_price(0);
order.set_last_executed_quantity(0);
*order.mutable_create_time() = utils::pb::pb_now();
*order.mutable_last_update_time() = utils::pb::pb_now();
std::vector<Order> updates;
matcher.add_or_merge(order, &updates);
for (auto const &o : updates)
if (o.account() != "SYNTH") {
LOG(ExSim, Diag,
"Generating fill in response to md change for {}: {} {}@{}",
sec_desc,
o.side() == api::SIDE_BUY ? std::string("Buy")
: std::string("Sell"),
o.open_quantity(), o.price());
fillOrder(o);
}
break;
}
case BookOp::CANCEL:
matcher.try_reduce_quantity_at_level(op.side, op.price, op.quantity,
"SYNTH");
break;
}
}
update_md(sec_desc);
auto order_book = matcher.get_order_book("SYNTH");
order_book.set_sec_desc(sec_desc);
persist_book(order_book);
auto sz = matcher.size();
LOG(ExSim, Diag, "Book size: {} {} {} {}", sec_desc, std::get<0>(sz),
std::get<1>(sz), std::get<2>(sz));
}
GCC_DIAG_RESTORE()
GCC_DIAG_RESTORE()
} // namespace ex_sim
} // namespace bts