Files
spark/src/lib/orderman/orders/NativeOrder.cpp
T

1322 lines
46 KiB
C++

#include "NativeOrder.h"
#include "lib/api/services/OrderService.pb.h"
#include "lib/domain_model/Security.h"
#include "lib/jetfix/codec/FIXFragment.h"
#include "lib/jetfix/codec/FieldDefinitions.h"
#include "lib/logging/logging.h"
#include "lib/orderman/order_factory/IOrderFactory.h"
#include "lib/orderman/order_router/OrderRouter.h"
#include "lib/orderman/orderman_utils/ExecutionReport.h"
#include "lib/orderman/orderman_utils/fix_order_codec.h"
#include "lib/orderman/orders/PreActivationOrderServices.h"
#include "lib/orderman/securities/ISecurityDB.h"
#include "lib/utils/PBUtils.h"
#include "lib/utils/bts_chrono.h"
#include "lib/orderman/actors/order_manager/OrderServices.h"
#include <algorithm>
#include <sys/time.h>
namespace bts {
namespace orderman {
using namespace api::orderman;
OrderStatus map_external_status(NativeOrderStatus);
struct NativeOrder::PImpl {
PImpl()
: services(nullptr), session_services(), sec(nullptr),
previous_position_contribution(0), definition(), state(),
price_encoding_precision(7), exchange_order_id() {
exchange_order_id.reserve(100);
}
OrderServices *services;
SessionServices *session_services;
const api::Security *sec;
Quantity previous_position_contribution;
OrderDefinition definition;
OrderState state;
int price_encoding_precision;
std::string exchange_order_id;
jetfix::FIXFragment update_encoding;
jetfix::FIXFragment::Bookmark<jetfix::fix::Price> update_price_bm;
jetfix::FIXFragment::Bookmark<jetfix::fix::OrderQty> update_quantity_bm;
jetfix::FIXFragment::Bookmark<jetfix::fix::TransactTimeStr>
update_transact_time_bm;
jetfix::FIXFragment create_encoding;
jetfix::FIXFragment::Bookmark<jetfix::fix::Price> create_price_bm;
jetfix::FIXFragment::Bookmark<jetfix::fix::OrderQty> create_quantity_bm;
jetfix::FIXFragment::Bookmark<jetfix::fix::TransactTimeStr>
create_transact_time_bm;
jetfix::FIXFragment cancel_encoding;
jetfix::FIXFragment::Bookmark<jetfix::fix::TransactTimeStr>
cancel_transact_time_bm;
OrderServices::ExeReportSubID exe_report_sid;
OrderServices::CancelRejectSubID cancel_reject_sid;
bool buffered_cancel = false;
std::optional<std::pair<Price, Quantity>> buffered_update;
};
NativeOrder::NativeOrder() : me(new PImpl()) {}
OrderDefinition const &NativeOrder::definition() const {
return me->definition;
}
void NativeOrder::on_init_new(OrderDefinition const &def,
const PreActivationOrderServices &serv) {
// order was just created.
me->definition = def;
switch (def.custom_def().custom_case()) {
case CustomOrderDefinition::kMarket:
me->state.mutable_market();
break;
case CustomOrderDefinition::kLimit:
me->state.mutable_limit();
break;
case CustomOrderDefinition::kStop:
me->state.mutable_stop();
break;
case CustomOrderDefinition::kStopLimit:
me->state.mutable_stop_limit();
break;
default:
bts_assert(false);
break;
}
me->sec = serv.sec_db.resolve(SecurityKey(def.security_key()));
if (!me->sec) {
auto msg = fmt::format("Unknown security: {}", def.security_key());
me->state.set_last_msg(msg);
transition(ORDER_STATUS_REJECTED_BY_SPARK, msg);
} else {
auto rq = serv.order_router.get_routing_record(
UserID(def.user_id()), AccountID(def.account_id()), *me->sec);
if (rq.has_route) {
transition(ORDER_STATUS_INACTIVE);
auto &native_state = get_native_state(me->state);
*native_state.mutable_routing_record() = rq.routing_record;
init_fix();
} else {
auto msg = fmt::format("Could not route order: {}", rq.error_msg);
LOG(OrderMan, Error, "{}", msg);
transition(ORDER_STATUS_REJECTED_BY_SPARK, msg);
}
}
}
void NativeOrder::on_init_recovered(OrderRecord const &rec,
const PreActivationOrderServices &serv) {
// order was recovered from our orders database or from an execution report
auto const &def = rec.def();
me->definition = def;
me->state = rec.state();
me->sec = serv.sec_db.resolve(SecurityKey(def.security_key()));
me->previous_position_contribution = Quantity(rec.state().filled_quantity());
auto const &native_state = get_native_state(me->state);
switch (me->state.status()) {
case ORDER_STATUS_WORKING:
case ORDER_STATUS_PENDING_WORKING:
case ORDER_STATUS_DISCONNECTED: {
// bail if we can't find the security for the order
if (!me->sec) {
LOG(OrderMan, Error,
"Unknown security {} referenced by recovered "
"order {}. This is a serious "
"problem which has to be corrected manually. ",
def.security_key(), rec.key());
transition_to_error(
NativeOrderState::SUB_ERROR_UNKNOWN_SECURITY,
fmt::format("Unknown security: {}", def.security_key()));
return;
}
// bail if the session we used for the order is no longer valid
auto const &session = native_state.routing_record().session();
if (!serv.order_router.is_valid_session(session)) {
LOG(OrderMan, Error,
"Unknown JetFIX session {} referenced by recovered "
"order {}. This is a serious "
"problem which has to be corrected manually. ",
session, rec.key());
transition_to_error(NativeOrderState::SUB_ERROR_UNKNOWN_FIX_SESSION,
fmt::format("Unknown FIX session: {}", session));
return;
}
// if we made it this far we are good.
init_fix();
if (me->state.status() != ORDER_STATUS_PENDING_WORKING) {
me->exchange_order_id = native_state.exchange_order_id();
add_exchange_order_id_to_fix();
}
return;
}
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_ERROR:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
// terminal states, nothing to do
return;
case ORDER_STATUS_INACTIVE:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
return;
}
}
api::orderman::OrderDefinition
NativeOrder::on_init_from_exe_report(ExecutionReport const &er,
const PreActivationOrderServices &os) {
auto k = er.order_key;
OrderRecord orecord;
orecord.set_key(k.value());
orecord.set_parent_key(null_order_key().value());
auto &def = *orecord.mutable_def();
def.set_security_key(er.security->id());
def.set_side(er.side);
auto sec = os.sec_db.resolve(SecurityKey(def.security_key()));
auto rev_rec = os.order_router.get_reverse_routing_record(
er.cfg->name(), to_trim_str(er.operator_id),
to_trim_str(er.account_name), sec);
if (rev_rec.ok) {
def.set_user(rev_rec.user);
def.set_user_id(rev_rec.user_id.value());
def.set_account(rev_rec.account);
def.set_account_id(rev_rec.account_id.value());
} else {
LOG(OrderMan, Error,
"Could not recover account and user information from order {}"
". The following information is available: {}. ",
k.value(), rev_rec.error_msg);
def.set_user("");
def.set_user_id(0);
def.set_account("");
def.set_account_id(0);
}
def.set_time_in_force(er.time_in_force);
def.set_manual_order_indicator(er.manual_order_indicator);
def.set_cti_code(CTICode(er.cfg->cti_code()[0]));
def.set_customer_or_firm(CustomerOrFirm(er.cfg->customer_or_firm()));
auto &state = *orecord.mutable_state();
state.set_status(map_external_status(er.order_status));
state.set_price(er.price.value());
state.set_quantity(er.quantity.value());
state.set_filled_quantity(er.filled_quantity.value());
state.set_last_price(er.last_price.value());
state.set_avg_price(er.last_price.value());
state.set_last_msg(er.msg);
*state.mutable_last_change() = utils::pb::pb_now();
switch (er.order_type) {
case NATIVE_ORDER_TYPE_MARKET:
def.mutable_custom_def()->mutable_market();
state.mutable_market();
break;
case NATIVE_ORDER_TYPE_LIMIT:
def.mutable_custom_def()->mutable_limit();
state.mutable_market();
break;
case NATIVE_ORDER_TYPE_STOP: {
auto &stop_def = *def.mutable_custom_def()->mutable_stop();
stop_def.set_stop_price(er.stop_price.value());
state.mutable_stop();
break;
}
case NATIVE_ORDER_TYPE_STOP_LIMIT: {
auto &stop_limit_def = *def.mutable_custom_def()->mutable_stop_limit();
stop_limit_def.set_stop_price(er.stop_price.value());
state.mutable_stop_limit();
break;
}
case NATIVE_ORDER_TYPE_MARKET_LIMIT:
case NATIVE_ORDER_TYPE_UNDEFINED:
case NativeOrderType_INT_MIN_SENTINEL_DO_NOT_USE_:
case NativeOrderType_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
auto &native_state = get_native_state(state);
native_state.set_exchange_order_id(to_trim_str(er.exchange_order_id));
native_state.set_last_msg_seq_num(er.msg_seq_num);
native_state.set_native_type(er.order_type);
auto &rr = *native_state.mutable_routing_record();
rr.set_session(er.cfg->name());
rr.set_account(to_trim_str(er.account_name));
rr.set_operator_id(to_trim_str(er.operator_id));
rr.set_cti_code(CTICode(er.cfg->cti_code()[0]));
rr.set_customer_or_firm(CustomerOrFirm(er.cfg->customer_or_firm()));
rr.set_location(er.cfg->sender_locationid());
on_init_recovered(orecord, os);
return orecord.def();
}
NativeOrder::~NativeOrder() {}
void NativeOrder::warm_up_activate(OrderServicesData &data,
OrderServices &services) {
bts_assert(!me->services);
me->services = &services;
init_order_services(data, services);
// save these so we can re-instate them after we are done with the warmup
auto before_state = me->state.status();
auto before_msg = me->state.last_msg();
_on_activate(NoLog, NoLog, Price(0), Quantity(0), services);
// undo warm up side effects
me->state.set_status(before_state);
me->state.set_last_msg(before_msg);
me->services->unsubscribe_from_exe_report(*me->session_services,
me->exe_report_sid);
me->services->unsubscribe_from_cancel_reject(*me->session_services,
me->cancel_reject_sid);
me->services->unsubscribe_from_gateway_status(*me->session_services);
me->services = nullptr;
}
OrderOpResult
NativeOrder::_on_activate(log::Level reject_level, log::Level transition_level,
Price p, Quantity q,
bts::orderman::OrderServices &services) {
me->state.set_price(p.value());
me->state.set_quantity(q.value());
me->state.set_filled_quantity(0);
me->state.set_last_price(0);
me->state.set_avg_price(0);
me->session_services = &me->services->get_session_services(
get_native_state(me->state).routing_record().session());
me->price_encoding_precision =
me->services->price_encoding_precision(*me->session_services);
// is our gateway connected?
if (!me->services->is_gateway_connected(*me->session_services)) {
LOG(OrderMan, reject_level,
"Attempt to send order to a disconnected gateway. "
"Dropping order {}",
key().value());
auto msg = "Exchange gateway is down";
transition(transition_level, ORDER_STATUS_REJECTED_BY_SPARK, msg);
return OrderOpResult{ORDER_ERROR_GATEWAY_NOT_READY, msg};
}
// can we trade?
auto lqr = me->services->limiter().can_trade(
UserID(me->definition.user_id()),
AccountID(me->definition.account_id()));
if (!lqr.ok) {
LOG(OrderMan, reject_level,
"Unauthorized order attempted. User: {} on "
"Account: {}. Msg: {} Order: {}. Dropping order",
me->definition.user(), me->definition.account(), lqr.error_msg,
key().value());
auto msg = fmt::format("Unauthorized order: {}", lqr.error_msg);
transition(transition_level, ORDER_STATUS_REJECTED_BY_SPARK, msg);
return OrderOpResult{ORDER_ERROR_UNAUTHORIZED, msg};
}
// can we trade this security, with this size?
lqr = me->services->limiter().can_send_new_order(
AccountID(me->definition.account_id()), *me->sec, me->definition.side(),
q);
if (!lqr.ok) {
LOG(OrderMan, reject_level,
"New order would breach limit. User: {} Account: "
"{} Msg: {} Order: {}. Dropping order",
me->definition.user(), me->definition.account(), lqr.error_msg,
key().value());
auto msg = fmt::format("Order would breach limit: {}", lqr.error_msg);
transition(transition_level, ORDER_STATUS_REJECTED_BY_SPARK, msg);
return OrderOpResult{ORDER_ERROR_LIMIT_BREACH, msg};
}
// are we under the messaging threshold?
if (!me->services->governor(*me->session_services).can_send()) {
LOG(OrderMan, reject_level,
"New order would cause breach of messaging "
"limits. Dropping order {}",
key().value());
auto msg = "Messaging threshold breached";
transition(transition_level, ORDER_STATUS_REJECTED_BY_SPARK, msg);
return OrderOpResult(ORDER_ERROR_MESSAGING_LIMIT_BREACH, msg);
}
// if we are here we are good.
// subscribe to usefull events
me->exe_report_sid = me->services->subscribe_to_exe_report(
*me->session_services, key(),
[this](auto const &r) { this->on_exe_report(r); });
me->cancel_reject_sid = me->services->subscribe_to_cancel_reject(
*me->session_services, key(),
[this](auto const &cr) { this->on_cancel_reject(cr); });
me->services->subscribe_to_gateway_status(
*me->session_services,
[this](auto const &gw) { this->on_gateway_status(gw); });
// send out order
auto &encoding = me->create_encoding;
switch (me->definition.custom_def().custom_case()) {
case CustomOrderDefinition::kLimit:
case CustomOrderDefinition::kStopLimit:
me->create_price_bm = encoding.overwrite(
me->create_price_bm,
securities::encode_exchange_price(*me->sec, p).value(),
me->price_encoding_precision, price_sz());
break;
case CustomOrderDefinition::kStop:
case CustomOrderDefinition::kMarket:
break;
default:
bts_assert(false);
break;
}
me->create_quantity_bm =
encoding.overwrite(me->create_quantity_bm, q.value(), quantity_sz());
auto const &ts = services.get_time_str();
me->create_transact_time_bm = encoding.overwrite(
me->create_transact_time_bm, &ts.value()[0], ts.value().size());
return OrderOpResult{};
}
OrderOpResult NativeOrder::on_activate(Price p, Quantity q,
bts::orderman::OrderServices &services) {
bts_assert(!me->services);
me->services = &services;
auto result = OrderOpResult{};
switch (me->state.status()) {
case ORDER_STATUS_INACTIVE: {
auto scoped_log =
services.log_scope("NativeOrder::on_activate", key().value());
// this is a brand new order
result = _on_activate(Warn, Info, p, q, services);
if (result.ok()) {
me->services->queue_fix(*me->session_services,
jetfix::MessageTypes::NewOrderSingle,
me->create_encoding);
me->services->governor(*me->session_services).record_sent();
me->state.set_status(ORDER_STATUS_PENDING_WORKING);
LOG(OrderMan, Info, "Sent new order with key {} to gateway",
key().value());
// Early return so we skip the expensive report_state()
// call. It is OK to spkip it because we are bound to report
// our state once the excange aknowledges the order.
update_working_position();
return OrderOpResult{};
}
// When the order fails its limits checks we fall through the
// switch to report the error state at the end of this function
break;
}
// all cases below are for orders recovered from disk
case ORDER_STATUS_WORKING:
me->session_services = &me->services->get_session_services(
get_native_state(me->state).routing_record().session());
me->services->subscribe_to_exe_report(
*me->session_services, key(),
[this](auto const &r) { this->on_exe_report(r); });
me->services->subscribe_to_cancel_reject(
*me->session_services, key(),
[this](auto const &cr) { this->on_cancel_reject(cr); });
me->services->subscribe_to_gateway_status(
*me->session_services,
[this](auto const &gw) { this->on_gateway_status(gw); });
if (!me->services->is_gateway_connected(*me->session_services)) {
result.error = ORDER_ERROR_GATEWAY_NOT_READY;
result.error_msg = "Gateway is disconnected";
transition(ORDER_STATUS_DISCONNECTED);
}
break;
case ORDER_STATUS_DISCONNECTED:
me->session_services = &me->services->get_session_services(
get_native_state(me->state).routing_record().session());
me->services->subscribe_to_exe_report(
*me->session_services, key(),
[this](auto const &r) { this->on_exe_report(r); });
me->services->subscribe_to_cancel_reject(
*me->session_services, key(),
[this](auto const &cr) { this->on_cancel_reject(cr); });
me->services->subscribe_to_gateway_status(
*me->session_services,
[this](auto const &gw) { this->on_gateway_status(gw); });
if (me->services->is_gateway_connected(*me->session_services))
transition(ORDER_STATUS_WORKING);
break;
case ORDER_STATUS_PENDING_WORKING:
me->session_services = &me->services->get_session_services(
get_native_state(me->state).routing_record().session());
me->services->subscribe_to_exe_report(
*me->session_services, key(),
[this](auto const &r) { this->on_exe_report(r); });
me->services->subscribe_to_cancel_reject(
*me->session_services, key(),
[this](auto const &cr) { this->on_cancel_reject(cr); });
me->services->subscribe_to_gateway_status(
*me->session_services,
[this](auto const &gw) { this->on_gateway_status(gw); });
break;
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_ERROR:
break;
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
report_state();
return result;
}
OrderOpResult NativeOrder::on_update(Price p, Quantity q) {
auto scoped_log =
me->services->log_scope("NativeOrder::on_update", key().value());
auto result = OrderOpResult{};
switch (me->state.status()) {
case ORDER_STATUS_WORKING: {
auto previous_working_qty = 0;
if (me->state.quantity() > me->state.filled_quantity())
previous_working_qty =
me->state.quantity() - me->state.filled_quantity();
auto current_working_qty = 0;
if (q.value() > me->state.filled_quantity())
current_working_qty = q.value() - me->state.filled_quantity();
auto lqr = me->services->limiter().can_update_order(
AccountID(me->definition.account_id()), *me->sec,
me->definition.side(), Quantity(previous_working_qty),
Quantity(current_working_qty));
// are we under the position limits?
if (!lqr.ok) {
auto msg = fmt::format("Cannot update order due to limit breach: {}",
lqr.error_msg);
LOG(OrderMan, Error,
"Order update failed due to limit breach. Order: {} Msg: {}",
key().value(), lqr.error_msg);
// if we are not being driven by an algo we just signal the
// error but keep running. otherwise we stop.
if (parent_key() == null_order_key())
transition(me->state.status(), msg);
else {
me->state.set_last_msg(msg);
on_cancel();
}
result.error = api::orderman::ORDER_ERROR_LIMIT_BREACH;
result.error_msg = lqr.error_msg;
break;
}
// are we under the messaging threshold?
if (!me->services->governor(*me->session_services).can_send()) {
LOG(OrderMan, Warn,
"Cannot update order {} because of messaging limits. ",
key().value());
auto msg = "Messaging threshold breached";
// if we are not being driven by an algo we just signal the
// error but keep running. otherwise we stop.
if (parent_key() == null_order_key())
transition(me->state.status(), msg);
else {
me->state.set_last_msg(msg);
on_cancel();
}
result.error = api::orderman::ORDER_ERROR_MESSAGING_LIMIT_BREACH;
result.error_msg = msg;
break;
}
// all good
auto &encoding = me->update_encoding;
me->update_price_bm = encoding.overwrite(
me->update_price_bm,
securities::encode_exchange_price(*me->sec, p).value(),
me->price_encoding_precision, price_sz());
me->update_quantity_bm =
encoding.overwrite(me->update_quantity_bm, q.value(), quantity_sz());
auto const &ts = me->services->get_time_str();
me->update_transact_time_bm = encoding.overwrite(
me->update_transact_time_bm, &ts.value()[0], ts.value().size());
me->services->queue_fix(*me->session_services,
jetfix::MessageTypes::CancelReplace,
me->update_encoding);
me->services->governor(*me->session_services).record_sent();
update_working_position();
if (me->state.last_msg().size())
me->state.set_last_msg("");
// early return so we skip report_state (for speed)
return OrderOpResult{};
}
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_PENDING_WORKING:
LOG(OrderMan, Warn,
"Cannot apply update to order {}. Update: {}@{}. Buffering",
key().value(), q.value(), p.value());
me->buffered_update.emplace(p, q);
break;
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_ERROR:
break;
case ORDER_STATUS_INACTIVE:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
report_state();
return result;
}
OrderOpResult NativeOrder::on_update(api::orderman::Update const &update) {
return on_update(Price(update.price()), Quantity(update.quantity()));
}
OrderOpResult NativeOrder::on_detach() { return OrderOpResult{}; }
void NativeOrder::on_attached(
api::orderman::OrderDefinition const &new_parent) {
switch (me->state.status()) {
case ORDER_STATUS_PENDING_WORKING:
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_WORKING: {
LOG(OrderMan, Info, "Native order {} has been attached to by {}",
key().value(), utils::pb::to_string(new_parent));
// Update FIX tags to reflect new parent's information
auto rq = me->services->route_order(UserID(new_parent.user_id()),
AccountID(new_parent.account_id()),
*me->sec);
if (!rq.has_route) {
auto msg = fmt::format("Cannot update order routing information: {}",
rq.error_msg);
transition(me->state.status(), msg);
report_state();
return;
}
auto &native_state = get_native_state(me->state);
*native_state.mutable_routing_record() = rq.routing_record;
init_fix();
if (has_exchange_order_id())
add_exchange_order_id_to_fix();
// Update user, account and manual order indicator based on new parent
me->definition.set_manual_order_indicator(
api::orderman::ManualOrderIndicator::MANUAL_ORDER_INDICATOR_NO);
me->definition.set_user(new_parent.user());
me->definition.set_user_id(new_parent.user_id());
me->definition.set_account(new_parent.account());
me->definition.set_account_id(new_parent.account_id());
me->services->report_definition(me->definition);
report_state();
break;
}
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_ERROR:
break;
case ORDER_STATUS_INACTIVE:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
}
OrderOpResult NativeOrder::on_cancel() {
auto scoped_log =
me->services->log_scope("NativeOrder::on_cancel", key().value());
auto result = OrderOpResult{};
switch (me->state.status()) {
case ORDER_STATUS_WORKING: {
auto &encoding = me->cancel_encoding;
auto const &ts = me->services->get_time_str();
me->cancel_transact_time_bm = encoding.overwrite(
me->cancel_transact_time_bm, &ts.value()[0], ts.value().size());
me->services->queue_fix(*me->session_services,
jetfix::MessageTypes::Cancel,
me->cancel_encoding);
break;
}
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_ERROR:
break;
case ORDER_STATUS_PENDING_WORKING: {
me->buffered_cancel = true;
LOG(OrderMan, Warn,
"Cannot cancel order {} while in the pending working state. "
"Buffering cancel",
key().value());
break;
}
case ORDER_STATUS_DISCONNECTED: {
me->buffered_cancel = true;
LOG(OrderMan, Warn,
"Cannot cancel order {} while in the disconnected state. Buffering "
"cancel",
key().value());
auto msg = "Order will be canceled when re-connected";
result.error = ORDER_ERROR_GATEWAY_NOT_READY;
result.error_msg = msg;
transition(me->state.status(), msg);
break;
}
case ORDER_STATUS_INACTIVE:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
report_state();
return result;
}
bool NativeOrder::is_old_exe_report(
api::orderman::NativeOrderState &native_state,
const NewExecutionReportEvent &er) const {
if (native_state.last_transact_time().size() !=
sizeof(mercury::TimeStampStr))
return false;
if (std::lexicographical_compare(native_state.last_transact_time().begin(),
native_state.last_transact_time().end(),
er.data->transact_time.value().begin(),
er.data->transact_time.value().end()))
return false;
if (std::equal(native_state.last_transact_time().begin(),
native_state.last_transact_time().end(),
er.data->transact_time.value().begin(),
er.data->transact_time.value().end()))
return native_state.last_msg_seq_num() >= er.data->msg_seq_num;
return true;
}
void NativeOrder::on_exe_report(const NewExecutionReportEvent &er) {
auto scoped_log =
me->services->log_scope("NativeOrder::on_exe_report", key().value());
auto &state = me->state;
auto &native_state = get_native_state(state);
// if we have a previous state we want to ensure this execution
// report was not already considered.
if (is_old_exe_report(native_state, er)) {
LOG(OrderMan, Info,
"Ignoring already seen execution report {} in order {}",
er.data->msg_seq_num, key().value());
return;
}
// we do not care about spread leg fills for purposes of order
// state maintenance
if (er.data->leg_security) {
LOG(OrderMan, Diag, "Ignoring leg execution report for order {}",
key().value());
return;
}
native_state.set_last_msg_seq_num(er.data->msg_seq_num);
native_state.set_last_transact_time(to_str(er.data->transact_time));
auto external_status = map_external_status(er.data->order_status);
// filled quantity is reset back to 0 when the order is canceled
if (external_status != ORDER_STATUS_CANCELED) {
if (er.data->exec_type == api::orderman::EXEC_TYPE_PARTIAL_FILL ||
er.data->exec_type == api::orderman::EXEC_TYPE_FILL) {
bts_assert(er.data->last_shares.value());
auto previous_avg = state.avg_price();
auto previous_sz = state.filled_quantity();
auto fill_price = er.data->last_price.value();
auto fill_sz = er.data->last_shares.value();
auto new_avg =
((previous_avg * previous_sz) + (fill_price * fill_sz)) /
(previous_sz + fill_sz);
state.set_avg_price(new_avg);
}
state.set_filled_quantity(er.data->filled_quantity.value());
state.set_last_price(er.data->last_price.value());
}
state.set_last_msg(er.data->msg);
native_state.set_native_status(er.data->order_status);
native_state.set_native_type(er.data->order_type);
bool got_first_exchange_order_id = !has_exchange_order_id();
if (got_first_exchange_order_id) {
me->exchange_order_id = to_trim_str(er.data->exchange_order_id);
native_state.set_exchange_order_id(me->exchange_order_id);
add_exchange_order_id_to_fix();
}
if (!terminal_state(me->state.status())) {
state.set_price(er.data->price.value());
state.set_quantity(er.data->quantity.value());
}
switch (me->state.status()) {
// terminal states, nothing can change them
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
break;
// receiving an execution report does does not mean
// we are connected
case ORDER_STATUS_DISCONNECTED:
break;
case ORDER_STATUS_PENDING_WORKING:
if (external_status == ORDER_STATUS_WORKING &&
!me->services->is_gateway_connected(*me->session_services))
transition(ORDER_STATUS_DISCONNECTED);
else {
transition(external_status);
if (external_status == ORDER_STATUS_WORKING) {
if (me->buffered_cancel) {
LOG(OrderMan, Warn,
"Applying buffered cancel to order {}, on first ack",
key().value());
on_cancel();
}
else if (me->buffered_update.has_value()) {
auto [p, q] = me->buffered_update.value();
me->buffered_update.reset();
LOG(OrderMan, Warn,
"Applying buffered update ({}@{}) to order {}, on "
"first ack",
q.value(),p.value(),
key().value());
on_update(p, q);
}
}
}
break;
case ORDER_STATUS_WORKING:
transition(external_status);
break;
case ORDER_STATUS_INACTIVE:
case ORDER_STATUS_ERROR:
case ORDER_STATUS_REJECTED_BY_SPARK:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
report_state();
// we only need the exhange order id and the routing record
// reported once, since they are both immutable, so we clear them
// after their first report to make subsquent state updates faster
if (got_first_exchange_order_id) {
native_state.clear_exchange_order_id();
native_state.mutable_routing_record()->Clear();
}
}
void NativeOrder::on_cancel_reject(const orderman::CancelRejectEvent &cr) {
auto &state = me->state;
state.set_last_msg(cr.reject_msg);
LOG(OrderMan, Warn, "Cancel reject received for order {}", key().value());
switch (me->state.status()) {
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_PENDING_WORKING:
case ORDER_STATUS_WORKING:
break;
case ORDER_STATUS_INACTIVE:
case ORDER_STATUS_ERROR:
case ORDER_STATUS_REJECTED_BY_SPARK:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
report_state();
}
void NativeOrder::on_gateway_status(const GatewayStatusChangeEvent &ev) {
if (ev.is_connected) {
switch (me->state.status()) {
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_ERROR:
case ORDER_STATUS_PENDING_WORKING:
case ORDER_STATUS_WORKING:
break;
case ORDER_STATUS_DISCONNECTED: {
auto const &native_state = get_native_state(me->state);
transition(map_external_status(native_state.native_status()));
if (me->state.status() == ORDER_STATUS_WORKING) {
if (me->buffered_cancel) {
LOG(OrderMan, Warn,
"Applying buffered cancel to order {}, on re-connection",
key().value());
on_cancel();
} else if (me->buffered_update.has_value()) {
auto [p, q] = me->buffered_update.value();
me->buffered_update.reset();
LOG(OrderMan, Warn,
"Applying buffered update ({}@{}) to order {}, on "
"re-connection",
q.value(),p.value(),
key().value());
on_update(p, q);
}
}
break;
}
case ORDER_STATUS_INACTIVE:
case ORDER_STATUS_REJECTED_BY_SPARK:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
} else {
switch (me->state.status()) {
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_ERROR:
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_PENDING_WORKING:
break;
case ORDER_STATUS_WORKING:
transition(ORDER_STATUS_DISCONNECTED);
break;
case ORDER_STATUS_INACTIVE:
case ORDER_STATUS_REJECTED_BY_SPARK:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
break;
}
}
report_state();
}
void NativeOrder::update_working_position() {
if (me->sec) {
auto current_position = position_contribution();
auto previous_position = me->previous_position_contribution;
me->previous_position_contribution = current_position;
if (current_position.value() > previous_position.value())
me->services->limiter().add(
*me->sec, AccountID(me->definition.account_id()),
me->definition.side(),
Quantity(current_position.value() - previous_position.value()));
else if (previous_position.value() > current_position.value())
me->services->limiter().remove(
*me->sec, AccountID(me->definition.account_id()),
me->definition.side(),
Quantity(previous_position.value() - current_position.value()));
} else
LOG(OrderMan, Error,
"Order {} ignored for purposes of limit enforcing "
"because it references unknown security {} ",
key().value(), me->definition.security_key());
}
void NativeOrder::report_state() {
update_working_position();
me->services->report_state(me->state);
}
void NativeOrder::init_fix() {
bts_assert(me->sec);
using namespace jetfix;
me->create_encoding = FIXFragment{};
me->update_encoding = FIXFragment{};
me->cancel_encoding = FIXFragment{};
auto const &def = me->definition;
auto const &sec = *me->sec;
auto const &rr = get_native_state(me->state).routing_record();
std::string clordid(clordid_sz(), ' ');
encode_clorid(key(), SecurityKey(sec.id()), &clordid);
// initialize create FIX
auto &create_encoding = me->create_encoding;
create_encoding.add<fix::SenderSubID>(rr.operator_id());
create_encoding.add<fix::SenderLocationID>(rr.location());
create_encoding.add<fix::Account>(rr.account());
create_encoding.add<fix::Side>(def.side());
create_encoding.add<fix::TimeInForce>(def.time_in_force());
create_encoding.add<fix::ManualOrderIndicator>(def.manual_order_indicator());
create_encoding.add<fix::CustomerOrFirm>(rr.customer_or_firm());
create_encoding.add<fix::CtiCode>(rr.cti_code());
create_encoding.add<fix::SecurityDesc>(sec.glbx().security_desc());
create_encoding.add<fix::ClOrdID>(clordid);
if (rr.smp_id().size()) {
create_encoding.add<fix::SelfMatchPreventionID>(rr.smp_id());
if (rr.smp_instruction() != SELF_MATCH_PREVENTION_INSTRUCTION_UNDEFINED)
create_encoding.add<fix::SelfMatchPreventionInstruction>(
rr.smp_instruction());
}
me->create_transact_time_bm = create_encoding.add<fix::TransactTimeStr>(
datetime::FIX_timestamp(datetime::now()));
me->create_quantity_bm =
create_encoding.add<fix::OrderQty>(0, quantity_sz());
// initialize update FIX
auto &update_encoding = me->update_encoding;
update_encoding.add<fix::SenderSubID>(rr.operator_id());
update_encoding.add<fix::SenderLocationID>(rr.location());
update_encoding.add<fix::Account>(rr.account());
update_encoding.add<fix::Side>(def.side());
update_encoding.add<fix::TimeInForce>(def.time_in_force());
update_encoding.add<fix::ManualOrderIndicator>(def.manual_order_indicator());
update_encoding.add<fix::CtiCode>(rr.cti_code());
update_encoding.add<fix::CustomerOrFirm>(rr.customer_or_firm());
update_encoding.add<fix::SecurityDesc>(sec.glbx().security_desc());
update_encoding.add<fix::ClOrdID>(clordid);
update_encoding.add<fix::OrigClOrdID>(clordid);
update_encoding.add<fix::CorrelationClOrdID>(clordid);
update_encoding.add<fix::InFlightMitigation>(true);
// Self Match Protection
if (rr.smp_id().size()) {
update_encoding.add<fix::SelfMatchPreventionID>(rr.smp_id());
if (rr.smp_instruction() != SELF_MATCH_PREVENTION_INSTRUCTION_UNDEFINED)
update_encoding.add<fix::SelfMatchPreventionInstruction>(
rr.smp_instruction());
}
me->update_transact_time_bm = update_encoding.add<fix::TransactTimeStr>(
datetime::FIX_timestamp(datetime::now()));
me->update_quantity_bm =
update_encoding.add<fix::OrderQty>(0, quantity_sz());
// initialize cancel FIX
auto &cancel_encoding = me->cancel_encoding;
cancel_encoding.add<fix::SenderSubID>(rr.operator_id());
cancel_encoding.add<fix::SenderLocationID>(rr.location());
cancel_encoding.add<fix::Account>(rr.account());
cancel_encoding.add<fix::Side>(def.side());
cancel_encoding.add<fix::TimeInForce>(def.time_in_force());
cancel_encoding.add<fix::ManualOrderIndicator>(def.manual_order_indicator());
cancel_encoding.add<fix::SecurityDesc>(sec.glbx().security_desc());
cancel_encoding.add<fix::ClOrdID>(clordid);
cancel_encoding.add<fix::OrigClOrdID>(clordid);
cancel_encoding.add<fix::CorrelationClOrdID>(clordid);
me->cancel_transact_time_bm = cancel_encoding.add<fix::TransactTimeStr>(
datetime::FIX_timestamp(datetime::now()));
// special tags
switch (def.custom_def().custom_case()) {
case CustomOrderDefinition::kMarket:
// type
create_encoding.add<fix::OrdType>(NATIVE_ORDER_TYPE_MARKET);
update_encoding.add<fix::OrdType>(NATIVE_ORDER_TYPE_MARKET);
cancel_encoding.add<fix::OrdType>(NATIVE_ORDER_TYPE_MARKET);
break;
case CustomOrderDefinition::kLimit:
// type
create_encoding.add<fix::OrdType>(NATIVE_ORDER_TYPE_LIMIT);
update_encoding.add<fix::OrdType>(NATIVE_ORDER_TYPE_LIMIT);
cancel_encoding.add<fix::OrdType>(NATIVE_ORDER_TYPE_LIMIT);
// price
me->create_price_bm = create_encoding.add<fix::Price>(
0, me->price_encoding_precision, price_sz());
me->update_price_bm = update_encoding.add<fix::Price>(
0, me->price_encoding_precision, price_sz());
break;
case CustomOrderDefinition::kStop: {
// type
create_encoding.add<fix::OrdType>(NATIVE_ORDER_TYPE_STOP);
update_encoding.add<fix::OrdType>(NATIVE_ORDER_TYPE_STOP);
cancel_encoding.add<fix::OrdType>(NATIVE_ORDER_TYPE_STOP);
// stop px
auto stop_px = def.custom_def().stop().stop_price();
auto encoded_stop_px =
securities::encode_exchange_price(*me->sec, Price(stop_px)).value();
create_encoding.add<fix::StopPx>(encoded_stop_px,
me->price_encoding_precision, -1);
update_encoding.add<fix::StopPx>(encoded_stop_px,
me->price_encoding_precision, -1);
break;
}
case CustomOrderDefinition::kStopLimit: {
// type
create_encoding.add<fix::OrdType>(NATIVE_ORDER_TYPE_STOP_LIMIT);
update_encoding.add<fix::OrdType>(NATIVE_ORDER_TYPE_STOP_LIMIT);
cancel_encoding.add<fix::OrdType>(NATIVE_ORDER_TYPE_STOP_LIMIT);
// stop px
auto stop_px = def.custom_def().stop_limit().stop_price();
auto encoded_stop_px =
securities::encode_exchange_price(*me->sec, Price(stop_px)).value();
create_encoding.add<fix::StopPx>(encoded_stop_px,
me->price_encoding_precision, -1);
update_encoding.add<fix::StopPx>(encoded_stop_px,
me->price_encoding_precision, -1);
// price
me->create_price_bm = create_encoding.add<fix::Price>(
0, me->price_encoding_precision, price_sz());
me->update_price_bm = update_encoding.add<fix::Price>(
0, me->price_encoding_precision, price_sz());
break;
}
default:
bts_assert(false);
break;
}
}
void NativeOrder::add_exchange_order_id_to_fix() {
bts_assert(has_exchange_order_id());
me->update_encoding.add<jetfix::fix::OrderID>(me->exchange_order_id);
me->cancel_encoding.add<jetfix::fix::OrderID>(me->exchange_order_id);
}
OrderStatus map_external_status(NativeOrderStatus status) {
switch (status) {
case NATIVE_ORDER_STATUS_NEW:
case NATIVE_ORDER_STATUS_PENDING_NEW:
case NATIVE_ORDER_STATUS_PARTIALLY_FILLED:
case NATIVE_ORDER_STATUS_REPLACED:
case NATIVE_ORDER_STATUS_PENDING_CANCEL:
case NATIVE_ORDER_STATUS_STOPPED:
case NATIVE_ORDER_STATUS_SUSPENDED:
case NATIVE_ORDER_STATUS_CANCEL_ACK:
case NATIVE_ORDER_STATUS_ACCEPTED_FOR_BIDDING:
case NATIVE_ORDER_STATUS_PENDING_REPLACE:
case NATIVE_ORDER_STATUS_CALCULATED:
return ORDER_STATUS_WORKING;
case NATIVE_ORDER_STATUS_FILLED:
return ORDER_STATUS_COMPLETED;
case NATIVE_ORDER_STATUS_EXPIRED:
case NATIVE_ORDER_STATUS_CANCELED:
case NATIVE_ORDER_STATUS_REJECTED:
case NATIVE_ORDER_STATUS_DONE:
case NATIVE_ORDER_STATUS_ORDER_NOT_FOUND:
return ORDER_STATUS_CANCELED;
case NATIVE_ORDER_STATUS_UNDEFINED:
return ORDER_STATUS_ERROR;
case NATIVE_ORDER_STATUS_UNACKNOWLEDGED:
case NativeOrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case NativeOrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
return ORDER_STATUS_ERROR;
}
return ORDER_STATUS_ERROR;
}
bool NativeOrder::has_exchange_order_id() {
return me->exchange_order_id.size();
}
void NativeOrder::transition(api::orderman::OrderStatus new_status,
std::string const &msg) {
transition(Info, new_status, msg);
}
void NativeOrder::transition(log::Level level,
api::orderman::OrderStatus new_status,
std::string const &msg) {
auto const &from = OrderStatus_Name(me->state.status());
auto const &to = OrderStatus_Name(new_status);
if (from != to)
LOG(OrderMan, level, "Transitioning order {} from {} to {}. Msg: {}",
key().value(), from, to, msg);
me->state.set_status(new_status);
me->state.set_last_msg(msg);
}
void NativeOrder::transition(api::orderman::OrderStatus new_status) {
auto const &from = OrderStatus_Name(me->state.status());
auto const &to = OrderStatus_Name(new_status);
if (from != to)
LOG(OrderMan, Info, "Transitioning order {} from {} to {}", key().value(),
from, to);
else
LOG(OrderMan, Diag, "Transitioning order {} from {} to {}", key().value(),
from, to);
me->state.set_status(new_status);
}
void NativeOrder::transition_to_error(
api::orderman::NativeOrderState::SubError sub_error,
std::string const &msg) {
me->state.mutable_limit()->mutable_native_state()->set_sub_error_code(
sub_error);
transition(ORDER_STATUS_ERROR, msg);
}
Quantity NativeOrder::position_contribution() {
switch (me->state.status()) {
case ORDER_STATUS_WORKING:
case ORDER_STATUS_PENDING_WORKING:
case ORDER_STATUS_DISCONNECTED:
return Quantity(
std::max(me->state.quantity(), me->state.filled_quantity()));
case ORDER_STATUS_REJECTED_BY_SPARK:
case ORDER_STATUS_ERROR:
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
return Quantity(me->state.filled_quantity());
case ORDER_STATUS_INACTIVE:
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
bts_assert(false);
return Quantity(0);
}
bts_assert(false);
return Quantity(0);
}
bool NativeOrder::terminal_state(api::orderman::OrderStatus status) const {
switch (status) {
case ORDER_STATUS_COMPLETED:
case ORDER_STATUS_CANCELED:
case ORDER_STATUS_ERROR:
case ORDER_STATUS_REJECTED_BY_SPARK:
return true;
case ORDER_STATUS_DISCONNECTED:
case ORDER_STATUS_PENDING_WORKING:
case ORDER_STATUS_WORKING:
return false;
case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_:
case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_:
case ORDER_STATUS_INACTIVE:
bts_assert(false);
return false;
break;
}
bts_assert(false);
return false;
}
api::orderman::NativeOrderState &
NativeOrder::get_native_state(api::orderman::OrderState &st) {
switch (st.custom_case()) {
case OrderState::kMarket:
return *st.mutable_market()->mutable_native_state();
case OrderState::kLimit:
return *st.mutable_limit()->mutable_native_state();
case OrderState::kStop:
return *st.mutable_stop()->mutable_native_state();
case OrderState::kStopLimit:
return *st.mutable_stop_limit()->mutable_native_state();
default:
break;
}
// just to appease compiler
bts_assert(false);
return *st.mutable_stop_limit()->mutable_native_state();
}
} // namespace orderman
} // namespace bts