Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
373f7ee3a1 | ||
|
|
2ed8b9caf0 | ||
|
|
470eee431e | ||
|
|
e5e6723b3a | ||
|
|
71be28911b | ||
|
|
8ab3182c21 | ||
|
|
508e2d4fcf | ||
|
|
027e434ac9 | ||
|
|
e9be6d9859 | ||
|
|
82e63f691c | ||
|
|
cb83ad08a6 | ||
|
|
e2e78d91c3 | ||
|
|
427d03d340 | ||
|
|
32e5111c54 | ||
|
|
ef12f81b73 | ||
|
|
c0f255f5a7 | ||
|
|
f6861820e4 | ||
|
|
6a3960b33f | ||
|
|
e3858fd203 | ||
|
|
3f1d9307e9 | ||
|
|
5f7c2330f3 | ||
|
|
315b2a8b26 | ||
|
|
29ebfebcbc | ||
|
|
4fbaca6d05 | ||
|
|
4d4f706e7c | ||
|
|
241396e292 | ||
|
|
4e68c98cfb | ||
|
|
30c1dfd9a7 | ||
|
|
a64ca702c9 | ||
|
|
0a2b6ce17e | ||
|
|
660f203132 | ||
|
|
27f27dbcf1 | ||
|
|
1ac6c8cd40 | ||
|
|
a13cececa1 | ||
|
|
01d2e88097 | ||
|
|
8f96cd07e3 | ||
|
|
b515e45b23 | ||
|
|
a37e2ead87 | ||
|
|
92ae592d31 | ||
|
|
db1cbf589f | ||
|
|
a561ff3aae | ||
|
|
3eeab708d2 | ||
|
|
082056ea3f | ||
|
|
e9f3e92f82 | ||
|
|
152c79a4a9 | ||
|
|
db877c859b |
No files matched your search
@@ -21,3 +21,5 @@ src/client/sparkcmd/Properties/launchSettings.json
|
||||
node_modules
|
||||
paket-files
|
||||
project.lock.json
|
||||
src/server/sparkd/orderman/perf_analysis/data
|
||||
src/server/sparkd/orderman/perf_analysis/trace_data/
|
||||
@@ -25,7 +25,7 @@ namespace bts.ui {
|
||||
public AboutDaemon(IBus bus, [Import("username")]string username, IBrowserWindowManager browser) {
|
||||
_user = username;
|
||||
_browser = browser;
|
||||
_dispose = bus.subscribe(SparkTags.About).ObserveOnDispatcher().Subscribe(_ => show_about());
|
||||
_dispose = bus.subscribe(SparkTags.About).OnDispatcher().Subscribe(_ => show_about());
|
||||
}
|
||||
|
||||
[Conditional("DEBUG")]
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace bts {
|
||||
public DeleteSyntheticDaemon(IBus bus, OrdersModel orders_model) {
|
||||
_orders_model = orders_model;
|
||||
_dispose = bus.subscribe(SparkTags.SyntheticDeleteTag)
|
||||
.ObserveOnDispatcher()
|
||||
.OnDispatcher()
|
||||
.Subscribe(_delete_synthetic);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ using mbts.ui;
|
||||
using System;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Threading;
|
||||
using System.Windows;
|
||||
|
||||
namespace bts.spark
|
||||
@@ -22,18 +22,21 @@ namespace bts.spark
|
||||
public ErrorReporter(ErrorReportService svc, IBus bus, [Import("username")] string username) {
|
||||
_svc = svc;
|
||||
_username = username;
|
||||
_dispose = bus.subscribe(SparkTags.Support).ObserveOnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
|
||||
_dispose = bus.subscribe(SparkTags.Support).OnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Disposable.dispose(ref _dispose);
|
||||
}
|
||||
|
||||
public async Task report_error(string message, string title = null, MessageBoxImage? image = null) {
|
||||
var result = await BTSMessageBox.ShowAsync(message, title = "Error", BTSMessageBoxButtons.OK | BTSMessageBoxButtons.Support, image ?? MessageBoxImage.Error);
|
||||
if (result == BTSMessageBoxButtons.Support) {
|
||||
show_support_window(message, title);
|
||||
}
|
||||
public void report_error(string message, string title = null, MessageBoxImage? image = null) {
|
||||
// Error reports are likely to arrive the api thread (which is not an STA thread)
|
||||
Application.Current.Dispatcher.BeginInvoke(async () => {
|
||||
var result = await BTSMessageBox.ShowAsync(message, title = "Error", BTSMessageBoxButtons.OK | BTSMessageBoxButtons.Support, image ?? MessageBoxImage.Error);
|
||||
if (result == BTSMessageBoxButtons.Support) {
|
||||
show_support_window(message, title);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void show_support_window(string message, string title) {
|
||||
@@ -70,11 +73,11 @@ namespace bts.spark
|
||||
return (get_successful_rv(result.value), string.Empty);
|
||||
else {
|
||||
string error_msg = template.fmt(error);
|
||||
error_reporter.report_error(error_msg).ignore();
|
||||
error_reporter.report_error(error_msg);
|
||||
return (get_unsuccessful_rv(), error_msg);
|
||||
}
|
||||
} else {
|
||||
error_reporter.report_error(result.error_message).ignore();
|
||||
error_reporter.report_error(result.error_message);
|
||||
return (get_unsuccessful_rv(), result.error_message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,11 +28,11 @@ namespace bts.shell {
|
||||
_model = model;
|
||||
_error_reporter = error_reporter;
|
||||
_dispose = Disposable.Create(
|
||||
model.on_orders.ObserveOnDispatcher().Subscribe(o => _orders = o),
|
||||
bus.subscribe(SparkTags.SecuritiesTag).ObserveOnDispatcher().Subscribe(m =>_md_map = m),
|
||||
bus.subscribe(SparkTags.ShowOrderTicketFromSecurityTag).ObserveOnDispatcher().Subscribe(_show_order_ticket),
|
||||
bus.subscribe(SparkTags.ShowOrderTicketTag).ObserveOnDispatcher().Subscribe(_show_order_ticket),
|
||||
bus.subscribe(SparkTags.ShowModifyOrderTag).ObserveOnDispatcher().Subscribe(_show_modify_order)
|
||||
model.on_orders.OnDispatcher().Subscribe(o => _orders = o),
|
||||
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m =>_md_map = m),
|
||||
bus.subscribe(SparkTags.ShowOrderTicketFromSecurityTag).OnDispatcher().Subscribe(_show_order_ticket),
|
||||
bus.subscribe(SparkTags.ShowOrderTicketTag).OnDispatcher().Subscribe(_show_order_ticket),
|
||||
bus.subscribe(SparkTags.ShowModifyOrderTag).OnDispatcher().Subscribe(_show_modify_order)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@ namespace bts {
|
||||
public SecurityPickerDaemon(IBus bus) {
|
||||
_bus = bus;
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.ShowPickOne).ObserveOnDispatcher().Subscribe(show_pick_one),
|
||||
bus.subscribe(SparkTags.FilterPickOne).ObserveOnDispatcher().Subscribe(show_filtered_pick_one),
|
||||
bus.subscribe(SparkTags.ShowPickMultiple).ObserveOnDispatcher().Subscribe(show_pick_multiple),
|
||||
bus.subscribe(SparkTags.ShowAndEditPickMultiple).ObserveOnDispatcher().Subscribe(show_and_edit_pick_multiple)
|
||||
bus.subscribe(SparkTags.ShowPickOne).OnDispatcher().Subscribe(show_pick_one),
|
||||
bus.subscribe(SparkTags.FilterPickOne).OnDispatcher().Subscribe(show_filtered_pick_one),
|
||||
bus.subscribe(SparkTags.ShowPickMultiple).OnDispatcher().Subscribe(show_pick_multiple),
|
||||
bus.subscribe(SparkTags.ShowAndEditPickMultiple).OnDispatcher().Subscribe(show_and_edit_pick_multiple)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -78,11 +78,11 @@ namespace mbts.spark {
|
||||
_animation.AnimationsChanged += on_animations_changed;
|
||||
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
|
||||
_dispose = Disposable.Create(
|
||||
_orders_model.on_security_orders(security.Id).ObserveOnDispatcher().Subscribe(_on_orders),
|
||||
_position.ObserveOnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
|
||||
_inside_bids.ObserveOnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
|
||||
_inside_asks.ObserveOnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
|
||||
_day_change.ObserveOnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
|
||||
_orders_model.on_security_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
|
||||
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
|
||||
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
|
||||
_inside_asks.OnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
|
||||
_day_change.OnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
|
||||
Disposable.Create(() => _animation.AnimationsChanged -= on_animations_changed)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ namespace mbts.spark {
|
||||
}
|
||||
//
|
||||
_resources.AddRange(
|
||||
state.ObserveOnDispatcher().Subscribe(s => {
|
||||
state.OnDispatcher().Subscribe(s => {
|
||||
using (rows_view.DeferRefresh()) {
|
||||
rows_view.SetSortingCriteria(s.sorting_criteria);
|
||||
var filters = s.filters;
|
||||
|
||||
@@ -57,7 +57,7 @@ namespace mbts.spark {
|
||||
SparkCommands.ShowTimeAndSales.complete(_vm.selected_securities_observable(), _ => true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
|
||||
SparkCommands.ShowSyntheticCreate.complete(_vm.selected_securities_observable(), secs => secs.Count > 0 && secs.Count < 3, secs => new SecurityCriteria(secs))
|
||||
};
|
||||
_subscriptions = _bus.subscribe(update_securities_tag).ObserveOnDispatcher().Subscribe(c =>
|
||||
_subscriptions = _bus.subscribe(update_securities_tag).OnDispatcher().Subscribe(c =>
|
||||
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.rows_view.GetSortingCriteria(), _vm.get_filter_states())));
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace bts.shell {
|
||||
public MarketViewWindowFactory(MarketShellModule module, IBus bus, OrdersModel orders_model) : base(module, "MarketView") {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_dispose = bus.subscribe(SparkTags.ShowMarketViewTag).ObserveOnDispatcher().Subscribe(_show);
|
||||
_dispose = bus.subscribe(SparkTags.ShowMarketViewTag).OnDispatcher().Subscribe(_show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
|
||||
@@ -47,8 +47,8 @@ namespace mbts.spark {
|
||||
_vm = new SingleValuePump<TimeAndSalesVM>();
|
||||
var update_securities_tag = new SparkTags.SparkTag<SecurityCriteria>("TimeAndSales Callback");
|
||||
var update_securities_cmd = SparkCommand.create("Update Time and Sales", Icon, update_securities_tag);
|
||||
var current_smd_source = _vm.observable.SelectMany(vm => vm.current_security_md_observable(_bus)).ObserveOnDispatcher();
|
||||
var selected_secs_source = _vm.observable.SelectMany(vm => vm.selected_securities_observable()).ObserveOnDispatcher();
|
||||
var current_smd_source = _vm.observable.SelectMany(vm => vm.current_security_md_observable(_bus)).OnDispatcher();
|
||||
var selected_secs_source = _vm.observable.SelectMany(vm => vm.selected_securities_observable()).OnDispatcher();
|
||||
_commands = new SparkCommand[] {
|
||||
SparkCommand.create("Change Securities", Icons.Admin, SparkTags.ShowAndEditPickMultiple, () => (update_securities_cmd, _state.security_criteria)),
|
||||
SparkCommands.ShowLadder.complete(current_smd_source, md => md.HasValue, ObjectHelper.ValueOrDefault),
|
||||
@@ -56,7 +56,7 @@ namespace mbts.spark {
|
||||
SparkCommands.ShowFills.complete(selected_secs_source, always_true, get_fills_startup_data),
|
||||
SparkCommands.ShowMarketView.complete(selected_secs_source, always_true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria)
|
||||
};
|
||||
_subscriptions = _bus.subscribe(update_securities_tag).ObserveOnDispatcher().Subscribe(update_criteria);
|
||||
_subscriptions = _bus.subscribe(update_securities_tag).OnDispatcher().Subscribe(update_criteria);
|
||||
_initialize();
|
||||
|
||||
bool always_true<T>(T _) => true;
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace bts.shell {
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowTimeAndSalesTag).ObserveOnDispatcher().Subscribe(show)
|
||||
bus.subscribe(SparkTags.ShowTimeAndSalesTag).OnDispatcher().Subscribe(show)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -98,9 +98,9 @@ namespace bts.shell {
|
||||
void IInitializable.initialize() {
|
||||
Disposable.dispose(ref _dispose);
|
||||
_dispose = Disposable.Create(
|
||||
_bus.subscribe(SparkTags.ShowSyntheticCreateTag).ObserveOnDispatcher().Subscribe(_show_synthetic_create),
|
||||
_bus.subscribe(SparkTags.ShowSyntheticStrategyTag).ObserveOnDispatcher().Subscribe(_show_synthetic_strategy),
|
||||
_bus.subscribe(SparkTags.ShowSyntheticCloneTag).ObserveOnDispatcher().Subscribe(_show_synthetic_clone)
|
||||
_bus.subscribe(SparkTags.ShowSyntheticCreateTag).OnDispatcher().Subscribe(_show_synthetic_create),
|
||||
_bus.subscribe(SparkTags.ShowSyntheticStrategyTag).OnDispatcher().Subscribe(_show_synthetic_strategy),
|
||||
_bus.subscribe(SparkTags.ShowSyntheticCloneTag).OnDispatcher().Subscribe(_show_synthetic_clone)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -32,14 +32,14 @@ namespace mbts.spark {
|
||||
SparkCommand.create("Add", Icons.AddSynthetic, SparkTags.ShowSyntheticCreateTag).complete(
|
||||
() => new SecurityCriteria(), () => true
|
||||
), SparkCommand.create("Remove", Icons.RemoveSynthetic, SparkTags.SyntheticDeleteTag).complete(
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable),
|
||||
can_row_change, r => r == null ? 0 : r.security_key
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable.SelectMany(observe_row_mod_params)),
|
||||
can_execute, select_security_id
|
||||
), SparkCommand.create("Change", Icons.ModifySynthetic, SparkTags.ShowSyntheticStrategyTag).complete(
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable),
|
||||
can_row_change, r => r == null ? SecurityDataHelper.Defaults.SecurityImmutable : r.security
|
||||
r => r != null, r => r?.security ?? SecurityDataHelper.Defaults.SecurityImmutable
|
||||
), SparkCommand.create("Clone", Icons.CloneSynthetic, SparkTags.ShowSyntheticCloneTag).complete(
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable),
|
||||
r => r != null && r.strategy.HasValue, r => r == null ? default : (r.security, r.strategy.Value)
|
||||
_vm.observable.SelectMany(vm => vm.current_row_observable.SelectMany(observe_row_strategy)),
|
||||
rs => rs.HasValue, select_row_strategy_tuple
|
||||
), SparkCommands.ShowLadder.complete(
|
||||
_vm.observable.SelectMany(vm => vm.current_security_md), md => md.HasValue, ObjectHelper.ValueOrDefault
|
||||
), SparkCommands.ShowOrders.complete(() => new OrderBookWindowFactory.OrderBookStartupData {
|
||||
@@ -49,10 +49,19 @@ namespace mbts.spark {
|
||||
})
|
||||
};
|
||||
|
||||
bool can_row_change(SyntheticManagementDialogVM.Row r) =>
|
||||
r != null && r.n_working_orders == 0;
|
||||
}
|
||||
IObservable<RowModificationParams?> observe_row_mod_params(SyntheticManagementDialogVM.Row r) =>
|
||||
r == null ? Observable.Return(new RowModificationParams?()) :
|
||||
_orders_model.security_has_working_orders(r.security_key).Select(hwo => RowModificationParams.create(r.security, hwo));
|
||||
|
||||
IObservable<RowStrategy?> observe_row_strategy(SyntheticManagementDialogVM.Row r) =>
|
||||
r == null ? Observable.Return(new RowStrategy?()) :
|
||||
_orders_model.spreader.security_strategy(r.security.Id).Select(strategy => RowStrategy.create(r.security, strategy));
|
||||
|
||||
uint select_security_id(RowModificationParams? rmp) => rmp.HasValue ? rmp.Value.security.Id : 0;
|
||||
(SecurityImmutable, ExecutionStrategyImmutable) select_row_strategy_tuple(RowStrategy? rs) => rs.HasValue ? (rs.Value.security, rs.Value.strategy) : default;
|
||||
bool can_execute(RowModificationParams? rmp) => rmp.HasValue && !rmp.Value.has_working_orders;
|
||||
}
|
||||
|
||||
public void initialize(StartupData state) {
|
||||
Logger.notnull(state);
|
||||
_state = state;
|
||||
@@ -71,5 +80,31 @@ namespace mbts.spark {
|
||||
Disposable.dispose(ref _vm);
|
||||
Commands.OfType<IDisposable>().DisposeAll();
|
||||
}
|
||||
|
||||
struct RowModificationParams {
|
||||
public readonly SecurityImmutable security;
|
||||
public readonly bool has_working_orders;
|
||||
|
||||
RowModificationParams(SecurityImmutable security, bool has_working_orders) {
|
||||
this.security = security;
|
||||
this.has_working_orders = has_working_orders;
|
||||
}
|
||||
|
||||
public static RowModificationParams? create(SecurityImmutable security, bool has_working_orders) =>
|
||||
new RowModificationParams(security, has_working_orders);
|
||||
}
|
||||
|
||||
struct RowStrategy {
|
||||
public readonly SecurityImmutable security;
|
||||
public readonly ExecutionStrategyImmutable strategy;
|
||||
|
||||
RowStrategy(SecurityImmutable security, ExecutionStrategyImmutable strategy) {
|
||||
this.security = security;
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
public static RowStrategy? create(SecurityImmutable security, ExecutionStrategyImmutable strategy) =>
|
||||
new RowStrategy(security, strategy);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ namespace bts.shell {
|
||||
public SyntheticManagementWindowFactory(SpreaderShellModule module, IBus bus, OrdersModel orders_model) : base(module, "SyntheticManagement") {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_dispose = bus.subscribe(SparkTags.ShowSyntheticManagementTag).ObserveOnDispatcher().Subscribe(show);
|
||||
_dispose = bus.subscribe(SparkTags.ShowSyntheticManagementTag).OnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace mbts.spark {
|
||||
readonly Property<bool> _enabled;
|
||||
|
||||
protected CellBase(IRowModel model, LegSpreaderParamsTuple leg_tup) =>
|
||||
_enabled = backing(nameof(enabled), model.observe_enabled(leg_tup).ObserveOnDispatcher());
|
||||
_enabled = backing(nameof(enabled), model.observe_enabled(leg_tup).OnDispatcher());
|
||||
|
||||
public bool enabled => _enabled.Value;
|
||||
}
|
||||
@@ -24,7 +24,7 @@ namespace mbts.spark {
|
||||
: base(model, leg_tup) {
|
||||
_model = model;
|
||||
_leg_tup = leg_tup;
|
||||
_override_value_sub = model.observe_override_value(leg_tup).ObserveOnDispatcher().Subscribe(v => {
|
||||
_override_value_sub = model.observe_override_value(leg_tup).OnDispatcher().Subscribe(v => {
|
||||
value = v;
|
||||
RaisePropertyChanged(nameof(value));
|
||||
});
|
||||
|
||||
@@ -44,10 +44,10 @@ namespace mbts.spark {
|
||||
_tick_factors = TickUtils.get_regional_ticks(synthetic.ContractSpec).low_tick;
|
||||
title = $"Edit '{synthetic.Synthetic.Name}'";
|
||||
icon = Icons.ModifySynthetic;
|
||||
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.ObserveOnDispatcher());
|
||||
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.OnDispatcher());
|
||||
var commit = new ObservantAsyncCommand(commit_async);
|
||||
_resources.Add(security_has_working_orders.Select(v => !v).ObserveOnDispatcher().Subscribe(commit));
|
||||
_resources.Add(_has_working_orders.ObserveOnDispatcher().Subscribe(vals => {
|
||||
_resources.Add(security_has_working_orders.Select(v => !v).OnDispatcher().Subscribe(commit));
|
||||
_resources.Add(_has_working_orders.OnDispatcher().Subscribe(vals => {
|
||||
if (vals.newval)
|
||||
set_error(nameof(has_working_orders), "Security has working orders");
|
||||
else clear_error(nameof(has_working_orders));
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace mbts.spark {
|
||||
.Select(t => (uint)(t.state
|
||||
.Where(kv => kv.Value.is_active())
|
||||
.Count())
|
||||
).ObserveOnDispatcher());
|
||||
).OnDispatcher());
|
||||
name = security.Synthetic.Name;
|
||||
n_legs = security.Synthetic.Legs.Count();
|
||||
description = SecurityFormatter.format_contract_legs(security.Synthetic.Legs);
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace bts.shell
|
||||
}
|
||||
|
||||
public void thread_init() {
|
||||
_dispose = _message_src.ObserveOnDispatcher().Subscribe(_messages.AddLog);
|
||||
_dispose = _message_src.OnDispatcher().Subscribe(_messages.AddLog);
|
||||
_content =
|
||||
new LogViewer {
|
||||
ItemsSource = _messages,
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace bts.shell {
|
||||
public HelpShellModule(IBus bus, IBrowserWindowManager browser) {
|
||||
_awf = new ActivityWindowFactory(this, bus);
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.ShowUri).ObserveOnDispatcher().Subscribe(u => browser.Pages.Add(u))
|
||||
bus.subscribe(SparkTags.ShowUri).OnDispatcher().Subscribe(u => browser.Pages.Add(u))
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,13 +24,16 @@ namespace mbts.spark {
|
||||
public FillsVM(IBus bus, OrdersModel model, StartupData state)
|
||||
: this(bus, model, _get_observable(bus, model, state), state.sorting_criteria, state.filters) { }
|
||||
|
||||
FillsVM(IBus bus, OrdersModel model, IObservable<IIncrementalUpdate<FillTuple, string>> fills_source, IReadOnlyCollection<SortingCriteria> sorting_criteria, FilterStates filters)
|
||||
FillsVM(IBus bus, OrdersModel model, IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> fills_source, IReadOnlyCollection<SortingCriteria> sorting_criteria, FilterStates filters)
|
||||
: base(RXCollection.CreateIncrementalUpdate(fills_source), sorting_criteria) {
|
||||
_model = model;
|
||||
account_filter = DiscreteValuesFilterVM.Create<Row, string>(_model.factory.subscribe_accounts(), r => r.account, nameof(Row.account));
|
||||
user_filter = DiscreteValuesFilterVM.Create<Row, string>(_model.factory.subscribe_users(), r => r.user, nameof(Row.user));
|
||||
security_filter = DiscreteValuesFilterVM.Create<Row, SecurityImmutable, SecurityCriteria>(
|
||||
fills_source.with_respect_to(ft => ft.security ?? SecurityDataHelper.Defaults.SecurityImmutable, s => s.Id, ft => ft.key).Select(t => (t.added.Values, t.removed.Values)),
|
||||
fills_source.with_respect_to(
|
||||
ft => ft.security_md.HasValue ? ft.security_md.Value.security : SecurityDataHelper.Defaults.SecurityImmutable,
|
||||
s => s.Id, ft => ft.key
|
||||
).Select(t => (t.added.Values, t.removed.Values)),
|
||||
r => r.security,
|
||||
SecurityCriteria.from_securities,
|
||||
SecurityCriteria.get_to_securities(bus),
|
||||
@@ -126,7 +129,7 @@ namespace mbts.spark {
|
||||
protected override Row row_factory(FillTuple row_model) => new Row(row_model, _model);
|
||||
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.fill_time), false); }
|
||||
|
||||
static IObservable<IIncrementalUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
|
||||
static IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
|
||||
if (state.orders != null && state.orders.Length == 0) {
|
||||
var orders_hash = new HashSet<uint>(state.orders);
|
||||
return model.on_fill_tuples.filter(f => orders_hash.Contains(f.fill.OrderKey));
|
||||
@@ -138,7 +141,7 @@ namespace mbts.spark {
|
||||
readonly FillImmutable _fill;
|
||||
readonly OrderRecordImmutable _order;
|
||||
|
||||
public Row(FillTuple fill_tuple, OrdersModel model) : base(fill_tuple.security ?? SecurityDataHelper.Defaults.Security) {
|
||||
public Row(FillTuple fill_tuple, OrdersModel model) : base(fill_tuple.security_md.HasValue ? fill_tuple.security_md.Value.security : SecurityDataHelper.Defaults.Security) {
|
||||
_fill = fill_tuple.fill;
|
||||
_order = fill_tuple.order ?? OrderDataHelper.Defaults.OrderRecordImmutable;
|
||||
_model = model;
|
||||
|
||||
@@ -18,7 +18,7 @@ namespace bts.shell {
|
||||
: base(module, "Fills") {
|
||||
_bus = bus;
|
||||
_orders_model = orders_model;
|
||||
_dispose = bus.subscribe(SparkTags.ShowFillsTag).ObserveOnDispatcher().Subscribe(show);
|
||||
_dispose = bus.subscribe(SparkTags.ShowFillsTag).OnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(StartupData state) =>
|
||||
|
||||
@@ -34,8 +34,8 @@ namespace bts.shell {
|
||||
_max_ladders_open = (int)(window_props?.ladder?.max_total_open ?? 75);
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
|
||||
bus.subscribe(SparkTags.ShowLadderTag).ObserveOnDispatcher().Subscribe(s => show_windows(new[] { s })),
|
||||
bus.subscribe(SparkTags.ShowLaddersTag).ObserveOnDispatcher().Subscribe(show_windows)
|
||||
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
|
||||
bus.subscribe(SparkTags.ShowLaddersTag).OnDispatcher().Subscribe(show_windows)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace bts.shell {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
|
||||
_dispose = bus.subscribe(SparkTags.ShowOrderBookTag).ObserveOnDispatcher().Subscribe(show);
|
||||
_dispose = bus.subscribe(SparkTags.ShowOrderBookTag).OnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
public override Task<IShellWindow> load(OrderBookStartupData state) {
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace bts.shell {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
|
||||
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).ObserveOnDispatcher().Subscribe(show);
|
||||
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).OnDispatcher().Subscribe(show);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace mbts.spark {
|
||||
var security_source = security_md_source.Select(smd => smd.security);
|
||||
//
|
||||
_security = backing(nameof(security), security_source, SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
properties_vm = new OrderPropertiesEditorVM(model, security_source.ObserveOnDispatcher(), order_source.Select(ot => new InlineOrderProperties(model, ot.order.Def)));
|
||||
properties_vm = new OrderPropertiesEditorVM(model, security_source.OnDispatcher(), order_source.Select(ot => new InlineOrderProperties(model, ot.order.Def)));
|
||||
_price = backing(nameof(price), double.NaN);
|
||||
size_calculator = new CalculatorLogic(null, calculator_buttons);
|
||||
_current_price = backing(nameof(current_price), order_source.Select(ort => ort.order.State.Price));
|
||||
@@ -83,15 +83,15 @@ namespace mbts.spark {
|
||||
size_calculator,
|
||||
properties_vm,
|
||||
bbo = new BBOVM(security_md_source),
|
||||
order_source.ObserveOnDispatcher().Subscribe(ort => {
|
||||
order_source.OnDispatcher().Subscribe(ort => {
|
||||
key = ort.order.Key;
|
||||
if (DoubleHelper.IsNaNOrInfinity(price))
|
||||
price = ort.order.State.Price;
|
||||
if (!size_calculator.value.HasValue || !size_calculator.value_setter_called)
|
||||
size_calculator.bypass_value_setter(ort.order.State.Quantity);
|
||||
}),
|
||||
Observable.Merge(_price_dirty, _size_dirty, _account_dirty, _time_in_force_dirty, _order_type_dirty).ObserveOnDispatcher().Subscribe(_ => RaisePropertyChanged(nameof(dirty_items_text))),
|
||||
_price_dirty.observe_current(true).CombineLatest(_size_dirty.observe_current(true), (pd, sd) => pd || sd).DistinctUntilChanged().ObserveOnDispatcher().Subscribe(modify_order));
|
||||
Observable.Merge(_price_dirty, _size_dirty, _account_dirty, _time_in_force_dirty, _order_type_dirty).OnDispatcher().Subscribe(_ => RaisePropertyChanged(nameof(dirty_items_text))),
|
||||
_price_dirty.observe_current(true).CombineLatest(_size_dirty.observe_current(true), (pd, sd) => pd || sd).DistinctUntilChanged().OnDispatcher().Subscribe(modify_order));
|
||||
this.modify_order = modify_order;
|
||||
set_price_command = new DelegateCommand<double>(_price.set);
|
||||
}
|
||||
|
||||
@@ -1,32 +1,53 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
namespace mbts.spark {
|
||||
class FillTuple : IEquatable<FillTuple> {
|
||||
public readonly FillImmutable fill;
|
||||
public readonly OrderRecordImmutable? order;
|
||||
public readonly SecurityImmutable? security;
|
||||
public readonly string key;
|
||||
public readonly SecurityMD? security_md;
|
||||
public readonly FillTupleKey key;
|
||||
|
||||
public FillTuple(FillImmutable fill, OrderRecordImmutable? order, SecurityImmutable? security) {
|
||||
public FillTuple(FillImmutable fill, OrderRecordImmutable? order, SecurityMD? security_md) {
|
||||
this.fill = fill;
|
||||
this.order = order;
|
||||
this.security = security;
|
||||
// a FillTuple that DOES NOT HAVE associated order/security needs to be distinguishable
|
||||
// from a FillTuple (of the same underlying FillImmutable) that DOES HAVE associated order/security
|
||||
key = fill.FillKey;
|
||||
if (!order.HasValue) key += "!o";
|
||||
if (!security.HasValue) key += "!s";
|
||||
this.security_md = security_md;
|
||||
key = new FillTupleKey(fill.FillKey, order.HasValue ? order.Value.Key : 0u, security_md.HasValue ? security_md.Value.security.Id : 0u);
|
||||
}
|
||||
|
||||
public bool Equals(FillTuple other) => other != null
|
||||
&& other.order.HasValue == order.HasValue
|
||||
&& other.security.HasValue == security.HasValue
|
||||
&& other.security_md.HasValue == security_md.HasValue
|
||||
&& other.fill.Equals(fill);
|
||||
|
||||
public override bool Equals(object obj) => Equals(obj as FillTuple);
|
||||
public override int GetHashCode() => key.GetHashCode();
|
||||
}
|
||||
|
||||
struct FillTupleKey : IEquatable<FillTupleKey> {
|
||||
public readonly string fill_key;
|
||||
public readonly uint order_key, security_key;
|
||||
|
||||
public FillTupleKey(string fill_key, uint order_key, uint security_key) {
|
||||
this.fill_key = fill_key;
|
||||
this.order_key = order_key;
|
||||
this.security_key = security_key;
|
||||
}
|
||||
|
||||
public bool Equals(FillTupleKey other) =>
|
||||
other.order_key == order_key &&
|
||||
other.security_key == security_key &&
|
||||
other.fill_key == fill_key;
|
||||
|
||||
public override bool Equals(object obj) =>
|
||||
obj is FillTupleKey k && Equals(k);
|
||||
|
||||
public override int GetHashCode() =>
|
||||
EqualityComparer.aggregate_hash(
|
||||
fill_key.GetHashCode(),
|
||||
(int)order_key,
|
||||
(int)security_key);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,4 @@
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using mbts.collections;
|
||||
using mbts.utils;
|
||||
@@ -11,20 +10,21 @@ using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
|
||||
namespace mbts.spark {
|
||||
class FillTupleSource : IDisposable {
|
||||
public static IObservable<IIncrementalUpdate<FillTuple, string>> create_observable(
|
||||
class FillTupleSource : IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>>, IDisposable {
|
||||
// returns a ref-count Observable.Create-wrapper that initializes/destroys the [hot-observable] FillTupleSource.
|
||||
// (initializes when the first subscription is made, and destroys when the last subscription is disposed).
|
||||
public static IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> create_observable(
|
||||
IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities,
|
||||
IObservable<TableUpdate<OrderRecordImmutable, uint>> orders,
|
||||
IObservable<TableUpdate<FillImmutable, string>> fills
|
||||
) {
|
||||
IObservable<TableUpdate<FillImmutable, string>> fills) {
|
||||
FillTupleSource source = null;
|
||||
int ref_count = 0;
|
||||
return Observable.Create<IIncrementalUpdate<FillTuple, string>>(observer => {
|
||||
return Observable.Create<IIncrementalUpdate<FillTuple, FillTupleKey>>(observer => {
|
||||
if (Interlocked.Increment(ref ref_count) == 1) {
|
||||
SparkUtils.lazy_assert(source == null, () => fmt_assert_msg("source should be null, there must be a leak."));
|
||||
source = new FillTupleSource(securities, orders, fills);
|
||||
}
|
||||
IDisposable sub = source.fill_tuples.Subscribe(observer);
|
||||
IDisposable sub = source.Subscribe(observer);
|
||||
return Disposable.Create(() => {
|
||||
Disposable.dispose_once(ref sub);
|
||||
if (Interlocked.Decrement(ref ref_count) == 0)
|
||||
@@ -34,180 +34,157 @@ namespace mbts.spark {
|
||||
string fmt_assert_msg(string msg) => $"{nameof(FillTupleSource)}.{nameof(create_observable)}: {msg}";
|
||||
}
|
||||
|
||||
readonly Subject<IIncrementalUpdate<FillTuple, FillTupleKey>> _subject = new Subject<IIncrementalUpdate<FillTuple, FillTupleKey>>();
|
||||
readonly Dictionary<FillTupleKey, FillTuple> _state = new Dictionary<FillTupleKey, FillTuple>();
|
||||
readonly SetMap<uint, string> _order_fills = new SetMap<uint, string>();
|
||||
readonly SetMap<uint, string> _security_fills = new SetMap<uint, string>();
|
||||
IReadOnlyDictionary<string, FillImmutable> _fills_cache = new Dictionary<string, FillImmutable>(0);
|
||||
IReadOnlyDictionary<uint, OrderRecordImmutable> _orders_cache = new Dictionary<uint, OrderRecordImmutable>(0);
|
||||
IReadOnlyDictionary<uint, SecurityMD> _security_md_cache = new Dictionary<uint, SecurityMD>(0);
|
||||
// _n_write_ops should never be greater than 1, bc all 3 input observables fire 'OnDispatcher'
|
||||
int _n_read_ops, _n_write_ops;
|
||||
IDisposable _dispose;
|
||||
|
||||
FillTupleSource(
|
||||
IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities,
|
||||
IObservable<TableUpdate<OrderRecordImmutable, uint>> orders,
|
||||
IObservable<TableUpdate<FillImmutable, string>> fills) {
|
||||
State state = new State();
|
||||
fill_tuples = Observable.Create<IIncrementalUpdate<FillTuple, string>>(observer => {
|
||||
observer.OnNext(new IncrementalUpdate<FillTuple, string>(state.full_state.ToDictionary(), new Dictionary<string, FillTuple>(0)));
|
||||
return _subject.Subscribe(observer);
|
||||
});
|
||||
IObservable<TableUpdate<FillImmutable, string>> fills) =>
|
||||
//
|
||||
_dispose = Disposable.Create(
|
||||
securities.ObserveOnDispatcher().Subscribe(s => {
|
||||
state.update_securities(s);
|
||||
_subject.OnNext(state.last_update);
|
||||
}),
|
||||
orders.ObserveOnDispatcher().Subscribe(o => {
|
||||
state.update_orders(o);
|
||||
_subject.OnNext(state.last_update);
|
||||
}),
|
||||
fills.ObserveOnDispatcher().Subscribe(f => {
|
||||
state.update_fills(f);
|
||||
_subject.OnNext(state.last_update);
|
||||
}));
|
||||
securities.OnDispatcher().Subscribe(_update_securities),
|
||||
orders.OnDispatcher().Subscribe(_update_orders),
|
||||
fills.OnDispatcher().Subscribe(_update_fills));
|
||||
|
||||
public IDisposable Subscribe(IObserver<IIncrementalUpdate<FillTuple, FillTupleKey>> observer) {
|
||||
using (_read()) {
|
||||
observer.OnNext(new IncrementalUpdate<FillTuple, FillTupleKey>(new Dictionary<FillTupleKey, FillTuple>(_state), new Dictionary<FillTupleKey, FillTuple>(0)));
|
||||
return _subject.Subscribe(observer);
|
||||
}
|
||||
}
|
||||
|
||||
readonly Subject<IIncrementalUpdate<FillTuple, string>> _subject = new Subject<IIncrementalUpdate<FillTuple, string>>();
|
||||
public void Dispose() => Disposable.dispose(ref _dispose);
|
||||
|
||||
IObservable<IIncrementalUpdate<FillTuple, string>> fill_tuples { get; }
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
class State {
|
||||
IReadOnlyDictionary<string, FillImmutable> _fills;
|
||||
IReadOnlyDictionary<uint, OrderRecordImmutable> _orders;
|
||||
IReadOnlyDictionary<uint, SecurityMD> _securities;
|
||||
IncrementalUpdate<FillTuple, string> _last_update;
|
||||
readonly Dictionary<string, FillTuple> _state;
|
||||
readonly SetMap<uint, string> _order_fills;
|
||||
readonly SetMap<uint, string> _security_fills;
|
||||
|
||||
public State() : this(
|
||||
new Dictionary<string, FillImmutable>(),
|
||||
new Dictionary<uint, OrderRecordImmutable>(),
|
||||
new Dictionary<uint, SecurityMD>(),
|
||||
new IncrementalUpdate<FillTuple, string>(new Dictionary<string, FillTuple>(), new Dictionary<string, FillTuple>()),
|
||||
new Dictionary<string, FillTuple>(),
|
||||
new SetMap<uint, string>(),
|
||||
new SetMap<uint, string>()
|
||||
) { }
|
||||
|
||||
State(
|
||||
IReadOnlyDictionary<string, FillImmutable> fills,
|
||||
IReadOnlyDictionary<uint, OrderRecordImmutable> orders,
|
||||
IReadOnlyDictionary<uint, SecurityMD> securities,
|
||||
IncrementalUpdate<FillTuple, string> last_update,
|
||||
Dictionary<string, FillTuple> state,
|
||||
SetMap<uint, string> order_fill_tuples,
|
||||
SetMap<uint, string> security_fill_tuples
|
||||
) {
|
||||
_fills = fills;
|
||||
_orders = orders;
|
||||
_securities = securities;
|
||||
_last_update = last_update;
|
||||
_state = state;
|
||||
_order_fills = order_fill_tuples;
|
||||
_security_fills = security_fill_tuples;
|
||||
}
|
||||
|
||||
public IncrementalUpdate<FillTuple, string> last_update => _last_update;
|
||||
public IReadOnlyDictionary<string, FillTuple> full_state => _state;
|
||||
|
||||
public void update_fills(TableUpdate<FillImmutable, string> fills) {
|
||||
var removed = fills.removed.ToDictionary(f => f.FillKey, get_fill_tuple);
|
||||
var added = fills.added.ToDictionary(f => f.FillKey, get_fill_tuple);
|
||||
HashSet<string> added_keys = new HashSet<string>(fills.added.Select(f => f.FillKey)),
|
||||
removed_keys = new HashSet<string>(fills.removed.Select(f => f.FillKey));
|
||||
foreach (var kv in removed) {
|
||||
_state.Remove(kv.Key);
|
||||
if (!added_keys.Contains(kv.Key)) {
|
||||
_order_fills.remove(kv.Value.fill.OrderKey, kv.Key);
|
||||
_security_fills.remove(kv.Value.fill.SecurityKey, kv.Key);
|
||||
}
|
||||
void _update_fills(TableUpdate<FillImmutable, string> fills) {
|
||||
using (_write()) {
|
||||
Dictionary<FillTupleKey, FillTuple> removed = new Dictionary<FillTupleKey, FillTuple>(), added = new Dictionary<FillTupleKey, FillTuple>();
|
||||
foreach (FillImmutable fill in fills.removed) {
|
||||
FillTuple old_ft = new FillTuple(fill, _get_order(fill.OrderKey), _get_security_md(fill.SecurityKey));
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
_security_fills.remove(fill.SecurityKey, fill.FillKey);
|
||||
_order_fills.remove(fill.OrderKey, fill.FillKey);
|
||||
}
|
||||
foreach (var kv in added) {
|
||||
_state.Add(kv.Key, kv.Value);
|
||||
if (!removed_keys.Contains(kv.Key)) {
|
||||
_order_fills.add(kv.Value.fill.OrderKey, kv.Key);
|
||||
_security_fills.add(kv.Value.fill.SecurityKey, kv.Key);
|
||||
}
|
||||
foreach (FillImmutable fill in fills.added) {
|
||||
FillTuple new_ft = new FillTuple(fill, _get_order(fill.OrderKey), _get_security_md(fill.SecurityKey));
|
||||
added.Add(new_ft.key, new_ft);
|
||||
_security_fills.add(fill.SecurityKey, fill.FillKey);
|
||||
_order_fills.add(fill.OrderKey, fill.FillKey);
|
||||
}
|
||||
_fills = fills.state;
|
||||
_last_update = new IncrementalUpdate<FillTuple, string>(added, removed);
|
||||
//
|
||||
FillTuple get_fill_tuple(FillImmutable fill) =>
|
||||
new FillTuple(fill, _orders.TryGetValue(fill.OrderKey, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?(),
|
||||
_securities.TryGetValue(fill.SecurityKey, out SecurityMD s) ? s.security : new SecurityImmutable?());
|
||||
_fills_cache = fills.state;
|
||||
_apply_changes(added, removed);
|
||||
}
|
||||
}
|
||||
|
||||
public void update_orders(TableUpdate<OrderRecordImmutable, uint> orders) {
|
||||
Dictionary<string, FillTuple> added = new Dictionary<string, FillTuple>(), removed = new Dictionary<string, FillTuple>();
|
||||
Dictionary<uint, (OrderRecordImmutable oldval, OrderRecordImmutable newval)> changed_orders = new Dictionary<uint, (OrderRecordImmutable oldval, OrderRecordImmutable newval)>();
|
||||
void _update_orders(TableUpdate<OrderRecordImmutable, uint> orders) {
|
||||
using (_write()) {
|
||||
Dictionary<FillTupleKey, FillTuple> added = new Dictionary<FillTupleKey, FillTuple>(), removed = new Dictionary<FillTupleKey, FillTuple>();
|
||||
HashSet<uint> changed_keys = new HashSet<uint>();
|
||||
foreach (var o in orders.removed) {
|
||||
if (orders.try_get(o.Key, out OrderRecordImmutable newval))
|
||||
changed_orders.Add(o.Key, (o, newval));
|
||||
else if (_order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
|
||||
foreach (FillTuple ft in get_fill_tuples(o, fill_keys))
|
||||
removed.Add(ft.key, ft);
|
||||
foreach (FillTuple ft in get_fill_tuples(null, fill_keys))
|
||||
added.Add(ft.key, ft);
|
||||
if (_order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
|
||||
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
|
||||
SecurityMD? sec_md = _get_security_md(o.security_key());
|
||||
foreach (FillImmutable fill in fills) {
|
||||
FillTuple old_ft = new FillTuple(fill, o, sec_md);
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
}
|
||||
OrderRecordImmutable? curr_order = null;
|
||||
if (orders.try_get(o.Key, out OrderRecordImmutable newval)) {
|
||||
curr_order = newval;
|
||||
changed_keys.Add(o.Key);
|
||||
}
|
||||
foreach (FillImmutable fill in fills) {
|
||||
FillTuple new_ft = new FillTuple(fill, curr_order, sec_md);
|
||||
added.Add(new_ft.key, new_ft);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var o in orders.added) {
|
||||
if (!changed_orders.ContainsKey(o.Key) && _order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
|
||||
foreach (FillTuple ft in get_fill_tuples(null, fill_keys))
|
||||
removed.Add(ft.key, ft);
|
||||
foreach (FillTuple ft in get_fill_tuples(o, fill_keys))
|
||||
added.Add(ft.key, ft);
|
||||
if (!changed_keys.Contains(o.Key) && _order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
|
||||
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
|
||||
SecurityMD? sec_md = _get_security_md(o.security_key());
|
||||
foreach (FillImmutable fill in fills) {
|
||||
FillTuple old_ft = new FillTuple(fill, null, sec_md);
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
FillTuple new_ft = new FillTuple(fill, o, sec_md);
|
||||
added.Add(new_ft.key, new_ft);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var kv in changed_orders) {
|
||||
if (_order_fills.try_lookup(kv.Key, out ISet<string> fill_keys)) {
|
||||
var fs_pairs = get_fill_security_pairs(fill_keys).ToList();
|
||||
foreach (FillTuple ft in fs_pairs.Select(fsp => new FillTuple(fsp.fill, kv.Value.oldval, fsp.security)))
|
||||
removed.Add(ft.key, ft);
|
||||
foreach (FillTuple ft in fs_pairs.Select(fsp => new FillTuple(fsp.fill, kv.Value.newval, fsp.security)))
|
||||
added.Add(ft.key, ft);
|
||||
}
|
||||
}
|
||||
_orders = orders.state;
|
||||
_last_update = new IncrementalUpdate<FillTuple, string>(added, removed);
|
||||
//
|
||||
IEnumerable<FillTuple> get_fill_tuples(OrderRecordImmutable? o, ISet<string> fill_keys) =>
|
||||
fill_keys.Select(k => {
|
||||
var fill = _fills[k];
|
||||
return new FillTuple(fill, o, _securities.TryGetValue(fill.SecurityKey, out SecurityMD s) ? s.security : new SecurityImmutable?());
|
||||
});
|
||||
|
||||
IEnumerable<(FillImmutable fill, SecurityImmutable? security)> get_fill_security_pairs(ISet<string> fill_keys) =>
|
||||
fill_keys.Select(k => {
|
||||
var fill = _fills[k];
|
||||
return (fill, _securities.TryGetValue(fill.SecurityKey, out SecurityMD s) ? s.security : new SecurityImmutable?());
|
||||
});
|
||||
_orders_cache = orders.state;
|
||||
_apply_changes(added, removed);
|
||||
}
|
||||
}
|
||||
|
||||
public void update_securities(IReadOnlyDictionary<uint, SecurityMD> securities) {
|
||||
Dictionary<string, FillTuple> added = new Dictionary<string, FillTuple>(), removed = new Dictionary<string, FillTuple>();
|
||||
_securities.Keys.difference(securities.Keys, out IEnumerable<uint> added_keys, out IEnumerable<uint> removed_keys);
|
||||
void _update_securities(IReadOnlyDictionary<uint, SecurityMD> securities) {
|
||||
using (_write()) {
|
||||
Dictionary<FillTupleKey, FillTuple> added = new Dictionary<FillTupleKey, FillTuple>(), removed = new Dictionary<FillTupleKey, FillTuple>();
|
||||
_security_md_cache.Keys.difference(securities.Keys, out IEnumerable<uint> added_keys, out IEnumerable<uint> removed_keys);
|
||||
foreach (var k in removed_keys) {
|
||||
if (_security_fills.try_lookup(k, out ISet<string> fill_keys)) {
|
||||
foreach (FillTuple ft in get_fill_tuples(_securities[k].security, fill_keys))
|
||||
removed.Add(ft.key, ft);
|
||||
foreach (FillTuple ft in get_fill_tuples(null, fill_keys))
|
||||
added.Add(ft.key, ft);
|
||||
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
|
||||
SecurityMD old_sec_md = _security_md_cache[k];
|
||||
foreach (FillImmutable fill in fills) {
|
||||
OrderRecordImmutable? order = _get_order(fill.OrderKey);
|
||||
FillTuple old_ft = new FillTuple(fill, order, old_sec_md);
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
FillTuple new_ft = new FillTuple(fill, order, null);
|
||||
added.Add(new_ft.key, new_ft);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (var k in added_keys) {
|
||||
if (_security_fills.try_lookup(k, out ISet<string> fill_keys)) {
|
||||
foreach (FillTuple ft in get_fill_tuples(null, fill_keys))
|
||||
removed.Add(ft.key, ft);
|
||||
foreach (FillTuple ft in get_fill_tuples(securities[k].security, fill_keys))
|
||||
added.Add(ft.key, ft);
|
||||
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
|
||||
SecurityMD new_sec_md = securities[k];
|
||||
foreach (FillImmutable fill in fills) {
|
||||
OrderRecordImmutable? order = _get_order(fill.OrderKey);
|
||||
FillTuple old_ft = new FillTuple(fill, order, null);
|
||||
removed.Add(old_ft.key, old_ft);
|
||||
FillTuple new_ft = new FillTuple(fill, order, new_sec_md);
|
||||
added.Add(new_ft.key, new_ft);
|
||||
}
|
||||
}
|
||||
}
|
||||
_securities = securities;
|
||||
_last_update = new IncrementalUpdate<FillTuple, string>(added, removed);
|
||||
//
|
||||
IEnumerable<FillTuple> get_fill_tuples(SecurityImmutable? s, ISet<string> fill_keys) =>
|
||||
fill_keys.Select(k => {
|
||||
var fill = _fills[k];
|
||||
return new FillTuple(fill, _orders.TryGetValue(fill.OrderKey, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?(), s);
|
||||
});
|
||||
_security_md_cache = securities;
|
||||
_apply_changes(added, removed);
|
||||
}
|
||||
}
|
||||
|
||||
SecurityMD? _get_security_md(uint key) =>
|
||||
_security_md_cache.TryGetValue(key, out SecurityMD smd) ? smd : new SecurityMD?();
|
||||
|
||||
OrderRecordImmutable? _get_order(uint key) =>
|
||||
_orders_cache.TryGetValue(key, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?();
|
||||
|
||||
void _apply_changes(Dictionary<FillTupleKey, FillTuple> added, Dictionary<FillTupleKey, FillTuple> removed) {
|
||||
foreach (var kv in removed)
|
||||
_state.Remove(kv.Key);
|
||||
foreach (var kv in added)
|
||||
_state.Add(kv.Key, kv.Value);
|
||||
_subject.OnNext(new IncrementalUpdate<FillTuple, FillTupleKey>(added, removed));
|
||||
}
|
||||
|
||||
IDisposable _read() {
|
||||
while (_n_write_ops > 0)
|
||||
Thread.Yield();
|
||||
Interlocked.Increment(ref _n_read_ops);
|
||||
return Disposable.Create(() => Interlocked.Decrement(ref _n_read_ops));
|
||||
}
|
||||
|
||||
IDisposable _write() {
|
||||
while (_n_read_ops > 0)
|
||||
Thread.Yield();
|
||||
Interlocked.Increment(ref _n_write_ops);
|
||||
return Disposable.Create(() => Interlocked.Decrement(ref _n_write_ops));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -61,7 +61,7 @@ namespace mbts.spark {
|
||||
set_price_command = new DelegateCommand<double>(set_price);
|
||||
_dispose = Disposable.Create(
|
||||
size_calculator = new CalculatorLogic(0, calculator_buttons),
|
||||
props_source.ObserveOnDispatcher().Subscribe(p => on_props(p.price, p.size))
|
||||
props_source.OnDispatcher().Subscribe(p => on_props(p.price, p.size))
|
||||
);
|
||||
|
||||
void on_props(InlineUserProperty<double> price_prop, InlineUserProperty<uint> size_prop) {
|
||||
|
||||
@@ -46,7 +46,7 @@ namespace mbts.spark {
|
||||
|
||||
public OrderPropertiesEditorVM(OrdersModel model, IObservable<SecurityImmutable> security_source, IObservable<InlineOrderProperties> order_properties) {
|
||||
_model = model;
|
||||
_security = backing(nameof(security), security_source.ObserveOnDispatcher(), SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
_security = backing(nameof(security), security_source.OnDispatcher(), SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
// guarentee that "this.security" will always be set before observers are notified
|
||||
// this is important bc some observers will reference "this.security" in their callbacks
|
||||
// - namely the [SelectorProperty]order_type's "get_default" function,
|
||||
|
||||
@@ -44,9 +44,9 @@ namespace mbts.spark {
|
||||
: base(model) {
|
||||
_security_source = security_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.ObserveOnDispatcher()
|
||||
security_source.OnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
inline_properties.ObserveOnDispatcher()
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override) {
|
||||
_order_types.SetItems(p.child_order_types.value);
|
||||
|
||||
@@ -88,9 +88,9 @@ namespace mbts.spark
|
||||
{
|
||||
_security_source = security_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.ObserveOnDispatcher()
|
||||
security_source.OnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
inline_properties.ObserveOnDispatcher()
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
{
|
||||
|
||||
@@ -55,9 +55,9 @@ namespace mbts.spark
|
||||
{
|
||||
_security_source = security_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.ObserveOnDispatcher()
|
||||
security_source.OnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
inline_properties.ObserveOnDispatcher()
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
{
|
||||
|
||||
@@ -31,10 +31,10 @@ namespace mbts.spark {
|
||||
_strategy_editor_wrapper = new StrategyEditorWrapper(this);
|
||||
_dispose = Disposable.Create(
|
||||
_strategy_editor_wrapper,
|
||||
_template.ObserveOnDispatcher().Subscribe(vals => {
|
||||
_template.OnDispatcher().Subscribe(vals => {
|
||||
_validate();
|
||||
}),
|
||||
order_template_source.ObserveOnDispatcher().Subscribe(prop => {
|
||||
order_template_source.OnDispatcher().Subscribe(prop => {
|
||||
if (prop.force_override || (!template.HasValue && prop.has_value)) {
|
||||
_template.Value = prop.value;
|
||||
_validate();
|
||||
|
||||
@@ -64,9 +64,9 @@ namespace mbts.spark
|
||||
{
|
||||
_security_source = security_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.ObserveOnDispatcher()
|
||||
security_source.OnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
inline_properties.ObserveOnDispatcher()
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
{
|
||||
|
||||
@@ -50,9 +50,9 @@ namespace mbts.spark {
|
||||
: base(model) {
|
||||
_security_source = security_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.ObserveOnDispatcher()
|
||||
security_source.OnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
inline_properties.ObserveOnDispatcher()
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override) {
|
||||
_order_types.SetItems(p.child_order_types.value);
|
||||
|
||||
@@ -87,9 +87,9 @@ namespace mbts.spark
|
||||
{
|
||||
_security_source = security_source;
|
||||
_dispose = Disposable.Create(
|
||||
security_source.ObserveOnDispatcher()
|
||||
security_source.OnDispatcher()
|
||||
.Subscribe(s => _security = s),
|
||||
inline_properties.ObserveOnDispatcher()
|
||||
inline_properties.OnDispatcher()
|
||||
.Subscribe(p =>
|
||||
{
|
||||
if (_order_types.Count == 0 || p.child_order_types.force_override)
|
||||
|
||||
@@ -31,11 +31,11 @@ namespace mbts.spark {
|
||||
_service = service;
|
||||
_spreader = spreader;
|
||||
_error = error;
|
||||
on_orders = _service.subscribei_orders().Table().Replay(1).RefCount();
|
||||
on_orders = _service.subscribei_orders().Table().Cache();
|
||||
IReadOnlyDictionary<uint, OrderRecordImmutable> empty_orders = new Dictionary<uint, OrderRecordImmutable>();
|
||||
working_orders =
|
||||
on_orders.Scan(empty_orders, (worders, table) => _update_working_orders(worders, table)).Replay(1).RefCount();
|
||||
on_fills = _service.subscribei_fills().Table().Replay(1).RefCount();
|
||||
on_fills = _service.subscribei_fills().Table().Cache();
|
||||
_activity = new OrderActivitySummarizer(bus, on_orders);
|
||||
on_fill_tuples = FillTupleSource.create_observable(bus.subscribe(SparkTags.SecuritiesTag), on_orders, on_fills);
|
||||
_security_orders_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
|
||||
@@ -53,7 +53,7 @@ namespace mbts.spark {
|
||||
public SpreaderModel spreader => _spreader;
|
||||
public IObservable<TableUpdate<OrderRecordImmutable, uint>> on_orders { get; }
|
||||
public IObservable<TableUpdate<FillImmutable, string>> on_fills { get; }
|
||||
public IObservable<IIncrementalUpdate<FillTuple, string>> on_fill_tuples { get; }
|
||||
public IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> on_fill_tuples { get; }
|
||||
public IObservable<IReadOnlyDictionary<uint,OrderRecordImmutable>> working_orders { get; }
|
||||
|
||||
public IObservable<ITableUpdateTransform<OrderRecordImmutable, uint>> on_security_orders(uint security) =>
|
||||
|
||||
@@ -175,8 +175,8 @@ namespace bts.spark
|
||||
public SparkCommandParamObserver(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute) : base(title, icon) {
|
||||
_tag = tag;
|
||||
_subs = Disposable.Create(
|
||||
parameter.ObserveOnDispatcher().Subscribe(p => _parameter = p),
|
||||
can_execute.ObserveOnDispatcher().Subscribe(ce => {
|
||||
parameter.OnDispatcher().Subscribe(p => _parameter = p),
|
||||
can_execute.OnDispatcher().Subscribe(ce => {
|
||||
_can_execute = ce;
|
||||
raiseCanExecuteChanged();
|
||||
})
|
||||
|
||||
@@ -345,6 +345,17 @@ namespace System.Reactive.Linq
|
||||
|
||||
public static async Task<(TResult result, bool success)> FirstAsyncTask<T, TResult>(this IObservable<T> source, Func<T, bool> predicate, Func<T, TResult> selector, TimeSpan timeout) where T : class =>
|
||||
await source.Where(predicate).Select(val => (selector(val), true)).Merge(Observable.Return((default(TResult), false)).Delay(timeout)).FirstAsync();
|
||||
|
||||
// This is placed here to intentionally make ObserveOnDispatcher ambiguous with the one provided by Rx
|
||||
public static IObservable<T> ObserveOnDispatcher<T>(this IObservable<T> source) {
|
||||
throw new InvalidOperationException("This method can starve the UI of input messages. Please use OnDispatcher() instead");
|
||||
}
|
||||
|
||||
public static IObservable<T> OnDispatcher<T>(this IObservable<T> source) {
|
||||
return DispatcherObservable.ObserveOnDispatcher(source, DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,9 @@ using bts.api.persistence;
|
||||
|
||||
namespace mbts.ui.settings
|
||||
{
|
||||
//
|
||||
// This class is used for tools like SettingsManager.exe
|
||||
//
|
||||
[Export(typeof(IUserPersistFactory))]
|
||||
class UISettingsFactory : IUserPersistFactory
|
||||
{
|
||||
@@ -34,7 +37,7 @@ namespace mbts.ui.settings
|
||||
|
||||
public IUserPersist get_persist_for(string name) {
|
||||
var user = _users.Result.FirstOrDefault(u => u.Login == name);
|
||||
return new UserUISettings(user.Id, _persist);
|
||||
return new UserUISettings(user.Id, _persist, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ using bts.api.core;
|
||||
using bts.api.persistence;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Reactive.Linq;
|
||||
using bts.spark;
|
||||
|
||||
namespace mbts.persist
|
||||
{
|
||||
@@ -18,11 +19,13 @@ namespace mbts.persist
|
||||
protected abstract string user();
|
||||
string uns(string ns) { return $"{user()}#{ns}"; }
|
||||
PersistenceService _persist;
|
||||
ErrorReporter _errors;
|
||||
|
||||
private const string _settings_change_base = "/ui/settingschange/";
|
||||
|
||||
public UISettingsBase(PersistenceService persist) {
|
||||
public UISettingsBase(PersistenceService persist, ErrorReporter errors) {
|
||||
_persist = persist;
|
||||
_errors = errors;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +53,7 @@ namespace mbts.persist
|
||||
Ns = uns(ns),
|
||||
Key = { key }
|
||||
});
|
||||
check_error(ret.value.Result, ns, key, out bool empty);
|
||||
check_error(ret, ns, key, out bool empty);
|
||||
if (empty) return default(T);
|
||||
return deserialize<T>(ret.value.Record.Single().Value);
|
||||
}
|
||||
@@ -67,20 +70,28 @@ namespace mbts.persist
|
||||
return ProtoBuf.Serializer.DeserializeWithLengthPrefix<T>(stream, ProtoBuf.PrefixStyle.Fixed32);
|
||||
}
|
||||
|
||||
private void check_error(RetrieveResponse.Types.Result result, string ns, string key, out bool empty) {
|
||||
switch (result) {
|
||||
case RetrieveResponse.Types.Result.EmptyNs:
|
||||
case RetrieveResponse.Types.Result.NoSuchNs:
|
||||
case RetrieveResponse.Types.Result.NoSuchKey:
|
||||
empty = true;
|
||||
return;
|
||||
case RetrieveResponse.Types.Result.EmptyKey:
|
||||
case RetrieveResponse.Types.Result.DuplicateKey:
|
||||
throw new ArgumentException($"Invalid Key {result} '{key}'");
|
||||
default:
|
||||
case RetrieveResponse.Types.Result.Ok:
|
||||
empty = false;
|
||||
return;
|
||||
private void check_error(Result<RetrieveResponse> result, string ns, string key, out bool empty) {
|
||||
if (result.success) {
|
||||
switch (result.value.Result) {
|
||||
case RetrieveResponse.Types.Result.EmptyNs:
|
||||
case RetrieveResponse.Types.Result.NoSuchNs:
|
||||
case RetrieveResponse.Types.Result.NoSuchKey:
|
||||
empty = true;
|
||||
return;
|
||||
case RetrieveResponse.Types.Result.EmptyKey:
|
||||
case RetrieveResponse.Types.Result.DuplicateKey:
|
||||
_errors?.report_error($"Invalid Key {result} '{key}'", "Requested Invalid Settings Key", System.Windows.MessageBoxImage.Information);
|
||||
empty = true;
|
||||
return;
|
||||
default:
|
||||
case RetrieveResponse.Types.Result.Ok:
|
||||
empty = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
_errors?.report_error($"There was an error communicating with the settings server: '{result.error_message}'", "Failed to retrieve settings from the server");
|
||||
empty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +101,7 @@ namespace mbts.persist
|
||||
new RetrieveAllRequest() {
|
||||
Ns = uns(ns)
|
||||
});
|
||||
check_error(results.value.Result, ns, "", out bool empty);
|
||||
check_error(results, ns, "", out bool empty);
|
||||
if (empty) return Enumerable.Empty<PersistResult<T>>();
|
||||
return results.value.Record
|
||||
.Where(r => re.IsMatch(r.Key))
|
||||
@@ -131,7 +142,7 @@ namespace mbts.persist
|
||||
protected override string user() { return "all_users"; }
|
||||
|
||||
[ImportingConstructor]
|
||||
public UISettings([Import("settings")]PersistenceService p) : base(p) { }
|
||||
public UISettings([Import("settings")]PersistenceService p, ErrorReporter err) : base(p,err) { }
|
||||
}
|
||||
|
||||
[Export(typeof(IUserPersist))]
|
||||
@@ -140,8 +151,8 @@ namespace mbts.persist
|
||||
string _user;
|
||||
|
||||
[ImportingConstructor]
|
||||
public UserUISettings([Import("userid")]uint user, [Import("settings")]PersistenceService persist)
|
||||
: base(persist) {
|
||||
public UserUISettings([Import("userid")]uint user, [Import("settings")]PersistenceService persist, ErrorReporter err)
|
||||
: base(persist,err) {
|
||||
_user = $"User {user}";
|
||||
}
|
||||
|
||||
|
||||
@@ -28,6 +28,20 @@ namespace mbts.utils {
|
||||
y != null &&
|
||||
non_null_equals(x, y);
|
||||
|
||||
public static int aggregate_hash(int item1_hash, int item2_hash, int item3_hash) {
|
||||
int rv = 63607;
|
||||
unchecked {
|
||||
_accumulate_hash(ref rv, ref item1_hash);
|
||||
_accumulate_hash(ref rv, ref item2_hash);
|
||||
_accumulate_hash(ref rv, ref item3_hash);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
// WARNING: be sure to call this within in an unchecked scope
|
||||
static void _accumulate_hash(ref int aggregate_hash, ref int item_hash) =>
|
||||
aggregate_hash = aggregate_hash * 6451 + item_hash;
|
||||
|
||||
private class AnonymousEqualityComparer<T> : IEqualityComparer<T> {
|
||||
readonly Func<T, T, bool> _equals;
|
||||
readonly Func<T, int> _get_hash_code;
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Subjects;
|
||||
using System.Threading;
|
||||
|
||||
namespace mbts.utils {
|
||||
interface IObservableDemuxer<TKey, in TInput, out TOutput> : IObserver<TInput> {
|
||||
@@ -89,21 +89,17 @@ namespace mbts.utils {
|
||||
_group_key = group_key;
|
||||
}
|
||||
|
||||
public void OnCompleted() { }
|
||||
public void OnError(Exception error) { }
|
||||
public IObservable<ITableUpdateTransform<T, TKey>> subscribe(TGroupKey group_key) =>
|
||||
Observable.Create<ITableUpdateTransform<T, TKey>>(o => {
|
||||
var data = _map.GetOrAdd(group_key, gk => new Data());
|
||||
lock (data) {
|
||||
var current = data.current;
|
||||
if (current.state.Count > 0) {
|
||||
var state_clone = current.state.ToDictionary();
|
||||
o.OnNext(new TableUpdateTransform<T, TKey>(state_clone, state_clone.Values.ToList(), new List<T>(0)));
|
||||
}
|
||||
using (data.read()) {
|
||||
o.OnNext(data.current.as_snapshot());
|
||||
return data.subject.Subscribe(o);
|
||||
}
|
||||
return data.subject.Subscribe(o);
|
||||
});
|
||||
|
||||
public void OnCompleted() { }
|
||||
public void OnError(Exception error) { }
|
||||
public void OnNext(TableUpdate<T, TKey> table) {
|
||||
var changes = new Dictionary<TGroupKey, ChangeSet>();
|
||||
foreach (T value in table.added)
|
||||
@@ -116,16 +112,29 @@ namespace mbts.utils {
|
||||
}
|
||||
|
||||
Data _update(Data data, ChangeSet changes) {
|
||||
lock (data) {
|
||||
data.current = data.current.update(changes.removed, changes.added, _key);
|
||||
data.subject.OnNext(data.current);
|
||||
}
|
||||
using (data.write())
|
||||
data.subject.OnNext(data.current = data.current.update(changes.removed, changes.added, _key));
|
||||
return data;
|
||||
}
|
||||
|
||||
class Data {
|
||||
int _n_read_ops, _n_write_ops;
|
||||
public readonly Subject<TableUpdateTransform<T, TKey>> subject = new Subject<TableUpdateTransform<T, TKey>>();
|
||||
public TableUpdateTransform<T, TKey> current = new TableUpdateTransform<T, TKey>();
|
||||
|
||||
public IDisposable read() {
|
||||
while (_n_write_ops > 0)
|
||||
Thread.Yield();
|
||||
Interlocked.Increment(ref _n_read_ops);
|
||||
return Disposable.Create(() => Interlocked.Decrement(ref _n_read_ops));
|
||||
}
|
||||
|
||||
public IDisposable write() {
|
||||
while (_n_read_ops > 0)
|
||||
Thread.Yield();
|
||||
Interlocked.Increment(ref _n_write_ops);
|
||||
return Disposable.Create(() => Interlocked.Decrement(ref _n_write_ops));
|
||||
}
|
||||
}
|
||||
|
||||
// [John]I prefer this class over anonymous tuples bc it is easy to mix up Item1/Item2
|
||||
|
||||
@@ -19,6 +19,9 @@ namespace mbts.utils {
|
||||
}
|
||||
|
||||
class IncrementalUpdate<T, TKey> : IIncrementalUpdate<T, TKey> where TKey : IEquatable<TKey> {
|
||||
public IncrementalUpdate()
|
||||
: this(new Dictionary<TKey, T>(0), new Dictionary<TKey, T>(0)) { }
|
||||
|
||||
public IncrementalUpdate(IReadOnlyDictionary<TKey, T> added, IReadOnlyDictionary<TKey, T> removed) {
|
||||
this.added = added;
|
||||
this.removed = removed;
|
||||
@@ -68,6 +71,9 @@ namespace mbts.utils {
|
||||
table._state.Add(kv.Key, kv.Value);
|
||||
return table;
|
||||
}
|
||||
|
||||
internal TableUpdateTransform<T, TKey> as_snapshot() =>
|
||||
new TableUpdateTransform<T, TKey>(_state, _state.Values.ToList(), new T[0]);
|
||||
}
|
||||
|
||||
[Browsable(false)]
|
||||
@@ -82,6 +88,15 @@ namespace mbts.utils {
|
||||
.Select(u => new FilteredTableUpdateTransform<T, TKey>(u, predicate))
|
||||
.Where(u => u.added.Count > 0 || u.removed.Count > 0);
|
||||
|
||||
/// <summary>
|
||||
/// Makes a 1:1 conversion to an IncrementalUpdate sequence of TResult (TKey remains the same).
|
||||
/// </summary>
|
||||
public static IObservable<IIncrementalUpdate<TResult, TKey>> convert<T, TKey, TResult>(this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, TResult> selector)
|
||||
where TKey : IEquatable<TKey> =>
|
||||
source.Select(update => new IncrementalUpdate<TResult, TKey>(
|
||||
update.added.ToDictionary(kv => kv.Key, kv => selector(kv.Value)),
|
||||
update.removed.ToDictionary(kv => kv.Key, kv => selector(kv.Value))));
|
||||
|
||||
/// <summary>
|
||||
/// Converts sequence to show incremental changes with respect to the [TResult] value returned by result_selector.
|
||||
/// </summary>
|
||||
|
||||
@@ -471,7 +471,7 @@ namespace mbts.ui.shell
|
||||
protected Property<T> backing<T, TData>(string name, IObservable<TData> source, Func<TData, T> select, T default_value = default) {
|
||||
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
|
||||
var prop = new ObservablePropertySink<T>(this, name, default_value);
|
||||
_observable_properties.Add(source.ObserveOnDispatcher().Subscribe(d => prop.set(select(d))));
|
||||
_observable_properties.Add(source.OnDispatcher().Subscribe(d => prop.set(select(d))));
|
||||
return prop;
|
||||
}
|
||||
|
||||
@@ -526,8 +526,8 @@ namespace mbts.ui.shell
|
||||
protected SelectorProperty<T> backing<T>(string name, IObservable<IEnumerable<T>> items, IObservable<InlineUserProperty<T>> desired_item, Func<T> select_default, Func<T, T, bool> items_match, IReadOnlyList<PropertyValidation<T>> validation_steps) {
|
||||
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
|
||||
var prop = new SelectorProperty<T>(this, name, select_default, items_match, validation_steps);
|
||||
_observable_properties.Add(items.ObserveOnDispatcher().Subscribe(prop));
|
||||
_observable_properties.Add(desired_item.ObserveOnDispatcher().Subscribe(prop));
|
||||
_observable_properties.Add(items.OnDispatcher().Subscribe(prop));
|
||||
_observable_properties.Add(desired_item.OnDispatcher().Subscribe(prop));
|
||||
return prop;
|
||||
}
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace mbts.ui.controls {
|
||||
Disposable.dispose(ref timer._trigger_sub);
|
||||
IObservable<object> newval = args.NewValue as IObservable<object>;
|
||||
if (newval != null) {
|
||||
timer._trigger_sub = newval.ObserveOnDispatcher().Subscribe(timer.start);
|
||||
timer._trigger_sub = newval.OnDispatcher().Subscribe(timer.start);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using mbts.spark.filters;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -60,13 +61,19 @@ namespace mbts.ui.controls {
|
||||
WPFHelper.create_dp<ListSortDirection?, ColumnBase>(
|
||||
nameof(SortDirection), (o, v) => o.OnSortDirectionChanged(v));
|
||||
|
||||
public ListSortDirection? SortDirection { get => GetValue(SortDirectionProperty) as ListSortDirection?; set => SetValue(SortDirectionProperty, value); }
|
||||
public ListSortDirection? SortDirection {
|
||||
get => GetValue(SortDirectionProperty) as ListSortDirection?;
|
||||
set => SetValue(SortDirectionProperty, value);
|
||||
}
|
||||
|
||||
public event Action<ColumnBase, ListSortDirection?> sort_direction_changed;
|
||||
|
||||
public string sort_member_path { get; set; }
|
||||
public double original_width { get; set; }
|
||||
public bool is_collapsed { get; set; }
|
||||
|
||||
protected virtual void OnSortDirectionChanged(ListSortDirection? new_value) { }
|
||||
protected virtual void OnSortDirectionChanged(ListSortDirection? new_value) =>
|
||||
sort_direction_changed?.Invoke(this, new_value);
|
||||
}
|
||||
|
||||
internal abstract class ColumnBase<TColKey> : ColumnBase, IKeyedColumn<TColKey> {
|
||||
|
||||
@@ -115,7 +115,7 @@ namespace mbts.spark.filters {
|
||||
IComparer<TValue> comparer = null
|
||||
) : this(get_row_val, get_state, load_state, prop_name, equality_comparer, comparer) {
|
||||
//
|
||||
_items_sub = items.ObserveOnDispatcher().Subscribe(new_items => {
|
||||
_items_sub = items.OnDispatcher().Subscribe(new_items => {
|
||||
_items_set.SetItems(new_items.Concat(_selected_items_set));
|
||||
_unsorted_items.sync_with(_items_set);
|
||||
});
|
||||
@@ -131,7 +131,7 @@ namespace mbts.spark.filters {
|
||||
IComparer<TValue> comparer = null
|
||||
) : this(get_row_val, get_state, load_state, prop_name, equality_comparer, comparer) {
|
||||
//
|
||||
_items_sub = items.ObserveOnDispatcher().Subscribe(update => {
|
||||
_items_sub = items.OnDispatcher().Subscribe(update => {
|
||||
foreach (var v in update.added)
|
||||
_items_set.Add(v);
|
||||
foreach (var v in update.removed.Except(_selected_items_set))
|
||||
@@ -182,7 +182,7 @@ namespace mbts.spark.filters {
|
||||
|
||||
protected override void _load_state_internal(TState state) {
|
||||
Disposable.dispose(ref _desired_items_sub);
|
||||
_desired_items_sub = _load_state(state).ObserveOnDispatcher().Subscribe(_on_desired_item);
|
||||
_desired_items_sub = _load_state(state).OnDispatcher().Subscribe(_on_desired_item);
|
||||
}
|
||||
|
||||
protected override void _apply() {
|
||||
|
||||
@@ -8,6 +8,7 @@ namespace mbts.ui.converters
|
||||
internal class EnumCharacterCasingConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
|
||||
if (value is null) return string.Empty;
|
||||
string s = value is Enum ? EnumHelper.get_display_name((Enum)value) : value.ToString();
|
||||
|
||||
CharacterCasing casing;
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace mbts.ui
|
||||
|
||||
private void _subscribe() {
|
||||
Disposable.dispose(ref _sub);
|
||||
_sub = _source.ObserveOnDispatcher().Subscribe(_on_data);
|
||||
_sub = _source.OnDispatcher().Subscribe(_on_data);
|
||||
}
|
||||
private void _on_data(T data) { Target.SetValue(_target_property, _last_value = data); }
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ namespace mbts.ui.shell
|
||||
|
||||
public async Task<IEnumerable<string>> get_workspaces(User user) {
|
||||
if (!_cache.TryGetValue(user.id, out List<string> result)) {
|
||||
using (var svc = new UserUISettings(user.id, _persist)) {
|
||||
using (var svc = new UserUISettings(user.id, _persist, null)) {
|
||||
var workspaces = await svc.query<WorkspaceDefinition>(WorkspacesModel.WORKSPACE_NAMESPACE, ".*");
|
||||
result = workspaces.Select(w => w.key).ToList();
|
||||
_cache[user.id] = result;
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace mbts.ui.shell {
|
||||
_window_settings = settings;
|
||||
_window_factory = factory;
|
||||
_undo = undo;
|
||||
_dispose = bus.subscribe(SparkTags.ShowWindow).ObserveOnDispatcher().Subscribe(w => add(w).ignore());
|
||||
_dispose = bus.subscribe(SparkTags.ShowWindow).OnDispatcher().Subscribe(w => add(w).ignore());
|
||||
}
|
||||
|
||||
#region Window creation
|
||||
|
||||
@@ -27,7 +27,7 @@ namespace mbts.spark
|
||||
{
|
||||
internal class ContractPickerModel : ViewModelBase {
|
||||
|
||||
private const string dot = "•";
|
||||
private const string dot = "•";
|
||||
|
||||
public ContractPickerModel(IBus bus, bool edit_mode, SecurityCriteria existing_criteria, SecurityCriteria world_view_filter = null) {
|
||||
_bus = bus;
|
||||
@@ -38,7 +38,7 @@ namespace mbts.spark
|
||||
_world_view_filter = world_view_filter;
|
||||
securities_view = CollectionViewSource.GetDefaultView(securities);
|
||||
(securities_view as ListCollectionView).CustomSort = new SecuritySorter();
|
||||
_subs.Add(_bus.subscribe(SecuritiesTag).ObserveOnDispatcher().Subscribe(on_securities_changed));
|
||||
_subs.Add(_bus.subscribe(SecuritiesTag).OnDispatcher().Subscribe(on_securities_changed));
|
||||
|
||||
add_criteria_command = new DelegateCommand(
|
||||
execute: _ => {
|
||||
|
||||
@@ -170,6 +170,13 @@ namespace mbts.spark {
|
||||
public static bool tick_equals(this ContractSpecificationImmutable contract, double x, double y) =>
|
||||
MathUtils.RoundFractionalUnits(x, contract.LowTick) == MathUtils.RoundFractionalUnits(y, contract.LowTick);
|
||||
|
||||
public static IEqualityComparer<double> tick_equality_comparer(this ContractSpecificationImmutable contract)
|
||||
{
|
||||
var eq = EqualityComparer.Create((double a, double b) => TickUtils.tick_equals(contract, a, b),
|
||||
x => MathUtils.RoundFractionalUnits(x, contract.LowTick));
|
||||
return eq;
|
||||
}
|
||||
|
||||
public static double round_to_tick(this ContractSpecificationImmutable spec, double value) {
|
||||
double intpart = (long)value;
|
||||
double floatpart = value % 1;
|
||||
|
||||
@@ -204,12 +204,12 @@ namespace mbts.spark {
|
||||
protected override async void OnMouseDown(MouseButtonEventArgs e) {
|
||||
Keyboard.ClearFocus();
|
||||
Focus();
|
||||
if (e.ChangedButton == MouseButton.Middle)
|
||||
var point = e.GetPosition(this);
|
||||
var column = _column_for_point(point);
|
||||
if (column.column_type == ColumnType.Price && e.ChangedButton == MouseButton.Middle)
|
||||
_scroll_to_center();
|
||||
else if (_settings != null) {
|
||||
var point = e.GetPosition(this);
|
||||
var price = price_for_point(point);
|
||||
var column = _column_for_point(point);
|
||||
Side? bs = null;
|
||||
if (column.column_type == ColumnType.Bid) bs = Side.Buy;
|
||||
if (column.column_type == ColumnType.Ask) bs = Side.Sell;
|
||||
@@ -340,7 +340,7 @@ namespace mbts.spark {
|
||||
_price_comparer = Comparer<double>.Create((x, y) => security.ContractSpec.tick_equals(x, y) ? 0 : y.CompareTo(x));
|
||||
_price_dirty = true;
|
||||
_vm.on_tick += _tick;
|
||||
_settings_sub = _vm.settings.ObserveOnDispatcher().Subscribe(_settings_updated);
|
||||
_settings_sub = _vm.settings.OnDispatcher().Subscribe(_settings_updated);
|
||||
_tick_sub = Disposable.Create(() => _vm.on_tick -= _tick);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,17 +89,19 @@ namespace mbts.spark {
|
||||
}
|
||||
var darker = BTSColor.darken2(color.R, color.G, color.B, 0.75);
|
||||
Color dark = new Color() { A = 255, R = darker.r, G = darker.g, B = darker.b };
|
||||
var completion = _parent_and_child_not_sharing_slot().Sum(order => OrderUtils.filled(order)) / _parent_and_child_not_sharing_slot().Sum(order => OrderUtils.quantity(order));
|
||||
background = mkbrush(color, _colorbg, completion);
|
||||
hoverbackground = mkbrush(dark, _darkbg, completion);
|
||||
//var completion = _parent_and_child_not_sharing_slot().Sum(order => OrderUtils.filled(order)) / _parent_and_child_not_sharing_slot().Sum(order => OrderUtils.quantity(order));
|
||||
background = _color_bg_brush; //mkbrush(color, _colorbg, 0);
|
||||
hoverbackground = mkbrush(dark, _darkbg, 0);
|
||||
border = new SolidColorBrush(color);
|
||||
filled = _parent_and_child_not_sharing_slot().Select(order => OrderUtils.filled(order)).Sum();
|
||||
working_quantity = _parent_and_child_not_sharing_slot().Select(order => order.working_quantity()).Sum();
|
||||
quantity = _parent_and_child_not_sharing_slot().Select(order => order.State.Quantity).Sum();
|
||||
|
||||
summary = _orders.Values.Select(order => order.short_description(_parent.security.ContractSpec)).Aggregate((s1, s2) => $"{s1}{Environment.NewLine}{Environment.NewLine}{s2}");
|
||||
description = _orders.Values.Select(order => order.full_description(_parent.security)).Aggregate((s1, s2) => $"{s1}{Environment.NewLine}{Environment.NewLine}{s2}");
|
||||
|
||||
if (_orders.Count > 0)
|
||||
{
|
||||
summary = _orders.Values.Select(order => order.short_description(_parent.security.ContractSpec)).Aggregate((s1, s2) => $"{s1}{Environment.NewLine}{Environment.NewLine}{s2}");
|
||||
description = _orders.Values.Select(order => order.full_description(_parent.security)).Aggregate((s1, s2) => $"{s1}{Environment.NewLine}{Environment.NewLine}{s2}");
|
||||
}
|
||||
_set_badge_text();
|
||||
}
|
||||
|
||||
@@ -132,7 +134,7 @@ namespace mbts.spark {
|
||||
if (distinct_sides.Count() > 1) {
|
||||
return Side.Undefined;
|
||||
}
|
||||
return distinct_sides.First();
|
||||
return distinct_sides.FirstOrDefault();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
using bts.spark;
|
||||
using mbts.co2;
|
||||
using mbts.persist;
|
||||
@@ -15,6 +16,8 @@ using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Media;
|
||||
using mbts.utils;
|
||||
using mbts.spark.utilities;
|
||||
|
||||
namespace mbts.spark {
|
||||
class LadderVM : DisposableViewModelBase, IKeyListener {
|
||||
@@ -26,11 +29,14 @@ namespace mbts.spark {
|
||||
readonly Property<Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
|
||||
IWindowSettingsManager<LadderSettings> _settings_manager;
|
||||
ulong _misc_time = 0;
|
||||
Dictionary<double, LadderOrderVM> _slot_price_vs_orders = new Dictionary<double, LadderOrderVM>();
|
||||
IEqualityComparer<double> _equality;
|
||||
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
|
||||
|
||||
public LadderVM(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager) {
|
||||
_bus = bus;
|
||||
_model = model;
|
||||
_equality = TickUtils.tick_equality_comparer(_model.security.ContractSpec);
|
||||
_slot_price_vs_orders = new Dictionary<double, LadderOrderVM>(_equality);
|
||||
_settings_manager = settings_manager;
|
||||
_high = backing(nameof(high), double.NaN);
|
||||
_low = backing(nameof(low), double.NaN);
|
||||
@@ -47,7 +53,7 @@ namespace mbts.spark {
|
||||
_ask_background = brush_prop(nameof(ask_background), s => s.ask_column.background.rgb_brush());
|
||||
_ask_foreground = brush_prop(nameof(ask_foreground), s => s.ask_column.foreground.rgb_brush());
|
||||
Property<Brush> brush_prop(string name, Func<LadderSettings, Brush> get_brush) =>
|
||||
backing(name, settings_manager.subscribe_current().ObserveOnDispatcher(), get_brush);
|
||||
backing(name, settings_manager.subscribe_current().OnDispatcher(), get_brush);
|
||||
//
|
||||
send_order_command = new DelegateCommand<(double price, Side? side, bool size_from_alternate)>(send_order);
|
||||
cancel_command = new AsyncCommand<LadderModel.CancelType>(_model.cancel_orders, true);
|
||||
@@ -69,11 +75,11 @@ namespace mbts.spark {
|
||||
settings_command,
|
||||
order_properties_vm = new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(security), _settings_manager.subscribe_current().Select(s => new InlineOrderProperties(model.order_gateway, s))),
|
||||
size_calculator = new CalculatorLogic(0, _settings_manager.subscribe_current().Select(s => s.calculator_buttons)),
|
||||
bus.subscribe(SparkTags.LogActivity).ObserveOnDispatcher().Subscribe(status_update),
|
||||
model.order_gateway.working_orders.ObserveOnDispatcher().Subscribe(working => {
|
||||
bus.subscribe(SparkTags.LogActivity).OnDispatcher().Subscribe(status_update),
|
||||
model.order_gateway.working_orders.OnDispatcher().Subscribe(working => {
|
||||
// Here we manage N orders to 1 price slot in the ladder
|
||||
|
||||
Dictionary<double, HashSet<uint>> orders_used_in_this_update = new Dictionary<double, HashSet<uint>>();
|
||||
Dictionary<double, HashSet<uint>> orders_used_in_this_update = new Dictionary<double, HashSet<uint>>(_equality);
|
||||
var ladders_orders = working.Values.Where(o => o.Def.SecurityKey == security.Id);
|
||||
foreach (var wo in ladders_orders) {
|
||||
var slot_price_target = wo.price();
|
||||
@@ -152,7 +158,7 @@ namespace mbts.spark {
|
||||
public ICommand status_click_command => SparkCommands.ShowActivityLog.ICommand(_bus);
|
||||
|
||||
public async void status_update(Activity a) {
|
||||
if (a.security == _model.security) {
|
||||
if (a.security == _model.security && a.category != ActivityLogCategory.modify) {
|
||||
_status.set(a.message);
|
||||
_status_brush.set(a.category.fill);
|
||||
await Task.Delay(TimeSpan.FromSeconds(5));
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
using bts.api;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.ComponentModel.Composition;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
namespace mbts.spark {
|
||||
[Export]
|
||||
class PAndLModel : IDisposable {
|
||||
readonly IBus _bus;
|
||||
@@ -24,19 +18,8 @@ namespace mbts.spark
|
||||
//_account_service = account_service;
|
||||
}
|
||||
|
||||
public IObservable<List<PnLData>> on_pnl_data =>
|
||||
// combine w/ Securities to get SecurityMD
|
||||
_order_model.on_fill_tuples.table(ft => ft.key).CombineLatest(_bus.subscribe(SparkTags.SecuritiesTag), (fills, securities) => {
|
||||
List<PnLData> pnl_data = new List<PnLData>(fills.state.Count);
|
||||
foreach (var ft in fills.state.Values) {
|
||||
if (ft.security.HasValue && securities.TryGetValue(ft.security.Value.Id, out SecurityMD sec_md)) {
|
||||
pnl_data.Add(new PnLData(ft.fill, sec_md, ft.order));
|
||||
} else {
|
||||
pnl_data.Add(new PnLData(ft.fill, null, ft.order));
|
||||
}
|
||||
}
|
||||
return pnl_data;
|
||||
});
|
||||
public IObservable<ITableUpdateTransform<PnLData, FillTupleKey>> on_pnl_data =>
|
||||
_order_model.on_fill_tuples.convert(ft => new PnLData(ft)).table(pd => pd.key);
|
||||
|
||||
//public async Task<Account[]> get_accounts() {
|
||||
// var request = await _account_service.get_accounts_async(new GetAccountsRequest());
|
||||
@@ -46,12 +29,8 @@ namespace mbts.spark
|
||||
// }
|
||||
// return new Account[] { };
|
||||
//}
|
||||
|
||||
|
||||
public void Dispose() =>
|
||||
Disposable.dispose(ref _dispose);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,10 @@ namespace mbts.spark {
|
||||
_model = model;
|
||||
var pnl = model.on_pnl_data
|
||||
.Sample(_POSITION_UPDATE_PERIOD)
|
||||
.ObserveOnDispatcher()
|
||||
.OnDispatcher()
|
||||
.Subscribe(p => {
|
||||
if (p != null) {
|
||||
PnLCalculator calc = new PnLCalculator(p);
|
||||
PnLCalculator calc = new PnLCalculator(p.state.Values);
|
||||
var summary_position_rows = calc.get_summary_position();
|
||||
_pnl_data.sync_with(summary_position_rows);
|
||||
_last_pnl_update_time = DateTime.Now;
|
||||
|
||||
@@ -76,9 +76,9 @@ namespace mbts.spark {
|
||||
|
||||
|
||||
class PnLCalculator {
|
||||
readonly List<PnLData> _data;
|
||||
readonly IEnumerable<PnLData> _data;
|
||||
|
||||
public PnLCalculator(List<PnLData> data) {
|
||||
public PnLCalculator(IEnumerable<PnLData> data) {
|
||||
_data = data;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
using bts.api;
|
||||
using bts.api.orderman;
|
||||
using mbts.co2;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace mbts.spark {
|
||||
|
||||
@@ -23,16 +18,16 @@ namespace mbts.spark {
|
||||
|
||||
class PnLData {
|
||||
|
||||
public readonly FillImmutable fill;
|
||||
public readonly SecurityMD? sec_md;
|
||||
public readonly OrderRecordImmutable? order;
|
||||
readonly FillTuple _fill_tuple;
|
||||
|
||||
public PnLData(FillImmutable fill, SecurityMD? sec_md, OrderRecordImmutable? order) {
|
||||
this.fill = fill;
|
||||
this.sec_md = sec_md;
|
||||
this.order = order;
|
||||
}
|
||||
public PnLData(FillTuple fill_tuple) =>
|
||||
_fill_tuple = fill_tuple;
|
||||
|
||||
public FillTupleKey key => _fill_tuple.key;
|
||||
public FillImmutable fill => _fill_tuple.fill;
|
||||
public SecurityMD? sec_md => _fill_tuple.security_md;
|
||||
public OrderRecordImmutable? order => _fill_tuple.order;
|
||||
//
|
||||
public string account => order?.Def.Account;
|
||||
public Venue? exchange => sec_md?.security.Exchange;
|
||||
public string symbol => sec_md?.security.Symbol;
|
||||
@@ -43,7 +38,5 @@ namespace mbts.spark {
|
||||
public override string ToString() {
|
||||
return $"Fill: ( {account} {exchange} {security.ToString()} buy: {buy_quantity} sell: {sell_quantity} ) Security: {security.ToString()}";
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,92 +5,153 @@ using System.ComponentModel;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class CVOrderBookRowVM {
|
||||
public interface IOrderBookCell : IComparable, INotifyPropertyChanged {
|
||||
public interface IOrderBookCell : IComparable {
|
||||
string text { get; }
|
||||
}
|
||||
|
||||
public interface IOrderBookCell<T> : IOrderBookCell {
|
||||
T value { get; }
|
||||
IOrderBookCellStore<T> store { get; }
|
||||
}
|
||||
|
||||
public interface IOrderBookCellStore {
|
||||
CVOrderBookRowVM row { get; }
|
||||
ICVOrderBookColumn column { get; }
|
||||
string prop_name { get; }
|
||||
IOrderBookCell current { get; }
|
||||
//
|
||||
IOrderBookCellStore select_other(CVOrderBookRowVM row);
|
||||
bool refresh();
|
||||
void regenerate_cell();
|
||||
}
|
||||
|
||||
public interface IOrderBookCellStore<T> : IOrderBookCellStore, IComparer<T>, IEqualityComparer<T> {
|
||||
new IOrderBookCell<T> current { get; }
|
||||
new IOrderBookCellStore<T> select_other(CVOrderBookRowVM row);
|
||||
//
|
||||
T get_value(CVOrderBookRowVM row);
|
||||
string format(T value);
|
||||
}
|
||||
|
||||
public static class CellFactory {
|
||||
public static OrderBookCell<T> create<T>(CVOrderBookRowVM row, ICVOrderBookColumn column, Func<CVOrderBookRowVM, T> get_value, Func<CVOrderBookRowVM, OrderBookCell<T>> get_cell, Func<T, string> format = null) =>
|
||||
new OrderBookCell<T>(row, column, get_value, get_cell, format, new Comparison<T>(Comparer<T>.Default.Compare), EqualityComparer<T>.Default);
|
||||
public static IOrderBookCellStore<T> create<T>(CVOrderBookRowVM row, ICVOrderBookColumn column, string prop_name, Func<CVOrderBookRowVM, T> get_value, Func<CVOrderBookRowVM, IOrderBookCellStore<T>> select_other, Func<T, string> format = null) =>
|
||||
new OrderBookCellStore<T>(row, column, prop_name, get_value, select_other, format, new Comparison<T>(Comparer<T>.Default.Compare), EqualityComparer<T>.Default);
|
||||
|
||||
public static OrderBookCell<T> create<T>(CVOrderBookRowVM row, ICVOrderBookColumn column, Func<CVOrderBookRowVM, T> get_value, Func<CVOrderBookRowVM, OrderBookCell<T>> get_cell, Comparison<T> comparison, Func<T, string> format = null) =>
|
||||
new OrderBookCell<T>(row, column, get_value, get_cell, format, comparison, EqualityComparer<T>.Default);
|
||||
public static IOrderBookCellStore<T> create<T>(CVOrderBookRowVM row, ICVOrderBookColumn column, string prop_name, Func<CVOrderBookRowVM, T> get_value, Func<CVOrderBookRowVM, IOrderBookCellStore<T>> select_other, Comparison<T> comparison, Func<T, string> format = null) =>
|
||||
new OrderBookCellStore<T>(row, column, prop_name, get_value, select_other, format, comparison, EqualityComparer<T>.Default);
|
||||
|
||||
public static OrderBookCell<T?> create_nullable<T>(CVOrderBookRowVM row, ICVOrderBookColumn column, Func<CVOrderBookRowVM, T?> get_value, Func<CVOrderBookRowVM, OrderBookCell<T?>> get_cell, Func<T?, string> format = null)
|
||||
where T : struct => new OrderBookCell<T?>(row, column, get_value, get_cell, format, CompareUtils.nullable_comparison<T>(Comparer<T>.Default.Compare), EqualityComparer.CreateNullable<T>(EqualityComparer<T>.Default.Equals));
|
||||
public static IOrderBookCellStore<T?> create_nullable<T>(CVOrderBookRowVM row, ICVOrderBookColumn column, string prop_name, Func<CVOrderBookRowVM, T?> get_value, Func<CVOrderBookRowVM, IOrderBookCellStore<T?>> select_other, Func<T?, string> format = null)
|
||||
where T : struct => new OrderBookCellStore<T?>(row, column, prop_name, get_value, select_other, format, CompareUtils.nullable_comparison<T>(Comparer<T>.Default.Compare), EqualityComparer.CreateNullable<T>(EqualityComparer<T>.Default.Equals));
|
||||
|
||||
public static OrderBookCell<T?> create_nullable<T>(CVOrderBookRowVM row, ICVOrderBookColumn column, Func<CVOrderBookRowVM, T?> get_value, Func<CVOrderBookRowVM, OrderBookCell<T?>> get_cell, Func<T, T, int> compare, Func<T?, string> format = null)
|
||||
where T : struct => new OrderBookCell<T?>(row, column, get_value, get_cell, format, CompareUtils.nullable_comparison(compare), EqualityComparer.CreateNullable<T>(EqualityComparer<T>.Default.Equals));
|
||||
}
|
||||
public static IOrderBookCellStore<T?> create_nullable<T>(CVOrderBookRowVM row, ICVOrderBookColumn column, string prop_name, Func<CVOrderBookRowVM, T?> get_value, Func<CVOrderBookRowVM, IOrderBookCellStore<T?>> select_other, Func<T, T, int> compare, Func<T?, string> format = null)
|
||||
where T : struct =>
|
||||
new OrderBookCellStore<T?>(row, column, prop_name, get_value, select_other, format, CompareUtils.nullable_comparison(compare), EqualityComparer.CreateNullable<T>(EqualityComparer<T>.Default.Equals));
|
||||
|
||||
public class OrderBookCell<T> : IComparable<OrderBookCell<T>>, IOrderBookCell {
|
||||
readonly CVOrderBookRowVM _row;
|
||||
readonly ICVOrderBookColumn _column;
|
||||
readonly Func<CVOrderBookRowVM, T> _get_value;
|
||||
readonly Func<CVOrderBookRowVM, OrderBookCell<T>> _get_cell;
|
||||
readonly Func<T, string> _format;
|
||||
readonly Comparison<T> _comparison;
|
||||
readonly IEqualityComparer<T> _eq_comparer;
|
||||
T _value;
|
||||
string _text;
|
||||
class OrderBookCellStore<T> : IOrderBookCellStore<T> {
|
||||
readonly CVOrderBookRowVM _row;
|
||||
readonly ICVOrderBookColumn _column;
|
||||
readonly string _prop_name;
|
||||
readonly Func<CVOrderBookRowVM, T> _get_value;
|
||||
readonly Func<CVOrderBookRowVM, IOrderBookCellStore<T>> _select_other;
|
||||
readonly Func<T, string> _format;
|
||||
readonly Comparison<T> _comparison;
|
||||
readonly IEqualityComparer<T> _eq_comparer;
|
||||
OrderBookCell<T> _current;
|
||||
|
||||
public OrderBookCell(CVOrderBookRowVM row, ICVOrderBookColumn column, Func<CVOrderBookRowVM, T> get_value, Func<CVOrderBookRowVM, OrderBookCell<T>> get_cell, Func<T, string> format, Comparison<T> comparison, IEqualityComparer<T> eq_comparer) {
|
||||
_row = row;
|
||||
_column = column;
|
||||
_get_value = get_value;
|
||||
_get_cell = get_cell;
|
||||
_format = format ?? new Func<T, string>(v => v?.ToString() ?? string.Empty);
|
||||
_comparison = comparison;
|
||||
_eq_comparer = eq_comparer;
|
||||
_value = _get_value(_row);
|
||||
}
|
||||
public OrderBookCellStore(
|
||||
CVOrderBookRowVM row,
|
||||
ICVOrderBookColumn column,
|
||||
string prop_name,
|
||||
Func<CVOrderBookRowVM, T> get_value,
|
||||
Func<CVOrderBookRowVM, IOrderBookCellStore<T>> select_other,
|
||||
Func<T, string> format,
|
||||
Comparison<T> comparison,
|
||||
IEqualityComparer<T> eq_comparer) {
|
||||
//
|
||||
_row = row;
|
||||
_column = column;
|
||||
_prop_name = prop_name;
|
||||
_get_value = get_value;
|
||||
_select_other = select_other;
|
||||
_format = format ?? new Func<T, string>(v => v?.ToString() ?? string.Empty);
|
||||
_comparison = comparison;
|
||||
_eq_comparer = eq_comparer;
|
||||
_current = new OrderBookCell<T>(this, _get_value(_row));
|
||||
}
|
||||
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
public CVOrderBookRowVM row => _row;
|
||||
public ICVOrderBookColumn column => _column;
|
||||
public string prop_name => _prop_name;
|
||||
public IOrderBookCell<T> current => _current;
|
||||
IOrderBookCell IOrderBookCellStore.current => _current;
|
||||
|
||||
public T value => _value;
|
||||
public string text => _text ?? (_text = _format(_value));
|
||||
public int Compare(T x, T y) => _comparison(x, y);
|
||||
public bool Equals(T x, T y) => _eq_comparer.Equals(x, y);
|
||||
public string format(T value) => _format(value);
|
||||
public int GetHashCode(T obj) => _eq_comparer.GetHashCode(obj);
|
||||
public T get_value(CVOrderBookRowVM row) => _get_value(row);
|
||||
public IOrderBookCellStore<T> select_other(CVOrderBookRowVM row) => _select_other(row);
|
||||
IOrderBookCellStore IOrderBookCellStore.select_other(CVOrderBookRowVM row) => select_other(row);
|
||||
|
||||
public bool refresh() {
|
||||
var v = _get_value(_row);
|
||||
if (!_eq_comparer.Equals(v, _value)) {
|
||||
_value = v;
|
||||
_text = null;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(value)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(text)));
|
||||
return true;
|
||||
public bool refresh() {
|
||||
T v = _get_value(_row);
|
||||
if (!_eq_comparer.Equals(v, _current.value)) {
|
||||
_current = new OrderBookCell<T>(this, v);
|
||||
_row.RaisePropertyChanged(_prop_name);
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
public void regenerate_cell() {
|
||||
_current = new OrderBookCell<T>(this, _current.value);
|
||||
_row.RaisePropertyChanged(_prop_name);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public int CompareTo(OrderBookCell<T> other) {
|
||||
if (other == null)
|
||||
return 1;
|
||||
var lin = lineage(_row);
|
||||
var olin = lineage(other._row);
|
||||
int c = 0;
|
||||
using (var en = lin.GetEnumerator())
|
||||
using (var oen = olin.GetEnumerator()) {
|
||||
while (en.MoveNext() && oen.MoveNext()) {
|
||||
if (en.Current.order_key.value != oen.Current.order_key.value) {
|
||||
c = _comparison(_get_cell(en.Current).value, _get_cell(oen.Current).value);
|
||||
if (c != 0)
|
||||
return c;
|
||||
else {
|
||||
c = en.Current.order_key.value.CompareTo(oen.Current.order_key.value);
|
||||
return _column.SortDirection.HasValue && _column.SortDirection.Value == ListSortDirection.Ascending ? c : -c;
|
||||
class OrderBookCell<T> : IOrderBookCell<T> {
|
||||
string _text;
|
||||
|
||||
public OrderBookCell(OrderBookCellStore<T> store, T value) {
|
||||
this.store = store;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public string text => _text ?? (_text = store.format(value));
|
||||
public T value { get; }
|
||||
public IOrderBookCellStore<T> store { get; }
|
||||
|
||||
public int CompareTo(OrderBookCell<T> other) {
|
||||
if (other == null)
|
||||
return 1;
|
||||
var lin = lineage(store.row);
|
||||
var olin = lineage(other.store.row);
|
||||
int c = 0;
|
||||
using (var en = lin.GetEnumerator())
|
||||
using (var oen = olin.GetEnumerator()) {
|
||||
while (en.MoveNext() && oen.MoveNext()) {
|
||||
if (en.Current.order_key.value != oen.Current.order_key.value) {
|
||||
c = store.Compare(
|
||||
store.select_other(en.Current).current.value,
|
||||
store.select_other(oen.Current).current.value);
|
||||
if (c != 0)
|
||||
return c;
|
||||
else {
|
||||
c = en.Current.order_key.value.CompareTo(
|
||||
oen.Current.order_key.value);
|
||||
return store.column.SortDirection.HasValue && store.column.SortDirection.Value == ListSortDirection.Ascending ? c : -c;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return store.column.SortDirection.HasValue && store.column.SortDirection.Value == ListSortDirection.Ascending ?
|
||||
lin.Count.CompareTo(olin.Count) :
|
||||
olin.Count.CompareTo(lin.Count);
|
||||
}
|
||||
return _column.SortDirection.HasValue && _column.SortDirection.Value == ListSortDirection.Ascending ?
|
||||
lin.Count.CompareTo(olin.Count) : olin.Count.CompareTo(lin.Count);
|
||||
|
||||
public int CompareTo(object obj) => CompareTo(obj as OrderBookCell<T>);
|
||||
|
||||
public override string ToString() => text;
|
||||
}
|
||||
|
||||
public int CompareTo(object obj) =>
|
||||
CompareTo(obj as OrderBookCell<T>);
|
||||
|
||||
public override string ToString() => text;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,8 @@ namespace mbts.spark {
|
||||
// TODO: Maybe binding to the SortDirection would be less smelly?
|
||||
// - initially, I tried to bind to the SortDirection of each column, unfortunately, the binding target was not updated before the ICollectionView sorting took place.
|
||||
// I decided it was not worth changing the DIYGrid.Sorting function to force the SortDirection binding targets to update immediately, before the ICollectionView sorting.
|
||||
interface ICVOrderBookColumn {
|
||||
interface ICVOrderBookColumn : IKeyedColumn<Columns> {
|
||||
string sort_member_path { get; }
|
||||
ListSortDirection? SortDirection { get; }
|
||||
IDataGridColumnFilter<CVOrderBookRowVM> filter { get; }
|
||||
}
|
||||
@@ -200,7 +201,7 @@ namespace mbts.spark {
|
||||
DataGridFilterKnownTemplates.Regex));
|
||||
|
||||
string value_path(string cell_name) =>
|
||||
string.Join(".", cell_name, nameof(CVOrderBookRowVM.OrderBookCell<uint>.value)); //uint is an arbitrary 'T', just want to get compile check on 'value' path.
|
||||
string.Join(".", cell_name, nameof(CVOrderBookRowVM.IOrderBookCell<uint>.value)); //uint is an arbitrary 'T', just want to get compile check on 'value' path.
|
||||
|
||||
if (startup_data?.filters is FilterStates fs) {
|
||||
security_column.filter.load_state(fs.security_criteria);
|
||||
@@ -317,7 +318,10 @@ namespace mbts.spark {
|
||||
string IFilterableColumn.filter_template => _filter_template;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection => _cached_sort_direction;
|
||||
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) => _cached_sort_direction = new_value;
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
|
||||
_cached_sort_direction = new_value;
|
||||
base.OnSortDirectionChanged(new_value);
|
||||
}
|
||||
}
|
||||
|
||||
public class SimpleTextColumn<TFilterState> : OrderbookColumnBase<TFilterState>, IRenderColumn<CVOrderBookRowVM> {
|
||||
@@ -414,7 +418,7 @@ namespace mbts.spark {
|
||||
public class OrderBookOrderTypeColumn : OrderTypeColumn<Columns>, ICVOrderBookColumn {
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
public OrderBookOrderTypeColumn(IDataGridColumnPersistentFilter<CVOrderBookRowVM, OrderType[]> filter)
|
||||
: base(Columns.OrderType, new Binding($"{nameof(CVOrderBookRowVM.order_type)}.{nameof(CVOrderBookRowVM.OrderBookCell<OrderType>.value)}"), new Binding(nameof(CVOrderBookRowVM.order_metadata)), filter) {
|
||||
: base(Columns.OrderType, new Binding($"{nameof(CVOrderBookRowVM.order_type)}.{nameof(CVOrderBookRowVM.IOrderBookCell<OrderType>.value)}"), new Binding(nameof(CVOrderBookRowVM.order_metadata)), filter) {
|
||||
this.filter = filter;
|
||||
sort_member_path = nameof(CVOrderBookRowVM.order_type);
|
||||
font_weight_binding = RenderUtils.get_font_weight_binding();
|
||||
@@ -426,14 +430,17 @@ namespace mbts.spark {
|
||||
IDataGridColumnFilter<CVOrderBookRowVM> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection => _cached_sort_direction;
|
||||
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) => _cached_sort_direction = new_value;
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
|
||||
_cached_sort_direction = new_value;
|
||||
base.OnSortDirectionChanged(new_value);
|
||||
}
|
||||
}
|
||||
|
||||
public class OrderBookLastMessageColumn : TextColumn<Columns>, ICVOrderBookColumn {
|
||||
ListSortDirection? _cached_sort_direction;
|
||||
|
||||
public OrderBookLastMessageColumn(IDataGridColumnPersistentFilter<CVOrderBookRowVM, string> filter)
|
||||
: base(Columns.LastMessage, new Binding(nameof(CVOrderBookRowVM.last_msg)), filter, DataGridFilterKnownTemplates.Regex) {
|
||||
: base(Columns.LastMessage, new Binding($"{nameof(CVOrderBookRowVM.last_msg)}.{nameof(CVOrderBookRowVM.IOrderBookCell<string>.value)}"), filter, DataGridFilterKnownTemplates.Regex) {
|
||||
this.filter = filter;
|
||||
Header = "Last Message";
|
||||
Width = 80;
|
||||
@@ -446,7 +453,10 @@ namespace mbts.spark {
|
||||
IDataGridColumnFilter<CVOrderBookRowVM> ICVOrderBookColumn.filter => filter;
|
||||
ListSortDirection? ICVOrderBookColumn.SortDirection => _cached_sort_direction;
|
||||
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) => _cached_sort_direction = new_value;
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
|
||||
_cached_sort_direction = new_value;
|
||||
base.OnSortDirectionChanged(new_value);
|
||||
}
|
||||
}
|
||||
|
||||
public class SecurityColumn : FrameworkElementColumnBase<Columns>, ICVOrderBookColumn, IFilterableColumn {
|
||||
@@ -480,7 +490,7 @@ namespace mbts.spark {
|
||||
toggle_button.SetResourceReference(ToggleButton.StyleProperty, ExpandCollapseToggleStyleKey);
|
||||
//
|
||||
var security_view = new TextBlock();
|
||||
security_view.SetBinding(TextBlock.TextProperty, nameof(CVOrderBookRowVM.security));
|
||||
security_view.SetBinding(TextBlock.TextProperty, $"{nameof(CVOrderBookRowVM.security)}.{nameof(CVOrderBookRowVM.IOrderBookCell<SecurityImmutable?>.text)}");
|
||||
security_view.SetBinding(TextBlock.FontWeightProperty, _font_weight_binding);
|
||||
//
|
||||
var grid = new Grid();
|
||||
@@ -492,7 +502,10 @@ namespace mbts.spark {
|
||||
return grid;
|
||||
}
|
||||
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) => _cached_sort_direction = new_value;
|
||||
protected override void OnSortDirectionChanged(ListSortDirection? new_value) {
|
||||
_cached_sort_direction = new_value;
|
||||
base.OnSortDirectionChanged(new_value);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
@@ -5,23 +5,47 @@ using mbts.ui;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reactive.Linq;
|
||||
using System.Windows.Input;
|
||||
using static mbts.spark.OrderBookView;
|
||||
using OrderStatus = bts.api.orderman.OrderStatus;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class CVOrderBookRowVM : ViewModelBase, IEquatable<CVOrderBookRowVM>, ui.controls.DIYGrid.IRowVM, ui.controls.DIYGrid.IActionableRowVM {
|
||||
partial class CVOrderBookRowVM : DisposableViewModelBase, IEquatable<CVOrderBookRowVM>, ui.controls.DIYGrid.IRowVM, ui.controls.DIYGrid.IActionableRowVM {
|
||||
readonly CVOrderBookVM _book_vm;
|
||||
readonly OrdersModel _model;
|
||||
readonly Property<bool> _visible, _expanded;
|
||||
readonly DelegateProperty<bool> _passes_filter;
|
||||
readonly HashSet<uint> _child_orders_pass_filter = new HashSet<uint>();
|
||||
readonly Property<bool> _visible, _expanded, _passes_filter;
|
||||
readonly DelegateProperty<int> _depth_level;
|
||||
readonly HashSet<CVOrderBookRowVM> _child_rows = new HashSet<CVOrderBookRowVM>();
|
||||
readonly ObservantCommand _show_order_modify_cmd;
|
||||
OrderRecordImmutable _order;
|
||||
SecurityImmutable? _security;
|
||||
SecurityImmutable? _security;
|
||||
CVOrderBookRowVM _parent;
|
||||
bool _row_passes_filter;
|
||||
CVOrderBookVM.BranchFilterSource _branch_filter_source;
|
||||
IDisposable _branch_filter_sub;
|
||||
// Core
|
||||
IOrderBookCellStore<uint> _order_key_store;
|
||||
IOrderBookCellStore<uint> _parent_order_key_store;
|
||||
IOrderBookCellStore<uint> _security_key_store;
|
||||
IOrderBookCellStore<SecurityImmutable?> _security_store;
|
||||
IOrderBookCellStore<Venue> _exchange_store;
|
||||
// Def
|
||||
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store;
|
||||
IOrderBookCellStore<Side> _side_store;
|
||||
IOrderBookCellStore<string> _user_store;
|
||||
IOrderBookCellStore<string> _account_store;
|
||||
IOrderBookCellStore<TimeInForce> _time_in_force_store;
|
||||
IOrderBookCellStore<DateTime> _time_created_store;
|
||||
// State
|
||||
IOrderBookCellStore<DateTime> _last_change_store { get; set; }
|
||||
IOrderBookCellStore<OrderStatus> _status_store;
|
||||
IOrderBookCellStore<double> _price_store;
|
||||
IOrderBookCellStore<uint> _quantity_store;
|
||||
IOrderBookCellStore<uint> _filled_quantity_store;
|
||||
IOrderBookCellStore<double> _last_price_store;
|
||||
IOrderBookCellStore<double> _avg_price_store;
|
||||
IOrderBookCellStore<string> _last_msg_store;
|
||||
IOrderBookCellStore<OrderSubType> _order_sub_type_store;
|
||||
|
||||
public CVOrderBookRowVM(IBus bus, CVOrderBookVM source_vm, OrdersModel model, OrderRecordImmutable record, SecurityImmutable? s) {
|
||||
_book_vm = source_vm;
|
||||
@@ -29,26 +53,26 @@ namespace mbts.spark {
|
||||
_order = record;
|
||||
_security = s;
|
||||
// cells
|
||||
security = CellFactory.create_nullable(this, _book_vm.columns.security_column, r => r._security, r => r.security, v => v.HasValue ? v.Value.ToString() : "unknown security");
|
||||
security_key = CellFactory.create(this, _book_vm.columns.security_key_column, r => r._order.security_key(), r => r.security_key);
|
||||
parent_order_key = CellFactory.create(this, _book_vm.columns.parent_key_column, r => r._order.ParentKey, r => r.parent_order_key);
|
||||
order_key = CellFactory.create(this, _book_vm.columns.order_key_column, r => r._order.Key, r => r.order_key);
|
||||
time_created = CellFactory.create(this, _book_vm.columns.time_created_column, r => r._order.created(), r => r.time_created, format_date);
|
||||
user = CellFactory.create(this, _book_vm.columns.user_column, r => _order.user(), r => r.user);
|
||||
account = CellFactory.create(this, _book_vm.columns.account_column, r => r._order.account(), r => r.account);
|
||||
exchange = CellFactory.create(this, _book_vm.columns.exchange_column, r => r._security.HasValue ? _security.Value.Exchange : Venue.Unset, r => r.exchange);
|
||||
time_in_force = CellFactory.create(this, _book_vm.columns.time_in_force_column, r => r._order.time_in_force(), r => r.time_in_force);
|
||||
order_type = CellFactory.create(this, _book_vm.columns.order_type_column, r => r._order.order_type(), r => r.order_type);
|
||||
order_sub_type = CellFactory.create(this, _book_vm.columns.sub_type_column, r => r._order.sub_type(), r => r.order_sub_type, format_sub_type);
|
||||
side = CellFactory.create(this, _book_vm.columns.side_column, r => r._order.side(), r => r.side);
|
||||
last_change = CellFactory.create(this, _book_vm.columns.last_change_column, r => _order.last_change(), r => r.last_change, format_date);
|
||||
price = CellFactory.create(this, _book_vm.columns.price_column, r => _order.price(), r => r.price, format_price);
|
||||
quantity = CellFactory.create(this, _book_vm.columns.qty_column, r => _order.quantity(), r => r.quantity);
|
||||
filled_quantity = CellFactory.create(this, _book_vm.columns.filled_qty_column, r => _order.filled(), r => r.filled_quantity);
|
||||
avg_price = CellFactory.create(this, _book_vm.columns.avg_price_column, r => _order.avg_price(), r => r.avg_price, format_price);
|
||||
last_price = CellFactory.create(this, _book_vm.columns.last_price_column, r => _order.last_price(), r => r.last_price, format_price);
|
||||
status = CellFactory.create(this, _book_vm.columns.status_column, r => _order.status(), r => r.status);
|
||||
last_msg = CellFactory.create(this, _book_vm.columns.last_message_column, r => _order.last_msg(), r => r.last_msg);
|
||||
_security_store = CellFactory.create_nullable(this, _book_vm.columns.security_column, nameof(security), r => r._security, r => r._security_store, v => v.HasValue ? v.Value.ToString() : "unknown security");
|
||||
_security_key_store = CellFactory.create(this, _book_vm.columns.security_key_column, nameof(security_key), r => r._order.security_key(), r => r._security_key_store);
|
||||
_parent_order_key_store = CellFactory.create(this, _book_vm.columns.parent_key_column, nameof(parent_order_key), r => r._order.ParentKey, r => r._parent_order_key_store);
|
||||
_order_key_store = CellFactory.create(this, _book_vm.columns.order_key_column, nameof(order_key), r => r._order.Key, r => r._order_key_store);
|
||||
_time_created_store = CellFactory.create(this, _book_vm.columns.time_created_column, nameof(time_created), r => r._order.created(), r => r._time_created_store, format_date);
|
||||
_user_store = CellFactory.create(this, _book_vm.columns.user_column, nameof(user), r => _order.user(), r => r._user_store);
|
||||
_account_store = CellFactory.create(this, _book_vm.columns.account_column, nameof(account), r => r._order.account(), r => r._account_store);
|
||||
_exchange_store = CellFactory.create(this, _book_vm.columns.exchange_column, nameof(exchange), r => r._security.HasValue ? _security.Value.Exchange : Venue.Unset, r => r._exchange_store);
|
||||
_time_in_force_store = CellFactory.create(this, _book_vm.columns.time_in_force_column, nameof(time_in_force), r => r._order.time_in_force(), r => r._time_in_force_store);
|
||||
_order_type_store = CellFactory.create(this, _book_vm.columns.order_type_column, nameof(order_type), r => r._order.order_type(), r => r._order_type_store);
|
||||
_order_sub_type_store = CellFactory.create(this, _book_vm.columns.sub_type_column, nameof(order_sub_type), r => r._order.sub_type(), r => r._order_sub_type_store, format_sub_type);
|
||||
_side_store = CellFactory.create(this, _book_vm.columns.side_column, nameof(side), r => r._order.side(), r => r._side_store);
|
||||
_last_change_store = CellFactory.create(this, _book_vm.columns.last_change_column, nameof(last_change), r => _order.last_change(), r => r._last_change_store, format_date);
|
||||
_price_store = CellFactory.create(this, _book_vm.columns.price_column, nameof(price), r => _order.price(), r => r._price_store, format_price);
|
||||
_quantity_store = CellFactory.create(this, _book_vm.columns.qty_column, nameof(quantity), r => _order.quantity(), r => r._quantity_store);
|
||||
_filled_quantity_store = CellFactory.create(this, _book_vm.columns.filled_qty_column, nameof(filled_quantity), r => _order.filled(), r => r._filled_quantity_store);
|
||||
_avg_price_store = CellFactory.create(this, _book_vm.columns.avg_price_column, nameof(avg_price), r => _order.avg_price(), r => r._avg_price_store, format_price);
|
||||
_last_price_store = CellFactory.create(this, _book_vm.columns.last_price_column, nameof(last_price), r => _order.last_price(), r => r._last_price_store, format_price);
|
||||
_status_store = CellFactory.create(this, _book_vm.columns.status_column, nameof(status), r => _order.status(), r => r._status_store);
|
||||
_last_msg_store = CellFactory.create(this, _book_vm.columns.last_message_column, nameof(last_msg), r => _order.last_msg(), r => r._last_msg_store);
|
||||
//
|
||||
string format_price(double v) => _security.HasValue ? _security.Value.ContractSpec.format(v) : v.ToString();
|
||||
string format_date(DateTime v) => $"{v:M/d} {v:h:mm:ss.fff tt}";
|
||||
@@ -60,61 +84,73 @@ namespace mbts.spark {
|
||||
}
|
||||
}
|
||||
//
|
||||
_visible = backing(nameof(is_visible), !_order.has_parent());
|
||||
_visible = backing(nameof(is_visible), true);
|
||||
_expanded = backing(nameof(is_expanded), true);
|
||||
_row_passes_filter = _book_vm.columns.filter(this);
|
||||
_passes_filter = backing(nameof(passes_filter), () => {
|
||||
if (_parent != null)
|
||||
return _parent.passes_filter;
|
||||
else return _row_passes_filter || _descendents(this).Any(r => r._row_passes_filter);
|
||||
_depth_level = backing(nameof(depth_level), () => {
|
||||
int i = 0;
|
||||
var curr = this;
|
||||
while (curr._parent != null) {
|
||||
i++;
|
||||
curr = curr._parent;
|
||||
}
|
||||
return i;
|
||||
});
|
||||
toggle_command = new DelegateCommand(() => is_expanded = !is_expanded);
|
||||
_show_order_modify_cmd = new ObservantCommand(() => bus.post(SparkTags.ShowModifyOrderTag, _order.Key));
|
||||
_show_order_modify_cmd.OnNext(status.value == OrderStatus.Working);
|
||||
row_passes_filter = _book_vm.columns.filter(this);
|
||||
_passes_filter = backing(nameof(passes_filter),
|
||||
Observable.FromEvent<Action<IObservable<bool>>, IObservable<bool>>(
|
||||
cb => _branch_filter_source_changed += cb, cb => _branch_filter_source_changed -= cb)
|
||||
.SelectMany(o => o));
|
||||
}
|
||||
|
||||
|
||||
public event Action invalidated;
|
||||
event Action<IObservable<bool>> _branch_filter_source_changed;
|
||||
|
||||
public OrderRecordImmutable order => _order;
|
||||
// Core
|
||||
public OrderBookCell<uint> order_key { get; }
|
||||
public OrderBookCell<uint> parent_order_key { get; }
|
||||
public OrderBookCell<uint> security_key { get; }
|
||||
public OrderBookCell<SecurityImmutable?> security { get; }
|
||||
public OrderBookCell<Venue> exchange { get; }
|
||||
// Def
|
||||
public OrderBookCell<CustomOrderDefinition.CustomOneofCase> order_type { get; }
|
||||
public OrderBookCell<Side> side { get; }
|
||||
public OrderBookCell<string> user { get; }
|
||||
public OrderBookCell<string> account { get; }
|
||||
public OrderBookCell<TimeInForce> time_in_force { get; }
|
||||
public OrderBookCell<DateTime> time_created { get; }
|
||||
// State
|
||||
public OrderBookCell<DateTime> last_change { get; }
|
||||
public OrderBookCell<OrderStatus> status { get; }
|
||||
public OrderBookCell<double> price { get; }
|
||||
public OrderBookCell<uint> quantity { get; }
|
||||
public OrderBookCell<uint> filled_quantity { get; }
|
||||
public OrderBookCell<double> last_price { get; }
|
||||
public OrderBookCell<double> avg_price { get; }
|
||||
public OrderBookCell<string> last_msg { get; }
|
||||
public OrderBookCell<OrderSubType> order_sub_type { get; }
|
||||
public OrderMetadata order_metadata => _model.factory.get_metadata(order_type.value);
|
||||
public CVOrderBookRowVM parent => _parent;
|
||||
// Core
|
||||
public IOrderBookCell<uint> order_key => _order_key_store.current;
|
||||
public IOrderBookCell<uint> parent_order_key => _parent_order_key_store.current;
|
||||
public IOrderBookCell<uint> security_key => _security_key_store.current;
|
||||
public IOrderBookCell<SecurityImmutable?> security => _security_store.current;
|
||||
public IOrderBookCell<Venue> exchange => _exchange_store.current;
|
||||
// Def
|
||||
public IOrderBookCell<CustomOrderDefinition.CustomOneofCase> order_type => _order_type_store.current;
|
||||
public IOrderBookCell<Side> side => _side_store.current;
|
||||
public IOrderBookCell<string> user => _user_store.current;
|
||||
public IOrderBookCell<string> account => _account_store.current;
|
||||
public IOrderBookCell<TimeInForce> time_in_force => _time_in_force_store.current;
|
||||
public IOrderBookCell<DateTime> time_created => _time_created_store.current;
|
||||
// State
|
||||
public IOrderBookCell<DateTime> last_change => _last_change_store.current;
|
||||
public IOrderBookCell<OrderStatus> status => _status_store.current;
|
||||
public IOrderBookCell<double> price => _price_store.current;
|
||||
public IOrderBookCell<uint> quantity => _quantity_store.current;
|
||||
public IOrderBookCell<uint> filled_quantity => _filled_quantity_store.current;
|
||||
public IOrderBookCell<double> last_price => _last_price_store.current;
|
||||
public IOrderBookCell<double> avg_price => _avg_price_store.current;
|
||||
public IOrderBookCell<string> last_msg => _last_msg_store.current;
|
||||
public IOrderBookCell<OrderSubType> order_sub_type => _order_sub_type_store.current;
|
||||
// Commands
|
||||
public ICommand toggle_command { get; }
|
||||
ICommand ui.controls.DIYGrid.IActionableRowVM.default_command => _show_order_modify_cmd;
|
||||
// Accessories
|
||||
public CVOrderBookRowVM parent_row => _parent;
|
||||
public bool is_parent => _child_rows.Count > 0;
|
||||
public bool is_child => _order.has_parent();
|
||||
public IReadOnlyCollection<CVOrderBookRowVM> child_orders => _child_rows;
|
||||
public CVOrderBookVM.BranchFilterSource branch_filter_source => _branch_filter_source;
|
||||
public bool passes_filter => _passes_filter.Value;
|
||||
public bool row_passes_filter { get; private set; }
|
||||
public bool is_visible {
|
||||
get => _visible.Value;
|
||||
set {
|
||||
_visible.set(value);
|
||||
if (is_expanded) {
|
||||
foreach (var child_row in _child_rows)
|
||||
child_row._visible.set(value);
|
||||
child_row.is_visible = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -126,164 +162,34 @@ namespace mbts.spark {
|
||||
child_row.is_visible = value;
|
||||
}
|
||||
}
|
||||
public int depth_level {
|
||||
get {
|
||||
int i = 0;
|
||||
var curr = this;
|
||||
while (curr._parent != null) {
|
||||
i++;
|
||||
curr = curr._parent;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
}
|
||||
public bool passes_filter => _passes_filter.Value;
|
||||
public bool row_passes_filter {
|
||||
get => _row_passes_filter;
|
||||
set {
|
||||
if (_row_passes_filter != value) {
|
||||
_row_passes_filter = value;
|
||||
if (value) {
|
||||
if (!passes_filter) {
|
||||
var root = root_node(this);
|
||||
root._passes_filter.refresh();
|
||||
foreach (var r in _descendents(root))
|
||||
r._passes_filter.refresh();
|
||||
}
|
||||
} else if (passes_filter) {
|
||||
var root = root_node(this);
|
||||
if (!root._row_passes_filter && !_descendents(root).AtLeast(2, r => r._row_passes_filter )) {
|
||||
root._passes_filter.refresh();
|
||||
foreach (var r in _descendents(root))
|
||||
r._passes_filter.refresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
public int depth_level => _depth_level.Value;
|
||||
|
||||
// '_dynamic_render_cells' are presented by 'RenderColumns,' and may change value during the lifetime of this row
|
||||
// Note: by instantiating all these cells inside a CellPropertyGroup, we can update all of them with a single call.
|
||||
// this reduces the risk of accidentally failing to update one of the cells, esp if we make changes to them down the road.
|
||||
IEnumerable<IOrderBookCell> _dynamic_render_cells {
|
||||
IEnumerable<IOrderBookCellStore> _dynamic_render_cells {
|
||||
get {
|
||||
yield return last_change;
|
||||
yield return price;
|
||||
yield return quantity;
|
||||
yield return filled_quantity;
|
||||
yield return avg_price;
|
||||
yield return last_price;
|
||||
yield return status;
|
||||
yield return last_msg;
|
||||
yield return _last_change_store;
|
||||
yield return _price_store;
|
||||
yield return _quantity_store;
|
||||
yield return _filled_quantity_store;
|
||||
yield return _avg_price_store;
|
||||
yield return _last_price_store;
|
||||
yield return _status_store;
|
||||
yield return _last_msg_store;
|
||||
}
|
||||
}
|
||||
|
||||
IEnumerable<IOrderBookCell> _security_dependent_cells {
|
||||
IEnumerable<IOrderBookCellStore> _security_dependent_cells {
|
||||
get {
|
||||
yield return security;
|
||||
yield return exchange;
|
||||
yield return price;
|
||||
yield return avg_price;
|
||||
yield return last_price;
|
||||
yield return _security_store;
|
||||
yield return _exchange_store;
|
||||
yield return _price_store;
|
||||
yield return _avg_price_store;
|
||||
yield return _last_price_store;
|
||||
}
|
||||
}
|
||||
|
||||
public void add_child_order(CVOrderBookRowVM child_row) {
|
||||
// we don't need to raise this every single time
|
||||
if (_child_rows.Add(child_row) && _child_rows.Count == 1)
|
||||
RaisePropertyChanged("is_parent");
|
||||
child_row._parent = this;
|
||||
child_row.RaisePropertyChanged(nameof(depth_level));
|
||||
var root = root_node(this);
|
||||
if (child_row.passes_filter) {
|
||||
if (child_row._row_passes_filter)
|
||||
_child_orders_pass_filter.Add(child_row._order.Key);
|
||||
// if this row isn't currently passing the filter, update it and the rest of the branch
|
||||
if (!passes_filter) {
|
||||
_passes_filter.set(true);
|
||||
// child_row and its descendents already know they passed failter
|
||||
foreach (var row in _descendents_except_node(root, child_row))
|
||||
row._passes_filter.set(true);
|
||||
}
|
||||
}
|
||||
// recursively updates 'live_sorting_props,' and 'is_visible' for child_row and descendents
|
||||
child_row.on_parent_updated();
|
||||
if (child_row.row_passes_filter && !root.passes_filter) {
|
||||
root._passes_filter.Value = true;
|
||||
foreach (var d in _descendents(this))
|
||||
d._passes_filter.Value = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void remove_child_order(CVOrderBookRowVM child_row) {
|
||||
if (_child_rows.Remove(child_row) && _child_rows.Count == 0)
|
||||
RaisePropertyChanged(nameof(is_parent));
|
||||
if (child_row._row_passes_filter) {
|
||||
_child_orders_pass_filter.Remove(child_row._order.Key);
|
||||
_passes_filter.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raises PropertyChanged events for all live sorting properties, then calls parent_updated() on all descendents.
|
||||
/// <para>Note: call this method when parent order is added, removed, or changed.</para>
|
||||
/// </summary>
|
||||
public void on_parent_updated() {
|
||||
foreach (var p in _book_vm.live_sorting_props)
|
||||
RaisePropertyChanged(p);
|
||||
is_visible = _parent == null || _parent.is_visible && _parent.is_expanded;
|
||||
foreach (var child_row in _child_rows)
|
||||
child_row.on_parent_updated();
|
||||
}
|
||||
|
||||
public void update_order(OrderRecordImmutable order) {
|
||||
_order = order;
|
||||
// use Count(), as opposed to Any(), to force it to iterate through all cells
|
||||
if (_dynamic_render_cells.Count(c => c.refresh()) > 0)
|
||||
invalidated?.Invoke();
|
||||
_show_order_modify_cmd.OnNext(status.value == OrderStatus.Working);
|
||||
_on_update();
|
||||
}
|
||||
|
||||
public void update_security(SecurityImmutable security) {
|
||||
_security = security;
|
||||
// use Count(), as opposed to Any(), to force it to iterate through all cells
|
||||
if (_security_dependent_cells.Count(c => c.refresh()) > 0)
|
||||
invalidated?.Invoke();
|
||||
_on_update();
|
||||
}
|
||||
|
||||
void _on_update() {
|
||||
foreach (var p in _book_vm.live_sorting_props)
|
||||
RaisePropertyChanged(p);
|
||||
foreach (var child_row in _child_rows)
|
||||
child_row.on_parent_updated();
|
||||
row_passes_filter = _book_vm.columns.filter(this);
|
||||
}
|
||||
|
||||
public void refresh_row_passes_filter() =>
|
||||
_row_passes_filter = _book_vm.columns.filter(this);
|
||||
|
||||
public void refresh_branch_passes_filter() {
|
||||
_passes_filter.refresh();
|
||||
foreach (var r in _descendents(this))
|
||||
r._passes_filter.refresh();
|
||||
}
|
||||
|
||||
public void on_filter_changed() =>
|
||||
_passes_filter.refresh();
|
||||
|
||||
public bool Equals(CVOrderBookRowVM other) => other != null && other.order_key == order_key;
|
||||
public override bool Equals(object obj) => Equals(obj as CVOrderBookRowVM);
|
||||
public override int GetHashCode() => (int)_order.Key;
|
||||
public override string ToString() => $"{order_key} {last_change} {parent_order_key}";
|
||||
|
||||
public static CVOrderBookRowVM root_node(CVOrderBookRowVM node) {
|
||||
while (node._parent != null)
|
||||
node = node._parent;
|
||||
return node;
|
||||
}
|
||||
|
||||
public static Stack<CVOrderBookRowVM> lineage(CVOrderBookRowVM node) {
|
||||
var rv = new Stack<CVOrderBookRowVM>();
|
||||
rv.Push(node);
|
||||
@@ -292,16 +198,73 @@ namespace mbts.spark {
|
||||
return rv;
|
||||
}
|
||||
|
||||
static IEnumerable<CVOrderBookRowVM> _descendents_except_node(CVOrderBookRowVM root, CVOrderBookRowVM except) {
|
||||
foreach (var child in root._child_rows) {
|
||||
if (child == except)
|
||||
continue;
|
||||
yield return child;
|
||||
foreach (var grand_child in _descendents(child))
|
||||
yield return grand_child;
|
||||
public void join_branch_filter_source(CVOrderBookVM.BranchFilterSource new_branch_filter_source) {
|
||||
_branch_filter_source = new_branch_filter_source;
|
||||
_branch_filter_source.set_row_passes_filter(_order.Key, row_passes_filter);
|
||||
_branch_filter_source_changed?.Invoke(_branch_filter_source);
|
||||
foreach (var child_row in _child_rows)
|
||||
child_row.join_branch_filter_source(_branch_filter_source);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attaches the child-row to this row's branch-filter-source, and recursively refreshes sort/filter properties for the child and it's descendents.
|
||||
/// </summary>
|
||||
public void add_child_order(CVOrderBookRowVM child_row) {
|
||||
if (_child_rows.Add(child_row) && _child_rows.Count == 1)
|
||||
RaisePropertyChanged(nameof(is_parent));
|
||||
child_row._parent = this;
|
||||
// child orders that arrived in an earlier update use a temporary branch_filter_source
|
||||
// need to tell _book_vm to remove the now-stale branch_filter_source.
|
||||
if (child_row._branch_filter_source != null)
|
||||
_book_vm.remove_branch_filter_root(child_row._order.Key);
|
||||
// recursively updates 'live_sorting_props,' and 'is_visible' for child_row and descendents
|
||||
child_row._on_lineage_changed();
|
||||
}
|
||||
|
||||
public void remove_child_order(CVOrderBookRowVM child_row) {
|
||||
if (_child_rows.Remove(child_row) && _child_rows.Count == 0)
|
||||
RaisePropertyChanged(nameof(is_parent));
|
||||
}
|
||||
|
||||
public void remove_parent(CVOrderBookVM.BranchFilterSource new_branch_filter_source) {
|
||||
_parent = null;
|
||||
join_branch_filter_source(new_branch_filter_source);
|
||||
refresh_row_passes_filter();
|
||||
foreach (var p in _book_vm.live_sorting_props)
|
||||
RaisePropertyChanged(p);
|
||||
_depth_level.refresh();
|
||||
foreach (CVOrderBookRowVM child_row in _child_rows)
|
||||
child_row._on_lineage_changed();
|
||||
}
|
||||
|
||||
public void update_order(OrderRecordImmutable order) {
|
||||
Logger.assert(order.ParentKey == _order.ParentKey, "Parent Keys can change?");
|
||||
Logger.assert(order.Key == _order.Key, "Keys can change?");
|
||||
_order = order;
|
||||
_update_partial(_dynamic_render_cells);
|
||||
}
|
||||
|
||||
public void update_security(SecurityImmutable security) {
|
||||
_security = security;
|
||||
_update_partial(_security_dependent_cells);
|
||||
}
|
||||
|
||||
public void refresh_row_passes_filter() {
|
||||
row_passes_filter = _book_vm.columns.filter(this);
|
||||
if (_branch_filter_source != null) {
|
||||
_branch_filter_source.set_row_passes_filter(_order.Key, row_passes_filter);
|
||||
} else {
|
||||
_passes_filter.set(row_passes_filter);
|
||||
}
|
||||
}
|
||||
|
||||
public bool Equals(CVOrderBookRowVM other) => other != null && other._order.Key == _order.Key;
|
||||
public override bool Equals(object obj) => Equals(obj as CVOrderBookRowVM);
|
||||
public override int GetHashCode() => (int)_order.Key;
|
||||
public override string ToString() => $"{order_key} {last_change} {parent_order_key}";
|
||||
|
||||
protected override void Dispose(bool disposing) => Disposable.dispose(ref _branch_filter_sub);
|
||||
|
||||
static IEnumerable<CVOrderBookRowVM> _descendents(CVOrderBookRowVM root) {
|
||||
foreach (var child in root.child_orders) {
|
||||
yield return child;
|
||||
@@ -309,5 +272,66 @@ namespace mbts.spark {
|
||||
yield return grand_child;
|
||||
}
|
||||
}
|
||||
|
||||
static IOrderBookCellStore _cell_store_from_column(CVOrderBookRowVM row, Columns column) {
|
||||
switch (column) {
|
||||
case Columns.OrderKey: return row._order_key_store;
|
||||
case Columns.ParentKey: return row._parent_order_key_store;
|
||||
case Columns.SecurityKey: return row._security_key_store;
|
||||
case Columns.Security: return row._security_store;
|
||||
case Columns.Exchange: return row._exchange_store;
|
||||
case Columns.OrderType: return row._order_type_store;
|
||||
case Columns.Side: return row._side_store;
|
||||
case Columns.User: return row._user_store;
|
||||
case Columns.Account: return row._account_store;
|
||||
case Columns.TimeInForce: return row._time_in_force_store;
|
||||
case Columns.Time: return row._time_created_store;
|
||||
case Columns.LastChange: return row._last_change_store;
|
||||
case Columns.Status: return row._status_store;
|
||||
case Columns.Price: return row._price_store;
|
||||
case Columns.Size: return row._quantity_store;
|
||||
case Columns.Filled: return row._filled_quantity_store;
|
||||
case Columns.LastPrice: return row._last_price_store;
|
||||
case Columns.AvgFilled: return row._avg_price_store;
|
||||
case Columns.LastMessage: return row._last_msg_store;
|
||||
case Columns.SubType: return row._order_sub_type_store;
|
||||
default: throw new ArgumentException(nameof(column));
|
||||
}
|
||||
}
|
||||
|
||||
// Note: this method assumes that lineage exists (i.e. _parent is not null).
|
||||
void _on_lineage_changed() {
|
||||
_depth_level.refresh();
|
||||
// parent 'branch_filter_source' can be null:
|
||||
// if the parent is itself an orphan - meaning this is the middle of an update -
|
||||
// the final branch-filter-root will be determined after all new orders have been added
|
||||
if (_parent._branch_filter_source != null)
|
||||
join_branch_filter_source(_parent._branch_filter_source);
|
||||
_visible.set(_parent.is_visible && _parent.is_expanded);
|
||||
// refresh the sort by calling regenerate_cell() for each live-sorting-columns corresponding cell-store
|
||||
foreach (Columns column in _book_vm.live_sorting_columns)
|
||||
_cell_store_from_column(this, column).regenerate_cell();
|
||||
// finally, repeat for each child-row
|
||||
foreach (var child_row in _child_rows)
|
||||
child_row._on_lineage_changed();
|
||||
}
|
||||
|
||||
void _update_partial(IEnumerable<IOrderBookCellStore> target_cell_stores) {
|
||||
bool invalidate = false;
|
||||
foreach (var cell_store in target_cell_stores) {
|
||||
if (cell_store.refresh()) {
|
||||
invalidate = true;
|
||||
if (_book_vm.live_sorting_columns.Contains(cell_store.column.key)) {
|
||||
// refresh the sort by calling regenerate_cell() for each descendent-row
|
||||
foreach (CVOrderBookRowVM dr in _descendents(this))
|
||||
cell_store.select_other(dr).regenerate_cell();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (invalidate)
|
||||
invalidated?.Invoke();
|
||||
_show_order_modify_cmd.OnNext(status.value == OrderStatus.Working);
|
||||
refresh_row_passes_filter();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using mbts.ui;
|
||||
using mbts.ui.controls;
|
||||
using mbts.ui.shell;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Collections.Specialized;
|
||||
@@ -16,6 +17,8 @@ using System.Reactive.Subjects;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Data;
|
||||
using static bts.shell.OrderBookWindowFactory;
|
||||
using static mbts.spark.OrderBookView;
|
||||
using static mbts.ui.controls.DIYGrid;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class CVOrderBookVM : DisposableViewModelBase, IHandleRowSelection, IOrderBookVM {
|
||||
@@ -23,29 +26,27 @@ namespace mbts.spark {
|
||||
// each row accesses the public 'live_sorting_props' when it, or an ancestor order, is updated,
|
||||
// copying the collection locally avoids constantly casting rows_view to ICollectionViewLiveShaping.
|
||||
readonly ObservableCollection<string> _live_sorting_props;
|
||||
readonly LiveSortingColumKeys _live_sorting_columns;
|
||||
readonly Property<CVOrderBookRowVM> _current_row;
|
||||
readonly Subject<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)> _order_securities_subject =
|
||||
new Subject<(IEnumerable<SecurityImmutable> added, IEnumerable<SecurityImmutable> removed)>();
|
||||
|
||||
IReadOnlyDictionary<uint, OrderRecordImmutable> _orders_cache = new Dictionary<uint, OrderRecordImmutable>();
|
||||
Dictionary<uint, CVOrderBookRowVM> _order_vms_cache = new Dictionary<uint, CVOrderBookRowVM>();
|
||||
ObservableCollection<CVOrderBookRowVM> _order_vms = new ObservableCollection<CVOrderBookRowVM>();
|
||||
Dictionary<uint, CVOrderBookRowVM> _order_rows_cache = new Dictionary<uint, CVOrderBookRowVM>();
|
||||
Dictionary<uint, BranchFilterSource> _branch_filter_roots = new Dictionary<uint, BranchFilterSource>();
|
||||
ObservableCollection<CVOrderBookRowVM> _order_rows = new ObservableCollection<CVOrderBookRowVM>();
|
||||
ObservableCollection<CVOrderBookRowVM> _selected_rows = new ObservableCollection<CVOrderBookRowVM>();
|
||||
SetMap<uint, uint> _parent_to_children = new SetMap<uint, uint>();
|
||||
SetMap<uint, uint> _security_orders = new SetMap<uint, uint>();
|
||||
|
||||
public CVOrderBookVM(IBus bus, OrdersModel model, OrderBookStartupData state) {
|
||||
columns = new CVOrderBookColumnCollection(bus, model, state, _order_securities_subject);
|
||||
rows_view = CollectionViewSource.GetDefaultView(_order_vms);
|
||||
foreach (ColumnBase col in columns)
|
||||
col.sort_direction_changed += on_column_sort_direction_changed;
|
||||
rows_view = CollectionViewSource.GetDefaultView(_order_rows);
|
||||
rows_view.Filter = obj => obj is CVOrderBookRowVM vm && vm.is_visible && vm.passes_filter;
|
||||
columns.filter_changed += () => {
|
||||
var root_nodes = new HashSet<CVOrderBookRowVM>();
|
||||
foreach (var vm in _order_vms) {
|
||||
foreach (var vm in _order_rows)
|
||||
vm.refresh_row_passes_filter();
|
||||
root_nodes.Add(CVOrderBookRowVM.root_node(vm));
|
||||
}
|
||||
foreach (var vm in root_nodes)
|
||||
vm.refresh_branch_passes_filter();
|
||||
};
|
||||
var cvls = (ICollectionViewLiveShaping)rows_view;
|
||||
_live_sorting_props = cvls.LiveSortingProperties;
|
||||
@@ -63,98 +64,129 @@ namespace mbts.spark {
|
||||
rows_view.SortDescriptions.Add(new SortDescription(nameof(CVOrderBookRowVM.time_created), ListSortDirection.Descending));
|
||||
_live_sorting_props.Add(nameof(CVOrderBookRowVM.time_created));
|
||||
}
|
||||
_live_sorting_columns = new LiveSortingColumKeys(_live_sorting_props);
|
||||
|
||||
SetMap<uint, uint> missing_security_orders = new SetMap<uint, uint>();
|
||||
IReadOnlyDictionary<uint, SecurityMD> security_dict = new Dictionary<uint, SecurityMD>();
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).ObserveOnDispatcher().Subscribe(on_securities),
|
||||
model.on_orders.ObserveOnDispatcher().Subscribe(on_orders),
|
||||
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(on_securities),
|
||||
model.on_orders.OnDispatcher().Subscribe(on_orders),
|
||||
columns);
|
||||
|
||||
void on_orders(TableUpdate<OrderRecordImmutable, uint> table) {
|
||||
// check sum (seems like there is a bug in orders api, see repro steps below)
|
||||
// 1) with an CVOrderBook open, enter a few 500+ lot iceberg order with < 20 show_quantity
|
||||
// 2) wait until the orders are filled, or until fills inexplicably stop coming in
|
||||
// 3) open another CVOrderBook, all the orders in the new book will have 'Status = inactive'
|
||||
// 4) open the log file and scroll to the bottom to find a log-message that came from the code below
|
||||
IEnumerable<OrderRecordImmutable> added, removed;
|
||||
if (_orders_cache.Count != (table.state.Count + table.removed.Count - table.added.Count)) {
|
||||
Logger.error(bts.log.Subsystem.Ui, $"CVOrderBook detected non-incremental changes in orders api, will delete all rows and (re)add everything in api state: " +
|
||||
$"orderbook count before update({_orders_cache.Count}), update.state count({table.state.Count}), update.added count({table.added.Count}), update.removed count({table.removed.Count}).");
|
||||
_order_vms.Clear();
|
||||
// clear everything (most likely the server restarted)
|
||||
if (table.removed.Count == _order_rows.Count && table.added.Count == 0) {
|
||||
_selected_rows.Clear();
|
||||
_order_rows.DisposeAll();
|
||||
_order_rows.Clear();
|
||||
_order_rows_cache.Clear();
|
||||
_branch_filter_roots.Clear();
|
||||
_parent_to_children.clear();
|
||||
added = table.state.Values;
|
||||
removed = Enumerable.Empty<OrderRecordImmutable>();
|
||||
} else {
|
||||
added = table.added;
|
||||
removed = table.removed;
|
||||
_security_orders.clear();
|
||||
missing_security_orders.clear();
|
||||
return;
|
||||
}
|
||||
_orders_cache = table.state;
|
||||
var changed_keys = new HashSet<uint>();
|
||||
var added_securities = new List<SecurityImmutable>(1);
|
||||
var removed_securities = new List<SecurityImmutable>(1);
|
||||
foreach (var o in removed) {
|
||||
|
||||
foreach (var o in table.removed) {
|
||||
// if state has it, then it wasn't actually removed, just changed
|
||||
if (table.state.TryGetValue(o.Key, out OrderRecordImmutable changed_order)) {
|
||||
changed_keys.Add(o.Key);
|
||||
_order_vms_cache[o.Key].update_order(changed_order);
|
||||
} else if (_order_vms_cache.TryPopValue(o.Key, out CVOrderBookRowVM vm)) {
|
||||
_order_rows_cache[o.Key].update_order(changed_order);
|
||||
} else if (_order_rows_cache.TryPopValue(o.Key, out CVOrderBookRowVM row)) {
|
||||
row.Dispose();
|
||||
_order_rows.Remove(row);
|
||||
if (_security_orders.removes_key(o.Def.SecurityKey, o.Key)) {
|
||||
if (vm.security.value.HasValue)
|
||||
removed_securities.Add(vm.security.value.Value);
|
||||
if (row.security.value.HasValue)
|
||||
removed_securities.Add(row.security.value.Value);
|
||||
}
|
||||
_order_vms.Remove(vm);
|
||||
if (o.has_parent()) {
|
||||
_parent_to_children.remove(o.ParentKey, o.Key);
|
||||
vm.parent_row?.remove_child_order(vm);
|
||||
if (row.parent != null) {
|
||||
row.parent.remove_child_order(row);
|
||||
} else {
|
||||
_branch_filter_roots.Remove(o.Key);
|
||||
}
|
||||
} else {
|
||||
_branch_filter_roots.Remove(o.Key);
|
||||
}
|
||||
if (_parent_to_children.try_pop(o.Key, out ISet<uint> children)) {
|
||||
foreach (var k in children)
|
||||
_order_vms_cache[k].on_parent_updated();
|
||||
// the child_rows will most-likely be removed in this update, but we can't be sure,
|
||||
// until that happens they must serve as tempororay branch-filter-roots
|
||||
foreach (var child_row in row.child_orders) {
|
||||
var bfs = new BranchFilterSource();
|
||||
child_row.remove_parent(bfs);
|
||||
_branch_filter_roots.Add(child_row.order.Key, bfs);
|
||||
}
|
||||
missing_security_orders.remove(o.Key);
|
||||
}
|
||||
}
|
||||
var orphans = new List<CVOrderBookRowVM>();
|
||||
foreach (var o in added) {
|
||||
var orphans = new Dictionary<uint, CVOrderBookRowVM>();
|
||||
foreach (var o in table.added) {
|
||||
if (changed_keys.Contains(o.Key))
|
||||
continue;
|
||||
var vm = new CVOrderBookRowVM(bus, this, model, o, security_dict.TryGetValue(o.Def.SecurityKey, out SecurityMD smd) ? new SecurityImmutable?(smd.security) : null);
|
||||
if (!vm.security.value.HasValue)
|
||||
SecurityImmutable? security;
|
||||
if (security_dict.TryGetValue(o.Def.SecurityKey, out SecurityMD smd)) {
|
||||
security = smd.security;
|
||||
if (_security_orders.adds_key(o.Def.SecurityKey, o.Key))
|
||||
added_securities.Add(security.Value);
|
||||
} else {
|
||||
missing_security_orders.add(o.Def.SecurityKey, o.Key);
|
||||
if (_security_orders.adds_key(o.Def.SecurityKey, o.Key)) {
|
||||
if (vm.security.value.HasValue)
|
||||
added_securities.Add(vm.security.value.Value);
|
||||
security = null;
|
||||
_security_orders.add(o.Def.SecurityKey, o.Key);
|
||||
}
|
||||
_order_vms_cache.Add(o.Key, vm);
|
||||
if (_parent_to_children.try_lookup(o.Key, out ISet<uint> children))
|
||||
foreach (var k in children) {
|
||||
// ** if children arrived in earlier update, this will notify them
|
||||
// that their parent has arrived, and cause them to be properly sorted **
|
||||
vm.add_child_order(_order_vms_cache[k]);
|
||||
}
|
||||
if (o.has_parent()) {
|
||||
CVOrderBookRowVM row = new CVOrderBookRowVM(bus, this, model, o, security);
|
||||
if (!o.has_parent()) {
|
||||
// if it doesn't have a parent, it must be a root; create a new BranchFilterSource
|
||||
var bfs = new BranchFilterSource();
|
||||
_branch_filter_roots.Add(o.Key, bfs);
|
||||
row.join_branch_filter_source(bfs);
|
||||
} else {
|
||||
_parent_to_children.add(o.ParentKey, o.Key);
|
||||
if (_order_vms_cache.TryGetValue(o.ParentKey, out CVOrderBookRowVM pvm)) {
|
||||
pvm.add_child_order(vm);
|
||||
if (_order_rows_cache.TryGetValue(o.ParentKey, out CVOrderBookRowVM pvm)) {
|
||||
pvm.add_child_order(row);
|
||||
} else {
|
||||
// look for orphans' parents again after all new orders have been added to _order_vm_cache
|
||||
orphans.Add(vm);
|
||||
// (**) look for orphans' parents again after all new orders have been added to _order_vm_cache
|
||||
// don't create a temporary BranchFilterSource root just yet. (**)
|
||||
orphans.Add(o.Key, row);
|
||||
_order_rows_cache.Add(o.Key, row);
|
||||
try_add_children(row);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
_order_vms.Add(vm);
|
||||
_order_rows_cache.Add(o.Key, row);
|
||||
_order_rows.Add(row);
|
||||
try_add_children(row);
|
||||
}
|
||||
// ** add the orphans now, most likely their parent was added after them in the iteration
|
||||
// if not, they will be notified when their parent arrives, and be properly sorted then. **
|
||||
foreach (var vm in orphans) {
|
||||
if (_order_vms_cache.TryGetValue(vm.parent_order_key.value, out CVOrderBookRowVM p))
|
||||
p.add_child_order(vm);
|
||||
_order_vms.Add(vm);
|
||||
// (**) since all new rows have been added by now, any remaining orphans will be assigned their own branch-filter-roots
|
||||
// (usually, rows are tempororily added to orphans bc their parent is just behind them in the list of added orders) (**)
|
||||
foreach (CVOrderBookRowVM row in orphans.Values) {
|
||||
var bfs = new BranchFilterSource();
|
||||
row.join_branch_filter_source(bfs);
|
||||
_branch_filter_roots.Add(row.order.Key, bfs);
|
||||
_order_rows.Add(row);
|
||||
}
|
||||
if (added_securities.Count > 0 || removed_securities.Count > 0)
|
||||
_order_securities_subject.OnNext((added_securities, removed_securities));
|
||||
|
||||
void try_add_children(CVOrderBookRowVM row) {
|
||||
if (_parent_to_children.try_lookup(row.order.Key, out ISet<uint> children))
|
||||
foreach (uint child_key in children) {
|
||||
CVOrderBookRowVM child_vm = _order_rows_cache[child_key];
|
||||
// (**) if children arrived in an earlier update, this will notify them that their parent has arrived (**)
|
||||
// - their tempororary branch-filter-roots will be removed, and they will join that of their parent's
|
||||
// - for the live-filter-properties, their corresponding cells will be regenerated and they will be re-inserted beneath their new parent.
|
||||
row.add_child_order(child_vm);
|
||||
if (orphans.Remove(child_key))
|
||||
_order_rows.Add(child_vm);
|
||||
else {
|
||||
_order_rows.Remove(child_vm);
|
||||
_order_rows.Add(child_vm);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// I assume we don't need to worry about orders "losing" or "changing" their security
|
||||
@@ -167,7 +199,7 @@ namespace mbts.spark {
|
||||
missing_security_orders.remove(missing_sec_orders.Key);
|
||||
added_securities.Add(smd.security);
|
||||
foreach (var order_key in missing_sec_orders) {
|
||||
var vm = _order_vms_cache[order_key];
|
||||
var vm = _order_rows_cache[order_key];
|
||||
vm.update_security(smd.security);
|
||||
}
|
||||
}
|
||||
@@ -175,30 +207,32 @@ namespace mbts.spark {
|
||||
if (added_securities.Count > 0)
|
||||
_order_securities_subject.OnNext((added_securities, new List<SecurityImmutable>(0)));
|
||||
}
|
||||
|
||||
// need to add sort_member_path to ICollectionView.LiveSortingProperties
|
||||
// since child-orders' sort position must always follow their parent,
|
||||
// if their parent is added after the child, the sort order must be updated.
|
||||
void on_column_sort_direction_changed(ColumnBase column, ListSortDirection? sort_direction) {
|
||||
if (column.SortDirection.HasValue) {
|
||||
if (!_live_sorting_props.Contains(column.sort_member_path))
|
||||
_live_sorting_props.Add(column.sort_member_path);
|
||||
} else {
|
||||
_live_sorting_props.Remove(column.sort_member_path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public event Action<OrderRecordImmutable?> selected_order_changed;
|
||||
|
||||
public CVOrderBookColumnCollection columns { get; }
|
||||
public IReadOnlyList<string> live_sorting_props => _live_sorting_props;
|
||||
public ISet<Columns> live_sorting_columns => _live_sorting_columns;
|
||||
public ICollectionView rows_view { get; }
|
||||
// this is a misnomer, but i want to support both kinds of OrderBookVMs, and OrderBookView currently binds to 'visible_orders,'
|
||||
public ICollectionView visible_orders => rows_view;
|
||||
public CVOrderBookRowVM current_row { get => _current_row.Value; set => _current_row.Value = value ?? _selected_rows.FirstOrDefault(); }
|
||||
public OrderRecordImmutable? current_order => current_row?.order;
|
||||
public IEnumerable<OrderRecordImmutable> selected_orders => _selected_rows.Select(r => r.order);
|
||||
|
||||
// need to add sort_member_path to ICollectionView.LiveSortingProperties
|
||||
// since child-orders' sort position must always follow their parent,
|
||||
// if their parent is added after the child, the sort order must be updated.
|
||||
public void on_sorting(DIYGridSortingEventArgs e) {
|
||||
if (e.Column.SortDirection.HasValue) {
|
||||
if (!_live_sorting_props.Contains(e.Column.sort_member_path))
|
||||
_live_sorting_props.Add(e.Column.sort_member_path);
|
||||
} else {
|
||||
_live_sorting_props.Remove(e.Column.sort_member_path);
|
||||
}
|
||||
}
|
||||
void IOrderBookVM.on_sorting(DIYGridSortingEventArgs e) { }
|
||||
|
||||
public void row_selection_changed(SelectionChangedEventArgs e) {
|
||||
RowSelectionHelper.apply_changes(_selected_rows, e);
|
||||
@@ -207,5 +241,89 @@ namespace mbts.spark {
|
||||
|
||||
public List<SortingCriteria> get_sorting_states() => rows_view.GetSortingCriteria();
|
||||
public FilterStates get_filter_states() => columns.get_filter_states();
|
||||
|
||||
internal void remove_branch_filter_root(uint key) =>
|
||||
_branch_filter_roots.Remove(key);
|
||||
|
||||
internal class BranchFilterSource : IObservable<bool> {
|
||||
readonly HashSet<uint> _orders_pass_filter = new HashSet<uint>();
|
||||
event Action<bool> _passes_filter_changed;
|
||||
|
||||
public void set_row_passes_filter(uint key, bool value) {
|
||||
if (value) {
|
||||
if (_orders_pass_filter.Add(key) && _orders_pass_filter.Count == 1)
|
||||
_passes_filter_changed?.Invoke(value);
|
||||
} else if (_orders_pass_filter.Remove(key) && _orders_pass_filter.Count == 0) {
|
||||
_passes_filter_changed?.Invoke(value);
|
||||
}
|
||||
}
|
||||
|
||||
public IDisposable Subscribe(IObserver<bool> observer) {
|
||||
_passes_filter_changed += observer.OnNext;
|
||||
observer.OnNext(_orders_pass_filter.Count > 0);
|
||||
return Disposable.Create(() => _passes_filter_changed -= observer.OnNext);
|
||||
}
|
||||
}
|
||||
|
||||
class LiveSortingColumKeys : HashSet<Columns> {
|
||||
public LiveSortingColumKeys(ObservableCollection<string> live_sorting_props) {
|
||||
_add_items(live_sorting_props);
|
||||
live_sorting_props.CollectionChanged += (s, e) => {
|
||||
switch (e.Action) {
|
||||
case NotifyCollectionChangedAction.Add: _add_items(e.NewItems); break;
|
||||
case NotifyCollectionChangedAction.Remove: _remove_items(e.OldItems); break;
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
_remove_items(e.OldItems);
|
||||
_add_items(e.NewItems);
|
||||
break;
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
Clear();
|
||||
_add_items(live_sorting_props);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static bool try_parse(string prop_name, out Columns result) {
|
||||
switch (prop_name) {
|
||||
case nameof(CVOrderBookRowVM.security): result = Columns.Security; break;
|
||||
case nameof(CVOrderBookRowVM.time_created): result = Columns.Time; break;
|
||||
case nameof(CVOrderBookRowVM.last_change): result = Columns.LastChange; break;
|
||||
case nameof(CVOrderBookRowVM.user): result = Columns.User; break;
|
||||
case nameof(CVOrderBookRowVM.account): result = Columns.Account; break;
|
||||
case nameof(CVOrderBookRowVM.exchange): result = Columns.Exchange; break;
|
||||
case nameof(CVOrderBookRowVM.time_in_force): result = Columns.TimeInForce; break;
|
||||
case nameof(CVOrderBookRowVM.order_type): result = Columns.OrderType; break;
|
||||
case nameof(CVOrderBookRowVM.order_sub_type): result = Columns.SubType; break;
|
||||
case nameof(CVOrderBookRowVM.side): result = Columns.Side; break;
|
||||
case nameof(CVOrderBookRowVM.quantity): result = Columns.Size; break;
|
||||
case nameof(CVOrderBookRowVM.filled_quantity): result = Columns.Filled; break;
|
||||
case nameof(CVOrderBookRowVM.price): result = Columns.Price; break;
|
||||
case nameof(CVOrderBookRowVM.avg_price): result = Columns.AvgFilled; break;
|
||||
case nameof(CVOrderBookRowVM.last_price): result = Columns.LastPrice; break;
|
||||
case nameof(CVOrderBookRowVM.status): result = Columns.Status; break;
|
||||
case nameof(CVOrderBookRowVM.last_msg): result = Columns.LastMessage; break;
|
||||
case nameof(CVOrderBookRowVM.security_key): result = Columns.SecurityKey; break;
|
||||
case nameof(CVOrderBookRowVM.order_key): result = Columns.OrderKey; break;
|
||||
case nameof(CVOrderBookRowVM.parent_order_key): result = Columns.ParentKey; break;
|
||||
default: result = default; return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void _add_items(IList prop_names) {
|
||||
foreach (string pn in prop_names) {
|
||||
if (try_parse(pn, out Columns c))
|
||||
Add(c);
|
||||
}
|
||||
}
|
||||
|
||||
void _remove_items(IList prop_names) {
|
||||
foreach (string pn in prop_names) {
|
||||
if (try_parse(pn, out Columns c))
|
||||
Remove(c);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,7 +120,7 @@ namespace mbts.spark {
|
||||
}
|
||||
return orders_and_securities;
|
||||
})
|
||||
.ObserveOnDispatcher()
|
||||
.OnDispatcher()
|
||||
.Subscribe(order_security_pairs => {
|
||||
using (_visible_orders.defer_refresh())
|
||||
{
|
||||
|
||||
@@ -20,39 +20,28 @@
|
||||
<ResourceDictionary Source="/view/Controls/DataGridEx/Filters/Views/FilterTemplates.xaml"/>
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
<uspcv:TickDisplayConverter x:Key="TickDisplayConverter"/>
|
||||
<uspcv:OrderSubTypeDisplayTextConverter x:Key="OrderSubTypeDisplayTextConverter" />
|
||||
<uspcv:OrderSubTypeDisplayTextConverter x:Key="OrderSubTypeLongDisplayTextConverter" UseLongName="True" />
|
||||
<DataTemplate x:Key="{x:Static sp:OrderBookView.SubTypeFilterTemplateKey}">
|
||||
<c:ListBoxEx
|
||||
ItemsSource="{Binding items}"
|
||||
Selection="{Binding selected_items}"
|
||||
SelectionMode="Multiple"
|
||||
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}">
|
||||
<ListBox.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource OrderSubTypeLongDisplayTextConverter}}" />
|
||||
</DataTemplate>
|
||||
</ListBox.ItemTemplate>
|
||||
</c:ListBoxEx>
|
||||
<usp:FilterContentControl
|
||||
ApplyCommandParameter="{Binding}"
|
||||
ApplyCommand="{x:Static c:DataGridFilterProperties.CommitCommand}"
|
||||
ClearCommandParameter="{Binding}"
|
||||
ClearCommand="{x:Static c:DataGridFilterProperties.ClearCommand}"
|
||||
>
|
||||
<c:ListBoxEx
|
||||
ItemsSource="{Binding items}"
|
||||
Selection="{Binding selected_items}"
|
||||
SelectionMode="Multiple"
|
||||
ItemContainerStyle="{StaticResource ListBoxItemCheckBoxStyle}"
|
||||
>
|
||||
<c:ListBoxEx.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Text="{Binding Converter={StaticResource OrderSubTypeLongDisplayTextConverter}}"/>
|
||||
</DataTemplate>
|
||||
</c:ListBoxEx.ItemTemplate>
|
||||
</c:ListBoxEx>
|
||||
</usp:FilterContentControl>
|
||||
</DataTemplate>
|
||||
<Style x:Key="SideColorCodedTextElement" TargetType="TextBlock">
|
||||
<Setter Property="Foreground" Value="{uc:BTSColor MarketData.bid}"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding side}" Value="Sell">
|
||||
<Setter Property="Foreground" Value="{uc:BTSColor MarketData.ask}"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
<Style x:Key="OrderTypeElement" TargetType="usp:OrderTypeItem">
|
||||
<Setter Property="OrderType" Value="{Binding order_type}"/>
|
||||
<Setter Property="Metadata" Value="{Binding order_metadata}"/>
|
||||
</Style>
|
||||
<Style x:Key="RightAlignedColumn" TargetType="{x:Type TextBlock}">
|
||||
<Setter Property="TextAlignment" Value="Right" />
|
||||
</Style>
|
||||
<Style x:Key="RightAlignedSideColorCodedTextElement" TargetType="{x:Type TextBlock}" BasedOn="{StaticResource SideColorCodedTextElement}">
|
||||
<Setter Property="TextAlignment" Value="Right" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="{x:Static sp:OrderBookView.RowStyleKey}" TargetType="ListBoxItem" BasedOn="{StaticResource DIYGridRowStyle}">
|
||||
<Style.Triggers>
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace mbts.spark {
|
||||
readonly Property<ushort> _bid_size, _ask_size;
|
||||
|
||||
public BBOVM(IObservable<SecurityMD> security_md)
|
||||
: this(security_md.Select(smd => smd.security).ObserveOnDispatcher(), new SecurityMDPump(security_md.Select(smd => smd.md))) { }
|
||||
: this(security_md.Select(smd => smd.security).OnDispatcher(), new SecurityMDPump(security_md.Select(smd => smd.md))) { }
|
||||
|
||||
public BBOVM(IObservable<SecurityImmutable> security, ISecurityMDPump md_pump) {
|
||||
_security = backing(nameof(security), security, SecurityDataHelper.Defaults.SecurityImmutable);
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace mbts.spark.utilities
|
||||
private class ColumnContainer : IDisposable {
|
||||
public ColumnContainer(TColumn column, IObservable<bool> is_selected, GridRowViewModelBase<TColumn> owner) {
|
||||
this.column = column;
|
||||
_dispose = is_selected.ObserveOnDispatcher().Subscribe(selected => {
|
||||
_dispose = is_selected.OnDispatcher().Subscribe(selected => {
|
||||
if (selected) owner._selected_columns.Add(column);
|
||||
else owner._selected_columns.Remove(column);
|
||||
});
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace mbts.spark.utilities {
|
||||
_account_map = new Dictionary<string, uint>();
|
||||
|
||||
var admin_data_sequence = _admin_model.on_admin_data
|
||||
.ObserveOnDispatcher()
|
||||
.OnDispatcher()
|
||||
.Subscribe(admin_data => {
|
||||
List<string> account_names = new List<string>();
|
||||
List<string> user_names = new List<string>();
|
||||
|
||||
@@ -66,7 +66,7 @@ namespace mbts.ui.utility {
|
||||
IDisposable _sub;
|
||||
|
||||
public AnonymousRXCollection(IObservable<TData> source, Action<IRXCollection<T>, TData> on_data) =>
|
||||
_sub = source.ObserveOnDispatcher().Subscribe(d => on_data(this, d));
|
||||
_sub = source.OnDispatcher().Subscribe(d => on_data(this, d));
|
||||
|
||||
public void Dispose() => Disposable.dispose(ref _sub);
|
||||
}
|
||||
@@ -78,7 +78,7 @@ namespace mbts.ui.utility {
|
||||
|
||||
internal TableUpdateCollection(IObservable<TableUpdate<T, TKey>> source, Func<T, bool> collection_filter) {
|
||||
_collection_filter = collection_filter;
|
||||
_sub = source.ObserveOnDispatcher().Subscribe(u => {
|
||||
_sub = source.OnDispatcher().Subscribe(u => {
|
||||
_raw_state = u.state;
|
||||
foreach (T val in u.removed.Where(_collection_filter)) Remove(val);
|
||||
foreach (T val in u.added.Where(_collection_filter)) Add(val);
|
||||
@@ -96,7 +96,7 @@ namespace mbts.ui.utility {
|
||||
|
||||
internal TableUpdateTransformCollection(IObservable<ITableUpdateTransform<T, TKey>> source, Func<T, bool> collection_filter) {
|
||||
_collection_filter = collection_filter;
|
||||
_sub = source.ObserveOnDispatcher().Subscribe(u => {
|
||||
_sub = source.OnDispatcher().Subscribe(u => {
|
||||
_raw_state = u.state;
|
||||
foreach (T val in u.removed.Where(_collection_filter)) Remove(val);
|
||||
foreach (T val in u.added.Where(_collection_filter)) Add(val);
|
||||
@@ -111,7 +111,7 @@ namespace mbts.ui.utility {
|
||||
IDisposable _sub;
|
||||
|
||||
internal IncrementalUpdateCollection(IObservable<IIncrementalUpdate<T, TKey>> source) {
|
||||
_sub = source.ObserveOnDispatcher().Subscribe(u => {
|
||||
_sub = source.OnDispatcher().Subscribe(u => {
|
||||
foreach (T val in u.removed.Values)
|
||||
Remove(val);
|
||||
foreach (T val in u.added.Values)
|
||||
|
||||
@@ -44,18 +44,16 @@ namespace bts.api.core {
|
||||
public class TableUpdate<T, TKey> where TKey : IEquatable<TKey> {
|
||||
|
||||
readonly Dictionary<TKey, T> _state;
|
||||
readonly IReadOnlyList<T> _added;
|
||||
readonly IReadOnlyList<T> _removed;
|
||||
|
||||
internal TableUpdate(Dictionary<TKey, T> init, IReadOnlyList<T> added, IReadOnlyList<T> removed) {
|
||||
_state = init;
|
||||
_added = added;
|
||||
_removed = removed;
|
||||
this.added = added;
|
||||
this.removed = removed;
|
||||
}
|
||||
|
||||
public IReadOnlyDictionary<TKey, T> state { get { return _state; } }
|
||||
public IReadOnlyList<T> added { get { return _added; } }
|
||||
public IReadOnlyList<T> removed { get { return _removed; } }
|
||||
public IReadOnlyList<T> added { get; }
|
||||
public IReadOnlyList<T> removed { get; }
|
||||
|
||||
internal TableUpdate<T, TKey> update(Update<T, TKey> update) =>
|
||||
this.update(update.removed, update.added, update.key);
|
||||
@@ -70,6 +68,10 @@ namespace bts.api.core {
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
internal TableUpdate<T, TKey> as_snapshot() {
|
||||
return new TableUpdate<T, TKey>(_state, _state.Values.ToList(), new T[0]);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ObservableUpdateExtensions {
|
||||
@@ -102,5 +104,22 @@ namespace bts.api.core {
|
||||
source.Scan(
|
||||
new TableUpdate<T,TKey>(new Dictionary<TKey, T>(), new List<T>(), new List<T>()),
|
||||
(table, update) => table.update(update));
|
||||
|
||||
public static IObservable<TableUpdate<T,TKey>> Cache<T, TKey>(this IObservable<TableUpdate<T, TKey>> source) where TKey : IEquatable<TKey> {
|
||||
var inner = source.Replay(1).RefCount();
|
||||
return Observable.Create<TableUpdate<T,TKey>>(o => {
|
||||
bool first = true;
|
||||
return inner.Subscribe(upd => {
|
||||
if (first) {
|
||||
o.OnNext(upd.as_snapshot());
|
||||
first = false;
|
||||
}
|
||||
else {
|
||||
o.OnNext(upd);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -30,13 +30,12 @@ namespace bts.log {
|
||||
Persist,
|
||||
User,
|
||||
OrderMan,
|
||||
Latency,
|
||||
Assert,
|
||||
Engine,
|
||||
Ui,
|
||||
ExSim,
|
||||
Mercury,
|
||||
Testing
|
||||
Testing
|
||||
// Please keep this list in sync with logging.h/logging.cpp
|
||||
}
|
||||
}
|
||||
@@ -121,14 +120,4 @@ namespace mbts
|
||||
}
|
||||
public static event Action<string, object> OnSystemCrashing;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -191,12 +191,7 @@ void ConfigurationState::initialize_builtins()
|
||||
}
|
||||
|
||||
// Version string
|
||||
std::string install_path = os::get_install_path();
|
||||
std::string app_version_file = install_path + "/edge.version";
|
||||
if (!boost::filesystem::exists(app_version_file))
|
||||
{
|
||||
app_version_file = install_path + "/blue2.version";
|
||||
}
|
||||
std::string app_version_file = os::get_install_path() + "/spark.version";
|
||||
if (boost::filesystem::exists(app_version_file))
|
||||
{
|
||||
std::ifstream input(app_version_file.c_str(), std::ifstream::in);
|
||||
|
||||
@@ -79,6 +79,11 @@ message JetFIX {
|
||||
optional int32 udf_support_indicator = 340 [default=1];
|
||||
optional bool use_cme_secure_logon = 341 [default=true];
|
||||
optional bool auto_cert_mode = 342 [default=false];
|
||||
optional int32 price_encoding_precision = 343 [default=7];
|
||||
optional uint32 max_order_msgs_per_window = 344 [default=166]; // At the CME 166 ~= 500 / 3
|
||||
optional uint32 window_duration_msecs = 345 [default=1000]; // leave it alone
|
||||
|
||||
optional uint32 max_connection_attempts = 346 [default=10];
|
||||
}
|
||||
|
||||
repeated Connection connection = 10;
|
||||
|
||||
@@ -15,8 +15,8 @@ TEST_CASE ("Security name formatting tests" , "[securities]")
|
||||
|
||||
std::smatch matches;
|
||||
REQUIRE(std::regex_match(example,matches,rx));
|
||||
for (auto const &m : matches)
|
||||
std::cout << "match: " << m.str() << "\n";
|
||||
// for (auto const &m : matches)
|
||||
// std::cout << "match: " << m.str() << "\n";
|
||||
}
|
||||
|
||||
SECTION("BS")
|
||||
@@ -26,8 +26,8 @@ TEST_CASE ("Security name formatting tests" , "[securities]")
|
||||
|
||||
std::smatch matches;
|
||||
REQUIRE(std::regex_match(example,matches,rx));
|
||||
for (auto const &m : matches)
|
||||
std::cout << "match: " << m.str() << "\n";
|
||||
// for (auto const &m : matches)
|
||||
// std::cout << "match: " << m.str() << "\n";
|
||||
}
|
||||
|
||||
SECTION("BF")
|
||||
@@ -37,8 +37,8 @@ TEST_CASE ("Security name formatting tests" , "[securities]")
|
||||
|
||||
std::smatch matches;
|
||||
REQUIRE(std::regex_match(example,matches,rx));
|
||||
for (auto const &m : matches)
|
||||
std::cout << "match: " << m.str() << "\n";
|
||||
// for (auto const &m : matches)
|
||||
// std::cout << "match: " << m.str() << "\n";
|
||||
}
|
||||
|
||||
SECTION("CF")
|
||||
@@ -48,8 +48,8 @@ TEST_CASE ("Security name formatting tests" , "[securities]")
|
||||
|
||||
std::smatch matches;
|
||||
REQUIRE(std::regex_match(example,matches,rx));
|
||||
for (auto const &m : matches)
|
||||
std::cout << "match: " << m.str() << "\n";
|
||||
// for (auto const &m : matches)
|
||||
// std::cout << "match: " << m.str() << "\n";
|
||||
}
|
||||
|
||||
SECTION("DF")
|
||||
@@ -59,8 +59,8 @@ TEST_CASE ("Security name formatting tests" , "[securities]")
|
||||
|
||||
std::smatch matches;
|
||||
REQUIRE(std::regex_match(example,matches,rx));
|
||||
for (auto const &m : matches)
|
||||
std::cout << "match: " << m.str() << "\n";
|
||||
// for (auto const &m : matches)
|
||||
// std::cout << "match: " << m.str() << "\n";
|
||||
}
|
||||
|
||||
SECTION("MP")
|
||||
@@ -70,8 +70,8 @@ TEST_CASE ("Security name formatting tests" , "[securities]")
|
||||
|
||||
std::smatch matches;
|
||||
REQUIRE(std::regex_match(example,matches,rx));
|
||||
for (auto const &m : matches)
|
||||
std::cout << "match: " << m.str() << "\n";
|
||||
// for (auto const &m : matches)
|
||||
// std::cout << "match: " << m.str() << "\n";
|
||||
}
|
||||
|
||||
SECTION("PB")
|
||||
@@ -81,8 +81,8 @@ TEST_CASE ("Security name formatting tests" , "[securities]")
|
||||
|
||||
std::smatch matches;
|
||||
REQUIRE(std::regex_match(example,matches,rx));
|
||||
for (auto const &m : matches)
|
||||
std::cout << "match: " << m.str() << "\n";
|
||||
// for (auto const &m : matches)
|
||||
// std::cout << "match: " << m.str() << "\n";
|
||||
}
|
||||
SECTION("PS")
|
||||
{
|
||||
@@ -91,8 +91,8 @@ TEST_CASE ("Security name formatting tests" , "[securities]")
|
||||
|
||||
std::smatch matches;
|
||||
REQUIRE(std::regex_match(example,matches,rx));
|
||||
for (auto const &m : matches)
|
||||
std::cout << "match: " << m.str() << "\n";
|
||||
// for (auto const &m : matches)
|
||||
// std::cout << "match: " << m.str() << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,11 +63,15 @@ Filter get_filter (LoggingConfig &cfg,
|
||||
for (auto const &sub_system : scfg.subsystem()) {
|
||||
|
||||
auto ss_it = find_key(cfg.subsystem_names,sub_system.name());
|
||||
bts_assert(ss_it != cfg.subsystem_names.end());
|
||||
if (ss_it == cfg.subsystem_names.end())
|
||||
continue;
|
||||
|
||||
auto ss = ss_it->first;
|
||||
|
||||
auto level_it = find_key(cfg.level_names,sub_system.level());
|
||||
bts_assert(level_it != cfg.level_names.end());
|
||||
if (level_it != cfg.level_names.end())
|
||||
continue;
|
||||
|
||||
auto level = level_it->first;
|
||||
|
||||
result[ss.ordinal] = level;
|
||||
|
||||
@@ -14,21 +14,16 @@ namespace cmd {
|
||||
int QuickTest::execute(std::vector<std::string> const &args) {
|
||||
using namespace double_conversion;
|
||||
|
||||
static char buffer[102];
|
||||
static char buffer1[102];
|
||||
static char buffer2[102];
|
||||
static char buffer3[102];
|
||||
int flags = DoubleToStringConverter::UNIQUE_ZERO;
|
||||
static DoubleToStringConverter converter(flags, "Infinity", "NaN", 'e', -6,
|
||||
21, 0, 0);
|
||||
auto n = std::stod(args[0], nullptr);
|
||||
auto p = std::stoi(args[1], nullptr);
|
||||
{
|
||||
double_conversion::StringBuilder builder(buffer, sizeof(buffer));
|
||||
converter.ToPrecision(n, p, &builder);
|
||||
}
|
||||
d_toa(n, buffer2, sizeof(buffer2), p);
|
||||
d_toa(n, buffer1, sizeof(buffer1), 7,p);
|
||||
d_toa(n, buffer2, sizeof(buffer2),7);
|
||||
d_toa(n, buffer3, sizeof(buffer3));
|
||||
std::cout << n << " -> " << buffer << " (" << strlen(buffer) << ") "
|
||||
std::cout << n
|
||||
<< " -> " << buffer1 << " (" << strlen(buffer1) << ") "
|
||||
<< " -> " << buffer2 << " (" << strlen(buffer2) << ") "
|
||||
<< " -> " << buffer3 << " (" << strlen(buffer3) << ") \n";
|
||||
return 0;
|
||||
|
||||
@@ -8,107 +8,100 @@ namespace bts {
|
||||
namespace jetfix {
|
||||
namespace fix {
|
||||
|
||||
typedef StringField<8> BeginString;
|
||||
typedef IntField<9> BodyLength;
|
||||
typedef StringField<35> MsgType;
|
||||
typedef IntField<34> MsgSeqNum;
|
||||
typedef IntField<369> LastSeqNumProcessed;
|
||||
typedef StringField<49> SenderCompID;
|
||||
typedef StringField<50> SenderSubID ;
|
||||
typedef TimeField<52> SendingTime;
|
||||
typedef TimeField<122> OrigSendingTime;
|
||||
typedef StringField<56> TargetCompId;
|
||||
typedef StringField<57> TargetSubID;
|
||||
typedef StringField<142> SenderLocationID;
|
||||
typedef StringField<37> OrderID;
|
||||
typedef StringField<117> QuoteID;
|
||||
typedef IntField<117> QuoteIDInt;
|
||||
typedef IntField<204> CustomerOrFirm;
|
||||
typedef IntField<296> NoQuoteSets;
|
||||
typedef StringField<302> QuoteSetID;
|
||||
typedef StringField<307> UnderlyingSecurityDesc;
|
||||
typedef IntField<304> TotQuoteEntries;
|
||||
typedef IntField<295> NoQuoteEntries;
|
||||
typedef StringField<304> TotQuoteEntriesStr;
|
||||
typedef StringField<295> NoQuoteEntriesStr;
|
||||
|
||||
typedef BoolField<1028> ManualOrderIndicator;
|
||||
typedef StringField<7928> SelfMatchPreventionID;
|
||||
typedef CharField<9702> CtiCode;
|
||||
typedef StringField<9771> MMAccount;
|
||||
typedef BoolField<123> GapFillFlag;
|
||||
typedef BoolField<43> PossDupFlag;
|
||||
typedef BoolField<9768> InFlightMitigation;
|
||||
typedef CharField<150> ExecType;
|
||||
typedef CharField<21> HandlInst;
|
||||
typedef CharField<40> OrdType;
|
||||
typedef CharField<59> TimeInForce;
|
||||
typedef CharField<39> OrdStatus;
|
||||
typedef StringField<41> OrigClOrdID;
|
||||
typedef StringField<9717> CorrelationClOrdID;
|
||||
typedef IntField<102> CXlRejReason;
|
||||
|
||||
|
||||
typedef FloatField<132> BidPx;
|
||||
typedef IntField<134> BidSize;
|
||||
typedef StringField<132> BidPxStr;
|
||||
typedef StringField<134> BidSizeStr;
|
||||
|
||||
typedef FloatField<133> OfferPx;
|
||||
typedef IntField<135> OfferSize;
|
||||
typedef StringField<133> OfferPxStr;
|
||||
typedef StringField<135> OfferSizeStr;
|
||||
|
||||
typedef StringField<299> QuoteEntryID;
|
||||
typedef IntField<299> QuoteEntryIDInt;
|
||||
typedef StringField<55> Symbol;
|
||||
typedef StringField<167> SecurityType;
|
||||
typedef StringField<107> SecurityDesc;
|
||||
typedef StringField<48> SecurityID;
|
||||
typedef CharField<22> SecurityIDSource;
|
||||
typedef TimeField<60> TransactTime;
|
||||
typedef TimeField<5979> RequestTime;
|
||||
typedef StringField<60> TransactTimeStr;
|
||||
typedef StringField<143> TargetLocationID;
|
||||
typedef StringField<142> SenderLocationID;
|
||||
typedef StringField<142> SenderLocationID;
|
||||
typedef IntField<10> Checksum;
|
||||
typedef IntField<98> EncryptMethod;
|
||||
typedef CharField<39> OrdStatus;
|
||||
typedef CharField<40> OrdType;
|
||||
typedef CharField<47> Rule80a;
|
||||
typedef CharField<54> Side;
|
||||
typedef CharField<59> TimeInForce;
|
||||
typedef CharField<9702> CtiCode;
|
||||
typedef CharField<9775> UnsolicitedCancelType;
|
||||
typedef FloatField<132> BidPx;
|
||||
typedef FloatField<133> OfferPx;
|
||||
typedef FloatField<151> LeavesQty;
|
||||
typedef FloatField<31> LastPx;
|
||||
typedef FloatField<6> AvgPx;
|
||||
typedef IntField<102> CXlRejReason;
|
||||
typedef IntField<108> HeartBtInt;
|
||||
typedef IntField<10> Checksum;
|
||||
typedef IntField<117> QuoteIDInt;
|
||||
typedef IntField<134> BidSize;
|
||||
typedef IntField<135> OfferSize;
|
||||
typedef IntField<14> CumQty;
|
||||
typedef IntField<16> EndSeqNo;
|
||||
typedef IntField<204> CustomerOrFirm;
|
||||
typedef IntField<295> NoQuoteEntries;
|
||||
typedef IntField<296> NoQuoteSets;
|
||||
typedef IntField<297> QuoteAckStatus;
|
||||
typedef IntField<298> QuoteCancelType;
|
||||
typedef IntField<299> QuoteEntryIDInt;
|
||||
typedef IntField<300> QuoteRejectReason;
|
||||
typedef IntField<304> TotQuoteEntries;
|
||||
typedef IntField<32> LastShares;
|
||||
typedef IntField<34> MsgSeqNum;
|
||||
typedef IntField<368> QuoteEntryRejectReason;
|
||||
typedef IntField<369> LastSeqNumProcessed;
|
||||
typedef IntField<36> NewSeqNo;
|
||||
typedef IntField<386> NoTradingSessions;
|
||||
typedef IntField<442> MultiLegReportingType;
|
||||
typedef IntField<7> BeginSeqNo;
|
||||
typedef IntField<95> RawDataLength;
|
||||
typedef IntField<98> EncryptMethod;
|
||||
typedef IntField<9> BodyLength;
|
||||
typedef StringField<107> SecurityDesc;
|
||||
typedef StringField<109> ClientID;
|
||||
typedef StringField<112> TestReqId;
|
||||
typedef StringField<117> QuoteID;
|
||||
typedef StringField<11> ClOrdID;
|
||||
typedef StringField<132> BidPxStr;
|
||||
typedef StringField<133> OfferPxStr;
|
||||
typedef StringField<134> BidSizeStr;
|
||||
typedef StringField<135> OfferSizeStr;
|
||||
typedef StringField<142> SenderLocationID;
|
||||
typedef StringField<143> TargetLocationID;
|
||||
typedef StringField<1603> ApplicationSystemName;
|
||||
typedef StringField<1604> ApplicationSystemVersion;
|
||||
typedef StringField<1605> ApplicationSystemVendor;
|
||||
typedef IntField<95> RawDataLength;
|
||||
typedef StringField<96> RawData;
|
||||
typedef StringField<109> ClientID;
|
||||
typedef IntField<7> BeginSeqNo;
|
||||
typedef IntField<16> EndSeqNo;
|
||||
typedef StringField<112> TestReqId;
|
||||
typedef BoolField<123> GapFillFlag;
|
||||
typedef IntField<36> NewSeqNo;
|
||||
typedef BoolField<43> PossDupFlag;
|
||||
typedef IntField<297> QuoteAckStatus;
|
||||
typedef CharField<9775> UnsolicitedCancelType;
|
||||
typedef IntField<300> QuoteRejectReason;
|
||||
typedef StringField<58> Text;
|
||||
typedef IntField<368> QuoteEntryRejectReason;
|
||||
typedef IntField<298> QuoteCancelType;
|
||||
typedef StringField<1> Account;
|
||||
typedef CharField<150> ExecType;
|
||||
typedef StringField<11> ClOrdID;
|
||||
typedef CharField<54> Side;
|
||||
typedef FloatField<31> LastPx;
|
||||
typedef FloatField<6> AvgPx;
|
||||
typedef IntField<32> LastShares;
|
||||
typedef IntField<14> CumQty;
|
||||
typedef StringField<527> SecondaryExecID;
|
||||
typedef StringField<76> ExecBroker;
|
||||
typedef StringField<167> SecurityType;
|
||||
typedef StringField<17> ExecID;
|
||||
typedef StringField<19> ExecRefID;
|
||||
typedef FloatField<151> LeavesQty;
|
||||
typedef IntField<442> MultiLegReportingType;
|
||||
typedef IntField<386> NoTradingSessions;
|
||||
typedef StringField<1> Account;
|
||||
typedef StringField<295> NoQuoteEntriesStr;
|
||||
typedef StringField<299> QuoteEntryID;
|
||||
typedef StringField<302> QuoteSetID;
|
||||
typedef StringField<304> TotQuoteEntriesStr;
|
||||
typedef StringField<307> UnderlyingSecurityDesc;
|
||||
typedef StringField<336> TradingSessionID;
|
||||
typedef StringField<35> MsgType;
|
||||
typedef StringField<37> OrderID;
|
||||
typedef StringField<41> OrigClOrdID;
|
||||
typedef StringField<440> ClearingAccount;
|
||||
typedef CharField<47> Rule80a;
|
||||
typedef StringField<48> SecurityID;
|
||||
typedef StringField<49> SenderCompID;
|
||||
typedef StringField<50> SenderSubID ;
|
||||
typedef StringField<527> SecondaryExecID;
|
||||
typedef StringField<55> Symbol;
|
||||
typedef StringField<56> TargetCompId;
|
||||
typedef StringField<57> TargetSubID;
|
||||
typedef StringField<58> Text;
|
||||
typedef StringField<60> TransactTimeStr;
|
||||
typedef StringField<76> ExecBroker;
|
||||
typedef StringField<7928> SelfMatchPreventionID;
|
||||
typedef StringField<8> BeginString;
|
||||
typedef StringField<9324> OrderOptionalData;
|
||||
typedef BoolField<9768> InFlightMitigation;
|
||||
typedef StringField<96> RawData;
|
||||
typedef StringField<9717> CorrelationClOrdID;
|
||||
typedef StringField<9771> MMAccount;
|
||||
typedef TimeField<122> OrigSendingTime;
|
||||
typedef TimeField<52> SendingTime;
|
||||
typedef TimeField<5979> RequestTime;
|
||||
typedef TimeField<60> TransactTime;
|
||||
|
||||
typedef Group<NoQuoteEntries,
|
||||
QuoteEntryID,
|
||||
|
||||
@@ -114,17 +114,17 @@ public:
|
||||
Parent::_value = atof (&(cursor.data ()[loc.first]));
|
||||
}
|
||||
|
||||
static uint32_t max_value_size ( double,int pad = 0)
|
||||
static uint32_t max_value_size ( double,int, int pad)
|
||||
{
|
||||
if (pad == 0)
|
||||
if (pad == -1)
|
||||
return 17; // 15 from FIX spec, 1 for decimal, 1 for sign
|
||||
else
|
||||
return pad;
|
||||
}
|
||||
|
||||
static std::pair<uint32_t,uint32_t> write (char*buffer,double d,int pad = 0)
|
||||
static std::pair<uint32_t,uint32_t> write (char*buffer,double d,int decimals, int pad )
|
||||
{
|
||||
auto written = d_toa (d,buffer,max_value_size (d,pad)+1,pad);
|
||||
auto written = d_toa (d,buffer,max_value_size (d,decimals, pad)+1,decimals,pad);
|
||||
return std::make_pair (written,sum_bytes (buffer,written));
|
||||
}
|
||||
};
|
||||
@@ -207,3 +207,4 @@ public:
|
||||
};
|
||||
|
||||
}}
|
||||
|
||||
@@ -2,32 +2,70 @@
|
||||
|
||||
#include "lib/utils/bts_assert.h"
|
||||
|
||||
#include <memory>
|
||||
#include <double-conversion/double-conversion.h>
|
||||
#include <memory>
|
||||
|
||||
namespace bts {
|
||||
|
||||
inline int d_toa (double n,char*buffer, size_t buffer_size,int pad=0)
|
||||
{
|
||||
inline int d_toa(double n, char *buffer, size_t buffer_size, int decimal_digits=-1, int pad = -1) {
|
||||
static double_conversion::DoubleToStringConverter const &c =
|
||||
double_conversion::DoubleToStringConverter::EcmaScriptConverter ();
|
||||
double_conversion::DoubleToStringConverter::EcmaScriptConverter();
|
||||
|
||||
double_conversion::StringBuilder builder(buffer,buffer_size);
|
||||
if (pad == 0) {
|
||||
bts_assert (c.ToShortest (n,&builder));
|
||||
return builder.position ();
|
||||
} else {
|
||||
bts_assert ((int) buffer_size >= pad);
|
||||
bts_assert(buffer_size);
|
||||
|
||||
if (pad == -1) {
|
||||
// no padding, return the smallest number that contains all the
|
||||
// specified digits
|
||||
double_conversion::StringBuilder builder(buffer, buffer_size);
|
||||
|
||||
if (n < 0)
|
||||
bts_assert (c.ToPrecision (n,pad-2,&builder));
|
||||
else
|
||||
bts_assert (c.ToPrecision (n,pad-1,&builder));
|
||||
if (decimal_digits == -1 ) {
|
||||
if (c.ToShortest(n,&builder))
|
||||
return builder.position();
|
||||
}
|
||||
else {
|
||||
if(c.ToFixed(n,decimal_digits,&builder))
|
||||
return builder.position();
|
||||
}
|
||||
buffer[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
|
||||
// padding, return something with a fixed size
|
||||
|
||||
std::array<char, 128> pre_buffer;
|
||||
bts_assert((int)buffer_size > pad);
|
||||
bts_assert((int)pre_buffer.size() >= pad);
|
||||
|
||||
double_conversion::StringBuilder builder(&pre_buffer[0], pre_buffer.size());
|
||||
|
||||
auto ok = false;
|
||||
if (decimal_digits == -1)
|
||||
ok = c.ToShortest(n,&builder);
|
||||
else
|
||||
ok = c.ToFixed(n,decimal_digits,&builder);
|
||||
|
||||
if(ok) {
|
||||
auto sz = builder.position();
|
||||
if (sz < pad) {
|
||||
auto n_left_zeros = pad - sz;
|
||||
memset(&buffer[0], '0',n_left_zeros);
|
||||
memcpy(&buffer[n_left_zeros], &pre_buffer[0],sz);
|
||||
if (n < 0) {
|
||||
buffer[0] = '-';
|
||||
buffer[n_left_zeros] = '0';
|
||||
}
|
||||
buffer[pad]=0;
|
||||
return pad;
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < pad;i++)
|
||||
buffer[i] = ' ';
|
||||
buffer[pad] = 0;
|
||||
return pad;
|
||||
}
|
||||
}
|
||||
|
||||
inline int f_toa (float n,char*buffer, size_t buffer_size,int pad=0)
|
||||
{return d_toa(n,buffer,buffer_size,pad);}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,78 +1,71 @@
|
||||
#include "CoreGateway.h"
|
||||
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/utils/TimeStamp.h"
|
||||
#include "lib/utils/bts_chrono.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include "server/sparkd/jetfix/common/OutFIX.h"
|
||||
#include "server/sparkd/jetfix/connection/AsyncReader.h"
|
||||
#include "server/sparkd/jetfix/connection/IConnection.h"
|
||||
#include "server/sparkd/jetfix/gateway/ICoreGatewayObserver.h"
|
||||
#include "server/sparkd/jetfix/gateway/Session.h"
|
||||
#include "server/sparkd/jetfix/store/IFIXStore.h"
|
||||
#include "lib/logging/logging.h"
|
||||
#include "lib/utils/TimeStamp.h"
|
||||
#include "lib/utils/datetime.h"
|
||||
#include "lib/utils/bts_chrono.h"
|
||||
#include "server/sparkd/orderman/orderman_utils/orderman_common.h"
|
||||
#include "server/sparkd/tracing/bts_trace.h"
|
||||
|
||||
#include <thread>
|
||||
#include <atomic>
|
||||
#include <fstream>
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
#include <moodycamel/concurrentqueue.h>
|
||||
#include <fmt/format.h>
|
||||
#include <fstream>
|
||||
#include <moodycamel/concurrentqueue.h>
|
||||
#include <thread>
|
||||
|
||||
namespace bts {
|
||||
namespace jetfix {
|
||||
|
||||
enum class CoreGateway::State {OFF,PAUSED,CONNECTING,READY,WREADER_DISCONNECT,WSESSION_TERMINATE};
|
||||
enum class CoreGateway::State {
|
||||
OFF,
|
||||
PAUSED,
|
||||
CONNECTING,
|
||||
READY,
|
||||
WREADER_DISCONNECT,
|
||||
WSESSION_TERMINATE
|
||||
};
|
||||
|
||||
struct CoreGateway::SessionInfo {
|
||||
SessionInfo() : connection(), store(), reader(), session() {}
|
||||
|
||||
struct CoreGateway::SessionInfo
|
||||
{
|
||||
SessionInfo () : connection (), store (), reader (), session ()
|
||||
{}
|
||||
|
||||
std::unique_ptr<IConnection> connection;
|
||||
std::unique_ptr<IFIXStore> store;
|
||||
std::unique_ptr<AsyncReader> reader;
|
||||
std::unique_ptr<Session> session;
|
||||
};
|
||||
|
||||
struct CoreGateway::EventData
|
||||
{
|
||||
typedef void (CoreGateway::*Handler)(EventData const&ev);
|
||||
struct CoreGateway::EventData {
|
||||
typedef void (CoreGateway::*Handler)(EventData const &ev);
|
||||
|
||||
EventData (const EventData &src) : handler (src.handler),
|
||||
out_msgs (src.out_msgs),
|
||||
in_msg (src.in_msg),
|
||||
cid(src.cid),
|
||||
ts (src.ts)
|
||||
{}
|
||||
EventData(const EventData &src)
|
||||
: handler(src.handler), out_msgs(src.out_msgs), in_msg(src.in_msg),
|
||||
cid(src.cid), ts(src.ts) {}
|
||||
|
||||
EventData () : handler (), out_msgs (),in_msg (), cid(orderman::NullTickToTradeCorID),ts () {}
|
||||
EventData (Handler handler) : handler (handler), out_msgs (),in_msg (),cid(), ts () {}
|
||||
EventData (Handler handler,orderman::TickToTradeCorID cid, OutFIXPtr const &out_msgs) :
|
||||
handler (handler),
|
||||
out_msgs (out_msgs),
|
||||
in_msg (),
|
||||
cid(cid),
|
||||
ts ()
|
||||
{}
|
||||
EventData()
|
||||
: handler(), out_msgs(), in_msg(), cid(orderman::NullTickToTradeCorID),
|
||||
ts() {}
|
||||
EventData(Handler handler)
|
||||
: handler(handler), out_msgs(), in_msg(), cid(), ts() {}
|
||||
EventData(Handler handler, orderman::TickToTradeCorID cid,
|
||||
OutFIXPtr const &out_msgs)
|
||||
: handler(handler), out_msgs(out_msgs), in_msg(), cid(cid), ts() {}
|
||||
|
||||
EventData (Handler handler,InMsgBuffer const&in_msg) :
|
||||
handler (handler), out_msgs (),
|
||||
in_msg (in_msg),
|
||||
cid(orderman::NullTickToTradeCorID),
|
||||
ts ()
|
||||
{}
|
||||
EventData(Handler handler, InMsgBuffer const &in_msg)
|
||||
: handler(handler), out_msgs(), in_msg(in_msg),
|
||||
cid(orderman::NullTickToTradeCorID), ts() {}
|
||||
|
||||
EventData (Handler handler,std::shared_ptr<std::string> const&ts)
|
||||
: handler (handler),
|
||||
out_msgs (),in_msg (),
|
||||
cid(orderman::NullTickToTradeCorID),
|
||||
ts (ts)
|
||||
{}
|
||||
|
||||
EventData& operator=(EventData&src)
|
||||
{
|
||||
EventData(Handler handler, std::shared_ptr<std::string> const &ts)
|
||||
: handler(handler), out_msgs(), in_msg(),
|
||||
cid(orderman::NullTickToTradeCorID), ts(ts) {}
|
||||
|
||||
EventData &operator=(EventData &src) {
|
||||
handler = src.handler;
|
||||
out_msgs = src.out_msgs;
|
||||
in_msg = src.in_msg;
|
||||
@@ -80,9 +73,8 @@ struct CoreGateway::EventData
|
||||
ts = src.ts;
|
||||
return *this;
|
||||
}
|
||||
|
||||
EventData& operator=(EventData&&src)
|
||||
{
|
||||
|
||||
EventData &operator=(EventData &&src) {
|
||||
handler = std::move(src.handler);
|
||||
out_msgs = std::move(src.out_msgs);
|
||||
in_msg = std::move(src.in_msg);
|
||||
@@ -97,36 +89,24 @@ struct CoreGateway::EventData
|
||||
std::shared_ptr<std::string> ts;
|
||||
};
|
||||
|
||||
struct CoreGateway::PImpl
|
||||
{
|
||||
PImpl(config::JetFIX::Connection const &cfg,
|
||||
ICoreGatewayObserver &observer,
|
||||
struct CoreGateway::PImpl {
|
||||
PImpl(config::JetFIX::Connection const &cfg, ICoreGatewayObserver &observer,
|
||||
ConnectionFactory const &conn_factory,
|
||||
StoreFactory const &store_factory) :
|
||||
cfg (cfg),
|
||||
observer (observer),
|
||||
make_connection (conn_factory),
|
||||
make_store (store_factory),
|
||||
state (State::OFF),
|
||||
start_of_pause (),
|
||||
session_info (),
|
||||
event_queue (),
|
||||
timer_thread (),
|
||||
destroy (false),
|
||||
is_on (false),
|
||||
has_replayed (false),
|
||||
send_batch (),
|
||||
recv_batch ()
|
||||
{
|
||||
send_batch.reserve (1024);
|
||||
recv_batch.reserve (1024);
|
||||
StoreFactory const &store_factory)
|
||||
: cfg(cfg), observer(observer), make_connection(conn_factory),
|
||||
make_store(store_factory), state(State::OFF), start_of_pause(),
|
||||
session_info(), event_queue(), timer_thread(), destroy(false),
|
||||
is_on(false), has_replayed(false), send_batch(), recv_batch(),
|
||||
connection_attempts(0) {
|
||||
send_batch.reserve(1024);
|
||||
recv_batch.reserve(1024);
|
||||
}
|
||||
|
||||
const config::JetFIX::Connection cfg;
|
||||
ICoreGatewayObserver &observer;
|
||||
ConnectionFactory const make_connection;
|
||||
StoreFactory const make_store;
|
||||
|
||||
|
||||
State state;
|
||||
datetime::PTime start_of_pause;
|
||||
std::unique_ptr<SessionInfo> session_info;
|
||||
@@ -137,152 +117,150 @@ struct CoreGateway::PImpl
|
||||
bool has_replayed;
|
||||
std::vector<EventData> send_batch;
|
||||
std::vector<EventData> recv_batch;
|
||||
uint32_t connection_attempts;
|
||||
};
|
||||
|
||||
CoreGateway::CoreGateway(config::JetFIX::Connection const &cfg,
|
||||
ICoreGatewayObserver &observer,
|
||||
ConnectionFactory const &conn_factory,
|
||||
StoreFactory const &store_factory)
|
||||
: me (new PImpl (cfg,observer,conn_factory,store_factory))
|
||||
{
|
||||
me->timer_thread = std::thread (std::bind (&CoreGateway::call_time,this));
|
||||
: me(new PImpl(cfg, observer, conn_factory, store_factory)) {
|
||||
me->timer_thread = std::thread(std::bind(&CoreGateway::call_time, this));
|
||||
}
|
||||
|
||||
CoreGateway::~CoreGateway ()
|
||||
{
|
||||
stop ();
|
||||
CoreGateway::~CoreGateway() {
|
||||
stop();
|
||||
while (me->state != State::OFF) {
|
||||
dispatch_events ();
|
||||
dispatch_events();
|
||||
std::this_thread::sleep_for(100ms);
|
||||
}
|
||||
me->destroy = true;
|
||||
me->timer_thread.join ();
|
||||
me->timer_thread.join();
|
||||
}
|
||||
|
||||
void CoreGateway::send (orderman::TickToTradeCorID cid, OutFIXPtr const &fix)
|
||||
{
|
||||
tracepoint (bts_trace,trace_order,(char*) "CoreGateway.send", 0,cid);
|
||||
me->event_queue.enqueue (EventData (&CoreGateway::sync_send,cid,fix));
|
||||
void CoreGateway::send(orderman::TickToTradeCorID cid, OutFIXPtr const &fix) {
|
||||
tracepoint(bts_trace, trace_order, (char *)"CoreGateway.send", 0, cid);
|
||||
me->event_queue.enqueue(EventData(&CoreGateway::sync_send, cid, fix));
|
||||
}
|
||||
|
||||
void CoreGateway::send_hb ()
|
||||
{me->event_queue.enqueue (EventData (&CoreGateway::sync_send_hb));}
|
||||
void CoreGateway::send_hb() {
|
||||
me->event_queue.enqueue(EventData(&CoreGateway::sync_send_hb));
|
||||
}
|
||||
|
||||
void CoreGateway::send_test_request ()
|
||||
{me->event_queue.enqueue (EventData (&CoreGateway::sync_send_test_request));}
|
||||
void CoreGateway::send_test_request() {
|
||||
me->event_queue.enqueue(EventData(&CoreGateway::sync_send_test_request));
|
||||
}
|
||||
|
||||
void CoreGateway::start ()
|
||||
{me->event_queue.enqueue (EventData (&CoreGateway::sync_start));}
|
||||
void CoreGateway::start() {
|
||||
me->event_queue.enqueue(EventData(&CoreGateway::sync_start));
|
||||
}
|
||||
|
||||
void CoreGateway::stop ()
|
||||
{me->event_queue.enqueue (EventData (&CoreGateway::sync_stop));}
|
||||
void CoreGateway::stop() {
|
||||
me->event_queue.enqueue(EventData(&CoreGateway::sync_stop));
|
||||
}
|
||||
|
||||
void CoreGateway::session_ready ()
|
||||
{me->event_queue.enqueue (EventData (&CoreGateway::sync_session_ready)); }
|
||||
void CoreGateway::session_ready() {
|
||||
me->event_queue.enqueue(EventData(&CoreGateway::sync_session_ready));
|
||||
}
|
||||
|
||||
void CoreGateway::session_terminated ()
|
||||
{me->event_queue.enqueue (EventData (&CoreGateway::sync_session_terminated));}
|
||||
void CoreGateway::session_terminated() {
|
||||
me->event_queue.enqueue(EventData(&CoreGateway::sync_session_terminated));
|
||||
}
|
||||
|
||||
void CoreGateway::reader_disconnected ()
|
||||
{me->event_queue.enqueue (EventData (&CoreGateway::sync_reader_disconnected));}
|
||||
void CoreGateway::reader_disconnected() {
|
||||
me->event_queue.enqueue(EventData(&CoreGateway::sync_reader_disconnected));
|
||||
}
|
||||
|
||||
void CoreGateway::in_msg_raw (InMsgBuffer const&msg)
|
||||
{me->event_queue.enqueue (EventData (&CoreGateway::sync_in_msg_raw,msg));}
|
||||
void CoreGateway::in_msg_raw(InMsgBuffer const &msg) {
|
||||
me->event_queue.enqueue(EventData(&CoreGateway::sync_in_msg_raw, msg));
|
||||
}
|
||||
|
||||
void CoreGateway::in_msg_sequenced (InMsgBuffer const&msg)
|
||||
{me->event_queue.enqueue (EventData (&CoreGateway::sync_in_msg_sequenced,msg));}
|
||||
void CoreGateway::in_msg_sequenced(InMsgBuffer const &msg) {
|
||||
me->event_queue.enqueue(EventData(&CoreGateway::sync_in_msg_sequenced, msg));
|
||||
}
|
||||
|
||||
void CoreGateway::dispatch_events ()
|
||||
{
|
||||
void CoreGateway::dispatch_events() {
|
||||
EventData ev;
|
||||
utils::TimeStamp cid;
|
||||
// we prioratize sends over receives
|
||||
while (me->event_queue.try_dequeue (ev)) {
|
||||
while (me->event_queue.try_dequeue(ev)) {
|
||||
if (ev.out_msgs) {
|
||||
cid = ev.out_msgs->get_ts (jetfix::OutFIX::MD_WRITE_TS);
|
||||
me->send_batch.push_back (ev);
|
||||
}
|
||||
else
|
||||
me->recv_batch.push_back (ev);
|
||||
cid = ev.out_msgs->get_ts(jetfix::OutFIX::MD_WRITE_TS);
|
||||
me->send_batch.push_back(ev);
|
||||
} else
|
||||
me->recv_batch.push_back(ev);
|
||||
}
|
||||
|
||||
if (me->send_batch.size () || me->recv_batch.size ()) {
|
||||
if (me->send_batch.size() || me->recv_batch.size()) {
|
||||
for (auto &ev : me->send_batch)
|
||||
(this->*ev.handler) (ev);
|
||||
(this->*ev.handler)(ev);
|
||||
for (auto &ev : me->recv_batch)
|
||||
(this->*ev.handler) (ev);
|
||||
me->send_batch.clear ();
|
||||
me->recv_batch.clear ();
|
||||
(this->*ev.handler)(ev);
|
||||
me->send_batch.clear();
|
||||
me->recv_batch.clear();
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGateway::call_time ()
|
||||
{
|
||||
os::set_thread_name(fmt::format("{0}_timer",me->cfg.name()).c_str());
|
||||
void CoreGateway::call_time() {
|
||||
os::set_thread_name(fmt::format("{0}_timer", me->cfg.name()).c_str());
|
||||
while (!me->destroy) {
|
||||
std::this_thread::sleep_for(10ms);
|
||||
auto ts = std::make_shared<std::string> (
|
||||
datetime::FIX_timestamp (datetime::now ()));
|
||||
me->event_queue.enqueue (EventData (&CoreGateway::sync_time,ts));
|
||||
auto ts = std::make_shared<std::string>(
|
||||
datetime::FIX_timestamp(datetime::now()));
|
||||
me->event_queue.enqueue(EventData(&CoreGateway::sync_time, ts));
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGateway::sync_start (EventData const &)
|
||||
{
|
||||
switch (me->state)
|
||||
{
|
||||
case State::OFF:
|
||||
{
|
||||
void CoreGateway::sync_start(EventData const &) {
|
||||
switch (me->state) {
|
||||
case State::OFF: {
|
||||
me->is_on = true;
|
||||
pause_or_shutdown ();
|
||||
pause_or_shutdown();
|
||||
break;
|
||||
}
|
||||
case State::PAUSED:
|
||||
case State::CONNECTING:
|
||||
case State::READY:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
break;
|
||||
case State::WREADER_DISCONNECT:
|
||||
case State::WSESSION_TERMINATE:
|
||||
bts_assert (!me->is_on);
|
||||
bts_assert(!me->is_on);
|
||||
me->is_on = true;
|
||||
break;
|
||||
default:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGateway::sync_stop (EventData const&)
|
||||
{
|
||||
switch (me->state)
|
||||
{
|
||||
void CoreGateway::sync_stop(EventData const &) {
|
||||
switch (me->state) {
|
||||
case State::OFF:
|
||||
break;
|
||||
case State::PAUSED:
|
||||
me->is_on = false;
|
||||
transition (State::OFF);
|
||||
transition(State::OFF);
|
||||
break;
|
||||
case State::CONNECTING:
|
||||
case State::READY:
|
||||
me->is_on = false;
|
||||
me->session_info->session->shutdown ();
|
||||
me->session_info->session->shutdown();
|
||||
break;
|
||||
case State::WREADER_DISCONNECT:
|
||||
case State::WSESSION_TERMINATE:
|
||||
me->is_on = false;
|
||||
break;
|
||||
default:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGateway::sync_send (EventData const &ev)
|
||||
{
|
||||
tracepoint (bts_trace,trace_order,(char*) "CoreGateway.sync_send", 0,ev.cid);
|
||||
switch (me->state)
|
||||
{
|
||||
void CoreGateway::sync_send(EventData const &ev) {
|
||||
tracepoint(bts_trace, trace_order, (char *)"CoreGateway.sync_send", 0,
|
||||
ev.cid);
|
||||
switch (me->state) {
|
||||
case State::OFF:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
break;
|
||||
case State::PAUSED:
|
||||
case State::CONNECTING:
|
||||
@@ -290,19 +268,17 @@ void CoreGateway::sync_send (EventData const &ev)
|
||||
case State::WSESSION_TERMINATE:
|
||||
break;
|
||||
case State::READY:
|
||||
me->session_info->session->send (ev.cid,ev.out_msgs);
|
||||
me->session_info->session->send(ev.cid, ev.out_msgs);
|
||||
break;
|
||||
default:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGateway::sync_send_hb (EventData const &)
|
||||
{
|
||||
switch (me->state)
|
||||
{
|
||||
void CoreGateway::sync_send_hb(EventData const &) {
|
||||
switch (me->state) {
|
||||
case State::OFF:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
break;
|
||||
case State::PAUSED:
|
||||
case State::CONNECTING:
|
||||
@@ -310,19 +286,17 @@ void CoreGateway::sync_send_hb (EventData const &)
|
||||
case State::WSESSION_TERMINATE:
|
||||
break;
|
||||
case State::READY:
|
||||
me->session_info->session->send_hb ();
|
||||
me->session_info->session->send_hb();
|
||||
break;
|
||||
default:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGateway::sync_send_test_request (EventData const &)
|
||||
{
|
||||
switch (me->state)
|
||||
{
|
||||
void CoreGateway::sync_send_test_request(EventData const &) {
|
||||
switch (me->state) {
|
||||
case State::OFF:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
break;
|
||||
case State::PAUSED:
|
||||
case State::CONNECTING:
|
||||
@@ -330,256 +304,252 @@ void CoreGateway::sync_send_test_request (EventData const &)
|
||||
case State::WSESSION_TERMINATE:
|
||||
break;
|
||||
case State::READY:
|
||||
me->session_info->session->send_test_request ();
|
||||
me->session_info->session->send_test_request();
|
||||
break;
|
||||
default:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGateway::sync_session_ready (EventData const&)
|
||||
{
|
||||
switch (me->state)
|
||||
{
|
||||
void CoreGateway::sync_session_ready(EventData const &) {
|
||||
switch (me->state) {
|
||||
case State::OFF:
|
||||
case State::PAUSED:
|
||||
case State::READY:
|
||||
case State::WREADER_DISCONNECT:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
break;
|
||||
case State::WSESSION_TERMINATE:
|
||||
break;
|
||||
case State::CONNECTING:
|
||||
transition (State::READY);
|
||||
me->observer.ready ();
|
||||
transition(State::READY);
|
||||
me->observer.ready();
|
||||
break;
|
||||
default:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGateway::sync_session_terminated (EventData const&)
|
||||
{
|
||||
switch (me->state)
|
||||
{
|
||||
void CoreGateway::sync_session_terminated(EventData const &) {
|
||||
switch (me->state) {
|
||||
case State::OFF:
|
||||
case State::PAUSED:
|
||||
case State::WREADER_DISCONNECT:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
break;
|
||||
|
||||
|
||||
case State::WSESSION_TERMINATE:
|
||||
pause_or_shutdown ();
|
||||
pause_or_shutdown();
|
||||
break;
|
||||
|
||||
|
||||
case State::READY:
|
||||
me->session_info->session.reset ();
|
||||
me->session_info->connection->shutdown ();
|
||||
transition (State::WREADER_DISCONNECT);
|
||||
me->observer.disconnect ();
|
||||
me->session_info->session.reset();
|
||||
me->session_info->connection->shutdown();
|
||||
transition(State::WREADER_DISCONNECT);
|
||||
me->observer.disconnect();
|
||||
break;
|
||||
|
||||
case State::CONNECTING:
|
||||
me->session_info->session.reset ();
|
||||
me->session_info->connection->shutdown ();
|
||||
transition (State::WREADER_DISCONNECT);
|
||||
me->session_info->session.reset();
|
||||
me->session_info->connection->shutdown();
|
||||
transition(State::WREADER_DISCONNECT);
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGateway::sync_reader_disconnected (EventData const&)
|
||||
{
|
||||
switch (me->state)
|
||||
{
|
||||
void CoreGateway::sync_reader_disconnected(EventData const &) {
|
||||
switch (me->state) {
|
||||
case State::OFF:
|
||||
case State::PAUSED:
|
||||
case State::WSESSION_TERMINATE:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
break;
|
||||
|
||||
case State::READY:
|
||||
me->session_info->session.reset ();
|
||||
me->observer.disconnect ();
|
||||
transition (State::WSESSION_TERMINATE);
|
||||
me->session_info->session.reset();
|
||||
me->observer.disconnect();
|
||||
transition(State::WSESSION_TERMINATE);
|
||||
break;
|
||||
|
||||
case State::CONNECTING:
|
||||
me->session_info->session.reset ();
|
||||
transition (State::WSESSION_TERMINATE);
|
||||
me->session_info->session.reset();
|
||||
transition(State::WSESSION_TERMINATE);
|
||||
break;
|
||||
|
||||
|
||||
case State::WREADER_DISCONNECT:
|
||||
pause_or_shutdown ();
|
||||
pause_or_shutdown();
|
||||
break;
|
||||
|
||||
|
||||
default:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void CoreGateway::sync_in_msg_raw (EventData const&ev)
|
||||
{
|
||||
switch (me->state)
|
||||
{
|
||||
void CoreGateway::sync_in_msg_raw(EventData const &ev) {
|
||||
switch (me->state) {
|
||||
case State::OFF:
|
||||
case State::PAUSED:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
break;
|
||||
case State::CONNECTING:
|
||||
case State::READY:
|
||||
me->session_info->session->receive (ev.in_msg);
|
||||
me->session_info->session->receive(ev.in_msg);
|
||||
break;
|
||||
case State::WREADER_DISCONNECT:
|
||||
case State::WSESSION_TERMINATE:
|
||||
break;
|
||||
default:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGateway::sync_in_msg_sequenced (EventData const&ev)
|
||||
{
|
||||
switch (me->state)
|
||||
{
|
||||
void CoreGateway::sync_in_msg_sequenced(EventData const &ev) {
|
||||
switch (me->state) {
|
||||
case State::OFF:
|
||||
case State::PAUSED:
|
||||
case State::WREADER_DISCONNECT:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
break;
|
||||
case State::CONNECTING:
|
||||
case State::READY:
|
||||
me->observer.msg (ev.in_msg);
|
||||
me->observer.msg(ev.in_msg);
|
||||
break;
|
||||
case State::WSESSION_TERMINATE:
|
||||
break;
|
||||
default:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGateway::sync_time (EventData const &ev)
|
||||
{
|
||||
switch (me->state)
|
||||
{
|
||||
void CoreGateway::sync_time(EventData const &ev) {
|
||||
switch (me->state) {
|
||||
case State::OFF:
|
||||
break;
|
||||
case State::PAUSED:
|
||||
if (datetime::is_at_least_secs_old (me->start_of_pause,
|
||||
me->cfg.seconds_to_pause ())) {
|
||||
LOG (Jetfix,Info,"Pause period ended for jetfix connection {}", me->cfg.name ());
|
||||
if (datetime::is_at_least_secs_old(me->start_of_pause,
|
||||
me->cfg.seconds_to_pause())) {
|
||||
LOG(Jetfix, Info, "Pause period ended for jetfix connection {}",
|
||||
me->cfg.name());
|
||||
me->session_info = std::make_unique<SessionInfo>();
|
||||
|
||||
me->session_info->store.reset (me->make_store (me->cfg));
|
||||
me->session_info->store.reset(me->make_store(me->cfg));
|
||||
|
||||
if (me->session_info->store->session_active()) {
|
||||
me->connection_attempts++;
|
||||
|
||||
if (me->session_info->store->session_active ()) {
|
||||
if (!me->has_replayed) {
|
||||
me->observer.replay (me->session_info->store->replay ());
|
||||
me->observer.replay(me->session_info->store->replay());
|
||||
me->has_replayed = true;
|
||||
}
|
||||
|
||||
me->session_info->connection.reset (me->make_connection (me->cfg));
|
||||
LOG (Jetfix,Warn, "Trying to connect to exchange for {}",me->cfg.name ());
|
||||
if (me->session_info->connection->connect ()) {
|
||||
LOG (Jetfix,Warn,"Connected to exchange gateway for {}",me->cfg.name ());
|
||||
|
||||
me->session_info->session.reset (new Session (me->cfg,
|
||||
*me->session_info->connection,
|
||||
*me->session_info->store,
|
||||
*this,
|
||||
[] () {return utils::TimeStamp::now ();}));
|
||||
me->session_info->reader.reset (new AsyncReader (*me->session_info->connection,
|
||||
me->cfg.name(),
|
||||
*this,
|
||||
me->cfg.msg_buffer_sz (),
|
||||
me->cfg.msg_high_water_mark ()));
|
||||
me->session_info->reader->start ();
|
||||
me->session_info->session->start ();
|
||||
transition (State::CONNECTING);
|
||||
if (me->connection_attempts > me->cfg.max_connection_attempts()) {
|
||||
me->session_info.reset();
|
||||
me->start_of_pause = datetime::now();
|
||||
LOG(Jetfix, Error,
|
||||
"Failed to connect to exchange gateway {} too many"
|
||||
"times. Giving up permanently. You must bounce "
|
||||
"the server to recover.",
|
||||
me->cfg.name());
|
||||
break;
|
||||
}
|
||||
else {
|
||||
me->session_info.reset ();
|
||||
me->start_of_pause = datetime::now ();
|
||||
LOG (Jetfix,Warn,"Failed to connect to exchange for {}", me->cfg.name ());
|
||||
|
||||
me->session_info->connection.reset(me->make_connection(me->cfg));
|
||||
LOG(Jetfix, Warn, "Trying to connect to exchange for {} ({}/{})",
|
||||
me->cfg.name(), me->connection_attempts,
|
||||
me->cfg.max_connection_attempts());
|
||||
if (me->session_info->connection->connect()) {
|
||||
LOG(Jetfix, Warn, "Connected to exchange gateway for {}",
|
||||
me->cfg.name());
|
||||
|
||||
me->session_info->session.reset(
|
||||
new Session(me->cfg, *me->session_info->connection,
|
||||
*me->session_info->store, *this,
|
||||
[]() { return utils::TimeStamp::now(); }));
|
||||
me->session_info->reader.reset(new AsyncReader(
|
||||
*me->session_info->connection, me->cfg.name(), *this,
|
||||
me->cfg.msg_buffer_sz(), me->cfg.msg_high_water_mark()));
|
||||
me->session_info->reader->start();
|
||||
me->session_info->session->start();
|
||||
transition(State::CONNECTING);
|
||||
} else {
|
||||
me->session_info.reset();
|
||||
me->start_of_pause = datetime::now();
|
||||
LOG(Jetfix, Warn,
|
||||
"Failed to connect to exchange for {}. ({}/{}) ",
|
||||
me->cfg.name(), me->connection_attempts,
|
||||
me->cfg.max_connection_attempts());
|
||||
}
|
||||
} else {
|
||||
me->session_info.reset ();
|
||||
me->start_of_pause = datetime::now ();
|
||||
me->session_info.reset();
|
||||
me->start_of_pause = datetime::now();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case State::CONNECTING:
|
||||
case State::READY:
|
||||
me->session_info->session->time (ev.ts);
|
||||
me->session_info->session->time(ev.ts);
|
||||
break;
|
||||
case State::WREADER_DISCONNECT:
|
||||
case State::WSESSION_TERMINATE:
|
||||
break;
|
||||
default:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
void CoreGateway::pause_or_shutdown ()
|
||||
{
|
||||
me->session_info.reset ();
|
||||
|
||||
void CoreGateway::pause_or_shutdown() {
|
||||
me->session_info.reset();
|
||||
|
||||
if (me->is_on) {
|
||||
me->start_of_pause = datetime::now ();
|
||||
transition (State::PAUSED);
|
||||
} else
|
||||
transition (State::OFF);
|
||||
me->start_of_pause = datetime::now();
|
||||
transition(State::PAUSED);
|
||||
} else
|
||||
transition(State::OFF);
|
||||
}
|
||||
|
||||
void CoreGateway::transition (State new_state)
|
||||
{
|
||||
LOG (Jetfix,Warn,"Transitioning Jetfix gateway {} from {} to {}",
|
||||
me->cfg.name (),
|
||||
std::string(state_name (me->state)),
|
||||
std::string(state_name (new_state)));
|
||||
void CoreGateway::transition(State new_state) {
|
||||
LOG(Jetfix, Warn, "Transitioning Jetfix gateway {} from {} to {}",
|
||||
me->cfg.name(), std::string(state_name(me->state)),
|
||||
std::string(state_name(new_state)));
|
||||
me->state = new_state;
|
||||
check_state_invariants ();
|
||||
check_state_invariants();
|
||||
}
|
||||
|
||||
void CoreGateway::check_state_invariants ()
|
||||
{
|
||||
switch (me->state)
|
||||
{
|
||||
void CoreGateway::check_state_invariants() {
|
||||
switch (me->state) {
|
||||
case State::OFF:
|
||||
bts_assert (!me->session_info);
|
||||
bts_assert (!me->is_on);
|
||||
bts_assert(!me->session_info);
|
||||
bts_assert(!me->is_on);
|
||||
return;
|
||||
case State::PAUSED:
|
||||
bts_assert (!me->session_info);
|
||||
bts_assert (me->is_on);
|
||||
bts_assert(!me->session_info);
|
||||
bts_assert(me->is_on);
|
||||
return;
|
||||
case State::CONNECTING:
|
||||
case State::READY:
|
||||
bts_assert (me->session_info);
|
||||
bts_assert (me->session_info->session);
|
||||
bts_assert (me->is_on);
|
||||
bts_assert(me->session_info);
|
||||
bts_assert(me->session_info->session);
|
||||
bts_assert(me->is_on);
|
||||
return;
|
||||
case State::WREADER_DISCONNECT:
|
||||
bts_assert (me->session_info);
|
||||
bts_assert (!me->session_info->session);
|
||||
bts_assert(me->session_info);
|
||||
bts_assert(!me->session_info->session);
|
||||
return;
|
||||
case State::WSESSION_TERMINATE:
|
||||
bts_assert (me->session_info);
|
||||
bts_assert (!me->session_info->session);
|
||||
bts_assert(me->session_info);
|
||||
bts_assert(!me->session_info->session);
|
||||
break;
|
||||
default:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const char* CoreGateway::state_name (State st)
|
||||
{
|
||||
switch (st)
|
||||
{
|
||||
const char *CoreGateway::state_name(State st) {
|
||||
switch (st) {
|
||||
case State::OFF:
|
||||
return "OFF";
|
||||
case State::PAUSED:
|
||||
@@ -593,9 +563,8 @@ const char* CoreGateway::state_name (State st)
|
||||
case State::WSESSION_TERMINATE:
|
||||
return "WSESSION_TERMINATE";
|
||||
default:
|
||||
bts_assert (false);
|
||||
bts_assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
}}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -53,12 +53,12 @@ TEST_CASE ("JetFIX Field2Tests", "[jetfix]")
|
||||
std::string out (20, ' ');
|
||||
typedef FloatField<123> Field;
|
||||
|
||||
REQUIRE (Field::max_value_size (1.2,6)==6);
|
||||
REQUIRE (Field::max_value_size (1.2,-1,6)==6);
|
||||
|
||||
auto result = Field::write (&out[0],1.2,6);
|
||||
auto result = Field::write (&out[0],1.2,-1,6);
|
||||
REQUIRE (result.first==6);
|
||||
REQUIRE (result.second=='0'+'0'+'0'+'1'+'.'+'2');
|
||||
REQUIRE (out.compare (0,6,"1.2000") == 0);
|
||||
REQUIRE (out.compare (0,6,"0001.2") == 0);
|
||||
REQUIRE (out[6]=='\0');
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
|
||||
{
|
||||
FIXFragment g (0);
|
||||
g.add<CharField<1>> ('a');
|
||||
g.add<FloatField<2>> (1.2,6);
|
||||
g.add<FloatField<2>> (1.2,-1,6);
|
||||
g.add<StringField<3>> ("hello");
|
||||
g.add<IntField<4>> (123,3);
|
||||
const char*t_s = "20150427-19:05:18.922";
|
||||
@@ -121,7 +121,7 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
|
||||
std::string out = g.to_str ();
|
||||
REQUIRE (out ==
|
||||
"1=a" "\001"
|
||||
"2=1.2000" "\001"
|
||||
"2=0001.2" "\001"
|
||||
"3=hello" "\001"
|
||||
"4=123" "\001"
|
||||
"5=20150427-19:05:18.922" "\001"
|
||||
@@ -140,14 +140,14 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
|
||||
|
||||
FIXFragment g;
|
||||
auto c_bm = g.add<CharField<1>> ('a');
|
||||
auto f_bm = g.add<FloatField<2>> (1.2,6);
|
||||
auto f_bm = g.add<FloatField<2>> (1.2,-1,6);
|
||||
auto s_bm = g.add<StringField<3>> ("hello");
|
||||
auto i_bm = g.add<IntField<4>> (123,3);
|
||||
auto t_bm = g.add<TimeField<5>> (datetime::parse_FIX_timestamp (t_s1));
|
||||
auto b_bm = g.add<BoolField<6>> (true);
|
||||
|
||||
g.overwrite (c_bm,'b');
|
||||
g.overwrite (f_bm,1.3,6);
|
||||
g.overwrite (f_bm,1.3,-1,6);
|
||||
g.overwrite (s_bm,"olleh");
|
||||
g.overwrite (i_bm,321,3);
|
||||
g.overwrite (t_bm,(datetime::parse_FIX_timestamp (t_s2)));
|
||||
@@ -155,7 +155,7 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
|
||||
|
||||
std::string out = g.to_str ();
|
||||
REQUIRE (out== "1=b" "\001"
|
||||
"2=1.3000" "\001"
|
||||
"2=0001.3" "\001"
|
||||
"3=olleh" "\001"
|
||||
"4=321" "\001"
|
||||
"5=20150427-19:05:18.923" "\001"
|
||||
@@ -171,7 +171,7 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
|
||||
{
|
||||
FIXFragment g1;;
|
||||
g1.add<CharField<1>> ('a');
|
||||
g1.add<FloatField<2>> (1.2,6);
|
||||
g1.add<FloatField<2>> (1.2,-1,6);
|
||||
g1.add<StringField<3>> ("hello");
|
||||
auto chk1 = g1.checksum ();
|
||||
|
||||
@@ -184,7 +184,7 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
|
||||
|
||||
std::string out = g1.to_str ();
|
||||
REQUIRE (out== "1=a" "\001"
|
||||
"2=1.2000" "\001"
|
||||
"2=0001.2" "\001"
|
||||
"3=hello" "\001"
|
||||
"4=goodbye" "\001"
|
||||
"5=321" "\001");
|
||||
@@ -197,3 +197,4 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
|
||||
|
||||
}}
|
||||
|
||||
|
||||
@@ -164,18 +164,18 @@ TEST_CASE ("JetFIX Performance Tests", "[jetfix] [benchmark]")
|
||||
g.add<Symbol>("55",sizeof("55")-1);
|
||||
g.add<SecurityDesc>("ESK5 C1855",sizeof("ESK5 C1855")-1);
|
||||
g.add<SecurityType>("OPT",sizeof("OPT")-1);
|
||||
g.add<BidPx>(25200.0);
|
||||
g.add<BidPx>(25200.0,-1,-1);
|
||||
g.add<BidSize>(1);
|
||||
g.add<OfferPx>(25200.0);
|
||||
g.add<OfferPx>(25200.0,-1,-1);
|
||||
g.add<OfferSize>(1);
|
||||
|
||||
g.add<QuoteEntryID>("10857",sizeof("10857")-1);
|
||||
g.add<Symbol>("55",sizeof("55")-1);
|
||||
g.add<SecurityDesc>("ESK5 C1855",sizeof("ESK5 C1855")-1);
|
||||
g.add<SecurityType>("OPT",sizeof("OPT")-1);
|
||||
g.add<BidPx>(25200.0);
|
||||
g.add<BidPx>(25200.0,-1,-1);
|
||||
g.add<BidSize>(1);
|
||||
g.add<OfferPx>(25200.0);
|
||||
g.add<OfferPx>(25200.0,-1,-1);
|
||||
g.add<OfferSize>(1);
|
||||
|
||||
g.add<Checksum>(2,3);
|
||||
@@ -207,9 +207,9 @@ TEST_CASE ("JetFIX Performance Tests", "[jetfix] [benchmark]")
|
||||
qt1.add<SecurityDesc>("ESK5 C1855",sizeof("ESK5 C1855")-1);
|
||||
qt1.add<SecurityType>("OPT",sizeof("OPT")-1);
|
||||
|
||||
auto qt1_bid_px = qt1.add<BidPx>(25200.0);
|
||||
auto qt1_bid_px = qt1.add<BidPx>(25200.0,-1,-1);
|
||||
auto qt1_bid_sz = qt1.add<BidSize>(1);
|
||||
auto qt1_offer_px = qt1.add<OfferPx>(25200.0);
|
||||
auto qt1_offer_px = qt1.add<OfferPx>(25200.0,-1,-1);
|
||||
auto qt1_offer_sz = qt1.add<OfferSize>(1);
|
||||
|
||||
FIXFragment qt2;
|
||||
@@ -218,9 +218,9 @@ TEST_CASE ("JetFIX Performance Tests", "[jetfix] [benchmark]")
|
||||
qt2.add<SecurityDesc>("ESK5 C1855",sizeof("ESK5 C1855")-1);
|
||||
qt2.add<SecurityType>("OPT",sizeof("OPT")-1);
|
||||
|
||||
auto qt2_bid_px = qt2.add<BidPx>(25200.0);
|
||||
auto qt2_bid_px = qt2.add<BidPx>(25200.0,-1,-1);
|
||||
auto qt2_bid_sz = qt2.add<BidSize>(1);
|
||||
auto qt2_offer_px = qt2.add<OfferPx>(25200.0);
|
||||
auto qt2_offer_px = qt2.add<OfferPx>(25200.0,-1,-1);
|
||||
auto qt2_offer_sz = qt2.add<OfferSize>(1);
|
||||
|
||||
FIXFragment g;
|
||||
@@ -233,16 +233,16 @@ TEST_CASE ("JetFIX Performance Tests", "[jetfix] [benchmark]")
|
||||
g.add<NoQuoteEntries> (2);
|
||||
|
||||
qt1.overwrite (qt1_quote_entry_id,"10857",sizeof("10857")-1);
|
||||
qt1.overwrite (qt1_bid_px,25200.0);
|
||||
qt1.overwrite (qt1_bid_px,25200.0,-1,-1);
|
||||
qt1.overwrite (qt1_bid_sz,1);
|
||||
qt1.overwrite (qt1_offer_px,25200.0);
|
||||
qt1.overwrite (qt1_offer_px,25200.0,-1,-1);
|
||||
qt1.overwrite (qt1_offer_sz,1);
|
||||
g.append (qt1);
|
||||
|
||||
qt2.overwrite (qt2_quote_entry_id,"10857",sizeof("10857")-1);
|
||||
qt2.overwrite (qt2_bid_px,25200.0);
|
||||
qt2.overwrite (qt2_bid_px,25200.0,-1,-1);
|
||||
qt2.overwrite (qt2_bid_sz,1);
|
||||
qt2.overwrite (qt2_offer_px,25200.0);
|
||||
qt2.overwrite (qt2_offer_px,25200.0,-1,-1);
|
||||
qt2.overwrite (qt2_offer_sz,1);
|
||||
g.append (qt2);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "server/sparkd/jetfix/common/OutFIX.h"
|
||||
#include "server/sparkd/orderman/actors/order_manager/OrderServicesData.h"
|
||||
#include "server/sparkd/orderman/orderman_utils/EventStream.h"
|
||||
#include "server/sparkd/orderman/governor/Governor.h"
|
||||
#include "server/sparkd/orderman/orders/Order.h"
|
||||
#include <memory>
|
||||
|
||||
@@ -24,7 +25,8 @@ public:
|
||||
{}
|
||||
|
||||
std::string const &session_name() const { return _session_data->session; }
|
||||
|
||||
config::JetFIX::Connection const &session_cfg() const { return _session_data->cfg; }
|
||||
|
||||
jetfix::OutFIXPtr get_fix_buffer() {
|
||||
jetfix::OutFIXPtr result;
|
||||
if (_session_data->free_buffers.size()) {
|
||||
@@ -53,7 +55,7 @@ public:
|
||||
EventStream<NewExecutionReportEvent> exe_reports;
|
||||
EventStream<CancelRejectEvent> cancel_rejects;
|
||||
EventStream<GatewayStatusChangeEvent, mercury::NoKey> gateway_status;
|
||||
|
||||
Governor &governor() {return _session_data->governor;}
|
||||
private:
|
||||
SessionServicesData *_session_data;
|
||||
OrderServicesData *_data;
|
||||
|
||||
@@ -12,13 +12,8 @@ OrderServicesData::OrderServicesData(mercury::Actor &owner)
|
||||
to<mercury::TimeStampStr>(datetime::FIX_timestamp(datetime::now()))) {
|
||||
|
||||
auto &all_connections = config::get<config::JetFIX>().connection();
|
||||
for (auto const &c : all_connections) {
|
||||
auto s = std::make_unique<SessionServicesData>();
|
||||
s->session = c.name();
|
||||
s->gw_connected = false;
|
||||
s->cfg = c;
|
||||
sessions.push_back (std::move(s));
|
||||
}
|
||||
for (auto const &c : all_connections)
|
||||
sessions.push_back (std::make_unique<SessionServicesData>(c));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "server/sparkd/orderman/events/UpdateOrderEvent.h"
|
||||
#include "server/sparkd/orderman/limiter/Limiter.h"
|
||||
#include "server/sparkd/orderman/order_router/OrderRouter.h"
|
||||
#include "server/sparkd/orderman/governor/Governor.h"
|
||||
|
||||
#include <boost/date_time/posix_time/posix_time.hpp>
|
||||
|
||||
@@ -41,6 +42,14 @@ struct ParentKey {
|
||||
};
|
||||
|
||||
struct SessionServicesData {
|
||||
SessionServicesData(config::JetFIX::Connection const&cfg)
|
||||
: session(cfg.name()),
|
||||
gw_connected(false),
|
||||
cfg(cfg),
|
||||
governor(cfg.max_order_msgs_per_window(),
|
||||
cfg.window_duration_msecs())
|
||||
{}
|
||||
|
||||
std::string session;
|
||||
std::vector<jetfix::OutFIXPtr> free_buffers;
|
||||
mercury::Stream<OutFIXMsgEvent> out_fix_pub;
|
||||
@@ -53,6 +62,8 @@ struct SessionServicesData {
|
||||
std::unique_ptr<
|
||||
mercury::ContentSubscription<GatewayStatusChangeEvent, mercury::NoKey>>
|
||||
gateway_status_subs;
|
||||
|
||||
Governor governor;
|
||||
};
|
||||
|
||||
class OrderServicesData {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "lib/utils/bts_chrono.h"
|
||||
|
||||
namespace bts {
|
||||
namespace orderman {
|
||||
|
||||
class Governor
|
||||
{
|
||||
public:
|
||||
Governor(uint32_t max_msgs_window,
|
||||
uint32_t window_duration_msecs,
|
||||
TimeStamp now = Timer::now()) :
|
||||
_max_msgs_window(max_msgs_window),
|
||||
_window_duration_msecs(window_duration_msecs),
|
||||
_start_of_window(now),
|
||||
_msgs_in_window(0)
|
||||
{}
|
||||
|
||||
bool can_send(TimeStamp now = Timer::now()) const
|
||||
{
|
||||
if (in_window(now))
|
||||
return _msgs_in_window < _max_msgs_window;
|
||||
else
|
||||
return true;
|
||||
}
|
||||
|
||||
void record_sent(TimeStamp now = Timer::now()) {
|
||||
if (!in_window(now)) {
|
||||
_start_of_window = now;
|
||||
_msgs_in_window = 0;
|
||||
}
|
||||
_msgs_in_window++;
|
||||
}
|
||||
|
||||
private:
|
||||
bool in_window(TimeStamp now) const {
|
||||
return ms_diff (now, _start_of_window) <= _window_duration_msecs;
|
||||
}
|
||||
|
||||
uint32_t _max_msgs_window;
|
||||
uint32_t _window_duration_msecs;
|
||||
TimeStamp _start_of_window;
|
||||
uint64_t _msgs_in_window;
|
||||
};
|
||||
|
||||
}}
|
||||
@@ -24,41 +24,45 @@ OrderRouter::RouteQueryResult
|
||||
OrderRouter::get_routing_record(UserID user_id, AccountID account_id,
|
||||
api::Security const &sec) const {
|
||||
api::admin::AdminServiceOperationResult result;
|
||||
api::orderman::RoutingRecord rr = bts::admin::AdminServiceUtils::get_routing_record(me->data, account_id.value()==0 ? 1 : account_id.value(),
|
||||
user_id.value()==0 ? 1 : user_id.value(), sec, result);
|
||||
api::orderman::RoutingRecord rr =
|
||||
bts::admin::AdminServiceUtils::get_routing_record(
|
||||
me->data, account_id.value() == 0 ? 1 : account_id.value(),
|
||||
user_id.value() == 0 ? 1 : user_id.value(), sec, result);
|
||||
if (result.is_success())
|
||||
return RouteQueryResult{true, std::string(""), rr};
|
||||
else
|
||||
return RouteQueryResult{false, result.error_text(), rr};
|
||||
}
|
||||
|
||||
OrderRouter::ReverseRoutingQueryResult
|
||||
OrderRouter::get_reverse_routing_record(std::string const &session, std::string const &operator_id, std::string const &account,
|
||||
api::Security const * sec) const {
|
||||
ReverseRoutingQueryResult rv {true,"","",UserID(0),"",AccountID(0)};
|
||||
api::Venue exchange = sec == nullptr ? api::Venue::VENUE_GLBX : sec->exchange();
|
||||
auto match = bts::admin::AdminServiceUtils::find_user_account_matches(me->data,session,account,operator_id,exchange);
|
||||
if(match.first != nullptr){
|
||||
OrderRouter::ReverseRoutingQueryResult OrderRouter::get_reverse_routing_record(
|
||||
std::string const &session, std::string const &operator_id,
|
||||
std::string const &account, api::Security const *sec) const {
|
||||
ReverseRoutingQueryResult rv{true, "", "", UserID(0), "", AccountID(0)};
|
||||
api::Venue exchange =
|
||||
sec == nullptr ? api::Venue::VENUE_GLBX : sec->exchange();
|
||||
auto match = bts::admin::AdminServiceUtils::find_user_account_matches(
|
||||
me->data, session, account, operator_id, exchange);
|
||||
if (match.first != nullptr) {
|
||||
rv.account = match.first->name();
|
||||
rv.account_id = AccountID(match.first->id());
|
||||
} else {
|
||||
rv.ok = false;
|
||||
rv.error_msg = "Could not resolve account";
|
||||
}
|
||||
else{
|
||||
rv.ok=false;
|
||||
rv.error_msg = "Could not resolve account";
|
||||
}
|
||||
if(match.second != nullptr){
|
||||
if (match.second != nullptr) {
|
||||
rv.user = match.second->name();
|
||||
rv.user_id = UserID(match.second->id());
|
||||
}
|
||||
else{
|
||||
rv.ok=false;
|
||||
rv.error_msg = (!rv.ok ? std::string(", ") : std::string("")) + "Could not resolve user";
|
||||
} else {
|
||||
rv.ok = false;
|
||||
rv.error_msg = (!rv.ok ? std::string(", ") : std::string("")) +
|
||||
"Could not resolve user";
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool OrderRouter::is_valid_session(std::string const &session) const {
|
||||
return bts::admin::AdminServiceUtils::find_connection(me->data, session) != nullptr;
|
||||
return bts::admin::AdminServiceUtils::find_connection(me->data, session) !=
|
||||
nullptr;
|
||||
}
|
||||
|
||||
OrderRouter::~OrderRouter() {}
|
||||
|
||||
@@ -27,7 +27,8 @@ OrderStatus map_external_status(NativeOrderStatus);
|
||||
struct NativeOrder::PImpl {
|
||||
PImpl()
|
||||
: services(nullptr), session_services(), sec(nullptr),
|
||||
previous_position_contribution(0) {}
|
||||
previous_position_contribution(0), definition(), state(),
|
||||
price_encoding_precision(7) {}
|
||||
|
||||
OrderServices *services;
|
||||
SessionServices *session_services;
|
||||
@@ -37,6 +38,7 @@ struct NativeOrder::PImpl {
|
||||
|
||||
OrderDefinition definition;
|
||||
OrderState state;
|
||||
int price_encoding_precision;
|
||||
|
||||
jetfix::FIXFragment update_encoding;
|
||||
jetfix::FIXFragment::Bookmark<jetfix::fix::Price> update_price_bm;
|
||||
@@ -266,7 +268,7 @@ NativeOrder::~NativeOrder() {}
|
||||
void NativeOrder::on_activate(Price p, Quantity q,
|
||||
bts::orderman::OrderServices &services,
|
||||
TickToTradeCorID cid) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "on_activate", 0,cid);
|
||||
tracepoint(bts_trace, trace_order, (char *)"on_activate", 0, cid);
|
||||
bts_assert(!me->services);
|
||||
me->services = &services;
|
||||
|
||||
@@ -275,8 +277,16 @@ void NativeOrder::on_activate(Price p, Quantity q,
|
||||
case ORDER_STATUS_INACTIVE: {
|
||||
// this is a brand new order
|
||||
|
||||
me->state.set_price(p.value());
|
||||
me->state.set_quantity(q.value());
|
||||
me->state.set_filled_quantity(0);
|
||||
me->state.set_last_price(0);
|
||||
me->state.set_avg_price(0);
|
||||
|
||||
me->session_services = &me->services->get_session_services(
|
||||
get_native_state(me->state).routing_record().session());
|
||||
me->price_encoding_precision =
|
||||
me->session_services->session_cfg().price_encoding_precision();
|
||||
|
||||
// is our gateway connected?
|
||||
if (!me->session_services->is_gateway_connected()) {
|
||||
@@ -319,6 +329,16 @@ void NativeOrder::on_activate(Price p, Quantity q,
|
||||
break;
|
||||
}
|
||||
|
||||
// are we under the messaging threshold?
|
||||
if (!me->session_services->governor().can_send()) {
|
||||
LOG(OrderMan, Warn, "New order would cause breach of messaging "
|
||||
"limits. Dropping order {}",
|
||||
key().value());
|
||||
transition(ORDER_STATUS_REJECTED_BY_SPARK,
|
||||
"Messaging threshold breached");
|
||||
break;
|
||||
}
|
||||
|
||||
// if we are here we are good.
|
||||
|
||||
// subscribe to usefull events
|
||||
@@ -337,7 +357,7 @@ void NativeOrder::on_activate(Price p, Quantity q,
|
||||
case CustomOrderDefinition::kStopLimit:
|
||||
me->create_price_bm = encoding.overwrite(
|
||||
me->create_price_bm,
|
||||
securities::encode_exchange_price(*me->sec, p).value(),
|
||||
securities::encode_exchange_price(*me->sec, p).value(), 7,
|
||||
price_sz());
|
||||
break;
|
||||
case CustomOrderDefinition::kStop:
|
||||
@@ -358,13 +378,12 @@ void NativeOrder::on_activate(Price p, Quantity q,
|
||||
auto &f = stream.new_msg(jetfix::MessageTypes::NewOrderSingle);
|
||||
f.append(me->create_encoding);
|
||||
stream.close();
|
||||
|
||||
// Send it
|
||||
me->session_services->governor().record_sent();
|
||||
me->session_services->send_fix_buffer(cid, buffer);
|
||||
|
||||
me->state.set_status(ORDER_STATUS_PENDING_WORKING);
|
||||
me->state.set_price(p.value());
|
||||
me->state.set_quantity(q.value());
|
||||
me->state.set_filled_quantity(0);
|
||||
me->state.set_last_price(0);
|
||||
me->state.set_avg_price(0);
|
||||
|
||||
LOG(OrderMan, Info, "Sent new order with key {} to gateway",
|
||||
key().value());
|
||||
@@ -426,7 +445,8 @@ void NativeOrder::on_activate(Price p, Quantity q,
|
||||
}
|
||||
|
||||
void NativeOrder::on_update(Price p, Quantity q, TickToTradeCorID cid) {
|
||||
tracepoint (bts_trace,trace_order,(char*) "on_update", 0,cid);
|
||||
tracepoint(bts_trace, trace_order, (char *)"on_update", 0, cid);
|
||||
|
||||
switch (me->state.status()) {
|
||||
|
||||
case ORDER_STATUS_WORKING: {
|
||||
@@ -445,6 +465,7 @@ void NativeOrder::on_update(Price p, Quantity q, TickToTradeCorID cid) {
|
||||
me->definition.side(), Quantity(previous_working_qty),
|
||||
Quantity(current_working_qty));
|
||||
|
||||
// are we under the position limits?
|
||||
if (!lqr.ok) {
|
||||
auto msg = fmt::format("Cannot update order due to limit breach: {}",
|
||||
lqr.error_msg);
|
||||
@@ -458,28 +479,46 @@ void NativeOrder::on_update(Price p, Quantity q, TickToTradeCorID cid) {
|
||||
me->state.set_last_msg(msg);
|
||||
on_cancel(cid);
|
||||
}
|
||||
} else {
|
||||
|
||||
auto &encoding = me->update_encoding;
|
||||
me->update_price_bm = encoding.overwrite(
|
||||
me->update_price_bm,
|
||||
securities::encode_exchange_price(*me->sec, p).value(),
|
||||
price_sz());
|
||||
me->update_quantity_bm = encoding.overwrite(me->update_quantity_bm,
|
||||
q.value(), quantity_sz());
|
||||
|
||||
auto const &ts = me->services->get_time_str();
|
||||
me->update_transact_time_bm = encoding.overwrite(
|
||||
me->update_transact_time_bm, &ts.value()[0], ts.value().size());
|
||||
auto buffer = me->session_services->get_fix_buffer();
|
||||
auto &stream = buffer->fix[0];
|
||||
auto &f = stream.new_msg(jetfix::MessageTypes::CancelReplace);
|
||||
f.append(me->update_encoding);
|
||||
stream.close();
|
||||
me->session_services->send_fix_buffer(cid, buffer);
|
||||
if (me->state.last_msg().size())
|
||||
me->state.set_last_msg("");
|
||||
break;
|
||||
}
|
||||
|
||||
// are we under the messaging threshold?
|
||||
if (!me->session_services->governor().can_send()) {
|
||||
LOG(OrderMan, Warn, "Cannot update order {} because of messaging limits. ",
|
||||
key().value());
|
||||
auto msg = "Messaging threshold breached";
|
||||
if (parent_key() == null_order_key())
|
||||
transition(me->state.status(),msg);
|
||||
else {
|
||||
me->state.set_last_msg(msg);
|
||||
on_cancel(cid);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// all good
|
||||
auto &encoding = me->update_encoding;
|
||||
me->update_price_bm = encoding.overwrite(
|
||||
me->update_price_bm,
|
||||
securities::encode_exchange_price(*me->sec, p).value(), 7,
|
||||
price_sz());
|
||||
me->update_quantity_bm =
|
||||
encoding.overwrite(me->update_quantity_bm, q.value(), quantity_sz());
|
||||
|
||||
auto const &ts = me->services->get_time_str();
|
||||
me->update_transact_time_bm = encoding.overwrite(
|
||||
me->update_transact_time_bm, &ts.value()[0], ts.value().size());
|
||||
auto buffer = me->session_services->get_fix_buffer();
|
||||
auto &stream = buffer->fix[0];
|
||||
auto &f = stream.new_msg(jetfix::MessageTypes::CancelReplace);
|
||||
f.append(me->update_encoding);
|
||||
stream.close();
|
||||
|
||||
// Send it
|
||||
me->session_services->governor().record_sent();
|
||||
me->session_services->send_fix_buffer(cid, buffer);
|
||||
if (me->state.last_msg().size())
|
||||
me->state.set_last_msg("");
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -790,8 +829,8 @@ void NativeOrder::init_fix() {
|
||||
cancel_encoding.add<fix::OrdType>(NATIVE_ORDER_TYPE_LIMIT);
|
||||
|
||||
// price
|
||||
me->create_price_bm = create_encoding.add<fix::Price>(0, price_sz());
|
||||
me->update_price_bm = update_encoding.add<fix::Price>(0, price_sz());
|
||||
me->create_price_bm = create_encoding.add<fix::Price>(0, 7, price_sz());
|
||||
me->update_price_bm = update_encoding.add<fix::Price>(0, 7, price_sz());
|
||||
break;
|
||||
|
||||
case CustomOrderDefinition::kStop: {
|
||||
@@ -804,8 +843,8 @@ void NativeOrder::init_fix() {
|
||||
auto stop_px = def.custom_def().stop().stop_price();
|
||||
auto encoded_stop_px =
|
||||
securities::encode_exchange_price(*me->sec, Price(stop_px)).value();
|
||||
create_encoding.add<fix::StopPx>(encoded_stop_px);
|
||||
update_encoding.add<fix::StopPx>(encoded_stop_px);
|
||||
create_encoding.add<fix::StopPx>(encoded_stop_px, -1, -1);
|
||||
update_encoding.add<fix::StopPx>(encoded_stop_px, -1, -1);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -819,12 +858,12 @@ void NativeOrder::init_fix() {
|
||||
auto stop_px = def.custom_def().stop_limit().stop_price();
|
||||
auto encoded_stop_px =
|
||||
securities::encode_exchange_price(*me->sec, Price(stop_px)).value();
|
||||
create_encoding.add<fix::StopPx>(encoded_stop_px);
|
||||
update_encoding.add<fix::StopPx>(encoded_stop_px);
|
||||
create_encoding.add<fix::StopPx>(encoded_stop_px, -1, -1);
|
||||
update_encoding.add<fix::StopPx>(encoded_stop_px, -1, -1);
|
||||
|
||||
// price
|
||||
me->create_price_bm = create_encoding.add<fix::Price>(0, price_sz());
|
||||
me->update_price_bm = update_encoding.add<fix::Price>(0, price_sz());
|
||||
me->create_price_bm = create_encoding.add<fix::Price>(0, 7, price_sz());
|
||||
me->update_price_bm = update_encoding.add<fix::Price>(0, 7, price_sz());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -15,10 +15,12 @@ SpreaderOrder::SpreaderOrder()
|
||||
:
|
||||
_cancelled (false),
|
||||
_cancel_timeout (5000),// TODO:get it from config
|
||||
_check_termination_period (250), // should also be a setting/config like above
|
||||
_active(true),
|
||||
_bookkeeping_data_ptr(),
|
||||
_order_services(nullptr),
|
||||
_reported_state(),
|
||||
_curr_timestamp(),
|
||||
_last_check_termination_timestamp(),
|
||||
_state_worker(nullptr),
|
||||
_start_state_worker(this), _working_state_worker(this), _waiting_for_cancel_all_state_worker(this), _end_state_worker(this){
|
||||
_transform_to_START();
|
||||
@@ -81,6 +83,7 @@ void SpreaderOrder::_on_time(const bts::mercury::TimerEvent& t_event){
|
||||
_curr_timestamp = t_event.ts;
|
||||
if (!_active) return;
|
||||
_state_worker->on_time (_curr_timestamp);
|
||||
_send_check_termination(false);
|
||||
}
|
||||
void SpreaderOrder::_on_book_update(const BookUpdateEvent& book_event){
|
||||
if (!_active) return;
|
||||
@@ -133,7 +136,7 @@ void SpreaderOrder::_on_child_order_update(const OrderStateUpdateEvent& ord_ev){
|
||||
LOG(OrderMan, Error, "SpreaderOrder::_on_child_order_update unknown order key: {} sec_key: {} order_key: {} status: {} filled_qty: {} last_price: {} msg: {}",
|
||||
key(), sec_key, order_key, status, filled_qty, last_price, ord_ev.state.last_msg ());
|
||||
}
|
||||
_state_worker->on_check_termination ();
|
||||
_send_check_termination(true);
|
||||
_report_state();
|
||||
}
|
||||
// State Transformations ////////////////////
|
||||
@@ -279,21 +282,26 @@ void SpreaderOrder::_compute_hedge_price_quantity(size_t filled_leg_index,
|
||||
hedged_leg_data.leg_property_data().tick_size(),
|
||||
computed_hedge_price, hedge_quantity);
|
||||
}
|
||||
void SpreaderOrder::_report_state (){
|
||||
if (_activated()){
|
||||
SpreaderBookkeepingData& bookkeeping_data = _bookkeeping_data();
|
||||
void SpreaderOrder::_report_state (){
|
||||
if (_activated()){
|
||||
SpreaderBookkeepingData& bookkeeping_data = _bookkeeping_data();
|
||||
auto& reported_state = bookkeeping_data.reported_state();
|
||||
api::orderman::OrderStatus status = _get_order_status ();
|
||||
Quantity filled_qty; Price avg_filled_price;
|
||||
bookkeeping_data.total_fills(filled_qty, avg_filled_price);
|
||||
_reported_state.set_status(status);
|
||||
_reported_state.set_price(bookkeeping_data.price().value());
|
||||
_reported_state.set_quantity(bookkeeping_data.quantity().value());
|
||||
_reported_state.set_filled_quantity(filled_qty.value());
|
||||
_reported_state.set_avg_price(avg_filled_price.value());
|
||||
// TODO fill in filled_qty etc
|
||||
if(bookkeeping_data.inited_new()){
|
||||
Quantity filled_qty; Price avg_filled_price;
|
||||
bookkeeping_data.total_fills(filled_qty, avg_filled_price);
|
||||
reported_state.set_status(status);
|
||||
reported_state.set_price(bookkeeping_data.price().value());
|
||||
reported_state.set_quantity(bookkeeping_data.quantity().value());
|
||||
reported_state.set_filled_quantity(filled_qty.value());
|
||||
reported_state.set_avg_price(avg_filled_price.value());
|
||||
}
|
||||
// if order is cons'ed from disc, with inited_new==false,
|
||||
// we just publish what was given to us
|
||||
LOG(OrderMan, Info, "SpreaderOrder::_report_state key: {} status: {} price: {} qty: {} filled: {} abg_price: {}",
|
||||
key(), status, _reported_state.price(), _reported_state.quantity(), _reported_state.filled_quantity(), _reported_state.avg_price());
|
||||
_order_services->report_state(_reported_state);
|
||||
key(), status, reported_state.price(), reported_state.quantity(),
|
||||
reported_state.filled_quantity(), reported_state.avg_price());
|
||||
_order_services->report_state(reported_state);
|
||||
}
|
||||
}
|
||||
bts::api::orderman::OrderStatus SpreaderOrder::_get_order_status () const{
|
||||
@@ -314,4 +322,11 @@ bts::api::orderman::OrderStatus SpreaderOrder::_get_order_status () const{
|
||||
return bts::api::orderman::OrderStatus::ORDER_STATUS_INACTIVE;
|
||||
}
|
||||
}
|
||||
}}
|
||||
void SpreaderOrder::_send_check_termination(bool force){
|
||||
if(force ||
|
||||
ms_diff (_curr_timestamp, _last_check_termination_timestamp) >= _check_termination_period){
|
||||
_last_check_termination_timestamp = _curr_timestamp;
|
||||
_state_worker->on_check_termination();
|
||||
}
|
||||
}
|
||||
}}
|
||||
@@ -49,17 +49,19 @@ private:
|
||||
bool _cancelled;
|
||||
// timeout before cancel waiting goes to end
|
||||
const int64_t _cancel_timeout;
|
||||
// period on which check termination calls are
|
||||
// sent to the state worker
|
||||
const int64_t _check_termination_period;
|
||||
// is false if created from disk
|
||||
const bool _active;
|
||||
// this is the main data
|
||||
std::unique_ptr<SpreaderBookkeepingData> _bookkeeping_data_ptr;
|
||||
// pointer kept to send reports
|
||||
SpreaderOrderServices* _order_services;
|
||||
// state thats sent out in report (its kept here amd modified
|
||||
// vs creating a new one everytime)
|
||||
bts::api::orderman::OrderState _reported_state;
|
||||
// timestamp of last the on_time call
|
||||
bts::TimeStamp _curr_timestamp;
|
||||
// spreader fires a periodic check
|
||||
bts::TimeStamp _last_check_termination_timestamp;
|
||||
// current state
|
||||
ISpreaderStateWorker* _state_worker;
|
||||
SpreaderStartStateWorker _start_state_worker;
|
||||
@@ -203,6 +205,10 @@ private:
|
||||
return ChildOrderError::MODIFY_ERROR;
|
||||
return ChildOrderError::NONE;
|
||||
}
|
||||
// the state worker's on_check_termination is sent
|
||||
// when there's an order update, as well as on
|
||||
// a periodic bases
|
||||
void _send_check_termination(bool force);
|
||||
// testing only //////////////////
|
||||
// this provides direct way to inject
|
||||
// data into the spreader, as well as
|
||||
|
||||
+18
-2
@@ -7,11 +7,14 @@ SpreaderBookkeepingData::SpreaderBookkeepingData (ISpreaderManager& spreader_man
|
||||
bts::orderman::OrderKey spreader_order_key,
|
||||
const api::orderman::OrderDefinition& order_def,
|
||||
const SpreaderPreActivationOrderServices& spreader_pao_services,
|
||||
const std::vector<LegPropertyData>& legs)
|
||||
const std::vector<LegPropertyData>& legs,
|
||||
bool inited_new)
|
||||
:
|
||||
_is_live(spreader_pao_services.is_live()),
|
||||
_n_legs(legs.size()),
|
||||
_order_def(order_def){
|
||||
_inited_new (inited_new),
|
||||
_order_def(order_def),
|
||||
_reported_state(){
|
||||
LOG(OrderMan, Info, "SpreaderBookkeepingData.cons started key: {} n_legs: {}", spreader_order_key.value(), _n_legs);
|
||||
_leg_prop_data.reserve(_n_legs),
|
||||
_books.reserve(_n_legs),
|
||||
@@ -39,6 +42,19 @@ _order_def(order_def){
|
||||
}
|
||||
LOG(OrderMan, Info, "SpreaderBookkeepingData.cons completed key: {} n_legs: {}", spreader_order_key.value(), _n_legs);
|
||||
}
|
||||
SpreaderBookkeepingData::SpreaderBookkeepingData (ISpreaderManager& spreader_manager,
|
||||
const bts::api::orderman::OrderRecord& order_record,
|
||||
const SpreaderPreActivationOrderServices& spreader_pao_services,
|
||||
const std::vector<LegPropertyData>& legs)
|
||||
:
|
||||
SpreaderBookkeepingData(spreader_manager,
|
||||
OrderKey(order_record.key()),
|
||||
order_record.def(),
|
||||
spreader_pao_services,
|
||||
legs,
|
||||
false){
|
||||
_reported_state=order_record.state();
|
||||
}
|
||||
void SpreaderBookkeepingData::total_fills (Quantity& out_quantity,
|
||||
Price& out_price) const{
|
||||
double avg_price = 0.0;
|
||||
|
||||
@@ -21,6 +21,9 @@ class SpreaderBookkeepingData
|
||||
private:
|
||||
const bool _is_live;
|
||||
const size_t _n_legs;
|
||||
// when the order is not created from the disk
|
||||
// (ie created live) this is set to true
|
||||
const bool _inited_new;
|
||||
std::vector<LegPropertyData> _leg_prop_data;
|
||||
std::vector<Book> _books;
|
||||
const api::orderman::OrderDefinition _order_def;
|
||||
@@ -31,6 +34,9 @@ private:
|
||||
std::vector<std::vector<SingleFillHedger>> _hedgers;
|
||||
std::vector<SpreaderLegData> _leg_data;
|
||||
boost::container::flat_map<uint32_t, size_t> _sec_key_to_index;
|
||||
// state thats sent out in report (its kept here amd modified
|
||||
// vs creating a new one everytime)
|
||||
bts::api::orderman::OrderState _reported_state;
|
||||
Price _price;
|
||||
Quantity _quantity;
|
||||
public:
|
||||
@@ -38,6 +44,11 @@ public:
|
||||
bts::orderman::OrderKey spreader_order_key,
|
||||
const api::orderman::OrderDefinition& order_def,
|
||||
const SpreaderPreActivationOrderServices& spreader_pao_services,
|
||||
const std::vector<LegPropertyData>& legs,
|
||||
bool inited_new=true);
|
||||
SpreaderBookkeepingData (ISpreaderManager& spreader_manager,
|
||||
const bts::api::orderman::OrderRecord& order_record,
|
||||
const SpreaderPreActivationOrderServices& spreader_pao_services,
|
||||
const std::vector<LegPropertyData>& legs);
|
||||
SpreaderBookkeepingData()=delete;
|
||||
SpreaderBookkeepingData (const SpreaderBookkeepingData&) = delete;
|
||||
@@ -101,5 +112,11 @@ public:
|
||||
_price = price;
|
||||
_quantity = quantity;
|
||||
}
|
||||
bts::api::orderman::OrderState& reported_state(){
|
||||
return _reported_state;
|
||||
}
|
||||
bool inited_new () const{
|
||||
return _inited_new;
|
||||
}
|
||||
};
|
||||
}}
|
||||
+11
-3
@@ -25,18 +25,26 @@ void SpreaderStartStateWorker::on_init_new(const api::orderman::OrderDefinition&
|
||||
_spreader->_bookkeeping_data_ptr = std::make_unique<SpreaderBookkeepingData>(*_spreader, _spreader->key(), order_def, spreader_pao_services, spreader_legs);
|
||||
else
|
||||
{
|
||||
_spreader->_bookkeeping_data_ptr = std::make_unique<SpreaderBookkeepingData>(*_spreader, _spreader->key(), order_def, spreader_pao_services, std::vector<LegPropertyData>());
|
||||
_spreader->_bookkeeping_data_ptr = std::make_unique<SpreaderBookkeepingData>(*_spreader,
|
||||
_spreader->key(),
|
||||
order_def,
|
||||
spreader_pao_services,
|
||||
std::vector<LegPropertyData>());
|
||||
// if securities can not be resolved, the order is terminated
|
||||
LOG(OrderMan, Error, "SpreaderStartStateWorker.on_init_new key: {} , could not resolve securities", _spreader->key().value());
|
||||
std::string msg = "Could not resolve securities";
|
||||
_spreader->_transform_to_END (&msg);
|
||||
}
|
||||
}
|
||||
void SpreaderStartStateWorker::on_init_recovered(const bts::api::orderman::OrderRecord& order_record, const SpreaderPreActivationOrderServices& spreader_pao_services)
|
||||
void SpreaderStartStateWorker::on_init_recovered(const bts::api::orderman::OrderRecord& order_record,
|
||||
const SpreaderPreActivationOrderServices& spreader_pao_services)
|
||||
{
|
||||
bts_assert(!_spreader->_inited());
|
||||
// we are not handling this yet, so lets send it directly to END
|
||||
_spreader->_bookkeeping_data_ptr = std::make_unique<SpreaderBookkeepingData>(*_spreader, _spreader->key(), order_record.def(), spreader_pao_services, std::vector<LegPropertyData>());
|
||||
_spreader->_bookkeeping_data_ptr = std::make_unique<SpreaderBookkeepingData>(*_spreader,
|
||||
order_record,
|
||||
spreader_pao_services,
|
||||
std::vector<LegPropertyData>());
|
||||
LOG(OrderMan, Warn, "SpreaderStartStateWorker.on_init_recovered key: {} , going to END", _spreader->key().value());
|
||||
std::string msg = "Recovered spreader orders and not supported";
|
||||
_spreader->_transform_to_END (&msg);
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
|
||||
import sys
|
||||
import re
|
||||
from pandas import DataFrame,Series
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
|
||||
pd.set_option('display.max_columns',20)
|
||||
pd.set_option('display.max_rows',2000)
|
||||
pd.set_option('display.width',1000)
|
||||
pd.set_option('display.precision',8)
|
||||
pd.set_option('display.float_format', lambda x: '%.3f' % x)
|
||||
|
||||
def parse_exps(exps):
|
||||
delta_exps=[]
|
||||
for field_exp in exps:
|
||||
delta_exps.append(re.match('(?P<result>\w+)=(?P<stop>\w+)-(?P<start>\w+)',field_exp).groupdict())
|
||||
return delta_exps
|
||||
|
||||
def parse_log():
|
||||
result = {}
|
||||
for line in sys.stdin.readlines():
|
||||
(cid, ts, ev) = line.split()
|
||||
result.setdefault(int(cid),{}) [ev] = int(ts)
|
||||
return result
|
||||
|
||||
def print_traces(last,traces):
|
||||
for k,in_rec in traces.iteritems():
|
||||
if last in in_rec and "zero" in in_rec:
|
||||
first = in_rec["zero"]
|
||||
shot = in_rec[last]
|
||||
o
|
||||
print "%d -> %d" % (k, shot-first)
|
||||
previous = first
|
||||
for key, value in sorted(in_rec.iteritems(), key=lambda (k,v): (v,k)):
|
||||
print "\t%s: %s" % (key, value-previous)
|
||||
previous = value
|
||||
|
||||
def transform(delta_exps,raw_events):
|
||||
out = []
|
||||
for k,in_rec in raw_events.iteritems():
|
||||
out_rec = {}
|
||||
out_rec['id'] = k
|
||||
all_present = True
|
||||
for d in delta_exps:
|
||||
start_k = d['start']
|
||||
stop_k = d['stop']
|
||||
if start_k in in_rec and stop_k in in_rec:
|
||||
out_rec[d['result']] = (in_rec[stop_k]-in_rec[start_k]) / 1000
|
||||
else:
|
||||
all_present = False
|
||||
if all_present:
|
||||
out.append(out_rec)
|
||||
return out
|
||||
|
||||
|
||||
exps = parse_exps(sys.argv[1:])
|
||||
traces = parse_log()
|
||||
print traces
|
||||
# print_traces("SocketConnection_write_pre_write",traces)
|
||||
# times = transform(exps,traces)
|
||||
|
||||
# clean_times = []
|
||||
# for t in times:
|
||||
# if t['t'] < 500000:
|
||||
# clean_times.append(t)
|
||||
|
||||
# frame = DataFrame(clean_times)
|
||||
# print "Overview (all data)"
|
||||
# print "--------------------"
|
||||
# print frame
|
||||
# print
|
||||
|
||||
# frame = frame.drop('id', 1)
|
||||
# print "Medians (all data)"
|
||||
# print "------------------"
|
||||
# print frame.median()
|
||||
# print
|
||||
|
||||
# print "StdDevs (all data)"
|
||||
# print "------------------"
|
||||
# print frame.std()
|
||||
|
||||
# clean_times = []
|
||||
# for t in times:
|
||||
# if t['t2t'] < 500:
|
||||
# clean_times.append(t)
|
||||
|
||||
# frame = DataFrame(clean_times)
|
||||
|
||||
# print "Overview (clean data)"
|
||||
# print "---------------------"
|
||||
# print frame
|
||||
# print
|
||||
|
||||
# frame = frame.drop('id', 1)
|
||||
# print "Medians (clean data)"
|
||||
# print "--------------------"
|
||||
# print frame.median()
|
||||
# print
|
||||
|
||||
# print "StdDevs (clean data)"
|
||||
# print "--------------------"
|
||||
# print frame.std()
|
||||
|
||||
Python finished at Tue Jun 5 14:54:09
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
./parse_log.sh $1 | ./analyze.py t=SocketConnection_write_pre_write-MDSource
|
||||
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
rm -f trace_data/trace.sqlite
|
||||
sqlite3 trace_data/trace.sqlite <<EOF
|
||||
|
||||
.separator ","
|
||||
|
||||
create table trace (seq integer, ts integer, hts text, msg text, cpu integer, tid integer,arg1 integer);
|
||||
create index trace_seq_idx on trace(seq);
|
||||
.import trace_data/parsed_trace.txt trace
|
||||
EOF
|
||||
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
cat trace_data/raw_trace.txt | python parse_helper.py> trace_data/parsed_trace.txt
|
||||
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user