Compare commits

..
87 Commits
Author SHA1 Message Date
Pedro Pinto 82d3e59024 Removed unused config values. 2018-06-18 20:44:25 +00:00
Pedro Pinto 6fe59386df SPARK-1137, We have server performance numbers and they are good. 2018-06-18 20:44:25 +00:00
Pedro Pinto 767cb36856 Ran clang-format. 2018-06-18 20:44:25 +00:00
Taha Afzal bbf961ed78 fixing build, .fs file 2018-06-18 08:40:20 -04:00
Taha Afzal f3b8ce8088 adding location to CME User Data 2018-06-17 22:44:37 -04:00
Pedro Pinto 026bda165e Added more data to server side performance tracing. 2018-06-17 15:22:56 +00:00
Pedro Pinto 9727f17edf Tweaks to pp custom build environment. 2018-06-17 15:21:47 +00:00
Pedro Pinto 61b2913a31 Fixed clang build. 2018-06-17 15:21:04 +00:00
Pedro Pinto 0a8bee2c31 Added a couple more digits to exchange price encoding. 2018-06-15 09:51:07 -04:00
Pedro Pinto 2dd8e64272 Perf analysis tools. 2018-06-15 09:51:07 -04:00
John Colvin aa704b8c96 timefilter - make text input filter more flexible 2018-06-14 20:56:19 -04:00
John Colvin 8673b857a7 timefilter - triple-click selects all time-text 2018-06-14 19:56:09 -04:00
John Colvin f83f2c7964 SPARK-1348 time-based period filter requires textbox to lose focus to accept new value 2018-06-14 19:13:18 -04:00
Eric Harding 86d8d7dafb Add seconds_to_recenter and max_open_ladders to UI.conf. Remove seconds and ticks from LadderSettings as it should not be stored per-ladder. 2018-06-14 18:35:15 -04:00
Eric Harding 5363fdd0db Reduce logging level for non-errors in PersistenceServiceServer 2018-06-14 18:08:52 -04:00
Eric Harding d3e7870b9a Fix build with the #ifdef stick 2018-06-14 18:08:32 -04:00
Eric Harding f05692e441 Merged Metrics into qa 2018-06-14 17:38:58 -04:00
Eric Harding 053c460ff6 Market data rate metrics reborn 2018-06-14 17:13:19 -04:00
Eric Harding af7e9dd3a8 Raise WebServer GET request logging to DIAG level 2018-06-14 17:11:57 -04:00
Eric Harding 3e3b6acfd7 Market data QueryServer response time/size. 2018-06-14 17:11:57 -04:00
Eric Harding b75496e665 Add sparkd memory use metric 2018-06-14 17:11:57 -04:00
Eric Harding 03864caa09 Add HistogramMetric type 2018-06-14 17:11:57 -04:00
Eric Harding 1558b79c16 Sparkd promethius metrics 2018-06-14 17:11:57 -04:00
Kevin Darby 57bfe5a85e added manual test cases for algos 2018-06-14 14:50:20 -04:00
John Colvin 3eaeaf0f35 fix LadderSettings equals method 2018-06-14 12:50:02 -04:00
Michael Harding ab3121cd5d Spark Admin: Admin user hack 2018-06-14 11:43:01 -04:00
Taha Afzal ec5bd4a164 SPARK-1332 2018-06-14 11:22:11 -04:00
John Colvin 3877aec09a SPARK-1354 Can't change bid, ask, or last colors in ladder 2018-06-14 10:37:29 -04:00
Jake Norbut fb17596304 updating test text 2018-06-14 08:32:48 -05:00
Michael Harding b36bcc66fb Update PnL manual test 2018-06-14 09:26:53 -04:00
Taha Afzal 73189fbbd8 2 admin limit tests changed slightly 2018-06-13 19:18:04 -04:00
John Colvin 484a9dba12 orderticket priceentrycontrol - update binding source in tickup/down command if has keyboard focus 2018-06-13 17:27:53 -04:00
John Colvin 83f2866604 SPARK-1349 Order Ticket Price Field - DoubleClick 2018-06-13 17:27:53 -04:00
Pedro Pinto 37f40046da Fixed typo in CTI code enum. 2018-06-13 15:48:17 -04:00
Pedro Pinto 2401b9db77 Typo in log msg. 2018-06-13 15:48:17 -04:00
John Colvin a45546690c SPARK-1352 ladder - auto-clear should only reset size to 0 2018-06-13 15:41:20 -04:00
John Colvin bc394ee3b5 SPARK-1350 Fill Window Comes Up Blank From Orders Window 2018-06-13 15:30:55 -04:00
Michael Harding b917fba19a Spark Admin: Assign correct default symbol to new productLimits rows 2018-06-13 14:58:54 -04:00
Michael Harding 63106eb866 SPARK-1245: Wrap Clip Limits help onto two lines 2018-06-13 14:28:27 -04:00
Eric Harding fa79bddb6b Make Spark webserver pipe down a bit 2018-06-13 13:49:34 -04:00
Pedro Pinto 83851c1494 QA assignments. 2018-06-13 10:35:02 -04:00
John Colvin 373f7ee3a1 SPARK-1208 synthetic modification disabled when it should not be 2018-06-12 23:50:56 -04:00
Taha Afzal 2ed8b9caf0 changes to the prehedge tests 2018-06-12 23:16:04 -04:00
John Colvin 470eee431e SPARK-1248 OrderBook - 'last message' cells are always empty 2018-06-12 23:08:00 -04:00
John Colvin e5e6723b3a orderbook - LiveSortingProperties were one step behind SortDescriptions 2018-06-12 22:54:42 -04:00
John Colvin 71be28911b SPARK-1243 crash on server bounce 2018-06-12 19:54:51 -04:00
Michael Harding 8ab3182c21 SPARK-1241: Username.ToLower 2018-06-12 17:33:20 -04:00
Pedro Pinto 508e2d4fcf SPARK-1234, Make sure rejected orders have valid price, quantity 2018-06-12 17:04:29 -04:00
Pedro Pinto 027e434ac9 SPARK-1236, Limit the number of exchange connection attempts. 2018-06-12 16:57:22 -04:00
Michael Harding e9be6d9859 SPARK-1245: Help text 2018-06-12 16:53:25 -04:00
Pedro Pinto 82e63f691c SPARK-1242, Added messaging governor to execution. 2018-06-12 16:01:35 -04:00
Pedro Pinto cb83ad08a6 Perf analysis tools. 2018-06-12 14:14:42 -04:00
Michael Harding e2e78d91c3 SPARK-1240, SPARK-1239: Hide Synthetic, Remove duplicate symbols 2018-06-12 13:21:28 -04:00
Michael Harding 427d03d340 SparkAdmin: Remove onclick from login 2018-06-12 11:29:53 -04:00
John Colvin 32e5111c54 orderbook - remove unused resources in OrderBookView.xaml 2018-06-11 15:11:53 -04:00
John Colvin ef12f81b73 orderbook - fix early-child sorting bug 2018-06-11 15:11:53 -04:00
Eric Harding c0f255f5a7 Spark Admin: Reload page if websocket is disconnected 2018-06-11 14:29:36 -04:00
John Colvin f6861820e4 orderbook - add apply/clear buttons to SubType filter popup 2018-06-11 12:19:26 -04:00
John Colvin 6a3960b33f FillTuple has SecurityMD field (instead of just Security) 2018-06-11 11:44:37 -04:00
Eric Harding e3858fd203 Merged in SPARK-1231 (pull request #39)
Persist should not throw exceptions. Nobody expects that

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2018-06-11 15:29:28 +00:00
Eric Harding 3f1d9307e9 Persist should not throw exceptions. Nobody expects that 2018-06-11 11:22:21 -04:00
Jake Norbut 5f7c2330f3 Ladder recenter on middle-click is now restricted to price column; this previously interferred with a badge's context menu from opening when the ladder was not centered 2018-06-11 09:56:48 -05:00
Jake Norbut 315b2a8b26 SPARK-1229 - EnumCharacterCasingConverter now checks for null 2018-06-11 09:55:29 -05:00
Taha Afzal 29ebfebcbc synthtic orders ressurected from disk show the correct filled price/qty 2018-06-10 23:32:02 -04:00
John Colvin 4fbaca6d05 orderbook - fix child-order filter bug 2018-06-10 17:15:25 -04:00
John Colvin 4d4f706e7c OrderBook - fix sorting bug (must recreate cell in addition to invoking PropertyChanged) 2018-06-10 16:26:46 -04:00
John Colvin 241396e292 clean up FillTupleSource/ObservableDemuxer 2018-06-10 15:59:52 -04:00
John Colvin 4e68c98cfb fix FillTupleSource 2018-06-08 23:36:06 -04:00
Eric Harding 30c1dfd9a7 Merged in SPARK-1123 (pull request #38)
SPARK-1123 on_orders.Replay().RefCount() does not handle late join correctly

Approved-by: Kevin Darby <kevin.darby@bluetradesys.com>
Approved-by: John Colvin <john.colvin@bluetradesys.com>
2018-06-09 01:37:48 +00:00
Pedro Pinto a64ca702c9 More tracing tools. 2018-06-08 18:24:07 -04:00
Taha Afzal 0a2b6ce17e SPARK-1212, delayed spreader order cancelling when no active achild orders 2018-06-08 18:21:29 -04:00
Pedro Pinto 660f203132 SPARK-1137, Server side tracing script. 2018-06-08 17:47:23 -04:00
Michael Harding 27f27dbcf1 AdminUI -> Spark Admin 2018-06-08 17:05:54 -04:00
Pedro Pinto 1ac6c8cd40 SPARK-1216, Formatted order prices properly. 2018-06-08 16:33:11 -04:00
Pedro Pinto a13cececa1 Formatted code a bit better. 2018-06-08 16:33:11 -04:00
Pedro Pinto 01d2e88097 Made logging more robust in the presence of config errors. 2018-06-08 16:33:11 -04:00
John Colvin 8f96cd07e3 FillTupleSource - actually update the state with incremental changes 2018-06-08 14:31:11 -04:00
Eric Harding b515e45b23 SPARK-1123 on_orders.Replay().RefCount() does not handle late join correctly 2018-06-08 13:49:22 -04:00
Eric Harding a37e2ead87 Hide status change messages in ladder 2018-06-08 13:44:34 -04:00
Kevin Darby 92ae592d31 Fixed a couple of bugs involving comparing doubles in the ladder and some ladder start crashes. 2018-06-08 13:10:50 -04:00
John Colvin db1cbf589f remove 'Latency' from Logger Subsystems in cs 2018-06-08 10:53:17 -04:00
Eric Harding a561ff3aae Fix merge problem with ObserveOn PR 2018-06-08 10:39:39 -04:00
Eric Harding 3eeab708d2 Merged in ObserveOn (pull request #37)
ObserveOnDispatcher -> ObserveOn + Lower priority so as not to block input

Approved-by: John Colvin <john.colvin@bluetradesys.com>
2018-06-08 14:37:20 +00:00
Eric Harding 082056ea3f Fix crash in ErrorReporter because api thread is not STA 2018-06-08 09:53:32 -04:00
Eric Harding e9f3e92f82 Read spark.version for version information 2018-06-08 09:26:21 -04:00
Jake Norbut 152c79a4a9 Badge should never fill in with completion progress as it previously did 2018-06-08 08:05:35 -05:00
Eric Harding db877c859b ObserveOnDispatcher -> ObserveOn + Lower priority so as not to block input 2018-06-08 08:42:23 -04:00
172 changed files with 2979 additions and 1759 deletions

No files matched your search

+2
View File
@@ -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/
+1 -1
View File
@@ -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);
}
+12 -9
View File
@@ -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) =>
+2 -2
View File
@@ -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,8 +129,8 @@ 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) {
if (state.orders != null && state.orders.Length == 0) {
static IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
if (!state.orders.is_null_or_empty()) {
var orders_hash = new HashSet<uint>(state.orders);
return model.on_fill_tuples.filter(f => orders_hash.Contains(f.fill.OrderKey));
} else return model.on_fill_tuples;
@@ -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) =>
@@ -30,12 +30,11 @@ namespace bts.shell {
_wm = wm;
_settings_hub = settings_hub;
_orders_model = orders_model;
var window_props = Configuration.get<bts.config.IShellWindowProps>();
_max_ladders_open = (int)(window_props?.ladder?.max_total_open ?? 75);
_max_ladders_open = (int)(Configuration.get<bts.config.LadderKnobs>().max_open_ladders);
_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);
}
+33 -12
View File
@@ -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);
}
}
+129 -152
View File
@@ -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));
}
}
}
+1 -1
View File
@@ -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)
+3 -3
View File
@@ -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) =>
+2 -2
View File
@@ -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}";
}
@@ -3,6 +3,8 @@ using System.Collections.Generic;
namespace mbts.utils {
public static class EqualityComparer {
const int _hash_seed = 63607, _hash_multiplier = 6451;
public static IEqualityComparer<T> Create<T>(Func<T, T, bool> equals) =>
new AnonymousEqualityComparer<T>(equals);
@@ -28,6 +30,31 @@ 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 = _hash_seed;
unchecked {
_accumulate_hash(ref rv, ref item1_hash);
_accumulate_hash(ref rv, ref item2_hash);
_accumulate_hash(ref rv, ref item3_hash);
}
return rv;
}
// Note: be wary of the performance cost this method may have, as frequently creating large arrays can be expensive.
// If you wish to avoid this cost, consider making an overload like the method above, with the appropriate number of item-hash arguments
public static int aggregate_hash(params int[] item_hashes) {
int hash = _hash_seed;
unchecked {
foreach (int item_hash in item_hashes)
hash = hash * _hash_multiplier + item_hash;
}
return hash;
}
// 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 * _hash_multiplier + 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() {
@@ -54,8 +54,9 @@ namespace mbts.ui {
public static TimeTextTuple FromTicks(long ticks) =>
FromDateTime(new DateTime(ticks));
public static TimeTextTuple FromDateTime(DateTime date_time) =>
new TimeTextTuple(DateTimeFormatInfo.CurrentInfo.AbbreviatedMonthNames[date_time.Month - 1], date_time.Day.ToString(), date_time.Year.ToString(), date_time.ToString("t"));
public static TimeTextTuple FromDateTime(DateTime date_time) => FromDateTime(date_time, "t");
public static TimeTextTuple FromDateTime(DateTime date_time, string time_format) =>
new TimeTextTuple(DateTimeFormatInfo.CurrentInfo.AbbreviatedMonthNames[date_time.Month - 1], date_time.Day.ToString(), date_time.Year.ToString(), date_time.ToString(time_format));
public TimeTextTuple(string month_text, string day_text, string year_text, string time_text) {
_month_text = month_text;
@@ -107,14 +108,23 @@ namespace mbts.ui {
static DateTime? _parse(string month_text, string day_text, string year_text, string time_text) {
DateTime? t = _parse(time_text);
TimeSpan? t = _parse_time(time_text);
if (t.HasValue) {
DateTime? d = _parse($"{month_text}/{day_text}/{year_text}");
if (d.HasValue) return d.Value.Date.AddDays(t.Value.TimeOfDay.TotalDays);
if (d.HasValue) return d.Value.Date.Add(t.Value);
}
return null;
}
static TimeSpan? _parse_time(string text) {
DateTime? dt = _parse(text);
if (dt.HasValue) {
return dt.Value.TimeOfDay;
} else if (TimeSpan.TryParse(text, out TimeSpan result) && result < TimeSpan.FromDays(1)) {
return result;
} else return null;
}
static DateTime? _parse(string text) {
if (!string.IsNullOrEmpty(text) && DateTime.TryParse(text, out DateTime result))
return result;
@@ -28,7 +28,14 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Text="{Binding Value, RelativeSource={RelativeSource TemplatedParent}, Converter={StaticResource TickDisplayTwoWayConverter}, Mode=TwoWay}"
/>
>
<TextBox.CommandBindings>
<CommandBinding Command="{x:Static u:TextBoxBehavior.SelectAllCommand}" Executed="{x:Static u:TextBoxBehavior.SelectAllCommandExecutedHandler}"/>
</TextBox.CommandBindings>
<TextBox.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{x:Static u:TextBoxBehavior.SelectAllCommand}"/>
</TextBox.InputBindings>
</TextBox>
</c:TickUpDown>
</ControlTemplate>
<Style TargetType="usp:PriceEntryControl">
@@ -47,24 +47,30 @@ namespace mbts.ui.spark {
#endregion
TextBox _text;
IDisposable _text_input_hook;
public PriceEntryControl() {
TickUpCommand = new DelegateCommand(_tick_up);
TickDownCommand = new DelegateCommand(_tick_down);
}
IDisposable _keyboard_focus_sub;
bool _has_keyboard_focus;
public override void OnApplyTemplate() {
Disposable.dispose(ref _keyboard_focus_sub);
_text = this.assert_template_child<TextBox>("_text");
Disposable.dispose(ref _text_input_hook);
_text_input_hook = _text.SubscribePreviewTextInput((s, e) => e.Handled = !RegexFilters.PriceInput.IsMatch(_text.PeekText(e)));
_keyboard_focus_sub = _text.SubscribeKeyboardFocus(val => _has_keyboard_focus = val);
_update_text_binding();
}
void _tick_up() {
if (_has_keyboard_focus) {
_text.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
}
double v = Value;
if (double.IsNaN(v)) SetCurrentValue(ValueProperty, TickDefaultValue);
else {
if (double.IsNaN(v)) {
SetCurrentValue(ValueProperty, TickDefaultValue);
} else {
var spec = ContractSpec;
if (spec.HasValue) SetCurrentValue(ValueProperty, spec.Value.tickup(v));
else SetCurrentValue(ValueProperty, TickDefaultValue);
@@ -72,9 +78,13 @@ namespace mbts.ui.spark {
}
void _tick_down() {
if (_has_keyboard_focus) {
_text.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
}
double v = Value;
if (double.IsNaN(v)) SetCurrentValue(ValueProperty, TickDefaultValue);
else {
if (double.IsNaN(v)) {
SetCurrentValue(ValueProperty, TickDefaultValue);
} else {
var spec = ContractSpec;
if (spec.HasValue) SetCurrentValue(ValueProperty, spec.Value.tickdown(v));
else SetCurrentValue(ValueProperty, TickDefaultValue);
@@ -23,38 +23,42 @@ namespace mbts.ui.controls {
}
protected override void OnPreviewKeyDown(KeyEventArgs e) {
switch (Keyboard.Modifiers) {
case ModifierKeys.None: {
switch (e.Key) {
case Key.Up:
TickUpCommand.TryExecuteIfCan(TickUpParameter);
e.Handled = true;
break;
case Key.Down:
TickDownCommand.TryExecuteIfCan(TickDownParameter);
e.Handled = true;
break;
if (EnableArrowKeyTick) {
switch (Keyboard.Modifiers) {
case ModifierKeys.None: {
switch (e.Key) {
case Key.Up:
TickUpCommand.TryExecuteIfCan(TickUpParameter);
e.Handled = true;
break;
case Key.Down:
TickDownCommand.TryExecuteIfCan(TickDownParameter);
e.Handled = true;
break;
}
}
}
break;
case ModifierKeys.Alt: {
// see: https://stackoverflow.com/questions/3099472/previewkeydown-is-not-seeing-alt-modifiers
switch (e.SystemKey) {
case Key.Up:
TickUpCommand.TryExecuteIfCan(TickUpAltParameter);
e.Handled = true;
break;
case Key.Down:
TickDownCommand.TryExecuteIfCan(TickDownAltParameter);
e.Handled = true;
break;
break;
case ModifierKeys.Alt: {
// see: https://stackoverflow.com/questions/3099472/previewkeydown-is-not-seeing-alt-modifiers
switch (e.SystemKey) {
case Key.Up:
TickUpCommand.TryExecuteIfCan(TickUpAltParameter);
e.Handled = true;
break;
case Key.Down:
TickDownCommand.TryExecuteIfCan(TickDownAltParameter);
e.Handled = true;
break;
}
}
}
break;
break;
}
}
base.OnPreviewKeyDown(e);
}
public bool EnableArrowKeyTick { get; set; } = true;
#region TickCommand
public ICommand TickCommand {
get { return (ICommand)GetValue(TickCommandProperty); }
@@ -31,8 +31,14 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<c:TickUpDown x:Name="PART_time_tick" Grid.Column="0" MinWidth="85" HorizontalAlignment="Stretch">
<TextBox x:Name="PART_time_text" Height="20" u:TextBoxBehavior.EnableArrowKeyTraverse="True" u:Behavior.DisablePaste="True"/>
<c:TickUpDown x:Name="PART_time_tick" Grid.Column="0" MinWidth="85" HorizontalAlignment="Stretch" EnableArrowKeyTick="False">
<TextBox x:Name="PART_time_text" Height="20" u:TextBoxBehavior.EnableArrowKeyTraverse="True" u:Behavior.DisablePaste="True">
<TextBox.InputBindings>
<KeyBinding Key="Up" Command="{x:Static c:DateTimePicker.TickTimeComponentCommand}" CommandParameter="{u:Int 1}"/>
<KeyBinding Key="Down" Command="{x:Static c:DateTimePicker.TickTimeComponentCommand}" CommandParameter="{u:Int -1}"/>
<u:CustomInputBinding Gesture="{x:Static u:InputGestures.LeftTripleClick}" Command="{x:Static u:TextBoxBehavior.SelectAllCommand}"/>
</TextBox.InputBindings>
</TextBox>
</c:TickUpDown>
<c:TickUpDown x:Name="PART_month_tick" Grid.Column="2" Width="50">
<TextBox x:Name="PART_month_text" Height="20" u:TextBoxBehavior.EnableArrowKeyTraverse="True" u:Behavior.DisablePaste="True"/>
@@ -3,6 +3,7 @@ using mbts.ui.shell;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
@@ -14,7 +15,9 @@ namespace mbts.ui.controls {
/// Interaction logic for DateTimePicker.xaml
/// </summary>
public class DateTimePicker : Control {
TimeTupleVM _vm;
public static readonly RoutedCommand TickTimeComponentCommand = new RoutedCommand();
DTPController _controller;
bool _suspend_notify_vm;
static DateTimePicker() =>
@@ -24,8 +27,8 @@ namespace mbts.ui.controls {
new FrameworkPropertyMetadata(default(TimeTextTuple), FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, OnTimeTupleChanged));
static void OnTimeTupleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var dtp = (DateTimePicker)d;
if (!dtp._suspend_notify_vm && dtp._vm != null)
dtp._vm.result = (TimeTextTuple)e.NewValue;
if (!dtp._suspend_notify_vm && dtp._controller != null)
dtp._controller.result = (TimeTextTuple)e.NewValue;
}
public TimeTextTuple TimeTuple { get => (TimeTextTuple)GetValue(TimeTupleProperty); set => SetValue(TimeTupleProperty, value); }
@@ -41,59 +44,74 @@ namespace mbts.ui.controls {
var time = this.assert_template_child<TickUpDown>("PART_time_tick");
var time_text = this.assert_template_child<TextBox>("PART_time_text");
if (_vm != null) _vm.PropertyChanged -= _vm_PropertyChanged;
_vm = new TimeTupleVM();
_vm.result = TimeTuple;
_vm.PropertyChanged += _vm_PropertyChanged;
if (_controller != null) _controller.PropertyChanged -= _vm_PropertyChanged;
_controller = new DTPController(time_text);
_controller.result = TimeTuple;
_controller.PropertyChanged += _vm_PropertyChanged;
time.CommandBindings.Add(new CommandBinding(TickTimeComponentCommand, (s, e) => {
_controller.tick_time_component((int)e.Parameter);
}));
time.CommandBindings.Add(new CommandBinding(TextBoxBehavior.SelectAllCommand, TextBoxBehavior.SelectAllCommandExecutedHandler));
const int delay = 500;
time.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_time") { Source = _vm });
time.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_time") { Source = _controller });
time.TickUpParameter = 1;
time.TickDownParameter = -1;
time.TickUpAltParameter = 60;
time.TickDownAltParameter = -60;
time_text.SetBinding(TextBox.TextProperty, new Binding("time_text") { Source = _vm, Delay = delay, ValidatesOnExceptions = true });
_hooks.Add(time_text.SubscribePreviewTextInput((s, e) =>
e.Handled = !MatchAny(time_text.PeekText(e), RegexFilters.Time.StandardTime, RegexFilters.Time.MilitaryTime)
));
time_text.SetBinding(TextBox.TextProperty, new Binding("time_text") { Source = _controller, Delay = delay, ValidatesOnExceptions = true });
_hooks.Add(time_text.SubscribePreviewTextInput((s, e) => {
string preview = time_text.PeekText(e);
if (RegexFilters.Time.StandardTimeOptionalSeconds.IsMatch(preview)) {
_controller.use_military_time = false;
} else if (RegexFilters.Time.MilitaryTimeOptionalSeconds.IsMatch(preview)) {
_controller.use_military_time = true;
} else {
e.Handled = true;
}
}));
month.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_month") { Source = _vm });
month.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_month") { Source = _controller });
month.TickUpParameter = 1;
month.TickDownParameter = -1;
month_text.SetBinding(TextBox.TextProperty, new Binding("month_text") { Source = _vm, Delay = delay });
month_text.SetBinding(TextBox.TextProperty, new Binding("month_text") { Source = _controller, Delay = delay });
_hooks.Add(month_text.SubscribePreviewTextInput((s, e) =>
e.Handled = !RegexFilters.Time.MonthAbbrev.IsMatch(month_text.PeekText(e))
));
day.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_day") { Source = _vm });
day.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_day") { Source = _controller });
day.TickUpParameter = 1;
day.TickDownParameter = -1;
day_text.SetBinding(TextBox.TextProperty, new Binding("day_text") { Source = _vm, Delay = delay });
day_text.SetBinding(TextBox.TextProperty, new Binding("day_text") { Source = _controller, Delay = delay });
_hooks.Add(day_text.SubscribePreviewTextInput((s, e) =>
e.Handled = !RegexFilters.Time.DaysInMonth.IsMatch(day_text.PeekText(e))
));
year.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_year") { Source = _vm });
year.SetBinding(TickUpDown.TickCommandProperty, new Binding("tick_year") { Source = _controller });
year.TickUpParameter = 1;
year.TickDownParameter = -1;
year_text.SetBinding(TextBox.TextProperty, new Binding("year_text") { Source = _vm, Delay = delay });
year_text.SetBinding(TextBox.TextProperty, new Binding("year_text") { Source = _controller, Delay = delay });
_hooks.Add(year_text.SubscribePreviewTextInput((s, e) =>
e.Handled = !RegexFilters.Time.Year.IsMatch(year_text.PeekText(e))
));
}
void _vm_PropertyChanged(object sender, PropertyChangedEventArgs e) {
if (e.PropertyName == nameof(TimeTupleVM.result)) {
if (e.PropertyName == nameof(DTPController.result)) {
_suspend_notify_vm = true;
TimeTuple = _vm.result;
TimeTuple = _controller.result;
_suspend_notify_vm = false;
}
}
private class TimeTupleVM : ViewModelBase {
// This is not exactly a view-model, bc it accesses the time TextBox directly so it can get/set the CaretIndex,
// which is used to determine which time-componenet - hh:mm:ss AM/PM - should be incremented/decremented via the up/down arrow-keys.
class DTPController : ViewModelBase {
readonly TextBox _time_text_box;
TimeTextTuple _result;
public TimeTupleVM() {
public DTPController(TextBox time_text_box) {
_time_text_box = time_text_box;
tick_time = new DelegateCommand<int>(_tick_time);
tick_month = new DelegateCommand<int>(_tick_month);
tick_day = new DelegateCommand<int>(_tick_day);
@@ -135,12 +153,52 @@ namespace mbts.ui.controls {
RaisePropertyChanged(nameof(month_text), nameof(day_text), nameof(year_text), nameof(time_text));
}
}
public bool use_military_time { get; set; }
public ICommand tick_time { get; }
public ICommand tick_month { get; }
public ICommand tick_day { get; }
public ICommand tick_year { get; }
string _time_format => use_military_time ? StringFormats.Time.MilitaryWithSeconds : StringFormats.Time.StandardWithSeconds;
public void tick_time_component(int increment) {
_ensure_time_text_up_to_date();
var r = _result.result;
if (r.HasValue) {
int index = _time_text_box.CaretIndex;
using (detect_property_changes()) {
DateTime dt;
if (index <= 0) {
dt = r.Value.AddHours(increment);
} else {
int segment = _time_text_box.Text.Take(index).Count(c => c == ':');
switch (segment) {
case 0: dt = r.Value.AddHours(increment); break;
case 1: dt = r.Value.AddMinutes(increment); break;
case 2:
if (_time_text_box.Text.Take(index).Contains(' ')) {
// toggle AM/PM
dt = r.Value.AddHours(r.Value.Hour < 12 ? 12 : -12);
} else {
dt = r.Value.AddSeconds(increment);
}
break;
default:
dt = r.Value.AddSeconds(increment);
break;
}
}
_result = TimeTextTuple.FromDateTime(dt, _time_format);
}
_time_text_box.CaretIndex = index;
} else {
using (detect_property_changes())
_force_valid_inputs();
}
}
void _tick_time(int increment) {
_ensure_time_text_up_to_date();
using (detect_property_changes()) {
var r = _result.result;
if (r.HasValue) {
@@ -187,6 +245,11 @@ namespace mbts.ui.controls {
}
}
}
void _ensure_time_text_up_to_date() {
if (_time_text_box.IsKeyboardFocused)
_time_text_box.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
}
void _force_valid_inputs() {
var dt = DateTime.Today.Date;
@@ -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); }
@@ -0,0 +1,16 @@
using System.Windows.Input;
namespace mbts.ui {
class CustomInputBinding : InputBinding { }
static class InputGestures {
public static readonly InputGesture LeftTripleClick = new LeftTripleClickGesture();
class LeftTripleClickGesture : InputGesture {
public override bool Matches(object targetElement, InputEventArgs inputEventArgs) =>
inputEventArgs is MouseButtonEventArgs mouse_args &&
mouse_args.ChangedButton == MouseButton.Left &&
mouse_args.ClickCount == 3;
}
}
}
@@ -7,7 +7,14 @@ using System.Windows.Input;
namespace mbts.ui {
static class TextBoxBehavior {
public static readonly RoutedCommand ReleaseCaptureCommand = new RoutedCommand();
#region RoutedCommands/Handlers
public static readonly RoutedCommand
ReleaseCaptureCommand = new RoutedCommand(),
SelectAllCommand = new RoutedCommand();
public static readonly ExecutedRoutedEventHandler SelectAllCommandExecutedHandler =
new ExecutedRoutedEventHandler((s, e) => (e.OriginalSource as TextBox ?? e.Source as TextBox ?? s as TextBox)?.SelectAll());
#endregion
#region SelectAllOnFocus
@@ -168,7 +175,7 @@ namespace mbts.ui {
public static void SetInputFilter(DependencyObject d, TextBoxInputFilter value) => d.SetValue(InputFilterProperty, value);
#endregion
#region
#region IncrementDecrementOnMouseClick
public static readonly DependencyProperty IncrementDecrementOnMouseClickProperty = DependencyProperty.RegisterAttached(
"IncrementDecrementOnMouseClick", typeof(bool), typeof(TextBoxBehavior), new PropertyMetadata(false, IncrementDecrementOnMouseClickPropertyChanged)
);
@@ -2,11 +2,28 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace mbts.ui {
static class TextElementHelper {
public static IDisposable SubscribeKeyboardFocus(this TextBox text_box, Action<bool> callback) {
text_box.GotKeyboardFocus += on_got_keyboard_focus;
text_box.LostKeyboardFocus += on_lost_keyboard_focus;
return Disposable.Create(() => {
text_box.GotKeyboardFocus -= on_got_keyboard_focus;
text_box.LostKeyboardFocus -= on_lost_keyboard_focus;
});
void on_got_keyboard_focus(object sender, KeyboardFocusChangedEventArgs e) {
if (e.NewFocus == text_box)
callback(true);
}
void on_lost_keyboard_focus(object sender, KeyboardFocusChangedEventArgs e) {
if (e.OldFocus == text_box)
callback(false);
}
}
namespace mbts.ui
{
public class TextElementHelper
{
public static void GetTargetProperty(DependencyObject d, out FrameworkElement text_element, out DependencyProperty text_property) {
text_element = (FrameworkElement)d;
if (text_element is TextBox) text_property = TextBox.TextProperty;
@@ -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;
+1 -1
View File
@@ -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);
}
}
@@ -533,6 +533,7 @@ namespace mbts.spark {
_visualHost.AddVisualChild(_mid_mkt_break_vis);
_visualHost.AddVisualChild(_ask_mkt_break_vis);
_visualHost.AddVisualChild(_bid_mkt_break_vis);
_render_grid_background();
_init_tactile_adorner();
// this effectively sets the background of the "Last" column
if (_scrollViewer != null) {
@@ -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.orderman;
using mbts.ui;
using mbts.utils;
using ProtoBuf;
using System;
using System.Linq;
@@ -124,8 +125,6 @@ namespace mbts.spark {
last_column = last_column,
calculator_buttons = new[] { 1, 5, 10, 25, 50, 100 },
scroll_bar_visible = false,
ticks_to_recenter = 10,
seconds_to_recenter = 300,
default_order_type = CustomOrderDefinition.CustomOneofCase.Limit,
default_time_in_force = TimeInForce.Day,
animate_recenter = true,
@@ -163,10 +162,8 @@ namespace mbts.spark {
public int[] calculator_buttons { get; set; }
[ProtoMember(10)]
public bool scroll_bar_visible { get; set; }
[ProtoMember(11)]
public int ticks_to_recenter { get; set; }
[ProtoMember(12)]
public int seconds_to_recenter { get; set; }
// USED [ProtoMember(11)]
// USED [ProtoMember(12)]
[ProtoMember(13)]
public CustomOrderDefinition.CustomOneofCase default_order_type { get; set; }
[ProtoMember(14)]
@@ -197,11 +194,8 @@ namespace mbts.spark {
other.price_column.safe_equals(price_column) &&
other.calculator_buttons.safe_equals(calculator_buttons, (a, b) => a.SequenceEqual(b)) &&
other.scroll_bar_visible == scroll_bar_visible &&
other.ticks_to_recenter == ticks_to_recenter &&
other.seconds_to_recenter == seconds_to_recenter &&
other.default_order_type == default_order_type &&
other.default_time_in_force == default_time_in_force &&
other.seconds_to_recenter == seconds_to_recenter &&
other.size == size &&
other.animate_recenter == animate_recenter &&
other.badge_style == badge_style &&
@@ -213,26 +207,24 @@ namespace mbts.spark {
public override bool Equals(object obj) => Equals(obj as LadderSettings);
public override int GetHashCode() =>
row_height.GetHashCode() +
grid_line_color.GetHashCode() +
orders_column.GetHashCode() +
bid_column.GetHashCode() +
ask_column.GetHashCode() +
price_column.GetHashCode() +
last_column.GetHashCode() +
calculator_buttons.GetHashCode() +
scroll_bar_visible.GetHashCode() +
ticks_to_recenter.GetHashCode() +
seconds_to_recenter.GetHashCode() +
default_order_type.GetHashCode() +
default_time_in_force.GetHashCode() +
seconds_to_recenter.GetHashCode() +
size.GetHashCode() +
animate_recenter.GetHashCode() +
badge_style.GetHashCode() +
bid_break_line.GetHashCode() +
ask_break_line.GetHashCode() +
mid_break_line.GetHashCode() +
alternate_order_size.GetHashCode();
EqualityComparer.aggregate_hash(
row_height.GetHashCode(),
grid_line_color.GetHashCode(),
orders_column.GetHashCode(),
bid_column.GetHashCode(),
ask_column.GetHashCode(),
price_column.GetHashCode(),
last_column.GetHashCode(),
calculator_buttons.GetHashCode(),
scroll_bar_visible.GetHashCode(),
default_order_type.GetHashCode(),
default_time_in_force.GetHashCode(),
size.GetHashCode(),
animate_recenter.GetHashCode(),
badge_style.GetHashCode(),
bid_break_line.GetHashCode(),
ask_break_line.GetHashCode(),
mid_break_line.GetHashCode(),
alternate_order_size.GetHashCode());
}
}
+15 -19
View File
@@ -1,5 +1,6 @@
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.spark;
using mbts.co2;
using mbts.persist;
@@ -22,15 +23,18 @@ namespace mbts.spark {
readonly LadderModel _model;
readonly Property<double> _high, _low, _change, _open, _settlement;
readonly Property<ulong> _volume;
readonly Property<bool> _revert;
readonly Property<bool> _auto_clear;
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);
@@ -38,7 +42,7 @@ namespace mbts.spark {
_open = backing(nameof(open), double.NaN);
_settlement = backing(nameof(settlement), double.NaN);
_volume = backing(nameof(volume), 0ul);
_revert = backing(nameof(revert), false);
_auto_clear = backing(nameof(auto_clear), false);
_status = backing(nameof(status_message), "");
_status_brush = backing(nameof(status_background), (Brush)BTSColors.UI.spark_menu_background);
// brush props
@@ -47,7 +51,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 +73,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 +156,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));
@@ -170,7 +174,7 @@ namespace mbts.spark {
public double open => _open.Value;
public double settlement => _settlement.Value;
public ulong volume => _volume.Value;
public bool revert { get => _revert.Value; set => _revert.set(value); }
public bool auto_clear { get => _auto_clear.Value; set => _auto_clear.set(value); }
public CalculatorLogic size_calculator { get; }
public OrderPropertiesEditorVM order_properties_vm { get; }
public ObservableCollection<LadderOrderVM> orders { get; }
@@ -226,16 +230,8 @@ namespace mbts.spark {
uint size = args.size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value.Value);
await _model.order_gateway.send_order(order, args.price, size);
if (revert) {
size_calculator.value = s.size;
if (order_properties_vm.selected_time_in_force != s.default_time_in_force && order_properties_vm.time_in_forces.Contains(s.default_time_in_force))
order_properties_vm.selected_time_in_force = s.default_time_in_force;
foreach (var ot in order_properties_vm.order_types) {
ot.load_from(_model.order_gateway.factory.custom_order(ot.order_type));
if (ot.order_type == s.default_order_type)
order_properties_vm.selected_order_type = ot;
}
}
if (auto_clear)
size_calculator.value = 0;
}
}
@@ -394,7 +394,7 @@
</ItemsControl>
<Button Margin="2" HorizontalAlignment="Stretch" Command="{Binding size_calculator.clear}">Clear</Button>
<!-- reset to defaults after order -->
<CheckBox Margin="2" IsChecked="{Binding revert}">Auto Clear</CheckBox>
<CheckBox Margin="2" IsChecked="{Binding auto_clear}">Auto Clear</CheckBox>
</StackPanel>
<Grid Grid.Row="1">
<Grid.RowDefinitions>
@@ -17,16 +17,17 @@ namespace mbts.spark {
};
SecurityImmutable? _security;
MDConsumerHandle _handle;
LadderSettings _settings;
DateTime _last_recenter = DateTime.MaxValue;
double _last_mid = 0.0;
IDisposable _dispose;
readonly TimeSpan _seconds_to_center;
public RecenterCountdown() {
DataContextChanged += RecenterCountdown_DataContextChanged;
Unloaded += (o,e) => on_unload(false);
_timer.Tick += _timer_Tick;
_timer.Start();
_seconds_to_center = TimeSpan.FromSeconds(config.Configuration.get<bts.config.LadderKnobs>().seconds_to_recenter);
}
static (double,double) tick_range(double price, int tick_count, ContractSpecificationImmutable spec) {
@@ -34,11 +35,10 @@ namespace mbts.spark {
}
private void _timer_Tick(object sender, EventArgs e) {
if (_settings != null && _security != null) {
if (_security != null) {
var mid = _handle.mid();
(double low, double high) = tick_range(_last_mid, _settings.ticks_to_recenter, _security.Value.ContractSpec);
if (//mid < low || mid > high ||
(_last_recenter + TimeSpan.FromSeconds(_settings.seconds_to_recenter) < DateTime.UtcNow)) {
(_last_recenter + _seconds_to_center < DateTime.UtcNow)) {
trigger_recenter(mid);
}
}
@@ -59,7 +59,6 @@ namespace mbts.spark {
vm.OnDisposed += on_unload;
_dispose =
Disposable.Create(
vm.settings.Subscribe(s => _settings = s),
Disposable.Create(() => vm.OnDisposed -= on_unload));
}
}
@@ -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);
}
}
}
+2 -2
View File
@@ -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;
}
+9 -16
View File
@@ -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>();
@@ -10,7 +10,7 @@ namespace mbts.spark {
static readonly TimeSpan default_interval;
static WorkLoop() {
var timer_params = config.Configuration.get<bts.config.LadderTimerParams>();
var timer_params = config.Configuration.get<bts.config.LadderKnobs>();
default_priority = Enum.TryParse(timer_params.priority, true, out DispatcherPriority p) ? p : DispatcherPriority.ApplicationIdle;
default_interval = TimeSpan.FromMilliseconds(timer_params.interval_ms);
}
@@ -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)
@@ -130,12 +130,22 @@ namespace mbts.ui.utility.text {
public static class Time {
public static readonly Regex
StandardTime = new Regex(@"^((0?[0-9])|(1?[0-2]?))(:?|:([0-5]?|[0-5][0-9]?)) ?(?i)((a|p)m?)?$"),
StandardTimeOptionalSeconds = new Regex(@"^((\d?|0\d|1[0-2])|((\d|0\d|1[0-2])(:?|(:([0-5]?|[0-5]\d(( ?|( ([aApP]?|[aApP][mM])))(:?|(: ?)|(: [aApP]?[mM]?)|(:[0-5]?\d? ?[aApP]?[mM]?))|:([0-5]?|([0-5]\d( ?|( ([aApP]?|([aApP][mM]))))))))))))$"),
MilitaryTime = new Regex(@"^(([0-1]?[0-9])|(2?[0-3]?))(:?|:([0-5]?|[0-5][0-9]?))$"),
MilitaryTimeOptionalSeconds = new Regex(@"^((\d?|[0-1]\d|2[0-3])|((\d|[0-1]\d|2[0-3])(:?|(:([0-5]?|[0-5]\d(:?|:([0-5]?|[0-5]\d)))))))$"),
DaysInMonth = new Regex(@"^([0-9]?|[0-2][0-9]|3[0-1])?$"),
MonthAbbrev = new Regex(@"^(?i)(a(ug?|pr?)?|d(e|ec)?|f(e?b)?|j(an?|u[nl]?)?|m(a[ry]?)?|(o(ct?)?)|s(ep?)?|n(ov?)?)?$"),
Year = new Regex(@"^\d{0,4}$"),
TimeSpan = new Regex(@"^(\d+(:?|:([0-5]?|[0-5]\d(:?|:([0-5]?|[0-5]\d)))))?$");
}
}
public static class StringFormats {
public static class Time {
public const string
MilitaryWithSeconds = "HH:mm:ss",
StandardWithSeconds = "hh:mm:ss tt";
}
}
}
}
+25 -6
View File
@@ -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);
}
});
});
}
}
}
+2 -13
View File
@@ -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;
}
}
}
@@ -901,7 +901,7 @@ bool AdminServiceUtils::match_security_scope (const bts::api::admin::SecuritySco
bool AdminServiceUtils::match_product_scope (const bts::api::admin::ProductScope& scope,
const bts::api::Product& product){
return product.exchange() == scope.exchange() &&
product.type() && scope.security_type() &&
product.type() == scope.security_type() &&
_match_limit_rule_symbol(product.symbol(), scope.security_symbol());
}
bool AdminServiceUtils::match_product(const api::Security& security,
+1
View File
@@ -263,6 +263,7 @@ message CMEUserData{
map<string, string> misc_tags = 60;
string smp_instruction = 70;
string max_order_rate_per_connection = 80;
string location = 90;
}
message CMEUserMarketDataEntitlementData{
+1 -1
View File
@@ -233,7 +233,7 @@ enum CTICode {
CTI_CODE_CTI_CODE1 = 49;
CTI_CODE_CTI_CODE2 = 50;
CTI_CODE_CTI_CODE3 = 51;
CTI_CODE_CTI4 = 52;
CTI_CODE_CTI_CODE4 = 52;
}
enum NativeOrderStatus
+1 -6
View File
@@ -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);
+5 -17
View File
@@ -28,20 +28,6 @@ message IShellModuleProps
repeated ModuleProps module = 1;
}
message IShellWindowProps {
// TODO: Maybe IWindowManager should be responsible for total-windows-open limits?
// To achieve that we would need to use a sort of type-windowprops map.
// However, that makes the problem more complicated, and at this point we are just aiming to limit n_ladders open.
// So for now, just let the LadderWindowFactory manage this.
message WindowProps {
// NOTE: 50 will be a dangerously high default for most other windows (than Ladder),
// so be careful if you add other WindowProps members.
optional uint32 max_total_open = 1 [default = 50];
}
optional WindowProps ladder = 1;
}
message GridSettings {
optional string font_family = 1 [default = "Calibri"];
optional float font_size = 2 [default = 10];
@@ -62,9 +48,11 @@ message ShellLimits {
optional uint64 limit_group_memory_mb = 3;
}
// priorities (ordered): SystemIdle, ApplicationIdle, ContextIdle, Background, Input, Loaded, Render, DataBind, Normal, Send
// It seems like all priorities higher than ApplicationIdle choke the main thread.
message LadderTimerParams {
message LadderKnobs {
// priorities (ordered): SystemIdle, ApplicationIdle, ContextIdle, Background, Input, Loaded, Render, DataBind, Normal, Send
// It seems like all priorities higher than ApplicationIdle choke the main thread.
optional string priority = 1 [default = "ApplicationIdle"];
optional double interval_ms = 2 [default = 100];
optional uint32 seconds_to_recenter = 3 [default = 300];
optional uint32 max_open_ladders = 4 [default = 75];
}
+5 -2
View File
@@ -71,14 +71,17 @@ message JetFIX {
optional bool log_fix_stream = 260 [default=false];
optional bool use_persisted_quote_ids = 270 [default=true];
optional uint32 quote_id_reservation_size = 290 [default=100000];
optional uint32 poll_period_ms = 300 [default=100];
optional bool use_dedicated_core = 310 [default=false];
optional uint32 cme_market_segment_id = 320 [default=0];
optional string trading_session_id = 330 [default=""];
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;
+16 -16
View File
@@ -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";
}
}
+6 -2
View File
@@ -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;
+58 -21
View File
@@ -15,6 +15,9 @@
#include "lib/api/apicore.pb.h"
#include "lib/api/services/MarketDataRelay.server.pb.h"
#include "lib/api/public/ServiceContext.h"
#ifndef _WIN32
#include "server/sparkd/metrics/metrics.h"
#endif
#include <vector>
#include <list>
@@ -61,13 +64,29 @@ struct DatedHandle {
struct MDConsumerGroup {
// probably just have to send the consumer in from outside
MDConsumerGroup(const UserSymbol& symbol, const MDConsumer& consumer) :
MDConsumerGroup(const UserSymbol& symbol, const MDConsumer& consumer, metrics::MetricsContainer& stats) :
symbol(symbol),
platform(platform_for_exchange(symbol.exchange())),
channel(consumer.get_channel(symbol)),
handles()
{
set_consumer(consumer);
// create and publish a stat for this group
#ifndef _WIN32
if (symbol.type() != SecurityType::SECURITYTYPE_SYNTHETIC) {
std::vector<metrics::Label> labels;
labels.push_back({ "symbol", symbol.symbol() });
labels.push_back({ "type", SecurityType_Name(symbol.type()).substr(13) /*SECURITYTYPE_*/ });
labels.push_back({ "exch", Venue_Name(symbol.exchange()).substr(6) /*VENUE_*/ });
stats.publish(
std::make_shared<metrics::LazyCounterMetric>("spark_md_book_updates", [=]() { return std::max(channel.bbo_count(), channel.depth_count()); }, labels));
stats.publish(
std::make_shared<metrics::LazyCounterMetric>("spark_md_security_count", [=]() { return channel.security_count(); }, labels));
stats.publish(
std::make_shared<metrics::LazyCounterMetric>("spark_md_trade_count", [=]() { return channel.trade_count(); }, labels));
}
#endif
}
MDConsumerGroup(const MDConsumerGroup& other) = delete;
@@ -109,6 +128,7 @@ void set_price(PriceSize ps, co2::TradeMessage* ptrade) {
ptrade->set_exponent(ps.exponent());
ptrade->set_size(ps.size());
}
void copy_misc(MiscData misc, co2::MiscMessage* pmisc) {
pmisc->set_settlement_price(misc.settlement_price);
pmisc->set_settlement_date(misc.settlement_date.to_short());
@@ -134,14 +154,14 @@ class QueryServerImpl : public IQueryServer {
boost::container::flat_map<UserSymbol,shared_ptr<MDConsumerGroup>> _groups;
public:
QueryServerImpl(const std::set<UserSymbol>& symbols, std::shared_ptr<api::ISyntheticSecurityService>& ss) :
QueryServerImpl(const std::set<UserSymbol>& symbols, std::shared_ptr<api::ISyntheticSecurityService>& ss, metrics::MetricsContainer& stats) :
_consumer(symbols, ss),
_symbols(symbols),
_ss(ss),
_groups()
{
for (const auto& sym : symbols) {
_groups.emplace(std::make_pair(sym, std::make_unique<MDConsumerGroup>(sym, _consumer)));
_groups.emplace(std::make_pair(sym, std::make_unique<MDConsumerGroup>(sym, _consumer, stats)));
}
_groups.shrink_to_fit();
@@ -336,24 +356,36 @@ public:
struct MDRelayServer : bts::co2::MarketDataRelayBase {
const QueryServerImpl& _db;
utils::Dispatcher& _threadpool;
MDRelayServer(const QueryServerImpl& db, utils::Dispatcher& threadpool, bts::api::ServiceContext ctx) :
#ifndef _WIN32
std::shared_ptr<metrics::HistogramMetric<5>> _time,_size;
#endif
MDRelayServer(const QueryServerImpl& db, utils::Dispatcher& threadpool, bts::api::ServiceContext ctx, metrics::MetricsContainer& stats) :
bts::co2::MarketDataRelayBase(ctx),
_db(db), _threadpool(threadpool)
{ }
#ifndef _WIN32
, _time(std::make_shared<metrics::HistogramMetric<5>>("spark_qserver_response_time_ms", std::array<double,5>({ 10.0, 50.0, 100.0, 500.0, 1000.0 }))),
_size(std::make_shared<metrics::HistogramMetric<5>>("spark_qserver_size_bytes", std::array<double,5>({ 1024 /*kb*/, 1024*1024 /*mb*/, (1024*1024)*4, (1024*1024)*16, (1024*1024)*32 })))
{
stats.publish(_time);
stats.publish(_size);
}
#else
{}
#endif
virtual void requestmd(const MDRequest & request, const std::function<void(const MDReply&)>& reply) override {
TimeStamp start = TimeStamp::now();
_threadpool.post([=]() {
google::protobuf::Arena arena;
MDReply* preply = google::protobuf::Arena::CreateMessage<MDReply>(&arena);
TimeStamp start = TimeStamp::now();
_db.query(request, preply);
auto elapsed = (TimeStamp::now() - start);
LOG(md, Diag,
"Request for: {} took {}us and returned {} bytes before compression",
request.host(),
elapsed.microseconds(),
preply->ByteSize());
reply(*preply);
auto elapsed = (TimeStamp::now() - start);
#ifndef _WIN32
_time->observe(elapsed.milliseconds());
_size->observe(preply->ByteSize());
#endif
});
}
@@ -382,11 +414,15 @@ struct MDRelayServer : bts::co2::MarketDataRelayBase {
};
struct QueryServer {
QueryServer(std::set<UserSymbol> symbols, utils::Dispatcher& threadpool, const bts::api::ServiceContext& ctx, std::shared_ptr<api::ISyntheticSecurityService>& ss):
db(symbols, ss),
relay(db, threadpool, ctx),
security_timer(),
health_timer()
QueryServer(std::set<UserSymbol> symbols,
utils::Dispatcher& threadpool,
const bts::api::ServiceContext& ctx,
std::shared_ptr<api::ISyntheticSecurityService>& ss,
metrics::MetricsContainer& stats):
db(symbols, ss, stats),
relay(db, threadpool, ctx, stats),
security_timer(),
health_timer()
{ }
QueryServerImpl db;
MDRelayServer relay;
@@ -394,7 +430,7 @@ struct QueryServer {
std::unique_ptr<utils::DispatcherTimer> health_timer;
};
QueryServerPtr run_query_server(const bts::api::ServiceContext& ctx, std::shared_ptr<api::ISyntheticSecurityService>& ss) {
QueryServerPtr run_query_server(const bts::api::ServiceContext& ctx, std::shared_ptr<api::ISyntheticSecurityService>& ss, metrics::MetricsContainer& stats) {
auto& threadpool = utils::Dispatcher::get_named("MDQuery");
std::set<UserSymbol> all_symbols;
@@ -403,7 +439,8 @@ QueryServerPtr run_query_server(const bts::api::ServiceContext& ctx, std::shared
SecurityType type;
bts_assert(api::SecurityType_Parse(config::get<PlatformConfig>().type(), &type));
get_system_md_symbols(venue, type, all_symbols);
QueryServerPtr container = std::make_shared<QueryServer>(all_symbols, threadpool, ctx, ss);
QueryServerPtr container = std::make_shared<QueryServer>(all_symbols, threadpool, ctx, ss, stats);
auto& db = container->db;
// Scan for new securities every half second
@@ -426,8 +463,8 @@ QueryServerPtr run_query_server(const bts::api::ServiceContext& ctx, std::shared
return container;
}
std::unique_ptr<IQueryServer> create_query_server(const std::set<UserSymbol>& symbols, std::shared_ptr<api::ISyntheticSecurityService>& ss) {
return std::make_unique<QueryServerImpl>(symbols, ss);
std::unique_ptr<IQueryServer> create_query_server(const std::set<UserSymbol>& symbols, std::shared_ptr<api::ISyntheticSecurityService>& ss, metrics::MetricsContainer& stats) {
return std::make_unique<QueryServerImpl>(symbols, ss, stats);
}
}}//namespace
+3 -2
View File
@@ -11,6 +11,7 @@
namespace bts {
namespace api { class ServiceContext; }
namespace metrics { class MetricsContainer; }
namespace md {
struct IQueryServer {
@@ -18,11 +19,11 @@ struct IQueryServer {
virtual void scan_securities() = 0;
virtual void query(const co2::MDRequest& request, co2::MDReply* reply) const = 0;
};
std::unique_ptr<IQueryServer> create_query_server(const std::set<UserSymbol>&, std::shared_ptr<api::ISyntheticSecurityService>&);
std::unique_ptr<IQueryServer> create_query_server(const std::set<UserSymbol>&, std::shared_ptr<api::ISyntheticSecurityService>&, metrics::MetricsContainer&);
struct QueryServer;
typedef std::shared_ptr<QueryServer> QueryServerPtr;
QueryServerPtr run_query_server(const bts::api::ServiceContext&, std::shared_ptr<api::ISyntheticSecurityService>&);
QueryServerPtr run_query_server(const bts::api::ServiceContext&, std::shared_ptr<api::ISyntheticSecurityService>&, metrics::MetricsContainer&);
}}//namespace
+12 -1
View File
@@ -10,6 +10,7 @@
#include "lib/utils/defer.h"
#include "lib/utils/warn_ctrl.h"
#include "lib/utils/bts_chrono.h"
#include "server/sparkd/metrics/metrics.h"
#include "lib/config/protos/MarketData.pb.h"
@@ -30,6 +31,15 @@
using namespace std;
namespace bts {
#ifdef _WIN32
namespace metrics {
struct MetricCollection{};
MetricsContainer::MetricsContainer() {}
MetricsContainer::~MetricsContainer() {}
}
#endif
int md_tests = 0;
namespace md {
// import this from RelayClient.cpp
@@ -1275,7 +1285,8 @@ struct Co2RelayTests {
set<UserSymbol> symbols;
symbols.insert(symbol);
shared_ptr<api::ISyntheticSecurityService> empty;
auto server = create_query_server(symbols, empty);
metrics::MetricsContainer stats;
auto server = create_query_server(symbols, empty, stats);
REQUIRE(server);
server->scan_securities();
+1 -1
View File
@@ -1 +1 @@
set(sources cmds/AutoCert.cpp;cmds/Benchmark.cpp;cmds/CmdRegistry.cpp;cmds/ConfigCheck.cpp;cmds/Help.cpp;cmds/Md.cpp;cmds/QuickTest.cpp;cmds/Run.cpp;cmds/RunSim.cpp;cmds/Span.cpp;cmds/Test.cpp;cmds/TestCeloxica.cpp;dspark/mirage.cpp;dspark/mirage/routing/Router.cpp;exchange_sim/ExchangeSim.cpp;exchange_sim/ExchangeSimApplication.cpp;exchange_sim/book/Book.cpp;exchange_sim/book/ExchangeSim.proto;exchange_sim/book/Order.cpp;exchange_sim/book/OrderMatcher.cpp;exchange_sim/book/book_transform.cpp;exchange_sim/tests/test_book_transform.cpp;http/ApiGateway.cpp;http/WebServer.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;main.cpp;md/platforms/celoxica/CXListener.cpp;md/platforms/celoxica/CXMain.cpp;md/platforms/celoxica/tests/cx_tests.cpp;md/platforms/mdp3_onixs/MDP3Listener.cpp;md/platforms/mdp3_onixs/MDP3Main.cpp;md/platforms/mdp3_onixs/MDP3Utils.cpp;md/platforms/mdp3_onixs/tests/mdp3_tests.cpp;md/run_md.cpp;mercury/actors/Actor.cpp;mercury/broker/AsyncEventBroker.cpp;mercury/broker/CoreEventBroker.cpp;mercury/broker/IEventBroker.cpp;mercury/events/TimerEvent.cpp;mercury/mercury_utils/Executor.cpp;mercury/mercury_utils/IPollableActor.cpp;mercury/mercury_utils/mercury_common.cpp;mercury/synopsis/SynopsisBuilder.cpp;mercury/topics/ITopic.cpp;orderman/actors/AsyncFactory.cpp;orderman/actors/ExecutionGateway.cpp;orderman/actors/FillStore.cpp;orderman/actors/OrderStore.cpp;orderman/actors/SecuritySrc.cpp;orderman/actors/ServiceBridge.cpp;orderman/actors/order_manager/OrderBuilder.cpp;orderman/actors/order_manager/OrderManager.cpp;orderman/actors/order_manager/OrderServicesData.cpp;orderman/events/CancelRejectEvent.cpp;orderman/events/NewExecutionReportEvent.cpp;orderman/limiter/LimitStore.cpp;orderman/limiter/Limiter.cpp;orderman/limiter/ProductLimitNode.cpp;orderman/limiter/ProductLimitTree.cpp;orderman/limiter/SecurityLimitNode.cpp;orderman/limiter/SecurityLimitTree.cpp;orderman/limiter/tests/limit_tests.cpp;orderman/order_factory/OrderFactory.cpp;orderman/order_router/OrderRouter.cpp;orderman/orderman_utils/fix_order_codec.cpp;orderman/orders/IcebergOrder.cpp;orderman/orders/NativeOrder.cpp;orderman/orders/Order.cpp;orderman/orders/TimeBoxOrder.cpp;orderman/orders/book_algos/BookAlgo.cpp;orderman/orders/book_algos/JoinOrder.cpp;orderman/orders/book_algos/LeanOrder.cpp;orderman/orders/book_algos/TickOrder.cpp;orderman/orders/book_algos/TopOrder.cpp;orderman/orders/spreader_orders/SpreaderOrder.cpp;orderman/orders/spreader_orders/spreader/LegPropertyData.cpp;orderman/orders/spreader_orders/spreader/QuoteLegPriceQuantityState.cpp;orderman/orders/spreader_orders/spreader/SingleFillHedger.cpp;orderman/orders/spreader_orders/spreader/SingleLegQuoter.cpp;orderman/orders/spreader_orders/spreader/SpreaderBookkeepingData.cpp;orderman/orders/spreader_orders/spreader/SpreaderChildOrderState.cpp;orderman/orders/spreader_orders/spreader/SpreaderLegData.cpp;orderman/orders/spreader_orders/spreader/SpreaderUtils.cpp;orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderOrderBuilder.cpp;orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderOrderServices.cpp;orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderPreActivationOrderServices.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerEndStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerStartStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerWaitingForCancelAckStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerWaitingForFirstAckCancelledStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerWaitingForFirstAckStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerWorkingStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterEndStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterStartStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForCancelAckStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForFirstAckCancelledWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForFirstAckWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForSizeStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWorkingStateWorker.cpp;orderman/Line truncated
set(sources cmds/AutoCert.cpp;cmds/Benchmark.cpp;cmds/CmdRegistry.cpp;cmds/ConfigCheck.cpp;cmds/Help.cpp;cmds/Md.cpp;cmds/QuickTest.cpp;cmds/Run.cpp;cmds/RunSim.cpp;cmds/Span.cpp;cmds/Test.cpp;cmds/TestCeloxica.cpp;dspark/mirage.cpp;dspark/mirage/routing/Router.cpp;exchange_sim/ExchangeSim.cpp;exchange_sim/ExchangeSimApplication.cpp;exchange_sim/book/Book.cpp;exchange_sim/book/ExchangeSim.proto;exchange_sim/book/Order.cpp;exchange_sim/book/OrderMatcher.cpp;exchange_sim/book/book_transform.cpp;exchange_sim/tests/test_book_transform.cpp;http/ApiGateway.cpp;http/WebServer.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;main.cpp;md/platforms/celoxica/CXListener.cpp;md/platforms/celoxica/CXMain.cpp;md/platforms/celoxica/tests/cx_tests.cpp;md/platforms/mdp3_onixs/MDP3Listener.cpp;md/platforms/mdp3_onixs/MDP3Main.cpp;md/platforms/mdp3_onixs/MDP3Utils.cpp;md/platforms/mdp3_onixs/tests/mdp3_tests.cpp;md/run_md.cpp;mercury/actors/Actor.cpp;mercury/broker/AsyncEventBroker.cpp;mercury/broker/CoreEventBroker.cpp;mercury/broker/IEventBroker.cpp;mercury/events/TimerEvent.cpp;mercury/mercury_utils/Executor.cpp;mercury/mercury_utils/IPollableActor.cpp;mercury/mercury_utils/mercury_common.cpp;mercury/synopsis/SynopsisBuilder.cpp;mercury/topics/ITopic.cpp;metrics/metrics.cpp;metrics/system_metrics.cpp;orderman/actors/AsyncFactory.cpp;orderman/actors/ExecutionGateway.cpp;orderman/actors/FillStore.cpp;orderman/actors/OrderStore.cpp;orderman/actors/SecuritySrc.cpp;orderman/actors/ServiceBridge.cpp;orderman/actors/order_manager/OrderBuilder.cpp;orderman/actors/order_manager/OrderManager.cpp;orderman/actors/order_manager/OrderServicesData.cpp;orderman/events/CancelRejectEvent.cpp;orderman/events/NewExecutionReportEvent.cpp;orderman/limiter/LimitStore.cpp;orderman/limiter/Limiter.cpp;orderman/limiter/ProductLimitNode.cpp;orderman/limiter/ProductLimitTree.cpp;orderman/limiter/SecurityLimitNode.cpp;orderman/limiter/SecurityLimitTree.cpp;orderman/limiter/tests/limit_tests.cpp;orderman/order_factory/OrderFactory.cpp;orderman/order_router/OrderRouter.cpp;orderman/orderman_utils/fix_order_codec.cpp;orderman/orders/IcebergOrder.cpp;orderman/orders/NativeOrder.cpp;orderman/orders/Order.cpp;orderman/orders/TimeBoxOrder.cpp;orderman/orders/book_algos/BookAlgo.cpp;orderman/orders/book_algos/JoinOrder.cpp;orderman/orders/book_algos/LeanOrder.cpp;orderman/orders/book_algos/TickOrder.cpp;orderman/orders/book_algos/TopOrder.cpp;orderman/orders/spreader_orders/SpreaderOrder.cpp;orderman/orders/spreader_orders/spreader/LegPropertyData.cpp;orderman/orders/spreader_orders/spreader/QuoteLegPriceQuantityState.cpp;orderman/orders/spreader_orders/spreader/SingleFillHedger.cpp;orderman/orders/spreader_orders/spreader/SingleLegQuoter.cpp;orderman/orders/spreader_orders/spreader/SpreaderBookkeepingData.cpp;orderman/orders/spreader_orders/spreader/SpreaderChildOrderState.cpp;orderman/orders/spreader_orders/spreader/SpreaderLegData.cpp;orderman/orders/spreader_orders/spreader/SpreaderUtils.cpp;orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderOrderBuilder.cpp;orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderOrderServices.cpp;orderman/orders/spreader_orders/spreader/external_interfacing/SpreaderPreActivationOrderServices.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerEndStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerStartStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerWaitingForCancelAckStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerWaitingForFirstAckCancelledStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerWaitingForFirstAckStateWorker.cpp;orderman/orders/spreader_orders/spreader/hedger/hedger_states/HedgerWorkingStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterEndStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterStartStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForCancelAckStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForFirstAckCancelledWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForFirstAckWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoter_states/QuoterWaitingForSizeStateWorker.cpp;orderman/orders/spreader_orders/spreader/quoter/quoteLine truncated
Loaded 100 of 172 files, more files were not shown because too many files have changed in this diff. Show more