Files
spark/src/lib/orderman/orders/TimeBoxOrder.cpp
T
2018-11-13 22:15:30 +00:00

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);
}
}
}