Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8badf5689 | ||
|
|
58533bf16f | ||
|
|
6b3c3ded68 | ||
|
|
d65e00fade | ||
|
|
68e5d07aef | ||
|
|
68345a181e | ||
|
|
69c121a19c | ||
|
|
780ce7416f | ||
|
|
9fe338a62a | ||
|
|
f027af66c8 | ||
|
|
6b2708b160 | ||
|
|
534474621f | ||
|
|
554614ab72 | ||
|
|
2ede69a263 | ||
|
|
9def94aceb | ||
|
|
0af30b3cc0 | ||
|
|
a91c2d9bc6 | ||
|
|
481c1fd2de | ||
|
|
e80fc807e0 | ||
|
|
c8e054d079 | ||
|
|
9480e76d4b | ||
|
|
538411353a | ||
|
|
da6998c391 | ||
|
|
60786b105c | ||
|
|
f488248abf | ||
|
|
830b590e5b | ||
|
|
c9c93b3cf0 | ||
|
|
9e5d43f44a | ||
|
|
5c1965f534 | ||
|
|
8553229119 | ||
|
|
4903c12533 | ||
|
|
88f23530c6 | ||
|
|
76ff36e514 | ||
|
|
740c14c261 | ||
|
|
dbdc915ff2 | ||
|
|
10476c6662 |
No files matched your search
@@ -4,6 +4,30 @@ weight = 3
|
||||
sort_by = "weight"
|
||||
insert_anchor_links = "right"
|
||||
+++
|
||||
## 2.7.0
|
||||
### User Visible Changes
|
||||
- Allowed algos to process "in-flight" fills while shutting down
|
||||
- Changed the cover algo to continue to hedge while shutting down
|
||||
### API Changes
|
||||
- Changed the API so that algos do not fully terminate until all
|
||||
their children are terminated. Algos that are in the process of
|
||||
shutting down in response to a complete or cancel call will still
|
||||
receive child updates, which they can use to calculate and report
|
||||
an accurate fill count (during this shutdown stage algos cannot
|
||||
create, cancel or update children). A new IAlgoServices method,
|
||||
is_shutting_down(), can be used to determine if an algo is
|
||||
shutting down.
|
||||
|
||||
## 2.6.2
|
||||
### User Visible Changes
|
||||
- Fixed an issue in the ladder where in some cases the mini order book could not be brought up from a badge
|
||||
- Fixed a rare race condition that could lead to erroneous limit enforcement
|
||||
- Fixed an issue with the margin limits where an error could occur if an account name with more than 12 chars was used
|
||||
|
||||
## 2.6.1
|
||||
### User Visible Changes
|
||||
- Fixed an issue in the ladder where an error could occur when trying to change ladder settings for the first time
|
||||
|
||||
## 2.6.0
|
||||
### User Visible Changes
|
||||
- In the ladder made last trade more distinctive when overlayed on the volumn column
|
||||
|
||||
@@ -339,6 +339,7 @@ namespace mbts.spark {
|
||||
zoom_slider_visible = false,
|
||||
zoom_factor = _default_zoom_factor,
|
||||
net_change = NetChangeTerms.None,
|
||||
mini_ob = new LadderMiniOrderBookSettings(bts.shell.orderbook.OrderBookStartupData.default_mini_grid.general),
|
||||
};
|
||||
rv.row_height = LadderRender.measure_row_height(rv);
|
||||
return rv;
|
||||
|
||||
@@ -113,7 +113,7 @@ namespace bts.shell {
|
||||
});
|
||||
});
|
||||
// vm
|
||||
vm = LadderVM.create(_bus, model, last_value ?? _settings_hub.current.ladder, settings_source);
|
||||
vm = LadderVM.create(_bus, model, LadderSettings.sanitize(last_value ?? _settings_hub.current.ladder), settings_source);
|
||||
vm.settings_invalidated += on_settings_invalidated;
|
||||
vm.order_properties_vm.open_algo_popup_on_select = _ladder_conf.open_algo_popup_on_select;
|
||||
vm.order_properties_vm.use_algo_param_grid = _ladder_conf.use_algo_param_grid;
|
||||
|
||||
@@ -363,7 +363,9 @@ namespace mbts.spark {
|
||||
} else {
|
||||
clone = SparkUtils.clone_proto(_current);
|
||||
#pragma warning disable CS0612 // Type or member is obsolete
|
||||
clone.mini_ob = new LadderMiniOrderBookSettings(mini_ob.create().with(sorting_criterion: _current.mini_ob.value.sorting_criterion));
|
||||
clone.mini_ob = new LadderMiniOrderBookSettings(_current.mini_ob.value != null ?
|
||||
mini_ob.create().with(sorting_criterion: _current.mini_ob.value.sorting_criterion) :
|
||||
mini_ob.create());
|
||||
#pragma warning restore CS0612 // Type or member is obsolete
|
||||
}
|
||||
clone.badge_style = badge_style;
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace mbts.spark {
|
||||
LadderVM.create_readonly(_bus,
|
||||
new LadderModel(security_md.Value, _model, _model.mbo_service,
|
||||
new Func<OrderSecurity, bool>(osec => _selected_order_tree.Contains(osec.order.Key))),
|
||||
Observable.Return(LadderSettings.Default)) : null);
|
||||
Observable.Return(LadderSettings.sanitize(LadderSettings.Default))) : null);
|
||||
RaisePropertyChanged(nameof(value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ namespace bts.shell.orderbook {
|
||||
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
|
||||
// numeric
|
||||
pb.add_numeric(fs.qty, o => o.order.quantity());
|
||||
pb.add_numeric(fs.price, o => o.order.price());
|
||||
pb.add_price(fs.price, o => o.order.price(), o => o.security);
|
||||
pb.add_numeric(fs.qty_filled, o => o.order.filled());
|
||||
pb.add_numeric(fs.avg_price, o => o.order.avg_price());
|
||||
pb.add_numeric(fs.last_price, o => o.order.last_price());
|
||||
|
||||
@@ -37,6 +37,51 @@ namespace mbts.spark.filters {
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) => max.CompareTo(selector(o)) >= 0;
|
||||
}
|
||||
}
|
||||
|
||||
// this is a copy of add_numeric except we do a tick_equals comparsion
|
||||
// if the ticks are not equals, then it falls back to a regular double comparison
|
||||
//
|
||||
// if the SecurityImmutable? doesn't have a value
|
||||
// then we fallback to double comparison
|
||||
public void add_price(DoubleMinMaxState value, Func<TRow, double> selector, Func<TRow, SecurityImmutable?> si_selector) {
|
||||
if (value == null)
|
||||
return;
|
||||
if (value.minimum.active) {
|
||||
var min = value.minimum.value.Value;
|
||||
if (value.maximum.active) {
|
||||
var max = value.maximum.value.Value;
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) {
|
||||
var v = selector(o);
|
||||
var si = si_selector(o);
|
||||
if (si.HasValue) {
|
||||
return si.Value.ContractSpec.compare_tick_fallback_to_double(min, v) <= 0 &&
|
||||
si.Value.ContractSpec.compare_tick_fallback_to_double(max, v) >= 0;
|
||||
}
|
||||
return min.CompareTo(v) <= 0 && max.CompareTo(v) >= 0;
|
||||
}
|
||||
} else {
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) {
|
||||
var si = si_selector(o);
|
||||
if (si.HasValue) {
|
||||
return si.Value.ContractSpec.compare_tick_fallback_to_double(min, selector(o)) <= 0;
|
||||
}
|
||||
return min.CompareTo(selector(o)) <= 0;
|
||||
}
|
||||
}
|
||||
} else if (value.maximum.active) {
|
||||
var max = value.maximum.value.Value;
|
||||
_predicates.Add(impl);
|
||||
bool impl(TRow o) {
|
||||
var si = si_selector(o);
|
||||
if (si.HasValue) {
|
||||
return si.Value.ContractSpec.compare_tick_fallback_to_double(max, selector(o)) >= 0;
|
||||
}
|
||||
return max.CompareTo(selector(o)) >= 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void add_regex(string str, Func<TRow, string> selector) {
|
||||
|
||||
@@ -128,6 +128,9 @@ namespace mbts.spark {
|
||||
public static IEqualityComparer<double> tick_equality_comparer(this ContractSpecificationImmutable contract) =>
|
||||
EqualityComparer.Create((double a, double b) => tick_equals(contract, a, b), x => MathUtils.RoundFractionalUnits(x, contract.LowTick));
|
||||
|
||||
public static int compare_tick_fallback_to_double(this ContractSpecificationImmutable contract, double x, double y) =>
|
||||
tick_equals(contract, x, y) ? 0 : x.CompareTo(y);
|
||||
|
||||
public static double round_to_tick(this ContractSpecificationImmutable spec, double value) {
|
||||
double intpart = (long)value;
|
||||
double floatpart = value % 1;
|
||||
|
||||
@@ -16,7 +16,13 @@ namespace orderman {
|
||||
|
||||
using namespace api::orderman;
|
||||
template <class K, class V> using Map = boost::container::flat_map<K, V>;
|
||||
enum class AlgoWrapperOrder::Status { INACTIVE, W_ATTACH, WORKING, COMPLETED };
|
||||
enum class AlgoWrapperOrder::Status {
|
||||
INACTIVE,
|
||||
W_ATTACH,
|
||||
WORKING,
|
||||
SHUTTING_DOWN,
|
||||
COMPLETED
|
||||
};
|
||||
|
||||
class ChildTemplate {
|
||||
public:
|
||||
@@ -62,6 +68,7 @@ struct AlgoWrapperOrder::PImpl {
|
||||
OrderKey attach_target = null_order_key();
|
||||
|
||||
std::optional<api::orderman::Update> pending_update;
|
||||
std::optional<std::pair<OrderStatus, std::string>> pending_terminate;
|
||||
OrderStatus external_status() const { return _state.status(); }
|
||||
|
||||
void set_external_status(OrderStatus s) {
|
||||
@@ -222,6 +229,7 @@ OrderOpResult AlgoWrapperOrder::on_activate(Price p, Quantity q,
|
||||
return {};
|
||||
|
||||
case Status::W_ATTACH:
|
||||
case Status::SHUTTING_DOWN:
|
||||
case Status::WORKING:
|
||||
bts_assert(false);
|
||||
}
|
||||
@@ -238,6 +246,7 @@ OrderOpResult AlgoWrapperOrder::on_update(Update const &update) {
|
||||
me->pending_update = update;
|
||||
return {};
|
||||
case Status::COMPLETED:
|
||||
case Status::SHUTTING_DOWN:
|
||||
return {};
|
||||
case Status::WORKING: {
|
||||
me->set_price_quantity(Price(update.price()),
|
||||
@@ -275,6 +284,7 @@ orderman::OrderOpResult AlgoWrapperOrder::on_detach() {
|
||||
switch (me->status) {
|
||||
case Status::W_ATTACH:
|
||||
case Status::WORKING:
|
||||
case Status::SHUTTING_DOWN:
|
||||
for (auto &[_, c] : me->children) {
|
||||
(void)_;
|
||||
c.order.mark_detached_from_parent();
|
||||
@@ -309,6 +319,7 @@ orderman::OrderOpResult AlgoWrapperOrder::on_cancel() {
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Status::SHUTTING_DOWN:
|
||||
case Status::COMPLETED:
|
||||
break;
|
||||
case Status::INACTIVE:
|
||||
@@ -347,13 +358,20 @@ Quantity AlgoWrapperOrder::total_fills() const {
|
||||
return Quantity(me->state().filled_quantity());
|
||||
}
|
||||
|
||||
bool AlgoWrapperOrder::is_shutting_down() const {
|
||||
return me->status == Status::SHUTTING_DOWN;
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::subscribe_book(SecurityKey k) {
|
||||
if (!is_working())
|
||||
return;
|
||||
bts_assert(me->book_subs.find(k) == me->book_subs.end());
|
||||
|
||||
me->book_subs[k] = me->services->subscribe_to_book(
|
||||
k, [this, k](auto const &ev) { me->algo->on_book(k, ev.book); });
|
||||
k, [this, k](auto const &ev) {
|
||||
ReportStateGuard _(*this);
|
||||
me->algo->on_book(k, ev.book);
|
||||
});
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::unsubscribe_book(SecurityKey k) {
|
||||
@@ -392,7 +410,10 @@ void AlgoWrapperOrder::subscribe_timer() {
|
||||
bts_assert(!me->has_timer_sub);
|
||||
me->has_timer_sub = true;
|
||||
me->services->subscribe_to_time(
|
||||
[this](auto const &) { me->algo->on_timer(); });
|
||||
[this](auto const &) {
|
||||
ReportStateGuard _(*this);
|
||||
me->algo->on_timer();
|
||||
});
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::unsubscribe_timer() {
|
||||
@@ -485,10 +506,9 @@ void AlgoWrapperOrder::detach_all_children() {
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::update_fill_state(Quantity q, Price p) {
|
||||
if (!is_working())
|
||||
if (!is_working() && !is_shutting_down())
|
||||
return;
|
||||
|
||||
ReportStateGuard _(*this);
|
||||
auto previous_qty = me->state().filled_quantity();
|
||||
if (previous_qty > q.value()) {
|
||||
LOG(OrderMan, Error,
|
||||
@@ -510,35 +530,37 @@ void AlgoWrapperOrder::update_fill_state(Quantity q, Price p) {
|
||||
void AlgoWrapperOrder::set_ladder_visibility(bool is_visible) {
|
||||
if (!is_working())
|
||||
return;
|
||||
|
||||
ReportStateGuard _(*this);
|
||||
me->set_ladder_visibility(is_visible);
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::cancel(std::string const &msg) {
|
||||
if (!is_working())
|
||||
return;
|
||||
ReportStateGuard _(*this);
|
||||
terminate(ORDER_STATUS_CANCELED, msg);
|
||||
if (working_children_count() == 0)
|
||||
terminate(ORDER_STATUS_CANCELED, msg);
|
||||
else
|
||||
shutdown(ORDER_STATUS_CANCELED, msg);
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::terminate_error(std::string const &msg) {
|
||||
if (!is_working() && me->status != Status::W_ATTACH)
|
||||
if (!is_working() && !is_shutting_down() && me->status != Status::W_ATTACH)
|
||||
return;
|
||||
ReportStateGuard _(*this);
|
||||
// If we attached and an error was signaled we do not want to
|
||||
// cancel our child because that child might be a long running
|
||||
// order with a good queue position that we want to preserve
|
||||
if (is_attaching())
|
||||
me->children.erase(me->attach_target);
|
||||
|
||||
terminate(ORDER_STATUS_ERROR, msg);
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::complete() {
|
||||
if (!is_working())
|
||||
return;
|
||||
ReportStateGuard _(*this);
|
||||
terminate(ORDER_STATUS_COMPLETED);
|
||||
if (working_children_count() == 0)
|
||||
terminate(ORDER_STATUS_COMPLETED);
|
||||
else
|
||||
shutdown(ORDER_STATUS_COMPLETED);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------------
|
||||
@@ -573,6 +595,7 @@ void AlgoWrapperOrder::on_attach_complete(
|
||||
break;
|
||||
case Status::COMPLETED:
|
||||
break;
|
||||
case Status::SHUTTING_DOWN:
|
||||
case Status::INACTIVE:
|
||||
case Status::WORKING:
|
||||
bts_assert(false);
|
||||
@@ -587,6 +610,7 @@ void AlgoWrapperOrder::on_child_order_update(
|
||||
switch (me->status) {
|
||||
case Status::WORKING:
|
||||
case Status::W_ATTACH:
|
||||
case Status::SHUTTING_DOWN:
|
||||
if (auto it = me->children.find(child_key); it != me->children.end()) {
|
||||
auto &child_record = it->second;
|
||||
switch (new_child_state.status()) {
|
||||
@@ -625,16 +649,38 @@ void AlgoWrapperOrder::on_child_order_update(
|
||||
break;
|
||||
}
|
||||
} else
|
||||
terminate_error(
|
||||
fmt::format("Unknown child order update: {}", child_key.value()));
|
||||
return;
|
||||
LOG(OrderMan, Info,
|
||||
"AlgoOrder {} received update for presumably detached order {}. "
|
||||
"Ignoring it",
|
||||
key().value(), child_key.value());
|
||||
break;
|
||||
case Status::COMPLETED:
|
||||
return;
|
||||
case Status::INACTIVE:
|
||||
bts_assert(false);
|
||||
return;
|
||||
}
|
||||
bts_assert(false);
|
||||
|
||||
if (me->status == Status::SHUTTING_DOWN)
|
||||
try_complete_shutdown();
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::try_complete_shutdown() {
|
||||
bts_assert(me->status == Status::SHUTTING_DOWN);
|
||||
bts_assert(me->children.size());
|
||||
bts_assert(me->pending_terminate.has_value());
|
||||
|
||||
if (working_children_count() == 0) {
|
||||
LOG(OrderMan, Info, "AlgoOrder {} completed shutdown. Terminated",
|
||||
key().value());
|
||||
terminate(me->pending_terminate.value().first,
|
||||
me->pending_terminate.value().second);
|
||||
} else {
|
||||
LOG(OrderMan, Info,
|
||||
"AlgoOrder {} still has {} orders working. Shutdown still in "
|
||||
"progress",
|
||||
key().value(), working_children_count());
|
||||
}
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::setup_transitions() {
|
||||
@@ -676,8 +722,21 @@ void AlgoWrapperOrder::setup_transitions() {
|
||||
// From WORKING
|
||||
me->transition_actions[{Status::WORKING, Status::COMPLETED}] = [this]() {
|
||||
cleanup();
|
||||
me->children.clear();
|
||||
_transition(Status::COMPLETED);
|
||||
};
|
||||
me->transition_actions[{Status::WORKING, Status::SHUTTING_DOWN}] = [this]() {
|
||||
cleanup();
|
||||
_transition(Status::SHUTTING_DOWN);
|
||||
};
|
||||
|
||||
// From SHUTTING_DOWN
|
||||
me->transition_actions[{Status::SHUTTING_DOWN, Status::COMPLETED}] =
|
||||
[this]() {
|
||||
me->children.clear();
|
||||
me->pending_terminate.reset();
|
||||
_transition(Status::COMPLETED);
|
||||
};
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::check_invariants() {
|
||||
@@ -697,6 +756,7 @@ void AlgoWrapperOrder::check_invariants() {
|
||||
bts_assert(me->book_subs.empty());
|
||||
bts_assert(me->children.empty());
|
||||
bts_assert(!me->has_timer_sub);
|
||||
bts_assert(!me->pending_terminate.has_value());
|
||||
break;
|
||||
|
||||
case Status::WORKING:
|
||||
@@ -704,10 +764,23 @@ void AlgoWrapperOrder::check_invariants() {
|
||||
bts_assert(me->security);
|
||||
bts_assert(me->external_status() == ORDER_STATUS_WORKING);
|
||||
bts_assert(!me->pending_update.has_value());
|
||||
bts_assert(!me->pending_terminate.has_value());
|
||||
break;
|
||||
|
||||
case Status::SHUTTING_DOWN:
|
||||
bts_assert(working_children_count());
|
||||
bts_assert(me->services);
|
||||
bts_assert(me->security);
|
||||
bts_assert(me->external_status() == ORDER_STATUS_WORKING);
|
||||
bts_assert(me->book_subs.empty());
|
||||
bts_assert(!me->has_timer_sub);
|
||||
bts_assert(!me->pending_update.has_value());
|
||||
bts_assert(me->pending_terminate.has_value());
|
||||
break;
|
||||
|
||||
case Status::COMPLETED:
|
||||
bts_assert(me->children.empty());
|
||||
bts_assert(!me->pending_update.has_value());
|
||||
bts_assert(me->external_status() == ORDER_STATUS_CANCELED ||
|
||||
me->external_status() == ORDER_STATUS_COMPLETED ||
|
||||
me->external_status() == ORDER_STATUS_ERROR ||
|
||||
@@ -715,6 +788,7 @@ void AlgoWrapperOrder::check_invariants() {
|
||||
bts_assert(me->book_subs.empty());
|
||||
bts_assert(!me->has_timer_sub);
|
||||
bts_assert(!me->pending_update.has_value());
|
||||
bts_assert(!me->pending_terminate.has_value());
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -745,15 +819,25 @@ void AlgoWrapperOrder::terminate(api::orderman::OrderStatus st,
|
||||
transition(Status::COMPLETED);
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::shutdown(api::orderman::OrderStatus st,
|
||||
std::string const &error) {
|
||||
bts_assert(!me->pending_terminate.has_value());
|
||||
bts_assert(working_children_count());
|
||||
me->pending_terminate = std::make_pair(st, error);
|
||||
LOG(OrderMan, Info,
|
||||
"AlgoOrder {} is shutting down. Waiting for {} child orders to complete "
|
||||
"before terminating",
|
||||
key().value(), working_children_count());
|
||||
transition(Status::SHUTTING_DOWN);
|
||||
}
|
||||
|
||||
void AlgoWrapperOrder::cleanup() {
|
||||
for (auto const &s : me->book_subs) {
|
||||
me->services->unsubscribe_from_book(s.second);
|
||||
me->book_subs.clear();
|
||||
}
|
||||
for (auto &c : me->children) {
|
||||
for (auto &c : me->children)
|
||||
c.second.order.cancel();
|
||||
me->children.clear();
|
||||
}
|
||||
if (me->has_timer_sub)
|
||||
unsubscribe_timer();
|
||||
me->pending_update.reset();
|
||||
@@ -785,6 +869,16 @@ OrderKey AlgoWrapperOrder::index_and_launch_child(ChildOrder &&child, Price p,
|
||||
return k;
|
||||
}
|
||||
|
||||
size_t AlgoWrapperOrder::working_children_count() const {
|
||||
auto working_children_count = 0;
|
||||
for (auto &c : me->children)
|
||||
if (!c.second.state.has_value() ||
|
||||
c.second.state.value().status == ChildStatus::WORKING) {
|
||||
working_children_count++;
|
||||
}
|
||||
return working_children_count;
|
||||
}
|
||||
|
||||
bool AlgoWrapperOrder::is_working() const {
|
||||
return me->status == Status::WORKING;
|
||||
}
|
||||
@@ -799,6 +893,8 @@ log::CharPtrWrapper AlgoWrapperOrder::state_name(Status st) {
|
||||
return {"INACTIVE"};
|
||||
case Status::WORKING:
|
||||
return {"WORKING"};
|
||||
case Status::SHUTTING_DOWN:
|
||||
return {"SHUTTING_DOWN"};
|
||||
}
|
||||
bts_assert(false);
|
||||
return {""};
|
||||
|
||||
@@ -40,6 +40,7 @@ class AlgoWrapperOrder : public Order,
|
||||
Price price() const override;
|
||||
Quantity quantity() const override;
|
||||
Quantity total_fills() const override;
|
||||
bool is_shutting_down() const override;
|
||||
void subscribe_book(SecurityKey) override;
|
||||
void unsubscribe_book(SecurityKey) override;
|
||||
void subscribe_child_change(OrderKey) override;
|
||||
@@ -75,10 +76,13 @@ class AlgoWrapperOrder : public Order,
|
||||
void transition(Status);
|
||||
void _transition(Status);
|
||||
void terminate(api::orderman::OrderStatus, std::string const &error = "");
|
||||
void shutdown(api::orderman::OrderStatus, std::string const &error = "");
|
||||
void try_complete_shutdown();
|
||||
void cleanup();
|
||||
void update_child_record(api::orderman::OrderState const &, ChildRecord *);
|
||||
OrderKey index_and_launch_child(ChildOrder &&, Price, Quantity, bool);
|
||||
bool is_working() const ;
|
||||
size_t working_children_count() const;
|
||||
log::CharPtrWrapper state_name(Status) ;
|
||||
ChildStatus map_child_status(api::orderman::OrderStatus) ;
|
||||
bool _is_attaching(api::orderman::OrderDefinition const &) const ;
|
||||
|
||||
@@ -20,6 +20,13 @@ class IAlgoServices : public IBeforeLaunchAlgoServices {
|
||||
// Returns the filled quantity for this algo
|
||||
virtual Quantity total_fills() const = 0;
|
||||
|
||||
// Returns wheter the algo is in the process of shutting down (due
|
||||
// to a cancel, an error, or just having completed its work). When
|
||||
// algo is shutting down any requests to create, update or cancel
|
||||
// child orders will be ignored.
|
||||
virtual bool is_shutting_down() const = 0;
|
||||
|
||||
|
||||
// --------------------------------------------------------------
|
||||
// Event Subscriptions
|
||||
// --------------------------------------------------------------
|
||||
|
||||
@@ -10,18 +10,8 @@ namespace orderman {
|
||||
|
||||
using namespace api::orderman;
|
||||
|
||||
enum class CoverAlgo::Status { WORKING, WAITING_FOR_HEDGES, DONE };
|
||||
const char *CoverAlgo::status_name(Status st) {
|
||||
switch (st) {
|
||||
case Status::WORKING:
|
||||
return "WORKING";
|
||||
case Status::WAITING_FOR_HEDGES:
|
||||
return "WAITING_FOR_HEDGES";
|
||||
case Status::DONE:
|
||||
return "Done";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
enum class CoverAlgo::Status { WORKING, WAITING_FOR_HEDGES, DONE, CANCELING };
|
||||
|
||||
struct CoverAlgo::PImpl {
|
||||
IAlgoServices *algo;
|
||||
OrderKey child_key = null_order_key();
|
||||
@@ -92,8 +82,11 @@ void CoverAlgo::do_launch_attached(IAlgoServices &algo, OrderKey k,
|
||||
on_child_change(k, st);
|
||||
}
|
||||
|
||||
void CoverAlgo::on_child_change(OrderKey k, ChildState const &st) {
|
||||
if (k == me->child_key) {
|
||||
void CoverAlgo::on_covered_change(ChildState const &st) {
|
||||
// First launch hedge if appropriate
|
||||
switch (me->status) {
|
||||
case Status::WORKING:
|
||||
case Status::CANCELING:
|
||||
if (st.total_fills.value() > me->hedged_fills.value()) {
|
||||
auto hedge_qty =
|
||||
Quantity(st.total_fills.value() - me->hedged_fills.value());
|
||||
@@ -116,6 +109,15 @@ void CoverAlgo::on_child_change(OrderKey k, ChildState const &st) {
|
||||
hedge_qty.value(), hedge_price.value());
|
||||
}
|
||||
algo().update_fill_state(st.total_fills, st.last_price);
|
||||
break;
|
||||
case Status::WAITING_FOR_HEDGES:
|
||||
case Status::DONE:
|
||||
break;
|
||||
}
|
||||
|
||||
// Now analyze child to figure out wheter we should terminate
|
||||
switch (me->status) {
|
||||
case Status::WORKING:
|
||||
switch (st.status) {
|
||||
case ChildStatus::WORKING:
|
||||
break;
|
||||
@@ -125,6 +127,7 @@ void CoverAlgo::on_child_change(OrderKey k, ChildState const &st) {
|
||||
algo().order_key().value());
|
||||
algo().set_ladder_visibility(false);
|
||||
me->status = Status::WAITING_FOR_HEDGES;
|
||||
check_terminate();
|
||||
break;
|
||||
case ChildStatus::CANCELED:
|
||||
LOG(OrderMan, Info, "CoverAlgo {}. Child was canceled, terminating",
|
||||
@@ -133,30 +136,60 @@ void CoverAlgo::on_child_change(OrderKey k, ChildState const &st) {
|
||||
algo().terminate_error("Unexpected child cancel");
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
case Status::CANCELING:
|
||||
switch (st.status) {
|
||||
case ChildStatus::WORKING:
|
||||
break;
|
||||
case ChildStatus::CANCELED:
|
||||
if (me->cancel_on_external_hedge_cancel) {
|
||||
LOG(OrderMan, Info, "CoverAlgo {}. Hedge was canceled, terminating",
|
||||
algo().order_key().value());
|
||||
me->status = Status::DONE;
|
||||
algo().terminate_error("Unexpected child cancel");
|
||||
} else
|
||||
me->hedges.erase(
|
||||
std::remove(me->hedges.begin(), me->hedges.end(), k),
|
||||
me->hedges.end());
|
||||
break;
|
||||
case ChildStatus::COMPLETED:
|
||||
me->hedges.erase(std::remove(me->hedges.begin(), me->hedges.end(), k),
|
||||
me->hedges.end());
|
||||
LOG(OrderMan, Info, "CoverAlgo {}. Shutdown complete",
|
||||
algo().order_key().value());
|
||||
me->status = Status::DONE;
|
||||
algo().cancel();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Status::WAITING_FOR_HEDGES:
|
||||
case Status::DONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void CoverAlgo::on_hedge_change(OrderKey k, ChildState const &st) {
|
||||
|
||||
// First adjust our hedge count or cancel if something went wrong
|
||||
switch (st.status) {
|
||||
case ChildStatus::WORKING:
|
||||
break;
|
||||
case ChildStatus::COMPLETED:
|
||||
me->hedges.erase(std::remove(me->hedges.begin(), me->hedges.end(), k),
|
||||
me->hedges.end());
|
||||
break;
|
||||
case ChildStatus::CANCELED:
|
||||
if (me->cancel_on_external_hedge_cancel) {
|
||||
LOG(OrderMan, Info, "CoverAlgo {}. Hedge was canceled, terminating",
|
||||
algo().order_key().value());
|
||||
me->status = Status::DONE;
|
||||
algo().terminate_error("Unexpected child cancel");
|
||||
return;
|
||||
} else
|
||||
me->hedges.erase(std::remove(me->hedges.begin(), me->hedges.end(), k),
|
||||
me->hedges.end());
|
||||
break;
|
||||
}
|
||||
|
||||
// Now if we are waiting for hedges to complete, bail
|
||||
check_terminate();
|
||||
}
|
||||
|
||||
void CoverAlgo::on_child_change(OrderKey k, ChildState const &st) {
|
||||
if (k == me->child_key)
|
||||
on_covered_change(st);
|
||||
else
|
||||
on_hedge_change(k,st);
|
||||
}
|
||||
|
||||
void CoverAlgo::do_update(api::orderman::Update const &update) {
|
||||
if (me->status == Status::WORKING) {
|
||||
LOG(OrderMan, Diag, "CoverAlgo order {} receiving update: {}",
|
||||
@@ -225,11 +258,45 @@ void CoverAlgo::check_terminate() {
|
||||
}
|
||||
|
||||
IAlgoOrder::OnCancelResponse CoverAlgo::on_cancel() {
|
||||
if (!me->cancel_hedges_on_cancel)
|
||||
for (auto &k : me->hedges)
|
||||
algo().detach_child(k);
|
||||
switch (me->status) {
|
||||
case Status::WORKING: {
|
||||
if (!me->cancel_hedges_on_cancel)
|
||||
for (auto &k : me->hedges)
|
||||
algo().detach_child(k);
|
||||
algo().cancel_child(me->child_key);
|
||||
|
||||
LOG(OrderMan, Info,
|
||||
"CoverAlgo {} received cancel. Canceling child and waiting for an ack",
|
||||
algo().order_key());
|
||||
me->status = Status::CANCELING;
|
||||
return IAlgoOrder::OnCancelResponse::IGNORE;
|
||||
}
|
||||
case Status::CANCELING:
|
||||
return IAlgoOrder::OnCancelResponse::IGNORE;
|
||||
case Status::WAITING_FOR_HEDGES:
|
||||
return IAlgoOrder::OnCancelResponse::HONOR;
|
||||
case Status::DONE:
|
||||
bts_assert(false);
|
||||
return IAlgoOrder::OnCancelResponse::HONOR;
|
||||
}
|
||||
|
||||
bts_assert(false);
|
||||
return IAlgoOrder::OnCancelResponse::HONOR;
|
||||
}
|
||||
|
||||
const char *CoverAlgo::status_name(Status st) {
|
||||
switch (st) {
|
||||
case Status::WORKING:
|
||||
return "WORKING";
|
||||
case Status::WAITING_FOR_HEDGES:
|
||||
return "WAITING_FOR_HEDGES";
|
||||
case Status::DONE:
|
||||
return "DONE";
|
||||
case Status::CANCELING:
|
||||
return "CANCELING";
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace orderman
|
||||
} // namespace bts
|
||||
@@ -31,6 +31,9 @@ private:
|
||||
void init_hedge_template(IBeforeLaunchAlgoServices &,
|
||||
api::orderman::CustomOrderDefinition const &);
|
||||
void check_terminate();
|
||||
|
||||
void on_hedge_change(OrderKey hedge, ChildState const&) ;
|
||||
void on_covered_change(ChildState const&);
|
||||
const char *status_name(Status st);
|
||||
};
|
||||
|
||||
|
||||
@@ -145,7 +145,7 @@ void IcebergAlgo::on_child_change(OrderKey k, ChildState const &st) {
|
||||
on_timer();
|
||||
return;
|
||||
case ChildStatus::CANCELED:
|
||||
algo().terminate_error("Unexpected child cancel");
|
||||
algo().cancel("Unexpected child cancel");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 40 KiB |
@@ -0,0 +1,39 @@
|
||||
#+TITLE: Algo Wrapper State Machien
|
||||
#+OPTIONS: ^:nil
|
||||
#+OPTIONS: H:2
|
||||
#+OPTIONS: ^:nil H:2 author:nil creator:nil timestamp:nil toc:nil
|
||||
#+OPTIONS: html-postamble:nil
|
||||
|
||||
|
||||
* State Machine
|
||||
#+begin_src dot :file algo_wrapper_sm.png :cmdline -Kdot -Tpng
|
||||
digraph G {
|
||||
bgcolor="transparent";
|
||||
edge [ fontname=Helvetica, fontsize=12 ];
|
||||
|
||||
start [style=invisible];
|
||||
COMPLETED [style=filled] ;
|
||||
start -> INACTIVE;
|
||||
INACTIVE -> COMPLETED [label="qty=0|error"];
|
||||
INACTIVE -> W_ATTACH [label="ok & attach"];
|
||||
INACTIVE -> WORKING [label="ok & !attach"];
|
||||
W_ATTACH -> COMPLETED [label=attach_fail];
|
||||
W_ATTACH -> WORKING [label=attach_ok];
|
||||
WORKING -> COMPLETED [label="[wchild=0]cancel|complete"];
|
||||
WORKING -> COMPLETED [label=error];
|
||||
WORKING -> SHUTTING_DOWN [label="[wchild>0]cancel|complete"];
|
||||
SHUTTING_DOWN -> COMPLETED [label="wchild==0|error|on_detach"];
|
||||
}
|
||||
#+END_SRC
|
||||
|
||||
#+RESULTS:
|
||||
[[file:algo_wrapper_sm.png]]
|
||||
|
||||
* Invariants
|
||||
| | attach_target | exernal_status | pending_update | pending_terminate | working_children | subs |
|
||||
|---------------+---------------+--------------------------------------+----------------+-------------------+------------------+------|
|
||||
| INACTIVE | | | | | | 0 |
|
||||
| W_ATTACH | 1 | WORKING | | 0 | 0 | 0 |
|
||||
| WORKING | | WORKING | 0 | 0 | | |
|
||||
| SHUTTING_DOWN | | WORKING | 0 | 1 | >0 | 0 |
|
||||
| COMPLETED | | CANCELED, COMPLETED, ERROR, REJECTED | 0 | 0 | 0 | 0 |
|
||||
@@ -27,6 +27,7 @@ struct Proxy : public IAlgoServices {
|
||||
Price price() const { return Price(0); }
|
||||
Quantity quantity() const { return Quantity(0); }
|
||||
Quantity total_fills() const { return Quantity(0); }
|
||||
bool is_shutting_down() const {return false;}
|
||||
void subscribe_book(SecurityKey){};
|
||||
void unsubscribe_book(SecurityKey) {}
|
||||
void subscribe_child_change(OrderKey) {}
|
||||
|
||||
@@ -33,7 +33,7 @@ int Span::execute(const std::vector<std::string>&)
|
||||
auto& disp = utils::Dispatcher::current();
|
||||
MarginServiceCore core(disp);
|
||||
|
||||
auto acct = orderman::to<orderman::AccountName>("ACC A");
|
||||
auto acct = std::string("ACC A");
|
||||
Position p;
|
||||
p.security_desc = "ESU9";
|
||||
p.account = acct;
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace bts
|
||||
namespace position
|
||||
{
|
||||
|
||||
using MarginMap = boost::container::flat_map<orderman::AccountName, MarginResult>;
|
||||
using MarginMap = boost::container::flat_map<std::string, MarginResult>;
|
||||
|
||||
struct MarginServiceCore::PImpl
|
||||
{
|
||||
@@ -314,10 +314,9 @@ struct MarginServiceCore::PImpl
|
||||
}
|
||||
};
|
||||
|
||||
bool compute_margin(const orderman::AccountName& acct, const std::string& portfolid_id,
|
||||
bool compute_margin(const std::string& account_name, const std::string& portfolid_id,
|
||||
const std::vector<Position>& pos, std::function<void(const MarginResult&)> cb)
|
||||
{
|
||||
auto account_name = orderman::to_trim_str(acct);
|
||||
pugi::xml_document doc;
|
||||
pugi::xml_node trades_node;
|
||||
create_margin_request(portfolid_id, doc, trades_node);
|
||||
@@ -384,7 +383,7 @@ MarginServiceCore::MarginServiceCore(utils::Dispatcher& disp) : me(new PImpl(dis
|
||||
MarginServiceCore::~MarginServiceCore() {}
|
||||
|
||||
|
||||
void MarginServiceCore::compute_margin(const orderman::AccountName& acct, const std::vector<Position>& pos,
|
||||
void MarginServiceCore::compute_margin(const std::string& acct, const std::vector<Position>& pos,
|
||||
std::function<void(const MarginResult&)> cb)
|
||||
{
|
||||
me->dispatcher.post([=] ()
|
||||
@@ -393,7 +392,7 @@ void MarginServiceCore::compute_margin(const orderman::AccountName& acct, const
|
||||
}, me->throttle);
|
||||
}
|
||||
|
||||
bool MarginServiceCore::query_margin_webapi(const orderman::AccountName& acct, const std::vector<Position>& pos,
|
||||
bool MarginServiceCore::query_margin_webapi(const std::string& acct, const std::vector<Position>& pos,
|
||||
std::function<void(const MarginResult&)> cb)
|
||||
{
|
||||
utils::Uuid guid;
|
||||
@@ -425,7 +424,7 @@ void MarginServiceCore::set_verbose_debugging(bool verb)
|
||||
me->verbose = verb;
|
||||
}
|
||||
|
||||
bool MarginServiceCore::get_last_valid_result(const orderman::AccountName& acct, MarginResult& result)
|
||||
bool MarginServiceCore::get_last_valid_result(const std::string& acct, MarginResult& result)
|
||||
{
|
||||
auto it = me->last_valid_results.find(acct);
|
||||
if(it != me->last_valid_results.end())
|
||||
@@ -436,7 +435,7 @@ bool MarginServiceCore::get_last_valid_result(const orderman::AccountName& acct,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MarginServiceCore::get_last_result(const orderman::AccountName& acct, MarginResult& result)
|
||||
bool MarginServiceCore::get_last_result(const std::string& acct, MarginResult& result)
|
||||
{
|
||||
auto it = me->last_results.find(acct);
|
||||
if(it != me->last_results.end())
|
||||
|
||||
@@ -22,18 +22,18 @@ public:
|
||||
|
||||
MarginServiceCore(utils::Dispatcher&);
|
||||
~MarginServiceCore();
|
||||
void compute_margin(const orderman::AccountName&, const std::vector<Position>&,
|
||||
void compute_margin(const std::string&, const std::vector<Position>&,
|
||||
std::function<void(const api::position::MarginResult&)>);
|
||||
void set_verbose_debugging(bool);
|
||||
bool get_last_valid_result(const orderman::AccountName&, api::position::MarginResult&);
|
||||
bool get_last_result(const orderman::AccountName&, api::position::MarginResult&);
|
||||
bool get_last_valid_result(const std::string&, api::position::MarginResult&);
|
||||
bool get_last_result(const std::string&, api::position::MarginResult&);
|
||||
|
||||
|
||||
private:
|
||||
friend class PositionTests;
|
||||
|
||||
//This is a blocking call to a WebAPI. Very Slow.
|
||||
bool query_margin_webapi(const orderman::AccountName&, const std::vector<Position>&,
|
||||
bool query_margin_webapi(const std::string&, const std::vector<Position>&,
|
||||
std::function<void(const api::position::MarginResult&)>);
|
||||
|
||||
private:
|
||||
|
||||
@@ -43,14 +43,10 @@ MarginServiceServer::MarginServiceServer(bts::api::ServiceContext& context, md::
|
||||
_last_report(),
|
||||
_first_time(true)
|
||||
{
|
||||
LOG(Gen, Info, "MarginServiceServer: service started");
|
||||
if(!config::get<bts::config::SpanMargin>().enabled())
|
||||
return;
|
||||
|
||||
// _admin_service.get_admin_data(api::VoidMessage(),
|
||||
// [=] (const bts::api::Result<::bts::api::admin::AdminData> r)
|
||||
// {
|
||||
// if(r.value())
|
||||
// _admin_data = *r.value();
|
||||
// });
|
||||
LOG(Gen, Info, "MarginServiceServer: service started");
|
||||
|
||||
_admin_service.subscribe_admin_data() |
|
||||
rxcpp::operators::subscribe<api::admin::Admin_dataUpdate>(
|
||||
@@ -91,8 +87,7 @@ void MarginServiceServer::publish_all()
|
||||
if(_position->query_positions(acct, pos, pnl, &since) && pos.size())
|
||||
{
|
||||
LOG(Gen, Info, "MarginServiceServer: requesting margin for {} ", acct);
|
||||
auto aname = orderman::to<orderman::AccountName>(acct);
|
||||
_margin->compute_margin(aname, pos, [this, since, acct, pnl](const MarginResult& m)
|
||||
_margin->compute_margin(acct, pos, [this, since, acct, pnl](const MarginResult& m)
|
||||
{
|
||||
std::scoped_lock<std::recursive_mutex> l(_lock);
|
||||
publish_margin(m);
|
||||
@@ -117,11 +112,9 @@ void MarginServiceServer::get_margin(const api::position::MarginRequest& request
|
||||
}
|
||||
else
|
||||
{
|
||||
orderman::AccountName acct;
|
||||
orderman::str_to(request.account(), &acct);
|
||||
std::vector<Position> pos; AccountPNL pnl;
|
||||
_position->query_positions(request.account(), pos, pnl);
|
||||
_margin->compute_margin(acct, pos, [=](const MarginResult& m)
|
||||
_margin->compute_margin(request.account(), pos, [=](const MarginResult& m)
|
||||
{
|
||||
reply(m);
|
||||
});
|
||||
@@ -183,7 +176,7 @@ void MarginServiceServer::incremental_update_report(const std::string& account,
|
||||
std::scoped_lock<std::recursive_mutex> l(_lock);
|
||||
|
||||
MarginResult margin;
|
||||
if(!_margin->get_last_valid_result(orderman::to<orderman::AccountName>(account), margin))
|
||||
if(!_margin->get_last_valid_result(account, margin))
|
||||
{
|
||||
margin.Clear();
|
||||
margin.set_account(account);
|
||||
@@ -203,6 +196,13 @@ void MarginServiceServer::incremental_update_report(const std::string& account,
|
||||
void MarginServiceServer::set_cash_factor(const ::bts::api::position::CashFactorUpdate& request,
|
||||
const api::Response<::bts::api::ResultMessage>& reply)
|
||||
{
|
||||
if(!config::get<bts::config::SpanMargin>().enabled())
|
||||
{
|
||||
api::ResultMessage resp; resp.set_success(true);
|
||||
reply(resp);
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock<std::recursive_mutex> l(_lock);
|
||||
auto acct = AdminServiceUtils::find_account(_admin_data, request.account(), false);
|
||||
if(!acct)
|
||||
@@ -234,6 +234,13 @@ void MarginServiceServer::set_cash_factor(const ::bts::api::position::CashFactor
|
||||
void MarginServiceServer::set_cash_balance(const ::bts::api::position::CashBalanceUpdate& request,
|
||||
const api::Response<::bts::api::ResultMessage>& reply)
|
||||
{
|
||||
if(!config::get<bts::config::SpanMargin>().enabled())
|
||||
{
|
||||
api::ResultMessage resp; resp.set_success(true);
|
||||
reply(resp);
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock<std::recursive_mutex> l(_lock);
|
||||
auto acct = AdminServiceUtils::find_account(_admin_data, request.account(), false);
|
||||
if(!acct)
|
||||
@@ -265,6 +272,13 @@ void MarginServiceServer::set_cash_balance(const ::bts::api::position::CashBalan
|
||||
void MarginServiceServer::set_should_enforce_margin(const ::bts::api::position::EnforceUpdate& request,
|
||||
const api::Response<::bts::api::ResultMessage>& reply)
|
||||
{
|
||||
if(!config::get<bts::config::SpanMargin>().enabled())
|
||||
{
|
||||
api::ResultMessage resp; resp.set_success(true);
|
||||
reply(resp);
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock<std::recursive_mutex> l(_lock);
|
||||
auto acct = AdminServiceUtils::find_account(_admin_data, request.account(), false);
|
||||
if(!acct)
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace bts {
|
||||
namespace position {
|
||||
struct Position
|
||||
{
|
||||
orderman::AccountName account;
|
||||
std::string account;
|
||||
std::string security_desc;
|
||||
Quantity position;
|
||||
api::Side side;
|
||||
|
||||
@@ -112,7 +112,7 @@ bool PositionAggregator::query_positions(const std::string& account, std::vector
|
||||
if(sec && sec->has_glbx() && sec->has_future())
|
||||
{
|
||||
Position pos;
|
||||
pos.account = orderman::to<orderman::AccountName>(account);
|
||||
pos.account = account;
|
||||
pos.security_desc = securities::descriptor(*sec);
|
||||
eraseSubStr(pos.security_desc, "SIM:");
|
||||
int32_t totalpos = unit.sod_long_pos() + unit.day_long_pos() - unit.sod_short_pos() - unit.day_short_pos();
|
||||
|
||||
@@ -84,16 +84,12 @@ me(new PImpl(context,
|
||||
rxcpp::operators::subscribe<api::orderman::FillsUpdate>(
|
||||
[this](auto const& update) {
|
||||
for (const auto& f : update.removed())
|
||||
if(_valid_account_id(*f))
|
||||
me->publisher.add_or_update(_on_fill(*f,false));
|
||||
me->publisher.add_or_update(_on_fill(*f,false));
|
||||
for (const auto& f : update.added())
|
||||
if(_valid_account_id(*f))
|
||||
me->publisher.add_or_update(_on_fill(*f,true));
|
||||
me->publisher.add_or_update(_on_fill(*f,true));
|
||||
for (const auto& f : update.changed()) {
|
||||
if(_valid_account_id(*f.previous()) && _valid_account_id(*f.current())){
|
||||
me->publisher.add_or_update(_on_fill(*f.previous(),false));
|
||||
me->publisher.add_or_update(_on_fill(*f.current(),true));
|
||||
}
|
||||
me->publisher.add_or_update(_on_fill(*f.previous(),false));
|
||||
me->publisher.add_or_update(_on_fill(*f.current(),true));
|
||||
}
|
||||
me->publisher.flush();
|
||||
_compute_pnl_and_publish();
|
||||
|
||||
Reference in new issue
Block a user