380 lines
11 KiB
C++
380 lines
11 KiB
C++
#include "TimeBoxOrder.h"
|
|
#include "lib/utils/PBUtils.h"
|
|
#include "lib/orderman/actors/order_manager/OrderBuilder.h"
|
|
#include <google/protobuf/timestamp.pb.h>
|
|
|
|
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);
|
|
}
|
|
|
|
}
|
|
}
|
|
|