Compare commits

...
94 Commits
Author SHA1 Message Date
Pedro Pinto ff461d42b8 Merge branch 'master' into prod 2019-06-14 10:13:40 -04:00
Michael Harding 12dd3909bf Fix documentation deploy 2019-06-13 12:49:13 -04:00
John Colvin 0af5b78318 Merged in SPARK-2489-ladder-settings (pull request #257)
RTM SPARK-2489 ladder settings

Approved-by: Pedro Pinto <pedro.pinto+atlassian@bluetradesys.com>
2019-06-10 21:31:24 +00:00
Pedro Pinto c652032ddf Updated release notes. 2019-06-10 17:27:46 -04:00
John Colvin 7bd868b753 Merge remote-tracking branch 'origin/master' into SPARK-2489-ladder-settings 2019-06-10 15:23:58 -05:00
John Colvin 1bc4510862 LadderSettingsView - touch up 'Market Price And Size Display' tab 2019-06-10 15:12:38 -05:00
John Colvin b1fb964648 Merged in SPARK-2508-fills-symbol-column (pull request #256)
RTM SPARK-2508 fills symbol column
2019-06-10 19:45:37 +00:00
Pedro Pinto ee19f7c7da Updated release notes. 2019-06-10 15:44:00 -04:00
John Colvin cd41632b53 LadderSettingsView - touch up 'Order Quantities' tab 2019-06-10 14:27:54 -05:00
John Colvin ecd384501e align LadderSettingsView (content in settings dialog) with hamburger button 2019-06-10 12:22:05 -05:00
John Colvin 0483460ac9 Merge remote-tracking branch 'origin/master' into SPARK-2489-ladder-settings 2019-06-10 12:00:08 -05:00
John Colvin 0bc24fa7b7 SPARK-2508 Fills - add 'Symbol' column 2019-06-10 11:48:09 -05:00
John Colvin ac60a1447e stack FillsView.Columns enum values vertically (easier to merge column changes) 2019-06-10 11:32:35 -05: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
John Colvin 445bbb10f5 LadderSettingsView - normalize spacing 2019-06-07 13:42:27 -05:00
John Colvin 70c006f797 SPARK-2489 / SPARK-2502 move 'Alternate Order Qty' into 'Order Quantities' tab 2019-06-07 13:42:27 -05: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
John Colvin 3a5810d8ac LadderGridHost.OnMouseDown impl should return after handling middle click 2019-06-07 10:12:10 -05:00
John Colvin 29c772cbb6 SPARK-2489 / SPARK-2501 rename 'Add Size' -> 'Add Quantity' 2019-06-06 23:41:45 -05:00
John Colvin 0b03180c4d SPARK-2489 / SPARK-2500 rename 'Size Presets' -> 'Order Quantities' 2019-06-06 23:39:02 -05:00
John Colvin 1518906b7f SPARK-2489 / SPARK-2498 remove 'Order Badge Style' option 2019-06-06 23:35:45 -05:00
John Colvin 31fe708907 touch up LadderSettingsView 2019-06-06 23:35:37 -05:00
John Colvin 4b2f39c536 LadderSettingsView - drop 'Show/Hide' from 'Advanced' hyperlinks 2019-06-06 22:42:55 -05:00
John Colvin 040960d33b SPARK-2489 / SPARK-2495 merge 'Volume Display' tab into the 'Ladder Columns' tab 2019-06-06 19:31:36 -05:00
Taha Afzal a4d63a1421 this should fix the confusion 2019-06-06 19:13:46 -04:00
John Colvin dcd8984b33 SPARK-2489 / SPARK-2494 rename 'Column Settings' tab 'Ladder Columns' 2019-06-06 12:35:09 -05: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 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
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
68 changed files with 1867 additions and 1309 deletions

No files matched your search

+29
View File
@@ -4,6 +4,35 @@ 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
- Added security symbol column to fills window
- Re-organized ladder settings dialog to make it clearer and easier to use
## 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
-5
View File
@@ -1,7 +1,6 @@
#/bin/bash
#python -m mkdocs build -d site
cp -r ../../src/sdk/doc/* ./content/bts_spark_sdk/Server/
./run_zola.sh build
scp -r ./public bts@bluetradesys.com:/home/bts/doc/
@@ -14,7 +13,3 @@ sudo mv ./public/* /var/www/documentation/
EOF
rm -r public
cd ./content/bts_spark_sdk/server/
find . -not -name '_index.md' -delete
cd ../..
@@ -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>(
@@ -63,6 +61,14 @@ namespace mbts.spark {
equality_comparer: SecurityDataHelper.EqualityComparer.IDComparer,
comparer: Comparer<SecurityImmutable>.Default
), DataGridFilterKnownTemplates.DiscreteValues, DefaultColumnWidths.Security);
// symbol
add_col(Columns.Symbol, DefaultColumnHeaders.Symbol, r => r.security.Symbol,
DiscreteValuesFilterVM.Create<FillTuple, string>(
co2.UserSymbol.get_all().Select(u => u.symbol).Distinct().ToList(),
ft => ft.security?.Symbol ?? string.Empty,
nameof(OrderBookRowVM.symbol)),
DataGridFilterKnownTemplates.DiscreteValues,
DefaultColumnWidths.Symbol);
// order type
var order_type_filter = dv_filter(OrderDataHelper.all_order_types, r => r.order?.order_type() ?? OrderType.None);
add_col_expl(Columns.OrderType,
@@ -85,25 +91,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 +121,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 +140,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 +162,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) {
@@ -202,6 +198,7 @@ namespace mbts.spark {
case Columns.SecurityKey: return wrap(ft => ft.fill.SecurityKey);
case Columns.Side: return wrap_default(ft => ft.fill.Side);
case Columns.Size: return wrap(ft => ft.fill.LastShares);
case Columns.Symbol: return wrap(ft => ft.security?.Symbol ?? string.Empty);
case Columns.TimeInForce: return wrap_default(ft => ft.order?.time_in_force() ?? TimeInForce.Undefined);
case Columns.User: return wrap(ft => ft.order?.user() ?? string.Empty);
default: goto case Columns.FillTime;
@@ -7,7 +7,23 @@ namespace mbts.ui.spark {
/// Interaction logic for FillsView.xaml
/// </summary>
partial class FillsView : UserControl {
public enum Columns { FillTime, User, Account, Exchange, Security, TimeInForce, OrderType, Side, Price, Size, FillKind, FillKey, OrderKey, SecurityKey }
public enum Columns {
FillTime,
User,
Account,
Exchange,
Security,
Symbol,
TimeInForce,
OrderType,
Side,
Price,
Size,
FillKind,
FillKey,
OrderKey,
SecurityKey
}
public FillsView() =>
InitializeComponent();
@@ -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,100 @@
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; }
[ProtoMember(15)]
[DIYGridColumnID(nameof(Columns.Symbol))]
public string[] symbol { 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);
pb.add_discrete_values(fs.symbol, ft => ft.security?.Symbol ?? string.Empty);
// 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,111 @@
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 symbol => new GridColumnSettings(col_id(Columns.Symbol), DefaultColumnHeaders.Symbol, width: DefaultColumnWidths.Symbol);
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 symbol;
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);
}
}
}
}
@@ -327,7 +327,15 @@ namespace mbts.spark {
public enum BadgeStyle { Normal, Alternate }
public enum NetChangeTerms { None, Dollars, Ticks }
// TODO: rename to PriceDisplayFormat (today is merge monday, since this isn't a functional change, it is not worth the potential merge headaches right now)
public enum NetChangeTerms {
[Description("Standard Ticks")]
None,
[Description("Net Change Dollars")]
Dollars,
[Description("Net Change Ticks")]
Ticks
}
[ProtoMember(1)]
public double row_height { get; set; }
@@ -23,6 +23,7 @@ namespace mbts.spark {
readonly Property<LadderSettings.BadgeStyle> _badge_style;
readonly Property<uint> _alternate_order_size;
readonly DelegateProperty<double> _row_height;
LadderSettings.NetChangeTerms _price_format;
Func<double> _get_order_entry_panel_width, _get_zoom_factor;
LadderSettings _current;
LadderColumnSettingsVM
@@ -30,8 +31,8 @@ namespace mbts.spark {
_bid = new LadderColumnSettingsVM("Bid"),
_price = new LadderColumnSettingsVM("Price"),
_ask = new LadderColumnSettingsVM("Ask"),
_last = new LadderColumnSettingsVM("Last", true),
_piq = new LadderColumnSettingsVM("PIQ", true);
_last = new LadderColumnSettingsVM("Last", LadderSettingsView.LastColumnTemplateKey, true),
_piq = new LadderColumnSettingsVM("PIQ", can_toggle_show: true);
VolumeColumnVM _volume = new VolumeColumnVM();
LadderMarketBreakLineVM
_bid_mkt_line = new LadderMarketBreakLineVM("Bid"),
@@ -59,10 +60,6 @@ namespace mbts.spark {
_cancel_buys_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Buys"),
_cancel_sells_visibility = new OrderEntryPanelItemVisibilityVM("Cancel Sells"),
_cancel_all_visibility = new OrderEntryPanelItemVisibilityVM("Cancel All");
readonly Property<bool>
_show_in_net_change,
_show_in_net_change_dollars,
_show_in_net_change_ticks;
public LadderSettingsVM(OrdersModel model, bool is_synthetic, Func<double> get_left_panel_width, Func<double> get_zoom_factor) {
this.is_synthetic = is_synthetic;
@@ -74,10 +71,6 @@ namespace mbts.spark {
_alternate_order_size = backing<uint>(nameof(alternate_order_size));
_get_order_entry_panel_width = get_left_panel_width;
_get_zoom_factor = get_zoom_factor;
_show_in_net_change = backing<bool>(nameof(show_in_net_change));
_show_in_net_change_dollars = backing<bool>(nameof(show_in_net_change_dollars));
_show_in_net_change_ticks = backing<bool>(nameof(show_in_net_change_ticks));
// be sure to initialize backings before this measure_row_height call
_row_height = backing(nameof(row_height), () => LadderRender.measure_row_height(get_result()));
_orders.font_size_changed += on_font_size_changed;
@@ -110,6 +103,8 @@ namespace mbts.spark {
}
public MarketSizeDisplayVM size_display { get; } = new MarketSizeDisplayVM();
public LadderSettings.NetChangeTerms price_format { get => _price_format; set => Set(ref _price_format, value); }
public IEnumerable<LadderSettings.NetChangeTerms> price_format_options => EnumHelper.get_values<LadderSettings.NetChangeTerms>();
public FavoriteAlgosVM favorite_algos_vm { get; }
public double row_height => _row_height.Value;
@@ -175,32 +170,6 @@ namespace mbts.spark {
}
}
public bool show_in_net_change {
get => _show_in_net_change.Value;
set {
// when unchecking, clear out dollars and ticks
if (!value) {
show_in_net_change_dollars = false;
show_in_net_change_ticks = false;
}
// when checking, by defalt check dollars
if (value) {
show_in_net_change_dollars = true;
}
_show_in_net_change.Value = value;
}
}
public bool show_in_net_change_dollars {
get => _show_in_net_change_dollars.Value;
set => _show_in_net_change_dollars.Value = value;
}
public bool show_in_net_change_ticks {
get => _show_in_net_change_ticks.Value;
set => _show_in_net_change_ticks.Value = value;
}
public string key => GridSettingsTemplateKeys.Ladder;
public void load(LadderSettings settings) {
@@ -241,13 +210,11 @@ namespace mbts.spark {
_cancel_buys_visibility.show = settings.cancel_buys_visible;
_cancel_sells_visibility.show = settings.cancel_sells_visible;
_cancel_all_visibility.show = settings.cancel_all_visible;
price_format = settings.net_change;
// do not read the order entry panel width here from settings
// because it could have changed from the UI
// there might be a better way of doing this, like how
// does the widths of the ladder grid column pull this off?
show_in_net_change = settings.net_change != LadderSettings.NetChangeTerms.None;
show_in_net_change_dollars = settings.net_change == LadderSettings.NetChangeTerms.Dollars;
show_in_net_change_ticks = settings.net_change == LadderSettings.NetChangeTerms.Ticks;
}
public LadderSettings get_result() {
@@ -291,19 +258,8 @@ namespace mbts.spark {
clone.order_entry_panel_initial_width = _get_order_entry_panel_width();
clone.zoom_slider_visible = _zoom_slider_visbility.show;
clone.zoom_factor = _get_zoom_factor();
clone.net_change = _create_net_change_terms(show_in_net_change_dollars, show_in_net_change_ticks);
clone.net_change = price_format;
return clone;
LadderSettings.NetChangeTerms _create_net_change_terms(bool dollars, bool ticks) {
if (dollars) {
return LadderSettings.NetChangeTerms.Dollars;
}
if (ticks) {
return LadderSettings.NetChangeTerms.Ticks;
}
return LadderSettings.NetChangeTerms.None;
}
}
public void Dispose() => favorite_algos_vm.Dispose();
@@ -323,11 +279,12 @@ namespace mbts.spark {
readonly DelegateProperty<FontWeight> _font_weight;
readonly DelegateProperty<string> _font_display_name;
readonly Property<bool> _show;
readonly bool _can_toggle_visibility;
readonly string _template_key;
public LadderColumnSettingsVM(string name, bool can_toggle_visibility = false) {
public LadderColumnSettingsVM(string name, string template_key = null, bool can_toggle_show = false) {
this.name = name;
_can_toggle_visibility = can_toggle_visibility;
_template_key = template_key;
this.can_toggle_show = can_toggle_show;
foreground_palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
background_palettes = new ObservableCollection<ColorPaletteVM>(ColorPalette.all_presets().Select(p => new ColorPaletteVM(p)));
_show = backing(nameof(show), true);
@@ -340,8 +297,14 @@ namespace mbts.spark {
_font_display_name = backing(nameof(font_display_name),
() => TextUtils.display_name(font_family, font_weight, font_size), nameof(font_family), nameof(font_weight), nameof(font_size));
_font_size.Changed += (oldval, newval) => font_size_changed?.Invoke(oldval, newval);
toggle_show_details_cmd = new DelegateCommand(() => {
show_details = !show_details;
RaisePropertyChanged(nameof(show_details));
});
}
public event Action<double, double> font_size_changed;
public string name { get; }
public Typeface typeface { get => _typeface.Value; set => _typeface.Value = value; }
public FontFamily font_family => _font_family.Value;
@@ -350,14 +313,17 @@ namespace mbts.spark {
public double font_size { get => _font_size.Value; set => _font_size.Value = value; }
public uint width { get; set; }
public bool show { get => _show.Value; set => _show.Value = value; }
public bool can_toggle_show { get; }
public bool show_details { get; private set; }
public Color foreground { get => _foreground.Value; set => _foreground.Value = value; }
public Color background { get => _background.Value; set => _background.Value = value; }
public ObservableCollection<ColorPaletteVM> foreground_palettes { get; }
public ObservableCollection<ColorPaletteVM> background_palettes { get; }
public event Action<double, double> font_size_changed;
public virtual string sample_text => "999-56789.01234";
string ITemplateSelectorKey.key => _can_toggle_visibility ? ui.spark.LadderSettingsView.ColumnVisibilityToggleTemplateKey : null;
public ICommand toggle_show_details_cmd { get; }
string ITemplateSelectorKey.key => _template_key;
public void assign_from(LadderColumnSettings settings) {
typeface = new Typeface(
@@ -395,7 +361,7 @@ namespace mbts.spark {
public Brush badge_border { get; } = new SolidColorBrush(BTSColors.MarketData.ask);
public Brush badge_background { get; } = Brushes.White;
string ITemplateSelectorKey.key => ui.spark.LadderSettingsView.OrdersColumnSampleTemplateKey;
string ITemplateSelectorKey.key => ui.spark.LadderSettingsView.OrdersColumnTemplateKey;
}
public class LadderMarketBreakLineVM : ViewModelBase {
@@ -463,7 +429,7 @@ namespace mbts.spark {
}
}
public LadderColumnSettingsVM general { get; } = new LadderColumnSettingsVM("Volume", true);
public LadderColumnSettingsVM general { get; } = new LadderColumnSettingsVM("Volume", LadderSettingsView.VolumeColumnTemplateKey, true);
public VolumeAtPriceAnimationVM buy { get; }
public VolumeAtPriceAnimationVM sell { get; }
public VolumeAtPriceAnimationVM passive { get; }
@@ -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) {
@@ -337,8 +337,15 @@ namespace mbts.spark {
if (e.ChangedButton == MouseButton.Middle) {
if (bs.HasValue) {
_send_parameterized_order(point, bs.Value);
return;
} 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();
}
return;
}
}
var pbs = (price, bs, e.ChangedButton);
File diff suppressed because it is too large. Load diff
@@ -10,10 +10,15 @@ namespace mbts.ui.spark {
/// </summary>
partial class LadderSettingsView : UserControl {
public const string
OrdersColumnSampleTemplateKey = nameof(OrdersColumnSampleTemplateKey),
ColumnVisibilityToggleTemplateKey = nameof(ColumnVisibilityToggleTemplateKey),
OrdersColumnTemplateKey = nameof(OrdersColumnTemplateKey),
VolumeColumnTemplateKey = nameof(VolumeColumnTemplateKey),
LastColumnTemplateKey = nameof(LastColumnTemplateKey),
FavoriteAlgoAddButtonTemplateKey = nameof(FavoriteAlgoAddButtonTemplateKey);
public static readonly IMultiValueConverter LadderColumnToggleShowToolTipConverter =
ValueConverter.create<bool, bool, string, string>((can_toggle_show, show, col_name) =>
can_toggle_show ? $"{(show ? "Hide" : "Show")} {col_name}" : $"{col_name} cannot be {(show ? "hidden" : "shown")}.");
public LadderSettingsView() =>
InitializeComponent();
@@ -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)));
}
}
@@ -77,7 +77,7 @@
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridSettingsTemplateKeys.Ladder}">
<DataTemplate>
<usp:LadderSettingsView Margin="0,4,0,0"/>
<usp:LadderSettingsView Margin="0,-16,0,0"/>
</DataTemplate>
</u:TemplateSelectorKeyValue>
<u:TemplateSelectorKeyValue Key="{x:Static usp:GridSettingsTemplateKeys.OrderBook}">
@@ -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);
}
@@ -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;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)
+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
+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;
}
+10 -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>(
@@ -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);
+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 [][] )