Compare commits

...
46 Commits
Author SHA1 Message Date
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
123 changed files with 1999 additions and 1486 deletions

No files matched your search

+2
View File
@@ -21,3 +21,5 @@ src/client/sparkcmd/Properties/launchSettings.json
node_modules
paket-files
project.lock.json
src/server/sparkd/orderman/perf_analysis/data
src/server/sparkd/orderman/perf_analysis/trace_data/
+1 -1
View File
@@ -25,7 +25,7 @@ namespace bts.ui {
public AboutDaemon(IBus bus, [Import("username")]string username, IBrowserWindowManager browser) {
_user = username;
_browser = browser;
_dispose = bus.subscribe(SparkTags.About).ObserveOnDispatcher().Subscribe(_ => show_about());
_dispose = bus.subscribe(SparkTags.About).OnDispatcher().Subscribe(_ => show_about());
}
[Conditional("DEBUG")]
@@ -18,7 +18,7 @@ namespace bts {
public DeleteSyntheticDaemon(IBus bus, OrdersModel orders_model) {
_orders_model = orders_model;
_dispose = bus.subscribe(SparkTags.SyntheticDeleteTag)
.ObserveOnDispatcher()
.OnDispatcher()
.Subscribe(_delete_synthetic);
}
+12 -9
View File
@@ -7,7 +7,7 @@ using mbts.ui;
using System;
using System.ComponentModel.Composition;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Threading;
using System.Windows;
namespace bts.spark
@@ -22,18 +22,21 @@ namespace bts.spark
public ErrorReporter(ErrorReportService svc, IBus bus, [Import("username")] string username) {
_svc = svc;
_username = username;
_dispose = bus.subscribe(SparkTags.Support).ObserveOnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
_dispose = bus.subscribe(SparkTags.Support).OnDispatcher().Subscribe(msg => show_support_window(msg, "Report a problem"));
}
public void Dispose() {
Disposable.dispose(ref _dispose);
}
public async Task report_error(string message, string title = null, MessageBoxImage? image = null) {
var result = await BTSMessageBox.ShowAsync(message, title = "Error", BTSMessageBoxButtons.OK | BTSMessageBoxButtons.Support, image ?? MessageBoxImage.Error);
if (result == BTSMessageBoxButtons.Support) {
show_support_window(message, title);
}
public void report_error(string message, string title = null, MessageBoxImage? image = null) {
// Error reports are likely to arrive the api thread (which is not an STA thread)
Application.Current.Dispatcher.BeginInvoke(async () => {
var result = await BTSMessageBox.ShowAsync(message, title = "Error", BTSMessageBoxButtons.OK | BTSMessageBoxButtons.Support, image ?? MessageBoxImage.Error);
if (result == BTSMessageBoxButtons.Support) {
show_support_window(message, title);
}
});
}
public void show_support_window(string message, string title) {
@@ -70,11 +73,11 @@ namespace bts.spark
return (get_successful_rv(result.value), string.Empty);
else {
string error_msg = template.fmt(error);
error_reporter.report_error(error_msg).ignore();
error_reporter.report_error(error_msg);
return (get_unsuccessful_rv(), error_msg);
}
} else {
error_reporter.report_error(result.error_message).ignore();
error_reporter.report_error(result.error_message);
return (get_unsuccessful_rv(), result.error_message);
}
}
@@ -28,11 +28,11 @@ namespace bts.shell {
_model = model;
_error_reporter = error_reporter;
_dispose = Disposable.Create(
model.on_orders.ObserveOnDispatcher().Subscribe(o => _orders = o),
bus.subscribe(SparkTags.SecuritiesTag).ObserveOnDispatcher().Subscribe(m =>_md_map = m),
bus.subscribe(SparkTags.ShowOrderTicketFromSecurityTag).ObserveOnDispatcher().Subscribe(_show_order_ticket),
bus.subscribe(SparkTags.ShowOrderTicketTag).ObserveOnDispatcher().Subscribe(_show_order_ticket),
bus.subscribe(SparkTags.ShowModifyOrderTag).ObserveOnDispatcher().Subscribe(_show_modify_order)
model.on_orders.OnDispatcher().Subscribe(o => _orders = o),
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(m =>_md_map = m),
bus.subscribe(SparkTags.ShowOrderTicketFromSecurityTag).OnDispatcher().Subscribe(_show_order_ticket),
bus.subscribe(SparkTags.ShowOrderTicketTag).OnDispatcher().Subscribe(_show_order_ticket),
bus.subscribe(SparkTags.ShowModifyOrderTag).OnDispatcher().Subscribe(_show_modify_order)
);
}
@@ -23,10 +23,10 @@ namespace bts {
public SecurityPickerDaemon(IBus bus) {
_bus = bus;
_dispose = Disposable.Create(
bus.subscribe(SparkTags.ShowPickOne).ObserveOnDispatcher().Subscribe(show_pick_one),
bus.subscribe(SparkTags.FilterPickOne).ObserveOnDispatcher().Subscribe(show_filtered_pick_one),
bus.subscribe(SparkTags.ShowPickMultiple).ObserveOnDispatcher().Subscribe(show_pick_multiple),
bus.subscribe(SparkTags.ShowAndEditPickMultiple).ObserveOnDispatcher().Subscribe(show_and_edit_pick_multiple)
bus.subscribe(SparkTags.ShowPickOne).OnDispatcher().Subscribe(show_pick_one),
bus.subscribe(SparkTags.FilterPickOne).OnDispatcher().Subscribe(show_filtered_pick_one),
bus.subscribe(SparkTags.ShowPickMultiple).OnDispatcher().Subscribe(show_pick_multiple),
bus.subscribe(SparkTags.ShowAndEditPickMultiple).OnDispatcher().Subscribe(show_and_edit_pick_multiple)
);
}
@@ -78,11 +78,11 @@ namespace mbts.spark {
_animation.AnimationsChanged += on_animations_changed;
void on_animations_changed() => RaisePropertyChanged(nameof(animations));
_dispose = Disposable.Create(
_orders_model.on_security_orders(security.Id).ObserveOnDispatcher().Subscribe(_on_orders),
_position.ObserveOnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
_inside_bids.ObserveOnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
_inside_asks.ObserveOnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
_day_change.ObserveOnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
_orders_model.on_security_orders(security.Id).OnDispatcher().Subscribe(_on_orders),
_position.OnDispatcher().Subscribe(v => _animation.animate_position(v.newval > v.oldval)),
_inside_bids.OnDispatcher().Subscribe(v => _animation.animate_working_bids_inside(v.newval)),
_inside_asks.OnDispatcher().Subscribe(v => _animation.animate_working_asks_inside(v.newval)),
_day_change.OnDispatcher().Subscribe(v => _animation.animate_day_change(v.newval > v.oldval)),
Disposable.Create(() => _animation.AnimationsChanged -= on_animations_changed)
);
}
@@ -81,7 +81,7 @@ namespace mbts.spark {
}
//
_resources.AddRange(
state.ObserveOnDispatcher().Subscribe(s => {
state.OnDispatcher().Subscribe(s => {
using (rows_view.DeferRefresh()) {
rows_view.SetSortingCriteria(s.sorting_criteria);
var filters = s.filters;
@@ -57,7 +57,7 @@ namespace mbts.spark {
SparkCommands.ShowTimeAndSales.complete(_vm.selected_securities_observable(), _ => true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria),
SparkCommands.ShowSyntheticCreate.complete(_vm.selected_securities_observable(), secs => secs.Count > 0 && secs.Count < 3, secs => new SecurityCriteria(secs))
};
_subscriptions = _bus.subscribe(update_securities_tag).ObserveOnDispatcher().Subscribe(c =>
_subscriptions = _bus.subscribe(update_securities_tag).OnDispatcher().Subscribe(c =>
_state_changed?.Invoke(_state = new MarketViewWindowFactory.StartupData(c, _vm.rows_view.GetSortingCriteria(), _vm.get_filter_states())));
}
@@ -16,7 +16,7 @@ namespace bts.shell {
public MarketViewWindowFactory(MarketShellModule module, IBus bus, OrdersModel orders_model) : base(module, "MarketView") {
_bus = bus;
_orders_model = orders_model;
_dispose = bus.subscribe(SparkTags.ShowMarketViewTag).ObserveOnDispatcher().Subscribe(_show);
_dispose = bus.subscribe(SparkTags.ShowMarketViewTag).OnDispatcher().Subscribe(_show);
}
public override Task<IShellWindow> load(StartupData state) =>
@@ -47,8 +47,8 @@ namespace mbts.spark {
_vm = new SingleValuePump<TimeAndSalesVM>();
var update_securities_tag = new SparkTags.SparkTag<SecurityCriteria>("TimeAndSales Callback");
var update_securities_cmd = SparkCommand.create("Update Time and Sales", Icon, update_securities_tag);
var current_smd_source = _vm.observable.SelectMany(vm => vm.current_security_md_observable(_bus)).ObserveOnDispatcher();
var selected_secs_source = _vm.observable.SelectMany(vm => vm.selected_securities_observable()).ObserveOnDispatcher();
var current_smd_source = _vm.observable.SelectMany(vm => vm.current_security_md_observable(_bus)).OnDispatcher();
var selected_secs_source = _vm.observable.SelectMany(vm => vm.selected_securities_observable()).OnDispatcher();
_commands = new SparkCommand[] {
SparkCommand.create("Change Securities", Icons.Admin, SparkTags.ShowAndEditPickMultiple, () => (update_securities_cmd, _state.security_criteria)),
SparkCommands.ShowLadder.complete(current_smd_source, md => md.HasValue, ObjectHelper.ValueOrDefault),
@@ -56,7 +56,7 @@ namespace mbts.spark {
SparkCommands.ShowFills.complete(selected_secs_source, always_true, get_fills_startup_data),
SparkCommands.ShowMarketView.complete(selected_secs_source, always_true, secs => secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria)
};
_subscriptions = _bus.subscribe(update_securities_tag).ObserveOnDispatcher().Subscribe(update_criteria);
_subscriptions = _bus.subscribe(update_securities_tag).OnDispatcher().Subscribe(update_criteria);
_initialize();
bool always_true<T>(T _) => true;
@@ -23,7 +23,7 @@ namespace bts.shell {
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
bus.subscribe(SparkTags.ShowTimeAndSalesTag).ObserveOnDispatcher().Subscribe(show)
bus.subscribe(SparkTags.ShowTimeAndSalesTag).OnDispatcher().Subscribe(show)
);
}
@@ -98,9 +98,9 @@ namespace bts.shell {
void IInitializable.initialize() {
Disposable.dispose(ref _dispose);
_dispose = Disposable.Create(
_bus.subscribe(SparkTags.ShowSyntheticCreateTag).ObserveOnDispatcher().Subscribe(_show_synthetic_create),
_bus.subscribe(SparkTags.ShowSyntheticStrategyTag).ObserveOnDispatcher().Subscribe(_show_synthetic_strategy),
_bus.subscribe(SparkTags.ShowSyntheticCloneTag).ObserveOnDispatcher().Subscribe(_show_synthetic_clone)
_bus.subscribe(SparkTags.ShowSyntheticCreateTag).OnDispatcher().Subscribe(_show_synthetic_create),
_bus.subscribe(SparkTags.ShowSyntheticStrategyTag).OnDispatcher().Subscribe(_show_synthetic_strategy),
_bus.subscribe(SparkTags.ShowSyntheticCloneTag).OnDispatcher().Subscribe(_show_synthetic_clone)
);
}
@@ -32,14 +32,14 @@ namespace mbts.spark {
SparkCommand.create("Add", Icons.AddSynthetic, SparkTags.ShowSyntheticCreateTag).complete(
() => new SecurityCriteria(), () => true
), SparkCommand.create("Remove", Icons.RemoveSynthetic, SparkTags.SyntheticDeleteTag).complete(
_vm.observable.SelectMany(vm => vm.current_row_observable),
can_row_change, r => r == null ? 0 : r.security_key
_vm.observable.SelectMany(vm => vm.current_row_observable.SelectMany(observe_row_mod_params)),
can_execute, select_security_id
), SparkCommand.create("Change", Icons.ModifySynthetic, SparkTags.ShowSyntheticStrategyTag).complete(
_vm.observable.SelectMany(vm => vm.current_row_observable),
can_row_change, r => r == null ? SecurityDataHelper.Defaults.SecurityImmutable : r.security
r => r != null, r => r?.security ?? SecurityDataHelper.Defaults.SecurityImmutable
), SparkCommand.create("Clone", Icons.CloneSynthetic, SparkTags.ShowSyntheticCloneTag).complete(
_vm.observable.SelectMany(vm => vm.current_row_observable),
r => r != null && r.strategy.HasValue, r => r == null ? default : (r.security, r.strategy.Value)
_vm.observable.SelectMany(vm => vm.current_row_observable.SelectMany(observe_row_strategy)),
rs => rs.HasValue, select_row_strategy_tuple
), SparkCommands.ShowLadder.complete(
_vm.observable.SelectMany(vm => vm.current_security_md), md => md.HasValue, ObjectHelper.ValueOrDefault
), SparkCommands.ShowOrders.complete(() => new OrderBookWindowFactory.OrderBookStartupData {
@@ -49,10 +49,19 @@ namespace mbts.spark {
})
};
bool can_row_change(SyntheticManagementDialogVM.Row r) =>
r != null && r.n_working_orders == 0;
}
IObservable<RowModificationParams?> observe_row_mod_params(SyntheticManagementDialogVM.Row r) =>
r == null ? Observable.Return(new RowModificationParams?()) :
_orders_model.security_has_working_orders(r.security_key).Select(hwo => RowModificationParams.create(r.security, hwo));
IObservable<RowStrategy?> observe_row_strategy(SyntheticManagementDialogVM.Row r) =>
r == null ? Observable.Return(new RowStrategy?()) :
_orders_model.spreader.security_strategy(r.security.Id).Select(strategy => RowStrategy.create(r.security, strategy));
uint select_security_id(RowModificationParams? rmp) => rmp.HasValue ? rmp.Value.security.Id : 0;
(SecurityImmutable, ExecutionStrategyImmutable) select_row_strategy_tuple(RowStrategy? rs) => rs.HasValue ? (rs.Value.security, rs.Value.strategy) : default;
bool can_execute(RowModificationParams? rmp) => rmp.HasValue && !rmp.Value.has_working_orders;
}
public void initialize(StartupData state) {
Logger.notnull(state);
_state = state;
@@ -71,5 +80,31 @@ namespace mbts.spark {
Disposable.dispose(ref _vm);
Commands.OfType<IDisposable>().DisposeAll();
}
struct RowModificationParams {
public readonly SecurityImmutable security;
public readonly bool has_working_orders;
RowModificationParams(SecurityImmutable security, bool has_working_orders) {
this.security = security;
this.has_working_orders = has_working_orders;
}
public static RowModificationParams? create(SecurityImmutable security, bool has_working_orders) =>
new RowModificationParams(security, has_working_orders);
}
struct RowStrategy {
public readonly SecurityImmutable security;
public readonly ExecutionStrategyImmutable strategy;
RowStrategy(SecurityImmutable security, ExecutionStrategyImmutable strategy) {
this.security = security;
this.strategy = strategy;
}
public static RowStrategy? create(SecurityImmutable security, ExecutionStrategyImmutable strategy) =>
new RowStrategy(security, strategy);
}
}
}
@@ -16,7 +16,7 @@ namespace bts.shell {
public SyntheticManagementWindowFactory(SpreaderShellModule module, IBus bus, OrdersModel orders_model) : base(module, "SyntheticManagement") {
_bus = bus;
_orders_model = orders_model;
_dispose = bus.subscribe(SparkTags.ShowSyntheticManagementTag).ObserveOnDispatcher().Subscribe(show);
_dispose = bus.subscribe(SparkTags.ShowSyntheticManagementTag).OnDispatcher().Subscribe(show);
}
public override Task<IShellWindow> load(StartupData state) =>
+2 -2
View File
@@ -10,7 +10,7 @@ namespace mbts.spark {
readonly Property<bool> _enabled;
protected CellBase(IRowModel model, LegSpreaderParamsTuple leg_tup) =>
_enabled = backing(nameof(enabled), model.observe_enabled(leg_tup).ObserveOnDispatcher());
_enabled = backing(nameof(enabled), model.observe_enabled(leg_tup).OnDispatcher());
public bool enabled => _enabled.Value;
}
@@ -24,7 +24,7 @@ namespace mbts.spark {
: base(model, leg_tup) {
_model = model;
_leg_tup = leg_tup;
_override_value_sub = model.observe_override_value(leg_tup).ObserveOnDispatcher().Subscribe(v => {
_override_value_sub = model.observe_override_value(leg_tup).OnDispatcher().Subscribe(v => {
value = v;
RaisePropertyChanged(nameof(value));
});
@@ -44,10 +44,10 @@ namespace mbts.spark {
_tick_factors = TickUtils.get_regional_ticks(synthetic.ContractSpec).low_tick;
title = $"Edit '{synthetic.Synthetic.Name}'";
icon = Icons.ModifySynthetic;
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.ObserveOnDispatcher());
_has_working_orders = backing(nameof(has_working_orders), security_has_working_orders.OnDispatcher());
var commit = new ObservantAsyncCommand(commit_async);
_resources.Add(security_has_working_orders.Select(v => !v).ObserveOnDispatcher().Subscribe(commit));
_resources.Add(_has_working_orders.ObserveOnDispatcher().Subscribe(vals => {
_resources.Add(security_has_working_orders.Select(v => !v).OnDispatcher().Subscribe(commit));
_resources.Add(_has_working_orders.OnDispatcher().Subscribe(vals => {
if (vals.newval)
set_error(nameof(has_working_orders), "Security has working orders");
else clear_error(nameof(has_working_orders));
@@ -66,7 +66,7 @@ namespace mbts.spark {
.Select(t => (uint)(t.state
.Where(kv => kv.Value.is_active())
.Count())
).ObserveOnDispatcher());
).OnDispatcher());
name = security.Synthetic.Name;
n_legs = security.Synthetic.Legs.Count();
description = SecurityFormatter.format_contract_legs(security.Synthetic.Legs);
@@ -38,7 +38,7 @@ namespace bts.shell
}
public void thread_init() {
_dispose = _message_src.ObserveOnDispatcher().Subscribe(_messages.AddLog);
_dispose = _message_src.OnDispatcher().Subscribe(_messages.AddLog);
_content =
new LogViewer {
ItemsSource = _messages,
@@ -23,7 +23,7 @@ namespace bts.shell {
public HelpShellModule(IBus bus, IBrowserWindowManager browser) {
_awf = new ActivityWindowFactory(this, bus);
_dispose = Disposable.Create(
bus.subscribe(SparkTags.ShowUri).ObserveOnDispatcher().Subscribe(u => browser.Pages.Add(u))
bus.subscribe(SparkTags.ShowUri).OnDispatcher().Subscribe(u => browser.Pages.Add(u))
);
}
@@ -24,13 +24,16 @@ namespace mbts.spark {
public FillsVM(IBus bus, OrdersModel model, StartupData state)
: this(bus, model, _get_observable(bus, model, state), state.sorting_criteria, state.filters) { }
FillsVM(IBus bus, OrdersModel model, IObservable<IIncrementalUpdate<FillTuple, string>> fills_source, IReadOnlyCollection<SortingCriteria> sorting_criteria, FilterStates filters)
FillsVM(IBus bus, OrdersModel model, IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> fills_source, IReadOnlyCollection<SortingCriteria> sorting_criteria, FilterStates filters)
: base(RXCollection.CreateIncrementalUpdate(fills_source), sorting_criteria) {
_model = model;
account_filter = DiscreteValuesFilterVM.Create<Row, string>(_model.factory.subscribe_accounts(), r => r.account, nameof(Row.account));
user_filter = DiscreteValuesFilterVM.Create<Row, string>(_model.factory.subscribe_users(), r => r.user, nameof(Row.user));
security_filter = DiscreteValuesFilterVM.Create<Row, SecurityImmutable, SecurityCriteria>(
fills_source.with_respect_to(ft => ft.security ?? SecurityDataHelper.Defaults.SecurityImmutable, s => s.Id, ft => ft.key).Select(t => (t.added.Values, t.removed.Values)),
fills_source.with_respect_to(
ft => ft.security_md.HasValue ? ft.security_md.Value.security : SecurityDataHelper.Defaults.SecurityImmutable,
s => s.Id, ft => ft.key
).Select(t => (t.added.Values, t.removed.Values)),
r => r.security,
SecurityCriteria.from_securities,
SecurityCriteria.get_to_securities(bus),
@@ -126,7 +129,7 @@ namespace mbts.spark {
protected override Row row_factory(FillTuple row_model) => new Row(row_model, _model);
protected override IEnumerable<SortingCriteria> get_default_sorting() { yield return new SortingCriteria(nameof(Row.fill_time), false); }
static IObservable<IIncrementalUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
static IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
if (state.orders != null && state.orders.Length == 0) {
var orders_hash = new HashSet<uint>(state.orders);
return model.on_fill_tuples.filter(f => orders_hash.Contains(f.fill.OrderKey));
@@ -138,7 +141,7 @@ namespace mbts.spark {
readonly FillImmutable _fill;
readonly OrderRecordImmutable _order;
public Row(FillTuple fill_tuple, OrdersModel model) : base(fill_tuple.security ?? SecurityDataHelper.Defaults.Security) {
public Row(FillTuple fill_tuple, OrdersModel model) : base(fill_tuple.security_md.HasValue ? fill_tuple.security_md.Value.security : SecurityDataHelper.Defaults.Security) {
_fill = fill_tuple.fill;
_order = fill_tuple.order ?? OrderDataHelper.Defaults.OrderRecordImmutable;
_model = model;
@@ -18,7 +18,7 @@ namespace bts.shell {
: base(module, "Fills") {
_bus = bus;
_orders_model = orders_model;
_dispose = bus.subscribe(SparkTags.ShowFillsTag).ObserveOnDispatcher().Subscribe(show);
_dispose = bus.subscribe(SparkTags.ShowFillsTag).OnDispatcher().Subscribe(show);
}
public override Task<IShellWindow> load(StartupData state) =>
@@ -34,8 +34,8 @@ namespace bts.shell {
_max_ladders_open = (int)(window_props?.ladder?.max_total_open ?? 75);
_dispose = Disposable.Create(
bus.subscribe(SparkTags.SecuritiesTag).Subscribe(s => _securities = s),
bus.subscribe(SparkTags.ShowLadderTag).ObserveOnDispatcher().Subscribe(s => show_windows(new[] { s })),
bus.subscribe(SparkTags.ShowLaddersTag).ObserveOnDispatcher().Subscribe(show_windows)
bus.subscribe(SparkTags.ShowLadderTag).OnDispatcher().Subscribe(s => show_windows(new[] { s })),
bus.subscribe(SparkTags.ShowLaddersTag).OnDispatcher().Subscribe(show_windows)
);
}
@@ -17,7 +17,7 @@ namespace bts.shell {
_bus = bus;
_model = model;
_dispose = bus.subscribe(SparkTags.ShowOrderBookTag).ObserveOnDispatcher().Subscribe(show);
_dispose = bus.subscribe(SparkTags.ShowOrderBookTag).OnDispatcher().Subscribe(show);
}
public override Task<IShellWindow> load(OrderBookStartupData state) {
@@ -17,7 +17,7 @@ namespace bts.shell {
_bus = bus;
_model = model;
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).ObserveOnDispatcher().Subscribe(show);
_dispose = bus.subscribe(SparkTags.ShowPAndLTag).OnDispatcher().Subscribe(show);
}
@@ -24,7 +24,7 @@ namespace mbts.spark {
var security_source = security_md_source.Select(smd => smd.security);
//
_security = backing(nameof(security), security_source, SecurityDataHelper.Defaults.SecurityImmutable);
properties_vm = new OrderPropertiesEditorVM(model, security_source.ObserveOnDispatcher(), order_source.Select(ot => new InlineOrderProperties(model, ot.order.Def)));
properties_vm = new OrderPropertiesEditorVM(model, security_source.OnDispatcher(), order_source.Select(ot => new InlineOrderProperties(model, ot.order.Def)));
_price = backing(nameof(price), double.NaN);
size_calculator = new CalculatorLogic(null, calculator_buttons);
_current_price = backing(nameof(current_price), order_source.Select(ort => ort.order.State.Price));
@@ -83,15 +83,15 @@ namespace mbts.spark {
size_calculator,
properties_vm,
bbo = new BBOVM(security_md_source),
order_source.ObserveOnDispatcher().Subscribe(ort => {
order_source.OnDispatcher().Subscribe(ort => {
key = ort.order.Key;
if (DoubleHelper.IsNaNOrInfinity(price))
price = ort.order.State.Price;
if (!size_calculator.value.HasValue || !size_calculator.value_setter_called)
size_calculator.bypass_value_setter(ort.order.State.Quantity);
}),
Observable.Merge(_price_dirty, _size_dirty, _account_dirty, _time_in_force_dirty, _order_type_dirty).ObserveOnDispatcher().Subscribe(_ => RaisePropertyChanged(nameof(dirty_items_text))),
_price_dirty.observe_current(true).CombineLatest(_size_dirty.observe_current(true), (pd, sd) => pd || sd).DistinctUntilChanged().ObserveOnDispatcher().Subscribe(modify_order));
Observable.Merge(_price_dirty, _size_dirty, _account_dirty, _time_in_force_dirty, _order_type_dirty).OnDispatcher().Subscribe(_ => RaisePropertyChanged(nameof(dirty_items_text))),
_price_dirty.observe_current(true).CombineLatest(_size_dirty.observe_current(true), (pd, sd) => pd || sd).DistinctUntilChanged().OnDispatcher().Subscribe(modify_order));
this.modify_order = modify_order;
set_price_command = new DelegateCommand<double>(_price.set);
}
+33 -12
View File
@@ -1,32 +1,53 @@
using bts.api;
using bts.api.orderman;
using mbts.utils;
using System;
namespace mbts.spark
{
namespace mbts.spark {
class FillTuple : IEquatable<FillTuple> {
public readonly FillImmutable fill;
public readonly OrderRecordImmutable? order;
public readonly SecurityImmutable? security;
public readonly string key;
public readonly SecurityMD? security_md;
public readonly FillTupleKey key;
public FillTuple(FillImmutable fill, OrderRecordImmutable? order, SecurityImmutable? security) {
public FillTuple(FillImmutable fill, OrderRecordImmutable? order, SecurityMD? security_md) {
this.fill = fill;
this.order = order;
this.security = security;
// a FillTuple that DOES NOT HAVE associated order/security needs to be distinguishable
// from a FillTuple (of the same underlying FillImmutable) that DOES HAVE associated order/security
key = fill.FillKey;
if (!order.HasValue) key += "!o";
if (!security.HasValue) key += "!s";
this.security_md = security_md;
key = new FillTupleKey(fill.FillKey, order.HasValue ? order.Value.Key : 0u, security_md.HasValue ? security_md.Value.security.Id : 0u);
}
public bool Equals(FillTuple other) => other != null
&& other.order.HasValue == order.HasValue
&& other.security.HasValue == security.HasValue
&& other.security_md.HasValue == security_md.HasValue
&& other.fill.Equals(fill);
public override bool Equals(object obj) => Equals(obj as FillTuple);
public override int GetHashCode() => key.GetHashCode();
}
struct FillTupleKey : IEquatable<FillTupleKey> {
public readonly string fill_key;
public readonly uint order_key, security_key;
public FillTupleKey(string fill_key, uint order_key, uint security_key) {
this.fill_key = fill_key;
this.order_key = order_key;
this.security_key = security_key;
}
public bool Equals(FillTupleKey other) =>
other.order_key == order_key &&
other.security_key == security_key &&
other.fill_key == fill_key;
public override bool Equals(object obj) =>
obj is FillTupleKey k && Equals(k);
public override int GetHashCode() =>
EqualityComparer.aggregate_hash(
fill_key.GetHashCode(),
(int)order_key,
(int)security_key);
}
}
+129 -152
View File
@@ -1,5 +1,4 @@
using bts.api;
using bts.api.core;
using bts.api.core;
using bts.api.orderman;
using mbts.collections;
using mbts.utils;
@@ -11,20 +10,21 @@ using System.Reactive.Subjects;
using System.Threading;
namespace mbts.spark {
class FillTupleSource : IDisposable {
public static IObservable<IIncrementalUpdate<FillTuple, string>> create_observable(
class FillTupleSource : IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>>, IDisposable {
// returns a ref-count Observable.Create-wrapper that initializes/destroys the [hot-observable] FillTupleSource.
// (initializes when the first subscription is made, and destroys when the last subscription is disposed).
public static IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> create_observable(
IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities,
IObservable<TableUpdate<OrderRecordImmutable, uint>> orders,
IObservable<TableUpdate<FillImmutable, string>> fills
) {
IObservable<TableUpdate<FillImmutable, string>> fills) {
FillTupleSource source = null;
int ref_count = 0;
return Observable.Create<IIncrementalUpdate<FillTuple, string>>(observer => {
return Observable.Create<IIncrementalUpdate<FillTuple, FillTupleKey>>(observer => {
if (Interlocked.Increment(ref ref_count) == 1) {
SparkUtils.lazy_assert(source == null, () => fmt_assert_msg("source should be null, there must be a leak."));
source = new FillTupleSource(securities, orders, fills);
}
IDisposable sub = source.fill_tuples.Subscribe(observer);
IDisposable sub = source.Subscribe(observer);
return Disposable.Create(() => {
Disposable.dispose_once(ref sub);
if (Interlocked.Decrement(ref ref_count) == 0)
@@ -34,180 +34,157 @@ namespace mbts.spark {
string fmt_assert_msg(string msg) => $"{nameof(FillTupleSource)}.{nameof(create_observable)}: {msg}";
}
readonly Subject<IIncrementalUpdate<FillTuple, FillTupleKey>> _subject = new Subject<IIncrementalUpdate<FillTuple, FillTupleKey>>();
readonly Dictionary<FillTupleKey, FillTuple> _state = new Dictionary<FillTupleKey, FillTuple>();
readonly SetMap<uint, string> _order_fills = new SetMap<uint, string>();
readonly SetMap<uint, string> _security_fills = new SetMap<uint, string>();
IReadOnlyDictionary<string, FillImmutable> _fills_cache = new Dictionary<string, FillImmutable>(0);
IReadOnlyDictionary<uint, OrderRecordImmutable> _orders_cache = new Dictionary<uint, OrderRecordImmutable>(0);
IReadOnlyDictionary<uint, SecurityMD> _security_md_cache = new Dictionary<uint, SecurityMD>(0);
// _n_write_ops should never be greater than 1, bc all 3 input observables fire 'OnDispatcher'
int _n_read_ops, _n_write_ops;
IDisposable _dispose;
FillTupleSource(
IObservable<IReadOnlyDictionary<uint, SecurityMD>> securities,
IObservable<TableUpdate<OrderRecordImmutable, uint>> orders,
IObservable<TableUpdate<FillImmutable, string>> fills) {
State state = new State();
fill_tuples = Observable.Create<IIncrementalUpdate<FillTuple, string>>(observer => {
observer.OnNext(new IncrementalUpdate<FillTuple, string>(state.full_state.ToDictionary(), new Dictionary<string, FillTuple>(0)));
return _subject.Subscribe(observer);
});
IObservable<TableUpdate<FillImmutable, string>> fills) =>
//
_dispose = Disposable.Create(
securities.ObserveOnDispatcher().Subscribe(s => {
state.update_securities(s);
_subject.OnNext(state.last_update);
}),
orders.ObserveOnDispatcher().Subscribe(o => {
state.update_orders(o);
_subject.OnNext(state.last_update);
}),
fills.ObserveOnDispatcher().Subscribe(f => {
state.update_fills(f);
_subject.OnNext(state.last_update);
}));
securities.OnDispatcher().Subscribe(_update_securities),
orders.OnDispatcher().Subscribe(_update_orders),
fills.OnDispatcher().Subscribe(_update_fills));
public IDisposable Subscribe(IObserver<IIncrementalUpdate<FillTuple, FillTupleKey>> observer) {
using (_read()) {
observer.OnNext(new IncrementalUpdate<FillTuple, FillTupleKey>(new Dictionary<FillTupleKey, FillTuple>(_state), new Dictionary<FillTupleKey, FillTuple>(0)));
return _subject.Subscribe(observer);
}
}
readonly Subject<IIncrementalUpdate<FillTuple, string>> _subject = new Subject<IIncrementalUpdate<FillTuple, string>>();
public void Dispose() => Disposable.dispose(ref _dispose);
IObservable<IIncrementalUpdate<FillTuple, string>> fill_tuples { get; }
public void Dispose() =>
Disposable.dispose(ref _dispose);
class State {
IReadOnlyDictionary<string, FillImmutable> _fills;
IReadOnlyDictionary<uint, OrderRecordImmutable> _orders;
IReadOnlyDictionary<uint, SecurityMD> _securities;
IncrementalUpdate<FillTuple, string> _last_update;
readonly Dictionary<string, FillTuple> _state;
readonly SetMap<uint, string> _order_fills;
readonly SetMap<uint, string> _security_fills;
public State() : this(
new Dictionary<string, FillImmutable>(),
new Dictionary<uint, OrderRecordImmutable>(),
new Dictionary<uint, SecurityMD>(),
new IncrementalUpdate<FillTuple, string>(new Dictionary<string, FillTuple>(), new Dictionary<string, FillTuple>()),
new Dictionary<string, FillTuple>(),
new SetMap<uint, string>(),
new SetMap<uint, string>()
) { }
State(
IReadOnlyDictionary<string, FillImmutable> fills,
IReadOnlyDictionary<uint, OrderRecordImmutable> orders,
IReadOnlyDictionary<uint, SecurityMD> securities,
IncrementalUpdate<FillTuple, string> last_update,
Dictionary<string, FillTuple> state,
SetMap<uint, string> order_fill_tuples,
SetMap<uint, string> security_fill_tuples
) {
_fills = fills;
_orders = orders;
_securities = securities;
_last_update = last_update;
_state = state;
_order_fills = order_fill_tuples;
_security_fills = security_fill_tuples;
}
public IncrementalUpdate<FillTuple, string> last_update => _last_update;
public IReadOnlyDictionary<string, FillTuple> full_state => _state;
public void update_fills(TableUpdate<FillImmutable, string> fills) {
var removed = fills.removed.ToDictionary(f => f.FillKey, get_fill_tuple);
var added = fills.added.ToDictionary(f => f.FillKey, get_fill_tuple);
HashSet<string> added_keys = new HashSet<string>(fills.added.Select(f => f.FillKey)),
removed_keys = new HashSet<string>(fills.removed.Select(f => f.FillKey));
foreach (var kv in removed) {
_state.Remove(kv.Key);
if (!added_keys.Contains(kv.Key)) {
_order_fills.remove(kv.Value.fill.OrderKey, kv.Key);
_security_fills.remove(kv.Value.fill.SecurityKey, kv.Key);
}
void _update_fills(TableUpdate<FillImmutable, string> fills) {
using (_write()) {
Dictionary<FillTupleKey, FillTuple> removed = new Dictionary<FillTupleKey, FillTuple>(), added = new Dictionary<FillTupleKey, FillTuple>();
foreach (FillImmutable fill in fills.removed) {
FillTuple old_ft = new FillTuple(fill, _get_order(fill.OrderKey), _get_security_md(fill.SecurityKey));
removed.Add(old_ft.key, old_ft);
_security_fills.remove(fill.SecurityKey, fill.FillKey);
_order_fills.remove(fill.OrderKey, fill.FillKey);
}
foreach (var kv in added) {
_state.Add(kv.Key, kv.Value);
if (!removed_keys.Contains(kv.Key)) {
_order_fills.add(kv.Value.fill.OrderKey, kv.Key);
_security_fills.add(kv.Value.fill.SecurityKey, kv.Key);
}
foreach (FillImmutable fill in fills.added) {
FillTuple new_ft = new FillTuple(fill, _get_order(fill.OrderKey), _get_security_md(fill.SecurityKey));
added.Add(new_ft.key, new_ft);
_security_fills.add(fill.SecurityKey, fill.FillKey);
_order_fills.add(fill.OrderKey, fill.FillKey);
}
_fills = fills.state;
_last_update = new IncrementalUpdate<FillTuple, string>(added, removed);
//
FillTuple get_fill_tuple(FillImmutable fill) =>
new FillTuple(fill, _orders.TryGetValue(fill.OrderKey, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?(),
_securities.TryGetValue(fill.SecurityKey, out SecurityMD s) ? s.security : new SecurityImmutable?());
_fills_cache = fills.state;
_apply_changes(added, removed);
}
}
public void update_orders(TableUpdate<OrderRecordImmutable, uint> orders) {
Dictionary<string, FillTuple> added = new Dictionary<string, FillTuple>(), removed = new Dictionary<string, FillTuple>();
Dictionary<uint, (OrderRecordImmutable oldval, OrderRecordImmutable newval)> changed_orders = new Dictionary<uint, (OrderRecordImmutable oldval, OrderRecordImmutable newval)>();
void _update_orders(TableUpdate<OrderRecordImmutable, uint> orders) {
using (_write()) {
Dictionary<FillTupleKey, FillTuple> added = new Dictionary<FillTupleKey, FillTuple>(), removed = new Dictionary<FillTupleKey, FillTuple>();
HashSet<uint> changed_keys = new HashSet<uint>();
foreach (var o in orders.removed) {
if (orders.try_get(o.Key, out OrderRecordImmutable newval))
changed_orders.Add(o.Key, (o, newval));
else if (_order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
foreach (FillTuple ft in get_fill_tuples(o, fill_keys))
removed.Add(ft.key, ft);
foreach (FillTuple ft in get_fill_tuples(null, fill_keys))
added.Add(ft.key, ft);
if (_order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
SecurityMD? sec_md = _get_security_md(o.security_key());
foreach (FillImmutable fill in fills) {
FillTuple old_ft = new FillTuple(fill, o, sec_md);
removed.Add(old_ft.key, old_ft);
}
OrderRecordImmutable? curr_order = null;
if (orders.try_get(o.Key, out OrderRecordImmutable newval)) {
curr_order = newval;
changed_keys.Add(o.Key);
}
foreach (FillImmutable fill in fills) {
FillTuple new_ft = new FillTuple(fill, curr_order, sec_md);
added.Add(new_ft.key, new_ft);
}
}
}
foreach (var o in orders.added) {
if (!changed_orders.ContainsKey(o.Key) && _order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
foreach (FillTuple ft in get_fill_tuples(null, fill_keys))
removed.Add(ft.key, ft);
foreach (FillTuple ft in get_fill_tuples(o, fill_keys))
added.Add(ft.key, ft);
if (!changed_keys.Contains(o.Key) && _order_fills.try_lookup(o.Key, out ISet<string> fill_keys)) {
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
SecurityMD? sec_md = _get_security_md(o.security_key());
foreach (FillImmutable fill in fills) {
FillTuple old_ft = new FillTuple(fill, null, sec_md);
removed.Add(old_ft.key, old_ft);
FillTuple new_ft = new FillTuple(fill, o, sec_md);
added.Add(new_ft.key, new_ft);
}
}
}
foreach (var kv in changed_orders) {
if (_order_fills.try_lookup(kv.Key, out ISet<string> fill_keys)) {
var fs_pairs = get_fill_security_pairs(fill_keys).ToList();
foreach (FillTuple ft in fs_pairs.Select(fsp => new FillTuple(fsp.fill, kv.Value.oldval, fsp.security)))
removed.Add(ft.key, ft);
foreach (FillTuple ft in fs_pairs.Select(fsp => new FillTuple(fsp.fill, kv.Value.newval, fsp.security)))
added.Add(ft.key, ft);
}
}
_orders = orders.state;
_last_update = new IncrementalUpdate<FillTuple, string>(added, removed);
//
IEnumerable<FillTuple> get_fill_tuples(OrderRecordImmutable? o, ISet<string> fill_keys) =>
fill_keys.Select(k => {
var fill = _fills[k];
return new FillTuple(fill, o, _securities.TryGetValue(fill.SecurityKey, out SecurityMD s) ? s.security : new SecurityImmutable?());
});
IEnumerable<(FillImmutable fill, SecurityImmutable? security)> get_fill_security_pairs(ISet<string> fill_keys) =>
fill_keys.Select(k => {
var fill = _fills[k];
return (fill, _securities.TryGetValue(fill.SecurityKey, out SecurityMD s) ? s.security : new SecurityImmutable?());
});
_orders_cache = orders.state;
_apply_changes(added, removed);
}
}
public void update_securities(IReadOnlyDictionary<uint, SecurityMD> securities) {
Dictionary<string, FillTuple> added = new Dictionary<string, FillTuple>(), removed = new Dictionary<string, FillTuple>();
_securities.Keys.difference(securities.Keys, out IEnumerable<uint> added_keys, out IEnumerable<uint> removed_keys);
void _update_securities(IReadOnlyDictionary<uint, SecurityMD> securities) {
using (_write()) {
Dictionary<FillTupleKey, FillTuple> added = new Dictionary<FillTupleKey, FillTuple>(), removed = new Dictionary<FillTupleKey, FillTuple>();
_security_md_cache.Keys.difference(securities.Keys, out IEnumerable<uint> added_keys, out IEnumerable<uint> removed_keys);
foreach (var k in removed_keys) {
if (_security_fills.try_lookup(k, out ISet<string> fill_keys)) {
foreach (FillTuple ft in get_fill_tuples(_securities[k].security, fill_keys))
removed.Add(ft.key, ft);
foreach (FillTuple ft in get_fill_tuples(null, fill_keys))
added.Add(ft.key, ft);
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
SecurityMD old_sec_md = _security_md_cache[k];
foreach (FillImmutable fill in fills) {
OrderRecordImmutable? order = _get_order(fill.OrderKey);
FillTuple old_ft = new FillTuple(fill, order, old_sec_md);
removed.Add(old_ft.key, old_ft);
FillTuple new_ft = new FillTuple(fill, order, null);
added.Add(new_ft.key, new_ft);
}
}
}
foreach (var k in added_keys) {
if (_security_fills.try_lookup(k, out ISet<string> fill_keys)) {
foreach (FillTuple ft in get_fill_tuples(null, fill_keys))
removed.Add(ft.key, ft);
foreach (FillTuple ft in get_fill_tuples(securities[k].security, fill_keys))
added.Add(ft.key, ft);
IEnumerable<FillImmutable> fills = fill_keys.Select(fk => _fills_cache[fk]);
SecurityMD new_sec_md = securities[k];
foreach (FillImmutable fill in fills) {
OrderRecordImmutable? order = _get_order(fill.OrderKey);
FillTuple old_ft = new FillTuple(fill, order, null);
removed.Add(old_ft.key, old_ft);
FillTuple new_ft = new FillTuple(fill, order, new_sec_md);
added.Add(new_ft.key, new_ft);
}
}
}
_securities = securities;
_last_update = new IncrementalUpdate<FillTuple, string>(added, removed);
//
IEnumerable<FillTuple> get_fill_tuples(SecurityImmutable? s, ISet<string> fill_keys) =>
fill_keys.Select(k => {
var fill = _fills[k];
return new FillTuple(fill, _orders.TryGetValue(fill.OrderKey, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?(), s);
});
_security_md_cache = securities;
_apply_changes(added, removed);
}
}
SecurityMD? _get_security_md(uint key) =>
_security_md_cache.TryGetValue(key, out SecurityMD smd) ? smd : new SecurityMD?();
OrderRecordImmutable? _get_order(uint key) =>
_orders_cache.TryGetValue(key, out OrderRecordImmutable o) ? o : new OrderRecordImmutable?();
void _apply_changes(Dictionary<FillTupleKey, FillTuple> added, Dictionary<FillTupleKey, FillTuple> removed) {
foreach (var kv in removed)
_state.Remove(kv.Key);
foreach (var kv in added)
_state.Add(kv.Key, kv.Value);
_subject.OnNext(new IncrementalUpdate<FillTuple, FillTupleKey>(added, removed));
}
IDisposable _read() {
while (_n_write_ops > 0)
Thread.Yield();
Interlocked.Increment(ref _n_read_ops);
return Disposable.Create(() => Interlocked.Decrement(ref _n_read_ops));
}
IDisposable _write() {
while (_n_read_ops > 0)
Thread.Yield();
Interlocked.Increment(ref _n_write_ops);
return Disposable.Create(() => Interlocked.Decrement(ref _n_write_ops));
}
}
}
+1 -1
View File
@@ -61,7 +61,7 @@ namespace mbts.spark {
set_price_command = new DelegateCommand<double>(set_price);
_dispose = Disposable.Create(
size_calculator = new CalculatorLogic(0, calculator_buttons),
props_source.ObserveOnDispatcher().Subscribe(p => on_props(p.price, p.size))
props_source.OnDispatcher().Subscribe(p => on_props(p.price, p.size))
);
void on_props(InlineUserProperty<double> price_prop, InlineUserProperty<uint> size_prop) {
@@ -46,7 +46,7 @@ namespace mbts.spark {
public OrderPropertiesEditorVM(OrdersModel model, IObservable<SecurityImmutable> security_source, IObservable<InlineOrderProperties> order_properties) {
_model = model;
_security = backing(nameof(security), security_source.ObserveOnDispatcher(), SecurityDataHelper.Defaults.SecurityImmutable);
_security = backing(nameof(security), security_source.OnDispatcher(), SecurityDataHelper.Defaults.SecurityImmutable);
// guarentee that "this.security" will always be set before observers are notified
// this is important bc some observers will reference "this.security" in their callbacks
// - namely the [SelectorProperty]order_type's "get_default" function,
@@ -44,9 +44,9 @@ namespace mbts.spark {
: base(model) {
_security_source = security_source;
_dispose = Disposable.Create(
security_source.ObserveOnDispatcher()
security_source.OnDispatcher()
.Subscribe(s => _security = s),
inline_properties.ObserveOnDispatcher()
inline_properties.OnDispatcher()
.Subscribe(p => {
if (_order_types.Count == 0 || p.child_order_types.force_override) {
_order_types.SetItems(p.child_order_types.value);
@@ -88,9 +88,9 @@ namespace mbts.spark
{
_security_source = security_source;
_dispose = Disposable.Create(
security_source.ObserveOnDispatcher()
security_source.OnDispatcher()
.Subscribe(s => _security = s),
inline_properties.ObserveOnDispatcher()
inline_properties.OnDispatcher()
.Subscribe(p => {
if (_order_types.Count == 0 || p.child_order_types.force_override)
{
@@ -55,9 +55,9 @@ namespace mbts.spark
{
_security_source = security_source;
_dispose = Disposable.Create(
security_source.ObserveOnDispatcher()
security_source.OnDispatcher()
.Subscribe(s => _security = s),
inline_properties.ObserveOnDispatcher()
inline_properties.OnDispatcher()
.Subscribe(p => {
if (_order_types.Count == 0 || p.child_order_types.force_override)
{
@@ -31,10 +31,10 @@ namespace mbts.spark {
_strategy_editor_wrapper = new StrategyEditorWrapper(this);
_dispose = Disposable.Create(
_strategy_editor_wrapper,
_template.ObserveOnDispatcher().Subscribe(vals => {
_template.OnDispatcher().Subscribe(vals => {
_validate();
}),
order_template_source.ObserveOnDispatcher().Subscribe(prop => {
order_template_source.OnDispatcher().Subscribe(prop => {
if (prop.force_override || (!template.HasValue && prop.has_value)) {
_template.Value = prop.value;
_validate();
@@ -64,9 +64,9 @@ namespace mbts.spark
{
_security_source = security_source;
_dispose = Disposable.Create(
security_source.ObserveOnDispatcher()
security_source.OnDispatcher()
.Subscribe(s => _security = s),
inline_properties.ObserveOnDispatcher()
inline_properties.OnDispatcher()
.Subscribe(p => {
if (_order_types.Count == 0 || p.child_order_types.force_override)
{
@@ -50,9 +50,9 @@ namespace mbts.spark {
: base(model) {
_security_source = security_source;
_dispose = Disposable.Create(
security_source.ObserveOnDispatcher()
security_source.OnDispatcher()
.Subscribe(s => _security = s),
inline_properties.ObserveOnDispatcher()
inline_properties.OnDispatcher()
.Subscribe(p => {
if (_order_types.Count == 0 || p.child_order_types.force_override) {
_order_types.SetItems(p.child_order_types.value);
@@ -87,9 +87,9 @@ namespace mbts.spark
{
_security_source = security_source;
_dispose = Disposable.Create(
security_source.ObserveOnDispatcher()
security_source.OnDispatcher()
.Subscribe(s => _security = s),
inline_properties.ObserveOnDispatcher()
inline_properties.OnDispatcher()
.Subscribe(p =>
{
if (_order_types.Count == 0 || p.child_order_types.force_override)
+3 -3
View File
@@ -31,11 +31,11 @@ namespace mbts.spark {
_service = service;
_spreader = spreader;
_error = error;
on_orders = _service.subscribei_orders().Table().Replay(1).RefCount();
on_orders = _service.subscribei_orders().Table().Cache();
IReadOnlyDictionary<uint, OrderRecordImmutable> empty_orders = new Dictionary<uint, OrderRecordImmutable>();
working_orders =
on_orders.Scan(empty_orders, (worders, table) => _update_working_orders(worders, table)).Replay(1).RefCount();
on_fills = _service.subscribei_fills().Table().Replay(1).RefCount();
on_fills = _service.subscribei_fills().Table().Cache();
_activity = new OrderActivitySummarizer(bus, on_orders);
on_fill_tuples = FillTupleSource.create_observable(bus.subscribe(SparkTags.SecuritiesTag), on_orders, on_fills);
_security_orders_demuxer = new TableUpdateDemuxer<OrderRecordImmutable, uint, uint>(o => o.Key, o => o.security_key());
@@ -53,7 +53,7 @@ namespace mbts.spark {
public SpreaderModel spreader => _spreader;
public IObservable<TableUpdate<OrderRecordImmutable, uint>> on_orders { get; }
public IObservable<TableUpdate<FillImmutable, string>> on_fills { get; }
public IObservable<IIncrementalUpdate<FillTuple, string>> on_fill_tuples { get; }
public IObservable<IIncrementalUpdate<FillTuple, FillTupleKey>> on_fill_tuples { get; }
public IObservable<IReadOnlyDictionary<uint,OrderRecordImmutable>> working_orders { get; }
public IObservable<ITableUpdateTransform<OrderRecordImmutable, uint>> on_security_orders(uint security) =>
+2 -2
View File
@@ -175,8 +175,8 @@ namespace bts.spark
public SparkCommandParamObserver(string title, ImageSource icon, ITag<T> tag, IObservable<T> parameter, IObservable<bool> can_execute) : base(title, icon) {
_tag = tag;
_subs = Disposable.Create(
parameter.ObserveOnDispatcher().Subscribe(p => _parameter = p),
can_execute.ObserveOnDispatcher().Subscribe(ce => {
parameter.OnDispatcher().Subscribe(p => _parameter = p),
can_execute.OnDispatcher().Subscribe(ce => {
_can_execute = ce;
raiseCanExecuteChanged();
})
@@ -345,6 +345,17 @@ namespace System.Reactive.Linq
public static async Task<(TResult result, bool success)> FirstAsyncTask<T, TResult>(this IObservable<T> source, Func<T, bool> predicate, Func<T, TResult> selector, TimeSpan timeout) where T : class =>
await source.Where(predicate).Select(val => (selector(val), true)).Merge(Observable.Return((default(TResult), false)).Delay(timeout)).FirstAsync();
// This is placed here to intentionally make ObserveOnDispatcher ambiguous with the one provided by Rx
public static IObservable<T> ObserveOnDispatcher<T>(this IObservable<T> source) {
throw new InvalidOperationException("This method can starve the UI of input messages. Please use OnDispatcher() instead");
}
public static IObservable<T> OnDispatcher<T>(this IObservable<T> source) {
return DispatcherObservable.ObserveOnDispatcher(source, DispatcherPriority.Background);
}
}
}
@@ -12,6 +12,9 @@ using bts.api.persistence;
namespace mbts.ui.settings
{
//
// This class is used for tools like SettingsManager.exe
//
[Export(typeof(IUserPersistFactory))]
class UISettingsFactory : IUserPersistFactory
{
@@ -34,7 +37,7 @@ namespace mbts.ui.settings
public IUserPersist get_persist_for(string name) {
var user = _users.Result.FirstOrDefault(u => u.Login == name);
return new UserUISettings(user.Id, _persist);
return new UserUISettings(user.Id, _persist, null);
}
}
}
@@ -10,6 +10,7 @@ using bts.api.core;
using bts.api.persistence;
using System.Text.RegularExpressions;
using System.Reactive.Linq;
using bts.spark;
namespace mbts.persist
{
@@ -18,11 +19,13 @@ namespace mbts.persist
protected abstract string user();
string uns(string ns) { return $"{user()}#{ns}"; }
PersistenceService _persist;
ErrorReporter _errors;
private const string _settings_change_base = "/ui/settingschange/";
public UISettingsBase(PersistenceService persist) {
public UISettingsBase(PersistenceService persist, ErrorReporter errors) {
_persist = persist;
_errors = errors;
}
@@ -50,7 +53,7 @@ namespace mbts.persist
Ns = uns(ns),
Key = { key }
});
check_error(ret.value.Result, ns, key, out bool empty);
check_error(ret, ns, key, out bool empty);
if (empty) return default(T);
return deserialize<T>(ret.value.Record.Single().Value);
}
@@ -67,20 +70,28 @@ namespace mbts.persist
return ProtoBuf.Serializer.DeserializeWithLengthPrefix<T>(stream, ProtoBuf.PrefixStyle.Fixed32);
}
private void check_error(RetrieveResponse.Types.Result result, string ns, string key, out bool empty) {
switch (result) {
case RetrieveResponse.Types.Result.EmptyNs:
case RetrieveResponse.Types.Result.NoSuchNs:
case RetrieveResponse.Types.Result.NoSuchKey:
empty = true;
return;
case RetrieveResponse.Types.Result.EmptyKey:
case RetrieveResponse.Types.Result.DuplicateKey:
throw new ArgumentException($"Invalid Key {result} '{key}'");
default:
case RetrieveResponse.Types.Result.Ok:
empty = false;
return;
private void check_error(Result<RetrieveResponse> result, string ns, string key, out bool empty) {
if (result.success) {
switch (result.value.Result) {
case RetrieveResponse.Types.Result.EmptyNs:
case RetrieveResponse.Types.Result.NoSuchNs:
case RetrieveResponse.Types.Result.NoSuchKey:
empty = true;
return;
case RetrieveResponse.Types.Result.EmptyKey:
case RetrieveResponse.Types.Result.DuplicateKey:
_errors?.report_error($"Invalid Key {result} '{key}'", "Requested Invalid Settings Key", System.Windows.MessageBoxImage.Information);
empty = true;
return;
default:
case RetrieveResponse.Types.Result.Ok:
empty = false;
return;
}
}
else {
_errors?.report_error($"There was an error communicating with the settings server: '{result.error_message}'", "Failed to retrieve settings from the server");
empty = true;
}
}
@@ -90,7 +101,7 @@ namespace mbts.persist
new RetrieveAllRequest() {
Ns = uns(ns)
});
check_error(results.value.Result, ns, "", out bool empty);
check_error(results, ns, "", out bool empty);
if (empty) return Enumerable.Empty<PersistResult<T>>();
return results.value.Record
.Where(r => re.IsMatch(r.Key))
@@ -131,7 +142,7 @@ namespace mbts.persist
protected override string user() { return "all_users"; }
[ImportingConstructor]
public UISettings([Import("settings")]PersistenceService p) : base(p) { }
public UISettings([Import("settings")]PersistenceService p, ErrorReporter err) : base(p,err) { }
}
[Export(typeof(IUserPersist))]
@@ -140,8 +151,8 @@ namespace mbts.persist
string _user;
[ImportingConstructor]
public UserUISettings([Import("userid")]uint user, [Import("settings")]PersistenceService persist)
: base(persist) {
public UserUISettings([Import("userid")]uint user, [Import("settings")]PersistenceService persist, ErrorReporter err)
: base(persist,err) {
_user = $"User {user}";
}
@@ -28,6 +28,20 @@ namespace mbts.utils {
y != null &&
non_null_equals(x, y);
public static int aggregate_hash(int item1_hash, int item2_hash, int item3_hash) {
int rv = 63607;
unchecked {
_accumulate_hash(ref rv, ref item1_hash);
_accumulate_hash(ref rv, ref item2_hash);
_accumulate_hash(ref rv, ref item3_hash);
}
return rv;
}
// WARNING: be sure to call this within in an unchecked scope
static void _accumulate_hash(ref int aggregate_hash, ref int item_hash) =>
aggregate_hash = aggregate_hash * 6451 + item_hash;
private class AnonymousEqualityComparer<T> : IEqualityComparer<T> {
readonly Func<T, T, bool> _equals;
readonly Func<T, int> _get_hash_code;
@@ -2,9 +2,9 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Reactive.Subjects;
using System.Threading;
namespace mbts.utils {
interface IObservableDemuxer<TKey, in TInput, out TOutput> : IObserver<TInput> {
@@ -89,21 +89,17 @@ namespace mbts.utils {
_group_key = group_key;
}
public void OnCompleted() { }
public void OnError(Exception error) { }
public IObservable<ITableUpdateTransform<T, TKey>> subscribe(TGroupKey group_key) =>
Observable.Create<ITableUpdateTransform<T, TKey>>(o => {
var data = _map.GetOrAdd(group_key, gk => new Data());
lock (data) {
var current = data.current;
if (current.state.Count > 0) {
var state_clone = current.state.ToDictionary();
o.OnNext(new TableUpdateTransform<T, TKey>(state_clone, state_clone.Values.ToList(), new List<T>(0)));
}
using (data.read()) {
o.OnNext(data.current.as_snapshot());
return data.subject.Subscribe(o);
}
return data.subject.Subscribe(o);
});
public void OnCompleted() { }
public void OnError(Exception error) { }
public void OnNext(TableUpdate<T, TKey> table) {
var changes = new Dictionary<TGroupKey, ChangeSet>();
foreach (T value in table.added)
@@ -116,16 +112,29 @@ namespace mbts.utils {
}
Data _update(Data data, ChangeSet changes) {
lock (data) {
data.current = data.current.update(changes.removed, changes.added, _key);
data.subject.OnNext(data.current);
}
using (data.write())
data.subject.OnNext(data.current = data.current.update(changes.removed, changes.added, _key));
return data;
}
class Data {
int _n_read_ops, _n_write_ops;
public readonly Subject<TableUpdateTransform<T, TKey>> subject = new Subject<TableUpdateTransform<T, TKey>>();
public TableUpdateTransform<T, TKey> current = new TableUpdateTransform<T, TKey>();
public IDisposable read() {
while (_n_write_ops > 0)
Thread.Yield();
Interlocked.Increment(ref _n_read_ops);
return Disposable.Create(() => Interlocked.Decrement(ref _n_read_ops));
}
public IDisposable write() {
while (_n_read_ops > 0)
Thread.Yield();
Interlocked.Increment(ref _n_write_ops);
return Disposable.Create(() => Interlocked.Decrement(ref _n_write_ops));
}
}
// [John]I prefer this class over anonymous tuples bc it is easy to mix up Item1/Item2
@@ -19,6 +19,9 @@ namespace mbts.utils {
}
class IncrementalUpdate<T, TKey> : IIncrementalUpdate<T, TKey> where TKey : IEquatable<TKey> {
public IncrementalUpdate()
: this(new Dictionary<TKey, T>(0), new Dictionary<TKey, T>(0)) { }
public IncrementalUpdate(IReadOnlyDictionary<TKey, T> added, IReadOnlyDictionary<TKey, T> removed) {
this.added = added;
this.removed = removed;
@@ -68,6 +71,9 @@ namespace mbts.utils {
table._state.Add(kv.Key, kv.Value);
return table;
}
internal TableUpdateTransform<T, TKey> as_snapshot() =>
new TableUpdateTransform<T, TKey>(_state, _state.Values.ToList(), new T[0]);
}
[Browsable(false)]
@@ -82,6 +88,15 @@ namespace mbts.utils {
.Select(u => new FilteredTableUpdateTransform<T, TKey>(u, predicate))
.Where(u => u.added.Count > 0 || u.removed.Count > 0);
/// <summary>
/// Makes a 1:1 conversion to an IncrementalUpdate sequence of TResult (TKey remains the same).
/// </summary>
public static IObservable<IIncrementalUpdate<TResult, TKey>> convert<T, TKey, TResult>(this IObservable<IIncrementalUpdate<T, TKey>> source, Func<T, TResult> selector)
where TKey : IEquatable<TKey> =>
source.Select(update => new IncrementalUpdate<TResult, TKey>(
update.added.ToDictionary(kv => kv.Key, kv => selector(kv.Value)),
update.removed.ToDictionary(kv => kv.Key, kv => selector(kv.Value))));
/// <summary>
/// Converts sequence to show incremental changes with respect to the [TResult] value returned by result_selector.
/// </summary>
@@ -471,7 +471,7 @@ namespace mbts.ui.shell
protected Property<T> backing<T, TData>(string name, IObservable<TData> source, Func<TData, T> select, T default_value = default) {
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
var prop = new ObservablePropertySink<T>(this, name, default_value);
_observable_properties.Add(source.ObserveOnDispatcher().Subscribe(d => prop.set(select(d))));
_observable_properties.Add(source.OnDispatcher().Subscribe(d => prop.set(select(d))));
return prop;
}
@@ -526,8 +526,8 @@ namespace mbts.ui.shell
protected SelectorProperty<T> backing<T>(string name, IObservable<IEnumerable<T>> items, IObservable<InlineUserProperty<T>> desired_item, Func<T> select_default, Func<T, T, bool> items_match, IReadOnlyList<PropertyValidation<T>> validation_steps) {
if (_observable_properties == null) _observable_properties = new List<IDisposable>();
var prop = new SelectorProperty<T>(this, name, select_default, items_match, validation_steps);
_observable_properties.Add(items.ObserveOnDispatcher().Subscribe(prop));
_observable_properties.Add(desired_item.ObserveOnDispatcher().Subscribe(prop));
_observable_properties.Add(items.OnDispatcher().Subscribe(prop));
_observable_properties.Add(desired_item.OnDispatcher().Subscribe(prop));
return prop;
}
@@ -73,7 +73,7 @@ namespace mbts.ui.controls {
Disposable.dispose(ref timer._trigger_sub);
IObservable<object> newval = args.NewValue as IObservable<object>;
if (newval != null) {
timer._trigger_sub = newval.ObserveOnDispatcher().Subscribe(timer.start);
timer._trigger_sub = newval.OnDispatcher().Subscribe(timer.start);
}
}
@@ -1,4 +1,5 @@
using mbts.spark.filters;
using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
@@ -60,13 +61,19 @@ namespace mbts.ui.controls {
WPFHelper.create_dp<ListSortDirection?, ColumnBase>(
nameof(SortDirection), (o, v) => o.OnSortDirectionChanged(v));
public ListSortDirection? SortDirection { get => GetValue(SortDirectionProperty) as ListSortDirection?; set => SetValue(SortDirectionProperty, value); }
public ListSortDirection? SortDirection {
get => GetValue(SortDirectionProperty) as ListSortDirection?;
set => SetValue(SortDirectionProperty, value);
}
public event Action<ColumnBase, ListSortDirection?> sort_direction_changed;
public string sort_member_path { get; set; }
public double original_width { get; set; }
public bool is_collapsed { get; set; }
protected virtual void OnSortDirectionChanged(ListSortDirection? new_value) { }
protected virtual void OnSortDirectionChanged(ListSortDirection? new_value) =>
sort_direction_changed?.Invoke(this, new_value);
}
internal abstract class ColumnBase<TColKey> : ColumnBase, IKeyedColumn<TColKey> {
@@ -115,7 +115,7 @@ namespace mbts.spark.filters {
IComparer<TValue> comparer = null
) : this(get_row_val, get_state, load_state, prop_name, equality_comparer, comparer) {
//
_items_sub = items.ObserveOnDispatcher().Subscribe(new_items => {
_items_sub = items.OnDispatcher().Subscribe(new_items => {
_items_set.SetItems(new_items.Concat(_selected_items_set));
_unsorted_items.sync_with(_items_set);
});
@@ -131,7 +131,7 @@ namespace mbts.spark.filters {
IComparer<TValue> comparer = null
) : this(get_row_val, get_state, load_state, prop_name, equality_comparer, comparer) {
//
_items_sub = items.ObserveOnDispatcher().Subscribe(update => {
_items_sub = items.OnDispatcher().Subscribe(update => {
foreach (var v in update.added)
_items_set.Add(v);
foreach (var v in update.removed.Except(_selected_items_set))
@@ -182,7 +182,7 @@ namespace mbts.spark.filters {
protected override void _load_state_internal(TState state) {
Disposable.dispose(ref _desired_items_sub);
_desired_items_sub = _load_state(state).ObserveOnDispatcher().Subscribe(_on_desired_item);
_desired_items_sub = _load_state(state).OnDispatcher().Subscribe(_on_desired_item);
}
protected override void _apply() {
@@ -8,6 +8,7 @@ namespace mbts.ui.converters
internal class EnumCharacterCasingConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if (value is null) return string.Empty;
string s = value is Enum ? EnumHelper.get_display_name((Enum)value) : value.ToString();
CharacterCasing casing;
@@ -70,7 +70,7 @@ namespace mbts.ui
private void _subscribe() {
Disposable.dispose(ref _sub);
_sub = _source.ObserveOnDispatcher().Subscribe(_on_data);
_sub = _source.OnDispatcher().Subscribe(_on_data);
}
private void _on_data(T data) { Target.SetValue(_target_property, _last_value = data); }
@@ -29,7 +29,7 @@ namespace mbts.ui.shell
public async Task<IEnumerable<string>> get_workspaces(User user) {
if (!_cache.TryGetValue(user.id, out List<string> result)) {
using (var svc = new UserUISettings(user.id, _persist)) {
using (var svc = new UserUISettings(user.id, _persist, null)) {
var workspaces = await svc.query<WorkspaceDefinition>(WorkspacesModel.WORKSPACE_NAMESPACE, ".*");
result = workspaces.Select(w => w.key).ToList();
_cache[user.id] = result;
+1 -1
View File
@@ -38,7 +38,7 @@ namespace mbts.ui.shell {
_window_settings = settings;
_window_factory = factory;
_undo = undo;
_dispose = bus.subscribe(SparkTags.ShowWindow).ObserveOnDispatcher().Subscribe(w => add(w).ignore());
_dispose = bus.subscribe(SparkTags.ShowWindow).OnDispatcher().Subscribe(w => add(w).ignore());
}
#region Window creation
@@ -27,7 +27,7 @@ namespace mbts.spark
{
internal class ContractPickerModel : ViewModelBase {
private const string dot = "•";
private const string dot = "•";
public ContractPickerModel(IBus bus, bool edit_mode, SecurityCriteria existing_criteria, SecurityCriteria world_view_filter = null) {
_bus = bus;
@@ -38,7 +38,7 @@ namespace mbts.spark
_world_view_filter = world_view_filter;
securities_view = CollectionViewSource.GetDefaultView(securities);
(securities_view as ListCollectionView).CustomSort = new SecuritySorter();
_subs.Add(_bus.subscribe(SecuritiesTag).ObserveOnDispatcher().Subscribe(on_securities_changed));
_subs.Add(_bus.subscribe(SecuritiesTag).OnDispatcher().Subscribe(on_securities_changed));
add_criteria_command = new DelegateCommand(
execute: _ => {
@@ -170,6 +170,13 @@ namespace mbts.spark {
public static bool tick_equals(this ContractSpecificationImmutable contract, double x, double y) =>
MathUtils.RoundFractionalUnits(x, contract.LowTick) == MathUtils.RoundFractionalUnits(y, contract.LowTick);
public static IEqualityComparer<double> tick_equality_comparer(this ContractSpecificationImmutable contract)
{
var eq = EqualityComparer.Create((double a, double b) => TickUtils.tick_equals(contract, a, b),
x => MathUtils.RoundFractionalUnits(x, contract.LowTick));
return eq;
}
public static double round_to_tick(this ContractSpecificationImmutable spec, double value) {
double intpart = (long)value;
double floatpart = value % 1;
@@ -204,12 +204,12 @@ namespace mbts.spark {
protected override async void OnMouseDown(MouseButtonEventArgs e) {
Keyboard.ClearFocus();
Focus();
if (e.ChangedButton == MouseButton.Middle)
var point = e.GetPosition(this);
var column = _column_for_point(point);
if (column.column_type == ColumnType.Price && e.ChangedButton == MouseButton.Middle)
_scroll_to_center();
else if (_settings != null) {
var point = e.GetPosition(this);
var price = price_for_point(point);
var column = _column_for_point(point);
Side? bs = null;
if (column.column_type == ColumnType.Bid) bs = Side.Buy;
if (column.column_type == ColumnType.Ask) bs = Side.Sell;
@@ -340,7 +340,7 @@ namespace mbts.spark {
_price_comparer = Comparer<double>.Create((x, y) => security.ContractSpec.tick_equals(x, y) ? 0 : y.CompareTo(x));
_price_dirty = true;
_vm.on_tick += _tick;
_settings_sub = _vm.settings.ObserveOnDispatcher().Subscribe(_settings_updated);
_settings_sub = _vm.settings.OnDispatcher().Subscribe(_settings_updated);
_tick_sub = Disposable.Create(() => _vm.on_tick -= _tick);
}
}
@@ -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();
}
}
+12 -6
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;
@@ -15,6 +16,8 @@ using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using System.Windows.Media;
using mbts.utils;
using mbts.spark.utilities;
namespace mbts.spark {
class LadderVM : DisposableViewModelBase, IKeyListener {
@@ -26,11 +29,14 @@ namespace mbts.spark {
readonly Property<Brush> _bid_background, _bid_foreground, _ask_background, _ask_foreground;
IWindowSettingsManager<LadderSettings> _settings_manager;
ulong _misc_time = 0;
Dictionary<double, LadderOrderVM> _slot_price_vs_orders = new Dictionary<double, LadderOrderVM>();
IEqualityComparer<double> _equality;
Dictionary<double, LadderOrderVM> _slot_price_vs_orders;
public LadderVM(IBus bus, LadderModel model, IWindowSettingsManager<LadderSettings> settings_manager) {
_bus = bus;
_model = model;
_equality = TickUtils.tick_equality_comparer(_model.security.ContractSpec);
_slot_price_vs_orders = new Dictionary<double, LadderOrderVM>(_equality);
_settings_manager = settings_manager;
_high = backing(nameof(high), double.NaN);
_low = backing(nameof(low), double.NaN);
@@ -47,7 +53,7 @@ namespace mbts.spark {
_ask_background = brush_prop(nameof(ask_background), s => s.ask_column.background.rgb_brush());
_ask_foreground = brush_prop(nameof(ask_foreground), s => s.ask_column.foreground.rgb_brush());
Property<Brush> brush_prop(string name, Func<LadderSettings, Brush> get_brush) =>
backing(name, settings_manager.subscribe_current().ObserveOnDispatcher(), get_brush);
backing(name, settings_manager.subscribe_current().OnDispatcher(), get_brush);
//
send_order_command = new DelegateCommand<(double price, Side? side, bool size_from_alternate)>(send_order);
cancel_command = new AsyncCommand<LadderModel.CancelType>(_model.cancel_orders, true);
@@ -69,11 +75,11 @@ namespace mbts.spark {
settings_command,
order_properties_vm = new OrderPropertiesEditorVM(model.order_gateway, Observable.Return(security), _settings_manager.subscribe_current().Select(s => new InlineOrderProperties(model.order_gateway, s))),
size_calculator = new CalculatorLogic(0, _settings_manager.subscribe_current().Select(s => s.calculator_buttons)),
bus.subscribe(SparkTags.LogActivity).ObserveOnDispatcher().Subscribe(status_update),
model.order_gateway.working_orders.ObserveOnDispatcher().Subscribe(working => {
bus.subscribe(SparkTags.LogActivity).OnDispatcher().Subscribe(status_update),
model.order_gateway.working_orders.OnDispatcher().Subscribe(working => {
// Here we manage N orders to 1 price slot in the ladder
Dictionary<double, HashSet<uint>> orders_used_in_this_update = new Dictionary<double, HashSet<uint>>();
Dictionary<double, HashSet<uint>> orders_used_in_this_update = new Dictionary<double, HashSet<uint>>(_equality);
var ladders_orders = working.Values.Where(o => o.Def.SecurityKey == security.Id);
foreach (var wo in ladders_orders) {
var slot_price_target = wo.price();
@@ -152,7 +158,7 @@ namespace mbts.spark {
public ICommand status_click_command => SparkCommands.ShowActivityLog.ICommand(_bus);
public async void status_update(Activity a) {
if (a.security == _model.security) {
if (a.security == _model.security && a.category != ActivityLogCategory.modify) {
_status.set(a.message);
_status_brush.set(a.category.fill);
await Task.Delay(TimeSpan.FromSeconds(5));
@@ -1,15 +1,9 @@
using bts.api;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel.Composition;
using System.Linq;
using System.Reactive.Linq;
using System.Threading.Tasks;
namespace mbts.spark
{
namespace mbts.spark {
[Export]
class PAndLModel : IDisposable {
readonly IBus _bus;
@@ -24,19 +18,8 @@ namespace mbts.spark
//_account_service = account_service;
}
public IObservable<List<PnLData>> on_pnl_data =>
// combine w/ Securities to get SecurityMD
_order_model.on_fill_tuples.table(ft => ft.key).CombineLatest(_bus.subscribe(SparkTags.SecuritiesTag), (fills, securities) => {
List<PnLData> pnl_data = new List<PnLData>(fills.state.Count);
foreach (var ft in fills.state.Values) {
if (ft.security.HasValue && securities.TryGetValue(ft.security.Value.Id, out SecurityMD sec_md)) {
pnl_data.Add(new PnLData(ft.fill, sec_md, ft.order));
} else {
pnl_data.Add(new PnLData(ft.fill, null, ft.order));
}
}
return pnl_data;
});
public IObservable<ITableUpdateTransform<PnLData, FillTupleKey>> on_pnl_data =>
_order_model.on_fill_tuples.convert(ft => new PnLData(ft)).table(pd => pd.key);
//public async Task<Account[]> get_accounts() {
// var request = await _account_service.get_accounts_async(new GetAccountsRequest());
@@ -46,12 +29,8 @@ namespace mbts.spark
// }
// return new Account[] { };
//}
public void Dispose() =>
Disposable.dispose(ref _dispose);
}
}
}
+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>();
@@ -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)
+25 -6
View File
@@ -44,18 +44,16 @@ namespace bts.api.core {
public class TableUpdate<T, TKey> where TKey : IEquatable<TKey> {
readonly Dictionary<TKey, T> _state;
readonly IReadOnlyList<T> _added;
readonly IReadOnlyList<T> _removed;
internal TableUpdate(Dictionary<TKey, T> init, IReadOnlyList<T> added, IReadOnlyList<T> removed) {
_state = init;
_added = added;
_removed = removed;
this.added = added;
this.removed = removed;
}
public IReadOnlyDictionary<TKey, T> state { get { return _state; } }
public IReadOnlyList<T> added { get { return _added; } }
public IReadOnlyList<T> removed { get { return _removed; } }
public IReadOnlyList<T> added { get; }
public IReadOnlyList<T> removed { get; }
internal TableUpdate<T, TKey> update(Update<T, TKey> update) =>
this.update(update.removed, update.added, update.key);
@@ -70,6 +68,10 @@ namespace bts.api.core {
}
return table;
}
internal TableUpdate<T, TKey> as_snapshot() {
return new TableUpdate<T, TKey>(_state, _state.Values.ToList(), new T[0]);
}
}
public static class ObservableUpdateExtensions {
@@ -102,5 +104,22 @@ namespace bts.api.core {
source.Scan(
new TableUpdate<T,TKey>(new Dictionary<TKey, T>(), new List<T>(), new List<T>()),
(table, update) => table.update(update));
public static IObservable<TableUpdate<T,TKey>> Cache<T, TKey>(this IObservable<TableUpdate<T, TKey>> source) where TKey : IEquatable<TKey> {
var inner = source.Replay(1).RefCount();
return Observable.Create<TableUpdate<T,TKey>>(o => {
bool first = true;
return inner.Subscribe(upd => {
if (first) {
o.OnNext(upd.as_snapshot());
first = false;
}
else {
o.OnNext(upd);
}
});
});
}
}
}
+2 -13
View File
@@ -30,13 +30,12 @@ namespace bts.log {
Persist,
User,
OrderMan,
Latency,
Assert,
Engine,
Ui,
ExSim,
Mercury,
Testing
Testing
// Please keep this list in sync with logging.h/logging.cpp
}
}
@@ -121,14 +120,4 @@ namespace mbts
}
public static event Action<string, object> OnSystemCrashing;
}
}
}
+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
@@ -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;
+5 -10
View File
@@ -14,21 +14,16 @@ namespace cmd {
int QuickTest::execute(std::vector<std::string> const &args) {
using namespace double_conversion;
static char buffer[102];
static char buffer1[102];
static char buffer2[102];
static char buffer3[102];
int flags = DoubleToStringConverter::UNIQUE_ZERO;
static DoubleToStringConverter converter(flags, "Infinity", "NaN", 'e', -6,
21, 0, 0);
auto n = std::stod(args[0], nullptr);
auto p = std::stoi(args[1], nullptr);
{
double_conversion::StringBuilder builder(buffer, sizeof(buffer));
converter.ToPrecision(n, p, &builder);
}
d_toa(n, buffer2, sizeof(buffer2), p);
d_toa(n, buffer1, sizeof(buffer1), 7,p);
d_toa(n, buffer2, sizeof(buffer2),7);
d_toa(n, buffer3, sizeof(buffer3));
std::cout << n << " -> " << buffer << " (" << strlen(buffer) << ") "
std::cout << n
<< " -> " << buffer1 << " (" << strlen(buffer1) << ") "
<< " -> " << buffer2 << " (" << strlen(buffer2) << ") "
<< " -> " << buffer3 << " (" << strlen(buffer3) << ") \n";
return 0;
@@ -8,107 +8,100 @@ namespace bts {
namespace jetfix {
namespace fix {
typedef StringField<8> BeginString;
typedef IntField<9> BodyLength;
typedef StringField<35> MsgType;
typedef IntField<34> MsgSeqNum;
typedef IntField<369> LastSeqNumProcessed;
typedef StringField<49> SenderCompID;
typedef StringField<50> SenderSubID ;
typedef TimeField<52> SendingTime;
typedef TimeField<122> OrigSendingTime;
typedef StringField<56> TargetCompId;
typedef StringField<57> TargetSubID;
typedef StringField<142> SenderLocationID;
typedef StringField<37> OrderID;
typedef StringField<117> QuoteID;
typedef IntField<117> QuoteIDInt;
typedef IntField<204> CustomerOrFirm;
typedef IntField<296> NoQuoteSets;
typedef StringField<302> QuoteSetID;
typedef StringField<307> UnderlyingSecurityDesc;
typedef IntField<304> TotQuoteEntries;
typedef IntField<295> NoQuoteEntries;
typedef StringField<304> TotQuoteEntriesStr;
typedef StringField<295> NoQuoteEntriesStr;
typedef BoolField<1028> ManualOrderIndicator;
typedef StringField<7928> SelfMatchPreventionID;
typedef CharField<9702> CtiCode;
typedef StringField<9771> MMAccount;
typedef BoolField<123> GapFillFlag;
typedef BoolField<43> PossDupFlag;
typedef BoolField<9768> InFlightMitigation;
typedef CharField<150> ExecType;
typedef CharField<21> HandlInst;
typedef CharField<40> OrdType;
typedef CharField<59> TimeInForce;
typedef CharField<39> OrdStatus;
typedef StringField<41> OrigClOrdID;
typedef StringField<9717> CorrelationClOrdID;
typedef IntField<102> CXlRejReason;
typedef FloatField<132> BidPx;
typedef IntField<134> BidSize;
typedef StringField<132> BidPxStr;
typedef StringField<134> BidSizeStr;
typedef FloatField<133> OfferPx;
typedef IntField<135> OfferSize;
typedef StringField<133> OfferPxStr;
typedef StringField<135> OfferSizeStr;
typedef StringField<299> QuoteEntryID;
typedef IntField<299> QuoteEntryIDInt;
typedef StringField<55> Symbol;
typedef StringField<167> SecurityType;
typedef StringField<107> SecurityDesc;
typedef StringField<48> SecurityID;
typedef CharField<22> SecurityIDSource;
typedef TimeField<60> TransactTime;
typedef TimeField<5979> RequestTime;
typedef StringField<60> TransactTimeStr;
typedef StringField<143> TargetLocationID;
typedef StringField<142> SenderLocationID;
typedef StringField<142> SenderLocationID;
typedef IntField<10> Checksum;
typedef IntField<98> EncryptMethod;
typedef CharField<39> OrdStatus;
typedef CharField<40> OrdType;
typedef CharField<47> Rule80a;
typedef CharField<54> Side;
typedef CharField<59> TimeInForce;
typedef CharField<9702> CtiCode;
typedef CharField<9775> UnsolicitedCancelType;
typedef FloatField<132> BidPx;
typedef FloatField<133> OfferPx;
typedef FloatField<151> LeavesQty;
typedef FloatField<31> LastPx;
typedef FloatField<6> AvgPx;
typedef IntField<102> CXlRejReason;
typedef IntField<108> HeartBtInt;
typedef IntField<10> Checksum;
typedef IntField<117> QuoteIDInt;
typedef IntField<134> BidSize;
typedef IntField<135> OfferSize;
typedef IntField<14> CumQty;
typedef IntField<16> EndSeqNo;
typedef IntField<204> CustomerOrFirm;
typedef IntField<295> NoQuoteEntries;
typedef IntField<296> NoQuoteSets;
typedef IntField<297> QuoteAckStatus;
typedef IntField<298> QuoteCancelType;
typedef IntField<299> QuoteEntryIDInt;
typedef IntField<300> QuoteRejectReason;
typedef IntField<304> TotQuoteEntries;
typedef IntField<32> LastShares;
typedef IntField<34> MsgSeqNum;
typedef IntField<368> QuoteEntryRejectReason;
typedef IntField<369> LastSeqNumProcessed;
typedef IntField<36> NewSeqNo;
typedef IntField<386> NoTradingSessions;
typedef IntField<442> MultiLegReportingType;
typedef IntField<7> BeginSeqNo;
typedef IntField<95> RawDataLength;
typedef IntField<98> EncryptMethod;
typedef IntField<9> BodyLength;
typedef StringField<107> SecurityDesc;
typedef StringField<109> ClientID;
typedef StringField<112> TestReqId;
typedef StringField<117> QuoteID;
typedef StringField<11> ClOrdID;
typedef StringField<132> BidPxStr;
typedef StringField<133> OfferPxStr;
typedef StringField<134> BidSizeStr;
typedef StringField<135> OfferSizeStr;
typedef StringField<142> SenderLocationID;
typedef StringField<143> TargetLocationID;
typedef StringField<1603> ApplicationSystemName;
typedef StringField<1604> ApplicationSystemVersion;
typedef StringField<1605> ApplicationSystemVendor;
typedef IntField<95> RawDataLength;
typedef StringField<96> RawData;
typedef StringField<109> ClientID;
typedef IntField<7> BeginSeqNo;
typedef IntField<16> EndSeqNo;
typedef StringField<112> TestReqId;
typedef BoolField<123> GapFillFlag;
typedef IntField<36> NewSeqNo;
typedef BoolField<43> PossDupFlag;
typedef IntField<297> QuoteAckStatus;
typedef CharField<9775> UnsolicitedCancelType;
typedef IntField<300> QuoteRejectReason;
typedef StringField<58> Text;
typedef IntField<368> QuoteEntryRejectReason;
typedef IntField<298> QuoteCancelType;
typedef StringField<1> Account;
typedef CharField<150> ExecType;
typedef StringField<11> ClOrdID;
typedef CharField<54> Side;
typedef FloatField<31> LastPx;
typedef FloatField<6> AvgPx;
typedef IntField<32> LastShares;
typedef IntField<14> CumQty;
typedef StringField<527> SecondaryExecID;
typedef StringField<76> ExecBroker;
typedef StringField<167> SecurityType;
typedef StringField<17> ExecID;
typedef StringField<19> ExecRefID;
typedef FloatField<151> LeavesQty;
typedef IntField<442> MultiLegReportingType;
typedef IntField<386> NoTradingSessions;
typedef StringField<1> Account;
typedef StringField<295> NoQuoteEntriesStr;
typedef StringField<299> QuoteEntryID;
typedef StringField<302> QuoteSetID;
typedef StringField<304> TotQuoteEntriesStr;
typedef StringField<307> UnderlyingSecurityDesc;
typedef StringField<336> TradingSessionID;
typedef StringField<35> MsgType;
typedef StringField<37> OrderID;
typedef StringField<41> OrigClOrdID;
typedef StringField<440> ClearingAccount;
typedef CharField<47> Rule80a;
typedef StringField<48> SecurityID;
typedef StringField<49> SenderCompID;
typedef StringField<50> SenderSubID ;
typedef StringField<527> SecondaryExecID;
typedef StringField<55> Symbol;
typedef StringField<56> TargetCompId;
typedef StringField<57> TargetSubID;
typedef StringField<58> Text;
typedef StringField<60> TransactTimeStr;
typedef StringField<76> ExecBroker;
typedef StringField<7928> SelfMatchPreventionID;
typedef StringField<8> BeginString;
typedef StringField<9324> OrderOptionalData;
typedef BoolField<9768> InFlightMitigation;
typedef StringField<96> RawData;
typedef StringField<9717> CorrelationClOrdID;
typedef StringField<9771> MMAccount;
typedef TimeField<122> OrigSendingTime;
typedef TimeField<52> SendingTime;
typedef TimeField<5979> RequestTime;
typedef TimeField<60> TransactTime;
typedef Group<NoQuoteEntries,
QuoteEntryID,
+5 -4
View File
@@ -114,17 +114,17 @@ public:
Parent::_value = atof (&(cursor.data ()[loc.first]));
}
static uint32_t max_value_size ( double,int pad = 0)
static uint32_t max_value_size ( double,int, int pad)
{
if (pad == 0)
if (pad == -1)
return 17; // 15 from FIX spec, 1 for decimal, 1 for sign
else
return pad;
}
static std::pair<uint32_t,uint32_t> write (char*buffer,double d,int pad = 0)
static std::pair<uint32_t,uint32_t> write (char*buffer,double d,int decimals, int pad )
{
auto written = d_toa (d,buffer,max_value_size (d,pad)+1,pad);
auto written = d_toa (d,buffer,max_value_size (d,decimals, pad)+1,decimals,pad);
return std::make_pair (written,sum_bytes (buffer,written));
}
};
@@ -207,3 +207,4 @@ public:
};
}}
+56 -18
View File
@@ -2,32 +2,70 @@
#include "lib/utils/bts_assert.h"
#include <memory>
#include <double-conversion/double-conversion.h>
#include <memory>
namespace bts {
inline int d_toa (double n,char*buffer, size_t buffer_size,int pad=0)
{
inline int d_toa(double n, char *buffer, size_t buffer_size, int decimal_digits=-1, int pad = -1) {
static double_conversion::DoubleToStringConverter const &c =
double_conversion::DoubleToStringConverter::EcmaScriptConverter ();
double_conversion::DoubleToStringConverter::EcmaScriptConverter();
double_conversion::StringBuilder builder(buffer,buffer_size);
if (pad == 0) {
bts_assert (c.ToShortest (n,&builder));
return builder.position ();
} else {
bts_assert ((int) buffer_size >= pad);
bts_assert(buffer_size);
if (pad == -1) {
// no padding, return the smallest number that contains all the
// specified digits
double_conversion::StringBuilder builder(buffer, buffer_size);
if (n < 0)
bts_assert (c.ToPrecision (n,pad-2,&builder));
else
bts_assert (c.ToPrecision (n,pad-1,&builder));
if (decimal_digits == -1 ) {
if (c.ToShortest(n,&builder))
return builder.position();
}
else {
if(c.ToFixed(n,decimal_digits,&builder))
return builder.position();
}
buffer[0] = 0;
return 0;
}
else {
// padding, return something with a fixed size
std::array<char, 128> pre_buffer;
bts_assert((int)buffer_size > pad);
bts_assert((int)pre_buffer.size() >= pad);
double_conversion::StringBuilder builder(&pre_buffer[0], pre_buffer.size());
auto ok = false;
if (decimal_digits == -1)
ok = c.ToShortest(n,&builder);
else
ok = c.ToFixed(n,decimal_digits,&builder);
if(ok) {
auto sz = builder.position();
if (sz < pad) {
auto n_left_zeros = pad - sz;
memset(&buffer[0], '0',n_left_zeros);
memcpy(&buffer[n_left_zeros], &pre_buffer[0],sz);
if (n < 0) {
buffer[0] = '-';
buffer[n_left_zeros] = '0';
}
buffer[pad]=0;
return pad;
}
}
for (int i = 0; i < pad;i++)
buffer[i] = ' ';
buffer[pad] = 0;
return pad;
}
}
inline int f_toa (float n,char*buffer, size_t buffer_size,int pad=0)
{return d_toa(n,buffer,buffer_size,pad);}
}
+258 -289
View File
@@ -1,78 +1,71 @@
#include "CoreGateway.h"
#include "lib/logging/logging.h"
#include "lib/utils/TimeStamp.h"
#include "lib/utils/bts_chrono.h"
#include "lib/utils/datetime.h"
#include "server/sparkd/jetfix/common/OutFIX.h"
#include "server/sparkd/jetfix/connection/AsyncReader.h"
#include "server/sparkd/jetfix/connection/IConnection.h"
#include "server/sparkd/jetfix/gateway/ICoreGatewayObserver.h"
#include "server/sparkd/jetfix/gateway/Session.h"
#include "server/sparkd/jetfix/store/IFIXStore.h"
#include "lib/logging/logging.h"
#include "lib/utils/TimeStamp.h"
#include "lib/utils/datetime.h"
#include "lib/utils/bts_chrono.h"
#include "server/sparkd/orderman/orderman_utils/orderman_common.h"
#include "server/sparkd/tracing/bts_trace.h"
#include <thread>
#include <atomic>
#include <fstream>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <moodycamel/concurrentqueue.h>
#include <fmt/format.h>
#include <fstream>
#include <moodycamel/concurrentqueue.h>
#include <thread>
namespace bts {
namespace jetfix {
enum class CoreGateway::State {OFF,PAUSED,CONNECTING,READY,WREADER_DISCONNECT,WSESSION_TERMINATE};
enum class CoreGateway::State {
OFF,
PAUSED,
CONNECTING,
READY,
WREADER_DISCONNECT,
WSESSION_TERMINATE
};
struct CoreGateway::SessionInfo {
SessionInfo() : connection(), store(), reader(), session() {}
struct CoreGateway::SessionInfo
{
SessionInfo () : connection (), store (), reader (), session ()
{}
std::unique_ptr<IConnection> connection;
std::unique_ptr<IFIXStore> store;
std::unique_ptr<AsyncReader> reader;
std::unique_ptr<Session> session;
};
struct CoreGateway::EventData
{
typedef void (CoreGateway::*Handler)(EventData const&ev);
struct CoreGateway::EventData {
typedef void (CoreGateway::*Handler)(EventData const &ev);
EventData (const EventData &src) : handler (src.handler),
out_msgs (src.out_msgs),
in_msg (src.in_msg),
cid(src.cid),
ts (src.ts)
{}
EventData(const EventData &src)
: handler(src.handler), out_msgs(src.out_msgs), in_msg(src.in_msg),
cid(src.cid), ts(src.ts) {}
EventData () : handler (), out_msgs (),in_msg (), cid(orderman::NullTickToTradeCorID),ts () {}
EventData (Handler handler) : handler (handler), out_msgs (),in_msg (),cid(), ts () {}
EventData (Handler handler,orderman::TickToTradeCorID cid, OutFIXPtr const &out_msgs) :
handler (handler),
out_msgs (out_msgs),
in_msg (),
cid(cid),
ts ()
{}
EventData()
: handler(), out_msgs(), in_msg(), cid(orderman::NullTickToTradeCorID),
ts() {}
EventData(Handler handler)
: handler(handler), out_msgs(), in_msg(), cid(), ts() {}
EventData(Handler handler, orderman::TickToTradeCorID cid,
OutFIXPtr const &out_msgs)
: handler(handler), out_msgs(out_msgs), in_msg(), cid(cid), ts() {}
EventData (Handler handler,InMsgBuffer const&in_msg) :
handler (handler), out_msgs (),
in_msg (in_msg),
cid(orderman::NullTickToTradeCorID),
ts ()
{}
EventData(Handler handler, InMsgBuffer const &in_msg)
: handler(handler), out_msgs(), in_msg(in_msg),
cid(orderman::NullTickToTradeCorID), ts() {}
EventData (Handler handler,std::shared_ptr<std::string> const&ts)
: handler (handler),
out_msgs (),in_msg (),
cid(orderman::NullTickToTradeCorID),
ts (ts)
{}
EventData& operator=(EventData&src)
{
EventData(Handler handler, std::shared_ptr<std::string> const &ts)
: handler(handler), out_msgs(), in_msg(),
cid(orderman::NullTickToTradeCorID), ts(ts) {}
EventData &operator=(EventData &src) {
handler = src.handler;
out_msgs = src.out_msgs;
in_msg = src.in_msg;
@@ -80,9 +73,8 @@ struct CoreGateway::EventData
ts = src.ts;
return *this;
}
EventData& operator=(EventData&&src)
{
EventData &operator=(EventData &&src) {
handler = std::move(src.handler);
out_msgs = std::move(src.out_msgs);
in_msg = std::move(src.in_msg);
@@ -97,36 +89,24 @@ struct CoreGateway::EventData
std::shared_ptr<std::string> ts;
};
struct CoreGateway::PImpl
{
PImpl(config::JetFIX::Connection const &cfg,
ICoreGatewayObserver &observer,
struct CoreGateway::PImpl {
PImpl(config::JetFIX::Connection const &cfg, ICoreGatewayObserver &observer,
ConnectionFactory const &conn_factory,
StoreFactory const &store_factory) :
cfg (cfg),
observer (observer),
make_connection (conn_factory),
make_store (store_factory),
state (State::OFF),
start_of_pause (),
session_info (),
event_queue (),
timer_thread (),
destroy (false),
is_on (false),
has_replayed (false),
send_batch (),
recv_batch ()
{
send_batch.reserve (1024);
recv_batch.reserve (1024);
StoreFactory const &store_factory)
: cfg(cfg), observer(observer), make_connection(conn_factory),
make_store(store_factory), state(State::OFF), start_of_pause(),
session_info(), event_queue(), timer_thread(), destroy(false),
is_on(false), has_replayed(false), send_batch(), recv_batch(),
connection_attempts(0) {
send_batch.reserve(1024);
recv_batch.reserve(1024);
}
const config::JetFIX::Connection cfg;
ICoreGatewayObserver &observer;
ConnectionFactory const make_connection;
StoreFactory const make_store;
State state;
datetime::PTime start_of_pause;
std::unique_ptr<SessionInfo> session_info;
@@ -137,152 +117,150 @@ struct CoreGateway::PImpl
bool has_replayed;
std::vector<EventData> send_batch;
std::vector<EventData> recv_batch;
uint32_t connection_attempts;
};
CoreGateway::CoreGateway(config::JetFIX::Connection const &cfg,
ICoreGatewayObserver &observer,
ConnectionFactory const &conn_factory,
StoreFactory const &store_factory)
: me (new PImpl (cfg,observer,conn_factory,store_factory))
{
me->timer_thread = std::thread (std::bind (&CoreGateway::call_time,this));
: me(new PImpl(cfg, observer, conn_factory, store_factory)) {
me->timer_thread = std::thread(std::bind(&CoreGateway::call_time, this));
}
CoreGateway::~CoreGateway ()
{
stop ();
CoreGateway::~CoreGateway() {
stop();
while (me->state != State::OFF) {
dispatch_events ();
dispatch_events();
std::this_thread::sleep_for(100ms);
}
me->destroy = true;
me->timer_thread.join ();
me->timer_thread.join();
}
void CoreGateway::send (orderman::TickToTradeCorID cid, OutFIXPtr const &fix)
{
tracepoint (bts_trace,trace_order,(char*) "CoreGateway.send", 0,cid);
me->event_queue.enqueue (EventData (&CoreGateway::sync_send,cid,fix));
void CoreGateway::send(orderman::TickToTradeCorID cid, OutFIXPtr const &fix) {
tracepoint(bts_trace, trace_order, (char *)"CoreGateway.send", 0, cid);
me->event_queue.enqueue(EventData(&CoreGateway::sync_send, cid, fix));
}
void CoreGateway::send_hb ()
{me->event_queue.enqueue (EventData (&CoreGateway::sync_send_hb));}
void CoreGateway::send_hb() {
me->event_queue.enqueue(EventData(&CoreGateway::sync_send_hb));
}
void CoreGateway::send_test_request ()
{me->event_queue.enqueue (EventData (&CoreGateway::sync_send_test_request));}
void CoreGateway::send_test_request() {
me->event_queue.enqueue(EventData(&CoreGateway::sync_send_test_request));
}
void CoreGateway::start ()
{me->event_queue.enqueue (EventData (&CoreGateway::sync_start));}
void CoreGateway::start() {
me->event_queue.enqueue(EventData(&CoreGateway::sync_start));
}
void CoreGateway::stop ()
{me->event_queue.enqueue (EventData (&CoreGateway::sync_stop));}
void CoreGateway::stop() {
me->event_queue.enqueue(EventData(&CoreGateway::sync_stop));
}
void CoreGateway::session_ready ()
{me->event_queue.enqueue (EventData (&CoreGateway::sync_session_ready)); }
void CoreGateway::session_ready() {
me->event_queue.enqueue(EventData(&CoreGateway::sync_session_ready));
}
void CoreGateway::session_terminated ()
{me->event_queue.enqueue (EventData (&CoreGateway::sync_session_terminated));}
void CoreGateway::session_terminated() {
me->event_queue.enqueue(EventData(&CoreGateway::sync_session_terminated));
}
void CoreGateway::reader_disconnected ()
{me->event_queue.enqueue (EventData (&CoreGateway::sync_reader_disconnected));}
void CoreGateway::reader_disconnected() {
me->event_queue.enqueue(EventData(&CoreGateway::sync_reader_disconnected));
}
void CoreGateway::in_msg_raw (InMsgBuffer const&msg)
{me->event_queue.enqueue (EventData (&CoreGateway::sync_in_msg_raw,msg));}
void CoreGateway::in_msg_raw(InMsgBuffer const &msg) {
me->event_queue.enqueue(EventData(&CoreGateway::sync_in_msg_raw, msg));
}
void CoreGateway::in_msg_sequenced (InMsgBuffer const&msg)
{me->event_queue.enqueue (EventData (&CoreGateway::sync_in_msg_sequenced,msg));}
void CoreGateway::in_msg_sequenced(InMsgBuffer const &msg) {
me->event_queue.enqueue(EventData(&CoreGateway::sync_in_msg_sequenced, msg));
}
void CoreGateway::dispatch_events ()
{
void CoreGateway::dispatch_events() {
EventData ev;
utils::TimeStamp cid;
// we prioratize sends over receives
while (me->event_queue.try_dequeue (ev)) {
while (me->event_queue.try_dequeue(ev)) {
if (ev.out_msgs) {
cid = ev.out_msgs->get_ts (jetfix::OutFIX::MD_WRITE_TS);
me->send_batch.push_back (ev);
}
else
me->recv_batch.push_back (ev);
cid = ev.out_msgs->get_ts(jetfix::OutFIX::MD_WRITE_TS);
me->send_batch.push_back(ev);
} else
me->recv_batch.push_back(ev);
}
if (me->send_batch.size () || me->recv_batch.size ()) {
if (me->send_batch.size() || me->recv_batch.size()) {
for (auto &ev : me->send_batch)
(this->*ev.handler) (ev);
(this->*ev.handler)(ev);
for (auto &ev : me->recv_batch)
(this->*ev.handler) (ev);
me->send_batch.clear ();
me->recv_batch.clear ();
(this->*ev.handler)(ev);
me->send_batch.clear();
me->recv_batch.clear();
}
}
void CoreGateway::call_time ()
{
os::set_thread_name(fmt::format("{0}_timer",me->cfg.name()).c_str());
void CoreGateway::call_time() {
os::set_thread_name(fmt::format("{0}_timer", me->cfg.name()).c_str());
while (!me->destroy) {
std::this_thread::sleep_for(10ms);
auto ts = std::make_shared<std::string> (
datetime::FIX_timestamp (datetime::now ()));
me->event_queue.enqueue (EventData (&CoreGateway::sync_time,ts));
auto ts = std::make_shared<std::string>(
datetime::FIX_timestamp(datetime::now()));
me->event_queue.enqueue(EventData(&CoreGateway::sync_time, ts));
}
}
void CoreGateway::sync_start (EventData const &)
{
switch (me->state)
{
case State::OFF:
{
void CoreGateway::sync_start(EventData const &) {
switch (me->state) {
case State::OFF: {
me->is_on = true;
pause_or_shutdown ();
pause_or_shutdown();
break;
}
case State::PAUSED:
case State::CONNECTING:
case State::READY:
bts_assert (false);
bts_assert(false);
break;
case State::WREADER_DISCONNECT:
case State::WSESSION_TERMINATE:
bts_assert (!me->is_on);
bts_assert(!me->is_on);
me->is_on = true;
break;
default:
bts_assert (false);
bts_assert(false);
}
}
void CoreGateway::sync_stop (EventData const&)
{
switch (me->state)
{
void CoreGateway::sync_stop(EventData const &) {
switch (me->state) {
case State::OFF:
break;
case State::PAUSED:
me->is_on = false;
transition (State::OFF);
transition(State::OFF);
break;
case State::CONNECTING:
case State::READY:
me->is_on = false;
me->session_info->session->shutdown ();
me->session_info->session->shutdown();
break;
case State::WREADER_DISCONNECT:
case State::WSESSION_TERMINATE:
me->is_on = false;
break;
default:
bts_assert (false);
bts_assert(false);
}
}
void CoreGateway::sync_send (EventData const &ev)
{
tracepoint (bts_trace,trace_order,(char*) "CoreGateway.sync_send", 0,ev.cid);
switch (me->state)
{
void CoreGateway::sync_send(EventData const &ev) {
tracepoint(bts_trace, trace_order, (char *)"CoreGateway.sync_send", 0,
ev.cid);
switch (me->state) {
case State::OFF:
bts_assert (false);
bts_assert(false);
break;
case State::PAUSED:
case State::CONNECTING:
@@ -290,19 +268,17 @@ void CoreGateway::sync_send (EventData const &ev)
case State::WSESSION_TERMINATE:
break;
case State::READY:
me->session_info->session->send (ev.cid,ev.out_msgs);
me->session_info->session->send(ev.cid, ev.out_msgs);
break;
default:
bts_assert (false);
bts_assert(false);
}
}
void CoreGateway::sync_send_hb (EventData const &)
{
switch (me->state)
{
void CoreGateway::sync_send_hb(EventData const &) {
switch (me->state) {
case State::OFF:
bts_assert (false);
bts_assert(false);
break;
case State::PAUSED:
case State::CONNECTING:
@@ -310,19 +286,17 @@ void CoreGateway::sync_send_hb (EventData const &)
case State::WSESSION_TERMINATE:
break;
case State::READY:
me->session_info->session->send_hb ();
me->session_info->session->send_hb();
break;
default:
bts_assert (false);
bts_assert(false);
}
}
void CoreGateway::sync_send_test_request (EventData const &)
{
switch (me->state)
{
void CoreGateway::sync_send_test_request(EventData const &) {
switch (me->state) {
case State::OFF:
bts_assert (false);
bts_assert(false);
break;
case State::PAUSED:
case State::CONNECTING:
@@ -330,256 +304,252 @@ void CoreGateway::sync_send_test_request (EventData const &)
case State::WSESSION_TERMINATE:
break;
case State::READY:
me->session_info->session->send_test_request ();
me->session_info->session->send_test_request();
break;
default:
bts_assert (false);
bts_assert(false);
}
}
void CoreGateway::sync_session_ready (EventData const&)
{
switch (me->state)
{
void CoreGateway::sync_session_ready(EventData const &) {
switch (me->state) {
case State::OFF:
case State::PAUSED:
case State::READY:
case State::WREADER_DISCONNECT:
bts_assert (false);
bts_assert(false);
break;
case State::WSESSION_TERMINATE:
break;
case State::CONNECTING:
transition (State::READY);
me->observer.ready ();
transition(State::READY);
me->observer.ready();
break;
default:
bts_assert (false);
bts_assert(false);
}
}
void CoreGateway::sync_session_terminated (EventData const&)
{
switch (me->state)
{
void CoreGateway::sync_session_terminated(EventData const &) {
switch (me->state) {
case State::OFF:
case State::PAUSED:
case State::WREADER_DISCONNECT:
bts_assert (false);
bts_assert(false);
break;
case State::WSESSION_TERMINATE:
pause_or_shutdown ();
pause_or_shutdown();
break;
case State::READY:
me->session_info->session.reset ();
me->session_info->connection->shutdown ();
transition (State::WREADER_DISCONNECT);
me->observer.disconnect ();
me->session_info->session.reset();
me->session_info->connection->shutdown();
transition(State::WREADER_DISCONNECT);
me->observer.disconnect();
break;
case State::CONNECTING:
me->session_info->session.reset ();
me->session_info->connection->shutdown ();
transition (State::WREADER_DISCONNECT);
me->session_info->session.reset();
me->session_info->connection->shutdown();
transition(State::WREADER_DISCONNECT);
break;
default:
bts_assert (false);
bts_assert(false);
}
}
void CoreGateway::sync_reader_disconnected (EventData const&)
{
switch (me->state)
{
void CoreGateway::sync_reader_disconnected(EventData const &) {
switch (me->state) {
case State::OFF:
case State::PAUSED:
case State::WSESSION_TERMINATE:
bts_assert (false);
bts_assert(false);
break;
case State::READY:
me->session_info->session.reset ();
me->observer.disconnect ();
transition (State::WSESSION_TERMINATE);
me->session_info->session.reset();
me->observer.disconnect();
transition(State::WSESSION_TERMINATE);
break;
case State::CONNECTING:
me->session_info->session.reset ();
transition (State::WSESSION_TERMINATE);
me->session_info->session.reset();
transition(State::WSESSION_TERMINATE);
break;
case State::WREADER_DISCONNECT:
pause_or_shutdown ();
pause_or_shutdown();
break;
default:
bts_assert (false);
bts_assert(false);
}
}
void CoreGateway::sync_in_msg_raw (EventData const&ev)
{
switch (me->state)
{
void CoreGateway::sync_in_msg_raw(EventData const &ev) {
switch (me->state) {
case State::OFF:
case State::PAUSED:
bts_assert (false);
bts_assert(false);
break;
case State::CONNECTING:
case State::READY:
me->session_info->session->receive (ev.in_msg);
me->session_info->session->receive(ev.in_msg);
break;
case State::WREADER_DISCONNECT:
case State::WSESSION_TERMINATE:
break;
default:
bts_assert (false);
bts_assert(false);
}
}
void CoreGateway::sync_in_msg_sequenced (EventData const&ev)
{
switch (me->state)
{
void CoreGateway::sync_in_msg_sequenced(EventData const &ev) {
switch (me->state) {
case State::OFF:
case State::PAUSED:
case State::WREADER_DISCONNECT:
bts_assert (false);
bts_assert(false);
break;
case State::CONNECTING:
case State::READY:
me->observer.msg (ev.in_msg);
me->observer.msg(ev.in_msg);
break;
case State::WSESSION_TERMINATE:
break;
default:
bts_assert (false);
bts_assert(false);
}
}
void CoreGateway::sync_time (EventData const &ev)
{
switch (me->state)
{
void CoreGateway::sync_time(EventData const &ev) {
switch (me->state) {
case State::OFF:
break;
case State::PAUSED:
if (datetime::is_at_least_secs_old (me->start_of_pause,
me->cfg.seconds_to_pause ())) {
LOG (Jetfix,Info,"Pause period ended for jetfix connection {}", me->cfg.name ());
if (datetime::is_at_least_secs_old(me->start_of_pause,
me->cfg.seconds_to_pause())) {
LOG(Jetfix, Info, "Pause period ended for jetfix connection {}",
me->cfg.name());
me->session_info = std::make_unique<SessionInfo>();
me->session_info->store.reset (me->make_store (me->cfg));
me->session_info->store.reset(me->make_store(me->cfg));
if (me->session_info->store->session_active()) {
me->connection_attempts++;
if (me->session_info->store->session_active ()) {
if (!me->has_replayed) {
me->observer.replay (me->session_info->store->replay ());
me->observer.replay(me->session_info->store->replay());
me->has_replayed = true;
}
me->session_info->connection.reset (me->make_connection (me->cfg));
LOG (Jetfix,Warn, "Trying to connect to exchange for {}",me->cfg.name ());
if (me->session_info->connection->connect ()) {
LOG (Jetfix,Warn,"Connected to exchange gateway for {}",me->cfg.name ());
me->session_info->session.reset (new Session (me->cfg,
*me->session_info->connection,
*me->session_info->store,
*this,
[] () {return utils::TimeStamp::now ();}));
me->session_info->reader.reset (new AsyncReader (*me->session_info->connection,
me->cfg.name(),
*this,
me->cfg.msg_buffer_sz (),
me->cfg.msg_high_water_mark ()));
me->session_info->reader->start ();
me->session_info->session->start ();
transition (State::CONNECTING);
if (me->connection_attempts > me->cfg.max_connection_attempts()) {
me->session_info.reset();
me->start_of_pause = datetime::now();
LOG(Jetfix, Error,
"Failed to connect to exchange gateway {} too many"
"times. Giving up permanently. You must bounce "
"the server to recover.",
me->cfg.name());
break;
}
else {
me->session_info.reset ();
me->start_of_pause = datetime::now ();
LOG (Jetfix,Warn,"Failed to connect to exchange for {}", me->cfg.name ());
me->session_info->connection.reset(me->make_connection(me->cfg));
LOG(Jetfix, Warn, "Trying to connect to exchange for {} ({}/{})",
me->cfg.name(), me->connection_attempts,
me->cfg.max_connection_attempts());
if (me->session_info->connection->connect()) {
LOG(Jetfix, Warn, "Connected to exchange gateway for {}",
me->cfg.name());
me->session_info->session.reset(
new Session(me->cfg, *me->session_info->connection,
*me->session_info->store, *this,
[]() { return utils::TimeStamp::now(); }));
me->session_info->reader.reset(new AsyncReader(
*me->session_info->connection, me->cfg.name(), *this,
me->cfg.msg_buffer_sz(), me->cfg.msg_high_water_mark()));
me->session_info->reader->start();
me->session_info->session->start();
transition(State::CONNECTING);
} else {
me->session_info.reset();
me->start_of_pause = datetime::now();
LOG(Jetfix, Warn,
"Failed to connect to exchange for {}. ({}/{}) ",
me->cfg.name(), me->connection_attempts,
me->cfg.max_connection_attempts());
}
} else {
me->session_info.reset ();
me->start_of_pause = datetime::now ();
me->session_info.reset();
me->start_of_pause = datetime::now();
}
}
break;
case State::CONNECTING:
case State::READY:
me->session_info->session->time (ev.ts);
me->session_info->session->time(ev.ts);
break;
case State::WREADER_DISCONNECT:
case State::WSESSION_TERMINATE:
break;
default:
bts_assert (false);
bts_assert(false);
}
}
void CoreGateway::pause_or_shutdown ()
{
me->session_info.reset ();
void CoreGateway::pause_or_shutdown() {
me->session_info.reset();
if (me->is_on) {
me->start_of_pause = datetime::now ();
transition (State::PAUSED);
} else
transition (State::OFF);
me->start_of_pause = datetime::now();
transition(State::PAUSED);
} else
transition(State::OFF);
}
void CoreGateway::transition (State new_state)
{
LOG (Jetfix,Warn,"Transitioning Jetfix gateway {} from {} to {}",
me->cfg.name (),
std::string(state_name (me->state)),
std::string(state_name (new_state)));
void CoreGateway::transition(State new_state) {
LOG(Jetfix, Warn, "Transitioning Jetfix gateway {} from {} to {}",
me->cfg.name(), std::string(state_name(me->state)),
std::string(state_name(new_state)));
me->state = new_state;
check_state_invariants ();
check_state_invariants();
}
void CoreGateway::check_state_invariants ()
{
switch (me->state)
{
void CoreGateway::check_state_invariants() {
switch (me->state) {
case State::OFF:
bts_assert (!me->session_info);
bts_assert (!me->is_on);
bts_assert(!me->session_info);
bts_assert(!me->is_on);
return;
case State::PAUSED:
bts_assert (!me->session_info);
bts_assert (me->is_on);
bts_assert(!me->session_info);
bts_assert(me->is_on);
return;
case State::CONNECTING:
case State::READY:
bts_assert (me->session_info);
bts_assert (me->session_info->session);
bts_assert (me->is_on);
bts_assert(me->session_info);
bts_assert(me->session_info->session);
bts_assert(me->is_on);
return;
case State::WREADER_DISCONNECT:
bts_assert (me->session_info);
bts_assert (!me->session_info->session);
bts_assert(me->session_info);
bts_assert(!me->session_info->session);
return;
case State::WSESSION_TERMINATE:
bts_assert (me->session_info);
bts_assert (!me->session_info->session);
bts_assert(me->session_info);
bts_assert(!me->session_info->session);
break;
default:
bts_assert (false);
bts_assert(false);
return;
}
}
const char* CoreGateway::state_name (State st)
{
switch (st)
{
const char *CoreGateway::state_name(State st) {
switch (st) {
case State::OFF:
return "OFF";
case State::PAUSED:
@@ -593,9 +563,8 @@ const char* CoreGateway::state_name (State st)
case State::WSESSION_TERMINATE:
return "WSESSION_TERMINATE";
default:
bts_assert (false);
bts_assert(false);
}
}
}}
}
}
@@ -53,12 +53,12 @@ TEST_CASE ("JetFIX Field2Tests", "[jetfix]")
std::string out (20, ' ');
typedef FloatField<123> Field;
REQUIRE (Field::max_value_size (1.2,6)==6);
REQUIRE (Field::max_value_size (1.2,-1,6)==6);
auto result = Field::write (&out[0],1.2,6);
auto result = Field::write (&out[0],1.2,-1,6);
REQUIRE (result.first==6);
REQUIRE (result.second=='0'+'0'+'0'+'1'+'.'+'2');
REQUIRE (out.compare (0,6,"1.2000") == 0);
REQUIRE (out.compare (0,6,"0001.2") == 0);
REQUIRE (out[6]=='\0');
}
@@ -111,7 +111,7 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
{
FIXFragment g (0);
g.add<CharField<1>> ('a');
g.add<FloatField<2>> (1.2,6);
g.add<FloatField<2>> (1.2,-1,6);
g.add<StringField<3>> ("hello");
g.add<IntField<4>> (123,3);
const char*t_s = "20150427-19:05:18.922";
@@ -121,7 +121,7 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
std::string out = g.to_str ();
REQUIRE (out ==
"1=a" "\001"
"2=1.2000" "\001"
"2=0001.2" "\001"
"3=hello" "\001"
"4=123" "\001"
"5=20150427-19:05:18.922" "\001"
@@ -140,14 +140,14 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
FIXFragment g;
auto c_bm = g.add<CharField<1>> ('a');
auto f_bm = g.add<FloatField<2>> (1.2,6);
auto f_bm = g.add<FloatField<2>> (1.2,-1,6);
auto s_bm = g.add<StringField<3>> ("hello");
auto i_bm = g.add<IntField<4>> (123,3);
auto t_bm = g.add<TimeField<5>> (datetime::parse_FIX_timestamp (t_s1));
auto b_bm = g.add<BoolField<6>> (true);
g.overwrite (c_bm,'b');
g.overwrite (f_bm,1.3,6);
g.overwrite (f_bm,1.3,-1,6);
g.overwrite (s_bm,"olleh");
g.overwrite (i_bm,321,3);
g.overwrite (t_bm,(datetime::parse_FIX_timestamp (t_s2)));
@@ -155,7 +155,7 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
std::string out = g.to_str ();
REQUIRE (out== "1=b" "\001"
"2=1.3000" "\001"
"2=0001.3" "\001"
"3=olleh" "\001"
"4=321" "\001"
"5=20150427-19:05:18.923" "\001"
@@ -171,7 +171,7 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
{
FIXFragment g1;;
g1.add<CharField<1>> ('a');
g1.add<FloatField<2>> (1.2,6);
g1.add<FloatField<2>> (1.2,-1,6);
g1.add<StringField<3>> ("hello");
auto chk1 = g1.checksum ();
@@ -184,7 +184,7 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
std::string out = g1.to_str ();
REQUIRE (out== "1=a" "\001"
"2=1.2000" "\001"
"2=0001.2" "\001"
"3=hello" "\001"
"4=goodbye" "\001"
"5=321" "\001");
@@ -197,3 +197,4 @@ TEST_CASE ("JetFIX FIXFragmentTests" , "[jetfix]")
}}
@@ -164,18 +164,18 @@ TEST_CASE ("JetFIX Performance Tests", "[jetfix] [benchmark]")
g.add<Symbol>("55",sizeof("55")-1);
g.add<SecurityDesc>("ESK5 C1855",sizeof("ESK5 C1855")-1);
g.add<SecurityType>("OPT",sizeof("OPT")-1);
g.add<BidPx>(25200.0);
g.add<BidPx>(25200.0,-1,-1);
g.add<BidSize>(1);
g.add<OfferPx>(25200.0);
g.add<OfferPx>(25200.0,-1,-1);
g.add<OfferSize>(1);
g.add<QuoteEntryID>("10857",sizeof("10857")-1);
g.add<Symbol>("55",sizeof("55")-1);
g.add<SecurityDesc>("ESK5 C1855",sizeof("ESK5 C1855")-1);
g.add<SecurityType>("OPT",sizeof("OPT")-1);
g.add<BidPx>(25200.0);
g.add<BidPx>(25200.0,-1,-1);
g.add<BidSize>(1);
g.add<OfferPx>(25200.0);
g.add<OfferPx>(25200.0,-1,-1);
g.add<OfferSize>(1);
g.add<Checksum>(2,3);
@@ -207,9 +207,9 @@ TEST_CASE ("JetFIX Performance Tests", "[jetfix] [benchmark]")
qt1.add<SecurityDesc>("ESK5 C1855",sizeof("ESK5 C1855")-1);
qt1.add<SecurityType>("OPT",sizeof("OPT")-1);
auto qt1_bid_px = qt1.add<BidPx>(25200.0);
auto qt1_bid_px = qt1.add<BidPx>(25200.0,-1,-1);
auto qt1_bid_sz = qt1.add<BidSize>(1);
auto qt1_offer_px = qt1.add<OfferPx>(25200.0);
auto qt1_offer_px = qt1.add<OfferPx>(25200.0,-1,-1);
auto qt1_offer_sz = qt1.add<OfferSize>(1);
FIXFragment qt2;
@@ -218,9 +218,9 @@ TEST_CASE ("JetFIX Performance Tests", "[jetfix] [benchmark]")
qt2.add<SecurityDesc>("ESK5 C1855",sizeof("ESK5 C1855")-1);
qt2.add<SecurityType>("OPT",sizeof("OPT")-1);
auto qt2_bid_px = qt2.add<BidPx>(25200.0);
auto qt2_bid_px = qt2.add<BidPx>(25200.0,-1,-1);
auto qt2_bid_sz = qt2.add<BidSize>(1);
auto qt2_offer_px = qt2.add<OfferPx>(25200.0);
auto qt2_offer_px = qt2.add<OfferPx>(25200.0,-1,-1);
auto qt2_offer_sz = qt2.add<OfferSize>(1);
FIXFragment g;
@@ -233,16 +233,16 @@ TEST_CASE ("JetFIX Performance Tests", "[jetfix] [benchmark]")
g.add<NoQuoteEntries> (2);
qt1.overwrite (qt1_quote_entry_id,"10857",sizeof("10857")-1);
qt1.overwrite (qt1_bid_px,25200.0);
qt1.overwrite (qt1_bid_px,25200.0,-1,-1);
qt1.overwrite (qt1_bid_sz,1);
qt1.overwrite (qt1_offer_px,25200.0);
qt1.overwrite (qt1_offer_px,25200.0,-1,-1);
qt1.overwrite (qt1_offer_sz,1);
g.append (qt1);
qt2.overwrite (qt2_quote_entry_id,"10857",sizeof("10857")-1);
qt2.overwrite (qt2_bid_px,25200.0);
qt2.overwrite (qt2_bid_px,25200.0,-1,-1);
qt2.overwrite (qt2_bid_sz,1);
qt2.overwrite (qt2_offer_px,25200.0);
qt2.overwrite (qt2_offer_px,25200.0,-1,-1);
qt2.overwrite (qt2_offer_sz,1);
g.append (qt2);
@@ -5,6 +5,7 @@
#include "server/sparkd/jetfix/common/OutFIX.h"
#include "server/sparkd/orderman/actors/order_manager/OrderServicesData.h"
#include "server/sparkd/orderman/orderman_utils/EventStream.h"
#include "server/sparkd/orderman/governor/Governor.h"
#include "server/sparkd/orderman/orders/Order.h"
#include <memory>
@@ -24,7 +25,8 @@ public:
{}
std::string const &session_name() const { return _session_data->session; }
config::JetFIX::Connection const &session_cfg() const { return _session_data->cfg; }
jetfix::OutFIXPtr get_fix_buffer() {
jetfix::OutFIXPtr result;
if (_session_data->free_buffers.size()) {
@@ -53,7 +55,7 @@ public:
EventStream<NewExecutionReportEvent> exe_reports;
EventStream<CancelRejectEvent> cancel_rejects;
EventStream<GatewayStatusChangeEvent, mercury::NoKey> gateway_status;
Governor &governor() {return _session_data->governor;}
private:
SessionServicesData *_session_data;
OrderServicesData *_data;
@@ -12,13 +12,8 @@ OrderServicesData::OrderServicesData(mercury::Actor &owner)
to<mercury::TimeStampStr>(datetime::FIX_timestamp(datetime::now()))) {
auto &all_connections = config::get<config::JetFIX>().connection();
for (auto const &c : all_connections) {
auto s = std::make_unique<SessionServicesData>();
s->session = c.name();
s->gw_connected = false;
s->cfg = c;
sessions.push_back (std::move(s));
}
for (auto const &c : all_connections)
sessions.push_back (std::make_unique<SessionServicesData>(c));
}
}
}
@@ -28,6 +28,7 @@
#include "server/sparkd/orderman/events/UpdateOrderEvent.h"
#include "server/sparkd/orderman/limiter/Limiter.h"
#include "server/sparkd/orderman/order_router/OrderRouter.h"
#include "server/sparkd/orderman/governor/Governor.h"
#include <boost/date_time/posix_time/posix_time.hpp>
@@ -41,6 +42,14 @@ struct ParentKey {
};
struct SessionServicesData {
SessionServicesData(config::JetFIX::Connection const&cfg)
: session(cfg.name()),
gw_connected(false),
cfg(cfg),
governor(cfg.max_order_msgs_per_window(),
cfg.window_duration_msecs())
{}
std::string session;
std::vector<jetfix::OutFIXPtr> free_buffers;
mercury::Stream<OutFIXMsgEvent> out_fix_pub;
@@ -53,6 +62,8 @@ struct SessionServicesData {
std::unique_ptr<
mercury::ContentSubscription<GatewayStatusChangeEvent, mercury::NoKey>>
gateway_status_subs;
Governor governor;
};
class OrderServicesData {
@@ -0,0 +1,48 @@
#pragma once
#include <memory>
#include "lib/utils/bts_chrono.h"
namespace bts {
namespace orderman {
class Governor
{
public:
Governor(uint32_t max_msgs_window,
uint32_t window_duration_msecs,
TimeStamp now = Timer::now()) :
_max_msgs_window(max_msgs_window),
_window_duration_msecs(window_duration_msecs),
_start_of_window(now),
_msgs_in_window(0)
{}
bool can_send(TimeStamp now = Timer::now()) const
{
if (in_window(now))
return _msgs_in_window < _max_msgs_window;
else
return true;
}
void record_sent(TimeStamp now = Timer::now()) {
if (!in_window(now)) {
_start_of_window = now;
_msgs_in_window = 0;
}
_msgs_in_window++;
}
private:
bool in_window(TimeStamp now) const {
return ms_diff (now, _start_of_window) <= _window_duration_msecs;
}
uint32_t _max_msgs_window;
uint32_t _window_duration_msecs;
TimeStamp _start_of_window;
uint64_t _msgs_in_window;
};
}}
@@ -24,41 +24,45 @@ OrderRouter::RouteQueryResult
OrderRouter::get_routing_record(UserID user_id, AccountID account_id,
api::Security const &sec) const {
api::admin::AdminServiceOperationResult result;
api::orderman::RoutingRecord rr = bts::admin::AdminServiceUtils::get_routing_record(me->data, account_id.value()==0 ? 1 : account_id.value(),
user_id.value()==0 ? 1 : user_id.value(), sec, result);
api::orderman::RoutingRecord rr =
bts::admin::AdminServiceUtils::get_routing_record(
me->data, account_id.value() == 0 ? 1 : account_id.value(),
user_id.value() == 0 ? 1 : user_id.value(), sec, result);
if (result.is_success())
return RouteQueryResult{true, std::string(""), rr};
else
return RouteQueryResult{false, result.error_text(), rr};
}
OrderRouter::ReverseRoutingQueryResult
OrderRouter::get_reverse_routing_record(std::string const &session, std::string const &operator_id, std::string const &account,
api::Security const * sec) const {
ReverseRoutingQueryResult rv {true,"","",UserID(0),"",AccountID(0)};
api::Venue exchange = sec == nullptr ? api::Venue::VENUE_GLBX : sec->exchange();
auto match = bts::admin::AdminServiceUtils::find_user_account_matches(me->data,session,account,operator_id,exchange);
if(match.first != nullptr){
OrderRouter::ReverseRoutingQueryResult OrderRouter::get_reverse_routing_record(
std::string const &session, std::string const &operator_id,
std::string const &account, api::Security const *sec) const {
ReverseRoutingQueryResult rv{true, "", "", UserID(0), "", AccountID(0)};
api::Venue exchange =
sec == nullptr ? api::Venue::VENUE_GLBX : sec->exchange();
auto match = bts::admin::AdminServiceUtils::find_user_account_matches(
me->data, session, account, operator_id, exchange);
if (match.first != nullptr) {
rv.account = match.first->name();
rv.account_id = AccountID(match.first->id());
} else {
rv.ok = false;
rv.error_msg = "Could not resolve account";
}
else{
rv.ok=false;
rv.error_msg = "Could not resolve account";
}
if(match.second != nullptr){
if (match.second != nullptr) {
rv.user = match.second->name();
rv.user_id = UserID(match.second->id());
}
else{
rv.ok=false;
rv.error_msg = (!rv.ok ? std::string(", ") : std::string("")) + "Could not resolve user";
} else {
rv.ok = false;
rv.error_msg = (!rv.ok ? std::string(", ") : std::string("")) +
"Could not resolve user";
}
return rv;
}
bool OrderRouter::is_valid_session(std::string const &session) const {
return bts::admin::AdminServiceUtils::find_connection(me->data, session) != nullptr;
return bts::admin::AdminServiceUtils::find_connection(me->data, session) !=
nullptr;
}
OrderRouter::~OrderRouter() {}
@@ -27,7 +27,8 @@ OrderStatus map_external_status(NativeOrderStatus);
struct NativeOrder::PImpl {
PImpl()
: services(nullptr), session_services(), sec(nullptr),
previous_position_contribution(0) {}
previous_position_contribution(0), definition(), state(),
price_encoding_precision(7) {}
OrderServices *services;
SessionServices *session_services;
@@ -37,6 +38,7 @@ struct NativeOrder::PImpl {
OrderDefinition definition;
OrderState state;
int price_encoding_precision;
jetfix::FIXFragment update_encoding;
jetfix::FIXFragment::Bookmark<jetfix::fix::Price> update_price_bm;
@@ -266,7 +268,7 @@ NativeOrder::~NativeOrder() {}
void NativeOrder::on_activate(Price p, Quantity q,
bts::orderman::OrderServices &services,
TickToTradeCorID cid) {
tracepoint (bts_trace,trace_order,(char*) "on_activate", 0,cid);
tracepoint(bts_trace, trace_order, (char *)"on_activate", 0, cid);
bts_assert(!me->services);
me->services = &services;
@@ -275,8 +277,16 @@ void NativeOrder::on_activate(Price p, Quantity q,
case ORDER_STATUS_INACTIVE: {
// this is a brand new order
me->state.set_price(p.value());
me->state.set_quantity(q.value());
me->state.set_filled_quantity(0);
me->state.set_last_price(0);
me->state.set_avg_price(0);
me->session_services = &me->services->get_session_services(
get_native_state(me->state).routing_record().session());
me->price_encoding_precision =
me->session_services->session_cfg().price_encoding_precision();
// is our gateway connected?
if (!me->session_services->is_gateway_connected()) {
@@ -319,6 +329,16 @@ void NativeOrder::on_activate(Price p, Quantity q,
break;
}
// are we under the messaging threshold?
if (!me->session_services->governor().can_send()) {
LOG(OrderMan, Warn, "New order would cause breach of messaging "
"limits. Dropping order {}",
key().value());
transition(ORDER_STATUS_REJECTED_BY_SPARK,
"Messaging threshold breached");
break;
}
// if we are here we are good.
// subscribe to usefull events
@@ -337,7 +357,7 @@ void NativeOrder::on_activate(Price p, Quantity q,
case CustomOrderDefinition::kStopLimit:
me->create_price_bm = encoding.overwrite(
me->create_price_bm,
securities::encode_exchange_price(*me->sec, p).value(),
securities::encode_exchange_price(*me->sec, p).value(), 7,
price_sz());
break;
case CustomOrderDefinition::kStop:
@@ -358,13 +378,12 @@ void NativeOrder::on_activate(Price p, Quantity q,
auto &f = stream.new_msg(jetfix::MessageTypes::NewOrderSingle);
f.append(me->create_encoding);
stream.close();
// Send it
me->session_services->governor().record_sent();
me->session_services->send_fix_buffer(cid, buffer);
me->state.set_status(ORDER_STATUS_PENDING_WORKING);
me->state.set_price(p.value());
me->state.set_quantity(q.value());
me->state.set_filled_quantity(0);
me->state.set_last_price(0);
me->state.set_avg_price(0);
LOG(OrderMan, Info, "Sent new order with key {} to gateway",
key().value());
@@ -426,7 +445,8 @@ void NativeOrder::on_activate(Price p, Quantity q,
}
void NativeOrder::on_update(Price p, Quantity q, TickToTradeCorID cid) {
tracepoint (bts_trace,trace_order,(char*) "on_update", 0,cid);
tracepoint(bts_trace, trace_order, (char *)"on_update", 0, cid);
switch (me->state.status()) {
case ORDER_STATUS_WORKING: {
@@ -445,6 +465,7 @@ void NativeOrder::on_update(Price p, Quantity q, TickToTradeCorID cid) {
me->definition.side(), Quantity(previous_working_qty),
Quantity(current_working_qty));
// are we under the position limits?
if (!lqr.ok) {
auto msg = fmt::format("Cannot update order due to limit breach: {}",
lqr.error_msg);
@@ -458,28 +479,46 @@ void NativeOrder::on_update(Price p, Quantity q, TickToTradeCorID cid) {
me->state.set_last_msg(msg);
on_cancel(cid);
}
} else {
auto &encoding = me->update_encoding;
me->update_price_bm = encoding.overwrite(
me->update_price_bm,
securities::encode_exchange_price(*me->sec, p).value(),
price_sz());
me->update_quantity_bm = encoding.overwrite(me->update_quantity_bm,
q.value(), quantity_sz());
auto const &ts = me->services->get_time_str();
me->update_transact_time_bm = encoding.overwrite(
me->update_transact_time_bm, &ts.value()[0], ts.value().size());
auto buffer = me->session_services->get_fix_buffer();
auto &stream = buffer->fix[0];
auto &f = stream.new_msg(jetfix::MessageTypes::CancelReplace);
f.append(me->update_encoding);
stream.close();
me->session_services->send_fix_buffer(cid, buffer);
if (me->state.last_msg().size())
me->state.set_last_msg("");
break;
}
// are we under the messaging threshold?
if (!me->session_services->governor().can_send()) {
LOG(OrderMan, Warn, "Cannot update order {} because of messaging limits. ",
key().value());
auto msg = "Messaging threshold breached";
if (parent_key() == null_order_key())
transition(me->state.status(),msg);
else {
me->state.set_last_msg(msg);
on_cancel(cid);
}
break;
}
// all good
auto &encoding = me->update_encoding;
me->update_price_bm = encoding.overwrite(
me->update_price_bm,
securities::encode_exchange_price(*me->sec, p).value(), 7,
price_sz());
me->update_quantity_bm =
encoding.overwrite(me->update_quantity_bm, q.value(), quantity_sz());
auto const &ts = me->services->get_time_str();
me->update_transact_time_bm = encoding.overwrite(
me->update_transact_time_bm, &ts.value()[0], ts.value().size());
auto buffer = me->session_services->get_fix_buffer();
auto &stream = buffer->fix[0];
auto &f = stream.new_msg(jetfix::MessageTypes::CancelReplace);
f.append(me->update_encoding);
stream.close();
// Send it
me->session_services->governor().record_sent();
me->session_services->send_fix_buffer(cid, buffer);
if (me->state.last_msg().size())
me->state.set_last_msg("");
break;
}
@@ -790,8 +829,8 @@ void NativeOrder::init_fix() {
cancel_encoding.add<fix::OrdType>(NATIVE_ORDER_TYPE_LIMIT);
// price
me->create_price_bm = create_encoding.add<fix::Price>(0, price_sz());
me->update_price_bm = update_encoding.add<fix::Price>(0, price_sz());
me->create_price_bm = create_encoding.add<fix::Price>(0, 7, price_sz());
me->update_price_bm = update_encoding.add<fix::Price>(0, 7, price_sz());
break;
case CustomOrderDefinition::kStop: {
@@ -804,8 +843,8 @@ void NativeOrder::init_fix() {
auto stop_px = def.custom_def().stop().stop_price();
auto encoded_stop_px =
securities::encode_exchange_price(*me->sec, Price(stop_px)).value();
create_encoding.add<fix::StopPx>(encoded_stop_px);
update_encoding.add<fix::StopPx>(encoded_stop_px);
create_encoding.add<fix::StopPx>(encoded_stop_px, -1, -1);
update_encoding.add<fix::StopPx>(encoded_stop_px, -1, -1);
break;
}
@@ -819,12 +858,12 @@ void NativeOrder::init_fix() {
auto stop_px = def.custom_def().stop_limit().stop_price();
auto encoded_stop_px =
securities::encode_exchange_price(*me->sec, Price(stop_px)).value();
create_encoding.add<fix::StopPx>(encoded_stop_px);
update_encoding.add<fix::StopPx>(encoded_stop_px);
create_encoding.add<fix::StopPx>(encoded_stop_px, -1, -1);
update_encoding.add<fix::StopPx>(encoded_stop_px, -1, -1);
// price
me->create_price_bm = create_encoding.add<fix::Price>(0, price_sz());
me->update_price_bm = update_encoding.add<fix::Price>(0, price_sz());
me->create_price_bm = create_encoding.add<fix::Price>(0, 7, price_sz());
me->update_price_bm = update_encoding.add<fix::Price>(0, 7, price_sz());
break;
}
default:
@@ -15,10 +15,12 @@ SpreaderOrder::SpreaderOrder()
:
_cancelled (false),
_cancel_timeout (5000),// TODO:get it from config
_check_termination_period (250), // should also be a setting/config like above
_active(true),
_bookkeeping_data_ptr(),
_order_services(nullptr),
_reported_state(),
_curr_timestamp(),
_last_check_termination_timestamp(),
_state_worker(nullptr),
_start_state_worker(this), _working_state_worker(this), _waiting_for_cancel_all_state_worker(this), _end_state_worker(this){
_transform_to_START();
@@ -81,6 +83,7 @@ void SpreaderOrder::_on_time(const bts::mercury::TimerEvent& t_event){
_curr_timestamp = t_event.ts;
if (!_active) return;
_state_worker->on_time (_curr_timestamp);
_send_check_termination(false);
}
void SpreaderOrder::_on_book_update(const BookUpdateEvent& book_event){
if (!_active) return;
@@ -133,7 +136,7 @@ void SpreaderOrder::_on_child_order_update(const OrderStateUpdateEvent& ord_ev){
LOG(OrderMan, Error, "SpreaderOrder::_on_child_order_update unknown order key: {} sec_key: {} order_key: {} status: {} filled_qty: {} last_price: {} msg: {}",
key(), sec_key, order_key, status, filled_qty, last_price, ord_ev.state.last_msg ());
}
_state_worker->on_check_termination ();
_send_check_termination(true);
_report_state();
}
// State Transformations ////////////////////
@@ -279,21 +282,26 @@ void SpreaderOrder::_compute_hedge_price_quantity(size_t filled_leg_index,
hedged_leg_data.leg_property_data().tick_size(),
computed_hedge_price, hedge_quantity);
}
void SpreaderOrder::_report_state (){
if (_activated()){
SpreaderBookkeepingData& bookkeeping_data = _bookkeeping_data();
void SpreaderOrder::_report_state (){
if (_activated()){
SpreaderBookkeepingData& bookkeeping_data = _bookkeeping_data();
auto& reported_state = bookkeeping_data.reported_state();
api::orderman::OrderStatus status = _get_order_status ();
Quantity filled_qty; Price avg_filled_price;
bookkeeping_data.total_fills(filled_qty, avg_filled_price);
_reported_state.set_status(status);
_reported_state.set_price(bookkeeping_data.price().value());
_reported_state.set_quantity(bookkeeping_data.quantity().value());
_reported_state.set_filled_quantity(filled_qty.value());
_reported_state.set_avg_price(avg_filled_price.value());
// TODO fill in filled_qty etc
if(bookkeeping_data.inited_new()){
Quantity filled_qty; Price avg_filled_price;
bookkeeping_data.total_fills(filled_qty, avg_filled_price);
reported_state.set_status(status);
reported_state.set_price(bookkeeping_data.price().value());
reported_state.set_quantity(bookkeeping_data.quantity().value());
reported_state.set_filled_quantity(filled_qty.value());
reported_state.set_avg_price(avg_filled_price.value());
}
// if order is cons'ed from disc, with inited_new==false,
// we just publish what was given to us
LOG(OrderMan, Info, "SpreaderOrder::_report_state key: {} status: {} price: {} qty: {} filled: {} abg_price: {}",
key(), status, _reported_state.price(), _reported_state.quantity(), _reported_state.filled_quantity(), _reported_state.avg_price());
_order_services->report_state(_reported_state);
key(), status, reported_state.price(), reported_state.quantity(),
reported_state.filled_quantity(), reported_state.avg_price());
_order_services->report_state(reported_state);
}
}
bts::api::orderman::OrderStatus SpreaderOrder::_get_order_status () const{
@@ -314,4 +322,11 @@ bts::api::orderman::OrderStatus SpreaderOrder::_get_order_status () const{
return bts::api::orderman::OrderStatus::ORDER_STATUS_INACTIVE;
}
}
}}
void SpreaderOrder::_send_check_termination(bool force){
if(force ||
ms_diff (_curr_timestamp, _last_check_termination_timestamp) >= _check_termination_period){
_last_check_termination_timestamp = _curr_timestamp;
_state_worker->on_check_termination();
}
}
}}
@@ -49,17 +49,19 @@ private:
bool _cancelled;
// timeout before cancel waiting goes to end
const int64_t _cancel_timeout;
// period on which check termination calls are
// sent to the state worker
const int64_t _check_termination_period;
// is false if created from disk
const bool _active;
// this is the main data
std::unique_ptr<SpreaderBookkeepingData> _bookkeeping_data_ptr;
// pointer kept to send reports
SpreaderOrderServices* _order_services;
// state thats sent out in report (its kept here amd modified
// vs creating a new one everytime)
bts::api::orderman::OrderState _reported_state;
// timestamp of last the on_time call
bts::TimeStamp _curr_timestamp;
// spreader fires a periodic check
bts::TimeStamp _last_check_termination_timestamp;
// current state
ISpreaderStateWorker* _state_worker;
SpreaderStartStateWorker _start_state_worker;
@@ -203,6 +205,10 @@ private:
return ChildOrderError::MODIFY_ERROR;
return ChildOrderError::NONE;
}
// the state worker's on_check_termination is sent
// when there's an order update, as well as on
// a periodic bases
void _send_check_termination(bool force);
// testing only //////////////////
// this provides direct way to inject
// data into the spreader, as well as
@@ -7,11 +7,14 @@ SpreaderBookkeepingData::SpreaderBookkeepingData (ISpreaderManager& spreader_man
bts::orderman::OrderKey spreader_order_key,
const api::orderman::OrderDefinition& order_def,
const SpreaderPreActivationOrderServices& spreader_pao_services,
const std::vector<LegPropertyData>& legs)
const std::vector<LegPropertyData>& legs,
bool inited_new)
:
_is_live(spreader_pao_services.is_live()),
_n_legs(legs.size()),
_order_def(order_def){
_inited_new (inited_new),
_order_def(order_def),
_reported_state(){
LOG(OrderMan, Info, "SpreaderBookkeepingData.cons started key: {} n_legs: {}", spreader_order_key.value(), _n_legs);
_leg_prop_data.reserve(_n_legs),
_books.reserve(_n_legs),
@@ -39,6 +42,19 @@ _order_def(order_def){
}
LOG(OrderMan, Info, "SpreaderBookkeepingData.cons completed key: {} n_legs: {}", spreader_order_key.value(), _n_legs);
}
SpreaderBookkeepingData::SpreaderBookkeepingData (ISpreaderManager& spreader_manager,
const bts::api::orderman::OrderRecord& order_record,
const SpreaderPreActivationOrderServices& spreader_pao_services,
const std::vector<LegPropertyData>& legs)
:
SpreaderBookkeepingData(spreader_manager,
OrderKey(order_record.key()),
order_record.def(),
spreader_pao_services,
legs,
false){
_reported_state=order_record.state();
}
void SpreaderBookkeepingData::total_fills (Quantity& out_quantity,
Price& out_price) const{
double avg_price = 0.0;
@@ -21,6 +21,9 @@ class SpreaderBookkeepingData
private:
const bool _is_live;
const size_t _n_legs;
// when the order is not created from the disk
// (ie created live) this is set to true
const bool _inited_new;
std::vector<LegPropertyData> _leg_prop_data;
std::vector<Book> _books;
const api::orderman::OrderDefinition _order_def;
@@ -31,6 +34,9 @@ private:
std::vector<std::vector<SingleFillHedger>> _hedgers;
std::vector<SpreaderLegData> _leg_data;
boost::container::flat_map<uint32_t, size_t> _sec_key_to_index;
// state thats sent out in report (its kept here amd modified
// vs creating a new one everytime)
bts::api::orderman::OrderState _reported_state;
Price _price;
Quantity _quantity;
public:
@@ -38,6 +44,11 @@ public:
bts::orderman::OrderKey spreader_order_key,
const api::orderman::OrderDefinition& order_def,
const SpreaderPreActivationOrderServices& spreader_pao_services,
const std::vector<LegPropertyData>& legs,
bool inited_new=true);
SpreaderBookkeepingData (ISpreaderManager& spreader_manager,
const bts::api::orderman::OrderRecord& order_record,
const SpreaderPreActivationOrderServices& spreader_pao_services,
const std::vector<LegPropertyData>& legs);
SpreaderBookkeepingData()=delete;
SpreaderBookkeepingData (const SpreaderBookkeepingData&) = delete;
@@ -101,5 +112,11 @@ public:
_price = price;
_quantity = quantity;
}
bts::api::orderman::OrderState& reported_state(){
return _reported_state;
}
bool inited_new () const{
return _inited_new;
}
};
}}
@@ -25,18 +25,26 @@ void SpreaderStartStateWorker::on_init_new(const api::orderman::OrderDefinition&
_spreader->_bookkeeping_data_ptr = std::make_unique<SpreaderBookkeepingData>(*_spreader, _spreader->key(), order_def, spreader_pao_services, spreader_legs);
else
{
_spreader->_bookkeeping_data_ptr = std::make_unique<SpreaderBookkeepingData>(*_spreader, _spreader->key(), order_def, spreader_pao_services, std::vector<LegPropertyData>());
_spreader->_bookkeeping_data_ptr = std::make_unique<SpreaderBookkeepingData>(*_spreader,
_spreader->key(),
order_def,
spreader_pao_services,
std::vector<LegPropertyData>());
// if securities can not be resolved, the order is terminated
LOG(OrderMan, Error, "SpreaderStartStateWorker.on_init_new key: {} , could not resolve securities", _spreader->key().value());
std::string msg = "Could not resolve securities";
_spreader->_transform_to_END (&msg);
}
}
void SpreaderStartStateWorker::on_init_recovered(const bts::api::orderman::OrderRecord& order_record, const SpreaderPreActivationOrderServices& spreader_pao_services)
void SpreaderStartStateWorker::on_init_recovered(const bts::api::orderman::OrderRecord& order_record,
const SpreaderPreActivationOrderServices& spreader_pao_services)
{
bts_assert(!_spreader->_inited());
// we are not handling this yet, so lets send it directly to END
_spreader->_bookkeeping_data_ptr = std::make_unique<SpreaderBookkeepingData>(*_spreader, _spreader->key(), order_record.def(), spreader_pao_services, std::vector<LegPropertyData>());
_spreader->_bookkeeping_data_ptr = std::make_unique<SpreaderBookkeepingData>(*_spreader,
order_record,
spreader_pao_services,
std::vector<LegPropertyData>());
LOG(OrderMan, Warn, "SpreaderStartStateWorker.on_init_recovered key: {} , going to END", _spreader->key().value());
std::string msg = "Recovered spreader orders and not supported";
_spreader->_transform_to_END (&msg);
@@ -1,107 +0,0 @@
#!/usr/bin/python
import sys
import re
from pandas import DataFrame,Series
import pandas as pd
import numpy as np
pd.set_option('display.max_columns',20)
pd.set_option('display.max_rows',2000)
pd.set_option('display.width',1000)
pd.set_option('display.precision',8)
pd.set_option('display.float_format', lambda x: '%.3f' % x)
def parse_exps(exps):
delta_exps=[]
for field_exp in exps:
delta_exps.append(re.match('(?P<result>\w+)=(?P<stop>\w+)-(?P<start>\w+)',field_exp).groupdict())
return delta_exps
def parse_log():
result = {}
for line in sys.stdin.readlines():
(cid, ts, ev) = line.split()
result.setdefault(int(cid),{}) [ev] = int(ts)
return result
def print_traces(last,traces):
for k,in_rec in traces.iteritems():
if last in in_rec and "zero" in in_rec:
first = in_rec["zero"]
shot = in_rec[last]
o
print "%d -> %d" % (k, shot-first)
previous = first
for key, value in sorted(in_rec.iteritems(), key=lambda (k,v): (v,k)):
print "\t%s: %s" % (key, value-previous)
previous = value
def transform(delta_exps,raw_events):
out = []
for k,in_rec in raw_events.iteritems():
out_rec = {}
out_rec['id'] = k
all_present = True
for d in delta_exps:
start_k = d['start']
stop_k = d['stop']
if start_k in in_rec and stop_k in in_rec:
out_rec[d['result']] = (in_rec[stop_k]-in_rec[start_k]) / 1000
else:
all_present = False
if all_present:
out.append(out_rec)
return out
exps = parse_exps(sys.argv[1:])
traces = parse_log()
print traces
# print_traces("SocketConnection_write_pre_write",traces)
# times = transform(exps,traces)
# clean_times = []
# for t in times:
# if t['t'] < 500000:
# clean_times.append(t)
# frame = DataFrame(clean_times)
# print "Overview (all data)"
# print "--------------------"
# print frame
# print
# frame = frame.drop('id', 1)
# print "Medians (all data)"
# print "------------------"
# print frame.median()
# print
# print "StdDevs (all data)"
# print "------------------"
# print frame.std()
# clean_times = []
# for t in times:
# if t['t2t'] < 500:
# clean_times.append(t)
# frame = DataFrame(clean_times)
# print "Overview (clean data)"
# print "---------------------"
# print frame
# print
# frame = frame.drop('id', 1)
# print "Medians (clean data)"
# print "--------------------"
# print frame.median()
# print
# print "StdDevs (clean data)"
# print "--------------------"
# print frame.std()
Python finished at Tue Jun 5 14:54:09
@@ -1,2 +0,0 @@
#!/bin/bash
./parse_log.sh $1 | ./analyze.py t=SocketConnection_write_pre_write-MDSource
@@ -0,0 +1,10 @@
#!/bin/bash
rm -f trace_data/trace.sqlite
sqlite3 trace_data/trace.sqlite <<EOF
.separator ","
create table trace (seq integer, ts integer, hts text, msg text, cpu integer, tid integer,arg1 integer);
create index trace_seq_idx on trace(seq);
.import trace_data/parsed_trace.txt trace
EOF
@@ -0,0 +1,2 @@
#!/bin/bash
cat trace_data/raw_trace.txt | python parse_helper.py> trace_data/parsed_trace.txt
Loaded 100 of 123 files, more files were not shown because too many files have changed in this diff. Show more