Compare commits

..
123 Commits
Author SHA1 Message Date
Taha Afzal f7cee30511 test for n legs 2018-07-05 10:00:05 -04:00
Taha Afzal 98690ee747 ractional hedging test 2018-07-05 00:04:25 -04:00
Taha Afzal 3e14d83dde Merge branch 'prod' of bitbucket.org:bluetradesys/spark into nlegs 2018-07-03 18:54:56 -04:00
Taha Afzal 746622161c SPARK-1366 unit tests 2018-07-02 20:33:34 -04:00
Taha Afzal c064cdd215 SPARK-1366 code complete, writing tests now 2018-07-02 18:59:51 -04:00
Eric Harding acce1e3f7d SPARK-1376 Need to explicitly ask for direct book maintenance 2018-07-02 11:30:50 -04:00
Taha Afzal 9bdf084838 SPARK-1366 ciruclar buffer for keeping history of drivers complete 2018-07-01 23:40:57 -04:00
Taha Afzal 5132f81bc1 name change to QuoteLegDriver 2018-07-01 20:17:41 -04:00
Taha Afzal ab797a311e QuoterLegDriverData broken into a cpp 2018-07-01 19:48:51 -04:00
Taha Afzal d032b23841 added 'compute_driver' which calculates the driver price/qty from a quoting leg qty - this is now called by both n legs and 2 legs 2018-07-01 18:33:04 -04:00
Taha Afzal 8c68a66ef1 Merge branch 'prod' of bitbucket.org:bluetradesys/spark into nlegs 2018-06-29 19:14:56 -04:00
Taha Afzal d617d1597c SPARK 1432 fractional legs 2018-06-29 18:58:44 -04:00
Pedro Pinto 625f76e1f4 Made iceberg order type more robust.
We were asserting that completed orders must have non zero fills, but
its easy for an algo to break that so we handle it.
2018-06-28 15:14:52 -04:00
Eric Harding ccff3ee160 SPARK-1427 monitor dispatcher background thread on sparkd 2018-06-28 11:24:35 -04:00
Eric Harding 1b1c8a7c1d Allow for discontiguous keys in replay md 2018-06-27 11:25:46 -04:00
Pedro Pinto 5206a378ff Fixed stop order price encoding 2018-06-27 11:12:34 -04:00
Pedro Pinto 7d2047d7ce Fixed the shared data problem that was crashing O'Grady. 2018-06-27 11:12:34 -04:00
Taha Afzal 8a43a4d157 SPARK-1416 (reactivated terminal status) 2018-06-27 11:12:34 -04:00
Jake Norbut 230e9e525f SPARK-1409 - slow picker fixed? 2018-06-27 11:12:34 -04:00
Jake Norbut 034318db5e fix early orders unsub - exception was taking out the observable 2018-06-27 11:12:34 -04:00
Pedro Pinto 0758dd6f46 Fixed clang build. 2018-06-27 11:12:34 -04:00
Pedro Pinto 83f9f3375c Fixed crash on client close. 2018-06-27 11:12:33 -04:00
Pedro Pinto 98a6616804 Removed current link in jetfix, it doesn't work well with docker. 2018-06-27 11:12:33 -04:00
Pedro Pinto b46f811cb5 Tweaks to pp custom build environment. 2018-06-27 11:12:33 -04:00
Eric Harding 7cf37bb080 SPARK-1407: Config off consolidated book. CMEChannel { book_type: BOOKTYPE_DIRECT } 2018-06-26 12:56:07 -04:00
John Colvin 6d918c0962 SPARK-1418 Remove constraint on creating NLegs spreads 2018-06-25 17:10:04 -04:00
Pedro Pinto 7cd052979e Fixed stop order price encoding 2018-06-22 16:29:45 -04:00
Pedro Pinto 3b3d44d6f6 Fixed the shared data problem that was crashing O'Grady. 2018-06-22 16:22:27 -04:00
Taha Afzal 2b2eaac979 SPARK-1416 (reactivated terminal status) 2018-06-21 19:50:32 -04:00
Taha Afzal 5b5274ab5f 1366 added hedging test 2018-06-21 18:02:08 -04:00
Jake Norbut 484ad656f9 SPARK-1409 - slow picker fixed? 2018-06-21 11:42:16 -05:00
Jake Norbut 0e92ee15f4 fix early orders unsub - exception was taking out the observable 2018-06-21 11:29:27 -05:00
Taha Afzal 2d5211ac9b SPARK-1366 n legs 2018-06-20 21:13:02 -04:00
Taha Afzal e62d116a44 SPARK-1366 n legs 2018-06-20 20:42:36 -04:00
Pedro Pinto 0deb4fbcf4 Fixed clang build. 2018-06-20 20:00:13 +00:00
Pedro Pinto b8b375eca5 Fixed crash on client close. 2018-06-20 20:00:13 +00:00
Pedro Pinto 4a42238cfb Removed current link in jetfix, it doesn't work well with docker. 2018-06-20 20:00:13 +00:00
Pedro Pinto d4081420ae Tweaks to pp custom build environment. 2018-06-20 20:00:13 +00:00
Taha Afzal 27d38cf443 SPARK-1400 spreader on_activate_recovered 2018-06-20 14:22:11 -04:00
Taha Afzal 4dad98a8b2 SPARK-1408 Limiter dupe value crash 2018-06-20 14:22:11 -04:00
Eric Harding a2b8d53ebb Add $HOST variable to install_config.json to make installer deploy easier 2018-06-20 11:22:00 -04:00
Eric Harding 33a1762cb7 Add sparkd web command for getting the web UI up in an emergency 2018-06-19 11:40:03 -04:00
Pedro Pinto f4f1bb7914 Fixed log msg. 2018-06-18 20:56:07 +00:00
John Colvin 908958ea3c SPARK-1386 format futures/future-spread-legs with exchange desc (if present) 2018-06-18 15:46:32 -04: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
212 changed files with 4138 additions and 2102 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));
@@ -173,7 +173,8 @@ namespace mbts.spark {
}
_is_setting_price_mults = false;
};
_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => leg_count < 2), leg_count < 2);
//_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => leg_count < 2), leg_count < 2);
_can_add_leg = backing(nameof(can_add_leg), leg_tuples.ObserveSelf().Select(_ => true), true);
} else {
_can_add_leg = backing(nameof(can_add_leg), false);
}
@@ -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) {
@@ -114,7 +114,7 @@ namespace bts.spark {
(var security, var spec) = get_security(neworder.Def.SecurityKey);
//
// description
var mod_desc = modified_description(oldorder, neworder, spec.Value);
var mod_desc = modified_description(oldorder, neworder, spec);
if (!string.IsNullOrEmpty(mod_desc)) {
log_status(ActivityLogCategory.modify, security, mod_desc);
}
@@ -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: _ => {
@@ -197,10 +197,6 @@ namespace mbts.spark
selected_securities = secs.Select(s => s.Id).ToList();
ok_button_command.TryRaiseCanExecuteChanged();
_compose_criteria_clause_name();
// _security_in_criteria is our fun to be returned to our callers
// test on click -- verify our func works
//var results = all_securities.Select(all => all.Value.security).Where(x => _security_in_criteria(x)).ToList();
}
private void on_securities_changed(IReadOnlyDictionary<uint, SecurityMD> changed_securities) {
@@ -212,10 +208,6 @@ namespace mbts.spark
all_securities = changed_securities;
}
securities.sync_with(all_securities
.Select(entry => entry.Value.security)
);
_update_exchanges();
_update_security_types();
_update_symbols();
@@ -327,7 +319,6 @@ namespace mbts.spark
.Select(s => s.Value.security.Symbol)
.Distinct()
);
}
private void _update_securities() {
@@ -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);
}
});
});
}
}
}
@@ -1,18 +1,18 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using System.Collections.Generic;
using static bts.api.Security;
namespace bts.api {
struct FutureLeg {
public readonly Venue exchange;
public readonly string symbol;
public readonly string symbol, exchange_desc;
public readonly FutureContractImmutable future;
public readonly int quantity;
public FutureLeg(Venue exchange, string symbol, FutureContractImmutable future, int quantity) {
public FutureLeg(Venue exchange, string symbol, string exchange_desc, FutureContractImmutable future, int quantity) {
this.exchange = exchange;
this.symbol = symbol;
this.exchange_desc = exchange_desc;
this.future = future;
this.quantity = quantity;
}
@@ -32,12 +32,14 @@ namespace bts.api {
return string.Join("", _format_future_legs(future_legs));
}
public static string format_future(string symbol, FutureContractImmutable future) =>
$"{symbol}{DateFormatter.Format(future.ExpirationYear, future.ExpirationMonth, future.ExpirationDay, DateFormat.MonthCodeLastDigitYear)}";
public static string format_future(string symbol, string exch_desc, FutureContractImmutable future) =>
string.IsNullOrEmpty(exch_desc) ?
$"{symbol}{DateFormatter.Format(future.ExpirationYear, future.ExpirationMonth, future.ExpirationDay, DateFormat.MonthCodeLastDigitYear)}" :
exch_desc;
static string _format(SecurityImmutable security) {
switch (security.ContractCase) {
case ContractOneofCase.Future: return format_future(security.Symbol, security.Future);
case ContractOneofCase.Future: return format_future(security.Symbol, security.glbx_desc(), security.Future);
case ContractOneofCase.Spread:
if (security.HasGlbx) {
// In case we ever bring it back, pretty printed spread names can be found here:
@@ -61,7 +63,7 @@ namespace bts.api {
}
static string _get_first_leg_name(FutureLeg fl) {
string sym_exp_name = format_future(fl.symbol, fl.future);
string sym_exp_name = format_future(fl.symbol, fl.exchange_desc, fl.future);
switch (fl.quantity) {
case 1: return sym_exp_name;
case -1: return $"-{sym_exp_name}";
@@ -70,7 +72,7 @@ namespace bts.api {
}
static string _get_subsequent_leg_name(FutureLeg fl) {
string sym_exp_name = format_future(fl.symbol, fl.future);
string sym_exp_name = format_future(fl.symbol, fl.exchange_desc, fl.future);
switch (fl.quantity) {
case 1: return $"+{sym_exp_name}";
case -1: return $"-{sym_exp_name}";
@@ -19,7 +19,7 @@ namespace bts.api {
foreach (var l in legs) {
switch (l.Security.ContractCase) {
case ContractOneofCase.Future:
yield return new FutureLeg(l.Security.Exchange, l.Security.Symbol, l.Security.Future, qty * l.SignedQuantity());
yield return new FutureLeg(l.Security.Exchange, l.Security.Symbol, l.Security.glbx_desc(), l.Security.Future, qty * l.SignedQuantity());
break;
case ContractOneofCase.Spread:
foreach (var fl in _get_flat_future_legs(l.Security.Spread.Legs, qty * l.SignedQuantity()))
@@ -56,6 +56,9 @@ namespace bts.api {
public static string DisplayExchange(this SecurityImmutable security) =>
security.ContractCase == ContractOneofCase.Synthetic ? $"[{string.Join(",", get_flat_future_legs(security).Select(flt => flt.exchange.ToString()).Distinct().OrderBy(e => e))}]" : security.Exchange.ToString();
public static string glbx_desc(this SecurityImmutable security) =>
security.HasGlbx ? security.Glbx.SecurityDesc ?? string.Empty : string.Empty;
#endregion
public static class Comparer {
+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;
}
}
}
@@ -820,19 +820,49 @@ void AdminServiceUtils::delete_user (bts::api::admin::AdminData& admin_data,
////////////////////////////////////////////
bool AdminServiceUtils::_sort_security_scopes (const bts::api::admin::SecurityScope& a,
const bts::api::admin::SecurityScope& b){
if (has_id(a.security_id()))
return true;
// for both the sec and product scope sorting, the more
// qualified arg is sent to the top (vs wildcard). the
// order of qualification is sec_id, symbol, type, exchange.
// if both a and b are equally qualified, it is a
// a dont-care case (in terms of sec/prod res) and
// ascending orderring is used
if (has_id(a.security_id())){
if (has_id(b.security_id()))
return a.security_id() < b.security_id();
else
return true;
}
if (has_id(b.security_id()))
return false;
if (a.security_symbol() != "*")
return true;
return false;
// symbol
if (is_symbol_set(a.security_symbol())){
if (is_symbol_set(b.security_symbol()))
return a.security_symbol() < b.security_symbol();
else
return true;
}
if (is_symbol_set(b.security_symbol()))
return false;
// sec type
if (a.security_type() != b.security_type())
return a.security_type() < b.security_type();
return a.exchange() < b.exchange();
}
bool AdminServiceUtils::_sort_product_scopes (const bts::api::admin::ProductScope& a,
const bts::api::admin::ProductScope&){
if (a.security_symbol() != "*")
return true;
return false;
const bts::api::admin::ProductScope& b){
// symbol
if (is_symbol_set(a.security_symbol())){
if (is_symbol_set(b.security_symbol()))
return a.security_symbol() < b.security_symbol();
else
return true;
}
if (is_symbol_set(b.security_symbol()))
return false;
// sec type
if (a.security_type() != b.security_type())
return a.security_type() < b.security_type();
return a.exchange() < b.exchange();
}
void AdminServiceUtils::sort_limits (bts::api::admin::BTSAccount& account){
std::sort (account.mutable_security_position_limits()->begin(),
@@ -901,7 +931,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,
@@ -1025,4 +1055,7 @@ void AdminServiceUtils::bootstrap_data (bts::api::admin::AdminData& data){
acct_a1_clip_size->set_clip_size(100);
}
bool AdminServiceUtils::has_id (uint32_t id){return id>0;}
bool AdminServiceUtils::is_symbol_set (const std::string& symbol){
return symbol !="*";
}
}}
@@ -240,6 +240,7 @@ public:
// misc//////// ////////////////////////////
////////////////////////////////////////////
static bool has_id (uint32_t id);
static bool is_symbol_set (const std::string& symbol);
static void bootstrap_data (bts::api::admin::AdminData& data);
};
}}
+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
View File
@@ -76,6 +76,10 @@ message CMEOptions
repeated uint32 affinity = 10;
optional uint32 udp_socket_buffer_size = 11 [default = 33554432]; //32mb
}
enum BookType {
BOOKTYPE_CONSOLIDATED = 0;
BOOKTYPE_DIRECT = 1;
}
message CMEChannel {
repeated string id = 2;
optional string feed_engine = 3 [default = "default"];
@@ -98,6 +102,7 @@ message CMEOptions
optional bool join_snapshot_on_gap = 32 [default = false];
optional bool load_security_db = 33 [default = true];
optional BookType book_type = 34 [default = BOOKTYPE_CONSOLIDATED];
}
optional string onixs_license = 1 [default = "{default}"];
+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
View File
@@ -79,6 +79,11 @@ message JetFIX {
optional int32 udf_support_indicator = 340 [default=1];
optional bool use_cme_secure_logon = 341 [default=true];
optional bool auto_cert_mode = 342 [default=false];
optional int32 price_encoding_precision = 343 [default=7];
optional uint32 max_order_msgs_per_window = 344 [default=166]; // At the CME 166 ~= 500 / 3
optional uint32 window_duration_msecs = 345 [default=1000]; // leave it alone
optional uint32 max_connection_attempts = 346 [default=10];
}
repeated Connection connection = 10;
+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;
Loaded 100 of 212 files, more files were not shown because too many files have changed in this diff. Show more