767 lines
27 KiB
C++
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
|