#include "CCAPIExecutionGateway.h" #include "lib/api/service_helpers/AdminServiceUtils.h" #include "lib/api/services/AdminService.pb.h" #include "lib/api/services/OrderService.pb.h" #include "lib/core/TimeStamp.h" #include "lib/domain_model/Security.pb.h" #include "lib/domain_model/SecurityKey.h" #include "lib/domain_model/common.h" #include "lib/logging/logging.h" #include "lib/md/SecurityOracle.h" #include "lib/orderman/CancelOrderMsg.h" #include "lib/orderman/CancelReplaceOrderMsg.h" #include "lib/orderman/CreateOrderMsg.h" #include "lib/orderman/OrderMsg.h" #include "lib/orderman/RFQMsg.h" #include "lib/orderman/ccapi/CCAPIDeribitOrderMux.h" #include "lib/orderman/ccapi/ICCAPIOrderMux.h" #include "lib/orderman/events/NewAdminDataEvent.h" #include "lib/orderman/events/NewExecutionReportEvent.h" #include "lib/orderman/events/NewSecuritiesEvent.h" #include "lib/orderman/orderman_utils/CancelReject.h" #include "lib/orderman/orderman_utils/ExchangeOpenOrders.h" #include "lib/orderman/orderman_utils/ExchangePosition.h" #include "lib/orderman/orderman_utils/ExecutionReport.h" #include "lib/orderman/orderman_utils/orderman_common.h" #include "lib/os/os.h" #include "lib/utils/bts_assert.h" #include "lib/utils/bts_chrono.h" #include "lib/utils/datetime.h" #include "lib/api/service_helpers/AdminServiceUtils.h" #include "lib/config/config.h" #include "lib/config/protos/toggles.pb.h" #include "lib/domain_model/Security.h" #include "lib/orderman/ccapi/CCAPIOrderState.h" #include "lib/orderman/ccapi/ICCAPIOrderMux.h" #include "lib/utils/PBUtils.h" #include "lib/utils/Uuid.h" #include #include //#include #include "lib/orderman/ccapi/CCAPIConnectionConfig.h" #include #include #include #include #include #include #include #include namespace bts::orderman { api::orderman::NativeOrderStatus native_order_status(const CCAPIOrderState &s) { switch (s.status) { case api::orderman::OrderStatus::ORDER_STATUS_INACTIVE: return api::orderman::NativeOrderStatus:: NATIVE_ORDER_STATUS_UNACKNOWLEDGED; case api::orderman::OrderStatus::ORDER_STATUS_WORKING: return api::orderman::NativeOrderStatus::NATIVE_ORDER_STATUS_NEW; case api::orderman::OrderStatus::ORDER_STATUS_COMPLETED: return api::orderman::NativeOrderStatus::NATIVE_ORDER_STATUS_FILLED; case api::orderman::OrderStatus::ORDER_STATUS_CANCELED: return api::orderman::NativeOrderStatus::NATIVE_ORDER_STATUS_CANCELED; case api::orderman::OrderStatus::ORDER_STATUS_ERROR: return api::orderman::NativeOrderStatus::NATIVE_ORDER_STATUS_REJECTED; case api::orderman::OrderStatus::ORDER_STATUS_DISCONNECTED: return api::orderman::NativeOrderStatus::NATIVE_ORDER_STATUS_UNDEFINED; case api::orderman::OrderStatus::ORDER_STATUS_PENDING_WORKING: return api::orderman::NativeOrderStatus::NATIVE_ORDER_STATUS_PENDING_NEW; case api::orderman::OrderStatus::ORDER_STATUS_REJECTED_BY_SPARK: return api::orderman::NativeOrderStatus::NATIVE_ORDER_STATUS_REJECTED; case api::orderman::OrderStatus::OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_: case api::orderman::OrderStatus::OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_: bts_assert(false); return api::orderman::NativeOrderStatus::NATIVE_ORDER_STATUS_UNDEFINED; } bts_assert(false); return api::orderman::NativeOrderStatus::NATIVE_ORDER_STATUS_UNDEFINED; } struct SingleOrderMux { ICCAPIOrderMux *order_mux; std::string account; uint32_t account_id; }; struct InitState { enum class StateType { Start = 0, Securities = 1, Admin = 2 }; std::map state_map = { {StateType::Start, false}, {StateType::Securities, false}, {StateType::Admin, false}}; bool set(StateType state) { bool s0 = inited(); auto it = state_map.find(state); bts_assert(it != state_map.end()); it->second = true; bool s1 = inited(); return !s0 && s1; } bool inited() const { for (const auto &kv : state_map) if (!kv.second) return false; return true; } }; struct CCAPIExecutionGateway::PImpl { PImpl(bts::api::Venue venue, md::SecurityOracle o) : venue(venue), sec_oracle(o) { LOG(OrderMan, Info, "CCAPIExecutionGateway::PImpl constructor"); } // TODO: get it from config std::vector configs = { CCAPIConnectionConfig{ "MAIN", CCAPIConnectionConfig::DeribitConnectionConfig{ "ZmIKL6fb", "_FY2PcXYNc8VdlfkZO5c-pkFJQFhn8P9z4_p8YA2fS0"}}, CCAPIConnectionConfig{ "SMALL", CCAPIConnectionConfig::DeribitConnectionConfig{ "v1iSzZrY", "fFOX8SBB2ZKj43xoab2f3YV70DDK5064STWK3WOCrK4"}}}; const bts::api::Venue venue; InitState init_state = {}; std::vector mux_vec = {}; const size_t pos_query_period_ms = 5000; core::TimeStamp last_pos_query_time = core::TimeStamp::now(); api::admin::AdminData admin_data = {}; md::SecurityOracle sec_oracle; // interface (IExecutionGateway) callbacks OnExeReport on_exe_report; OnGwStatusChange on_gw_status_change; OnCancelReject on_cancel_reject; OnExchangePositionState on_exchange_position_state; OnExchangeOpenOrdersState on_exchange_open_orders; // orders are sent out and callbacks received via IEMOrderMuxer size_t msg_seq_num = 0; std::unordered_map order_map = {}; // this is updated as we get exchange positions ExchangePosition exchange_position; ExchangeOpenOrders exchange_open_orders; }; CCAPIExecutionGateway::CCAPIExecutionGateway( bts::api::Venue venue, md::SecurityOracle o) : IExecutionGateway(), me(new PImpl(venue, o)) { LOG(OrderMan, Info, "CCAPIExecutionGateway::CCAPIExecutionGateway constructor venue: {} " "t_id: {} " "t_name: {}", bts::api::Venue_Name(venue), os::get_thread_id(), os::get_application_name()); } CCAPIExecutionGateway::~CCAPIExecutionGateway() {} void CCAPIExecutionGateway::start() { LOG(OrderMan, Info, "CCAPIExecutionGateway::start t_id: {} " "t_name: {}", os::get_thread_id(), os::get_thread_name()); bool just_inited = me->init_state.set(InitState::StateType::Start); if (just_inited) { _init(); } } // this is a periodic timer void CCAPIExecutionGateway::dispatch_incoming_fix_messages() { if (!me->init_state.inited()) return; for (auto &mux : me->mux_vec) mux.order_mux->dispatch_incoming_messages(); if (core::TimeStamp::now().millis() - me->last_pos_query_time.millis() >= me->pos_query_period_ms) { me->last_pos_query_time = core::TimeStamp::now(); for (auto &mux : me->mux_vec) { mux.order_mux->query_positions(); mux.order_mux->query_open_orders(); } } } void CCAPIExecutionGateway::on_securities( const mercury::Update &update) { LOG(OrderMan, Info, "CCAPIExecutionGateway::on_securities t_id: {} t_name: {} n_secs: {}", os::get_thread_id(), os::get_thread_name(), update.events == nullptr ? 0 : update.events->size()); bool just_inited = me->init_state.set(InitState::StateType::Securities); if (just_inited) _init(); } void CCAPIExecutionGateway::on_admin_data( const mercury::Update &update) { LOG(OrderMan, Info, "CCAPIExecutionGateway::on_admin_data t_id: {} t_name: {} size: {}", os::get_thread_id(), os::get_thread_name(), update.events != nullptr ? update.events->size() : 0); bool has_data = update.events != nullptr && update.events->size() == 1; if (!has_data) return; me->admin_data.CopyFrom(*(update.events)->at(0).admin_data); bool just_inited = me->init_state.set(InitState::StateType::Admin); if (just_inited) { _init(); } } void CCAPIExecutionGateway::send_fix(const jetfix::OutFIXPtr &, trace::CorID) { bts_assert_msg(false, "CCAPIExecutionGateway does not support fix"); } void CCAPIExecutionGateway::send_msgs( std::vector const &msg_vec, trace::CorID) { if (!me->init_state.inited()) return; LOG(OrderMan, Info, "CCAPIExecutionGateway::send_msgs count: {}", msg_vec.size()); for (const auto &m : msg_vec) { LOG(OrderMan, Info, "CCAPIExecutionGateway::send_msgs {}", m); if (std::holds_alternative(m)) { const CreateOrderMsg &cr_msg = std::get(m); SingleOrderMux *mux = _find_mux(cr_msg.routing_record); if (mux == nullptr) { LOG(OrderMan, Error, "CCAPIExecutionGateway::send_msgs could not resolve acount for " "cr: {}", m); // TODO: send execution report continue; } LOG(OrderMan, Info, "CCAPIExecutionGateway::send_msgs Create sec: {} order_key", cr_msg.security->id(), cr_msg.key); me->order_map.insert({cr_msg.key, cr_msg}); mux->order_mux->launch_new_order(cr_msg); } else if (std::holds_alternative(m)) { const CancelReplaceOrderMsg &cxl_repl_msg = std::get(m); SingleOrderMux *mux = _find_mux_from_orderkey(cxl_repl_msg.create_fields.key); if (mux == nullptr) { LOG(OrderMan, Error, "CCAPIExecutionGateway::send_msgs could not resolve acount for " "cr: {}", m); continue; } LOG(OrderMan, Info, "CCAPIExecutionGateway::send_msgs CancelReplace sec: {} " "order_key: {}", cxl_repl_msg.create_fields.security->id(), cxl_repl_msg.create_fields.key); mux->order_mux->update_order(cxl_repl_msg); } else if (std::holds_alternative(m)) { const CancelOrderMsg &cxl_msg = std::get(m); SingleOrderMux *mux = _find_mux_from_orderkey(cxl_msg.key); if (mux == nullptr) { LOG(OrderMan, Error, "CCAPIExecutionGateway::send_msgs could not resolve acount for " "cr: {}", m); continue; } LOG(OrderMan, Info, "CCAPIExecutionGateway::send_msgs Cancel order_key: {}", cxl_msg.key); mux->order_mux->cancel_order(cxl_msg); } else if (std::holds_alternative(m)) { const RFQMsg &rfq_msg = std::get(m); LOG(OrderMan, Info, "CCAPIExecutionGateway::send_msgs RFQ: {}", utils::pb::to_string(rfq_msg.rfq)); // me->order_mux->send_rfq(rfq_msg); } } } OnExeReport &CCAPIExecutionGateway::on_exe_report() { return me->on_exe_report; } OnGwStatusChange &CCAPIExecutionGateway::on_gw_status_change() { return me->on_gw_status_change; } OnCancelReject &CCAPIExecutionGateway::on_cancel_reject() { return me->on_cancel_reject; } OnExchangePositionState &CCAPIExecutionGateway::on_exchange_position_state() { LOG(OrderMan, Info, "CCAPIExecutionGateway::on_exchange_position_state subscribed"); return me->on_exchange_position_state; } OnExchangeOpenOrdersState &CCAPIExecutionGateway::on_exchange_open_orders() { LOG(OrderMan, Info, "CCAPIExecutionGateway::on_exchange_open_orders subscribed"); return me->on_exchange_open_orders; } void CCAPIExecutionGateway::_init() { bts_assert(me->init_state.inited()); LOG(OrderMan, Info, "CCAPIExecutionGateway::_init"); for (const auto &p : me->configs) { api::admin::BTSAccount *acct_ptr = admin::AdminServiceUtils::find_account( me->admin_data, p.internal_account); if (acct_ptr == nullptr) { LOG(OrderMan, Error, "CCAPIExecutionGateway::_init could not resolve account: {}", p.internal_account); continue; } CCAPIDeribitOrderMux *mux = new CCAPIDeribitOrderMux(p, me->sec_oracle); me->mux_vec.push_back({mux, p.internal_account, acct_ptr->id()}); mux->order_update_cb() = [this](const CCAPIOrderState &order_state) { _on_order_cb(order_state); }; mux->exchange_position_update_cb() = [this](const auto &pos_vec) { _on_exchange_positions(pos_vec); }; mux->exchange_open_orders_update_cb() = [this](const auto &ord_vec) { _on_exchange_working_orders(ord_vec); }; } } void CCAPIExecutionGateway::_on_order_cb(const CCAPIOrderState &s) { LOG(OrderMan, Info, "CCAPIExecutionGateway::_on_order_cb order_state: {}", s); auto it = me->order_map.find(s.order_key); if (it == me->order_map.end()) { LOG(OrderMan, Error, "CCAPIExecutionGateway::_process_cb_and_fire_er key: {} not found", s.order_key); } const auto &cr_msg = it->second; // cant create one on the stack ExecutionReport er; // length of these have to be <= 12 to_array(cr_msg.routing_record->account(), &er.account_name.value()); to_array(cr_msg.routing_record->operator_id(), &er.operator_id.value()); er.order_key = cr_msg.key; er.msg_seq_num = me->msg_seq_num++; // this is needed for fix, here we just ++ it er.security = cr_msg.security; er.leg_security = nullptr; er.side = cr_msg.side; er.time_in_force = cr_msg.time_in_force; er.order_type = cr_msg.order_type; er.exec_type = api::orderman::ExecType::EXEC_TYPE_NEW; // TODO er.order_status = native_order_status(s); er.manual_order_indicator = api::orderman::ManualOrderIndicator::MANUAL_ORDER_INDICATOR_NO; auto &er_exch_oid = std::get(er.exchange_order_id).value(); to_array(s.cc_order_key, &er_exch_oid); er.price.value() = s.order_px; er.quantity = s.order_qty; er.msg = s.msg; // fractional qty // fills er.avg_price.value() = s.avg_filled_price; er.filled_quantity = s.total_fills; if (!s.cb_trades.empty()) { if (s.cb_trades.size() > 1) { LOG(OrderMan, Error, "CCAPIExecutionGateway::_on_order_cb multiple trade fills for " "order_key: {}", s.order_key); } er.exec_type = api::orderman::ExecType::EXEC_TYPE_PARTIAL_FILL; if (s.status == api::orderman::ORDER_STATUS_COMPLETED) { er.exec_type = api::orderman::ExecType::EXEC_TYPE_FILL; } auto &tr = s.cb_trades.back(); er.last_price = Price(tr.last_exec_price); er.last_shares = Quantity::create_new(tr.last_exec_qty); to_array(tr.trade_id, &er.exchange_fill_id.value()); } else { er.last_shares = Quantity(0); } er.max_show = Quantity(0); er.exchange = cr_msg.security->exchange(); ExchangeTimeStamp64 now = ExchangeTimeStamp64(datetime::unix_nano_now()); er.transact_time = now; LOG(OrderMan, Info, "CCAPIExecutionGateway::_process_cb_and_fire_er firing er: {}", er.to_string()); me->on_exe_report(std::make_shared(std::move(er)), {}); // _fire_leg_er(em_cb, er); } api::Security const *CCAPIExecutionGateway::_find_sec( // TODO api::Venue venue, const std::string &exchange_security_desc) { for (auto p : me->sec_oracle.get_securities()) { if (p->exchange() == venue) { bts_assert(venue == api::VENUE_DERIBIT); if (p->deribit().security_desc() == exchange_security_desc) return p; } } return nullptr; } void CCAPIExecutionGateway::_on_exchange_positions( const std::vector &pos_vec) { LOG(OrderMan, Info, "CCAPIExecutionGateway::_on_exchange_positions cb sz: {}", pos_vec.size()); ExchangePosition out_pos = {me->venue, {}}; for (const CCAPIPosition &pos : pos_vec) { auto sec_ptr = _find_sec(pos.venue, pos.exchange_sec_desc); api::admin::BTSAccount *acct_ptr = admin::AdminServiceUtils::find_account( me->admin_data, pos.internal_account); if (sec_ptr != nullptr && acct_ptr != nullptr) { out_pos.total_exchange_positions.push_back( ExchangePosition::UnitPosition{ pos.exchange_sec_desc, sec_ptr, pos.internal_account, acct_ptr->id(), pos.position, Price{pos.average_price}}); } else { LOG(OrderMan, Error, "CCAPIExecutionGateway::_on_exchange_positions could not " "resolve " "exchange: {} desc: {} account: {} , this position will not be " "reported", api::Venue_Name(pos.venue), pos.exchange_sec_desc, pos.internal_account); } } LOG(OrderMan, Info, "CCAPIExecutionGateway::_on_exchange_positions firing positions sz: " "{}", out_pos.total_exchange_positions.size()); me->on_exchange_position_state(out_pos); } void CCAPIExecutionGateway::_on_exchange_working_orders( const std::vector &ord_vec) { LOG(OrderMan, Info, "CCAPIExecutionGateway::_on_exchange_working_orders sz: {}", ord_vec.size()); ExchangeOpenOrders out_ords = {me->venue, {}}; for (const auto &ord : ord_vec) { auto sec_ptr = _find_sec(me->venue, ord.exchange_sec_desc); api::admin::BTSAccount *acct_ptr = admin::AdminServiceUtils::find_account( me->admin_data, ord.account_name); if (sec_ptr != nullptr && acct_ptr != nullptr) { out_ords.all_open_orders.push_back(ExchangeOpenOrders::UnitOrder{ ord.exchange_sec_desc, ord.exchange_account, sec_ptr, ord.exchange_order_id, ord.account_name, acct_ptr->id(), ord.tif, ord.side, Price{ord.price}, Quantity::create_new(ord.qty), Price{ord.filled_price}, Quantity::create_new(ord.filled_qty), ord.creation_timestamp, ord.last_update_timestamp, !ord.web, ord.web}); } else { LOG(OrderMan, Error, "CCAPIExecutionGateway::_on_exchange_working_orders could not " "resolve exchange: {} desc: {} account: {} this order will not be " "reported", me->venue, ord.exchange_sec_desc, ord.account_name); } } LOG(OrderMan, Info, "CCAPIExecutionGateway::_on_exchange_working_orders firing orders " "sz: " "{}", out_ords.all_open_orders.size()); me->on_exchange_open_orders(out_ords); } SingleOrderMux *CCAPIExecutionGateway::_find_mux( api::orderman::RoutingRecord const *r) { if (r == nullptr) return nullptr; for (auto &m : me->mux_vec) { if (m.account == r->account()) return &m; } return nullptr; } SingleOrderMux *CCAPIExecutionGateway::_find_mux_from_orderkey(OrderKey key) { auto it = me->order_map.find(key); if (it == me->order_map.end()) return nullptr; return _find_mux(it->second.routing_record); } } // namespace bts::orderman