Compare commits

...
Author SHA1 Message Date
Michael Harding 189157b1c7 initialize_for_test changes (thanks eric) 2019-06-13 14:50:24 -04:00
Michael Harding 64fd8addb2 Update Lean qty factor shell script 2019-06-13 09:38:11 -04:00
Michael Harding 4490ac303e SPARK-2438: Lean Qty test 2019-06-12 15:41:16 -04:00
Michael Harding 666bd07411 Merge branch 'master' into spreader_lean_qty 2019-06-10 12:56:44 -04:00
Pedro Pinto 0ff4624778 Updated release notes. 2019-06-10 11:05:59 -04:00
Taha Afzal ab04b4afab Merged in SPARK-2505_qho_ib_1 (pull request #254)
RTM
2019-06-10 15:02:57 +00:00
Taha Afzal ac226ddd3b Merged in SPARK-2488-LeanAlgoBug (pull request #255)
RTM
2019-06-10 14:36:18 +00:00
Pedro Pinto 0223e43aa2 Updated release notes. 2019-06-10 10:20:51 -04:00
John Colvin 73e5cfdada Merged in SPARK-2481-fills-filter-fix (pull request #249)
SPARK-2481 fills filter fix

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-10 14:19:31 +00:00
Eric Harding 33b4b88af1 For some strange reason the gregorian calender does not start counting days at 0. Maybe 0 hadn't been invented yet. 2019-06-10 10:12:44 -04:00
Pedro Pinto 12f4b4b25b Merged with master 2019-06-10 08:49:56 -04:00
Taha Afzal 96cacb8bb9 qho's were reporting their cancelled state twice, which was causing the spreader to cancel it 2019-06-09 21:16:41 -04:00
John Colvin 300a1ed17f SPARK-2481 filtering in fills window is badly broken (2nd take) 2019-06-07 23:06:14 -05:00
John Colvin 1a0836ca6d RXCollection.connect - always assign local table in callback 2019-06-07 20:41:04 -05:00
Jake Norbut 88908543e1 Merged in SPARK-2165 (pull request #244)
RTM - Ladder Settings - hide net change group setting for synthetic ladders

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-06-07 21:26:37 +00:00
Pedro Pinto 80c9b6a534 Merged with master 2019-06-07 17:26:05 -04:00
Pedro Pinto f6282c0676 Updated release notes 2019-06-07 17:20:17 -04:00
Pedro Pinto 02fe83f20b Merge branch 'master' into SPARK-2165 2019-06-07 17:15:18 -04:00
John Colvin 84d666c29c Merged in SPARK-2476-OB-KeyNotFoundException (pull request #248)
RTM: SPARK-2476 OB _get_legs KeyNotFoundException

Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-06-07 21:14:14 +00:00
Pedro Pinto 147e43b464 Merged with master 2019-06-07 16:58:35 -04:00
Pedro Pinto b909a6062e Updated release notes 2019-06-07 15:58:32 -04:00
Pedro Pinto 421b3af6a7 Merge branch 'master' into SPARK-2481-fills-filter-fix 2019-06-07 15:54:05 -04:00
Pedro Pinto 46a340d493 Merged in fix-fill-accounting (pull request #251)
RTM. Fix fill accounting
2019-06-07 19:52:02 +00:00
Pedro Pinto 3cc7d76a1b Merged with master 2019-06-07 15:49:10 -04:00
Jake Norbut 5123b928f2 Merged in SPARK-2480 (pull request #247)
RTM - SPARK-2480 - fixing Composite order type showing in root algo

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-06-07 19:40:43 +00:00
Pedro Pinto c2631e3257 Merged with master 2019-06-07 15:16:34 -04:00
Pedro Pinto 685181ae28 Merge branch 'fix-fill-accounting' of bitbucket.org:bluetradesys/spark into fix-fill-accounting 2019-06-07 13:50:58 -04:00
Pedro Pinto 929492c2f0 Made Limiter more robust for orders with undiscovered securities. 2019-06-07 13:50:44 -04:00
Pedro Pinto be023ff6cb Made Limiter more robust for orders with undiscovered securities. 2019-06-07 13:21:06 -04:00
Taha Afzal a4d63a1421 this should fix the confusion 2019-06-06 19:13:46 -04:00
Michael Harding 2fd8c39888 Merge branch 'spreader_lean_qty' of bitbucket.org:bluetradesys/spark into spreader_lean_qty 2019-06-06 16:01:56 -04:00
Michael Harding eb97fd045a SPARK-2438: spreader_lean_qty_factor test setup 2019-06-06 16:01:16 -04:00
Michael Harding a07bc152d1 SPARK-2438: init spreader_lean_qty_factor test 2019-06-06 16:01:16 -04:00
Michael Harding aef8e2dbb1 spell check manual tests 2019-06-06 16:01:16 -04:00
Pedro Pinto baac6a893d Re-directed SecurityDB's expiration to securities::expiration 2019-06-06 11:14:56 -04:00
Pedro Pinto df681ca209 Removed duplicate declaration. 2019-06-06 11:03:36 -04:00
Jake Norbut 21b34434ef Merged in SPARK-2464 (pull request #233)
RTM - SPARK-2464 - ladder cursor provides visual feedback when middle clicking over bid ask cols

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-06-05 21:57:43 +00:00
Pedro Pinto 0c17dbb55b Updated release notes. 2019-06-05 17:57:11 -04:00
Pedro Pinto fd6c6a3055 Merge branch 'master' into SPARK-2464 2019-06-05 17:52:29 -04:00
Pedro Pinto 259355d629 Updated release notes. 2019-06-05 17:51:22 -04:00
Michael Harding c6c2d3448d Merged in net_contracts_fix (pull request #250)
RTM. Fix lazyview updating net cotracts
2019-06-05 21:49:54 +00:00
Eric Harding f860649996 Merged in SPARK-2472-rerun (pull request #245)
RTM. SPARK-2472 rerun

* SPARK-2482 - End Jake's left handed discrimination

* SPARK-2472 add * to our order size

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-05 21:43:03 +00:00
Pedro Pinto 05c3c97713 Hid fills for expired securities. 2019-06-05 17:11:14 -04:00
Pedro Pinto f3be277484 Fixed limits double counting of spread leg fills 2019-06-05 17:10:16 -04:00
John Colvin 0ba311788c DIYGridRowsManager - expose fills/sort manager methods, so callers don't need to hang on to fills/sort manager references 2019-06-05 11:35:32 -05:00
John Colvin 341eda5962 FillsVM - rows does not need to be an OperableCollection (just a regular ObservableCollection) 2019-06-05 11:35:32 -05:00
John Colvin 3398bb5b86 FillsVM - remove unusued field '_active_comparers' 2019-06-05 11:35:28 -05:00
John Colvin dc7cacc126 get rid of hashset-vs-array optimization (doesn't actually help) 2019-06-05 10:59:43 -05:00
John Colvin 6d3d4e1f56 SPARK-2481 Filtering in fills window is badly broken 2019-06-04 18:57:36 -05:00
John Colvin 433b1abecd abstract out OrderBookFilterStates.build_predicate method for generic use 2019-06-04 18:55:41 -05:00
John Colvin 4544180c5f move 'FillsStartupData' into its own file, change typename to 'FillsSettings' 2019-06-04 18:54:43 -05:00
John Colvin 5722fcffc3 SPARK-2476 OB _get_legs KeyNotFoundException 2019-06-04 16:08:47 -05:00
Jake Norbut f1880615b1 SPARK-2480 - fixing a particular overload of the OrderSecurity constructor (root_* specific) 2019-06-04 14:08:46 -05:00
Jake Norbut e60f5678ec PR nits from chat 2019-06-04 10:22:10 -05:00
Pedro Pinto e9a70e51e4 Merged with master 2019-06-04 10:40:56 -04:00
Jake Norbut e98d18a5aa Merged in SPARK-2482 (pull request #243)
RTM - SPARK-2482 - Ladder middle click change - middle click recentering is back; alt + middle click now brings up mbo popup

Approved-by: Eric Harding <eric.harding@bluetradesys.com>
2019-06-04 14:35:16 +00:00
Pedro Pinto 513cc248b8 Updated release notes and added shift+middle click to trigger MBO
(Alt+Middle Click doesn't work very well on the linux rdesktop client)
2019-06-04 10:33:54 -04:00
Pedro Pinto c024095f75 Merged master 2019-06-04 10:02:19 -04:00
Pedro Pinto 3e5eb80a7c Updated release notes 2019-06-04 09:57:59 -04:00
Eric Harding 942dc1640e Merged in SPARK-2472 (pull request #242)
RTM: 🏭 SPARK-2472 - our orders are hard to identify

Approved-by: Michael Harding <michael.harding@bluetradesys.com>
Approved-by: Jake Norbut <jake.norbut@bluetradesys.com>
2019-06-04 13:17:29 +00:00
Pedro Pinto b5c3abb782 Merged from prod 2019-06-04 09:15:36 -04:00
Pedro Pinto 3478d1ad52 Updated release notes. 2019-06-04 09:14:10 -04:00
Pedro Pinto d54e9233d1 Improved comment. 2019-06-04 09:12:11 -04:00
Jake Norbut e94759cd89 Ladder Settings - hide net change group setting for synthetic ladders 2019-06-03 15:47:54 -05:00
Jake Norbut b58da4a20e SPARK-2482 - Ladder middle click change - middle click recentering is back; alt + middle click now brings up mbo popup 2019-06-03 12:43:44 -05:00
Jake Norbut 8426399a23 SPARK-2479 - bug introduced in 8dd0709; Contract Pickers launched via the Security Picker Daemon now close on cancel button being pressed 2019-06-03 12:11:17 -05:00
Eric Harding 9b2f41c805 SPARK-2472 our orders are hard to identify in mbo 2019-05-30 17:00:09 -04:00
Eric Harding 15f96dda3e Fix mbo command line diagnostic utility 2019-05-30 16:02:07 -04:00
Michael Harding a9d5c17d63 Merged master into spreader_lean_qty 2019-05-30 14:43:53 -04:00
Michael Harding 715b629778 Set use_onixs to false; for system tests 2019-05-30 12:04:21 -04:00
Eric Harding dd82d127ea Merged in shell_crash_empty_size (pull request #241)
RTM: Fix for shell able to send 0 size orders

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-05-29 21:37:27 +00:00
Pedro Pinto f97e22d6eb Updated release notes. 2019-05-29 17:31:34 -04:00
Eric Harding f9e04ee28c Fix for shell able to send 0 size orders 2019-05-29 17:31:24 -04:00
Pedro Pinto 1ce00ca13d Fixed simulator's handling of 0 sized quantities. 2019-05-29 17:27:08 -04:00
Jake Norbut 3b9c2f5d70 adding a try/catch around the leg lookup 2019-05-28 16:58:27 -05:00
Pedro Pinto 5cd2dfec18 Fixed PIQ in GLBX Icerberg, fixed handling of stop limit. 2019-05-28 16:26:16 -04:00
Jake Norbut 31b2ba9165 PR suggestion - if statement readability 2019-05-28 14:38:27 -05:00
Jake Norbut 2b1b1f1681 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SPARK-2464 2019-05-28 13:23:17 -05:00
Jake Norbut 7e75a71898 check root algo's can_expand to see if we should draw Cursor.No 2019-05-28 10:47:11 -05:00
Jake Norbut 7e47d7f8a5 Merge branch 'master' of bitbucket.org:bluetradesys/spark into SPARK-2464 2019-05-28 10:08:14 -05:00
Jake Norbut 90526a8bfd SPARK-2464 - ladder cursor provides visual feedback when middle clicking over bid ask cols 2019-05-21 14:18:58 -05:00
Michael Harding ebb2d5618c Merge branch 'master' into spreader_lean_qty 2019-05-15 13:35:33 -04:00
Michael Harding ab993423a6 Merge branch 'master' into spreader_lean_qty 2019-05-15 10:30:16 -04:00
Michael Harding 4138ab0730 Merged master into spreader_lean_qty 2019-05-14 09:55:20 -04:00
Michael Harding c0d097cb40 SPARK-2438: spreader_lean_qty_factor test setup 2019-05-13 13:26:53 -04:00
Michael Harding 533e688d1a SPARK-2438: init spreader_lean_qty_factor test 2019-05-13 13:09:15 -04:00
Michael Harding 36d40dec70 spell check manual tests 2019-05-13 12:59:49 -04:00
74 changed files with 1299 additions and 619 deletions

No files matched your search

+31
View File
@@ -4,6 +4,37 @@ weight = 3
sort_by = "weight"
insert_anchor_links = "right"
+++
## 2.2.0
### User Visible Changes
- Made our orders easier to spot in MBO display
- Changed middle-click on center column to recenter ladder and introduced Alt middle-click on center to bring up the MBO popup
- Fixed Net/Absolute radio button in risk admin UI
- Fixed double counting of spread legs in limits
- Removed expired fills from the system
- Improved handling of orders with undiscovered securities
- Added feedback to the ladder when the user attempts to middle click on bid/ask columns using an order that does not have parameters
- Fixed display of algo studio algo types in order book
- Hid net change setting in synthetic ladders
- Fixed a number of problems with filtering on the Fills form
- On the Lean algo, OnToward and Away are now calculated with respect to the opposite inside market at time of the order launch
- Iceberg Spreaders with queue holders are not erroneously cancelled after the first fill
## 2.1.4
### User Visible Changes
- Fixed handling of cancel button in security picker
## 2.1.3
### User Visible Changes
- Fixed a simulator bug in the handling of 0 sized orders
## 2.2.1
### User Visible Changes
- Fixed order book form exception when display a spreader order for whichs the legs were not present yet
## 2.1.1
### User Visible Changes
- Added PIQ to GLBX Iceberg orders
- Corrected handling of stop limit orders
## 2.1.0
### User Visible Changes
- Added support for the GLBX Iceberg order type
@@ -73,6 +73,7 @@ namespace bts {
window.Close();
message.complete(select_result(vm)).execute(_bus);
};
vm.cancel_action = () => window.Close();
window.move_to_mouse();
window.Show();
}
@@ -1,6 +1,7 @@
using bts;
using bts.api;
using bts.shell;
using bts.shell.fills;
using bts.spark;
using bts.utils;
using mbts.persist;
@@ -117,8 +118,8 @@ namespace mbts.spark {
// show fills
var show_fills_cmd = new DelegateCommand(() => {
var sc = _selected_security_criteria;
_bus.post(SparkTags.ShowFillsTag, new FillsWindowFactory.StartupData(
new FillsWindowFactory.FilterStates {
_bus.post(SparkTags.ShowFillsTag, new FillsSettings(
new FillsFilterStates {
security = sc.is_defined() ? sc : _state.security_criteria
}));
});
@@ -1,6 +1,7 @@
using bts;
using bts.api;
using bts.shell;
using bts.shell.fills;
using bts.spark;
using mbts.co2;
using mbts.persist;
@@ -92,8 +93,8 @@ namespace mbts.spark {
_initialize();
bool always_true<T>(T _) => true;
FillsWindowFactory.StartupData get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
new FillsWindowFactory.StartupData(new FillsWindowFactory.FilterStates {
FillsSettings get_fills_startup_data(IReadOnlyList<SecurityImmutable> secs) =>
new FillsSettings(new FillsFilterStates {
security = secs.Count > 0 ? new SecurityCriteria(secs) : _state.security_criteria
});
void update_criteria(SecurityCriteria c) {
@@ -321,9 +321,12 @@ namespace mbts.spark {
return string.Empty;
}
static IEnumerable<LegSpreaderParamsTuple> _get_legs(Security synthetic, ExecutionStrategyImmutable strategy) =>
synthetic.Synthetic.Legs
.Select(l => new LegSpreaderParamsTuple(l, strategy.LegParamsMap[l.Security.Id].Clone()));
static IEnumerable<LegSpreaderParamsTuple> _get_legs(Security synthetic, ExecutionStrategyImmutable strategy) {
foreach (var l in synthetic.Synthetic.Legs) {
if (strategy.LegParamsMap.TryGetValue(l.Security.Id, out var p))
yield return new LegSpreaderParamsTuple(l, p);
}
}
void _close_new_leg_popup() {
is_new_leg_popup_open = false;
@@ -2,6 +2,7 @@
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.shell.fills;
using bts.spark;
using bts.utils;
using mbts.collections;
@@ -18,7 +19,6 @@ using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
@@ -34,21 +34,19 @@ using OrderType = bts.api.orderman.CustomOrderDefinition.CustomOneofCase;
namespace mbts.spark {
class FillsVM : DisposableViewModelBase, IHandleGridSorting {
readonly OrdersModel _model;
readonly DIYGridSortManager<Columns> _sort_manager;
readonly IDIYGridFiltersManager<FillTuple, Columns, FilterStates> _filters_manager;
readonly DIYGridRowsManager<FillTuple, Columns, FillsFilterStates> _rows_manager;
readonly ObservableKeyedCollection<ColumnBase, Columns> _columns = new ObservableKeyedCollection<ColumnBase, Columns>(_col_key);
List<(ColumnBase col, Columns key, ListSortDirection direction)> _active_comparers = new List<(ColumnBase col, Columns key, ListSortDirection direction)>();
IWindowSettingsManager<GridSettings> _settings_manager;
int _total_row_count;
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IWindowSettingsManager<GridSettings> settings_manager)
: this(bus, model, state_obs, _get_observable(bus, model, state_obs), settings_manager) { }
public FillsVM(IBus bus, OrdersModel model, IObservable<StartupData> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
public FillsVM(IBus bus, OrdersModel model, IObservable<FillsSettings> state_obs, IObservable<TableUpdate<FillTuple, string>> fills_source, IWindowSettingsManager<GridSettings> settings_manager) {
_model = model;
_settings_manager = settings_manager;
/// Columns
var col_tups = new ObservableKeyedCollection<DIYGridColumnData<FillTuple, Columns, FilterStates>, Columns>(c => c.key);
var col_tups = new ObservableKeyedCollection<DIYGridColumnData<FillTuple, Columns, FillsFilterStates>, Columns>(c => c.key);
// security
add_col(Columns.Security, DefaultColumnHeaders.Security, r => r.security.ToString(),
DiscreteValuesFilterVM.Create<FillTuple, SecurityImmutable, SecurityCriteria>(
@@ -85,25 +83,15 @@ namespace mbts.spark {
add_col(Columns.FillKey, DefaultColumnHeaders.FillKey, r => r.fill_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.FillKey), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.FillKey);
add_col(Columns.OrderKey, DefaultColumnHeaders.OrderKey, r => r.order_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.OrderKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.OrderKey);
add_col(Columns.SecurityKey, DefaultColumnHeaders.SecurityKey, r => r.security_key.ToString(), new RegexFilterVM<FillTuple>(r => r.fill.SecurityKey.ToString()), DataGridFilterKnownTemplates.Regex, DefaultColumnWidths.SecurityKey);
/// Rows
rows = new OperableCollection<Row, FillTuple, string>(
(ft, fk) => new Row(ft, model),
ft => ft.fill.FillKey,
new ComparerBuilder<FillTuple>(new[] {
_get_column_comparer(Columns.FillTime),
_get_column_comparer(Columns.FillKey)
}).to_comparer());
// row sort/filter managers
_sort_manager = new DIYGridSortManager<Columns>(col_tups.WrapSelector(t => (t.col, t.key)), Columns.FillKey);
_filters_manager = DIYGridFiltersManager.create(col_tups);
var rows_manager = new DIYGridRowsManager<FillTuple, Columns, FilterStates>(_filters_manager, _sort_manager, _get_column_comparer, FillsWindow.window_type_name);
// rows
_rows_manager = DIYGridRowsManager.create(FillsWindow.window_type_name, Columns.FillKey, col_tups, col_tups.WrapSelector(t => (t.col, t.key)), _get_column_comparer, fs => fs.build_predicate());
// grid settings
grid_settings_manager = new SimpleGridSettingsManager<Row>(columns, new Dictionary<string, GridColumnSettingsFeatureFlags> { }, rows);
// this will initialize the immutable properties that correspond to the col_settings_flags above (e.g. Bid/Ask font_color_override, etc.)
if (_settings_manager != null)
grid_settings_manager.load_from(_settings_manager.current_settings());
var filter_src = Observable.FromEvent<Action<Func<FillTuple, bool>>, Func<FillTuple, bool>>(cb => rows_manager.filter_changed += cb, cb => rows_manager.filter_changed -= cb);
var comp_src = Observable.FromEvent<Action<IComparer<FillTuple>>, IComparer<FillTuple>>(cb => rows_manager.comparer_changed += cb, cb => rows_manager.comparer_changed -= cb);
var filter_src = Observable.FromEvent<Action<Func<FillTuple, bool>>, Func<FillTuple, bool>>(cb => _rows_manager.filter_changed += cb, cb => _rows_manager.filter_changed -= cb);
var comp_src = Observable.FromEvent<Action<IComparer<FillTuple>>, IComparer<FillTuple>>(cb => _rows_manager.comparer_changed += cb, cb => _rows_manager.comparer_changed -= cb);
TableUpdate<FillTuple, string> last_unfiltered_fills = TableUpdate<FillTuple, string>.mk_empty(FillTuple.key_fn);
var fills_buffer = Observable.Create<TableUpdate<FillTuple, string>>(o => {
// this is what .Cache() does, but is a little cheaper, since we know subscriptions and callbacks occur on the same thread (i.e., on this Dispatcher).
@@ -125,13 +113,13 @@ namespace mbts.spark {
// Note: settings_manager in turn notifies grid_settings_manager of this update
_settings_manager.instance_settings = s.grid;
}
var fs = s.filters ?? new FilterStates {
var fs = s.filters ?? new FillsFilterStates {
fill_time = new TimeFilterTuple(
TimeFilterMode.CMEOpen,
TimeTextTuple.FromDateTime(DateTime.Today),
TimeTextTuple.FromDateTime(DateTime.Today.AddDays(1)))
};
rows_manager.on_settings(fs, s.sorting_criterion);
_rows_manager.on_settings(fs, s.sorting_criterion);
}),
settings_manager == null ? Disposable.empty : settings_manager.subscribe_current().OnDispatcher().Subscribe(grid_settings_manager),
(fills_source as IConnectableObservable<TableUpdate<FillTuple, string>>)?.Connect() ?? Disposable.empty
@@ -144,7 +132,7 @@ namespace mbts.spark {
Width = width
}, filter);
void add_col_expl<TFilter>(Columns col_key, ColumnBase col, IDataGridColumnPersistentFilter<FillTuple, TFilter> filter) =>
col_tups.Add(col_key, DIYGridColumnData.create<FillTuple, Columns, FilterStates, TFilter>(col, filter, _get_column_comparer(col_key)));
col_tups.Add(col_key, DIYGridColumnData.create<FillTuple, Columns, FillsFilterStates, TFilter>(col, filter, _get_column_comparer(col_key)));
// discrete-values-filter factories
IDataGridColumnPersistentFilter<FillTuple, T[]> dv_filter<T>(IReadOnlyCollection<T> items, Func<FillTuple, T> get_row_val) => DiscreteValuesFilterVM.Create<FillTuple, T>(items, get_row_val);
IDataGridColumnPersistentFilter<FillTuple, T[]> dv_filter_rx<T>(IObservable<IEnumerable<T>> items, Func<FillTuple, T> get_row_val) => DiscreteValuesFilterVM.Create<FillTuple, T>(items, get_row_val);
@@ -166,26 +154,26 @@ namespace mbts.spark {
return grid_settings_manager.create();
}
public FillsFilterStates get_filters() =>
_rows_manager.get_filters();
public IEnumerable<SortingCriteria> get_sorting_criterion() =>
_sort_manager.create();
_rows_manager.get_sorting_criterion();
public void on_sorting(object sender, DIYGridSortingEventArgs e) =>
_sort_manager.on_sorting(e);
_rows_manager.on_sorting(e);
static Columns _col_key(ColumnBase column) =>
((IKeyedColumn<Columns>)column).key;
public FilterStates get_filters() =>
_filters_manager.create();
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, StartupData state) {
static IObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, FillsSettings state) {
if (!state.orders.is_null_or_empty()) {
var orders_hash = new HashSet<uint>(state.orders);
return model.on_fill_tuples.Filter(f => orders_hash.Contains(f.fill.OrderKey));
} else return model.on_fill_tuples;
}
static IConnectableObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<StartupData> state) =>
static IConnectableObservable<TableUpdate<FillTuple, string>> _get_observable(IBus bus, OrdersModel model, IObservable<FillsSettings> state) =>
state.SelectManyInSeries(s => _get_observable(bus, model, s));
static IComparer<FillTuple> _get_column_comparer(Columns col) {
@@ -1,6 +1,7 @@
using bts;
using bts.api;
using bts.shell;
using bts.shell.fills;
using bts.spark;
using mbts.persist;
using mbts.ui;
@@ -13,21 +14,20 @@ using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using StartupData = bts.shell.FillsWindowFactory.StartupData;
namespace mbts.spark {
class FillsWindow : SparkWindow<StartupData>, ISharedWindow {
class FillsWindow : SparkWindow<FillsSettings>, ISharedWindow {
public const string window_type_name = "Fills";
readonly AllowOnce _init_guard = new AllowOnce("Fills");
readonly OrdersModel _orders_model;
readonly IWindowSettingsManager<GridSettings> _settings_manager;
FillsVM _vm;
FillsView _view;
StartupData _state;
FillsSettings _state;
IReadOnlyList<SparkCommand> _commands;
IDisposable _subscriptions;
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, StartupData state, WindowSettingsManager<GridSettings> settings_manager)
public FillsWindow(FillsWindowFactory factory, IBus bus, OrdersModel orders_model, FillsSettings state, WindowSettingsManager<GridSettings> settings_manager)
: base("Fills", bus, factory, SparkCommands.ShowFills().icon) {
_orders_model = orders_model;
_state = state;
@@ -35,15 +35,15 @@ namespace mbts.spark {
thread_id = config.Configuration.get<bts.config.MultiThreadedWindows>().fills ? "Fills" : null;
}
event Action<FillsWindowFactory.StartupData> _state_changed;
event Action<FillsSettings> _state_changed;
public override string window_type => window_type_name;
public override UIElement Content => _view;
public override IEnumerable<SparkCommand> Commands => _commands;
public override StartupData SaveState => _state = new StartupData(_state.orders, _vm.get_sorting_criterion().ToList(), _vm.get_filters(), _vm.get_grid_save_state());
public override FillsSettings SaveState => _state = new FillsSettings(_state.orders, _vm.get_sorting_criterion().ToList(), _vm.get_filters(), _vm.get_grid_save_state());
public override double MinWidth =>
StartupData.DefaultGridSettings.columns.Sum(gcs => gcs.width)
FillsSettings.DefaultGridSettings.columns.Sum(gcs => gcs.width)
+ SPARK_MENU_COLLAPSED_WIDTH
+ LAUNCHER_BUTTON_BORDER_WIDTH
+ EXTRA_FOR_NO_HORIZONTAL_SCROLLBAR_WIDTH;
@@ -53,7 +53,7 @@ namespace mbts.spark {
public void thread_init() {
_init_guard.check_access();
Log.notnull(_state);
var state_src = Observable.FromEvent<Action<FillsWindowFactory.StartupData>, FillsWindowFactory.StartupData>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
var state_src = Observable.FromEvent<Action<FillsSettings>, FillsSettings>(cb => _state_changed += cb, cb => _state_changed -= cb).StartWith(_state);
_vm = new FillsVM(_bus, _orders_model, state_src, _settings_manager);
_view = new FillsView { DataContext = _vm };
@@ -79,7 +79,7 @@ namespace mbts.spark {
};
_subscriptions = Disposable.Create(() =>
_state_changed?.Invoke(
_state = new StartupData(_state.orders,
_state = new FillsSettings(_state.orders,
_vm.get_sorting_criterion().ToList(),
_vm.get_filters(),
_vm.grid_settings_manager.create()
@@ -1,5 +1,6 @@
using bts.api;
using bts.api.orderman;
using bts.shell.fills;
using mbts.persist;
using mbts.spark;
using mbts.spark.filters;
@@ -16,7 +17,7 @@ using static mbts.ui.controls.DIYGrid;
using static mbts.ui.spark.FillsView;
namespace bts.shell {
class FillsWindowFactory : ShellWindowFactory<FillsWindowFactory.StartupData> {
class FillsWindowFactory : ShellWindowFactory<FillsSettings> {
readonly IBus _bus;
readonly OrdersModel _orders_model;
readonly UserSettingsHub _settings_hub;
@@ -52,13 +53,13 @@ namespace bts.shell {
public static string col_id(Columns col) => col.ToString();
public override Task<IShellWindow> load(StartupData state) =>
public override Task<IShellWindow> load(FillsSettings state) =>
Task.FromResult(_create(state));
public async void show(StartupData state) =>
public async void show(FillsSettings state) =>
_bus.post(SparkTags.ShowWindow, await load(state));
IShellWindow _create(StartupData state) {
IShellWindow _create(FillsSettings state) {
GridSettingsHelper.normalize_sorting_criterion(state.sorting_criterion, _sort_member_to_id);
var settings_manager = new WindowSettingsManager<GridSettings>(_settings_hub,
state.grid,
@@ -66,162 +67,5 @@ namespace bts.shell {
set: (us, gs) => us.with(provided_fills_gridsettings: gs));
return new FillsWindow(this, _bus, _orders_model, state, settings_manager);
}
[ProtoContract]
public class StartupData {
public const int Version = 1;
[ProtoMember(1)]
public uint[] orders;
[ProtoMember(2)]
List<SortingCriteria> _sorting_criteria;
[ProtoMember(3)]
public FilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
public StartupData() { }
public StartupData(uint[] orders) : this(orders, null, null) { }
public StartupData(FilterStates filters) : this(null, null, filters) { }
public StartupData(uint[] orders, List<SortingCriteria> sorting_criteria, FilterStates filters, GridSettings grid = default) {
this.orders = orders;
this._sorting_criteria = sorting_criteria;
this.filters = filters;
this.grid = grid;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public static GridSettings MiniGridSettings => GridSettings.Default.with(DefaultColumns.Mini.all, DefaultColumns.Mini.sorting_criterion);
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
public static StartupData Default() =>
new StartupData(null, null, new FilterStates(), null);
static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings fill_time => new GridColumnSettings(col_id(Columns.FillTime), DefaultColumnHeaders.FillTime, width: DefaultColumnWidths.Time);
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, width: DefaultColumnWidths.User);
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
public static GridColumnSettings time_in_force => new GridColumnSettings(col_id(Columns.TimeInForce), DefaultColumnHeaders.TimeInForce, width: DefaultColumnWidths.TimeInForce);
public static GridColumnSettings order_type => new GridColumnSettings(col_id(Columns.OrderType), DefaultColumnHeaders.OrderType, width: DefaultColumnWidths.OrderType);
public static GridColumnSettings side => new GridColumnSettings(col_id(Columns.Side), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Side);
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings fill_kind => new GridColumnSettings(col_id(Columns.FillKind), DefaultColumnHeaders.FillKind, width: DefaultColumnWidths.FillKind);
public static GridColumnSettings fill_key => new GridColumnSettings(col_id(Columns.FillKey), DefaultColumnHeaders.FillKey, width: DefaultColumnWidths.FillKey);
public static GridColumnSettings order_key => new GridColumnSettings(col_id(Columns.OrderKey), DefaultColumnHeaders.OrderKey, width: DefaultColumnWidths.OrderKey);
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
yield return new SortingCriteria(fill_time.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return fill_time;
yield return user;
yield return account;
yield return exchange;
yield return time_in_force;
yield return order_type;
yield return side;
yield return quantity;
yield return price;
yield return fill_kind;
yield return fill_key;
yield return order_key;
yield return security_key;
}
}
public static class Mini {
static readonly IReadOnlyDictionary<Columns, (bool is_visible, double? width_override)> _mini_cols_data =
new Dictionary<Columns, (bool is_visible, double? width_override)> {
{ Columns.Security, (true, null) },
{ Columns.FillTime, (true, ShortColumnWidths.Time) },
{ Columns.OrderType, (true, ShortColumnWidths.OrderType) },
{ Columns.Side, (true, ShortColumnWidths.Side) },
{ Columns.Size, (true, ShortColumnWidths.Size) },
{ Columns.Price, (true, ShortColumnWidths.Price) },
};
public static IEnumerable<GridColumnSettings> all => DefaultColumns.all.Select(_convert);
public static IEnumerable<SortingCriteria> sorting_criterion => default_sorting_criterion.Where(c => _try_get_data(c.col_name, out (bool is_visible, double? _) d) && d.is_visible);
static bool _try_get_data(string id_str, out (bool is_visible, double? width_override) data) {
if (Enum.TryParse(id_str, out Columns k) && _mini_cols_data.TryGetValue(k, out data))
return true;
data = default;
return false;
}
static GridColumnSettings _convert(GridColumnSettings default_col) =>
_try_get_data(default_col.id, out (bool is_visible, double? width_override) d) ?
default_col.with(is_visible: d.is_visible, width: d.width_override) :
default_col.with(is_visible: false);
}
}
}
[ProtoContract]
public class FilterStates {
[ProtoMember(1)]
[DIYGridColumnID(nameof(Columns.FillTime))]
public TimeFilterTuple fill_time { get; set; }
[ProtoMember(2)]
[DIYGridColumnID(nameof(Columns.TimeInForce))]
public TimeInForce[] time_in_force { get; set; }
[ProtoMember(3)]
[DIYGridColumnID(nameof(Columns.OrderType))]
public CustomOrderDefinition.CustomOneofCase[] order_type { get; set; }
[ProtoMember(4)]
[DIYGridColumnID(nameof(Columns.Account))]
public string[] account { get; set; }
[ProtoMember(5)]
[DIYGridColumnID(nameof(Columns.User))]
public string[] user { get; set; }
[ProtoMember(6)]
[DIYGridColumnID(nameof(Columns.Exchange))]
public Venue[] exchange { get; set; }
[ProtoMember(7)]
[DIYGridColumnID(nameof(Columns.Security))]
public SecurityCriteria security { get; set; }
[ProtoMember(8)]
[DIYGridColumnID(nameof(Columns.Side))]
public Side[] side { get; set; }
[ProtoMember(9)]
[DIYGridColumnID(nameof(Columns.Price))]
public DoubleMinMaxState price { get; set; }
[ProtoMember(10)]
[DIYGridColumnID(nameof(Columns.Size))]
public DoubleMinMaxState size { get; set; }
[ProtoMember(11)]
[DIYGridColumnID(nameof(Columns.FillKey))]
public string fill_key { get; set; }
[ProtoMember(12)]
[DIYGridColumnID(nameof(Columns.OrderKey))]
public string order_key { get; set; }
[ProtoMember(13)]
[DIYGridColumnID(nameof(Columns.SecurityKey))]
public string security_key { get; set; }
[ProtoMember(14)]
[DIYGridColumnID(nameof(Columns.FillKind))]
public Fill.Types.FillKind[] fill_kind { get; set; }
}
}
}
@@ -0,0 +1,95 @@
using bts.api;
using bts.api.orderman;
using mbts.spark;
using mbts.spark.filters;
using mbts.ui;
using mbts.ui.controls;
using ProtoBuf;
using System;
using Columns = mbts.ui.spark.FillsView.Columns;
namespace bts.shell.fills {
[ProtoContract]
class FillsFilterStates {
[ProtoMember(1)]
[DIYGridColumnID(nameof(Columns.FillTime))]
public TimeFilterTuple fill_time { get; set; }
[ProtoMember(2)]
[DIYGridColumnID(nameof(Columns.TimeInForce))]
public TimeInForce[] time_in_force { get; set; }
[ProtoMember(3)]
[DIYGridColumnID(nameof(Columns.OrderType))]
public CustomOrderDefinition.CustomOneofCase[] order_type { get; set; }
[ProtoMember(4)]
[DIYGridColumnID(nameof(Columns.Account))]
public string[] account { get; set; }
[ProtoMember(5)]
[DIYGridColumnID(nameof(Columns.User))]
public string[] user { get; set; }
[ProtoMember(6)]
[DIYGridColumnID(nameof(Columns.Exchange))]
public Venue[] exchange { get; set; }
[ProtoMember(7)]
[DIYGridColumnID(nameof(Columns.Security))]
public SecurityCriteria security { get; set; }
[ProtoMember(8)]
[DIYGridColumnID(nameof(Columns.Side))]
public Side[] side { get; set; }
[ProtoMember(9)]
[DIYGridColumnID(nameof(Columns.Price))]
public DoubleMinMaxState price { get; set; }
[ProtoMember(10)]
[DIYGridColumnID(nameof(Columns.Size))]
public DoubleMinMaxState size { get; set; }
[ProtoMember(11)]
[DIYGridColumnID(nameof(Columns.FillKey))]
public string fill_key { get; set; }
[ProtoMember(12)]
[DIYGridColumnID(nameof(Columns.OrderKey))]
public string order_key { get; set; }
[ProtoMember(13)]
[DIYGridColumnID(nameof(Columns.SecurityKey))]
public string security_key { get; set; }
[ProtoMember(14)]
[DIYGridColumnID(nameof(Columns.FillKind))]
public Fill.Types.FillKind[] fill_kind { get; set; }
}
static class FillsFilterStatesExtensions {
public static Func<FillTuple, bool> build_predicate(this FillsFilterStates fs) {
var pb = new ColumnPredicateBuilder<FillTuple>();
// fill time
pb.add_time_filter(fs.fill_time, ft => ft.fill_time());
// security
pb.add_security_criteria(fs.security, ft => ft.security);
// regex
pb.add_regex(fs.fill_key, ft => ft.fill.FillKey);
pb.add_regex(fs.order_key, ft => ft.order_key().ToString());
// discrete values
pb.add_discrete_values(fs.user, ft => ft.user());
pb.add_discrete_values(fs.account, ft => ft.account);
pb.add_discrete_values(fs.exchange, ft => ft.exchange());
pb.add_discrete_values(fs.time_in_force, ft => ft.time_in_force());
pb.add_discrete_values(fs.order_type, ft => ft.order_type());
pb.add_discrete_values(fs.side, ft => ft.fill.Side);
pb.add_discrete_values(fs.fill_kind, ft => ft.fill.FillKind);
// numeric
pb.add_numeric(fs.size, ft => ft.fill.LastShares);
pb.add_numeric(fs.price, ft => ft.fill.LastPrice);
return pb.to_predicate();
}
}
}
@@ -0,0 +1,109 @@
using mbts.ui;
using mbts.ui.settings;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using static bts.shell.FillsWindowFactory;
using static mbts.ui.controls.DIYGrid;
using Columns = mbts.ui.spark.FillsView.Columns;
namespace bts.shell.fills {
[ProtoContract]
class FillsSettings {
public const int Version = 1;
[ProtoMember(1)]
public uint[] orders;
[ProtoMember(2)]
List<SortingCriteria> _sorting_criteria;
[ProtoMember(3)]
public FillsFilterStates filters;
[ProtoMember(4)]
public GridSettings grid;
public FillsSettings() { }
public FillsSettings(uint[] orders) : this(orders, null, null) { }
public FillsSettings(FillsFilterStates filters) : this(null, null, filters) { }
public FillsSettings(uint[] orders, List<SortingCriteria> sorting_criteria, FillsFilterStates filters, GridSettings grid = default) {
this.orders = orders;
this._sorting_criteria = sorting_criteria;
this.filters = filters;
this.grid = grid;
}
public static GridSettings DefaultGridSettings => GridSettings.Default.with(DefaultColumns.all, DefaultColumns.default_sorting_criterion);
public static GridSettings MiniGridSettings => GridSettings.Default.with(DefaultColumns.Mini.all, DefaultColumns.Mini.sorting_criterion);
public IReadOnlyList<SortingCriteria> sorting_criterion => _sorting_criteria ?? (_sorting_criteria = DefaultColumns.default_sorting_criterion.ToList());
public static FillsSettings Default() =>
new FillsSettings(null, null, new FillsFilterStates(), null);
static class DefaultColumns {
public static GridColumnSettings security => new GridColumnSettings(col_id(Columns.Security), DefaultColumnHeaders.Security, width: DefaultColumnWidths.Security);
public static GridColumnSettings fill_time => new GridColumnSettings(col_id(Columns.FillTime), DefaultColumnHeaders.FillTime, width: DefaultColumnWidths.Time);
public static GridColumnSettings user => new GridColumnSettings(col_id(Columns.User), DefaultColumnHeaders.User, width: DefaultColumnWidths.User);
public static GridColumnSettings account => new GridColumnSettings(col_id(Columns.Account), DefaultColumnHeaders.Account, width: DefaultColumnWidths.Account);
public static GridColumnSettings exchange => new GridColumnSettings(col_id(Columns.Exchange), DefaultColumnHeaders.Exchange, width: DefaultColumnWidths.Exchange);
public static GridColumnSettings time_in_force => new GridColumnSettings(col_id(Columns.TimeInForce), DefaultColumnHeaders.TimeInForce, width: DefaultColumnWidths.TimeInForce);
public static GridColumnSettings order_type => new GridColumnSettings(col_id(Columns.OrderType), DefaultColumnHeaders.OrderType, width: DefaultColumnWidths.OrderType);
public static GridColumnSettings side => new GridColumnSettings(col_id(Columns.Side), DefaultColumnHeaders.Side, width: DefaultColumnWidths.Side);
public static GridColumnSettings quantity => new GridColumnSettings(col_id(Columns.Size), DefaultColumnHeaders.Size, width: DefaultColumnWidths.Size, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings price => new GridColumnSettings(col_id(Columns.Price), DefaultColumnHeaders.Price, width: DefaultColumnWidths.Price, horizontal_alignment: System.Windows.HorizontalAlignment.Right);
public static GridColumnSettings fill_kind => new GridColumnSettings(col_id(Columns.FillKind), DefaultColumnHeaders.FillKind, width: DefaultColumnWidths.FillKind);
public static GridColumnSettings fill_key => new GridColumnSettings(col_id(Columns.FillKey), DefaultColumnHeaders.FillKey, width: DefaultColumnWidths.FillKey);
public static GridColumnSettings order_key => new GridColumnSettings(col_id(Columns.OrderKey), DefaultColumnHeaders.OrderKey, width: DefaultColumnWidths.OrderKey);
public static GridColumnSettings security_key => new GridColumnSettings(col_id(Columns.SecurityKey), DefaultColumnHeaders.SecurityKey, is_visible: false, width: DefaultColumnWidths.SecurityKey);
public static IEnumerable<SortingCriteria> default_sorting_criterion {
get {
yield return new SortingCriteria(fill_time.id, false);
}
}
public static IEnumerable<GridColumnSettings> all {
get {
yield return security;
yield return fill_time;
yield return user;
yield return account;
yield return exchange;
yield return time_in_force;
yield return order_type;
yield return side;
yield return quantity;
yield return price;
yield return fill_kind;
yield return fill_key;
yield return order_key;
yield return security_key;
}
}
public static class Mini {
static readonly IReadOnlyDictionary<Columns, (bool is_visible, double? width_override)> _mini_cols_data =
new Dictionary<Columns, (bool is_visible, double? width_override)> {
{ Columns.Security, (true, null) },
{ Columns.FillTime, (true, ShortColumnWidths.Time) },
{ Columns.OrderType, (true, ShortColumnWidths.OrderType) },
{ Columns.Side, (true, ShortColumnWidths.Side) },
{ Columns.Size, (true, ShortColumnWidths.Size) },
{ Columns.Price, (true, ShortColumnWidths.Price) },
};
public static IEnumerable<GridColumnSettings> all => DefaultColumns.all.Select(_convert);
public static IEnumerable<SortingCriteria> sorting_criterion => default_sorting_criterion.Where(c => _try_get_data(c.col_name, out (bool is_visible, double? _) d) && d.is_visible);
static bool _try_get_data(string id_str, out (bool is_visible, double? width_override) data) {
if (Enum.TryParse(id_str, out Columns k) && _mini_cols_data.TryGetValue(k, out data))
return true;
data = default;
return false;
}
static GridColumnSettings _convert(GridColumnSettings default_col) =>
_try_get_data(default_col.id, out (bool is_visible, double? width_override) d) ?
default_col.with(is_visible: d.is_visible, width: d.width_override) :
default_col.with(is_visible: false);
}
}
}
}
@@ -390,12 +390,16 @@ namespace mbts.spark {
if (order_properties_vm == null)
return;
var s = _settings_manager.current_settings();
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && s.alternate_order_size == 0))
if (DoubleHelper.IsNaNOrInfinity(args.price) || !args.side.HasValue || HasErrors || order_properties_vm.HasErrors || (size_from_alternate && s.alternate_order_size == 0)) {
return;
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0))
}
if (!size_from_alternate && (!size_calculator.value.HasValue || size_calculator.value.Value == 0)) {
size_calculator.flash_error();
uint size = size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value.Value);
send_order_async(args.side.Value, size, args.price).ignore();
}
else {
uint size = size_from_alternate ? s.alternate_order_size : (uint)Math.Abs(size_calculator.value ?? 0);
send_order_async(args.side.Value, size, args.price).ignore();
}
}
public async Task send_order_async(Side side, uint size, double price) {
@@ -338,7 +338,12 @@ namespace mbts.spark {
if (bs.HasValue) {
_send_parameterized_order(point, bs.Value);
} else if (column.column_type == ColumnType.Price) {
show_mbo_popup(point).ignore();
if (Keyboard.IsKeyDown(Key.LeftShift) || Keyboard.IsKeyDown (Key.RightShift) ||
Keyboard.IsKeyDown(Key.LeftAlt) || Keyboard.IsKeyDown(Key.RightAlt)) {
show_mbo_popup(point).ignore();
} else {
_scroll_to_center();
}
}
}
var pbs = (price, bs, e.ChangedButton);
@@ -13,6 +13,7 @@
TextElement.FontSize="{x:Static u:FontSizes.Small}"
>
<UserControl.Resources>
<cv:BooleanToVisibilityConverter x:Key="NetChangeGroupBoxIsVisibleVisibilityConverter" VisibilityForTrue="Hidden" VisibilityForFalse="Visible"/>
<sys:Double x:Key="HorizontalPush">4</sys:Double>
<sys:Double x:Key="HorizontalSplit">8</sys:Double>
<sys:Double x:Key="IndentChild">24</sys:Double>
@@ -382,7 +383,8 @@
FontStyle="Italic"
Foreground="{Binding can_choose_alternate_implied_format, Converter={StaticResource IsEnabledForegroundConverter}}"
/>
<GroupBox Grid.Row="3" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,10,0,0" Grid.ColumnSpan="3">
<GroupBox Grid.Row="3" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,10,0,0" Grid.ColumnSpan="3"
Visibility="{Binding DataContext.is_synthetic, RelativeSource={RelativeSource AncestorType=usp:LadderSettingsView}, Converter={StaticResource NetChangeGroupBoxIsVisibleVisibilityConverter}}">
<GroupBox.Header>
<CheckBox
x:Name="show_name_change_chk"
@@ -149,11 +149,33 @@ namespace mbts.spark {
}
void _host_PreviewMouseDown(object sender, MouseButtonEventArgs e) {
// When middle click over bid or ask columns:
// Non-parameterizalbe order: change cursor to No indicating sending an order is not possible
// Parameterizable order: if qty is null/0, flash and No cursor
var pos = e.GetPosition(_host);
var column_type = _host._column_for_point(pos).column_type;
var selected_algo_can_expand = _host._vm.order_properties_vm?.selected_algo_definition?.algo_root?.can_expand ?? true;
if (e.MiddleButton == MouseButtonState.Pressed &&
(column_type == LadderGridHost.ColumnType.Bid || column_type == LadderGridHost.ColumnType.Ask)) {
if (!selected_algo_can_expand) {
_host.Cursor = Cursors.No;
}
if (selected_algo_can_expand && (_host._vm.size_calculator.value ?? 0) == 0) {
_host._vm.size_calculator.flash_error();
_host.Cursor = Cursors.No;
}
}
_is_mouse_down = true;
InvalidateVisual();
}
void _host_MouseUp(object sender, MouseButtonEventArgs e) {
// This will reset the cursor appropriately if the middle mouse button
// has been pressed while over the bid or ask columns
var point = e.GetPosition(_host);
_set_current_row_and_column(point);
_is_mouse_down = false;
_is_mouse_dragging = false;
InvalidateVisual();
@@ -3,12 +3,11 @@ using bts.api;
using bts.api.core;
using bts.api.orderman;
using bts.collections;
using bts.md;
using bts.shell;
using bts.shell.fills;
using bts.shell.orderbook;
using bts.spark;
using bts.utils;
using mbts.collections;
using mbts.persist;
using mbts.spark.utilities;
using mbts.ui;
@@ -45,7 +44,7 @@ namespace mbts.spark {
readonly OperableHierarchicalCollection<OrderBookRowVM, OrderSecurity, uint> _row_collection;
readonly OrderBookColumnCollection _columns;
readonly IWindowSettingsManager<OrderBookGridSettings> _settings_manager;
readonly IWindowSettingsManager<GridSettings> _fills_settings_manager = new WindowSettingsManager<GridSettings>.DisconnectedImpl(FillsWindowFactory.StartupData.MiniGridSettings, FillsWindowFactory.StartupData.MiniGridSettings);
readonly IWindowSettingsManager<GridSettings> _fills_settings_manager = new WindowSettingsManager<GridSettings>.DisconnectedImpl(FillsSettings.MiniGridSettings, FillsSettings.MiniGridSettings);
readonly SelectedOrderParamsMon _selected_params_mon;
readonly RightPanelModel _right_panel = new RightPanelModel();
OrderBookFillsGridSettings _cached_fills_settings = OrderBookStartupData.DefaultFillsGrid;
@@ -1,6 +1,7 @@
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.shell.fills;
using bts.shell.orderbook;
using bts.spark;
using bts.view;
@@ -68,7 +69,7 @@ namespace mbts.spark {
_vm.load_from(_state);
_view = new OrderBookView(_vm);
var show_fills_command = SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(() => new FillsWindowFactory.StartupData(_vm.selected_orders.Select(r => r.Key).ToArray()));
var show_fills_command = SparkCommands.ShowFills(SparkCommands.KeyGestures.ShowFills).complete(() => new FillsSettings(_vm.selected_orders.Select(r => r.Key).ToArray()));
var toggle_show_right_panel_command = new SparkCommandCommand(
get_toggle_show_right_panel_cmd_title(),
@@ -1,4 +1,5 @@
using mbts.ui;
using bts.shell.fills;
using mbts.ui;
using ProtoBuf;
using System.Collections.Generic;
using GridSettings = mbts.ui.settings.GridSettings;
@@ -9,11 +10,11 @@ namespace bts.shell.orderbook {
[ProtoMember(1)]
public GridSettings general { get; set; }
[ProtoMember(2)]
public FillsWindowFactory.FilterStates filters { get; set; }
public FillsFilterStates filters { get; set; }
[ProtoMember(3)]
public List<SortingCriteria> sorting_criterion { get; set; }
public FillsWindowFactory.StartupData to_startup_data() =>
new FillsWindowFactory.StartupData(null, sorting_criterion, filters, general);
public FillsSettings to_startup_data() =>
new FillsSettings(null, sorting_criterion, filters, general);
}
}
@@ -7,8 +7,6 @@ using mbts.spark.filters;
using mbts.ui;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
namespace bts.shell.orderbook {
[ProtoContract]
@@ -121,86 +119,36 @@ namespace bts.shell.orderbook {
static class FilterStatesExtensions {
public static Func<OrderSecurity, bool> build_predicate(this FilterStates fs) {
var predicates = new List<Func<OrderSecurity, bool>>();
var pb = new ColumnPredicateBuilder<OrderSecurity>();
// security
if (fs.security_criteria?.is_defined() ?? false)
predicates.Add(o => o.security.HasValue && ContractPickerModel.security_in_criteria(fs.security_criteria, o.security.Value));
pb.add_security_criteria(fs.security_criteria, o => o.security);
// regex
add_regex(fs.order_key_regex, o => o.order.Key.ToString());
add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
pb.add_regex(fs.order_key_regex, o => o.order.Key.ToString());
pb.add_regex(fs.exchange_message_regex, o => o.order.State.LastMsg);
pb.add_regex(fs.attached_target_order_key_regex, o => o.order.attached_target_order_key().ToString());
pb.add_regex(fs.parent_key_regex, o => o.order.ParentKey.ToString());
// time
add_time_filter(fs.time_created, o => o.order.created());
add_time_filter(fs.last_change, o => o.order.last_change());
pb.add_time_filter(fs.time_created, o => o.order.created());
pb.add_time_filter(fs.last_change, o => o.order.last_change());
// discrete values
add_discrete_values(fs.users, o => o.order.user());
add_discrete_values(fs.accounts, o => o.order.account());
add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
add_discrete_values(fs.tifs, o => o.order.time_in_force());
add_discrete_values(fs.order_types, o => AlgoID.from_order_def(o.order.Def));
add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key));
add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
add_discrete_values(fs.sides, o => o.order.side());
add_discrete_values(fs.statuses, o => o.order.status());
add_discrete_values(fs.symbols, o => o.security?.Symbol);
pb.add_discrete_values(fs.users, o => o.order.user());
pb.add_discrete_values(fs.accounts, o => o.order.account());
pb.add_discrete_values(fs.exchanges, o => o.security?.Exchange ?? Venue.Unset);
pb.add_discrete_values(fs.tifs, o => o.order.time_in_force());
pb.add_discrete_values(fs.order_types, o => AlgoID.from_order_def(o.order.Def));
pb.add_discrete_values(fs.root_algo_types, o => AlgoID.from_algo_key(o.root_algo.key));
pb.add_discrete_values(fs.order_sub_types, o => o.order.sub_type());
pb.add_discrete_values(fs.sides, o => o.order.side());
pb.add_discrete_values(fs.statuses, o => o.order.status());
pb.add_discrete_values(fs.symbols, o => o.security?.Symbol);
// numeric
add_numeric(fs.qty, o => o.order.quantity());
add_numeric(fs.price, o => o.order.price());
add_numeric(fs.qty_filled, o => o.order.filled());
add_numeric(fs.avg_price, o => o.order.avg_price());
add_numeric(fs.last_price, o => o.order.last_price());
add_numeric(fs.piq, o => o.piq ?? uint.MaxValue);
return predicates.Count == 0 ? null : new Func<OrderSecurity, bool>(o => predicates.All(p => p(o)));
// local methods (impl)
void add_regex(string str, Func<OrderSecurity, string> selector) {
var regex = RegexFilterUtils.build_regex(str);
if (regex == null)
return;
predicates.Add(impl);
bool impl(OrderSecurity o) => regex.IsMatch(selector(o) ?? string.Empty);
}
void add_time_filter(TimeFilterTuple t, Func<OrderSecurity, DateTime> selector) {
if (!t.is_active)
return;
var dt_pred = TimeFilterUtils.build_predicate(t);
predicates.Add(impl);
bool impl(OrderSecurity o) => dt_pred(selector(o));
}
void add_discrete_values<T>(T[] values, Func<OrderSecurity, T> selector) {
if (values.is_null_or_empty())
return;
if (values.Length < 16) {
predicates.Add(impl);
bool impl(OrderSecurity o) => values.Contains(selector(o));
} else {
var set = new HashSet<T>(values);
predicates.Add(impl);
bool impl(OrderSecurity o) => set.Contains(selector(o));
}
}
void add_numeric<T>(IMinMaxState<T> value, Func<OrderSecurity, T> selector) where T : struct, IComparable<T> {
if (value == null)
return;
if (value.minimum.active) {
var min = value.minimum.value.Value;
if (value.maximum.active) {
var max = value.maximum.value.Value;
predicates.Add(impl);
bool impl(OrderSecurity o) {
var v = selector(o);
return min.CompareTo(v) <= 0 && max.CompareTo(v) >= 0;
}
} else {
predicates.Add(impl);
bool impl(OrderSecurity o) => min.CompareTo(selector(o)) <= 0;
}
} else if (value.maximum.active) {
var max = value.maximum.value.Value;
predicates.Add(impl);
bool impl(OrderSecurity o) => max.CompareTo(selector(o)) >= 0;
}
}
pb.add_numeric(fs.qty, o => o.order.quantity());
pb.add_numeric(fs.price, o => o.order.price());
pb.add_numeric(fs.qty_filled, o => o.order.filled());
pb.add_numeric(fs.avg_price, o => o.order.avg_price());
pb.add_numeric(fs.last_price, o => o.order.last_price());
pb.add_numeric(fs.piq, o => o.piq ?? uint.MaxValue);
return pb.to_predicate();
}
}
}
@@ -1,4 +1,5 @@
using bts.api.orderman;
using bts.shell.fills;
using mbts;
using mbts.ui;
using mbts.ui.controls;
@@ -127,11 +128,11 @@ namespace bts.shell.orderbook {
public static OrderBookFillsGridSettings DefaultFillsGrid {
get {
var default_fills = FillsWindowFactory.StartupData.Default();
var default_fills = FillsSettings.Default();
var rv = new OrderBookFillsGridSettings {
sorting_criterion = default_fills.sorting_criterion.ToList(),
filters = default_fills.filters,
general = FillsWindowFactory.StartupData.MiniGridSettings
general = FillsSettings.MiniGridSettings
};
return rv;
}
@@ -1,4 +1,5 @@
using bts.api;
using bts.shell.fills;
using bts.shell.manual_fills;
using bts.spark;
using mbts.persist;
@@ -48,7 +49,7 @@ namespace bts.shell {
public IEnumerable<SparkCommand> actions => new [] {
SparkCommands.ShowOrders.complete(() => bts.shell.orderbook.OrderBookStartupData.Default),
SparkCommands.ShowFills(null).complete(() => new FillsWindowFactory.StartupData()),
SparkCommands.ShowFills(null).complete(() => new FillsSettings()),
SparkCommands.ShowPAndL.complete(() => new PAndLWindowFactory.PAndLStartupData()),
SparkCommands.PickLadder,
SparkCommands.PickOrderTicket,
+20
View File
@@ -46,4 +46,24 @@ namespace mbts.spark {
order.HasValue ? order.Value.Key.GetHashCode() : 0,
security.HasValue ? security.Value.Id.GetHashCode() : 0);
}
static class FillTupleExtensions {
public static DateTime fill_time(this FillTuple fill_tuple) =>
fill_tuple.fill.Timestamp.to_local_time();
public static TimeInForce time_in_force(this FillTuple fill_tuple) =>
fill_tuple.order.HasValue ? fill_tuple.order.Value.time_in_force() : TimeInForce.Undefined;
public static CustomOrderDefinition.CustomOneofCase order_type(this FillTuple fill_tuple) =>
fill_tuple.order.HasValue ? fill_tuple.order.Value.order_type() : CustomOrderDefinition.CustomOneofCase.None;
public static Venue exchange(this FillTuple fill_tuple) =>
fill_tuple.security.HasValue ? fill_tuple.security.Value.Exchange : Venue.Unset;
public static uint order_key(this FillTuple fill_tuple) =>
fill_tuple.order.HasValue ? fill_tuple.order.Value.Key : 0u;
public static string user(this FillTuple fill_tuple) =>
fill_tuple.order.HasValue ? fill_tuple.order.Value.user() : string.Empty;
}
}
+2 -2
View File
@@ -37,8 +37,8 @@ namespace bts.api.orderman {
this.piq = piq;
this.algo = algo;
this.meta = meta;
root_algo = new AlgoData(order.Def.CustomDef);
root_meta = null;
root_algo = algo ?? new AlgoData(order.Def.CustomDef);
root_meta = meta;
}
public OrderSecurity(SecurityImmutable? sec, OrderRecordImmutable order, uint? piq, AlgoData? algo, AlgoDefinitionMetadata? meta, AlgoData? rootalgo = null, AlgoDefinitionMetadata? rootmeta = null) {
this.security = sec;
+2 -1
View File
@@ -1,6 +1,7 @@
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.shell.fills;
using bts.shell.manual_fills;
using mbts;
using mbts.spark;
@@ -27,7 +28,7 @@ namespace bts.spark {
SparkCommand.create("Order Book", Icons.Book, SparkTags.ShowOrderBookTag);
// Fills
public static PartialCommand<FillsWindowFactory.StartupData> ShowFills(KeyGesture key_gesture = null) =>
public static PartialCommand<FillsSettings> ShowFills(KeyGesture key_gesture = null) =>
SparkCommand.create("Fills", Icons.Payment, SparkTags.ShowFillsTag, key_gesture);
public static PartialCommand<ManualFillsSettings> ShowManualFills(KeyGesture key_gesture = null) =>
+2 -1
View File
@@ -1,6 +1,7 @@
using bts.api;
using bts.api.orderman;
using bts.shell;
using bts.shell.fills;
using bts.shell.manual_fills;
using bts.spark;
using mbts.ui;
@@ -52,7 +53,7 @@ namespace mbts.spark
#endregion
#region Spark Window
public static ITag<FillsWindowFactory.StartupData> ShowFillsTag { get; } = new SparkTag<FillsWindowFactory.StartupData>(nameof(ShowFillsTag));
public static ITag<FillsSettings> ShowFillsTag { get; } = new SparkTag<FillsSettings>(nameof(ShowFillsTag));
public static ITag<ManualFillsSettings> ShowManualFillsTag { get; } = new SparkTag<ManualFillsSettings>(nameof(ShowManualFillsTag));
// TODO: maybe get rid of the singlular ShowLadderTag, and just use the plural ShowLaddersTag.
// The owners of the various SparkWindows may remove dependencies on the former at their leasure.
@@ -17,7 +17,7 @@ namespace mbts.spark {
_ladder = LadderSettings.Default,
_notification = NotificationSettings.Default,
_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings,
_fills_grid = bts.shell.FillsWindowFactory.StartupData.DefaultGridSettings,
_fills_grid = bts.shell.fills.FillsSettings.DefaultGridSettings,
_time_and_sales_grid = bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultGridSettings,
_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings,
_order_book_grid = bts.shell.orderbook.OrderBookStartupData.DefaultGrid
@@ -48,7 +48,7 @@ namespace mbts.spark {
public NotificationSettings notification => _notification ?? (_notification = NotificationSettings.Default);
public GridSettings market_view_grid => _market_view_grid ?? (_market_view_grid = bts.shell.MarketViewWindowFactory.StartupData.DefaultGridSettings);
public GridSettings time_and_sales_grid => _time_and_sales_grid ?? (_time_and_sales_grid = GridSettings.Default.with(bts.shell.TimeAndSalesWindowFactory.StartupData.DefaultColumns.all));
public GridSettings fills_grid => _fills_grid ?? (_fills_grid = bts.shell.FillsWindowFactory.StartupData.DefaultGridSettings);
public GridSettings fills_grid => _fills_grid ?? (_fills_grid = bts.shell.fills.FillsSettings.DefaultGridSettings);
public GridSettings synthetic_management_grid => _synthetic_management_grid ?? (_synthetic_management_grid = bts.shell.SyntheticManagementWindowFactory.StartupData.DefaultGridSettings);
public GridSettings pnl_grid => _pnl_grid ?? (_pnl_grid = bts.shell.PAndLWindowFactory.PAndLStartupData.DefaultGridSettings);
public bts.shell.orderbook.OrderBookGridSettings order_book_grid => _order_book_grid ?? (_order_book_grid = bts.shell.orderbook.OrderBookStartupData.Default.grid_settings_wrapper());
@@ -0,0 +1,69 @@
using bts.api;
using mbts.ui;
using System;
using System.Collections.Generic;
using System.Linq;
namespace mbts.spark.filters {
class ColumnPredicateBuilder<TRow> {
readonly List<Func<TRow, bool>> _predicates = new List<Func<TRow, bool>>();
public void add_discrete_values<T>(T[] values, Func<TRow, T> selector) {
if (values.is_null_or_empty())
return;
var set = new HashSet<T>(values);
_predicates.Add(impl);
bool impl(TRow o) => set.Contains(selector(o));
}
public void add_numeric<T>(IMinMaxState<T> value, Func<TRow, T> selector) where T : struct, IComparable<T> {
if (value == null)
return;
if (value.minimum.active) {
var min = value.minimum.value.Value;
if (value.maximum.active) {
var max = value.maximum.value.Value;
_predicates.Add(impl);
bool impl(TRow o) {
var v = selector(o);
return min.CompareTo(v) <= 0 && max.CompareTo(v) >= 0;
}
} else {
_predicates.Add(impl);
bool impl(TRow o) => min.CompareTo(selector(o)) <= 0;
}
} else if (value.maximum.active) {
var max = value.maximum.value.Value;
_predicates.Add(impl);
bool impl(TRow o) => max.CompareTo(selector(o)) >= 0;
}
}
public void add_regex(string str, Func<TRow, string> selector) {
var regex = RegexFilterUtils.build_regex(str);
if (regex == null)
return;
_predicates.Add(impl);
bool impl(TRow o) => regex.IsMatch(selector(o) ?? string.Empty);
}
public void add_security_criteria(SecurityCriteria security_criteria, Func<TRow, SecurityImmutable?> selector) {
if (security_criteria != null && security_criteria.is_defined())
_predicates.Add(r => {
var sec = selector(r);
return sec.HasValue && ContractPickerModel.security_in_criteria(security_criteria, sec.Value);
});
}
public void add_time_filter(TimeFilterTuple t, Func<TRow, DateTime> selector) {
if (!t.is_active)
return;
var dt_pred = TimeFilterUtils.build_predicate(t);
_predicates.Add(impl);
bool impl(TRow o) => dt_pred(selector(o));
}
public Func<TRow, bool> to_predicate() =>
_predicates.Count == 0 ? null : new Func<TRow, bool>(o => _predicates.All(p => p(o)));
}
}
@@ -1,5 +1,6 @@
using bts;
using bts.utils;
using mbts.collections;
using mbts.utils;
using System;
using System.Collections.Generic;
@@ -7,13 +8,30 @@ using System.ComponentModel;
using System.Linq;
namespace mbts.ui.controls {
static class DIYGridRowsManager {
public static DIYGridRowsManager<T, TColKey, TFiltersState> create<T, TColKey, TFiltersState>(
string window_name,
TColKey primary_key,
ObservableKeyedCollection<DIYGridColumnData<T, TColKey, TFiltersState>, TColKey> columns,
IReadOnlyObservableKeyedCollection<(DIYGrid.ColumnBase col, TColKey key), TColKey> map,
Func<TColKey, IComparer<T>> get_comparer_for_column,
Func<TFiltersState, Func<T, bool>> build_filter_from_state = null) where TColKey : struct where TFiltersState : new() =>
//
new DIYGridRowsManager<T, TColKey, TFiltersState>(
DIYGridFiltersManager.create(columns),
new DIYGridSortManager<TColKey>(map, primary_key),
get_comparer_for_column,
window_name,
build_filter_from_state);
}
class DIYGridRowsManager<T, TColKey, TFiltersState> where TColKey : struct {
readonly IDIYGridFiltersManager<T, TColKey, TFiltersState> _filters;
readonly DIYGridSortManager<TColKey> _sorting;
readonly DeferrableActionWrapper _update;
bool _filters_dirty, _sorting_dirty;
public DIYGridRowsManager(IDIYGridFiltersManager<T, TColKey, TFiltersState> filters_manager, DIYGridSortManager<TColKey> sort_manager, Func<TColKey, IComparer<T>> get_comparer_for_column, string window_name) {
public DIYGridRowsManager(IDIYGridFiltersManager<T, TColKey, TFiltersState> filters_manager, DIYGridSortManager<TColKey> sort_manager, Func<TColKey, IComparer<T>> get_comparer_for_column, string window_name, Func<TFiltersState, Func<T, bool>> build_filter_from_state = null) {
_filters = filters_manager;
_sorting = sort_manager;
_update = new DeferrableActionWrapper(() => {
@@ -59,10 +77,15 @@ namespace mbts.ui.controls {
desc = "(none)";
return null;
} else {
// TODO: the actual IDataGridFilter implementations should be able to provide a concise description of their filter state/parameters (e.g., for a min/max filter: 'min=40, max=60')
desc = string.Join(", ", col_filters.Select(f => f.col));
var filters = Array.ConvertAll(col_filters, cf => cf.filter);
return new Func<T, bool>(r => filters.All(f => f.filter(r)));
if (build_filter_from_state != null) {
var filter_state = filters_manager.create();
return build_filter_from_state(filter_state);
} else {
// TODO: the actual IDataGridFilter implementations should be able to provide a concise description of their filter state/parameters (e.g., for a min/max filter: 'min=40, max=60')
var filters = Array.ConvertAll(col_filters, cf => cf.filter);
return new Func<T, bool>(r => filters.All(f => f.filter(r)));
}
}
}
}
@@ -70,6 +93,10 @@ namespace mbts.ui.controls {
public event Action<Func<T, bool>> filter_changed;
public event Action<IComparer<T>> comparer_changed;
public TFiltersState get_filters() => _filters.create();
public IEnumerable<SortingCriteria> get_sorting_criterion() => _sorting.create();
public void on_sorting(DIYGridSortingEventArgs e) => _sorting.on_sorting(e);
public void on_settings(TFiltersState filters, IEnumerable<SortingCriteria> sorting) {
using (_update.defer_execution()) {
_filters.load_from(filters);
@@ -44,7 +44,7 @@ namespace mbts.spark {
readonly MBOService _mbo;
readonly SecurityImmutable _security;
readonly MBOSecurityQueryImmutable _query;
readonly Dictionary<string, FormattedText> _cache;
readonly Dictionary<(string,bool), FormattedText> _cache;
int _max_order_size = 1;
double _height = 0;
double _number_width = 0;
@@ -60,17 +60,17 @@ namespace mbts.spark {
Enumerable.Range(1, 1+(int)default_query.Limit)
.AsParallel()
.Select(i => TextUtils.GetFormattedText(i.ToString(), font_size))
.ToDictionary(ft => ft.Text);
.ToDictionary(ft => (ft.Text,false));
}
FormattedText format_text(string s, bool bold=false) {
// cache FormattedText for the life of the popup
if (_cache.TryGetValue(s, out FormattedText ft)) {
var key = (s, bold);
if (_cache.TryGetValue(key, out FormattedText ft)) {
return ft;
}
else
ft = bold ? TextUtils.GetFormattedTextBold(s, font_size) : TextUtils.GetFormattedText(s, font_size);
_cache.TryAdd(s, ft);
_cache.TryAdd(key, ft);
return ft;
}
@@ -98,6 +98,8 @@ namespace mbts.spark {
}
}
public static bool ismine(OrderPositionImmutable op) => op.OrderId != 0;
public MBOPopupDrawParams compute_layout(MBOPopupData p) {
var pdp = new MBOPopupDrawParams { data = p };
pdp.header = format_text(
@@ -106,9 +108,9 @@ namespace mbts.spark {
var margin = MBOPopupDrawParams.margin;
var text_margin = margin.Left + margin.Right;
pdp.row_numbers = p.orders.Select((o, i) => format_text((i+1).ToString())).ToList();
pdp.row_numbers = p.orders.Select((o, i) => format_text((i+1).ToString(), ismine(o))).ToList();
pdp.numbers_width = _number_width = Math.Max(_number_width, (int)pdp.row_numbers.Max(r => r.Width) + text_margin);
pdp.row_labels = p.orders.Select(o => format_text(o.Size.ToString())).ToList();
pdp.row_labels = p.orders.Select(o => format_text(o.Size.ToString() + (ismine(o) ? "*" : ""), ismine(o))).ToList();
var max_label_width = pdp.row_labels.Max(l => l.Width) + text_margin;
var available_width = pdp.width - pdp.numbers_width - max_label_width;
@@ -57,7 +57,7 @@ namespace mbts.spark {
}
static Brush brush_for(Side side, OrderPositionImmutable order) {
if (order.OrderId != 0) return Brushes.Black;
if (MBOPopupModel.ismine(order)) return Brushes.Black;
switch (side) {
case Side.Buy: return BTSColors.MarketData.bid_brush;
case Side.Sell: return BTSColors.MarketData.ask_brush;
@@ -1,14 +1,11 @@
using bts;
using bts.api.core;
using bts.api.core;
using bts.collections;
using bts.utils;
using mbts.utils;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive;
using System.Reactive.Linq;
namespace mbts.ui.utility {
@@ -37,16 +34,16 @@ namespace mbts.ui.utility {
table_update_source.Subscribe(on_table));
// on comparer
void on_comp(IComparer<T> new_comp) {
comp = new_comp;
if (table != null) {
target.Clear();
target.InsertSorted(table.state.Values, comp);
target.InsertSorted(table.state.Values, new_comp);
}
comp = new_comp;
}
// on table update
void on_table(TableUpdate<T, TKey> new_table) {
table = table == null ? new_table.as_snapshot() : new_table;
if (comp != null) {
table = table == null ? new_table.as_snapshot() : new_table;
target.RemoveSorted(table.removed.Concat(table.changes.Old()), comp);
target.InsertSorted(table.added.Concat(table.changed), comp);
}
+163 -4
View File
@@ -76,7 +76,7 @@ namespace bts.tests
);
var spreader_order1_response = await OrderUtilities.send_order(_order_service, order1_def, -0.30, 1);
Assert.IsTrue(spreader_order1_response.success, "Failed to send first spreader order");
Assert.IsTrue(spreader_order1_response.success, "Failed to send spreader order");
var (spreader_order_is_working, working_spreader_order1) = await verify_order_status(spreader_order1_response.value.OrderKey, OrderStatus.Working);
Assert.IsTrue(spreader_order_is_working, "Spreader order is not Working");
@@ -120,6 +120,156 @@ namespace bts.tests
}
[Test(Timeout = 300000)]
public async Task spreader_lean_qty_factor() {
await setup_spreader_tests();
string test_symbol = "SI";
var test_user = (name: "bts", id: 1u);
var test_account = (name: "ACC A", id: 1u);
(SecurityImmutable s1, SecurityImmutable s2) test_securities = await SpreaderUtilities.get_securities_for_common_setup_one(_md_relay, test_symbol);
var setup_product = await SpreaderUtilities.common_setup_one(_order_service, _synthetic_service, test_securities, test_account, test_user);
var order1_def = OrderDefinitionUtilities.create_spreader_order_def(
setup_product.synthetic.security,
Side.Buy,
setup_product.synthetic.strategy,
test_account,
test_user
);
var spreader_order1_response = await OrderUtilities.send_order(_order_service, order1_def, -0.30, 1);
Assert.IsTrue(spreader_order1_response.success, "Failed to send spreader order");
var (spreader_order_is_working, working_spreader_order1) = await verify_order_status(spreader_order1_response.value.OrderKey, OrderStatus.Working);
Assert.IsTrue(spreader_order_is_working, "Spreader order is not Working");
var (quoting_order_is_working, working_quoting_order) = await verify_child_order_status(spreader_order1_response.value.OrderKey, OrderStatus.Working);
Assert.IsTrue(quoting_order_is_working, "Order is not Working");
Assert.IsTrue(working_quoting_order.Value.State.Price == 18.6, "Quoting order has the wrong price");
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, new List<OrderRecordImmutable> {working_spreader_order1.Value}), "Failed to cancel order");
var (quoting_order_is_canceled, canceled_quoting_order) = await verify_child_order_status(spreader_order1_response.value.OrderKey, OrderStatus.Canceled);
Assert.IsTrue(quoting_order_is_canceled, "Quoting order may not have been canceled");
// change the synthetic
var hedge_leg = setup_product.synthetic.strategy.LegParamsMap.Where(m => !m.Value.IsActiveQuoting).TryFirst();
Assert.IsTrue(hedge_leg.HasValue, "you need more legs");
foreach(var key in setup_product.synthetic.strategy.LegParamsMap.Keys) {
if(!setup_product.synthetic.strategy.LegParamsMap[key].IsActiveQuoting) {
setup_product.synthetic.strategy.LegParamsMap[key].QuoteParams.LeanQuantityRatio = 2d;
break;
}
}
Assert.IsTrue(await SpreaderUtilities.save_strategy(_synthetic_service, setup_product.synthetic.security.Id, (ExecutionStrategyImmutable) setup_product.synthetic.strategy), "Failed to save strategy");
var order2_def = OrderDefinitionUtilities.create_spreader_order_def(
setup_product.synthetic.security,
Side.Buy,
setup_product.synthetic.strategy,
test_account,
test_user
);
var spreader_order2_response = await OrderUtilities.send_order(_order_service, order2_def, -0.30, 1);
Assert.IsTrue(spreader_order2_response.success, "Failed to send spreader order");
var (spreader_order2_is_working, working_spreader_order2) = await verify_order_status(spreader_order2_response.value.OrderKey, OrderStatus.Working);
Assert.IsTrue(spreader_order2_is_working, "Spreader order is not Working");
var (quoting_order2_is_working, working_quoting_order2) = await verify_child_order_status(spreader_order2_response.value.OrderKey, OrderStatus.Working);
Assert.IsTrue(quoting_order2_is_working, "Order is not Working");
Assert.IsTrue(working_quoting_order2.Value.State.Price == 18.61, "Quoting order has the wrong price");
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, new List<OrderRecordImmutable> {working_spreader_order2.Value}), "Failed to cancel order");
var (quoting_order2_is_canceled, canceled_quoting_order2) = await verify_child_order_status(spreader_order2_response.value.OrderKey, OrderStatus.Canceled);
Assert.IsTrue(quoting_order2_is_canceled, "Quoting order may not have been canceled");
// Order 3
foreach(var key in setup_product.synthetic.strategy.LegParamsMap.Keys) {
if(!setup_product.synthetic.strategy.LegParamsMap[key].IsActiveQuoting) {
setup_product.synthetic.strategy.LegParamsMap[key].QuoteParams.LeanQuantityRatio = 4d;
break;
}
}
Assert.IsTrue(await SpreaderUtilities.save_strategy(_synthetic_service, setup_product.synthetic.security.Id, (ExecutionStrategyImmutable) setup_product.synthetic.strategy), "Failed to save strategy");
var order3_def = OrderDefinitionUtilities.create_spreader_order_def(
setup_product.synthetic.security,
Side.Buy,
setup_product.synthetic.strategy,
test_account,
test_user
);
var spreader_order3_response = await OrderUtilities.send_order(_order_service, order3_def, -0.30, 1);
Assert.IsTrue(spreader_order3_response.success, "Failed to send spreader order");
var (spreader_order3_is_working, working_spreader_order3) = await verify_order_status(spreader_order3_response.value.OrderKey, OrderStatus.Working);
Assert.IsTrue(spreader_order3_is_working, "Spreader order is not Working");
var (quoting_order3_is_working, working_quoting_order3) = await verify_child_order_status(spreader_order3_response.value.OrderKey, OrderStatus.Working);
Assert.IsTrue(quoting_order3_is_working, "Order is not Working");
Assert.IsTrue(working_quoting_order3.Value.State.Price == 18.63, "Quoting order has the wrong price");
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, new List<OrderRecordImmutable> {working_spreader_order3.Value}), "Failed to cancel order");
var (quoting_order3_is_canceled, canceled_quoting_order3) = await verify_child_order_status(spreader_order3_response.value.OrderKey, OrderStatus.Canceled);
Assert.IsTrue(quoting_order3_is_canceled, "Quoting order may not have been canceled");
// Order 4
foreach(var key in setup_product.synthetic.strategy.LegParamsMap.Keys) {
if(!setup_product.synthetic.strategy.LegParamsMap[key].IsActiveQuoting) {
setup_product.synthetic.strategy.LegParamsMap[key].QuoteParams.LeanQuantityRatio = 5d;
break;
}
}
Assert.IsTrue(await SpreaderUtilities.save_strategy(_synthetic_service, setup_product.synthetic.security.Id, (ExecutionStrategyImmutable) setup_product.synthetic.strategy), "Failed to save strategy");
var order4_def = OrderDefinitionUtilities.create_spreader_order_def(
setup_product.synthetic.security,
Side.Buy,
setup_product.synthetic.strategy,
test_account,
test_user
);
var spreader_order4_response = await OrderUtilities.send_order(_order_service, order4_def, -0.30, 1);
Assert.IsTrue(spreader_order4_response.success, "Failed to send spreader order");
var (spreader_order4_is_working, working_spreader_order4) = await verify_order_status(spreader_order4_response.value.OrderKey, OrderStatus.Working);
Assert.IsTrue(spreader_order4_is_working, "Spreader order is not Working");
var no_quoting_order4 = await get_order(ord => ord.ParentKey == spreader_order4_response.value.OrderKey, TimeSpan.FromSeconds(10));
Assert.IsFalse(no_quoting_order4.HasValue, "There should not have been a quoting order here...");
OrderRecordImmutable order_from_book = setup_product.book.Where(ordi => ordi.State.Price == 18.30).First();
var mod_resp = await OrderUtilities.modify_order(_order_service, order_from_book, 2);
Assert.IsTrue(mod_resp.success, "Order size was not changed");
var (quoting_order4_is_working, working_quoting_order4) = await verify_child_order_status(spreader_order4_response.value.OrderKey, OrderStatus.Working);
Assert.IsTrue(quoting_order4_is_working, "Order is not Working");
Assert.IsTrue(working_quoting_order4.Value.State.Price == 18.63, "Quoting order has the wrong price");
var mod_resp2 = await OrderUtilities.modify_order(_order_service, order_from_book, 1);
Assert.IsTrue(mod_resp.success, "Order size was not changed");
var (quoting_order5_is_canceled, canceled_quoting_order5) = await verify_child_order_status(spreader_order4_response.value.OrderKey, OrderStatus.Canceled);
Assert.IsTrue(quoting_order5_is_canceled, "Order was not canceld");
Assert.IsTrue(await OrderUtilities.cancel_orders(_order_service, new List<OrderRecordImmutable> {working_spreader_order4.Value}), "Failed to cancel order");
}
private async Task<(Boolean, OrderRecordImmutable?)> verify_child_order_status(uint parent_order_key, OrderStatus expected_status) {
var order = await get_order(ordi => ordi.ParentKey == parent_order_key && ordi.State.Status == expected_status);
return (order.HasValue && order.Value.State.Status == expected_status, order);
@@ -140,13 +290,22 @@ namespace bts.tests
return order.HasValue && (Math.Truncate(order.Value.State.Price * precision) == precision * expected_price);
}
private async Task<OrderRecordImmutable?> get_order(Func<OrderRecordImmutable, bool> predicate)
private async Task<OrderRecordImmutable?> get_order(Func<OrderRecordImmutable, bool> predicate, TimeSpan? timeout = null)
{
if(timeout == null){
timeout = TimeSpan.FromSeconds(60);
}
var orders = await OrderUtilities.wait_for_filtered_orders(_order_service,
o => o.Where(predicate).Count() > 0,
ord => ord.Where(predicate),
TimeSpan.FromSeconds(30));
return orders.result.TryFirst();
timeout);
if(orders.result != null) {
return orders.result.TryFirst();
}
else {
return null;
}
}
@@ -75,6 +75,10 @@ namespace bts.tests
});
var tr = await tcs.Task.timeout(timeout.Value);
sub.Dispose();
if(tr.result == null) {
Console.WriteLine("Timed out while retreving orders.");
}
return tr;
}
@@ -124,7 +128,7 @@ namespace bts.tests
}
/// <summary>
/// We should replace with something like the OrderDataHeler from the Spark project.
/// We should replace with something like the OrderDataHelper from the Spark project.
/// </summary>
public static class OrderDefinitionUtilities {
@@ -27,8 +27,24 @@ namespace bts.tests
Assert.IsNotNull(exps);
Assert.IsTrue(exps.Count() > 2, "Need at least two expirations");
var exp1 = exps[0];
var exp2 = exps[1];
(uint, uint) preferred_expirations = (07,12);
FutureContract exp1;
FutureContract exp2;
if(exps.Exists(expir => expir.ExpirationMonth == preferred_expirations.Item1)) {
exp1 = exps.Where(ex => ex.ExpirationMonth == preferred_expirations.Item1).First();
}
else {
exp1 = exps[0];
}
if(exps.Exists(expir => expir.ExpirationMonth == preferred_expirations.Item2 && expir.ExpirationYear == exp1.ExpirationYear)) {
exp2 = exps.Where(ex => ex.ExpirationMonth == preferred_expirations.Item2 && ex.ExpirationYear == exp1.ExpirationYear).First();
}
else {
exp2 = exps[1];
}
Func<ChannelUpdate, bool> sec1_match = ch =>
ch.Security.Future.ExpirationYear == exp1.ExpirationYear &&
@@ -103,13 +119,15 @@ namespace bts.tests
new_synth.Synthetic.Legs.Add(legs.Select(l => l.contract));
new_synth.Symbol = deduce_symbol(new_synth);
new_synth.ContractSpec = new ContractSpecification();
new_synth.ContractSpec.LowTick = 0.01;
new_synth.ContractSpec.HighTick = 0.01;
new_synth.ContractSpec.LowTick = 0.005;
new_synth.ContractSpec.HighTick = 0.005;
new_synth.ContractSpec.PriceBreak = double.MaxValue;
new_synth.ContractSpec.MainFraction = 1u;
new_synth.ContractSpec.PriceDisplayFormat = 3u;
new_synth.ContractSpec.CashPerPoint = 1u;
new_synth.ContractSpec.PriceMultiplier = 1u;
new_synth.Exchange = Venue.Bts;
new_synth.Synthetic.Name = "TEST_SYNTH" + DateTime.Now.ToString();
new_synth.Synthetic.Name = "TEST_SYNTH" + DateTime.Now.TimeOfDay.ToString();
var result = await service.create_synthetic_async(new_synth);
Assert.IsTrue(result.success, "Unable to create synthetic");
@@ -0,0 +1,52 @@
#!/bin/bash
if [ -d ~/sparkdata/ ]
then
sparkdata_dir=~/sparkdata
else
sparkdata_dir=/sparkdata
fi
./delete_dbs.sh
config=$sparkdata_dir/conf/spark.conf
bak=$sparkdata_dir/conf/spark.conf.bak
[ ! -d "$sparkdata_dir/conf" ] && mkdir $sparkdata_dir/conf/
[ -f "$conf" ] && mv $conf $bak
cat << EOF > $sparkdata_dir/conf/spark.conf
ExchangeSim {
value {
merge_with_md:false
}
}
Logging {
value {
console { default_level: "Error" }
syslog { default_level: "Error" }
file { default_level: "Error" }
}
}
SymbolList {
value {
symbol: "SI"
}
}
EOF
nohup ./sparkd run &
pid=$!
echo "sparkd pid: " $pid
dotnet sparkcmd.dll test spreader_lean_qty_factor
exit_status=$?
pkill -P $$
[ -f "$bak" ] && mv $bak $conf
[ $exit_status -eq 0 ] && echo "Test Finished" || echo "Test failed"
exit $exit_status
@@ -27,7 +27,8 @@ cat << EOF > $sparkdata_dir/conf/spark.conf
syslog { default_level: "Error" }
file { default_level: "Error" }
}
}
}
MarketDataConfig { value { use_onixs: false; } }
EOF
@@ -27,7 +27,8 @@ cat << EOF > $sparkdata_dir/conf/spark.conf
syslog { default_level: "Error" }
file { default_level: "Error" }
}
}
}
MarketDataConfig { value { use_onixs: false; } }
EOF
nohup ./sparkd run &
@@ -60,25 +60,28 @@ say_verbose() {
fi
}
is_merge_with_md_on() {
# is_merge_with_md_on() {
# eval $invocation
# if [ $(./sparkd configcheck ExchangeSim | grep merge_with_md: | cut -d " " -f 2) == "false" ]; then
# return 0
# else
# return 1
# fi
# }
set_config() {
eval $invocation
if [ $(./sparkd configcheck ExchangeSim | grep merge_with_md: | cut -d " " -f 2) == "false" ]; then
return 0
else
return 1
fi
}
set_merge_with_md_off() {
eval $invocation
[ ! -d "$sparkdata_dir/conf" ] && mkdir $sparkdata_dir/conf/
[ -f "$spark_conf" ] && mv $spark_conf $spark_conf_bak
if [ -f $config_file_path ]; then
say_verbose "config file exists ... removing"
rm $config_file_path
fi
echo "$merge_with_md_key" >> $config_file_path && test_config_file_created=true
echo "$test_conf" >> $config_file_path && test_config_file_created=true
return 0
}
@@ -97,14 +100,6 @@ get_tests_in_class() {
return 0
}
# Start sparkd
nohup ./sparkd run &
pid=$!
echo "sparkd pid: " $pid
echo "sleeping for 120 seconds..."
sleep 120
# args
# class_name - $1
run_bts_test() {
@@ -125,27 +120,43 @@ run_bts_test() {
test_script_name=$(basename "$0")
verbose=true
spark_conf=$sparkdata_dir/conf/spark.conf
spark_conf_bak=$sparkdata_dir/conf/spark.conf.bak
config_file_path="$sparkdata_dir/conf/test_join_algo_tests.conf"
test_config_file_created=false
merge_with_md_key=$(/bin/cat <<'END_HEREDOC'
test_conf=$(/bin/cat <<'END_HEREDOC'
ExchangeSim {
value {
merge_with_md: false
}
}
MarketDataConfig {
value {
use_onixs: false;
}
}
END_HEREDOC
)
if is_merge_with_md_on; then
set_merge_with_md_off
if is_merge_with_md_on; then
say_err "Could not turn off merge_with_md"
say_err "Please investigate manually"
exit 1
fi
else
say_verbose "merge with md is already off"
fi
# Start sparkd
set_config
nohup ./sparkd run &
pid=$!
echo "sparkd pid: " $pid
echo "sleeping for 60 seconds..."
sleep 60
# if is_merge_with_md_on; then
# set_config
# if is_merge_with_md_on; then
# say_err "Could not turn off merge_with_md"
# say_err "Please investigate manually"
# exit 1
# fi
# else
# say_verbose "merge with md is already off"
# fi
run_bts_test "JoinAlgoTests"
@@ -153,6 +164,8 @@ if [ "$test_config_file_created" = true ]; then
rm $config_file_path
fi
[ -f "$spark_conf_bak" ] && mv $spark_conf_bak $spark_conf
# Kill sparkd
pkill -P $$
@@ -27,7 +27,8 @@ cat << EOF > $sparkdata_dir/conf/spark.conf
syslog { default_level: "Error" }
file { default_level: "Error" }
}
}
}
MarketDataConfig { value { use_onixs: false; } }
EOF
+22 -6
View File
@@ -5,6 +5,7 @@ using bts.api.core;
using bts.md;
using CommandLine;
using System.Linq;
using bts.test;
namespace bts {
@@ -26,17 +27,32 @@ namespace bts {
static void dump_mbo(uint key, api.Side side) {
using (var ctx = new ServiceContext()) {
var mbo = ctx.locate<MBOService>();
var data = mbo.get_orders_for_security(new MBOSecurityQuery { SecurityId = key });
var data = mbo.get_orders_for_security(new MBOSecurityQuery { SecurityId = key, PriceLevel = double.NaN });
Console.WriteLine($"Order data for {data.SecurityId}");
var price = 0.0;
foreach(var e in data.Bid.Concat(data.Ask)) {
if (e.Price != price) {
price = e.Price;
Console.WriteLine("================");
var color = Console.ForegroundColor;
if (side == api.Side.Sell || side == api.Side.Undefined) {
Console.ForegroundColor = ConsoleColor.Red;
foreach(var e in data.Ask.Reverse()) {
if (e.Price != price) {
price = e.Price;
Console.WriteLine("================");
}
Console.WriteLine(e);
}
Console.WriteLine(e);
}
if (side == api.Side.Buy || side == api.Side.Undefined) {
Console.ForegroundColor = ConsoleColor.Blue;
foreach(var e in data.Bid) {
if (e.Price != price) {
price = e.Price;
Console.WriteLine("================");
}
Console.WriteLine(e);
}
}
Console.ForegroundColor = color;
}
}
}
+1 -1
View File
@@ -1 +1 @@
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/spreader.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;mercury/actors/IPollableActor.cpp;mercury/broker/Broker.cpp;mercury/broker/BrokerProxy.cpp;mercury/broker/ILocalBroker.cpp;mercury/broker/IRemoteBroker.cpp;mercury/common/mercury_common.cpp;mercury/events/TimerEvent.cpp;mercury/executor/Executor.cpp;mercury/topics/Topic.cpp;metrics/metrics.cpp;metrics/system_metrics.cpp;orderman/actors/AsyncFactory.cpp;orderman/actors/ExecutionGateway.cpp;orderman/actors/FillStore.cpp;orderman/actors/MDSupplementarySource.cpp;orderman/actors/OrderStore.cpp;orderman/actors/SODPosition.cpp;orderman/actors/SecuritySrc.cpp;orderman/actors/ServiceBridge.cpp;orderman/actors/order_manager/OrderBuilder.cpp;orderman/actors/order_manager/OrderManager.cpp;orderman/actors/order_manager/OrderServices.cpp;orderman/actors/order_manager/OrderServicesData.cpp;orderman/core/IOrderServices.cLine truncated
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/spreader.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;jetfix/codec/FIXArray.cpp;jetfix/common/OutFIX.cpp;jetfix/connection/AsyncReader.cpp;jetfix/connection/IConnection.cpp;jetfix/connection/SocketConnection.cpp;jetfix/gateway/CoreGateway.cpp;jetfix/gateway/JetFIXGateway.cpp;jetfix/gateway/Session.cpp;jetfix/store/FIXFileStore.cpp;jetfix/store/session_name.cpp;jetfix/tests/async_reader_tests.cpp;jetfix/tests/decoding_tests.cpp;jetfix/tests/fix_encoding_stream_tests.cpp;jetfix/tests/fix_fragment_tests.cpp;jetfix/tests/jetfix_performance_tests.cpp;jetfix/tests/sample_fix.cpp;jetfix/tests/store_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;mercury/actors/IPollableActor.cpp;mercury/broker/Broker.cpp;mercury/broker/BrokerProxy.cpp;mercury/broker/ILocalBroker.cpp;mercury/broker/IRemoteBroker.cpp;mercury/common/mercury_common.cpp;mercury/events/TimerEvent.cpp;mercury/executor/Executor.cpp;mercury/topics/Topic.cpp;metrics/metrics.cpp;metrics/system_metrics.cpp;orderman/actors/AsyncFactory.cpp;orderman/actors/ExecutionGateway.cpp;orderman/actors/FillStore.cpp;orderman/actors/MDSupplementarySource.cpp;orderman/actors/OrderStore.cpp;orderman/actors/SODPosition.cpp;orderman/actors/SecuritySrc.cpp;orderman/actors/ServiceBridge.cpp;orderman/actors/order_manager/OrderBuilder.cpp;orderman/actors/order_manager/OrderManager.cpp;orderman/actors/order_manager/OrderServices.cpp;orderman/actors/order_manager/OrderServicesData.cpp;orderman/core/IOrderServices.cLine truncated
+1 -1
View File
@@ -1 +1 @@
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/spreader.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;os/os.cpp;os/tests/os_tests.cpp;quickbus/core/LocalNode.cpp;quickbus/core/Quickbus.cpp;quickbus/core/SubscriptionID.cpp;quickbus/core/TableID.cpp;quickbus/message_model/Update.cpp;quickbus/message_model/merge_updates.cpp;quickbus/network/QBConnection.cpp;quickbus/network/QBConnectionManager.cpp;quickbus/network/endpoint_description.cpp;quickbus/network/quickbus_msgs.proto;quickbus/pubsub/TablePublisher.cpp;quickbus/pubsub/TableSubscriber.cpp;quickbus/pubsub/tests/qb_pubsub_tests.cpp;quickbus/routing/INode.cpp;quickbus/routing/NodeID.cpp;quickbus/routing/Router.cpp;quickbus/rpc/RPCClient.cpp;quickbus/rpc/RPCServer.cpp;quickbus/rpc/RPCSessionID.cpp;quickbus/rpc/rpc_utils.cpp;quickbus/rpc/tests/qb_rpc_tests.cpp;quickbus/tests/quickbus_tests.cpp;sparkproto/SparkProtos.proto;testing/catch_test_runner.cpp;utils/AuthToken.cpp;utils/Dispatcher.cpp;utils/GrowableSPSCQueue.cpp;utils/HTTPStream.cpp;utils/PBUtils.cpp;utils/Puid.cpp;utils/SecureLogon.cpp;utils/SharedMemory.cpp;utils/Sqlite_utils.cpp;utils/Uuid.cpp;utils/datetime.cpp;utils/download.cpp;utils/file_utils.cpp;utils/init_spark_lib.cpp;utils/is_unittest_environment.cpp;utils/stats.cpp;utils/tests/AuthTokenTests.cpp;utils/thread_manager.cpp)
set(sources api/apicore.proto;api/apigateway.proto;api/apioptions.proto;api/core/ApiClientInterop.cpp;api/core/ApiServerInterop.cpp;api/public/ApiServer.cpp;api/public/ClientChannel.cpp;api/public/QuickbusClientChannel.cpp;api/public/ServerChannel.cpp;api/public/ServiceContext.cpp;api/service_helpers/AdminServiceUtils.cpp;api/service_helpers/AlgoServiceUtils.cpp;api/services/AdminService.proto;api/services/AlgoDefinitionService.proto;api/services/AlgoService.proto;api/services/ApiUnitTestService.proto;api/services/AuthService.proto;api/services/ErrorReportService.proto;api/services/HelloService.proto;api/services/MBOService.proto;api/services/MarketDataAuxiliaryService.proto;api/services/MarketDataRelay.proto;api/services/OrderService.proto;api/services/OutOfProcessURLListener.proto;api/services/PersistenceService.proto;api/services/PositionService.proto;api/services/ShellDiagnosticService.proto;api/services/ShellLatencyService.proto;api/services/SyntheticSecurityService.proto;api/test/ApiTests.cpp;api/test/HelloServiceServer.cpp;config/ConfigCheck.cpp;config/ConfigDownloader.cpp;config/ConfigErrorCollector.cpp;config/ConfigLog.cpp;config/ConfigPropertyEvaluator.cpp;config/ConfigValueQueue.cpp;config/ConfigurationException.cpp;config/ConfigurationState.cpp;config/PreprocessInputStream.cpp;config/config.cpp;config/defaults/MarketData.conf;config/defaults/Ports.conf;config/defaults/Symbols.conf;config/defaults/cme_reference_data.conf;config/defaults/edge.conf;config/defaults/jetfix.conf;config/defaults/logging.conf;config/defaults/orderman_cfg.conf;config/defaults/orders_fills_cleanup.conf;config/defaults/spreader.conf;config/defaults/testkeys.conf;config/defaults/threads.conf;config/defaults/toggles.conf;config/extensions/extensions.proto;config/invalid/invalid.proto;config/protos/ExchangeSimCfg.proto;config/protos/MarketData.proto;config/protos/Ports.proto;config/protos/Symbols.proto;config/protos/UI.proto;config/protos/cme_reference_data.proto;config/protos/jetfix.proto;config/protos/logging.proto;config/protos/mercury_cfg.proto;config/protos/orderman_cfg.proto;config/protos/orders_fills_cleanup.proto;config/protos/perflog_cfg.proto;config/protos/quickbus_cfg.proto;config/protos/spreader.proto;config/protos/system.proto;config/protos/testkeys.proto;config/protos/threads.proto;config/protos/toggles.proto;config/tests/HTTPServer.cpp;config/tests/config_ref_tests.cpp;config/tests/config_tests.cpp;config/text_format/text_format.cpp;domain_model/Security.cpp;domain_model/Security.proto;domain_model/SecurityTests.cpp;domain_model/tests/domain_tests.cpp;expression/BooleanGrammar.cpp;expression/ExpressionEvaluator.cpp;expression/ParserExceptions.cpp;expression/Value.cpp;expression/tests/expression_parser_tests.cpp;logging/LoggerHub.cpp;logging/Sink.cpp;logging/logging.cpp;logging/tests/logging_tests.cpp;md/Diag/MDPeriodicDumper.cpp;md/Diag/ShmDumper.cpp;md/MBOProcessor.cpp;md/MBOServiceServer.cpp;md/MDClient.cpp;md/MDConsumer.cpp;md/MDConsumerHandle.cpp;md/MDConsumerInterop.cpp;md/MDProducer.cpp;md/MDProducerHandle.cpp;md/MemorySegmentUtils.cpp;md/PlatformLoader.cpp;md/PriceSize.cpp;md/SecurityCache.cpp;md/SecurityDB.cpp;md/SecurityIDRecorder.cpp;md/SecurityOracle.cpp;md/SettlementRecorder.cpp;md/Shm/FixedSecurity.cpp;md/SyntheticSecurityService.cpp;md/cme/MDP3Utils.cpp;md/common.cpp;md/ice/ICEUtils.cpp;md/protos/co2.proto;md/relay/QueryServer.cpp;md/relay/RelayClient.cpp;md/relay/RelayRecorder.cpp;md/test/Co2ManagedTestHelper.cpp;md/test/Co2TestInstrument.cpp;md/test/mbo_tests.cpp;md/test/md2_tests.cpp;md/test/md_test_utils.cpp;md/test/synthetic_book_merge_tests.cpp;os/os.cpp;os/tests/os_tests.cpp;quickbus/core/LocalNode.cpp;quickbus/core/Quickbus.cpp;quickbus/core/SubscriptionID.cpp;quickbus/core/TableID.cpp;quickbus/message_model/Update.cpp;quickbus/message_model/merge_updates.cpp;quickbus/network/QBConnection.cpp;quickbus/network/QBConnectionManager.cpp;quickbus/network/endpoint_description.cpp;quickbus/network/quickbus_msgs.proto;quickbus/pubsub/TablePublisher.cpp;quickbus/pubsub/TableSubscriber.cpp;quickbus/pubsub/tests/qb_pubsub_tests.cpp;quickbus/routing/INode.cpp;quickbus/routing/NodeID.cpp;quickbus/routing/Router.cpp;quickbus/rpc/RPCClient.cpp;quickbus/rpc/RPCServer.cpp;quickbus/rpc/RPCSessionID.cpp;quickbus/rpc/rpc_utils.cpp;quickbus/rpc/tests/qb_rpc_tests.cpp;quickbus/tests/quickbus_tests.cpp;sparkproto/SparkProtos.proto;testing/catch_test_runner.cpp;utils/AuthToken.cpp;utils/Dispatcher.cpp;utils/GrowableSPSCQueue.cpp;utils/HTTPStream.cpp;utils/PBUtils.cpp;utils/Puid.cpp;utils/SecureLogon.cpp;utils/SharedMemory.cpp;utils/Sqlite_utils.cpp;utils/Uuid.cpp;utils/datetime.cpp;utils/download.cpp;utils/file_utils.cpp;utils/init_spark_lib.cpp;utils/is_unittest_environment.cpp;utils/stats.cpp;utils/tests/AuthTokenTests.cpp;utils/tests/SingleWriterConcurrentVectorTests.cpp;utils/thread_manager.cpp)
+8 -4
View File
@@ -58,7 +58,7 @@ namespace interop {
// Functions used for interop to other languages
/////////////////////////////////////////////////
std::string getbuffer_serialized_buffer;
extern "C" BTS_EXPORT const char* get_buffer(const char* key_name, int& out_length)
{
@@ -69,7 +69,7 @@ extern "C" BTS_EXPORT const char* get_buffer(const char* key_name, int& out_leng
getbuffer_serialized_buffer.clear();
message.SerializePartialToString(&getbuffer_serialized_buffer);
out_length = getbuffer_serialized_buffer.size();
return getbuffer_serialized_buffer.c_str();
}
@@ -89,10 +89,14 @@ namespace internal {
// Functions used in testing
//////////////////////////////////////////
extern "C" BTS_EXPORT void initialize_for_test(const std::string& values, bool print_errors)
extern "C" BTS_EXPORT void initialize_for_test(const char* values, bool print_errors)
{
config_state.reset();
config_state.load_string(values, print_errors);
config_state.load_string(std::string(values), print_errors);
}
void initialize_for_test(const std::string& value, bool print_errors) {
config_state.reset();
config_state.load_string(value, print_errors);
}
extern "C" BTS_EXPORT void reset()
+3 -2
View File
@@ -22,7 +22,7 @@ namespace config {
// Load and initialize config.
// This will be automatically called on the first key request but since loading config
// can result in network requests this function is available for applications that want to
// manage pauses in execution.
// manage pauses in execution.
// Ex: initialize() on a background thread so that config::get<T>() is fast on a UI thread
extern "C" BTS_EXPORT void initialize();
@@ -56,7 +56,8 @@ namespace config {
{
// Initialize configuration state from a string instead of from the filesystem. defaults.conf will still be loaded.
// Calling this a second time clears all values from previous initialization
extern "C" BTS_EXPORT void initialize_for_test(const std::string& configuration, bool print_errors=true);
extern "C" BTS_EXPORT void initialize_for_test(const char* configuration, bool print_errors=true);
void initialize_for_test(const std::string& configuration, bool print_errors=true);
// Releases all resources and returns config to factory defaults
extern "C" BTS_EXPORT void reset();
+3 -1
View File
@@ -27,7 +27,9 @@ message ToastNotifications {
message SoundNotifications {
optional bool enabled = 1 [default = true];
}
message FilterExpiredFills {
optional bool enabled = 1 [default = true];
}
message VolumeAtPrice{
optional bool enabled = 1 [default = true];
}
+54 -11
View File
@@ -3,31 +3,74 @@
#include <google/protobuf/text_format.h>
#include "lib/utils/datetime.h"
#include <boost/date_time/posix_time/posix_time.hpp>
namespace bts {
namespace securities {
std::string to_str (api::Security const&sec)
{
std::string to_str(api::Security const &sec) {
std::string serialized;
google::protobuf::TextFormat::PrintToString (sec, &serialized);
google::protobuf::TextFormat::PrintToString(sec, &serialized);
return serialized;
}
std::pair<bool, api::Security> from_str(std::string const&str)
{
std::pair<bool, api::Security> from_str(std::string const &str) {
api::Security result;
auto success = google::protobuf::TextFormat::ParseFromString (str,&result);
return std::make_pair(success,result);
auto success = google::protobuf::TextFormat::ParseFromString(str, &result);
return std::make_pair(success, result);
}
std::string descriptor(api::Security const&sec)
{
std::string descriptor(api::Security const &sec) {
if (sec.has_glbx())
return sec.glbx().security_desc();
else if (sec.has_ice())
return sec.ice().security_desc();
return sec.ice().security_desc();
else
return "No descriptor for non glbx exchange";
}
}}
boost::gregorian::date smallest_leg(
::google::protobuf::RepeatedPtrField<::bts::api::ContractLeg> const &l) {
bts_assert(l.size());
auto smallest = expiration(l[0].security());
for (const auto &leg : l) {
auto this_date = expiration(leg.security());
if (this_date < smallest) {
smallest = this_date;
}
}
return smallest;
}
boost::gregorian::date expiration(api::Security const &security) {
switch (security.Contract_case()) {
case api::Security::kFuture:
return {
boost::gregorian::greg_year(security.future().expiration_year()),
boost::gregorian::greg_month(security.future().expiration_month()),
boost::gregorian::greg_day(security.future().expiration_day())};
case api::Security::kOption:
return {
boost::gregorian::greg_year(security.option().expiration_year()),
boost::gregorian::greg_month(security.option().expiration_month()),
boost::gregorian::greg_day(security.option().expiration_day())};
case api::Security::kSpread:
return smallest_leg(security.spread().legs());
case api::Security::kSynthetic:
return smallest_leg(security.synthetic().legs());
default:
return {};
}
}
bool is_expired(api::Security const &security) {
auto e = expiration(security);
if (e == boost::gregorian::date())
return false;
else
return e < boost::posix_time::microsec_clock::universal_time().date();
}
} // namespace securities
} // namespace bts
+5
View File
@@ -7,6 +7,8 @@
#include "lib/domain_model/common.h"
#include "lib/utils/bts_assert.h"
namespace boost {namespace gregorian {class date;}}
namespace bts {
namespace api {
@@ -149,6 +151,9 @@ inline Price round_to_quote_price(api::Security const &sec, Price in,
raw_round_to_quote_price(p, sec.contract_spec().low_tick(), side));
}
boost::gregorian::date expiration(api::Security const&sec);
bool is_expired(api::Security const &security) ;
inline Price tick_size(api::Security const &sec) {
return Price(sec.contract_spec().low_tick());
}
+123 -32
View File
@@ -1,6 +1,10 @@
#include "catch/catch.hpp"
#include "../Security.h"
#include "catch/catch.hpp"
#include "lib/domain_model/Security.h"
#include "lib/utils/PBUtils.h"
#include <boost/date_time/gregorian/gregorian.hpp>
namespace bts {
namespace test {
@@ -8,33 +12,30 @@ namespace test {
TEST_CASE("rounding_tests", "[md]") {
SECTION("test_price_size") {
const double tick_sizes[] {
// Dan's production system
// ./sparkd configcheck Symbols | grep future | sed -e 's/.*future: \"\(.*\)\"/\1/' | xargs -I{} sh -c "./sparkd md dump {} future glbx" | grep -o low_tick': [0-9.]\+
0.0078125,
0.015625,
0.03125,
0.1,
0.25,
1,
// From Kevin's ramblings
// 2.5/320,
// 31./32,
// 10./32,
// 10.25/32,
// 10.375/32,
// 10.125/32,
// 10.25/32,
// .0025,
// .0000001,
// 1./32,
// 0.5/32,
// 0.25/32,
// 0.125/32,
// 0.01,
// 0.005,
// 1./100000,
// 1./16,
const double tick_sizes[]{
// Dan's production system
// ./sparkd configcheck Symbols | grep future | sed -e 's/.*future:
// \"\(.*\)\"/\1/' | xargs -I{} sh -c "./sparkd md dump {} future
// glbx" | grep -o low_tick': [0-9.]\+
0.0078125, 0.015625, 0.03125, 0.1, 0.25, 1,
// From Kevin's ramblings
// 2.5/320,
// 31./32,
// 10./32,
// 10.25/32,
// 10.375/32,
// 10.125/32,
// 10.25/32,
// .0025,
// .0000001,
// 1./32,
// 0.5/32,
// 0.25/32,
// 0.125/32,
// 0.01,
// 0.005,
// 1./100000,
// 1./16,
};
for (double tick : tick_sizes) {
@@ -43,15 +44,105 @@ TEST_CASE("rounding_tests", "[md]") {
for (double price = 0; price < 101; price += tick) {
// Since all prices are valid ticks rounding should not change them
const double epsilon = 1e-9;
for (api::Side side : { api::Side::SIDE_BUY, api::Side::SIDE_SELL }) {
double rounded = securities::raw_round_to_quote_price(price, tick, side);
REQUIRE(std::abs(price-rounded) <= epsilon);
for (api::Side side : {api::Side::SIDE_BUY, api::Side::SIDE_SELL}) {
double rounded =
securities::raw_round_to_quote_price(price, tick, side);
REQUIRE(std::abs(price - rounded) <= epsilon);
}
}
}
}
}
auto spread_txt = "id: 17\n"
"symbol: \"ES\"\n"
"exchange: VENUE_GLBX\n"
"contract_spec {\n"
" low_tick: 0.05\n"
" high_tick: 0.05\n"
" price_multiplier: 0.01\n"
"}\n"
"spread {\n"
" legs {\n"
" security {\n"
" id: 6\n"
" symbol: \"ES\"\n"
" exchange: VENUE_GLBX\n"
" contract_spec {\n"
" low_tick: 0.25\n"
" high_tick: 0.25\n"
" cash_per_point: 50\n"
" price_multiplier: 0.01\n"
" }\n"
" future {\n"
" expiration_year: 2019\n"
" expiration_month: 1\n"
" expiration_day: 20\n"
" }\n"
" glbx {\n"
" exchange_id: 16145\n"
" security_desc: \"ESZ9\"\n"
" cfi_code: \"FFIXSX\"\n"
" cme_symbol: \"ES\"\n"
" market_segment_id: 64\n"
" }\n"
" }\n"
" quantity: 1\n"
" side: SIDE_SELL\n"
" leg_delta: 1\n"
" }\n"
" legs {\n"
" security {\n"
" id: 2\n"
" symbol: \"ES\"\n"
" exchange: VENUE_GLBX\n"
" contract_spec {\n"
" low_tick: 0.25\n"
" high_tick: 0.25\n"
" cash_per_point: 50\n"
" price_multiplier: 0.01\n"
" }\n"
" future {\n"
" expiration_year: 3020\n"
" expiration_month: 6\n"
" expiration_day: 19\n"
" }\n"
" glbx {\n"
" exchange_id: 21336\n"
" security_desc: \"ESM0\"\n"
" cfi_code: \"FFIXSX\"\n"
" cme_symbol: \"ES\"\n"
" market_segment_id: 64\n"
" }\n"
" }\n"
" quantity: 1\n"
" side: SIDE_BUY\n"
" leg_delta: 1\n"
" }\n"
"}\n"
"glbx {\n"
" exchange_id: 35451\n"
" security_desc: \"ESZ9-ESM0\"\n"
" cfi_code: \"FMIXSX\"\n"
" cme_symbol: \"ES\"\n"
" market_segment_id: 64\n"
"}";
TEST_CASE("expiration_tests", "[security]") {
SECTION("test_expiration_retrieval") {
}}
using namespace boost::gregorian;
auto spread = utils::pb::from_string<api::Security>(spread_txt);
REQUIRE(securities::expiration(spread) ==
date(greg_year(2019), greg_month(1), greg_day(20)));
auto &leg = spread.spread().legs(1).security();
REQUIRE(securities::expiration(leg) ==
date(greg_year(3020), greg_month(6), greg_day(19)));
REQUIRE(securities::is_expired(spread));
REQUIRE(!securities::is_expired(leg));
}
}
} // namespace test
} // namespace bts
+3 -1
View File
@@ -207,6 +207,8 @@ const NativeOrderState* native_order_state(const OrderRecord& record) {
return &record.state().stop().native_state();
case OrderState::kStopLimit:
return &record.state().stop_limit().native_state();
case OrderState::kGlbxIceberg:
return &record.state().glbx_iceberg().native_state();
default:
return nullptr;
}
@@ -352,4 +354,4 @@ void MBOServiceServer::get_orders_for_security(const MBOSecurityQuery& request,
}
}} //namespace
#endif
#endif
+10 -30
View File
@@ -8,6 +8,7 @@
#include "lib/config/protos/system.pb.h"
#include "lib/md/Shm/FixedSecurity.h"
#include "lib/config/protos/MarketData.pb.h"
#include "lib/domain_model/Security.h"
#include <boost/container/flat_set.hpp>
#include <boost/filesystem.hpp>
@@ -141,7 +142,15 @@ Result insert_or_replace(const std::vector<SecurityPtr>& securities) {
insert.bind(3, psecurity->exchange());
insert.bind(4, securitytype_of(*psecurity));
insert.bind(5, buffer);
insert.bind(6, expiration(*psecurity).to_short());
auto greg_date = securities::expiration(*psecurity);
if (greg_date == boost::gregorian::date()) {
insert.bind(6, datetime::Date(9999,1,1).to_short());
} else {
ushort year = greg_date.year();
ushort month = greg_date.month();
ushort day = greg_date.day();
insert.bind(6, datetime::Date(year,month,day).to_short());
}
insert.exec();
insert.reset();
}
@@ -204,33 +213,4 @@ SecurityPtr find_security(SecurityKey) { return nullptr; }
#endif
template <class L>
datetime::Date smallest_leg(L l) {
// Iterate though all legs and choose the smallest
datetime::Date date(std::numeric_limits<uint16_t>::max(),0,0);
for (const auto& leg : l) {
auto legDate = expiration(leg.security());
if (legDate < date) {
date = legDate;
}
}
return date;
}
datetime::Date expiration(const api::Security& security) {
switch(security.Contract_case()) {
case api::Security::kFuture:
return datetime::Date(security.future().expiration_year(), security.future().expiration_month(), security.future().expiration_day());
case api::Security::kOption:
return datetime::Date(security.option().expiration_year(), security.option().expiration_month(), security.option().expiration_day());
case api::Security::kSpread:
return smallest_leg(security.spread().legs());
case api::Security::kSynthetic:
return smallest_leg(security.synthetic().legs());
default:
// return a 0 date which will always be considered expired.
return datetime::Date();
}
}
}}}//namespace
+1 -2
View File
@@ -36,6 +36,5 @@ Result insert_or_replace(const std::vector<SecurityPtr>&);
std::vector<api::synthetic::Metadata> load_metadata();
bool store_metadata(const api::synthetic::Metadata&);
datetime::Date expiration(const api::Security& security);
}}}//namespace
}}}//namespace
+2 -2
View File
@@ -1270,10 +1270,10 @@ struct Co2RelayTests {
// That's right. Expirations daily.
out_producers->push_back(
producer.add_security(
*mkfixedfuture("FOO", invalid_security_key, 2017, 1+(x/28), x%28)));
*mkfixedfuture("FOO", invalid_security_key, 2017, 1+(x/28), x%27+1)));
out_producers->push_back(
producer.add_security(
*mkfixedfuture("FOOF", invalid_security_key, 2017, 1+(x/28), x%28)));
*mkfixedfuture("FOOF", invalid_security_key, 2017, 1+(x/28), x%27+1)));
}
assign_ids(out_producers->begin(), out_producers->end(), 1000);
+18 -8
View File
@@ -3,6 +3,7 @@
#include "lib/jetfix/common/OutFIX.h"
#include "lib/jetfix/gateway/JetFIXGateway.h"
#include "lib/logging/logging.h"
#include "lib/md/SecurityOracle.h"
#include "lib/orderman/events/CancelRejectEvent.h"
#include "lib/orderman/events/GatewayStatusChangeEvent.h"
#include "lib/orderman/events/NewExecutionReportEvent.h"
@@ -24,10 +25,11 @@ using JFGw =
fix::RejectMsg, fix::CancelRejectMsg>;
struct ExecutionGateway::PImpl {
PImpl(config::JetFIX::Connection const &cfg, trace::PerfLog &l)
PImpl(config::JetFIX::Connection const &cfg, trace::PerfLog &l,
md::SecurityOracle o)
: gw(cfg, l), exe_report_pool(), state(State::WAITING_FOR_SECURITIES),
securities(), securities_by_descriptor(), gw_name(cfg.name()),
cfg(cfg), perf_log(l) {}
cfg(cfg), perf_log(l), sec_oracle(o) {}
JFGw gw;
boost::object_pool<ExecutionReport> exe_report_pool;
@@ -41,11 +43,12 @@ struct ExecutionGateway::PImpl {
OnGwStatusChange on_gw_status_change;
OnCancelReject on_cancel_reject;
trace::PerfLog &perf_log;
md::SecurityOracle sec_oracle;
};
ExecutionGateway::ExecutionGateway(config::JetFIX::Connection const &cfg,
trace::PerfLog &log)
: me(new PImpl(cfg, log)) {
trace::PerfLog &log, md::SecurityOracle o)
: me(new PImpl(cfg, log, o)) {
me->state = State::WAITING_FOR_SECURITIES;
}
ExecutionGateway::~ExecutionGateway() {}
@@ -111,11 +114,17 @@ void ExecutionGateway::on_exe_report(
SecurityKey sk;
decode_clorid(msg.get<fix::ClOrdID>(), &ok, &sk);
auto scoped_log = trace::log_scope(me->perf_log, msg.trace(), "ExecutionGateway::on_exe_report", ok.value());
auto scoped_log =
trace::log_scope(me->perf_log, msg.trace(),
"ExecutionGateway::on_exe_report", ok.value());
LOG(Latency, Info, "Started handling of execution report for order {}",
ok.value());
auto sec = me->securities[sk.value()];
auto &sec = me->securities[sk.value()];
if (!sec) {
sec = me->sec_oracle.get_security(SecurityKey(sk.value()));
me->securities_by_descriptor[securities::descriptor(*sec)] = sec;
}
if (!sec)
LOG(OrderMan, Error,
@@ -303,8 +312,9 @@ void ExecutionGateway::dispatch_incoming_fix_messages() {
void ExecutionGateway::send_fix(jetfix::OutFIXPtr const &fix,
trace::CorID trace) {
auto scoped_log = trace::log_scope(me->perf_log,trace,"ExecutionGateway::send_fix");
me->gw.send(fix,trace);
auto scoped_log =
trace::log_scope(me->perf_log, trace, "ExecutionGateway::send_fix");
me->gw.send(fix, trace);
me->gw.dispatch_events();
}
+3 -1
View File
@@ -9,6 +9,7 @@
#include <memory>
namespace bts {
namespace md { class SecurityOracle;}
namespace trace {struct CorID;class PerfLog;}
namespace orderman {
class NewSecuritiesEvent;
@@ -22,7 +23,8 @@ using OnCancelReject = std::function<void(OrderKey, std::string const &)>;
// fix execution gateway for a single FIX session
class ExecutionGateway {
public:
ExecutionGateway(config::JetFIX::Connection const &, trace::PerfLog &);
ExecutionGateway(config::JetFIX::Connection const &, trace::PerfLog &,
md::SecurityOracle);
~ExecutionGateway();
void start();
+13 -5
View File
@@ -1,5 +1,8 @@
#include "FillStore.h"
#include "lib/api/services/OrderService.pb.h"
#include "lib/config/config.h"
#include "lib/config/protos/toggles.pb.h"
#include "lib/domain_model/Security.h"
#include "lib/orderman/actors/SODPosition.h"
#include "lib/orderman/events/NewExecutionReportEvent.h"
#include "lib/orderman/events/OrderDefinitionUpdateEvent.h"
@@ -53,10 +56,15 @@ void FillStore::init() {
auto sec = me->oracle.get_security(SecurityKey(fill.security_key()));
if (sec) {
auto fk = kvp.first;
if (!me->sod_pos_map.add_fill_to_sod_pos(fill))
stored_batch.emplace_back(fill, *sec);
me->known_fills.insert(fk);
if (config::get<config::toggles::FilterExpiredFills>().enabled() &&
securities::is_expired(*sec))
continue;
else {
auto fk = kvp.first;
if (!me->sod_pos_map.add_fill_to_sod_pos(fill))
stored_batch.emplace_back(fill, *sec);
me->known_fills.insert(fk);
}
} else
LOG(persist, Error,
"Stored fill {} contains unresolved security {}. ", kvp.first,
@@ -78,7 +86,7 @@ void FillStore::init() {
// account ids for future execution reports
subscribe<OrderDefinitionUpdateEvent>(
[this](auto const &ev) { this->on_order_definition_update(ev); });
}
} // namespace orderman
FillStore::~FillStore() {}
@@ -118,7 +118,7 @@ void OrderManager::on_fill_recovery(
boost::algorithm::trim_copy(ev.fill.account());
me->services_data.limiter.add(*ev.security, trimmed_account_name,
ev.fill.side(),
Quantity(ev.fill.last_shares()));
Quantity(ev.fill.last_shares()),false);
}
me->stored_order_sub = subscribe<StoredOrderEvent>(
@@ -16,7 +16,7 @@ OrderServicesData::OrderServicesData(OrderManager &owner,
auto &all_connections = config::get<config::JetFIX>().connection();
for (auto const &c : all_connections)
sessions.push_back(std::make_unique<SessionServicesData>(c, l));
sessions.push_back(std::make_unique<SessionServicesData>(c, l,o));
}
} // namespace orderman
} // namespace bts
@@ -48,10 +48,10 @@ struct ParentKey {
struct SessionServicesData {
SessionServicesData(config::JetFIX::Connection const &cfg,
trace::PerfLog &log)
trace::PerfLog &log, md::SecurityOracle o)
: session(cfg.name()), gw_connected(false), cfg(cfg),
governor(cfg.max_order_msgs_per_window(), cfg.window_duration_msecs()),
gw(cfg, log) {}
gw(cfg, log,o) {}
std::string session;
std::vector<jetfix::OutFIXPtr> free_buffers;
+17 -17
View File
@@ -62,29 +62,29 @@ public:
void copy_positions_from(LimitStore& source);
void add_position(const api::Security& security,
AccountID account_id, api::Side side,
Quantity quantity){
Quantity quantity, bool recurse){
SecurityLimitTree* sec_tree = _find_security(security.id());
bts_assert(sec_tree != nullptr);
sec_tree->add_position(account_id, side, quantity);
if (security.has_spread()){
if (recurse && security.has_spread()){
for (const api::ContractLeg& leg : security.spread().legs()){
api::Side effective_leg_side = side == api::Side::SIDE_BUY ? leg.side() : LimiterUtils::invert_side(leg.side());
Quantity effective_leg_qty = Quantity (quantity.value() * std::abs(leg.quantity()));
add_position (leg.security(), account_id, effective_leg_side, effective_leg_qty);
add_position (leg.security(), account_id, effective_leg_side, effective_leg_qty, recurse);
}
}
}
void remove_position(const api::Security& security,
AccountID account_id,
api::Side side, Quantity quantity){
api::Side side, Quantity quantity, bool recurse){
SecurityLimitTree* sec_tree = _find_security(security.id());
bts_assert(sec_tree != nullptr);
sec_tree->remove_position(account_id, side, quantity);
if (security.has_spread()){
if (recurse && security.has_spread()){
for (const api::ContractLeg& leg : security.spread().legs()){
api::Side effective_leg_side = side == api::Side::SIDE_BUY ? leg.side() : LimiterUtils::invert_side(leg.side());
Quantity effective_leg_qty = Quantity (quantity.value() * std::abs(leg.quantity()));
remove_position (leg.security(), account_id, effective_leg_side, effective_leg_qty);
remove_position (leg.security(), account_id, effective_leg_side, effective_leg_qty,recurse);
}
}
}
@@ -98,7 +98,7 @@ public:
//iv. account should be activated
Limiter::LimiterQueryResult can_trade(UserID user_id,
AccountID account_id) const{
Limiter::LimiterQueryResult result; result.ok = true;
Limiter::LimiterQueryResult result; result.result = Limiter::OK;
auto user_it = _user_data_map.find(user_id.value());
if (user_it != _user_data_map.end()){
const UserData& user_data = user_it->second;
@@ -106,23 +106,23 @@ public:
const api::admin::BTSAccount* account_ptr = user_data.find_account(account_id);
if (account_ptr != nullptr){
if (!account_ptr->is_active()){
result.ok=false;
result.result=Limiter::ACCOUNT_DEACTIVATED;
result.error_msg = "Account is de-activated";
}
}
else{
result.ok=false;
result.result=Limiter::NO_SUCH_ACCOUNT;
result.error_msg = "Can not find account";
}
}
else
{
result.ok=false;
result.result=Limiter::USER_DEACTIVATED;
result.error_msg = "User is de-activated";
}
}
else{
result.ok=false;
result.result=Limiter::NO_SUCH_USER;
result.error_msg = "Can not find user";
}
return result;
@@ -131,11 +131,11 @@ public:
const api::Security& security,
api::Side side,
Quantity quantity) const{
Limiter::LimiterQueryResult rv; rv.ok = true;
Limiter::LimiterQueryResult rv; rv.result = Limiter::OK;
const SecurityLimitTree* sec_tree = _find_security(security.id());
bts_assert(sec_tree != nullptr);
rv = sec_tree->can_send_new_order(account_id, side, quantity);
if (rv.ok && security.has_spread()){
if (rv.ok() && security.has_spread()){
// if its rejected we dont need to validate further,
// however if its ok and its a spread we need to check
// the legs
@@ -143,7 +143,7 @@ public:
api::Side effective_leg_side = side == api::Side::SIDE_BUY ? leg.side() : LimiterUtils::invert_side(leg.side());
Quantity effective_leg_qty = Quantity (quantity.value() * std::abs(leg.quantity()));
rv = can_send_new_order (account_id, leg.security(), effective_leg_side, effective_leg_qty);
if (!rv.ok)
if (!rv.ok())
return rv;
}
}
@@ -153,18 +153,18 @@ public:
const api::Security& security,
api::Side side,
Quantity from, Quantity to) const{
Limiter::LimiterQueryResult rv; rv.ok = true;
Limiter::LimiterQueryResult rv; rv.result = Limiter::OK;
const SecurityLimitTree* sec_tree = _find_security(security.id());
bts_assert(sec_tree != nullptr);
rv = sec_tree->can_update_order(account_id, side, from, to);
// same logic for the spread legs as above
if (rv.ok && security.has_spread()){
if (rv.ok() && security.has_spread()){
for (const api::ContractLeg& leg : security.spread().legs()){
api::Side effective_leg_side = side == api::Side::SIDE_BUY ? leg.side() : LimiterUtils::invert_side(leg.side());
Quantity effective_leg_from_qty = Quantity (from.value() * std::abs(leg.quantity()));
Quantity effective_leg_to_qty = Quantity (to.value() * std::abs(leg.quantity()));
rv = can_update_order (account_id, leg.security(), effective_leg_side, effective_leg_from_qty, effective_leg_to_qty);
if (!rv.ok)
if (!rv.ok())
return rv;
}
}
+25 -14
View File
@@ -33,45 +33,45 @@ void Limiter::add_security(api::Security const &security) {
_reset(me->limit_store->admin_data());
}
void Limiter::add(api::Security const &security, AccountID account_id,
api::Side side, Quantity quantity) {
api::Side side, Quantity quantity, bool recurse) {
if (!_contains_security(&security))
add_security(security);
if (!me->limit_store->has_account(account_id)) {
// add a temporary account, so trades can go in there
_add_account_and_reset(account_id);
}
me->limit_store->add_position(security, account_id, side, quantity);
me->limit_store->add_position(security, account_id, side, quantity, recurse);
}
void Limiter::remove(api::Security const &security, AccountID account_id,
api::Side side, Quantity quantity) {
api::Side side, Quantity quantity, bool recurse) {
if (!_contains_security(&security))
add_security(security);
if (!me->limit_store->has_account(account_id)) {
_add_account_and_reset(account_id);
}
me->limit_store->remove_position(security, account_id, side, quantity);
me->limit_store->remove_position(security, account_id, side, quantity, recurse);
}
void Limiter::add(const api::Security &security,
const std::string &account_name, api::Side side,
Quantity quantity) {
Quantity quantity, bool recurse) {
const auto *acct_ptr = bts::admin::AdminServiceUtils::find_account(
me->limit_store->admin_data(), account_name);
if (acct_ptr == nullptr)
LOG(OrderMan, Error, "Limiter::add can not find account:{}",
account_name);
else
add(security, AccountID(acct_ptr->id()), side, quantity);
add(security, AccountID(acct_ptr->id()), side, quantity, recurse);
}
void Limiter::remove(const api::Security &security,
const std::string &account_name, api::Side side,
Quantity quantity) {
Quantity quantity, bool recurse) {
const auto *acct_ptr = bts::admin::AdminServiceUtils::find_account(
me->limit_store->admin_data(), account_name);
if (acct_ptr == nullptr)
LOG(OrderMan, Error, "Limiter::remove can not find account:{}",
account_name);
else
remove(security, AccountID(acct_ptr->id()), side, quantity);
remove(security, AccountID(acct_ptr->id()), side, quantity, recurse);
}
Limiter::LimiterQueryResult Limiter::can_trade(UserID user_id,
AccountID account_id) const {
@@ -80,21 +80,32 @@ Limiter::LimiterQueryResult Limiter::can_trade(UserID user_id,
}
Limiter::LimiterQueryResult
Limiter::can_send_new_order(AccountID account_id, api::Security const &security,
api::Side side, Quantity quantity) const {
LimiterQueryResult rv = me->limit_store->can_send_new_order(
api::Side side, Quantity quantity) {
LimiterQueryResult rv =
me->limit_store->can_send_new_order(
account_id, security, side, quantity);
// rv.ok = true;
if (rv.result == Limiter::NO_SUCH_SECURITY) {
add_security(security);
rv = me->limit_store->can_send_new_order(
account_id, security, side, quantity);
}
return rv;
}
Limiter::LimiterQueryResult
Limiter::can_update_order(AccountID account_id, api::Security const &security,
api::Side side, Quantity from, Quantity to) const {
api::Side side, Quantity from, Quantity to) {
LimiterQueryResult rv;
if (from == to)
rv.ok = true;
else
rv.result = Limiter::OK;
else {
rv = me->limit_store->can_update_order(account_id, security, side, from,
to);
if (rv.result == Limiter::NO_SUCH_SECURITY) {
add_security(security);
rv = me->limit_store->can_update_order(account_id, security, side, from,
to);
}
}
return rv;
}
void Limiter::_add_account_and_reset(AccountID account_id) {
+26 -7
View File
@@ -24,26 +24,45 @@ class Limiter {
void add_securities(std::vector<api::Security const *> const &secs);
void add_security(api::Security const &new_sec);
void add(api::Security const &, AccountID, api::Side, Quantity);
void remove(api::Security const &, AccountID, api::Side, Quantity);
void add(api::Security const &, AccountID, api::Side, Quantity,
bool recurse);
void remove(api::Security const &, AccountID, api::Side, Quantity,
bool recurse);
void add(api::Security const &, std::string const & /* account_name */,
api::Side, Quantity);
api::Side, Quantity, bool recurse);
void remove(api::Security const &, std::string const & /* account_name */,
api::Side, Quantity);
api::Side, Quantity, bool recurse);
enum LimiterErrorCode {
OK,
NO_SUCH_SECURITY,
SEC_LONG_POS_BREACHED,
PROD_LONG_POS_BREACHED,
SEC_SHORT_POS_BREACHED,
PROD_SHORT_POS_BREACHED,
CLIP_SIZE_BREACHED,
ACCOUNT_DEACTIVATED,
NO_SUCH_ACCOUNT,
USER_DEACTIVATED,
NO_SUCH_USER
};
struct LimiterQueryResult {
bool ok;
std::string error_msg;
LimiterErrorCode result;
bool ok() const { return result == OK; }
bool error() const { return result != OK; }
};
LimiterQueryResult can_trade(UserID user, AccountID into_account) const;
LimiterQueryResult can_send_new_order(AccountID into_account,
api::Security const &sec,
api::Side side,
Quantity quantity) const;
Quantity quantity) ;
LimiterQueryResult can_update_order(AccountID account,
api::Security const &sec, api::Side side,
Quantity from, Quantity to) const;
Quantity from, Quantity to) ;
~Limiter();
// for testing purposes only
LimitStore &limit_store();
+32 -20
View File
@@ -16,7 +16,7 @@ public:
int short_pos_limit,
int net_pos,
Limiter::LimiterQueryResult& out_res){
out_res.ok = true;
out_res.result = Limiter::OK;
bts_assert(long_pos_limit >= 0);
bts_assert(short_pos_limit <= 0);
int net_pos_new = 0;
@@ -25,18 +25,24 @@ public:
else if(side == api::Side::SIDE_SELL)
net_pos_new = net_pos - quantity.value();
if (net_pos_new > long_pos_limit && side==api::Side::SIDE_BUY){
out_res.ok = false;
if (is_security_level)
if (is_security_level) {
out_res.result = Limiter::SEC_LONG_POS_BREACHED;
out_res.error_msg = fmt::format("Security Long Position (Max {} Current {})", long_pos_limit, net_pos);
else
}
else {
out_res.result = Limiter::PROD_LONG_POS_BREACHED;
out_res.error_msg = fmt::format("Product Long Position (Max {} Current {})", long_pos_limit, net_pos);
}
}
else if (net_pos_new < short_pos_limit && side==api::Side::SIDE_SELL){
out_res.ok = false;
if (is_security_level)
if (is_security_level) {
out_res.result = Limiter::SEC_SHORT_POS_BREACHED;
out_res.error_msg = fmt::format("Security Short Position (Max {} Current {})", short_pos_limit, net_pos);
else
}
else {
out_res.result = Limiter::PROD_SHORT_POS_BREACHED;
out_res.error_msg = fmt::format("Product Short Position (Max {} Current {})", short_pos_limit, net_pos);
}
}
}
static void can_update_order_net (api::Side side,
@@ -47,7 +53,7 @@ public:
int short_pos_limit,
int net_pos,
Limiter::LimiterQueryResult& out_res){
out_res.ok = true;
out_res.result = Limiter::OK;
if (to.value() > from.value()){
int net_pos_new = 0;
uint32_t diff = to.value() - from.value();
@@ -56,18 +62,24 @@ public:
else if(side == api::Side::SIDE_SELL)
net_pos_new = net_pos - diff;
if (net_pos_new > long_pos_limit){
out_res.ok = false;
if (is_security_level)
if (is_security_level) {
out_res.result = Limiter::SEC_LONG_POS_BREACHED;
out_res.error_msg = fmt::format("Security Long Position (Max {} Current {})", long_pos_limit, net_pos);
else
}
else {
out_res.result = Limiter::PROD_LONG_POS_BREACHED;
out_res.error_msg = fmt::format("Product Long Position (Max {} Current {})", long_pos_limit, net_pos);
}
}
else if (net_pos_new < short_pos_limit){
out_res.ok = false;
if (is_security_level)
if (is_security_level) {
out_res.result = Limiter::SEC_SHORT_POS_BREACHED;
out_res.error_msg = fmt::format("Security Short Position (Max {} Current {})", short_pos_limit, net_pos);
else
}
else {
out_res.result = Limiter::SEC_SHORT_POS_BREACHED;
out_res.error_msg = fmt::format("Product Short Position (Max {} Current {})", short_pos_limit, net_pos);
}
}
}
}
@@ -78,13 +90,13 @@ public:
uint32_t long_pos,
uint32_t short_pos,
Limiter::LimiterQueryResult& out_res){
out_res.ok = true;
out_res.result = Limiter::OK;
if(side == api::Side::SIDE_BUY){
if(long_pos + quantity.value() > long_pos_limit){
out_res.error_msg = fmt::format("Long Position (Abs) "
"(Max {} Current {})",
long_pos_limit,long_pos);
out_res.ok = false;
out_res.result = Limiter::SEC_LONG_POS_BREACHED;
}
}
else{
@@ -92,7 +104,7 @@ public:
out_res.error_msg = fmt::format("Short Position (Abs) "
"(Max {} Current {})",
short_pos_limit,short_pos);
out_res.ok = false;
out_res.result = Limiter::SEC_SHORT_POS_BREACHED;
}
}
}
@@ -104,7 +116,7 @@ public:
uint32_t long_pos,
uint32_t short_pos,
Limiter::LimiterQueryResult& out_res){
out_res.ok = true;
out_res.result = Limiter::OK;
if (to.value() > from.value()){
uint32_t diff = to.value() - from.value();
if(side == api::Side::SIDE_BUY){
@@ -112,7 +124,7 @@ public:
out_res.error_msg = fmt::format("Long Position (Abs) "
"(Max {} Current {})",
long_pos_limit,long_pos);
out_res.ok = false;
out_res.result = Limiter::SEC_LONG_POS_BREACHED;
}
}
else{
@@ -120,7 +132,7 @@ public:
out_res.error_msg = fmt::format("Short Position (Abs) "
"(Max {} Current {})",
short_pos_limit,short_pos);
out_res.ok = false;
out_res.result = Limiter::SEC_SHORT_POS_BREACHED;
}
}
}
+6 -6
View File
@@ -64,9 +64,9 @@ public:
// risk limit checks
////////////////////
Limiter::LimiterQueryResult can_send_new_order(api::Side side, Quantity quantity) const{
Limiter::LimiterQueryResult result;result.ok=true;
Limiter::LimiterQueryResult result;result.result= Limiter::OK;
if (quantity.value() > _clip_size){
result.ok = false;
result.result = Limiter::CLIP_SIZE_BREACHED;
result.error_msg = fmt::format("Security Clip Size (Max {} Qty {})", _clip_size, quantity.value());
return result;
}
@@ -74,14 +74,14 @@ public:
LimiterUtils::can_send_new_order_net (side, quantity, true,
_long_pos_limit, _short_pos_limit,
_net_pos, result);
if (result.ok)
if (result.ok())
result = _product_node.can_send_new_order(side, quantity);
return result;
}
Limiter::LimiterQueryResult can_update_order(api::Side side, Quantity from, Quantity to) const{
Limiter::LimiterQueryResult result;result.ok=true;
Limiter::LimiterQueryResult result;result.result=Limiter::OK;
if (to.value() > _clip_size){
result.ok = false;
result.result = Limiter::CLIP_SIZE_BREACHED;
result.error_msg = fmt::format("Security Clip Size (Max {} Qty {})", _clip_size, to.value());
return result;
}
@@ -89,7 +89,7 @@ public:
LimiterUtils::can_update_order_net (side, from, to, true,
_long_pos_limit, _short_pos_limit,
_net_pos, result);
if (result.ok)
if (result.ok())
result = _product_node.can_update_order(side, from, to);
return result;
}
+12 -10
View File
@@ -270,8 +270,7 @@ NativeOrder::on_init_from_exe_report(ExecutionReport const &er,
if (er.max_show.value() == 0) {
def.mutable_custom_def()->mutable_limit();
state.mutable_limit();
}
else {
} else {
def.mutable_custom_def()->mutable_glbx_iceberg();
state.mutable_glbx_iceberg();
}
@@ -370,7 +369,7 @@ NativeOrder::_on_activate(log::Level reject_level, log::Level transition_level,
auto lqr = me->services->limiter().can_trade(
UserID(me->definition.user_id()),
AccountID(me->definition.account_id()));
if (!lqr.ok) {
if (!lqr.ok()) {
LOG(OrderMan, reject_level,
"Unauthorized order attempted. User: {} on "
"Account: {}. Msg: {} Order: {}. Dropping order",
@@ -387,7 +386,7 @@ NativeOrder::_on_activate(log::Level reject_level, log::Level transition_level,
AccountID(me->definition.account_id()), *me->sec, me->definition.side(),
q);
if (!lqr.ok) {
if (!lqr.ok()) {
LOG(OrderMan, reject_level,
"New order would breach limit. User: {} Account: "
"{} Msg: {} Order: {}. Dropping order",
@@ -587,7 +586,7 @@ OrderOpResult NativeOrder::on_update(api::orderman::Update const &update) {
Quantity(current_working_qty));
// are we under the position limits?
if (!lqr.ok) {
if (!lqr.ok()) {
auto msg = fmt::format("Cannot update order due to limit breach: {}",
lqr.error_msg);
LOG(OrderMan, Error,
@@ -1047,12 +1046,14 @@ void NativeOrder::update_working_position() {
me->services->limiter().add(
*me->sec, AccountID(me->definition.account_id()),
me->definition.side(),
Quantity(current_position.value() - previous_position.value()));
Quantity(current_position.value() - previous_position.value()),
true);
else if (previous_position.value() > current_position.value())
me->services->limiter().remove(
*me->sec, AccountID(me->definition.account_id()),
me->definition.side(),
Quantity(previous_position.value() - current_position.value()));
Quantity(previous_position.value() - current_position.value()),
true);
} else
LOG(OrderMan, Error,
"Order {} ignored for purposes of limit enforcing "
@@ -1194,9 +1195,8 @@ void NativeOrder::init_fix() {
securities::encode_exchange_price(*me->sec, Price(stop_px)).value();
create_encoding.add<fix::StopPx>(encoded_stop_px,
me->price_encoding_precision, -1);
me->update_stop_px_bm=
update_encoding.add<fix::StopPx>(encoded_stop_px,
me->price_encoding_precision, -1);
me->update_stop_px_bm = update_encoding.add<fix::StopPx>(
encoded_stop_px, me->price_encoding_precision, -1);
// price
me->create_price_bm = create_encoding.add<fix::Price>(
@@ -1376,6 +1376,8 @@ NativeOrder::get_native_state(api::orderman::OrderState &st) {
return *st.mutable_limit()->mutable_native_state();
case OrderState::kStop:
return *st.mutable_stop()->mutable_native_state();
case OrderState::kStopLimit:
return *st.mutable_stop_limit()->mutable_native_state();
case OrderState::kGlbxIceberg:
return *st.mutable_glbx_iceberg()->mutable_native_state();
default:
@@ -20,11 +20,11 @@ struct LeanAlgo::PImpl {
IAlgoServices* algo_services;
double tick_size = os::nan();
Status status = Status::WAITING_FOR_MD;
Price initial_observed_price = Price(os::nan());
// the first observed opp inside price
Price anchor_price = Price(os::nan());
Price observed_price = Price(os::nan());
OrderKey child_key = null_order_key();
ChildTemplate *child_template = nullptr;
md::PriceSize last_opp_inside = md::PriceSize();
Book curr_book;
Price out_price = Price(os::nan());
Quantity out_qty = Quantity(0);
@@ -106,8 +106,8 @@ void LeanAlgo::on_book(SecurityKey, const Book &book) {
switch (me->status) {
case LeanAlgo::Status::WAITING_FOR_MD:
if (_opp_inside_market().size() != 0) {
me->initial_observed_price = Price(_opp_inside_market().price());
me->observed_price = me->initial_observed_price;
me->anchor_price = Price(_opp_inside_market().price());
me->observed_price = me->anchor_price;
me->status = LeanAlgo::Status::WORKING;
_analyze_and_act();
}
@@ -158,11 +158,11 @@ LeanAlgo::_on_toward_behaviour() const {
bool LeanAlgo::_book_moved_toward() const {
return securities::is_better(_opp_side(),
Price(_opp_inside_market().price()),
Price(me->last_opp_inside.price()));
me->anchor_price);
}
bool LeanAlgo::_book_moved_away() const {
return securities::is_better(_opp_side(),
Price(me->last_opp_inside.price()),
me->anchor_price,
Price(_opp_inside_market().price()));
}
md::PriceSize LeanAlgo::_opp_inside_market() const {
@@ -173,29 +173,29 @@ api::Side LeanAlgo::_opp_side() const {
}
bool LeanAlgo::_within_chase_band(Price price) const{
return securities::is_better(algo().definition().side(),
me->initial_observed_price,
me->anchor_price,
price) ||
static_cast<uint32_t>(std::abs(
securities::price_diff_to_ticks(price.value(),
me->initial_observed_price.value(),
me->anchor_price.value(),
me->tick_size))) <= _max_chase_ticks();
}
Price LeanAlgo::_chase_limit() const{
double offset=me->tick_size*_max_chase_ticks();
return securities::improve_price(algo().definition().side(),
me->initial_observed_price,
me->anchor_price,
Price(offset));
}
LeanAlgo::MarketChange LeanAlgo::_market_change() const{
LeanAlgo::OppMarketState LeanAlgo::_market_state() const{
md::PriceSize opp_inside=_opp_inside_market();
if(opp_inside.size()==0)
return MarketChange::NO_MARKET;
return OppMarketState::NO_MARKET;
else if (_book_moved_away())
return MarketChange::AWAY;
return OppMarketState::AWAY;
else if (_book_moved_toward())
return MarketChange::TOWARD;
return OppMarketState::TOWARD;
else
return MarketChange::SIZE_CHANGE;
return OppMarketState::NO_DIRECTIONAL_CHANGE;
}
Price LeanAlgo::_get_price_to_pass (Price price) const{
// if it has crossed once, then the
@@ -208,24 +208,25 @@ Price LeanAlgo::_get_price_to_pass (Price price) const{
///////////////////////////////////////////////////////////////////////////////
void LeanAlgo::_analyze_and_act() {
bts_assert(me->status == LeanAlgo::Status::WORKING);
auto market_change=_market_change();
me->last_opp_inside=_opp_inside_market();
auto market_change=_market_state();
switch(market_change){
case MarketChange::NO_MARKET:
case OppMarketState::NO_MARKET:
_go_furthest_possible();
break;
case MarketChange::SIZE_CHANGE:
case OppMarketState::NO_DIRECTIONAL_CHANGE:
_try_cross(me->observed_price);
break;
case MarketChange::AWAY:
case OppMarketState::AWAY:
me->observed_price = Price(_opp_inside_market().price());
_go_furthest_possible();
break;
case MarketChange::TOWARD:
case OppMarketState::TOWARD:
_on_toward_me();
break;
}
}
// since this is more complicated than the
// other market states
void LeanAlgo::_on_toward_me() {
switch (_on_toward_behaviour()) {
case LeanOrderDefinition_OnToward_CANCEL:
@@ -242,7 +243,7 @@ void LeanAlgo::_on_toward_me() {
algo().cancel("Opposite inside market moved towards order(detaching)");
break;
case LeanOrderDefinition_OnToward_REPRICE:
LOG(OrderMan, Diag,
LOG(OrderMan, Info,
"LeanAlgo {} changing its target to {} because opposite moved "
"towards order",
algo().order_key().value(), _opp_inside_market().price());
@@ -250,7 +251,7 @@ void LeanAlgo::_on_toward_me() {
_try_cross(me->observed_price);
break;
case LeanOrderDefinition_OnToward_NO_CHANGE:
LOG(OrderMan, Diag, "LeanAlgo {} ignoring opposite move towards order",
LOG(OrderMan, Info, "LeanAlgo {} ignoring opposite move towards order",
algo().order_key().value());
_try_cross(me->observed_price);
break;
@@ -259,10 +260,11 @@ void LeanAlgo::_on_toward_me() {
break;
}
}
// this assumes the opposite side is there
// and will always try to cross into the other side
// IF the qty threshold AND chase band condition is met.
// it returns true if it successfully crossed.
// if the price is within the chase bands
// (which is only applied on AWAY prices),
// and the cum qty on the opposite side
// up to the price is less than
// the threshold, then it jumps to that price.
bool LeanAlgo::_try_cross(Price price) {
if(_within_chase_band(price)){
Quantity cumm_qty;
@@ -271,6 +273,13 @@ bool LeanAlgo::_try_cross(Price price) {
price,
me->tick_size,
cumm_qty);
// if we are within the chase bands and
// the price happens to be better than
// the opposite inside market (ie for buy orders
// the price < top sell market)
// then cumm_qty will be 0, which
// means the price will always be modified
// to the arg
Quantity trigger_quantity = _cross_threshold();
if(cumm_qty.value()<trigger_quantity.value()){
_modify_price_price_qty(price,algo().quantity());
@@ -279,14 +288,28 @@ bool LeanAlgo::_try_cross(Price price) {
}
return false;
}
// this function is called when NO_MARKET or AWAY
// it tries to place the order as far as it can;
// with the priority:
// 1. observed_price
// 2. chase_limit
// 3. opp inside-1 (for buys)
// 4. same side inside
void LeanAlgo::_go_furthest_possible(){
if(!_try_cross(me->observed_price)){
// this will be false if the observed price
// is outside the chase OR qty threshold
// is not met
if(!_try_cross(_chase_limit())){
// this will only be false if chase_limit is
// on the opposite side and the qty > threshold
// (if it was on the same side, cum_qty==0)
// this will only be false
// if chase_limit is on the opposite side
// and the qty > threshold (if it was on the
// same side, cum_qty==0 and it should go at
// that price, see try_cross)
auto opp_inside=_opp_inside_market();
if(opp_inside.size()>0){
// goes to the opp side, improves a tick to get
// to the same market top, and crosses there
auto inside = securities::worsen_price(algo().definition().side(),
Price(opp_inside.price()),
Price(me->tick_size));
@@ -21,7 +21,15 @@ class LeanAlgo : public IAlgoOrder {
void on_child_change(OrderKey, ChildState const &) override;
private:
enum class MarketChange {NO_MARKET,SIZE_CHANGE,AWAY,TOWARD};
// this is the state of opposite inside market (oim)
// NO_MARKET: oim.qty==0
// NO_DIRECTIONAL_CHANGE: oim.price==anchor_price
// AWAY: anchor_price is_better_than oim.price
// TOWARD: oim.price is_better_than anchor_price
// since lean order is placed away from the market
// on the same size, AWAY means opp inside moves
// AWAY from the order, similiarly TOWARD
enum class OppMarketState {NO_MARKET,NO_DIRECTIONAL_CHANGE,AWAY,TOWARD};
struct PImpl;
enum class Status;
IAlgoServices &algo() const ;
@@ -34,7 +42,7 @@ class LeanAlgo : public IAlgoOrder {
api::Side _opp_side() const;
bool _within_chase_band(Price price) const;
Price _chase_limit() const;
MarketChange _market_change() const;
OppMarketState _market_state() const;
Price _get_price_to_pass (Price price) const;
void _analyze_and_act();
bool _try_cross(Price);
@@ -24,6 +24,7 @@ _order_builder(),
_order_manager(),
_services(nullptr),
_reported_state(),
_sent_state(),
_book(),
_state_worker(nullptr),
_start_state_worker(this),
@@ -120,7 +121,7 @@ void QueueHolderOrder::_switch_state(QueueHolderOrderState new_state) {
LOG(OrderMan, Info, "QueueHolderOrder::_switch_state key:{} {}->{}",
key(), s0,_state_int());
}
api::orderman::OrderStatus QueueHolderOrder::status(std::string& message) const{
api::orderman::OrderStatus QueueHolderOrder::_status(std::string& message) const{
QueueHolderOrderState curr_state=
_state_worker==nullptr?QueueHolderOrderState::START:_state();
switch (curr_state){
@@ -11,6 +11,7 @@
#include "lib/orderman/orders/queue_holder/state_workers/QHOEndStateWorker.h"
#include "lib/orderman/orders/queue_holder/QHOOrderManager.h"
#include "lib/orderman/orders/OrderOpResult.h"
#include "lib/domain_model/Security.h"
namespace bts {
namespace api {namespace orderman {class OrderDefinition;}}
@@ -83,12 +84,13 @@ private:
_termination_reason=termination_reason;
_switch_state(QueueHolderOrderState::WAITING_FOR_CANCEL_ALL);
}
api::orderman::OrderStatus status(std::string& message) const;
api::orderman::OrderStatus _status(std::string& message) const;
// sends orders for the first time
void _report_state(){
if(_activated()){
std::string err_msg;
_reported_state.set_status(status(err_msg));
std::string& err_msg = *(_reported_state.mutable_last_msg());
// both status and err_msg are set here
_reported_state.set_status(_status(err_msg));
double filled_price;
uint32_t filled_qty;
_order_manager.filled_price(filled_price,filled_qty);
@@ -104,8 +106,10 @@ private:
}
_reported_state.set_avg_price(filled_price);
_reported_state.set_filled_quantity(filled_qty);
_reported_state.set_last_msg(err_msg);
_services->report_state(_reported_state);
if(!_state_eq()){
_sent_state=_reported_state;
_services->report_state(_reported_state);
}
}
}
void _set_state_price_qty(Price price, Quantity qty){
@@ -124,6 +128,18 @@ private:
oss << str << " key:" << key();
bts_assert_msg(false, oss.str());
}
bool _state_eq() const{
return _reported_state.status() == _sent_state.status() &&
securities::is_same(Price(_reported_state.price()),
Price(_sent_state.price())) &&
_reported_state.quantity() == _sent_state.quantity() &&
_reported_state.filled_quantity() == _sent_state.filled_quantity() &&
securities::is_same(Price(_reported_state.last_price()),
Price(_sent_state.last_price())) &&
securities::is_same(Price(_reported_state.avg_price()),
Price(_sent_state.avg_price())) &&
_reported_state.last_msg() == _sent_state.last_msg();
}
private:
// if the init call succeeded which means
// i. order is properly built
@@ -138,7 +154,7 @@ private:
OrderBuilder _order_builder;
QHOOrderManager _order_manager;
OrderServices *_services;
api::orderman::OrderState _reported_state;
api::orderman::OrderState _reported_state, _sent_state;
Book _book;
IQueueHolderOrderStateWorker* _state_worker;
QHOStartStateWorker _start_state_worker;
+3 -2
View File
@@ -304,11 +304,12 @@ void DynamicJoin::on_child_change(OrderKey, ChildState const &st) {
}
}
// This is called by the spark daemon at startup. In this call we
// describe our algo's parameters and register a factory function to
// create an instance of the algo.
extern "C" void init_spark_plugin(bts::SparkServices &serv) {
using namespace bts;
// Describe our new algo order so that Spark can generate a GUI for
// it.
DynamicOrderDescriptor join_desc;
// Start by naming our order type
@@ -222,7 +222,7 @@ void ExchangeSimApplication::onMessage(const FIX42::NewOrderSingle &message,
order.set_type(convert(ordType));
if (orderQty.getValue() == 0)
throw new std::logic_error("Quantity must be greater than 0");
throw std::logic_error("Quantity must be greater than 0");
if (order.type() == api::orderman::NATIVE_ORDER_TYPE_LIMIT)
order.set_price(price);
@@ -232,13 +232,13 @@ void ExchangeSimApplication::onMessage(const FIX42::NewOrderSingle &message,
if (book.asks.size())
order.set_price(book.asks.begin()->first.value());
else
throw new std::logic_error("No sell market");
throw std::logic_error("No sell market");
} else {
if (book.bids.size())
order.set_price(book.bids.begin()->first.value());
else
throw new std::logic_error("No buy market");
throw std::logic_error("No buy market");
}
}
order.set_quantity(orderQty);
+1 -1
View File
@@ -1 +1 @@
set(sources ../../externals/linux/include/google/protobuf/descriptor.proto;../../externals/linux/include/google/protobuf/timestamp.proto;../lib/api/apicore.proto;../lib/api/apigateway.proto;../lib/api/apioptions.proto;../lib/api/services/AdminService.proto;../lib/api/services/AlgoDefinitionService.proto;../lib/api/services/AlgoService.proto;../lib/api/services/ApiUnitTestService.proto;../lib/api/services/AuthService.proto;../lib/api/services/ErrorReportService.proto;../lib/api/services/HelloService.proto;../lib/api/services/MBOService.proto;../lib/api/services/MarketDataAuxiliaryService.proto;../lib/api/services/MarketDataRelay.proto;../lib/api/services/OrderService.proto;../lib/api/services/OutOfProcessURLListener.proto;../lib/api/services/PersistenceService.proto;../lib/api/services/PositionService.proto;../lib/api/services/ShellDiagnosticService.proto;../lib/api/services/ShellLatencyService.proto;../lib/api/services/SyntheticSecurityService.proto;../lib/domain_model/Security.proto;../lib/md/protos/co2.proto;../lib/sparkproto/SparkProtos.proto;api/build.sh;api/node_modules/cssstyle/scripts/run_tests.sh;api/node_modules/exit/test/fixtures/create-files.sh;api/node_modules/protobufjs/cli/node_modules/acorn-jsx/node_modules/acorn/bin/update_authors.sh;api/node_modules/watch/release.sh;api/quiet_build.sh;api/src/DynamicService.ts;api/src/ServiceContext.ts;api/src/bts_assert.ts;ui/src/Access.fs;ui/src/Accounts/ClipLimits.fs;ui/src/Accounts/ProductLimits.fs;ui/src/Accounts/SecurityLimits.fs;ui/src/Accounts/State.fs;ui/src/Accounts/Types.fs;ui/src/Accounts/View.fs;ui/src/App.fs;ui/src/Cmd.fs;ui/src/Controls/Button.fs;ui/src/Controls/DataGrid.fs;ui/src/Controls/Fields.fs;ui/src/Controls/Modal.fs;ui/src/Controls/ReactGrid.fs;ui/src/Controls/Table.fs;ui/src/Controls/nbsp.fs;ui/src/Global.fs;ui/src/Imports.fs;ui/src/Lens.fs;ui/src/Login/Login.fs;ui/src/Nav.fs;ui/src/State.fs;ui/src/Types.fs;ui/src/Users/State.fs;ui/src/Users/Types.fs;ui/src/Users/View.fs;ui/src/Util/EnumUtils.fs;ui/src/Util/JSMap.fs;ui/src/obj/Debug/netstandard2.0/ui.AssemblyInfo.fs)
set(sources ../../externals/linux/include/google/protobuf/descriptor.proto;../../externals/linux/include/google/protobuf/timestamp.proto;../lib/api/apicore.proto;../lib/api/apigateway.proto;../lib/api/apioptions.proto;../lib/api/services/AdminService.proto;../lib/api/services/AlgoDefinitionService.proto;../lib/api/services/AlgoService.proto;../lib/api/services/ApiUnitTestService.proto;../lib/api/services/AuthService.proto;../lib/api/services/ErrorReportService.proto;../lib/api/services/HelloService.proto;../lib/api/services/MBOService.proto;../lib/api/services/MarketDataAuxiliaryService.proto;../lib/api/services/MarketDataRelay.proto;../lib/api/services/OrderService.proto;../lib/api/services/OutOfProcessURLListener.proto;../lib/api/services/PersistenceService.proto;../lib/api/services/PositionService.proto;../lib/api/services/ShellDiagnosticService.proto;../lib/api/services/ShellLatencyService.proto;../lib/api/services/SyntheticSecurityService.proto;../lib/domain_model/Security.proto;../lib/md/protos/co2.proto;../lib/sparkproto/SparkProtos.proto;api/build.sh;api/node_modules/exit/test/fixtures/create-files.sh;api/node_modules/protobufjs/cli/node_modules/acorn-jsx/node_modules/acorn/bin/update_authors.sh;api/node_modules/watch/release.sh;api/quiet_build.sh;api/src/DynamicService.ts;api/src/ServiceContext.ts;api/src/bts_assert.ts;ui/src/Access.fs;ui/src/Accounts/ClipLimits.fs;ui/src/Accounts/ProductLimits.fs;ui/src/Accounts/SecurityLimits.fs;ui/src/Accounts/State.fs;ui/src/Accounts/Types.fs;ui/src/Accounts/View.fs;ui/src/App.fs;ui/src/Cmd.fs;ui/src/Controls/Button.fs;ui/src/Controls/DataGrid.fs;ui/src/Controls/Fields.fs;ui/src/Controls/Modal.fs;ui/src/Controls/ReactGrid.fs;ui/src/Controls/Table.fs;ui/src/Controls/nbsp.fs;ui/src/Global.fs;ui/src/Imports.fs;ui/src/Lens.fs;ui/src/Login/Login.fs;ui/src/Nav.fs;ui/src/State.fs;ui/src/Types.fs;ui/src/Users/State.fs;ui/src/Users/Types.fs;ui/src/Users/View.fs;ui/src/Util/EnumUtils.fs;ui/src/Util/JSMap.fs;ui/src/obj/Debug/netstandard2.0/ui.AssemblyInfo.fs)
+11 -10
View File
@@ -348,8 +348,8 @@ let clipLimitsView model dispatch =
TableControl.createDataTable (Some headerRow) rws (Button.button "Add" "" "plus-circle" Button.Enabled addRow)
]
let lazyClipLimitsView model dispatch =
lazyView2With (fun a b ->
let lazyClipLimitsView model dispatch =
lazyView2With (fun a b ->
LanguagePrimitives.PhysicalEquality a.SelectedClipLimits b.SelectedClipLimits &&
LanguagePrimitives.PhysicalEquality a.scratch.securityClipLimits b.scratch.securityClipLimits)
clipLimitsView
@@ -542,10 +542,11 @@ let productLimits model dispatch =
br []
]
let lazyProductLimitsView model dispatch =
lazyView2With (fun a b ->
let lazyProductLimitsView model dispatch =
lazyView2With (fun a b ->
LanguagePrimitives.PhysicalEquality a.SelectedPositionLimits b.SelectedPositionLimits &&
LanguagePrimitives.PhysicalEquality a.scratch.productPositionLimits b.scratch.productPositionLimits)
LanguagePrimitives.PhysicalEquality a.scratch.productPositionLimits b.scratch.productPositionLimits &&
a.scratch.riskLimitEnforcement = b.scratch.riskLimitEnforcement)
productLimits
model dispatch
@@ -764,7 +765,7 @@ let tabContent model dispatch =
div [Key (string model.scratch.id); ClassName "is-tab-content"] [
// (match model.tab with
(match model.tab, (accountIsSelected model.scratch Access.Accounts.empty) with
| _ , false -> p [Style [ Color "Red" ]] [ str "You are not authorized to view this data" ]
| _ , false -> p [Style [ Color "Red" ]] [ str "You are not authorized to view this data" ]
| General, _ -> generalView model dispatch
| LimitsTab, _ -> LimitsView model dispatch
| ConnectionsTab, _ -> ConnectionsView model dispatch
@@ -871,7 +872,7 @@ let menuItem account selectedAccount model dispatch =
]
]
]
else span[][])
else span[][])
]
let NavRoot (accounts:BTSAccount[]) selectedAccount model dispatch =
@@ -881,10 +882,10 @@ let NavRoot (accounts:BTSAccount[]) selectedAccount model dispatch =
span [Class "is-size-4"] [str "Accounts"]
span [ Class "is-pulled-right circle-icon" ]
(if model.can_create then
[ Button.fabutton
[ Button.fabutton
"Add Account"
"plus-circle"
""
""
Button.Enabled
(fun _ -> dispatch AddAccount) ]
else [])
@@ -929,7 +930,7 @@ let root (model:Model) dispatch =
[ NavRoot data.accounts (selectedAccount model) model dispatch
div [ Class "column is-main-content" ]
[
div [ ClassName "tabs is-boxed" ] [
ul [] [
(if (Array.length data.accounts > 0) then (tabHeader General model.tab dispatch) else span [][] )
@@ -2,9 +2,9 @@ This test verifies that fractional hedges work. That is, when a fill in a quotin
not equate to one full contract in the hedging leg, we accumulate the unhedged
delta, until we can send one or more hedging contracts.
1. Create a new synthetic spread. For the legs choose SIF9 and SIH9 (if you dont
1. Create a new synthetic spread. For the legs choose SIF9 and SIH9 (if you don't
have these contracts, other contracts can be chosen too) and for the quantities choose 1 and -1.5
respectively (and same for the Price multiplier).
respectively (and same for the Price multiplier).
Enable quoting for SIH9, and disable all quoting, pre-hedge and post-hedge parameters.
Name the synthetic SIF9-1.5SIH9.
@@ -18,13 +18,13 @@ to enter orders). The quoting order will lean against this.
4. Verify that a 15 lot quoting order is sent for SIH9 at 19.935.
5. Now we will start filling the quoting order and verify the hedges. A 1 lot fill in the
quoting leg means a 2/3 contract in the hedging leg. Since we can not send 2/3 constracts,
quoting leg means a 2/3 contract in the hedging leg. Since we can not send 2/3 contracts,
we will add it to the "unhedged delta" till the unhedged delta > 1, and then we will send one
or more orders to get the unhedged delta < 1.
Force quoting fills based on the left most column (by lifting the quoting offer)
and verify that hedge orders of size "hedge_order_sent" (3rd column) are sent as a result.
The "unhedged_delta" column shows the unhedged delta immediatly after the quoting fill
The "unhedged_delta" column shows the unhedged delta immediately after the quoting fill
and the "remaining_unhedged_delta" column is the unhedged delta after the hedge order
has been sent.
You can check the hedge orders being sent easily by looking at the order reduction
@@ -10,7 +10,7 @@ ExchangeSim {
}
}
1. Open up the mview and find a security with with both direct and implied markets, on the ask side.
1. Open up the mview and find a security with with both direct and implied markets, on the ask side.
Make sure those markets are stable and you can rely on them.
2. Create a synthetic with the security in 1. being the hedge leg (buy) and choose a quoting
@@ -19,11 +19,11 @@ use "Merged"
3. Disable all spreader params.
4. Now enter a spreader order so that it doesnt get filled immediatly. Note the quoting order.
4. Now enter a spreader order so that it doesn't get filled immediately. Note the quoting order.
quoting leg price = hedge_price - spreader_price.
hedge_price is where it can buy the hedge order (which since its a buy will be on the ask side of
the hedging market).
Make sure thta its correct and its using the merged book in the hedging market.
Make sure that its correct and its using the merged book in the hedging market.
5. Cancel the order and change the spreader so its using Direct prices.
Repeat 4, and make sure its using direct price on the hedging market.
+3 -3
View File
@@ -5,8 +5,8 @@ with 1 quoting leg.
1. Create a 4 legged synthetic spread with legs SIF9, SIH9, SIK9, SIN9, with leg quantities and
price multipliers of 1,1,1 and -1 respectively. Leg SIN9 is the only quoting leg.
Disable all Quote, Pre-Hedge and Post-Hedge paramaters (leg contracts are arbitrary, so if you
dont have those contracts, choose any set of 4 contracts).
Disable all Quote, Pre-Hedge and Post-Hedge parameters (leg contracts are arbitrary, so if you
don't have those contracts, choose any set of 4 contracts).
2. Enter a synthetic buy order of 10 lots at 40.00. The ladders will most likely be empty, so
use the market view to enter orders.
@@ -16,7 +16,7 @@ Now enter:
SIH9 10 lot sell @ 19.950
SIK9 10 lot sell @ 20.200
There should still be no quoting order for SIN9 since the SIF9 hedge leg still doesnt have a market.
There should still be no quoting order for SIN9 since the SIF9 hedge leg still doesn't have a market.
Now Enter:
SIF9 1 lot sell @ 20.000