Compare commits

...
36 Commits
Author SHA1 Message Date
Kevin Darby c8badf5689 Merged in fix_span_error_message (pull request #306)
guard against UI setting cash and leverage factors for accounts I don't know about when span is off

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-17 14:51:23 +00:00
Kevin Darby 58533bf16f guard against UI setting cash and leverage factors for accounts I don't know about when span is off 2019-07-17 10:48:16 -04:00
Pedro Pinto 6b3c3ded68 Fixed up release notes. 2019-07-17 10:24:51 -04:00
Pedro Pinto d65e00fade Merged in SP-132-make-algo-shutdown-orderly (pull request #305)
RTM. SP-132 make algo shutdown orderly
2019-07-17 14:23:09 +00:00
Pedro Pinto 68e5d07aef Merge branch 'master' of bitbucket.org:bluetradesys/spark 2019-07-17 09:36:15 -04:00
Pedro Pinto 68345a181e Updated release notes. 2019-07-17 09:35:43 -04:00
Kevin Darby 69c121a19c Merged in fix_span (pull request #301)
fix crash where account name > 12 chars, tested with long account name

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-17 13:32:33 +00:00
Pedro Pinto 780ce7416f Merge branch 'master' into SP-132-make-algo-shutdown-orderly 2019-07-17 09:07:54 -04:00
Pedro Pinto 9fe338a62a Updated release notes. 2019-07-17 09:07:24 -04:00
Jake Norbut f027af66c8 Merged in ob_price_filter_tick_comparison (pull request #304)
RTM - Ob price filter tick comparison

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2019-07-16 21:43:23 +00:00
Jake Norbut 6b2708b160 PR suggestions 2019-07-16 16:34:59 -05:00
Jake Norbut 534474621f price filter should use new add_price tick-based method 2019-07-16 15:51:27 -05:00
Jake Norbut 554614ab72 add means to perform price comparsion based on tick equality 2019-07-16 15:50:44 -05:00
Pedro Pinto 2ede69a263 Updated release notes. 2019-07-16 16:21:31 -04:00
Pedro Pinto 9def94aceb Changed IB to cancel rather than error if it sees a child cancel.
This allows IB to remain agnostic of wheter the child cancel was a
simply a consequence of the IB itself shutting down, in which case it
would be ignored. If the IB instead errorred out than that error
superseeds the cancel and results in the IB reporting an error.
2019-07-16 15:55:28 -04:00
Pedro Pinto 0af30b3cc0 Changed cover to handle fills while canceling. 2019-07-16 15:32:38 -04:00
Taha Afzal a91c2d9bc6 SP-146 (Limiter will not accept unresolved fills) 2019-07-16 11:20:15 -04:00
Pedro Pinto 481c1fd2de Fixed gcc build. 2019-07-16 10:29:59 -04:00
Pedro Pinto e80fc807e0 Moved all reporting state calls to top level event handler.
Any CPU the algo order receives must originate from a top level event,
its simpler to consolidate all report guards in those events, instead
of chasing every call that comes out of the algo.
2019-07-16 10:20:30 -04:00
Pedro Pinto c8e054d079 Added algo wrapper documentation. 2019-07-16 10:10:46 -04:00
Pedro Pinto 9480e76d4b Cleaned up algo wrapper 2019-07-16 10:10:11 -04:00
Pedro Pinto 538411353a Improved cover to hedge while shutting down. 2019-07-16 08:48:36 -04:00
Pedro Pinto da6998c391 Do not error out when receiving an udpate from an unknown child.
Because the child is presumably a detached child.
2019-07-15 16:43:39 -04:00
Pedro Pinto 60786b105c Handled terminate_error while shutting down. 2019-07-15 16:26:22 -04:00
Pedro Pinto f488248abf Fixed handling of child update while shutdown. 2019-07-15 16:03:36 -04:00
Pedro Pinto 830b590e5b Algos now go through an orderly shutdown phase.
During which they can still process child updates.
2019-07-15 10:59:13 -04:00
Kevin Darby c9c93b3cf0 Merged in fix_span1 (pull request #300)
fix crash when acct names are > 12 chars

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-12 18:58:13 +00:00
Kevin Darby 9e5d43f44a fix crash where account name > 12 chars, tested with long account name 2019-07-11 17:53:48 -04:00
Kevin Darby 5c1965f534 fix crash when acct names are > 12 chars 2019-07-11 14:07:31 -04:00
Pedro Pinto 8553229119 Merge branch 'master' into prod 2019-07-11 10:45:10 -04:00
Pedro Pinto 4903c12533 _index.md edited online with Bitbucket 2019-07-11 14:23:13 +00:00
Jake Norbut 88f23530c6 Merged in SP-119 (pull request #298)
RTM - SP-119 Ladder settings NullRef

Approved-by: John Colvin <john.colvin@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-07-11 14:18:45 +00:00
Jake Norbut 76ff36e514 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SP-119 2019-07-09 11:54:46 -05:00
Jake Norbut 740c14c261 add LadderSettings santization on paths to VM instantiation 2019-07-09 11:52:41 -05:00
Jake Norbut dbdc915ff2 populating mini_ob (LadderMiniOrderBookSettings) in LadderSettings.Default 2019-07-08 14:24:36 -05:00
Jake Norbut 10476c6662 null ref check on LadderSettings.mini_ob.value 2019-07-08 14:22:55 -05:00
24 changed files with 391 additions and 90 deletions

No files matched your search

+24
View File
@@ -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;
+116 -20
View File
@@ -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 |
+1
View File
@@ -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) {}
+1 -1
View File
@@ -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)
+1 -1
View File
@@ -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();