#include "TimeBoxOrder.h" #include "lib/utils/PBUtils.h" #include "lib/orderman/actors/order_manager/OrderBuilder.h" #include namespace bts { namespace orderman { using namespace api::orderman; struct TimeBoxOrder::PImpl { PImpl() {} OrderServices *services; ChildOrder child; OrderBuilder child_builder; api::orderman::OrderDefinition def; api::orderman::OrderState state; api::orderman::OrderState child_state; google::protobuf::Timestamp start_time; google::protobuf::Timestamp stop_time; }; TimeBoxOrder::TimeBoxOrder() : me(new PImpl()) {} TimeBoxOrder::~TimeBoxOrder() {} api::orderman::OrderDefinition const &TimeBoxOrder::definition() const { return me->def; } void TimeBoxOrder::on_init_new(api::orderman::OrderDefinition const &def, const PreActivationOrderServices &services) { me->services = nullptr; me->def = def; me->state.mutable_time_box(); auto const &timebox_def = def.custom_def().time_box(); me->start_time = timebox_def.start(); me->stop_time = timebox_def.stop(); me->child_state.set_status(ORDER_STATUS_INACTIVE); auto child_def = def; *child_def.mutable_custom_def() = timebox_def.child(); if (me->start_time.seconds() > utils::pb::pb_now().seconds() && me->stop_time.seconds() > me->start_time.seconds()) { me->child_builder = OrderBuilder(key(), child_def, services, 1); transition(ORDER_STATUS_INACTIVE); } else { auto msg = fmt::format("Invalid time"); me->state.set_last_msg(msg); transition(ORDER_STATUS_REJECTED_BY_SPARK, msg); } } void TimeBoxOrder::on_init_recovered(api::orderman::OrderRecord const &rec, const PreActivationOrderServices &) { me->services = nullptr; auto const &def = rec.def(); me->def = def; me->state = rec.state(); switch (me->state.status()) { case ORDER_STATUS_WORKING: { auto msg = "System restarted with active time box order"; me->state.set_last_msg(msg); transition(ORDER_STATUS_ERROR, msg); break; } // terminal states case ORDER_STATUS_ERROR: case ORDER_STATUS_REJECTED_BY_SPARK: case ORDER_STATUS_COMPLETED: case ORDER_STATUS_CANCELED: break; // impossible to reach states for this order type case ORDER_STATUS_DISCONNECTED: case ORDER_STATUS_PENDING_WORKING: case ORDER_STATUS_INACTIVE: case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_: case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_: bts_assert(false); break; } } void TimeBoxOrder::on_activate(Price p, Quantity q, OrderServices &services,PerfTrace const&) { bts_assert(!me->services); me->services = &services; me->child_builder.activate(services); switch (me->state.status()) { case ORDER_STATUS_INACTIVE: me->child = me->child_builder.build(); me->state.set_price(p.value()); me->state.set_quantity(q.value()); services.child_updates.sub( key(), [this](auto const &os) { this->on_child_order_update(os); }); services.time_change.sub([this](auto) { this->on_time_change(); }); transition(ORDER_STATUS_WORKING); break; // terminal states case ORDER_STATUS_ERROR: case ORDER_STATUS_REJECTED_BY_SPARK: case ORDER_STATUS_COMPLETED: case ORDER_STATUS_CANCELED: break; // impossible to reach states at this stage (on recovery from disk // we would have transitioned to error if not already in a terminal // state) case ORDER_STATUS_WORKING: case ORDER_STATUS_PENDING_WORKING: case ORDER_STATUS_DISCONNECTED: case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_: case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_: bts_assert(false); break; } report_state(); } void TimeBoxOrder::on_update(Price p, Quantity q,PerfTrace const&) { LOG(OrderMan, Info, "Updating TimeBox Order {}", key().value()); switch (me->state.status()) { case ORDER_STATUS_WORKING: // what is state of our child order? switch (me->child_state.status()) { case ORDER_STATUS_INACTIVE: // we have yet to launch it me->state.set_price(p.value()); me->state.set_quantity(q.value()); break; case ORDER_STATUS_WORKING: // we launched our child, everything is nominal me->state.set_price(p.value()); me->state.set_quantity(q.value()); me->child.update(p,q); break; case ORDER_STATUS_PENDING_WORKING: // we launched our child but we are not yet able // to update it transition(me->state.status(), "Cannot update order"); break; // if the child was in any of these states we couldn't be // in the working state case ORDER_STATUS_DISCONNECTED: case ORDER_STATUS_ERROR: case ORDER_STATUS_REJECTED_BY_SPARK: case ORDER_STATUS_COMPLETED: case ORDER_STATUS_CANCELED: case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_: case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_: bts_assert(false); break; } break; case ORDER_STATUS_REJECTED_BY_SPARK: case ORDER_STATUS_COMPLETED: case ORDER_STATUS_CANCELED: case ORDER_STATUS_ERROR: transition(me->state.status(), "Cannot update order"); break; // these are all unreacheable for this order type at this point case ORDER_STATUS_INACTIVE: case ORDER_STATUS_DISCONNECTED: case ORDER_STATUS_PENDING_WORKING: case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_: case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_: bts_assert(false); break; } report_state(); } void TimeBoxOrder::on_crack(PerfTrace const&) {} void TimeBoxOrder::on_cancel(PerfTrace const&) { LOG(OrderMan, Info, "Canceling TimeBox Order {}", key().value()); switch (me->state.status()) { case ORDER_STATUS_WORKING: // how is our child doing? switch (me->child_state.status()) { case ORDER_STATUS_INACTIVE: // we have not yet launched the child, so nothing to do transition(ORDER_STATUS_CANCELED); break; case ORDER_STATUS_WORKING: // child is running, we need to send it a cancel me->child.cancel(); transition(ORDER_STATUS_CANCELED); break; case ORDER_STATUS_PENDING_WORKING: // child is running but it cannot be canceled at this point transition(me->state.status(), "Cannot cancel order"); break; // child cannot be in any of these if we are in the working state case ORDER_STATUS_DISCONNECTED: case ORDER_STATUS_ERROR: case ORDER_STATUS_REJECTED_BY_SPARK: case ORDER_STATUS_COMPLETED: case ORDER_STATUS_CANCELED: case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_: case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_: bts_assert(false); break; } break; case ORDER_STATUS_REJECTED_BY_SPARK: case ORDER_STATUS_COMPLETED: case ORDER_STATUS_CANCELED: case ORDER_STATUS_ERROR: transition(me->state.status(), "Cannot cancel order"); break; // these are unreacheable for this order type case ORDER_STATUS_PENDING_WORKING: case ORDER_STATUS_DISCONNECTED: 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(); } void TimeBoxOrder::transition(api::orderman::OrderStatus new_status, std::string msg) { auto const &from = OrderStatus_Name(me->state.status()); auto const &to = OrderStatus_Name(new_status); LOG(OrderMan, Info, "Transitioning time box order {} from {} to {}. Msg: {}", key().value(), from, to, msg); me->state.set_status(new_status); } void TimeBoxOrder::on_time_change() { auto now = utils::pb::pb_now().seconds(); switch (me->state.status()) { case ORDER_STATUS_WORKING: // what is the state of our child? switch (me->child_state.status()) { case ORDER_STATUS_INACTIVE: { // we haven't yet launched the child, check the time and // launch it if appropriate if ( now >= me->start_time.seconds() && now < me->stop_time.seconds()) { LOG(OrderMan, Info, "TimeBox order {} launching child {}", key().value(), me->child.key().value()); me->child.activate(Price(me->state.price()), Quantity(me->state.quantity())); me->child_state.set_status(ORDER_STATUS_PENDING_WORKING); transition(ORDER_STATUS_WORKING); report_state(); } break; } case ORDER_STATUS_WORKING: // child is already running, could it be time to stop it? if (now >= me->stop_time.seconds()) { LOG(OrderMan, Info, "TimeBox order {} canceling child {}", key().value(), me->child.key().value()); me->child.cancel(); transition(ORDER_STATUS_CANCELED); report_state(); } break; case ORDER_STATUS_PENDING_WORKING: break; // child cannot be in any of these states if we are in the working state case ORDER_STATUS_DISCONNECTED: case ORDER_STATUS_ERROR: case ORDER_STATUS_REJECTED_BY_SPARK: case ORDER_STATUS_COMPLETED: case ORDER_STATUS_CANCELED: case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_: case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_: bts_assert(false); break; } break; case ORDER_STATUS_COMPLETED: case ORDER_STATUS_CANCELED: case ORDER_STATUS_ERROR: break; case ORDER_STATUS_INACTIVE: case ORDER_STATUS_REJECTED_BY_SPARK: case ORDER_STATUS_PENDING_WORKING: case ORDER_STATUS_DISCONNECTED: case OrderStatus_INT_MIN_SENTINEL_DO_NOT_USE_: case OrderStatus_INT_MAX_SENTINEL_DO_NOT_USE_: bts_assert(false); break; } } void TimeBoxOrder::on_child_order_update(OrderStateUpdateEvent const&ev) { me->child_state = ev.rebuild_state(); switch (me->state.status()) { case ORDER_STATUS_WORKING: switch (ev.status) { case ORDER_STATUS_WORKING: case ORDER_STATUS_PENDING_WORKING: // nothing to do but report new state break; case ORDER_STATUS_COMPLETED: transition(ORDER_STATUS_COMPLETED); break; case ORDER_STATUS_DISCONNECTED: case ORDER_STATUS_REJECTED_BY_SPARK: case ORDER_STATUS_CANCELED: case ORDER_STATUS_ERROR: transition(ORDER_STATUS_ERROR, "Unexpected child order state"); 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; } break; case ORDER_STATUS_CANCELED: case ORDER_STATUS_ERROR: case ORDER_STATUS_COMPLETED: break; case ORDER_STATUS_DISCONNECTED: case ORDER_STATUS_PENDING_WORKING: case ORDER_STATUS_REJECTED_BY_SPARK: 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(); } void TimeBoxOrder::report_state() { auto merged_state = me->state; merged_state.set_filled_quantity (me->child_state.filled_quantity()); merged_state.set_last_price ( me->child_state.last_price()); merged_state.set_avg_price ( me->child_state.avg_price()); me->services->report_state(merged_state); } } }