Compare commits

...
Author SHA1 Message Date
Eric Harding fc053f2aea Add some notes 2019-06-06 12:26:47 -04:00
Eric Harding f95a1df158 SP-15 Create bare bones trigger algo 2019-06-06 12:20:42 -04:00
7 changed files with 261 additions and 672 deletions

No files matched your search

-1
View File
@@ -519,7 +519,6 @@ message TriggerPayupPrice{
LAST_TRADE_PRICE = 2; //unused
TRIGGER=3;
}
TriggerPriceType price_type = 10; // defunct
TriggerPayupPriceType payup_price_type = 15;
int32 payup_ticks = 20;
}
@@ -12,13 +12,13 @@
#include "lib/orderman/orders/DynamicOrder.h"
#include "lib/orderman/orders/NativeOrder.h"
#include "lib/orderman/orders/TimeBoxOrder.h"
#include "lib/orderman/orders/TriggerOrder.h"
#include "lib/orderman/orders/algo/AlgoWrapperOrder.h"
#include "lib/orderman/orders/algo/algo_orders/CoverAlgo.h"
#include "lib/orderman/orders/algo/algo_orders/JoinAlgo.h"
#include "lib/orderman/orders/algo/algo_orders/TopAlgo.h"
#include "lib/orderman/orders/algo/algo_orders/IcebergAlgo.h"
#include "lib/orderman/orders/algo/algo_orders/LeanAlgo.h"
#include "lib/orderman/orders/algo/algo_orders/TriggerAlgo.h"
#include "lib/orderman/orders/book_algos/OCOOrder.h"
#include "lib/orderman/orders/book_algos/OffsetOrder.h"
#include "lib/orderman/orders/book_algos/PricerOrder.h"
@@ -176,7 +176,7 @@ Order &OrderFactory::allocate(OrderKey k, OrderKey pk,
new orderman::AlgoWrapperOrder(std::make_unique<TopAlgo>()));
break;
case CustomOrderDefinition::kTrigger:
order_ptr.reset(new orderman::TriggerOrder());
order_ptr.reset( new orderman::AlgoWrapperOrder(std::make_unique<TriggerAlgo>()));
break;
case CustomOrderDefinition::kCover:
order_ptr.reset(
-534
View File
@@ -1,534 +0,0 @@
#include "TriggerOrder.h"
#include "lib/domain_model/Security.h"
#include "lib/logging/logging.h"
#include "lib/orderman/actors/order_manager/OrderBuilder.h"
#include "lib/orderman/events/BookUpdateEvent.h"
#include "lib/orderman/orders/spreader_orders/spreader/SpreaderUtils.h"
#include "lib/os/os.h"
#include "lib/utils/PBUtils.h"
#include "lib/orderman/orders/PreActivationOrderServices.h"
#include <algorithm>
#include <sstream>
namespace bts {
namespace orderman {
struct TriggerOrder::PImpl {
OrderServices *services;
api::orderman::OrderDefinition order_def;
double tick_size;
// true if timeout_exit is enabled
bool timeout_exit;
uint32_t timeout_msecs;
// true position cond exit is enabled
bool price_cond_exit;
api::orderman::OrderState state;
// child state copied directly from the
// child order udpate callback
api::orderman::OrderState child_state;
OrderBuilder order_builder;
ChildOrder child_order;
// true if child order has been placed once
bool order_placed;
Book book;
CancelMode cancel_mode;
// this tells that when the order is placed
// initially, if the trigger price above the
// market (trigger_condition's chosen market)
bool trigger_above_market;
// true is non 0 market corresponding
// to the trigger market is obs'ed once
bool market_observed_once;
bool is_recovered;
bool is_succ_inited;
bts::TimeStamp child_order_time;
PImpl()
: services(nullptr), order_def(), timeout_exit(false),
timeout_msecs(std::numeric_limits<uint32_t>::max()),
price_cond_exit(false), state(), child_state(), child_order(),
order_placed(false), book(), cancel_mode(CancelMode::none),
market_observed_once(false), is_recovered(false),
is_succ_inited(false) {}
};
TriggerOrder::TriggerOrder() : _state(State::START), me(new PImpl()) {}
TriggerOrder::~TriggerOrder() {}
const api::orderman::OrderDefinition &TriggerOrder::definition() const {
return me->order_def;
}
void TriggerOrder::on_init_new(const api::orderman::OrderDefinition &order_def,
const PreActivationOrderServices &pre_os) {
LOG(OrderMan, Info, "TriggerOrder::on_init_new key:{} state:{}", key(),
_state);
switch (_state) {
case State::START:
me->is_succ_inited = _on_init_new(order_def, pre_os);
break;
case State::WAITING_FOR_TRIGGER:
case State::WAITING_FOR_FIRST_ACK:
case State::STEADY:
case State::END:
std::ostringstream oss;
oss << "TriggerOrder::on_init_new key:" << key() << "state:" << _state;
bts_assert_msg(false, oss.str());
break;
}
}
void TriggerOrder::on_init_recovered(const api::orderman::OrderRecord &rec,
const PreActivationOrderServices &pre_os) {
LOG(OrderMan, Info, "TriggerOrder::on_init_recovered key:{} state:{}", key(),
_state);
switch (_state) {
case State::START:
me->is_succ_inited = _on_init_new(rec.def(), pre_os);
// get the state from what was recovered (we will only use
// it for reporting)
me->state = rec.state();
// if it was not in a terminal state, we will call it cancelled
if (!SpreaderUtils::order_in_terminal_state(me->state.status()))
me->state.set_status(
api::orderman::OrderStatus::ORDER_STATUS_CANCELED);
me->is_recovered = true;
break;
case State::WAITING_FOR_TRIGGER:
case State::WAITING_FOR_FIRST_ACK:
case State::STEADY:
case State::END:
std::ostringstream oss;
oss << "TriggerOrder::on_init_recovered key:" << key()
<< "state:" << _state;
bts_assert_msg(false, oss.str());
break;
}
}
OrderOpResult TriggerOrder::on_activate(Price price, Quantity qty,
OrderServices &services) {
LOG(OrderMan, Info,
"TriggerOrder::on_activate key:{} state:{} price:{} qty:{}", key(),
_state, price, qty);
switch (_state) {
case State::START:
me->services = &services;
me->state.set_price(price.value());
me->state.set_quantity(qty.value());
if (me->is_recovered || !me->is_succ_inited)
_switch_state(State::END);
else {
me->order_builder.activate(services);
me->child_order = me->order_builder.build();
services.subscribe_to_child_updates(
[this](const OrderStateUpdateEvent &os) {
this->_on_child_order_update(os);
});
services.subscribe_to_book(SecurityKey(me->order_def.security_key()),
[this](const BookUpdateEvent &bk_event) {
this->_on_book_update(bk_event);
});
services.subscribe_to_time(
[this](const bts::mercury::TimerEvent &te) { _on_time(te); });
_switch_state(State::WAITING_FOR_TRIGGER);
}
break;
case State::WAITING_FOR_TRIGGER:
case State::WAITING_FOR_FIRST_ACK:
case State::STEADY:
case State::END: {
std::ostringstream oss;
oss << "TriggerOrder::on_activate key:" << key() << "state:" << _state;
bts_assert_msg(false, oss.str());
break;
}
}
return OrderOpResult{};
}
// order price, qty change
OrderOpResult TriggerOrder::on_update(Price price, Quantity qty) {
LOG(OrderMan, Info,
"TriggerOrder::on_update key:{} state:{} price:{} qty:{}", key(), _state,
price, qty);
switch (_state) {
case State::START:
me->state.set_price(price.value());
me->state.set_quantity(qty.value());
// cant report because does not have order_services yet
break;
case State::WAITING_FOR_TRIGGER:
me->state.set_price(price.value());
me->state.set_quantity(qty.value());
_report_state();
_analyze_trigger_and_shoot();
break;
case State::WAITING_FOR_FIRST_ACK:
case State::STEADY:
me->state.set_last_msg(
"Trigger order can not be modified after the trigger has been hit");
_report_state();
break;
case State::END:
break;
}
return OrderOpResult{};
}
OrderOpResult TriggerOrder::on_cancel() {
LOG(OrderMan, Info, "TriggerOrder::on_cancel key:{} state:{}", key(),
_state);
me->cancel_mode = CancelMode::external;
switch (_state) {
case State::START:
_switch_state(State::END);
break;
case State::WAITING_FOR_TRIGGER:
_switch_state(State::END);
break;
case State::WAITING_FOR_FIRST_ACK:
// we dont have a WAITING_FOR_ACK_CANCELLED
// (like the Quoter), so this message is
// rejected and reported
me->state.set_last_msg("Order is in flight");
_report_state();
break;
case State::STEADY:
_cancel_order(CancelMode::external);
break;
case State::END:
break;
}
return OrderOpResult{};
}
void TriggerOrder::_on_book_update(const BookUpdateEvent &b_event) {
switch (_state) {
case State::START:
break;
case State::WAITING_FOR_TRIGGER:
if (b_event.key().value() == me->order_def.security_key()) {
me->book = b_event.book;
_observe_market_once();
_analyze_trigger_and_shoot();
}
break;
case State::WAITING_FOR_FIRST_ACK:
break;
case State::STEADY:
if (b_event.key().value() == me->order_def.security_key()) {
if (me->price_cond_exit) {
me->book = b_event.book;
_analyze_trigger_and_cancel();
}
}
case State::END:
break;
}
}
void TriggerOrder::_on_child_order_update(const OrderStateUpdateEvent &o_ev) {
if (o_ev.key == me->child_order.key()) {
auto ev_state = o_ev.rebuild_state();
me->child_state = ev_state;
switch (_state) {
case State::START:
case State::WAITING_FOR_TRIGGER:
bts_assert(false);
break;
case State::WAITING_FOR_FIRST_ACK:
if (SpreaderUtils::order_acknowledged(o_ev.status)) {
if (SpreaderUtils::order_in_terminal_state(o_ev.status))
_switch_state(State::END);
else
_switch_state(State::STEADY);
}
break;
case State::STEADY:
if (SpreaderUtils::order_in_terminal_state(ev_state.status()))
_switch_state(State::END);
break;
case State::END:
break;
}
_report_state();
}
}
void TriggerOrder::_on_time(const bts::mercury::TimerEvent &) {
switch (_state) {
case State::START:
case State::WAITING_FOR_TRIGGER:
case State::WAITING_FOR_FIRST_ACK:
break;
case State::STEADY: {
if (me->timeout_exit)
_analyze_trigger_and_cancel();
break;
}
case State::END:
break;
}
}
bool TriggerOrder::_on_init_new(const api::orderman::OrderDefinition &order_def,
const PreActivationOrderServices &pre_os) {
me->order_def = order_def;
if (!me->order_def.has_custom_def() ||
!me->order_def.custom_def().has_trigger()) {
LOG(OrderMan, Error,
"TriggerOrder::_on_init_new key:{} state:{} "
"OrderDefinition does not have TriggerOrderDefinition",
key(), _state);
me->state.set_last_msg("Trigger Order not properly formed");
return false;
}
if (pre_os.sec_db.resolve(SecurityKey(order_def.security_key())) ==
nullptr) {
LOG(OrderMan, Error,
"TriggerOrder::_on_init_new key:{} state:{} "
"Could not resolve sec_key:{}",
key(), _state, order_def.security_key());
me->state.set_last_msg("Could not resolve Trigger Order security");
return false;
}
const auto &trigger_order_def = order_def.custom_def().trigger();
if (pre_os.sec_db.resolve(SecurityKey(
trigger_order_def.trigger_action().security_id())) == nullptr) {
LOG(OrderMan, Error,
"TriggerOrder::_on_init_new key:{} state:{} "
"Could not resolve child key sec_key:{}",
key(), _state, trigger_order_def.trigger_action().security_id());
me->state.set_last_msg("Could not resolve Trigger Order child security");
return false;
}
if (!trigger_order_def.trigger_action().has_payup() &&
bts::os::is_nan(trigger_order_def.trigger_action().price())) {
LOG(OrderMan, Error,
"TriggerOrder::_on_init_new key:{} state:{} "
"illegal child order price",
key(), _state);
me->state.set_last_msg("Illegal Child Order price");
return false;
}
me->timeout_exit = trigger_order_def.has_exit_condition() &&
trigger_order_def.exit_condition().timeout_enabled();
me->timeout_msecs =
trigger_order_def.has_exit_condition()
? 1000.0 * trigger_order_def.exit_condition().timeout_seconds()
: std::numeric_limits<uint32_t>::max();
me->price_cond_exit =
trigger_order_def.has_exit_condition() &&
trigger_order_def.exit_condition().conditional_cancel();
api::orderman::OrderDefinition child_order_def = order_def;
child_order_def.set_security_key(
trigger_order_def.trigger_action().security_id());
child_order_def.mutable_custom_def()->CopyFrom(trigger_order_def.child());
me->order_builder = OrderBuilder(key(), child_order_def, pre_os.order_factory);
const auto *sec_ptr =
pre_os.sec_db.resolve(SecurityKey(order_def.security_key()));
me->tick_size = std::min(sec_ptr->contract_spec().low_tick(),
sec_ptr->contract_spec().high_tick());
LOG(OrderMan, Info,
"TriggerOrder::_on_init_new key:{} state:{} "
"trigger_price_type:{} child_order_price:{}",
key(), _state, trigger_order_def.trigger_condition().price_type(),
trigger_order_def.trigger_action().price());
return true;
}
void TriggerOrder::_report_state() {
if (_activated()) {
_compute_state();
me->services->report_state(me->state);
}
}
void TriggerOrder::_switch_state(State new_state) {
switch (new_state) {
case State::START:
break;
case State::WAITING_FOR_TRIGGER:
bts_assert(_state == State::START);
break;
case State::WAITING_FOR_FIRST_ACK:
bts_assert(_state == State::WAITING_FOR_TRIGGER);
break;
case State::STEADY:
bts_assert(_state == State::WAITING_FOR_FIRST_ACK);
break;
case State::END:
bts_assert(_state != State::END);
break;
}
LOG(OrderMan, Info, "TriggerOrder::_report_state key:{} {}->{}", key(),
_state, new_state);
_state = new_state;
_report_state();
}
void TriggerOrder::_analyze_trigger_and_shoot() {
if (me->market_observed_once) {
double trigger_price = _trigger_price();
const md::PriceSize *obs_md = _observed_market();
if (_trigger_met(obs_md, trigger_price, me->tick_size,
me->trigger_above_market)) {
me->child_order_time = bts::Timer::now();
me->order_placed = true;
LOG(OrderMan, Info,
"TriggerOrder::_analyze_trigger_and_shoot"
"key:{} state:{} "
"trigger_above_md:{} trigger_price:{} md:{} "
"order_price:{} order_qty:{}",
key(), _state, me->trigger_above_market, trigger_price,
obs_md->price(), _child_order_price(), me->state.quantity());
me->child_order.activate(Price(_child_order_price()),
Quantity(me->state.quantity()));
_switch_state(State::WAITING_FOR_FIRST_ACK);
}
}
}
void TriggerOrder::_analyze_trigger_and_cancel() {
if (me->timeout_exit) {
int64_t time_diff = ms_diff(bts::Timer::now(), me->child_order_time);
if (time_diff > me->timeout_msecs) {
LOG(OrderMan, Info,
"TriggerOrder::_analyze_trigger_and_cancel "
"key:{} state:{} msecs:{} "
"time out, cancelling order",
key(), _state, time_diff);
_cancel_order(CancelMode::trigger);
return;
}
}
if (me->price_cond_exit) {
double trigger_price = _trigger_price();
const md::PriceSize *obs_md = _observed_market();
if (!_trigger_met(obs_md, trigger_price, me->tick_size,
me->trigger_above_market)) {
LOG(OrderMan, Info,
"TriggerOrder::_analyze_trigger_and_cancel"
"key:{} state:{} "
"price condition untrue, cancelling order"
"trigger_above_md:{} trigger_price:{} md:{} ",
key(), _state, me->trigger_above_market, trigger_price,
obs_md->price());
_cancel_order(CancelMode::trigger);
return;
}
}
}
api::orderman::OrderStatus TriggerOrder::_status() const {
switch (_state) {
// will only be called once the order
// has been activated
case State::START:
case State::WAITING_FOR_TRIGGER:
case State::WAITING_FOR_FIRST_ACK:
case State::STEADY:
return api::orderman::OrderStatus::ORDER_STATUS_WORKING;
case State::END:
if (!me->is_succ_inited)
return api::orderman::OrderStatus::ORDER_STATUS_ERROR;
else {
if (me->order_placed) {
if (!SpreaderUtils::order_in_terminal_state(
me->child_state.status()))
return api::orderman::OrderStatus::ORDER_STATUS_WORKING;
else {
if (SpreaderUtils::order_in_error_state(
me->child_state.status()))
return api::orderman::OrderStatus::ORDER_STATUS_ERROR;
else if (SpreaderUtils::order_in_completed_state(
me->child_state.status()))
return api::orderman::OrderStatus::ORDER_STATUS_COMPLETED;
else if (SpreaderUtils::order_in_cancelled_state(
me->child_state.status())) {
switch (me->cancel_mode) {
case CancelMode::none: // this means an unscolicted child cxl
case CancelMode::external:
return api::orderman::OrderStatus::ORDER_STATUS_CANCELED;
case CancelMode::trigger:
return api::orderman::OrderStatus::ORDER_STATUS_COMPLETED;
}
}
}
} else
return api::orderman::OrderStatus::ORDER_STATUS_CANCELED;
}
}
bts_assert(false);
return api::orderman::OrderStatus::ORDER_STATUS_INACTIVE;
}
void TriggerOrder::_compute_state() {
if (!me->is_recovered) {
me->state.set_status(_status());
me->state.set_filled_quantity(me->child_state.filled_quantity());
me->state.set_last_price(me->child_state.last_price());
me->state.set_avg_price(me->child_state.avg_price());
if (_state == State::STEADY)
me->state.set_display_status(bts::api::orderman::OrderState::HIDDEN);
else
me->state.set_display_status(bts::api::orderman::OrderState::VISIBLE);
}
}
void TriggerOrder::_observe_market_once() {
if (!me->market_observed_once) {
const md::PriceSize *obs_md = _observed_market();
if (obs_md != nullptr && obs_md->size() > 0) {
me->market_observed_once = true;
me->trigger_above_market = _trigger_price() >= obs_md->price();
LOG(OrderMan, Info,
"TriggerOrder::_observe_market_once "
"key:{} state:{} "
"md_p:{} md_q:{} trigger_above_md:{}",
key(), _state, obs_md->price(), obs_md->size(),
me->trigger_above_market);
}
}
}
const md::PriceSize *TriggerOrder::_observed_market() const {
if (me->order_def.custom_def().trigger().trigger_condition().price_type() ==
api::orderman::TriggerPriceType::BID) {
const auto &rv = me->book.bid()[0];
return &rv;
} else if (me->order_def.custom_def()
.trigger()
.trigger_condition()
.price_type() == api::orderman::TriggerPriceType::ASK) {
const auto &rv = me->book.ask()[0];
return &rv;
} else {
bts_assert(false);
return nullptr;
}
}
double TriggerOrder::_trigger_price() const { return me->state.price(); }
double TriggerOrder::_child_order_price() const {
const auto &trigger_action =
me->order_def.custom_def().trigger().trigger_action();
double rv = bts::os::nan();
if (trigger_action.has_payup()) {
double offset = trigger_action.payup().payup_ticks() * me->tick_size;
if (me->order_def.side() == api::Side::SIDE_BUY)
rv = _trigger_price() + offset;
else
rv = _trigger_price() - offset;
} else
rv = trigger_action.price();
rv = bts::securities::raw_round_to_quote_price(rv, me->tick_size,
me->order_def.side());
return rv;
}
void TriggerOrder::_cancel_order(CancelMode cancel_mode) {
me->cancel_mode = cancel_mode;
me->child_order.cancel();
LOG(OrderMan, Info, "TriggerOrder::_cancel_order key:{} state:{} mode:{}",
key(), _state, cancel_mode);
_switch_state(State::END);
}
bool TriggerOrder::_activated() const { return me->services != nullptr; }
bool TriggerOrder::_gr_than_eq(double p1, double p2, double tick_size) {
return bts::securities::test_price_equality(p1, p2, tick_size) || p1 >= p2;
}
bool TriggerOrder::_trigger_met(const md::PriceSize *obs_md, double trpr,
double tick_size, bool trigger_above_md) {
if (obs_md != nullptr)
return (trigger_above_md &&
_gr_than_eq(obs_md->price(), trpr, tick_size)) ||
(!trigger_above_md &&
_gr_than_eq(trpr, obs_md->price(), tick_size));
return false;
}
} // namespace orderman
} // namespace bts
-112
View File
@@ -1,112 +0,0 @@
#pragma once
#include <memory>
#include "lib/orderman/orders/Order.h"
#include "lib/api/services/OrderService.server.pb.h"
namespace bts {
namespace api {namespace orderman {class OrderDefinition;}}
namespace mercury {class TimerEvent;}
namespace md {class PriceSize;}
namespace orderman {
class OrderStateUpdateEvent;
class BookUpdateEvent;
class PreActivationOrderServices;
class TriggerOrder : public Order
{
private:
enum State{
// remains here till on_actiavte is called
START=0,
// waiting for trigger conditions, sends
// order and waits to steady
WAITING_FOR_TRIGGER=1,
// once the order is submitted it gets into
// this state until getting a callback
// from the child order
WAITING_FOR_FIRST_ACK=2,
// order is out
STEADY=3,
END=4
};
// this is the ways a child order
// can be cancelled
enum CancelMode{
// not cancelled, initial value
none=0,
// meaning the trigger order
// cancelled the child order because
// of time or conditional reasons
trigger,
// the trigger order is itself
// externally cancelled
// through the cancel call
external
};
public:
TriggerOrder();
~TriggerOrder();
api::orderman::OrderDefinition const& definition() const ;
void on_init_new(api::orderman::OrderDefinition const&,
const PreActivationOrderServices&) override;
void on_init_recovered(api::orderman::OrderRecord const&,
const PreActivationOrderServices &) override;
OrderOpResult on_activate(Price, Quantity,
OrderServices&) override ;
OrderOpResult on_update(Price, Quantity) override;
OrderOpResult on_cancel() override;
private:
// callbacks from book and child order udpates
void _on_book_update(const BookUpdateEvent&);
void _on_child_order_update(OrderStateUpdateEvent const&);
void _on_time (const bts::mercury::TimerEvent&);
private:
// called from init_new
bool _on_init_new(const api::orderman::OrderDefinition& order_def,
const PreActivationOrderServices& pre_os);
void _report_state();
void _switch_state(State new_state);
// this is called in the WAITING_FOR_TRIGGER state,
// to check for the trigger condition and shoot
// an order if the condition is met
void _analyze_trigger_and_shoot();
// this is called in the STEADY state when the
// child order is working. (Based on the
// parameters) if timed-out or the trigger
// condition being false, the
// child order is cancelled
void _analyze_trigger_and_cancel();
api::orderman::OrderStatus _status() const;
void _compute_state();
// for non trailing trigger prices, we need a
// notion if initially the trigger price
// was over the observed market or not
void _observe_market_once();
// based on the trigger market to watch returns
// a pointer to the market (bid, ask, ltp). If not
// present returns a nullptr
const md::PriceSize* _observed_market() const;
// this is the price of the parent order
// (the trigger price)
double _trigger_price() const;
// this is in the trigger action
double _child_order_price() const;
void _cancel_order(CancelMode cancel_mode);
bool _activated() const;
//returns true is the trigger condition is met
static bool _trigger_met(const md::PriceSize* obs_md,
double trigger_price,
double tick_size,
bool trigger_above_md);
//returns true is p1 >= p2
static bool _gr_than_eq(double p1, double p2, double tick_size);
private:
State _state;
struct PImpl;
std::unique_ptr<PImpl> me;
};
}}
@@ -0,0 +1,156 @@
#include "TriggerAlgo.h"
#include "lib/orderman/orders/algo/IAlgoServices.h"
#include "lib/domain_model/Security.h"
#include "lib/logging/logging.h"
#include "lib/orderman/order_factory/IOrderFactory.h"
#include "lib/api/services/OrderService.pb.h"
#include "lib/utils/PBUtils.h"
namespace bts {
namespace orderman {
using namespace api::orderman;
enum class Status { PAUSED, WORKING };
struct TriggerAlgo::Impl {
IAlgoServices *algo = nullptr;
Status status = Status::PAUSED;
OrderKey child_key;
ChildTemplate *child_template = nullptr;
TriggerOrderDefinition definition;
};
bool check_trigger(const TriggerCondition&) { return false; }
bool check_trigger(const TriggerExitCondition&) { return false; }
TriggerAlgo::TriggerAlgo() :
me(new Impl())
{ }
TriggerAlgo::~TriggerAlgo() { }
// Not latency sensitive. Do expensive initialization here
OrderOpResult TriggerAlgo::do_before_launch(IBeforeLaunchAlgoServices& blas) {
const auto& def = blas.definition();
// Validation
bts_assert(def.has_custom_def());
bts_assert(def.custom_def().has_trigger());
const auto& trigdef = def.custom_def().trigger();
me->definition = trigdef;
if (!trigdef.has_child()) {
return OrderOpResult{ORDER_ERROR_INVALID_ORDER_DEFINITON,
"Order must have either a child order definition "};
}
if (!trigdef.has_trigger_condition()) {
return OrderOpResult{ORDER_ERROR_INVALID_ORDER_DEFINITON,
"Order must have trigger condition "};
}
if(!trigdef.has_exit_condition()) {
return OrderOpResult{ORDER_ERROR_INVALID_ORDER_DEFINITON,
"Order must have exit condition "};
}
init_child_template(blas, trigdef.child());
return {};
}
void TriggerAlgo::do_launch(IAlgoServices& algo) {
me->algo = &algo;
LOG(OrderMan, Diag, "Starting Trigger Order {}. {} {}@{} Def: {}",
algo.order_key().value(),
securities::side_name(algo.definition().side()), algo.quantity().value(),
algo.price().value(), utils::pb::to_string(algo.definition()));
algo.subscribe_timer();
me->status = Status::PAUSED;
on_timer();
}
void TriggerAlgo::do_launch_attached(IAlgoServices& algo, OrderKey k, ChildState state) {
me->algo = &algo;
LOG(OrderMan, Diag, "Starting Trigger Order {} attached to child {}. {} {}@{} Def: {}",
algo.order_key().value(), k.value(),
securities::side_name(algo.definition().side()), algo.quantity().value(),
algo.price().value(), utils::pb::to_string(algo.definition()));
algo.subscribe_timer();
me->child_key = k;
me->status = Status::WORKING;
on_child_change(k,state);
on_timer();
}
void TriggerAlgo::do_update(const api::orderman::Update& update) {
LOG(OrderMan, Diag, "Trigger order {} receiving update: {}",
algo().order_key().value(), utils::pb::to_string(update));
// validate the update
if (update.has_custom() && !update.custom().has_trigger()) {
return;
}
Update child_update;
child_update.set_price(update.price());
child_update.set_quantity(update.quantity());
if (update.has_custom()) {
const auto& trigger_update = update.custom().trigger();
// Copy updated fields from trigger_update without erasing missing values (like child)
me->definition.MergeFrom(trigger_update);
if (trigger_update.has_child()) {
*child_update.mutable_custom() = trigger_update.child();
init_child_template(algo(), trigger_update.child());
}
}
if (me->status == Status::WORKING) {
algo().update_child(me->child_key, child_update);
}
// Q: Better to call on_timer now or just wait?
on_timer();
}
void TriggerAlgo::on_child_change(OrderKey k, const ChildState& st) {
if (k == me->child_key) {
algo().update_fill_state(st.total_fills, st.last_price);
switch(st.status) {
case ChildStatus::WORKING:
return;
case ChildStatus::COMPLETED:
// Q: Does trigger coplete when the child completes or does it wait until condition is met again?
algo().complete();
return;
case ChildStatus::CANCELED:
algo().cancel("Unexpected child cancel");
return;
}
}
}
void TriggerAlgo::on_timer() {
// Q: Do we need to throttle here or just make sure check_trigger is fast?
if (me->status == Status::PAUSED) {
if (check_trigger(me->definition.trigger_condition())) {
activate_order(me->definition.trigger_action());
}
}
else if (me->status == Status::WORKING) {
// check exit condition
if (check_trigger(me->definition.exit_condition())) {
terminate_order();
}
}
}
void TriggerAlgo::init_child_template(IBeforeLaunchAlgoServices& blas, const api::orderman::CustomOrderDefinition& def) {
auto child_def = blas.definition();
*child_def.mutable_custom_def() = def;
me->child_template = &(blas.make_child_template(child_def));
}
void TriggerAlgo::activate_order(const TriggerAction&) {
}
void TriggerAlgo::terminate_order() {
}
IAlgoServices& TriggerAlgo::algo() const { return *me->algo; }
}} //namespace
@@ -0,0 +1,35 @@
#pragma once
#include "lib/orderman/orders/algo/IAlgoOrder.h"
#include <memory>
namespace bts {
namespace orderman {
// TriggerAlgo starts and stops a child order based on an external event
// such as a market move or a specified clock time.
class TriggerAlgo : public IAlgoOrder {
public:
TriggerAlgo();
~TriggerAlgo();
OrderOpResult do_before_launch(IBeforeLaunchAlgoServices &) override;
void do_launch(IAlgoServices &) override;
void do_launch_attached(IAlgoServices &, OrderKey, ChildState) override;
void do_update(api::orderman::Update const &) override;
void on_child_change(OrderKey, ChildState const &) override;
void on_timer() override;
private:
struct Impl;
std::unique_ptr<Impl> me;
void init_child_template(IBeforeLaunchAlgoServices&, const api::orderman::CustomOrderDefinition&);
void activate_order(const api::orderman::TriggerAction&);
void terminate_order();
IAlgoServices& algo() const;
};
}} //namespace
+68 -23
View File
@@ -132,6 +132,7 @@
"resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
"integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=",
"dev": true,
"optional": true,
"requires": {
"kind-of": "^3.0.2",
"longest": "^1.0.1",
@@ -1832,7 +1833,8 @@
"ansi-regex": {
"version": "2.1.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"aproba": {
"version": "1.1.1",
@@ -1883,7 +1885,8 @@
"balanced-match": {
"version": "0.4.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"bcrypt-pbkdf": {
"version": "1.0.1",
@@ -1898,6 +1901,7 @@
"version": "0.0.9",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"inherits": "~2.0.0"
}
@@ -1906,6 +1910,7 @@
"version": "2.10.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"hoek": "2.x.x"
}
@@ -1914,6 +1919,7 @@
"version": "1.1.7",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^0.4.1",
"concat-map": "0.0.1"
@@ -1922,7 +1928,8 @@
"buffer-shims": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"caseless": {
"version": "0.12.0",
@@ -1939,12 +1946,14 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"combined-stream": {
"version": "1.0.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"delayed-stream": "~1.0.0"
}
@@ -1952,22 +1961,26 @@
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"cryptiles": {
"version": "2.0.5",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"boom": "2.x.x"
}
@@ -2007,7 +2020,8 @@
"delayed-stream": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"delegates": {
"version": "1.0.0",
@@ -2039,7 +2053,8 @@
"extsprintf": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"forever-agent": {
"version": "0.6.1",
@@ -2061,12 +2076,14 @@
"fs.realpath": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"fstream": {
"version": "1.0.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"graceful-fs": "^4.1.2",
"inherits": "~2.0.0",
@@ -2122,6 +2139,7 @@
"version": "7.1.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
@@ -2134,7 +2152,8 @@
"graceful-fs": {
"version": "4.1.11",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"har-schema": {
"version": "1.0.5",
@@ -2162,6 +2181,7 @@
"version": "3.1.3",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"boom": "2.x.x",
"cryptiles": "2.x.x",
@@ -2172,7 +2192,8 @@
"hoek": {
"version": "2.16.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"http-signature": {
"version": "1.1.1",
@@ -2189,6 +2210,7 @@
"version": "1.0.6",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"once": "^1.3.0",
"wrappy": "1"
@@ -2197,7 +2219,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.4",
@@ -2209,6 +2232,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "1.0.1"
}
@@ -2222,7 +2246,8 @@
"isarray": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"isstream": {
"version": "0.1.2",
@@ -2295,12 +2320,14 @@
"mime-db": {
"version": "1.27.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"mime-types": {
"version": "2.1.15",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"mime-db": "1.27.0"
}
@@ -2309,6 +2336,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "1.1.7"
}
@@ -2316,12 +2344,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"mkdirp": {
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@@ -2376,7 +2406,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"oauth-sign": {
"version": "0.8.2",
@@ -2394,6 +2425,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@@ -2423,7 +2455,8 @@
"path-is-absolute": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"performance-now": {
"version": "0.2.0",
@@ -2434,7 +2467,8 @@
"process-nextick-args": {
"version": "1.0.7",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"punycode": {
"version": "1.4.1",
@@ -2472,6 +2506,7 @@
"version": "2.2.9",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"buffer-shims": "~1.0.0",
"core-util-is": "~1.0.0",
@@ -2516,6 +2551,7 @@
"version": "2.6.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"glob": "^7.0.5"
}
@@ -2523,7 +2559,8 @@
"safe-buffer": {
"version": "5.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"semver": {
"version": "5.3.0",
@@ -2547,6 +2584,7 @@
"version": "1.0.9",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"hoek": "2.x.x"
}
@@ -2580,6 +2618,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@@ -2590,6 +2629,7 @@
"version": "1.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.0.1"
}
@@ -2604,6 +2644,7 @@
"version": "3.0.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@@ -2618,6 +2659,7 @@
"version": "2.2.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"block-stream": "*",
"fstream": "^1.0.2",
@@ -2673,7 +2715,8 @@
"util-deprecate": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"uuid": {
"version": "3.0.1",
@@ -2702,7 +2745,8 @@
"wrappy": {
"version": "1.0.2",
"bundled": true,
"dev": true
"dev": true,
"optional": true
}
}
},
@@ -4011,7 +4055,8 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
"integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=",
"dev": true
"dev": true,
"optional": true
},
"loose-envify": {
"version": "1.3.1",