Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5330000e12 | ||
|
|
1d51a063ea | ||
|
|
da3dc9f477 | ||
|
|
f437d50041 | ||
|
|
c7bbcc1738 | ||
|
|
3bdde95b42 | ||
|
|
52e6be5d60 | ||
|
|
bda76d0234 | ||
|
|
7cb08ba90a | ||
|
|
ee6d05c737 | ||
|
|
62a5ee94e8 | ||
|
|
f6e12d1664 | ||
|
|
d022080b80 | ||
|
|
44acb1caac | ||
|
|
28347f1074 | ||
|
|
7826a1d0b4 | ||
|
|
975edb48df | ||
|
|
c2a5c278e9 | ||
|
|
f895e69aa9 | ||
|
|
7c050a9e10 | ||
|
|
38087d21d8 | ||
|
|
4b2e49c4aa | ||
|
|
e31c0a8112 | ||
|
|
876d6e0981 | ||
|
|
cb2dac6126 | ||
|
|
ae2712c9ac | ||
|
|
c3f4183531 | ||
|
|
6de5416394 | ||
|
|
1a947097a5 |
No files matched your search
Vendored
+12
-1
@@ -42,7 +42,18 @@
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "Client Tests",
|
||||
"type": "clr",
|
||||
"request": "launch",
|
||||
"program": "${workspaceRoot}/build/Debug/bin/net471/sparkcmd.exe",
|
||||
"args": ["test","test_filter"],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"stopAtEntry": false,
|
||||
"externalConsole": true,
|
||||
"requireExactSource": false
|
||||
},
|
||||
{
|
||||
"name": "Client Attach",
|
||||
"type": "clr",
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
1.3.4
|
||||
- Changed default logging config for Windows. If Logging config is overridden for a system it should be adjusted to override only linux.
|
||||
|
||||
1.1.0
|
||||
- With this release we dropped wild cards from limits, so any user
|
||||
that has limits expressed in terms of wild cards will have to have
|
||||
|
||||
@@ -1,3 +1,15 @@
|
||||
1.3.4
|
||||
- Significantly reduced client memory use
|
||||
- Improved OrderBook performance
|
||||
- Improved workspace load time
|
||||
|
||||
1.3.3
|
||||
- Reduced server memory usage
|
||||
|
||||
1.3.2
|
||||
- Improved OrderBook performance
|
||||
- Fix some memory leaks when switching workspaces
|
||||
|
||||
1.3.1
|
||||
- Improved server side logging
|
||||
- Fixed leaking of market data subscriptions on market view form closing
|
||||
|
||||
@@ -11,18 +11,22 @@ namespace mbts.spark {
|
||||
[Export]
|
||||
class AdminModel : IDisposable {
|
||||
readonly string _user_name;
|
||||
public string user_name => _user_name;
|
||||
|
||||
readonly BehaviorSubject<IReadOnlyDictionary<uint, string>>
|
||||
_accounts_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>()),
|
||||
_users_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>());
|
||||
_users_subject = new BehaviorSubject<IReadOnlyDictionary<uint, string>>(new Dictionary<uint, string>());
|
||||
IReadOnlyDictionary<uint, string>
|
||||
_account_map = new Dictionary<uint, string>(),
|
||||
_user_map = new Dictionary<uint, string>();
|
||||
IDisposable _dispose;
|
||||
readonly BehaviorSubject<AdminModelWrapper> _wrapper_subject = new BehaviorSubject<AdminModelWrapper>(null);
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
AdminModel(AdminService service, [Import("username")]string user_name) {
|
||||
_user_name = user_name;
|
||||
_dispose = service.subscribei_admin_data().Select(update => {
|
||||
_wrapper_subject.OnNext(new AdminModelWrapper(update.all.FirstOrDefault(), _user_name));
|
||||
IEnumerable<BTSUserImmutable> active_users =
|
||||
from admin_data in update.all
|
||||
from bts_user in admin_data.Users
|
||||
@@ -52,9 +56,10 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_users => _users_subject.AsObservable();
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts => _accounts_subject.AsObservable();
|
||||
public IObservable<IReadOnlyDictionary<uint, string>> on_accounts => _accounts_subject.AsObservable();
|
||||
public IObservable<IReadOnlyList<UserTuple>> on_user_tuples => on_users.Select(d => d.Select(kv => new UserTuple(kv.Key, kv.Value)).ToList());
|
||||
public IObservable<IReadOnlyList<AccountTuple>> on_account_tuples => on_accounts.Select(d => d.Select(kv => new AccountTuple(kv.Key, kv.Value)).ToList());
|
||||
public IObservable<AdminModelWrapper> on_admin_data => _wrapper_subject.AsObservable();
|
||||
|
||||
public UserTuple? get_current_user() {
|
||||
foreach (var kv in _user_map) {
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
using bts;
|
||||
using bts.api.admin;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace mbts.spark
|
||||
{
|
||||
class AdminModelWrapper
|
||||
{
|
||||
readonly AdminDataImmutable? _admin_data;
|
||||
readonly string _user_name;
|
||||
readonly List<BTSAccountImmutable> _allowed_accounts;
|
||||
readonly Dictionary<string, string> _allowed_account_dict = new Dictionary<string, string>();
|
||||
public AdminModelWrapper(AdminDataImmutable? admin_data = null, string user_name = null)
|
||||
{
|
||||
_admin_data = admin_data;
|
||||
_user_name = user_name;
|
||||
_allowed_accounts = search_admin(u => u.Name == _user_name).Select(x => x.Item1).ToList();
|
||||
foreach(var val in _allowed_accounts)
|
||||
_allowed_account_dict[val.Name] = val.Name;
|
||||
}
|
||||
public IEnumerable<Tuple<BTSAccountImmutable, TradingPermissionUnitImmutable>> get_trading_permissions(UInt32 user_id)
|
||||
{
|
||||
return search_admin(u => u.Id == user_id);
|
||||
}
|
||||
public IEnumerable<BTSAccountImmutable> viewable_accounts()
|
||||
{
|
||||
return _allowed_accounts;
|
||||
}
|
||||
public bool can_view_account(string account)
|
||||
{
|
||||
return _allowed_account_dict.ContainsKey(account);
|
||||
}
|
||||
|
||||
IEnumerable<Tuple<BTSAccountImmutable, TradingPermissionUnitImmutable>> search_admin(Func<BTSUserImmutable, bool> predicate)
|
||||
{
|
||||
var rv = new List<Tuple<BTSAccountImmutable, TradingPermissionUnitImmutable>>();
|
||||
if (_admin_data.HasValue)
|
||||
{
|
||||
var users = _admin_data.Value.Users.Where(predicate);
|
||||
if (users != null && users.Count() == 1)
|
||||
{
|
||||
var user = users.ElementAt(0);
|
||||
if (user.HasUserData && user.UserData.HasAccountPermissions)
|
||||
{
|
||||
foreach (var kv in user.UserData.AccountPermissions.AccountPermissions)
|
||||
{
|
||||
var account_id = kv.Key;
|
||||
var accounts = _admin_data.Value.Accounts.Where(a => a.Id == account_id);
|
||||
if (accounts != null && accounts.Count() == 1)
|
||||
{
|
||||
var account = accounts.ElementAt(0);
|
||||
var tp = kv.Value.TradingPermission;
|
||||
var rv_val = new TradingPermissionUnitImmutable(tp);
|
||||
rv.Add(Tuple.Create(account, rv_val));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.api.position;
|
||||
using bts.spark;
|
||||
using mbts.collections;
|
||||
using mbts.spark;
|
||||
using mbts.spark.utilities;
|
||||
using mbts.utils;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
|
||||
namespace bts.api.core
|
||||
{
|
||||
|
||||
static class PermissionFilters
|
||||
{
|
||||
public static IObservable<Update<PositionUnitImmutable, int>> PermissionFilter(this IObservable<Update<PositionUnitImmutable, int>> source, AdminModel am)
|
||||
{
|
||||
var pred = Observable.Create<Func<PositionUnitImmutable, bool>>( observer =>
|
||||
{
|
||||
return am.on_admin_data.Subscribe(x =>
|
||||
{
|
||||
Func<PositionUnitImmutable, bool> filter = (z => x.can_view_account(z.Account));
|
||||
observer.OnNext(filter);
|
||||
});
|
||||
});
|
||||
|
||||
return source.Filter(pred);
|
||||
}
|
||||
|
||||
public static IObservable<Update<OrderRecordImmutable, uint>> PermissionFilter(this IObservable<Update<OrderRecordImmutable, uint>> source, AdminModel am)
|
||||
{
|
||||
var pred = Observable.Create<Func<OrderRecordImmutable, bool>>(observer =>
|
||||
{
|
||||
return am.on_admin_data.Subscribe(x =>
|
||||
{
|
||||
Func<OrderRecordImmutable, bool> filter = (z => x.can_view_account(z.account()));
|
||||
observer.OnNext(filter);
|
||||
});
|
||||
});
|
||||
|
||||
return source.Filter(pred);
|
||||
}
|
||||
|
||||
public static IObservable<Update<FillImmutable, string>> PermissionFilter(this IObservable<Update<FillImmutable, string>> source, AdminModel am)
|
||||
{
|
||||
var pred = Observable.Create<Func<FillImmutable, bool>>(observer =>
|
||||
{
|
||||
return am.on_admin_data.Subscribe(x =>
|
||||
{
|
||||
Func<FillImmutable, bool> filter = (z => x.can_view_account(z.Account));
|
||||
observer.OnNext(filter);
|
||||
});
|
||||
});
|
||||
|
||||
return source.Filter(pred);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -47,7 +47,7 @@ namespace mbts.spark {
|
||||
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));
|
||||
|
||||
class OrderBookCellStore<T> : IOrderBookCellStore<T> {
|
||||
class OrderBookCellStore<T> : IOrderBookCellStore<T>, INotifyPropertyChanged {
|
||||
readonly OrderBookRowVM _row;
|
||||
readonly ICVOrderBookColumn _column;
|
||||
readonly string _prop_name;
|
||||
@@ -58,6 +58,10 @@ namespace mbts.spark {
|
||||
readonly IEqualityComparer<T> _eq_comparer;
|
||||
OrderBookCell<T> _current;
|
||||
|
||||
// Property changed handler to avoid WPF binding leak
|
||||
// https://blog.jetbrains.com/dotnet/2014/09/04/fighting-common-wpf-memory-leaks-with-dotmemory/
|
||||
public event PropertyChangedEventHandler PropertyChanged = (_,__) => { };
|
||||
|
||||
public OrderBookCellStore(
|
||||
OrderBookRowVM row,
|
||||
ICVOrderBookColumn column,
|
||||
|
||||
@@ -15,7 +15,7 @@ using OrderStatus = bts.api.orderman.OrderStatus;
|
||||
|
||||
namespace mbts.spark {
|
||||
partial class OrderBookRowVM : DisposableViewModelBase, IEquatable<OrderBookRowVM>, DIYGrid.IRowVM<OrderRecordTuple>, DIYGrid.IActionableRowVM {
|
||||
public readonly PredicateAnimator<OrderBookRowVM, OrderRecordTuple> animator;
|
||||
//public readonly PredicateAnimator<OrderBookRowVM, OrderRecordTuple> animator;
|
||||
|
||||
const string _unknown_security_price_format_string = "0.#########";
|
||||
const int _unknown_security_price_num_fractional_digits = 9;
|
||||
@@ -31,29 +31,65 @@ namespace mbts.spark {
|
||||
OrderBookRowVM _parent;
|
||||
OrderBookVM.BranchFilterSource _branch_filter_source;
|
||||
// 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;
|
||||
IOrderBookCellStore<string> _symbol_store;
|
||||
IOrderBookCellStore<uint> _order_key_store_backing;
|
||||
IOrderBookCellStore<uint> _parent_order_key_store_backing;
|
||||
IOrderBookCellStore<uint> _security_key_store_backing;
|
||||
IOrderBookCellStore<SecurityImmutable?> _security_store_backing;
|
||||
IOrderBookCellStore<Venue> _exchange_store_backing;
|
||||
IOrderBookCellStore<string> _symbol_store_backing;
|
||||
// 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;
|
||||
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store_backing;
|
||||
IOrderBookCellStore<Side> _side_store_backing;
|
||||
IOrderBookCellStore<string> _user_store_backing;
|
||||
IOrderBookCellStore<string> _account_store_backing;
|
||||
IOrderBookCellStore<TimeInForce> _time_in_force_store_backing;
|
||||
IOrderBookCellStore<DateTime> _time_created_store_backing;
|
||||
// State
|
||||
IOrderBookCellStore<DateTime> _last_change_store;
|
||||
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;
|
||||
IOrderBookCellStore<DateTime> _last_change_store_backing;
|
||||
IOrderBookCellStore<OrderStatus> _status_store_backing;
|
||||
IOrderBookCellStore<double> _price_store_backing;
|
||||
IOrderBookCellStore<uint> _quantity_store_backing;
|
||||
IOrderBookCellStore<uint> _filled_quantity_store_backing;
|
||||
IOrderBookCellStore<double> _last_price_store_backing;
|
||||
IOrderBookCellStore<double> _avg_price_store_backing;
|
||||
IOrderBookCellStore<string> _last_msg_store_backing;
|
||||
IOrderBookCellStore<OrderSubType> _order_sub_type_store_backing;
|
||||
// Core
|
||||
IOrderBookCellStore<uint> _order_key_store => _order_key_store_backing ?? (_order_key_store_backing = CellFactory.create(this, _book_vm.columns.order_key_column, nameof(order_key), r => r._order.Key, r => r._order_key_store));
|
||||
IOrderBookCellStore<uint> _parent_order_key_store => _parent_order_key_store_backing ?? (_parent_order_key_store_backing = CellFactory.create(this, _book_vm.columns.parent_key_column, nameof(parent_order_key), r => r._order.ParentKey, r => r._parent_order_key_store));
|
||||
IOrderBookCellStore<uint> _security_key_store => _security_key_store_backing ?? (_security_key_store_backing = CellFactory.create(this, _book_vm.columns.security_key_column, nameof(security_key), r => r._order.security_key(), r => r._security_key_store));
|
||||
IOrderBookCellStore<SecurityImmutable?> _security_store => _security_store_backing ?? (_security_store_backing = CellFactory.create_nullable(this, _book_vm.columns.security_column, nameof(security), r => r._security, r => r._security_store, v => v.display_name()));
|
||||
IOrderBookCellStore<Venue> _exchange_store => _exchange_store_backing ?? (_exchange_store_backing = CellFactory.create(this, _book_vm.columns.exchange_column, nameof(exchange), r => r._security.HasValue ? _security.Value.Exchange : Venue.Unset, r => r._exchange_store));
|
||||
IOrderBookCellStore<string> _symbol_store => _symbol_store_backing ?? (_symbol_store_backing = CellFactory.create(this, _book_vm.columns.symbol_column, nameof(symbol), r => r._symbol, r => r._symbol_store, v => !String.IsNullOrEmpty(v) ? v : "unknown symbol"));
|
||||
// Def
|
||||
IOrderBookCellStore<CustomOrderDefinition.CustomOneofCase> _order_type_store => _order_type_store_backing ?? (_order_type_store_backing = CellFactory.create(this, _book_vm.columns.order_type_column, nameof(order_type), r => r._order.order_type(), r => r._order_type_store));
|
||||
IOrderBookCellStore<Side> _side_store => _side_store_backing ?? (_side_store_backing = CellFactory.create(this, _book_vm.columns.side_column, nameof(side), r => r._order.side(), r => r._side_store));
|
||||
IOrderBookCellStore<string> _user_store => _user_store_backing ?? (_user_store_backing = CellFactory.create(this, _book_vm.columns.user_column, nameof(user), r => _order.user(), r => r._user_store));
|
||||
IOrderBookCellStore<string> _account_store => _account_store_backing ?? (_account_store_backing = CellFactory.create(this, _book_vm.columns.account_column, nameof(account), r => r._order.account(), r => r._account_store));
|
||||
IOrderBookCellStore<TimeInForce> _time_in_force_store => _time_in_force_store_backing ?? (_time_in_force_store_backing = 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));
|
||||
IOrderBookCellStore<DateTime> _time_created_store => _time_created_store_backing ?? (_time_created_store_backing = CellFactory.create(this, _book_vm.columns.time_created_column, nameof(time_created), r => r._order.created(), r => r._time_created_store, format_date));
|
||||
// State
|
||||
IOrderBookCellStore<DateTime> _last_change_store => _last_change_store_backing ?? (_last_change_store_backing = CellFactory.create(this, _book_vm.columns.last_change_column, nameof(last_change), r => _order.last_change(), r => r._last_change_store, format_date));
|
||||
IOrderBookCellStore<OrderStatus> _status_store => _status_store_backing ?? (_status_store_backing = CellFactory.create(this, _book_vm.columns.status_column, nameof(status), r => _order.status(), r => r._status_store));
|
||||
IOrderBookCellStore<double> _price_store => _price_store_backing ?? (_price_store_backing = CellFactory.create(this, _book_vm.columns.price_column, nameof(price), r => _order.price(), r => r._price_store, format_price));
|
||||
IOrderBookCellStore<uint> _quantity_store => _quantity_store_backing ?? (_quantity_store_backing = CellFactory.create(this, _book_vm.columns.qty_column, nameof(quantity), r => _order.quantity(), r => r._quantity_store));
|
||||
IOrderBookCellStore<uint> _filled_quantity_store => _filled_quantity_store_backing ?? (_filled_quantity_store_backing = CellFactory.create(this, _book_vm.columns.filled_qty_column, nameof(filled_quantity), r => _order.filled(), r => r._filled_quantity_store));
|
||||
IOrderBookCellStore<double> _last_price_store => _last_price_store_backing ?? (_last_price_store_backing = CellFactory.create(this, _book_vm.columns.last_price_column, nameof(last_price), r => _order.last_price(), r => r._last_price_store, format_price));
|
||||
IOrderBookCellStore<double> _avg_price_store => _avg_price_store_backing ?? (_avg_price_store_backing = CellFactory.create(this, _book_vm.columns.avg_price_column, nameof(avg_price), r => _order.avg_price(), r => r._avg_price_store, format_price));
|
||||
IOrderBookCellStore<string> _last_msg_store => _last_msg_store_backing ?? (_last_msg_store_backing = CellFactory.create(this, _book_vm.columns.last_message_column, nameof(last_msg), r => _order.last_msg(), r => r._last_msg_store));
|
||||
IOrderBookCellStore<OrderSubType> _order_sub_type_store => _order_sub_type_store_backing ?? (_order_sub_type_store_backing = 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));
|
||||
|
||||
static string format_date(DateTime v) => $"{v:M/d} {v:h:mm:ss.fff tt}";
|
||||
string format_price(double v) => _security.HasValue ?
|
||||
_security.Value.ContractSpec.format(v) :
|
||||
Math.Round(v, _unknown_security_price_num_fractional_digits, MidpointRounding.AwayFromZero).ToString(_unknown_security_price_format_string);
|
||||
static string format_sub_type(OrderSubType v) {
|
||||
switch (v) {
|
||||
case OrderSubType.HedgingLeg: return "H";
|
||||
case OrderSubType.QuotingLeg: return "Q";
|
||||
default: return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
public OrderBookRowVM(IBus bus, OrderBookVM source_vm, OrdersModel model, OrderRecordImmutable record, SecurityMD? s) {
|
||||
_book_vm = source_vm;
|
||||
@@ -61,43 +97,7 @@ namespace mbts.spark {
|
||||
_order = record;
|
||||
_security_md = s;
|
||||
_symbol = _security_md.HasValue ? _security_md.Value.security.Symbol : null;
|
||||
animator = new PredicateAnimator<OrderBookRowVM,OrderRecordTuple>(this);
|
||||
// cells
|
||||
cell_stores = new DIYGrid.IGridCellStore[] {
|
||||
_security_store = CellFactory.create_nullable(this, _book_vm.columns.security_column, nameof(security), r => r._security, r => r._security_store, v => v.display_name()),
|
||||
_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),
|
||||
_symbol_store = CellFactory.create(this, _book_vm.columns.symbol_column, nameof(symbol), r => r._symbol, r => r._symbol_store, v => !String.IsNullOrEmpty(v) ? v : "unknown symbol"),
|
||||
_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) :
|
||||
Math.Round(v, _unknown_security_price_num_fractional_digits, MidpointRounding.AwayFromZero).ToString(_unknown_security_price_format_string);
|
||||
string format_date(DateTime v) => $"{v:M/d} {v:h:mm:ss.fff tt}";
|
||||
string format_sub_type(OrderSubType v) {
|
||||
switch (v) {
|
||||
case OrderSubType.HedgingLeg: return "H";
|
||||
case OrderSubType.QuotingLeg: return "Q";
|
||||
default: return string.Empty;
|
||||
}
|
||||
}
|
||||
//
|
||||
//animator = new PredicateAnimator<OrderBookRowVM,OrderRecordTuple>(this);
|
||||
_visible = backing(nameof(is_visible), true);
|
||||
_expanded = backing(nameof(is_expanded), false);
|
||||
_depth_level = backing(nameof(depth_level), () => {
|
||||
@@ -212,7 +212,31 @@ namespace mbts.spark {
|
||||
}
|
||||
|
||||
public ObservableCollection<RowAnimation> row_animations { get; } = new ObservableCollection<RowAnimation>();
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores { get; }
|
||||
|
||||
DIYGrid.IGridCellStore[] _cell_stores_backing;
|
||||
public IReadOnlyList<DIYGrid.IGridCellStore> cell_stores => _cell_stores_backing ?? (_cell_stores_backing = new DIYGrid.IGridCellStore[] {
|
||||
_security_store,
|
||||
_security_key_store,
|
||||
_parent_order_key_store,
|
||||
_order_key_store,
|
||||
_symbol_store,
|
||||
_time_created_store,
|
||||
_user_store,
|
||||
_account_store,
|
||||
_exchange_store,
|
||||
_time_in_force_store,
|
||||
_order_type_store,
|
||||
_order_sub_type_store,
|
||||
_side_store,
|
||||
_last_change_store,
|
||||
_price_store,
|
||||
_quantity_store,
|
||||
_filled_quantity_store,
|
||||
_avg_price_store,
|
||||
_last_price_store,
|
||||
_status_store,
|
||||
_last_msg_store
|
||||
});
|
||||
|
||||
public static Stack<OrderBookRowVM> lineage(OrderBookRowVM node) {
|
||||
var rv = new Stack<OrderBookRowVM>();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using bts.api;
|
||||
using bts;
|
||||
using bts.api;
|
||||
using bts.api.core;
|
||||
using bts.api.orderman;
|
||||
using bts.shell;
|
||||
@@ -69,12 +70,12 @@ namespace mbts.spark {
|
||||
|
||||
_dispose = Disposable.Create(
|
||||
bus.subscribe(SparkTags.SecuritiesTag).OnDispatcher().Subscribe(on_securities),
|
||||
model.on_orders.OnDispatcher().AddRemoveOnly().Subscribe(on_orders),
|
||||
model.on_orders.OnDispatcher().Subscribe(on_orders),
|
||||
grid_settings_manager,
|
||||
columns,
|
||||
order_securities_subject);
|
||||
|
||||
void on_orders(LegacyTableUpdate<OrderRecordImmutable, uint> table) {
|
||||
void on_orders(TableUpdate<OrderRecordImmutable, uint> table) {
|
||||
// clear everything (most likely the server restarted)
|
||||
if (table.removed.Count() == _order_rows.Count && !table.added.Any()) {
|
||||
_selected_rows.Clear();
|
||||
@@ -85,18 +86,14 @@ namespace mbts.spark {
|
||||
_parent_to_children.clear();
|
||||
_security_orders.clear();
|
||||
missing_security_orders.clear();
|
||||
Log.Ui.warn("OrderBook received reset message.");
|
||||
return;
|
||||
}
|
||||
var changed_keys = new HashSet<uint>();
|
||||
var added_securities = new List<SecurityImmutable>(1);
|
||||
var removed_securities = new List<SecurityImmutable>(1);
|
||||
|
||||
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_rows_cache[o.Key].update_order(changed_order);
|
||||
} else if (_order_rows_cache.TryPopValue(o.Key, out OrderBookRowVM row)) {
|
||||
if (_order_rows_cache.TryPopValue(o.Key, out OrderBookRowVM row)) {
|
||||
row.Dispose();
|
||||
_order_rows.Remove(row);
|
||||
if (_security_orders.removes_key(o.Def.SecurityKey, o.Key)) {
|
||||
@@ -123,10 +120,10 @@ namespace mbts.spark {
|
||||
missing_security_orders.remove(o.Key);
|
||||
}
|
||||
}
|
||||
foreach (var ch in table.changes)
|
||||
_order_rows_cache[ch.New.Key].update_order(ch.New);
|
||||
var orphans = new Dictionary<uint, OrderBookRowVM>();
|
||||
foreach (var o in table.added) {
|
||||
if (changed_keys.Contains(o.Key))
|
||||
continue;
|
||||
SecurityMD? security_md;
|
||||
if (security_dict.TryGetValue(o.Def.SecurityKey, out SecurityMD smd)) {
|
||||
security_md = smd;
|
||||
|
||||
@@ -15,11 +15,9 @@ namespace mbts.spark {
|
||||
CVRowTemplateKey = "CVRowTemplateKey";
|
||||
|
||||
public const double RowHeight = 17;
|
||||
OrderBookVM _vm;
|
||||
|
||||
public OrderBookView(OrderBookVM vm) {
|
||||
InitializeComponent();
|
||||
_vm = vm;
|
||||
DataContext = vm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace mbts.spark {
|
||||
IDisposable _dispose;
|
||||
|
||||
[ImportingConstructor]
|
||||
private OrdersModel(IBus bus, OrderFactory factory, OrderService service, PositionService position_service, SpreaderModel spreader, ErrorReporter error) {
|
||||
private OrdersModel(IBus bus, OrderFactory factory, OrderService service, PositionService position_service, SpreaderModel spreader, ErrorReporter error, AdminModel admin_model) {
|
||||
_bus = bus;
|
||||
_factory = factory;
|
||||
_service = service;
|
||||
@@ -41,15 +41,15 @@ namespace mbts.spark {
|
||||
_error = error;
|
||||
var scheduler = ThreadPoolScheduler.Instance;
|
||||
var interval = TimeSpan.FromMilliseconds(100);
|
||||
on_orders = _service.subscribei_orders().Naggle(interval, scheduler).Table().Cache();
|
||||
on_positions = _position_service.subscribei_position_data().Naggle(interval, scheduler).Table().Cache();
|
||||
on_orders = _service.subscribei_orders().Naggle(interval, scheduler).PermissionFilter(admin_model).Table().Cache();
|
||||
on_positions = _position_service.subscribei_position_data().Naggle(interval, scheduler).PermissionFilter(admin_model).Table().Cache();
|
||||
working_orders =
|
||||
on_orders.Scan(
|
||||
ImmutableDictionary<uint,OrderRecordImmutable>.Empty,
|
||||
(workers, table) => _update_working_orders(workers, table))
|
||||
.Replay(1).RefCount();
|
||||
on_order_tuples = OrderRecordTupleSource.create_observable(bus.subscribe(SparkTags.SecuritiesTag), on_orders);
|
||||
on_fills = _service.subscribei_fills().Naggle(interval, scheduler).Table().Cache();
|
||||
on_fills = _service.subscribei_fills().Naggle(interval, scheduler).PermissionFilter(admin_model).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());
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using mbts.collections.extensions;
|
||||
using System.Collections;
|
||||
using bts.collections;
|
||||
|
||||
namespace mbts.ui.utility
|
||||
{
|
||||
|
||||
@@ -666,44 +666,6 @@ namespace mbts.collections.extensions {
|
||||
}
|
||||
}
|
||||
|
||||
public static class ListExtensions
|
||||
{
|
||||
#region RemoveWhere
|
||||
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> where) { foreach (var r in list.Where(where).ToList()) list.Remove(r); }
|
||||
|
||||
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> where, Action<T> on_remove) {
|
||||
int count = list.Count;
|
||||
for (int i = 0; i < count; i++) {
|
||||
var t = list[i];
|
||||
if (where(t)) {
|
||||
list.RemoveAt(i--);
|
||||
--count;
|
||||
on_remove(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Random rng = new Random();
|
||||
|
||||
public static void Shuffle<T>(this IList<T> list) {
|
||||
int n = list.Count;
|
||||
while (n > 1) {
|
||||
n--;
|
||||
int k = rng.Next(n + 1);
|
||||
T value = list[k];
|
||||
list[k] = list[n];
|
||||
list[n] = value;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static void RemoveAt<T>(this IList<T> list, int index, int count) {
|
||||
for (int i = 0; i < count; i++)
|
||||
list.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DictionaryExtensions
|
||||
{
|
||||
#region GetValueOrDefault
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace mbts.ui.settings
|
||||
}
|
||||
|
||||
public Task<IShellWindow> restore(WindowState window) {
|
||||
Log.Ui.info("Loading window '{0}'".fmt(window.title));
|
||||
bool changed;
|
||||
int count = 0;
|
||||
do {
|
||||
|
||||
@@ -446,9 +446,6 @@ namespace mbts.ui.shell
|
||||
public IEnumerable<string> AllErrors { get { return _errors.Values; } }
|
||||
#endregion
|
||||
|
||||
protected Property<T> backing<T>(string name, T default_value, bool validate_now = false, params Func<T, bool>[] validation_steps) =>
|
||||
backing(name, default_value, validation_steps.Select(v => new PropertyValidation<T>(v)).ToLArray(), validate_now);
|
||||
|
||||
protected Property<T> backing<T>(string name, T default_value, IEnumerable<PropertyValidation<T>> validation_steps, bool validate_now = false) {
|
||||
void run_validation(T val) {
|
||||
foreach (var pv in validation_steps) {
|
||||
|
||||
@@ -168,7 +168,7 @@ namespace mbts.ui.controls {
|
||||
o.Resources[ActiveSelectionHighlightKey] = v;
|
||||
o.Resources[InactiveSelectionHighlightKey] = new SolidColorBrush(v.Color) { Opacity = 0.2 }.frozen();
|
||||
o.Resources[SelectionStrokeKey] = new Pen(o.SelectionHighlight.Color.to_brush().frozen(), 1).frozen();
|
||||
foreach (var col in o.Columns.OfType<ISelectionHighlightListener>())
|
||||
foreach (var col in o.Columns.empty_if_null().OfType<ISelectionHighlightListener>())
|
||||
col.on_selection_fill(v);
|
||||
}, (o, v) => v as SolidColorBrush ?? DefaultSelectionHighlight,
|
||||
DefaultSelectionHighlight);
|
||||
@@ -634,7 +634,7 @@ namespace mbts.ui.controls {
|
||||
(o, v) => {
|
||||
if (DoubleHelper.IsNaNOrInfinity(v))
|
||||
v = 0;
|
||||
if (!o._visible_columns.is_null_or_empty()) {
|
||||
if (!o._visible_columns.is_null_or_empty() && o.Columns != null) {
|
||||
double max_font_size = o.Columns.Max(c => c.font_size);
|
||||
double min_row_height = o.RowPadding.Top + max_font_size + o.RowPadding.Bottom;
|
||||
if (v < min_row_height)
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace mbts.ui {
|
||||
readonly ObservableCollection<TRow> _rows;
|
||||
readonly ICollectionView _rows_view;
|
||||
readonly ObservableCollection<ColumnBase> _columns;
|
||||
readonly ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow> _animators;
|
||||
//readonly ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow> _animators;
|
||||
readonly Property<GridSettings> _grid_settings;
|
||||
readonly DelegateProperty<FontFamily> _font_family;
|
||||
readonly DelegateProperty<FontWeight> _font_weight;
|
||||
@@ -59,18 +59,18 @@ namespace mbts.ui {
|
||||
_font_size = backing(nameof(font_size), () => grid_settings.font_size, nameof(font_size));
|
||||
_font_color = backing(nameof(font_color), () => grid_settings.font_color.rgb_solid_brush().frozen(), nameof(grid_settings));
|
||||
_selection_highlight = backing(nameof(selection_highlight), () => grid_settings.selection_highlight.to_brush().frozen(), nameof(grid_settings));
|
||||
_animators = new ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow>(_rows = rows, create_animator);
|
||||
//_animators = new ObservableAutoWrapper<PredicateAnimator<TRow, TRowData>, TRow>(_rows = rows, create_animator);
|
||||
_resources = new List<IDisposable> {
|
||||
ObservableAutoWrapper.connect<SortDescriptionCollection, SortDescription, string>(
|
||||
rows_view.SortDescriptions, live_sorting_props = ((ICollectionViewLiveShaping)rows_view).LiveSortingProperties, sd => sd.PropertyName),
|
||||
(launch_settings_editor = new DialogCommand(create_settings_view))
|
||||
};
|
||||
//
|
||||
PredicateAnimator<TRow, TRowData> create_animator(TRow r) {
|
||||
var pa = new PredicateAnimator<TRow, TRowData>(r);
|
||||
pa.set_predicate_animations(_animation_defs);
|
||||
return pa;
|
||||
}
|
||||
//PredicateAnimator<TRow, TRowData> create_animator(TRow r) {
|
||||
// var pa = new PredicateAnimator<TRow, TRowData>(r);
|
||||
// pa.set_predicate_animations(_animation_defs);
|
||||
// return pa;
|
||||
//}
|
||||
Window create_settings_view() {
|
||||
var vm = new GridSettingsVM<TRowData, TPredicateBuilder>(owner_title, _bus, _orders_model, create_predicate_vm, create_column_predicate_vm);
|
||||
// need to refresh before launching the settings-view bc users can chanve some grid-column-settings properties directly from the grid (e.g. width, is_collapsed, etc.)
|
||||
@@ -134,9 +134,9 @@ namespace mbts.ui {
|
||||
_animation_defs.add(pa.predicate_builder.create_predicate(_arg), PredicateAnimations.flat_definitions<TRow, TRowData>(pa.row_animations, pa.cell_animations, lookup_column));
|
||||
}
|
||||
}
|
||||
foreach (var a in _animators) {
|
||||
a.set_predicate_animations(_animation_defs);
|
||||
}
|
||||
//foreach (var a in _animators) {
|
||||
// a.set_predicate_animations(_animation_defs);
|
||||
//}
|
||||
// ** in the unfortunate event that we botch backwards compatability, and the columns don't bear a 1:1 relationship with the column-settings
|
||||
// don't add insult to injury by crashing **
|
||||
if (grid_settings.columns != null && _columns != null) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using bts.api;
|
||||
using bts.spark;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -14,9 +15,10 @@ namespace mbts.ui.controls {
|
||||
SetCurrentValue(FocusableProperty, false);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public SparkMenu(IEnumerable<SparkCommand> commands, IBus bus) : this() =>
|
||||
ItemsSource = commands.Select(c => new SparkCommandVM(c, bus)).ToList();
|
||||
// ObservableCollection to avoid WPF Binding Leak
|
||||
ItemsSource = new ObservableCollection<SparkCommandVM>(commands.Select(c => new SparkCommandVM(c, bus)));
|
||||
|
||||
public bool ShowText {
|
||||
get { return (bool)GetValue(ShowTextProperty); }
|
||||
|
||||
@@ -6,6 +6,7 @@ using System.Windows.Interop;
|
||||
using System.Runtime.InteropServices;
|
||||
using mbts.ui.native;
|
||||
using System.Windows;
|
||||
using System.Windows.Threading;
|
||||
|
||||
namespace mbts.ui.shell
|
||||
{
|
||||
@@ -82,7 +83,14 @@ namespace mbts.ui.shell
|
||||
return _hwnd;
|
||||
}
|
||||
|
||||
protected override void DestroyWindowCore(HandleRef hwnd) { }
|
||||
protected override void DestroyWindowCore(HandleRef hwnd) {
|
||||
// Improve GC performance by keeping windows out of the Finalizer queue
|
||||
if (hwnd.Wrapper is WPFPanel p) {
|
||||
p.BeginInvoke((Action)(() => {
|
||||
p.Dispose();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
[DllImport("user32.dll", SetLastError=true)]
|
||||
static extern IntPtr SetParent(HandleRef hwndChild, HandleRef hwndParent);
|
||||
|
||||
@@ -222,16 +222,15 @@ namespace mbts.ui.shell
|
||||
}
|
||||
}
|
||||
if (def.windows != null) {
|
||||
foreach (var window in def.windows) {
|
||||
Log.Ui.info("Loading window '{0}'".fmt(window.title));
|
||||
var new_shell_window = await _window_restore.restore(window);
|
||||
// user can close the launcher from the taskbar while a workspace is loading
|
||||
if (_closing) {
|
||||
return;
|
||||
} else if (new_shell_window != null) {
|
||||
await _wm.add(new_shell_window);
|
||||
async Task restore(WindowState window) {
|
||||
var w = await _window_restore.restore(window);
|
||||
if (!_closing) {
|
||||
await _wm.add(w);
|
||||
}
|
||||
}
|
||||
var loading = def.windows.Select(restore);
|
||||
await Task.WhenAll(loading);
|
||||
if (_closing) return;
|
||||
}
|
||||
sw.Stop();
|
||||
Log.Ui.info($"Time to load workspace '{name}': {sw.Elapsed}");
|
||||
|
||||
@@ -16,6 +16,7 @@ using System.Windows.Media.Imaging;
|
||||
using System.Windows.Navigation;
|
||||
using System.Windows.Shapes;
|
||||
using mbts.ui;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace bts.view {
|
||||
public partial class SparkWindowView : UserControl {
|
||||
@@ -36,7 +37,8 @@ namespace bts.view {
|
||||
_menu.ShowText = false;
|
||||
_grid.Children.Add(_menu);
|
||||
if (ContextMenu != null) {
|
||||
ContextMenu.ItemsSource = commands.Select(c => new SparkCommandVM(c, bus)).ToList();
|
||||
// ObservableCollection to avoid WPF Binding Leak
|
||||
ContextMenu.ItemsSource = new ObservableCollection<SparkCommandVM>(commands.Select(c => new SparkCommandVM(c, bus)));
|
||||
}
|
||||
} else {
|
||||
expand_button.Visibility = Visibility.Collapsed;
|
||||
|
||||
@@ -129,6 +129,7 @@ namespace bts.api.core {
|
||||
public IEnumerable<T> removed { get; }
|
||||
|
||||
public TKey key(T t) { return _key(t); }
|
||||
internal Func<T, TKey> key_extractor => _key;
|
||||
|
||||
#if DEBUG
|
||||
public override string ToString() {
|
||||
@@ -347,5 +348,72 @@ namespace bts.api.core {
|
||||
return source.Select(t => new LegacyTableUpdate<T, TKey>(t));
|
||||
}
|
||||
|
||||
public static IObservable<Update<T, TKey>> Filter<T, TKey>(this IObservable<Update<T, TKey>> source, IObservable<Func<T, bool>> predicate) where TKey : IEquatable<TKey>
|
||||
{
|
||||
return Observable.Create<Update<T, TKey>>(observer =>
|
||||
{
|
||||
Update<T, TKey> unfiltered_cache = null, filtered_cache = null;
|
||||
Func<T, bool> pred_cache = null;
|
||||
Log.DataFlow.diag($"Create Filter {typeof(T).Name}");
|
||||
|
||||
return source.CombineLatest(predicate, (upd, pred) => (upd, pred)).Subscribe( tup =>
|
||||
{
|
||||
bool pred_changed = !object.ReferenceEquals(tup.pred, pred_cache) && (pred_cache != null);
|
||||
pred_cache = tup.pred;
|
||||
unfiltered_cache = tup.upd;
|
||||
if (pred_changed && filtered_cache != null)
|
||||
{
|
||||
//clear out existing
|
||||
var clear = new Update<T, TKey>(
|
||||
(IReadOnlyCollection<T>)Array.Empty<T>(),
|
||||
Enumerable.Empty<T>(),
|
||||
Enumerable.Empty<Change<T>>(),
|
||||
filtered_cache.all, unfiltered_cache.key_extractor);
|
||||
observer.OnNext(clear);
|
||||
|
||||
var refilter = unfiltered_cache.all.Where(tup.pred).ToList();
|
||||
var readd = new Update<T, TKey>(
|
||||
refilter, refilter,
|
||||
Enumerable.Empty<Change<T>>(),
|
||||
Enumerable.Empty<T>(), unfiltered_cache.key_extractor);
|
||||
observer.OnNext(readd);
|
||||
filtered_cache = readd;
|
||||
}
|
||||
else
|
||||
{
|
||||
IReadOnlyCollection<T> all; IEnumerable<T> added, removed; IEnumerable<Change<T>> changed;
|
||||
apply_predicate(tup.pred, tup.upd, out all, out added, out changed, out removed);
|
||||
var next = new Update<T, TKey>(all, added, changed, removed, tup.upd.key_extractor);
|
||||
observer.OnNext(next);
|
||||
filtered_cache = next;
|
||||
}
|
||||
},
|
||||
() => observer.OnCompleted());
|
||||
});
|
||||
}
|
||||
|
||||
private static void apply_predicate<T, TKey>(Func<T, bool> predicate, Update<T, TKey> cache,
|
||||
out IReadOnlyCollection<T> all, out IEnumerable<T> added,
|
||||
out IEnumerable<Change<T>> changed, out IEnumerable<T> removed) where TKey : IEquatable<TKey>
|
||||
{
|
||||
var adds = new List<T>();
|
||||
adds.AddRange(cache.added.Where(predicate));
|
||||
var rems = new List<T>();
|
||||
rems.AddRange(cache.removed.Where(predicate));
|
||||
var diffs = new List<Change<T>>();
|
||||
foreach (var change in cache.changes)
|
||||
{
|
||||
if(!predicate(change.Old) && predicate(change.New))
|
||||
adds.Add(change.New);
|
||||
else if(predicate(change.Old) && !predicate(change.New))
|
||||
rems.Add(change.Old);
|
||||
else if(predicate(change.Old) && predicate(change.New))
|
||||
diffs.Add(change);
|
||||
}
|
||||
all = cache.all.Where(predicate).ToList();
|
||||
added = adds;
|
||||
changed = diffs;
|
||||
removed = rems;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace bts.collections {
|
||||
public static class ListExtensions {
|
||||
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> where) { foreach (var r in list.Where(where).ToList()) list.Remove(r); }
|
||||
|
||||
public static void RemoveWhere<T>(this IList<T> list, Func<T, bool> where, Action<T> on_remove) {
|
||||
int count = list.Count;
|
||||
for (int i = 0; i < count; i++) {
|
||||
var t = list[i];
|
||||
if (where(t)) {
|
||||
list.RemoveAt(i--);
|
||||
--count;
|
||||
on_remove(t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Random rng = new Random();
|
||||
|
||||
public static void Shuffle<T>(this IList<T> list) {
|
||||
int n = list.Count;
|
||||
while (n > 1) {
|
||||
n--;
|
||||
int k = rng.Next(n + 1);
|
||||
T value = list[k];
|
||||
list[k] = list[n];
|
||||
list[n] = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static void RemoveAt<T>(this IList<T> list, int index, int count) {
|
||||
for (int i = 0; i < count; i++)
|
||||
list.RemoveAt(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@ namespace bts.collections {
|
||||
|
||||
// Insert (return: true) or overwrite (return false) a new value
|
||||
// NOTE: you should use AddAll if you are adding multiple values
|
||||
bool Insert(T item) {
|
||||
public bool Insert(T item) {
|
||||
var item_id = id(item);
|
||||
if (!_index.TryGetValue(item_id, out Node itemnode)) {
|
||||
// Item is new
|
||||
@@ -57,7 +57,7 @@ namespace bts.collections {
|
||||
}
|
||||
}
|
||||
}
|
||||
void Remove(T item) {
|
||||
public void Remove(T item) {
|
||||
var item_id = id(item);
|
||||
if (_index.TryGetValue(item_id, out Node itemnode)) {
|
||||
if (_index.TryGetValue(parent(item), out Node parentnode)) {
|
||||
@@ -75,7 +75,7 @@ namespace bts.collections {
|
||||
}
|
||||
|
||||
// Note: This method does not handle replacements correctly.
|
||||
void AddAll(IEnumerable<T> items) {
|
||||
public void AddAll(IEnumerable<T> items) {
|
||||
// When adding multiple items we build the index first to avoid the children before parents problem
|
||||
foreach (var item in items) {
|
||||
//_index[id(item)] = new Node(item, _comparer);
|
||||
@@ -103,6 +103,27 @@ namespace bts.collections {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Node parentmost(T item) {
|
||||
if (_index.TryGetValue(parent(item), out Node pnode)) {
|
||||
return parentmost(pnode.value);
|
||||
}
|
||||
else if (_index.TryGetValue(id(item), out Node mynode)) {
|
||||
return mynode;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
public IEnumerable<T> EnumerateGroup(T item) {
|
||||
var pnode = parentmost(item);
|
||||
if (pnode != null) {
|
||||
yield return pnode.value;
|
||||
foreach (var child in iterate(pnode.children)) {
|
||||
yield return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
public IEnumerator<T> GetEnumerator() {
|
||||
return iterate(_tree).GetEnumerator();
|
||||
}
|
||||
|
||||
+27
-3
@@ -1,6 +1,5 @@
|
||||
using bts.collections;
|
||||
using mbts.collections.extensions;
|
||||
using NUnit.Framework;
|
||||
using bts.test;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
@@ -9,7 +8,7 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace bts.tests {
|
||||
[TestFixture]
|
||||
[TestGroup]
|
||||
class SortedTreeTests {
|
||||
|
||||
class Order {
|
||||
@@ -92,5 +91,30 @@ namespace bts.tests {
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void test_enumerate_group() {
|
||||
var comparer = Comparer<Order>.Create((l, r) => l.name.CompareTo(r.name));
|
||||
var input = orders.ToList();
|
||||
input.Shuffle();
|
||||
var tree = new SortedTree<Order, int>(comparer, id, pid, input);
|
||||
Assert.SequenceEqual(tree, orders);
|
||||
|
||||
var children_of_a = tree.EnumerateGroup(orders.First(o => o.name == 'A'));
|
||||
var also_children_of_a = tree.EnumerateGroup(orders.First(o => o.name == 'C'));
|
||||
Assert.AreEqual(3, children_of_a.Count());
|
||||
Assert.AreEqual(3, also_children_of_a.Count());
|
||||
Assert.SequenceEqual(children_of_a, also_children_of_a);
|
||||
|
||||
var children_of_d = tree.EnumerateGroup(orders.First(o => o.name == 'F'));
|
||||
var also_children_of_d = tree.EnumerateGroup(orders.First(o => o.name == 'E'));
|
||||
Assert.AreEqual(children_of_d.First().name, 'D');
|
||||
Assert.AreEqual(also_children_of_d.First().name, 'D');
|
||||
Assert.SequenceEqual(children_of_d, also_children_of_d);
|
||||
Assert.SequenceEqual(children_of_d.Select(o => o.name), new [] { 'D', 'E', 'F' });
|
||||
|
||||
var children_of_g = tree.EnumerateGroup(orders.First(o => o.name == 'G'));
|
||||
Assert.SequenceEqual(children_of_g.Select(o => o.name), new [] { 'G' });
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ using System.Reactive.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Linq;
|
||||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Subjects;
|
||||
|
||||
namespace mbts.api.Test {
|
||||
[TestGroup]
|
||||
@@ -104,6 +105,121 @@ namespace mbts.api.Test {
|
||||
|
||||
}
|
||||
|
||||
void validate_filter(IObservable<RawUpdate<Item>> raw, IObservable<Update<Item, int>> filtered, Func<Item, bool> pred)
|
||||
{
|
||||
var total = raw.ToEnumerable().Count();
|
||||
|
||||
// Validate that update is right at each point
|
||||
for (var count = 1; count < total; ++count)
|
||||
{
|
||||
var orig = raw.Take(count).reconstitute(id).ToEnumerable().Last();
|
||||
var next = filtered.Take(count).ToEnumerable().Last();
|
||||
Assert.That(next.all.All(pred));
|
||||
Assert.That(next.added.All(pred));
|
||||
Assert.That(next.removed.All(pred));
|
||||
Assert.That(next.changed.All(pred));
|
||||
}
|
||||
|
||||
// Do it again with TableUpdate
|
||||
for (var count = 1; count < total; ++count)
|
||||
{
|
||||
var orig = raw.Take(count).reconstitute(id).Table().ToEnumerable().Last();
|
||||
var next = filtered.Take(count).ToEnumerable().Last();
|
||||
Assert.That(next.added.All(pred));
|
||||
Assert.That(next.removed.All(pred));
|
||||
Assert.That(next.changed.All(pred));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void validate_filter_sub(IObservable<RawUpdate<Item>> raw, IObservable<Update<Item, int>> filtered, BehaviorSubject<Func<Item, bool>> pred)
|
||||
{
|
||||
raw.reconstitute(x => x.id).CombineLatest(filtered, (r, f) => Tuple.Create(r, f)).Subscribe(tup =>
|
||||
{
|
||||
Assert.That(tup.Item2.all.All(pred.Value));
|
||||
Assert.That(tup.Item2.added.All(pred.Value));
|
||||
Assert.That(!tup.Item2.removed.Any(pred.Value));
|
||||
Assert.That(tup.Item2.changed.All(pred.Value));
|
||||
|
||||
Dictionary<int, Item> rawd = tup.Item1.added.ToDictionary(x => x.id);
|
||||
Dictionary<int, Item> fd = tup.Item2.added.ToDictionary(x => x.id);
|
||||
foreach (var kv in rawd)
|
||||
{
|
||||
if(pred.Value(kv.Value))
|
||||
Assert.That(fd.ContainsKey(kv.Key));
|
||||
else
|
||||
Assert.That(!fd.ContainsKey(kv.Key));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[Test]
|
||||
public void test_filter()
|
||||
{
|
||||
var raw = Observable.Create<RawUpdate<Item>>(observer =>
|
||||
{
|
||||
RawUpdate<Item> rup = new RawUpdate<Item>();
|
||||
for (int x = 0; x < 50; ++x)
|
||||
{
|
||||
rup.Clear();
|
||||
var item = _item_pool[x];
|
||||
rup.added_items.Add(item);
|
||||
rup.added_keys.Add(item.id);
|
||||
observer.OnNext(rup);
|
||||
}
|
||||
var rand = new Random();
|
||||
for (int r = 0; r < rand.Next(4, 30); r += rand.Next(1, 2))
|
||||
{
|
||||
rup.Clear();
|
||||
rup.removed_keys.Add(_item_pool[r].id);
|
||||
observer.OnNext(rup);
|
||||
}
|
||||
rup.Clear();
|
||||
for (int c = 31; c < rand.Next(32, 50); c += rand.Next(1, 2))
|
||||
{
|
||||
var ch = new Item(_item_pool[c].id, _item_pool[c].name + "_change");
|
||||
rup.changed_items.Add(ch);
|
||||
rup.changed_keys.Add(ch.id);
|
||||
}
|
||||
observer.OnNext(rup);
|
||||
|
||||
observer.OnCompleted();
|
||||
return Disposable.empty;
|
||||
});
|
||||
|
||||
Func<Item, bool> evens = (x => x.id % 2 == 0);
|
||||
Func<Item, bool> odds = (x => x.id % 2 == 1);
|
||||
var predeven = Observable.Create<Func<Item, bool>>(observer =>
|
||||
{
|
||||
observer.OnNext(evens);
|
||||
observer.OnCompleted();
|
||||
return Disposable.empty;
|
||||
});
|
||||
|
||||
var filtered = raw.reconstitute(x=> x.id).Filter(predeven);
|
||||
validate_filter(raw, filtered, evens);
|
||||
|
||||
var predodd = Observable.Create<Func<Item, bool>>(observer =>
|
||||
{
|
||||
observer.OnNext(odds);
|
||||
observer.OnCompleted();
|
||||
return Disposable.empty;
|
||||
});
|
||||
|
||||
filtered = raw.reconstitute(x => x.id).Filter(predodd);
|
||||
validate_filter(raw, filtered, odds);
|
||||
|
||||
|
||||
|
||||
var sub= new BehaviorSubject<Func<Item, bool>>(evens);
|
||||
filtered = raw.reconstitute(x => x.id).Filter(sub);
|
||||
validate_filter_sub(raw, filtered, sub);
|
||||
|
||||
sub.OnNext(odds);
|
||||
sub.OnCompleted();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task test_add() {
|
||||
// Add 1 item 10x
|
||||
|
||||
@@ -8,6 +8,17 @@ Logging {
|
||||
}
|
||||
}
|
||||
|
||||
Logging {
|
||||
rule: "System.platform == 'windows'"
|
||||
value {
|
||||
console { default_level: "Error" }
|
||||
syslog { default_level: "Info" }
|
||||
file { default_level: "Info" }
|
||||
initial_queue_size: 1024
|
||||
max_queue_growth: 100
|
||||
}
|
||||
}
|
||||
|
||||
Logging
|
||||
{
|
||||
value
|
||||
|
||||
@@ -18,7 +18,7 @@ class FIXEncodingStream
|
||||
{
|
||||
public:
|
||||
FIXEncodingStream(config::JetFIX::Connection const &cfg,
|
||||
uint32_t initial_capacity = 100*1024*10)
|
||||
uint32_t initial_capacity = 1024*10)
|
||||
:
|
||||
_msg_header_template (),
|
||||
_header_template_size (),
|
||||
|
||||
@@ -30,7 +30,7 @@ public:
|
||||
_owns_buffer (false)
|
||||
{}
|
||||
|
||||
explicit FIXFragment(uint32_t initial_capacity=100*1024)
|
||||
explicit FIXFragment(uint32_t initial_capacity=100)
|
||||
:
|
||||
_buffer (new FIXBuffer (initial_capacity)),
|
||||
_checksum (0),
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
This test verifies that users can only see trades that are in accounts for which they're permissioned
|
||||
|
||||
1. Start a client and the admin gui
|
||||
|
||||
2. For your user, add permissions to see all accounts in the system in the admin gui
|
||||
|
||||
3. Enter some orders in the client for those accounts (you may have to add limits, etc), make some orders fill, cancel some orders
|
||||
|
||||
4. Now close the client, and in the admin UI pick another user and remove trading access to the account you just traded into. Start the client again with the user you just de-permissioned. You should not be able to see any of the trades you just made
|
||||
|
||||
5. Next re-permission your current user for the accounts with trades in them and the should show up in the UI.
|
||||
|
||||
6. Start another client with -multiple with the original user and make sure you can still see all trades.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
pushd %~dp0\..\src\client\sparkcmd
|
||||
dotnet watch run test %1 -t
|
||||
popd
|
||||
Reference in new issue
Block a user